django-drfkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-drfkit
3
+ Version: 0.1.0
4
+ Summary: A CLI tool for production-ready DRF scaffolding
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer
8
+ Requires-Dist: jinja2
9
+
10
+ # DRFKit
11
+
12
+ A powerful CLI tool for scaffolding Django REST Framework projects with a clean, production-ready architecture.
13
+
14
+
15
+
16
+ ## Features
17
+
18
+ * **Instant Scaffolding:** Creates a scalable folder structure with `apps/` and `config/` directories.
19
+ * **Security First:** Auto-generates a cryptographically secure `SECRET_KEY` during creation.
20
+ * **Split Settings:** Pre-configured `base.py`, `dev.py`, and `prod.py` for better environment management.
21
+ * **DRF Ready:** Django REST Framework is installed and added to installed apps by default.
22
+ * **Clean Templates:** Generates `.env` and requirements files automatically.
23
+
24
+ ## Installation
25
+
26
+ Install the package via pip:
27
+
28
+ ```bash
29
+ pip install drfkit
30
+ ```
31
+
32
+ *(Requires Python 3.8+)*
33
+
34
+ ## Quick Start
35
+
36
+ ### 1. Create a New Project
37
+
38
+ Run the following command to scaffold your project:
39
+
40
+ ```bash
41
+ drfkit new project myproject
42
+ ```
43
+
44
+ This creates the following structure:
45
+
46
+ ```text
47
+ myproject/
48
+ ├── config/
49
+ │ ├── settings/
50
+ │ │ ├── base.py
51
+ │ │ ├── dev.py
52
+ │ │ └── prod.py
53
+ │ ├── urls.py
54
+ │ ├── wsgi.py
55
+ │ └── asgi.py
56
+ ├── apps/ # Your apps go here
57
+ ├── requirements/
58
+ │ └── base.txt
59
+ ├── .env # Pre-filled with your secret key
60
+ └── manage.py
61
+ ```
62
+
63
+ ### 2. Run the Server
64
+
65
+ Navigate to your project and start the development server:
66
+
67
+ ```bash
68
+ cd myproject
69
+ python -m venv venv
70
+ source venv/bin/activate
71
+ pip install -r requirements/base.txt
72
+ python manage.py runserver
73
+ ```
74
+
75
+ No extra configuration needed—it works out of the box!
76
+
77
+ ## Usage
78
+
79
+ ### Creating an App
80
+
81
+ DRFKit encourages a modular structure by placing apps inside the `apps/` directory.
82
+
83
+ ```bash
84
+ drfkit new app myapp
85
+ ```
86
+
87
+ This generates:
88
+
89
+ ```text
90
+ apps/myapp/
91
+ ├── __init__.py
92
+ ├── admin.py
93
+ ├── apps.py
94
+ ├── models.py
95
+ ├── serializers.py # Ready for DRF
96
+ ├── tests.py
97
+ ├── urls.py
98
+ └── views.py
99
+ ```
100
+
101
+ > **Note:** Remember to add `'apps.myapp'` to `INSTALLED_APPS` in `config/settings/base.py`.
102
+
103
+ ## Configuration
104
+
105
+ ### Environment Settings
106
+
107
+ * **Development:** By default, `manage.py` uses `config.settings.dev`. This is perfect for local coding.
108
+ * **Production:** To switch to production settings, set the environment variable:
109
+ ```bash
110
+ export DJANGO_SETTINGS_MODULE=config.settings.prod
111
+ ```
112
+
113
+ ### Secret Key
114
+
115
+ A unique `SECRET_KEY` is generated and placed inside `settings/base.py` automatically when you create a project. This ensures your local development is secure from the start.
116
+
117
+ ## Contributing
118
+
119
+ Contributions are welcome! If you have ideas for improvement or find a bug, feel free to open an issue or submit a pull request.
120
+
121
+ ## 👤 Author
122
+
123
+ **[Your Name]**
124
+ * GitHub: [[Oviyan](https://github.com/Oviyan007/)]
125
+ * Linkedin: [[Oviyan](https://www.linkedin.com/in/oviyan-s/)]
@@ -0,0 +1,32 @@
1
+ drfkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ drfkit/cli.py,sha256=INYFxTanTapYSwp-41yhZkpwYNp1K0i65RClXYA5_kM,284
3
+ drfkit/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ drfkit/commands/app.py,sha256=5nqTrEo584hvxm3y2ThnkF1xWCP9TLF0IKL0sBU6s2w,254
5
+ drfkit/commands/new.py,sha256=CnZewHBCXsZb-oAVGCF8vXeSP66bc0Y2p2FzCtzekCc,3942
6
+ drfkit/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ drfkit/core/renderer.py,sha256=s_7I1dkIPXz8nt8lbcKBgxy6QuPEp37-K5LE85Uep9k,648
8
+ drfkit/templates/app/__init__.py.j2,sha256=rQnHgFRsn-6y3yS_AXU5885xoQqB0TepUqCHnx4q9-g,79
9
+ drfkit/templates/app/admin.py.j2,sha256=yZDOS7tlghDMcAu6A4UJoLX-fRd04iV1hkmTjAj0yZI,240
10
+ drfkit/templates/app/apps.py.j2,sha256=xs3u_vhqyKcd5xU9u39JaWmL4tIzHd4J-NVCFDJXO0Y,178
11
+ drfkit/templates/app/models.py.j2,sha256=hPlMx-cG1oXfyB4ubH-nJOP_fp0wvtFN8_z8Vj8M8gw,58
12
+ drfkit/templates/app/serializers.py.j2,sha256=hQ4xI__4BqlrvcO3Pj2JQLd8oDcRD35WK3RRIIqbqYw,70
13
+ drfkit/templates/app/tests.py.j2,sha256=Fr0r-qWlGT2-ZiB69i1rFX1V-IPCPzLzRBA7z5u3xQY,61
14
+ drfkit/templates/app/urls.py.j2,sha256=BHvQjr0g_a6dHCpDprDlHSj_NZGS8_vPJjsbtAxdsj0,304
15
+ drfkit/templates/app/views.py.j2,sha256=Yn-dwHFEkxaRK-bVuKSsKeH9_PtVCloHiYxt4ycNuE0,326
16
+ drfkit/templates/project/asgi.py.j2,sha256=eq8XfXfI0ljsnU2tqiqGMLNzlw9HBeGpzS34cdfCb6s,175
17
+ drfkit/templates/project/env.j2,sha256=uLJjpnxs_1eTrE9882qAm24YjEPyGd2n2enJe25snaI,87
18
+ drfkit/templates/project/manage.py.j2,sha256=0AFA81IVWAUYaHex6OlWymjj1oZiFLXz7_46vst-9t4,261
19
+ drfkit/templates/project/urls.py.j2,sha256=VXKHD07dxVfNc6ahE8-hvwItsWeV22wpNJnr1gjnT2c,210
20
+ drfkit/templates/project/wsgi.py.j2,sha256=c9Vy6vYmL657Jso-qvd6TfJkXJOX4juIuhydt9w-vbc,175
21
+ drfkit/templates/project/requirements/base.txt.j2,sha256=sNq-iGbbaW-RnnWLDUtt17xZdcGmxvJLM2vDNT1t1-Q,53
22
+ drfkit/templates/project/settings/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ drfkit/templates/project/settings/base.py.j2,sha256=perECpjwyI_okFCeHmGuJsZaoYB1KY8hItqxhTJTT6w,1985
24
+ drfkit/templates/project/settings/dev.py.j2,sha256=wVMV6nDmlWuQaKDoQRxgiEdOKrCtLpPUtsmYk1baDuI,55
25
+ drfkit/templates/project/settings/prod.py.j2,sha256=7th7ZgG_NV2NuZKCGYu9iZWjZH2h1I82twEXnxTfDgY,56
26
+ drfkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ drfkit/utils/template.py,sha256=Gz_yL72gu4ofec1H2yS8AQ28si1UobHndInKdUEBxwU,483
28
+ django_drfkit-0.1.0.dist-info/METADATA,sha256=14IRMz-Tfc-eOWmkoKe2pqWXzlOJLMZfTn6GLI1Vrjs,3190
29
+ django_drfkit-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
30
+ django_drfkit-0.1.0.dist-info/entry_points.txt,sha256=F0VpvMcgitF_-AXaMsiN2fq0JTDc51WlC__bmipV8TA,42
31
+ django_drfkit-0.1.0.dist-info/top_level.txt,sha256=KC8RxM3aOEmOemRFr04GNggBa8F-uMBfngJJgfIsqN8,7
32
+ django_drfkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ drfkit = drfkit.cli:cli
@@ -0,0 +1 @@
1
+ drfkit
drfkit/__init__.py ADDED
File without changes
drfkit/cli.py ADDED
@@ -0,0 +1,10 @@
1
+ import typer
2
+ from drfkit.commands import new
3
+
4
+ cli = typer.Typer(name="drfkit", help="A CLI tool for scaffolding DRF projects")
5
+
6
+ # Register the 'new' group which contains 'project' and 'app' commands
7
+ cli.add_typer(new.app_cmd, name="new")
8
+
9
+ if __name__ == "__main__":
10
+ cli()
File without changes
drfkit/commands/app.py ADDED
@@ -0,0 +1,7 @@
1
+ import typer
2
+
3
+ app = typer.Typer()
4
+
5
+ @app.command()
6
+ def create():
7
+ print("Create app")
drfkit/commands/new.py ADDED
@@ -0,0 +1,102 @@
1
+ import typer
2
+ import secrets
3
+ import string
4
+ from pathlib import Path
5
+ from drfkit.utils.template import render
6
+
7
+ app_cmd= typer.Typer(help="Create new DRF projects or apps")
8
+
9
+ def generate_secret_key(length=50):
10
+ """Generate a cryptographically secure random string."""
11
+ chars = string.ascii_letters + string.digits + string.punctuation
12
+ # Ensure we don't use characters that might break .env files or Python strings easily (optional)
13
+ # For simplicity, we just pick from the standard set:
14
+ return ''.join(secrets.choice(chars) for _ in range(length))
15
+ @app_cmd.command()
16
+ def project(name: str):
17
+ """
18
+ Scaffold a new DRF project with split settings and clean structure.
19
+ """
20
+ root = Path(name)
21
+
22
+ if root.exists():
23
+ typer.secho(f"Error: Directory '{name}' already exists.", fg=typer.colors.RED)
24
+ raise typer.Exit(1)
25
+ # Generate the key here
26
+ secret_key = generate_secret_key()
27
+ context = {"project_name": name,"secret_key": secret_key}
28
+
29
+ # Define folder structure
30
+ dirs = [
31
+ root / "config" / "settings",
32
+ root / "apps",
33
+ root / "scripts",
34
+ root / "requirements"
35
+ ]
36
+
37
+ for d in dirs:
38
+ d.mkdir(parents=True, exist_ok=True)
39
+
40
+ # Create __init__.py for python packages
41
+ (root / "apps").joinpath("__init__.py").touch()
42
+ (root / "config").joinpath("__init__.py").touch()
43
+
44
+ # Define files to render
45
+ files = {
46
+ "project/manage.py.j2": root / "manage.py",
47
+ "project/urls.py.j2": root / "config" / "urls.py",
48
+ "project/wsgi.py.j2": root / "config" / "wsgi.py",
49
+ "project/asgi.py.j2": root / "config" / "asgi.py",
50
+ "project/settings/base.py.j2": root / "config" / "settings" / "base.py",
51
+ "project/settings/dev.py.j2": root / "config" / "settings" / "dev.py",
52
+ "project/settings/prod.py.j2": root / "config" / "settings" / "prod.py",
53
+ "project/settings/__init__.py.j2": root / "config" / "settings" / "__init__.py",
54
+ "project/env.j2": root / ".env",
55
+ "project/requirements/base.txt.j2": root / "requirements" / "base.txt",
56
+ }
57
+
58
+ with typer.progressbar(files.items(), label="Generating files") as progress_bar:
59
+ for template, dest in progress_bar:
60
+ render(template, dest, context)
61
+
62
+ typer.secho(f"\n✔ Project '{name}' created successfully!", fg=typer.colors.GREEN)
63
+ typer.echo(f" cd {name}")
64
+ typer.echo(f" python -m venv venv && venv/bin/activate")
65
+ typer.echo(f" pip install -r requirements/base.txt")
66
+
67
+ @app_cmd.command()
68
+ def app(name: str):
69
+ """
70
+ Scaffold a new DRF app inside the 'apps/' directory.
71
+ """
72
+ # We assume this command is run inside a drfkit-generated project root
73
+ app_dir = Path("apps") / name
74
+
75
+ if app_dir.exists():
76
+ typer.secho(f"Error: App '{name}' already exists in apps/.", fg=typer.colors.RED)
77
+ raise typer.Exit(1)
78
+
79
+ if not Path("apps").exists():
80
+ typer.secho("Error: 'apps' directory not found. Are you in a drfkit project root?", fg=typer.colors.RED)
81
+ raise typer.Exit(1)
82
+
83
+ app_dir.mkdir(parents=True, exist_ok=True)
84
+
85
+ context = {"app_name": name}
86
+
87
+ files = {
88
+ "app/models.py.j2": app_dir / "models.py",
89
+ "app/serializers.py.j2": app_dir / "serializers.py",
90
+ "app/views.py.j2": app_dir / "views.py",
91
+ "app/urls.py.j2": app_dir / "urls.py",
92
+ "app/tests.py.j2": app_dir / "tests.py",
93
+ "app/admin.py.j2": app_dir / "admin.py",
94
+ "app/apps.py.j2": app_dir / "apps.py",
95
+ "app/__init__.py.j2": app_dir / "__init__.py",
96
+ }
97
+
98
+ for template, dest in files.items():
99
+ render(template, dest, context)
100
+
101
+ typer.secho(f"✔ App '{name}' created at apps/{name}", fg=typer.colors.GREEN)
102
+ typer.echo(f" Don't forget to add 'apps.{name}' to INSTALLED_APPS in config/settings/base.py")
File without changes
@@ -0,0 +1,21 @@
1
+ import os
2
+ from jinja2 import Environment, FileSystemLoader
3
+
4
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
+ TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")
6
+
7
+ env = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
8
+
9
+
10
+ def render_project(project_name: str):
11
+ context = {"project_name": project_name}
12
+
13
+ os.makedirs(project_name, exist_ok=True)
14
+
15
+ template = env.get_template("project/manage.py.j2")
16
+ content = template.render(context)
17
+
18
+ with open(os.path.join(project_name, "manage.py"), "w") as f:
19
+ f.write(content)
20
+
21
+ print(f"✔ Project '{project_name}' created")
@@ -0,0 +1 @@
1
+ default_app_config = 'apps.{{ app_name }}.apps.{{ app_name|capitalize }}Config'
@@ -0,0 +1,6 @@
1
+ from django.contrib import admin
2
+ from .models import {{ app_name|capitalize }}
3
+
4
+ @admin.register({{ app_name|capitalize }})
5
+ class {{ app_name|capitalize }}Admin(admin.ModelAdmin):
6
+ list_display = ('id',) # Add your model fields here
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+ class {{ app_name|capitalize }}Config(AppConfig):
4
+ default_auto_field = 'django.db.models.BigAutoField'
5
+ name = 'apps.{{ app_name }}'
@@ -0,0 +1,3 @@
1
+ from django.db import models
2
+
3
+ # Create your models here.
@@ -0,0 +1,3 @@
1
+ from rest_framework import serializers
2
+
3
+ # Add your serializers here.
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,10 @@
1
+ from django.urls import path, include
2
+ from rest_framework.routers import DefaultRouter
3
+ from .views import {{ app_name|capitalize }}ViewSet
4
+
5
+ router = DefaultRouter()
6
+ router.register(r'{{ app_name|lower }}s', {{ app_name|capitalize }}ViewSet)
7
+
8
+ urlpatterns = [
9
+ path('', include(router.urls)),
10
+ ]
@@ -0,0 +1,7 @@
1
+ from rest_framework import viewsets
2
+ from .models import {{ app_name|capitalize }}
3
+ from .serializers import {{ app_name|capitalize }}Serializer
4
+
5
+ class {{ app_name|capitalize }}ViewSet(viewsets.ModelViewSet):
6
+ queryset = {{ app_name|capitalize }}.objects.all()
7
+ serializer_class = {{ app_name|capitalize }}Serializer
@@ -0,0 +1,6 @@
1
+ import os
2
+ from django.core.asgi import get_asgi_application
3
+
4
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod')
5
+
6
+ application = get_asgi_application()
@@ -0,0 +1,3 @@
1
+ DJANGO_SETTINGS_MODULE=config.settings.dev
2
+ SECRET_KEY=your-secret-key-here
3
+ DEBUG=True
@@ -0,0 +1,10 @@
1
+ import os
2
+ import sys
3
+
4
+ def main():
5
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev')
6
+ from django.core.management import execute_from_command_line
7
+ execute_from_command_line(sys.argv)
8
+
9
+ if __name__ == '__main__':
10
+ main()
@@ -0,0 +1,3 @@
1
+ django>=4.0
2
+ djangorestframework>=3.14
3
+ python-dotenv
File without changes
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+
3
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent
4
+
5
+ SECRET_KEY = '{{secret_key}}'
6
+
7
+ INSTALLED_APPS = [
8
+ 'django.contrib.admin',
9
+ 'django.contrib.auth',
10
+ 'django.contrib.contenttypes',
11
+ 'django.contrib.sessions',
12
+ 'django.contrib.messages',
13
+ 'django.contrib.staticfiles',
14
+
15
+ # Third party
16
+ 'rest_framework',
17
+
18
+ # Local apps
19
+ # Add your apps here, e.g., 'apps.users'
20
+ ]
21
+
22
+ MIDDLEWARE = [
23
+ 'django.middleware.security.SecurityMiddleware',
24
+ 'django.contrib.sessions.middleware.SessionMiddleware',
25
+ 'django.middleware.common.CommonMiddleware',
26
+ 'django.middleware.csrf.CsrfViewMiddleware',
27
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
28
+ 'django.contrib.messages.middleware.MessageMiddleware',
29
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
30
+ ]
31
+
32
+ ROOT_URLCONF = 'config.urls'
33
+
34
+ TEMPLATES = [
35
+ {
36
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
37
+ 'DIRS': [],
38
+ 'APP_DIRS': True,
39
+ 'OPTIONS': {
40
+ 'context_processors': [
41
+ 'django.template.context_processors.debug',
42
+ 'django.template.context_processors.request',
43
+ 'django.contrib.auth.context_processors.auth',
44
+ 'django.contrib.messages.context_processors.messages',
45
+ ],
46
+ },
47
+ },
48
+ ]
49
+
50
+ WSGI_APPLICATION = 'config.wsgi.application'
51
+
52
+ AUTH_PASSWORD_VALIDATORS = [
53
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
54
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
55
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
56
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
57
+ ]
58
+
59
+ LANGUAGE_CODE = 'en-us'
60
+ TIME_ZONE = 'UTC'
61
+ USE_I18N = True
62
+ USE_TZ = True
63
+
64
+ STATIC_URL = 'static/'
65
+
66
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@@ -0,0 +1,4 @@
1
+ from .base import *
2
+
3
+ DEBUG = True
4
+ ALLOWED_HOSTS = []
@@ -0,0 +1,4 @@
1
+ from .base import *
2
+
3
+ DEBUG = False
4
+ ALLOWED_HOSTS = []
@@ -0,0 +1,8 @@
1
+ from django.contrib import admin
2
+ from django.urls import path, include
3
+
4
+ urlpatterns = [
5
+ path('admin/', admin.site.urls),
6
+ # Add your API urls here
7
+ # path('api/', include('apps.users.urls')),
8
+ ]
@@ -0,0 +1,6 @@
1
+ import os
2
+ from django.core.wsgi import get_wsgi_application
3
+
4
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod')
5
+
6
+ application = get_wsgi_application()
File without changes
@@ -0,0 +1,14 @@
1
+ from jinja2 import Environment, FileSystemLoader
2
+ from pathlib import Path
3
+
4
+ BASE_DIR = Path(__file__).resolve().parent.parent
5
+ TEMPLATE_DIR = BASE_DIR / "templates"
6
+
7
+ env = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
8
+
9
+ def render(template_path: str, destination: Path, context: dict):
10
+ template = env.get_template(template_path)
11
+ content = template.render(**context)
12
+
13
+ destination.parent.mkdir(parents=True, exist_ok=True)
14
+ destination.write_text(content)