Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
epicserve committed Oct 12, 2024
1 parent 23457ee commit 5a2363c
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Test project directory
example_project/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13.0
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# dj-beat-drop

`dj-beat-drop` is a CLI utility designed to simplify the creation of new Django projects by organizing all configuration
files into a `config` directory, instead of using Django's default naming convention. This approach avoids the
antipattern of naming the config directory the same as the project name.

## Project Status

This project is in the very early stages of development, focusing on defining the API. Future releases will include
additional features and improvements.

## Features

- **Simplified Project Structure**: All configuration files are placed in a `config` directory.
- **Latest Django Version**: Currently, the utility uses the latest release of Django.

## Future Goals

- **Specify Django Version**: Add an option to specify the Django version when creating a new project.
- **Polish**: Add lots of polish inspired by `laravel` CLI. Possibly refactor to use Rust or the coolest CLI library.
- **Additional Subcommands**: Introduce more subcommands to enhance functionality.
- **Official Django Project**: Aim to have this utility included as an official Django project, potentially renaming the
command to `django` for easier usage (e.g., `django new`).
- **`pyproject.toml` Integration**: Set up new Django projects with a `pyproject.toml` file that can be used by `uv` to
run the project.

## Installation

```sh
pip install dj-beat-drop
```

## Usage

```sh
beatdrop new example_project
```
2 changes: 2 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
format:
ruff format
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "dj-beat-drop"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"django>=5.1a0",
"requests>=2.32.3",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = [
"ruff>=0.6.9",
]

[project.scripts]
beatdrop = "dj_beat_drop.main_cli:main"
Empty file added src/dj_beat_drop/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions src/dj_beat_drop/main_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import argparse

from dj_beat_drop.new import handle_new


def main():
parser = argparse.ArgumentParser(description="DJ Beat Drop")
subparsers = parser.add_subparsers(dest="command")

# Add 'new' subcommand
new_parser = subparsers.add_parser("new", help="Create a new Django project.")
new_parser.add_argument(
"name",
type=str,
help="Project name (e.g. 'example_project' or 'example-project').",
)
new_parser.add_argument(
"--overwrite",
action="store_true",
help="Overwrite the project directory if it already exists.",
)

args = parser.parse_args()

if args.command == "new":
handle_new(args.name, args.overwrite)
else:
parser.print_help()


if __name__ == "__main__":
main()
101 changes: 101 additions & 0 deletions src/dj_beat_drop/new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import os
import re
import secrets
import shutil
import zipfile
from io import BytesIO

import requests


def get_latest_django_version():
response = requests.get("https://pypi.org/pypi/Django/json")
if response.status_code == 200:
data = response.json()
return data["info"]["version"]
else:
raise Exception("Failed to fetch the latest Django version")


def get_version(version):
rtn = ".".join(version)
mapping = {"alpha": "a", "beta": "b", "rc": "rc"}
for key in mapping:
if key in version:
key_index = version.index(key)
first_part = ".".join(version[: key_index - 1])
return f"{first_part}{mapping[key]}{version[key_index + 1]}"
print(rtn)
return rtn


def get_secret_key():
"""
Return a 50 character random string usable as a SECRET_KEY setting value.
"""
chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"
return "".join(secrets.choice(chars) for i in range(50))


def handle_new(name, overwrite):
django_version = get_latest_django_version()
docs_version = ".".join(django_version.split(".")[0:2])
template_url = (
f"https://github.com/django/django/archive/refs/tags/{django_version}.zip"
)
template_dir = (
f"/tmp/django_template/django-{django_version}/django/conf/project_template"
)
project_dir = os.path.join(os.getcwd(), name)

if os.path.exists(template_dir):
shutil.rmtree(template_dir)

if os.path.exists(project_dir):
if overwrite is False:
overwrite_response = input(
f"The directory '{name}' already exists. Do you want to overwrite it? (yes/no): "
)
if (
overwrite_response.lower() != "yes"
and overwrite_response.lower() != "y"
):
print("Operation cancelled.")
return
shutil.rmtree(project_dir)

response = requests.get(template_url)
if response.status_code == 200:
with zipfile.ZipFile(BytesIO(response.content)) as zip_ref:
zip_ref.extractall("/tmp/django_template")

config_dir = os.path.join(template_dir, "config")
os.rename(os.path.join(template_dir, "project_name"), config_dir)
shutil.copytree(template_dir, project_dir)

# Rename .py-tpl files to .py
for root, _, files in os.walk(project_dir):
for file in files:
if file.endswith(".py-tpl"):
old_file = os.path.join(root, file)
new_file = os.path.join(root, file[:-4])
os.rename(old_file, new_file)

# Replace variables with values
for root, _, files in os.walk(project_dir):
for file in files:
file_path = os.path.join(root, file)
with open(file_path, "r") as f:
content = f.read()
content = content.replace("{{ project_name }}", "config")
content = content.replace("{{ django_version }}", django_version)
content = content.replace("{{ docs_version }}", docs_version)
content = content.replace("{{ secret_key }}", get_secret_key())
with open(file_path, "w") as f:
f.write(content)

shutil.rmtree("/tmp/django_template")

print(f"Created a new Django project with name: {name}")
else:
print("Failed to download the Django template.")
Empty file added src/dj_beat_drop/py.typed
Empty file.
Loading

0 comments on commit 5a2363c

Please sign in to comment.