feat(djinit/templates): migrate templates to double-bracket placeholder syntax
New Features - Adopt double-bracket placeholders (`[[ var ]]`) and rename templates to `.py-tpl`/.`md-tpl` extensions. - Introduce new `runtime_txt-tpl` template. Enhancements - Update `api_readme`, `models`, `apps`, `routes`, `pyproject`, `readme`, and `requirements` templates with new syntax and naming. - Align placeholder naming with a unified convention. Bug Fixes - None.
This commit is contained in:
65 files changed
+378
-292
No files matched your search
File renamed without changes.
+3
-3
@@ -24,7 +24,7 @@ If you have a `User` model in `models/user.py`, create:
|
||||
### 1. `api/user/serializers.py`
|
||||
```python
|
||||
from rest_framework import serializers
|
||||
from {{ module_name }}.models.user import User
|
||||
from [[ module_name ]].models.user import User
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
@@ -35,7 +35,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
### 2. `api/user/views.py`
|
||||
```python
|
||||
from rest_framework import viewsets
|
||||
from {{ module_name }}.models.user import User
|
||||
from [[ module_name ]].models.user import User
|
||||
from .serializers import UserSerializer
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
@@ -62,7 +62,7 @@ urlpatterns = [
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('user/', include('{{ module_name }}.api.user.urls')),
|
||||
path('user/', include('[[ module_name ]].api.user.urls')),
|
||||
# Add more API endpoints here
|
||||
]
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class {{ app_name|title }}Config(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = '{{ app_name }}'
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class [[ app_name|title ]]Config(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = '[[ app_name ]]'
|
||||
@@ -1,5 +1,3 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
+1
-1
@@ -69,7 +69,7 @@ Create or update `admin/__init__.py`:
|
||||
|
||||
```python
|
||||
from django.contrib import admin
|
||||
from {{ module_name }}.models import User
|
||||
from [[ module_name ]].models import User
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
@@ -1,18 +0,0 @@
|
||||
"""
|
||||
generated with djinit
|
||||
|
||||
API routes for {{ app_name }} app.
|
||||
"""
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from . import views
|
||||
|
||||
# Create router instance
|
||||
router = DefaultRouter()
|
||||
|
||||
# Register viewsets with router
|
||||
# Example:
|
||||
# router.register(r'{{ app_name }}', views.{{ app_name|title }}ViewSet)
|
||||
|
||||
# Get URL patterns
|
||||
urlpatterns = router.urls
|
||||
@@ -0,0 +1,8 @@
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
# Register your viewsets here
|
||||
# router.register(r'endpoint', YourViewSet, basename='your-endpoint')
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -1,14 +0,0 @@
|
||||
"""
|
||||
generated with djinit
|
||||
serializers for {{ app_name }} app.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
# Add your serializers here
|
||||
# Example:
|
||||
# class {{ app_name|title }}Serializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = YourModel
|
||||
# fields = '__all__'
|
||||
@@ -0,0 +1,3 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
# Create your serializers here.
|
||||
@@ -1,5 +1,3 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,11 +0,0 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = '{{ app_name }}'
|
||||
|
||||
urlpatterns = [
|
||||
# Add your URL patterns here
|
||||
# path('', views.index, name='index'),
|
||||
]
|
||||
@@ -1,5 +1,3 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -7,5 +7,5 @@ from django.core.asgi import get_asgi_application
|
||||
env = environ.Env()
|
||||
environ.Env.read_env()
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="{{ project_name }}.settings.production"))
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="[[ project_name ]].settings.production"))
|
||||
application = get_asgi_application()
|
||||
+7
-6
@@ -29,8 +29,9 @@ THIRD_PARTY_APPS = [
|
||||
]
|
||||
|
||||
USER_DEFINED_APPS = [
|
||||
{% for app in app_names %} "{{ app }}",
|
||||
{% endfor %}]
|
||||
# @LOOP app in app_names "[[ app ]]",
|
||||
# @ENDLOOP
|
||||
]
|
||||
|
||||
BUILT_IN_APPS = [
|
||||
"django.contrib.admin",
|
||||
@@ -56,7 +57,7 @@ MIDDLEWARE = [
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "{{ project_name }}.urls"
|
||||
ROOT_URLCONF = "[[ project_name ]].urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
@@ -73,7 +74,7 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "{{ project_name }}.wsgi.application"
|
||||
WSGI_APPLICATION = "[[ project_name ]].wsgi.application"
|
||||
|
||||
|
||||
# Password validation
|
||||
@@ -133,8 +134,8 @@ REST_FRAMEWORK = {
|
||||
|
||||
# DRF Spectacular settings
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": "{{ project_name }} API",
|
||||
"DESCRIPTION": "API documentation for {{ project_name }}",
|
||||
"TITLE": "[[ project_name ]] API",
|
||||
"DESCRIPTION": "API documentation for [[ project_name ]]",
|
||||
"VERSION": "1.0.0",
|
||||
"SERVE_INCLUDE_SCHEMA": False,
|
||||
"COMPONENT_SPLIT_REQUEST": True,
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
from .base import *
|
||||
|
||||
SECRET_KEY = "{{ secret_key }}"
|
||||
SECRET_KEY = "[[ secret_key ]]"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
+6
-6
@@ -8,29 +8,29 @@ SECRET_KEY = env('SECRET_KEY')
|
||||
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])
|
||||
|
||||
# Database
|
||||
{% if use_database_url %}
|
||||
# @IF use_database_url
|
||||
# Using dj_database_url (recommended for production)
|
||||
DATABASES = {
|
||||
'default': env.db('DATABASE_URL')
|
||||
}
|
||||
{% else %}
|
||||
# @ELSE
|
||||
# Using individual database parameters
|
||||
DATABASES = {
|
||||
'default': {
|
||||
{% if database_type == 'mysql' %}
|
||||
# @IF database_type == 'mysql'
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'PORT': env('DB_PORT', default='3306'),
|
||||
{% else %}
|
||||
# @ELSE
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'PORT': env('DB_PORT', default='5432'),
|
||||
{% endif %}
|
||||
# @ENDIF
|
||||
'NAME': env('DB_NAME'),
|
||||
'USER': env('DB_USER'),
|
||||
'PASSWORD': env('DB_PASSWORD'),
|
||||
'HOST': env('DB_HOST'),
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
# @ENDIF
|
||||
|
||||
# CORS settings for production
|
||||
# Filter out empty strings from FRONTEND_URL
|
||||
@@ -0,0 +1,103 @@
|
||||
# @IF url_type == 'project'
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
# @IF include_api or include_apps
|
||||
from django.urls import include
|
||||
# @ENDIF
|
||||
# @IF include_api
|
||||
from django.conf import settings
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
TokenBlacklistView,
|
||||
)
|
||||
# @ENDIF
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
# @IF include_api
|
||||
# JWT tokens
|
||||
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"),
|
||||
# @ENDIF
|
||||
# @IF include_api and comment_out_api_url
|
||||
# path("api/", include("[[ api_module ]].urls")),
|
||||
# @ENDIF
|
||||
# @IF include_api and not comment_out_api_url
|
||||
path("api/", include("[[ api_module ]].urls")),
|
||||
# @ENDIF
|
||||
# @IF include_apps
|
||||
# App URLs
|
||||
# @LOOP app in app_names
|
||||
path("", include("[[ app ]].urls")),
|
||||
# @ENDLOOP
|
||||
# @ENDIF
|
||||
]
|
||||
# @IF include_api
|
||||
|
||||
# Don't show schema in production
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("docs/", SpectacularSwaggerView.as_view(url_name="schema")),
|
||||
]
|
||||
# @ENDIF
|
||||
# @ENDIF
|
||||
# @IF url_type == 'api_root'
|
||||
from django.urls import include, path
|
||||
|
||||
app_name = "[[ app_name ]]"
|
||||
|
||||
urlpatterns = [
|
||||
path("[[ version ]]/", include("[[ api_module ]].[[ version ]].urls")),
|
||||
]
|
||||
# @ENDIF
|
||||
# @IF url_type == 'api_version'
|
||||
from django.urls import include, path
|
||||
|
||||
app_name = "[[ app_name ]]"
|
||||
|
||||
urlpatterns = [
|
||||
# @LOOP app in app_list
|
||||
path("[[ app ]]/", include("[[ app_module ]].[[ app ]].urls")),
|
||||
# @ENDLOOP
|
||||
]
|
||||
# @ENDIF
|
||||
# @IF url_type == 'drf_router'
|
||||
from rest_framework.routers import DefaultRouter
|
||||
# @IF has_viewset
|
||||
from [[ app_module ]].views.[[ view_file ]] import [[ viewset_name ]]
|
||||
# @ENDIF
|
||||
# @IF has_app_name
|
||||
|
||||
app_name = "[[ app_name ]]"
|
||||
# @ENDIF
|
||||
|
||||
router = DefaultRouter()
|
||||
# @IF has_viewset
|
||||
router.register(r"[[ router_prefix ]]", [[ viewset_name ]], basename="[[ basename ]]")
|
||||
# @ENDIF
|
||||
|
||||
urlpatterns = [
|
||||
# @IF has_include
|
||||
path("", include(router.urls)),
|
||||
# @ENDIF
|
||||
]
|
||||
# @IF not has_include
|
||||
urlpatterns = router.urls
|
||||
# @ENDIF
|
||||
# @ENDIF
|
||||
# @IF url_type == 'component'
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
# Add your URL patterns here
|
||||
]
|
||||
# @ENDIF
|
||||
@@ -1,8 +0,0 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
TokenBlacklistView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
# JWT tokens
|
||||
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"),
|
||||
{% if comment_out_api_url %}# {% endif %}path("api/", include("{{ api_module }}.urls")),
|
||||
]
|
||||
|
||||
# Don't show schema in production
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("docs/", SpectacularSwaggerView.as_view(url_name="schema")),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
TokenBlacklistView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
# JWT tokens
|
||||
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"),
|
||||
# App URLs
|
||||
{% if comment_out_api_url %}# {% endif %} {% for app in app_names %} path("", include("{{ app }}.urls")),
|
||||
{% endfor %}]
|
||||
|
||||
# Don't show schema in production
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("docs/", SpectacularSwaggerView.as_view(url_name="schema")),
|
||||
]
|
||||
@@ -7,6 +7,6 @@ from django.core.wsgi import get_wsgi_application
|
||||
env = environ.Env()
|
||||
environ.Env.read_env()
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="{{ project_name }}.settings.production"))
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="[[ project_name ]].settings.production"))
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -1,7 +0,0 @@
|
||||
from django.urls import include, path
|
||||
|
||||
app_name = "api"
|
||||
|
||||
urlpatterns = [
|
||||
path("v1/", include("api.v1.urls")),
|
||||
]
|
||||
@@ -1,7 +0,0 @@
|
||||
from django.urls import include, path
|
||||
|
||||
app_name = "v1"
|
||||
|
||||
urlpatterns = [
|
||||
path("users/", include("apps.users.urls")),
|
||||
]
|
||||
@@ -1,8 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class {{ model_class_name }}(models.Model):
|
||||
"""Generated model for {{ app_name }} app."""
|
||||
|
||||
# Add fields here
|
||||
pass
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class [[ model_class_name ]](models.Model):
|
||||
"""[[ model_class_name ]] model."""
|
||||
|
||||
# Add your fields here
|
||||
name = models.CharField(max_length=255)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "[[ model_class_name ]]"
|
||||
verbose_name_plural = "[[ model_class_name ]]s"
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -1,9 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from {{ app_module }}.models.{{ app_name }} import {{ model_class_name }}
|
||||
|
||||
|
||||
class {{ model_class_name }}Serializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = {{ model_class_name }}
|
||||
fields = "__all__"
|
||||
@@ -0,0 +1,11 @@
|
||||
from rest_framework import serializers
|
||||
from [[ app_module ]].models.[[ app_name ]] import [[ model_class_name ]]
|
||||
|
||||
|
||||
class [[ model_class_name ]]Serializer(serializers.ModelSerializer):
|
||||
"""Serializer for [[ model_class_name ]] model."""
|
||||
|
||||
class Meta:
|
||||
model = [[ model_class_name ]]
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id", "created_at", "updated_at"]
|
||||
@@ -1,10 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from {{ app_module }}.models.{{ app_name }} import {{ model_class_name }}
|
||||
|
||||
|
||||
def get_{{ app_name }}_by_id(object_id: int) -> Optional[{{ model_class_name }}]:
|
||||
try:
|
||||
return {{ model_class_name }}.objects.get(id=object_id)
|
||||
except {{ model_class_name }}.DoesNotExist:
|
||||
return None
|
||||
@@ -0,0 +1,36 @@
|
||||
from [[ app_module ]].models.[[ app_name ]] import [[ model_class_name ]]
|
||||
|
||||
|
||||
class [[ model_class_name ]]Service:
|
||||
"""Service class for [[ model_class_name ]] business logic."""
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
"""Get all [[ model_class_name ]] instances."""
|
||||
return [[ model_class_name ]].objects.all()
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(id):
|
||||
"""Get [[ model_class_name ]] by ID."""
|
||||
try:
|
||||
return [[ model_class_name ]].objects.get(id=id)
|
||||
except [[ model_class_name ]].DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def create(data):
|
||||
"""Create a new [[ model_class_name ]] instance."""
|
||||
return [[ model_class_name ]].objects.create(**data)
|
||||
|
||||
@staticmethod
|
||||
def update(instance, data):
|
||||
"""Update an existing [[ model_class_name ]] instance."""
|
||||
for key, value in data.items():
|
||||
setattr(instance, key, value)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
@staticmethod
|
||||
def delete(instance):
|
||||
"""Delete a [[ model_class_name ]] instance."""
|
||||
instance.delete()
|
||||
@@ -1,12 +0,0 @@
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_{{ app_name }}_list_empty():
|
||||
client = APIClient()
|
||||
url = reverse("v1:{{ app_name }}-list")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
@@ -0,0 +1,55 @@
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from [[ app_module ]].models.[[ app_name ]] import [[ model_class_name ]]
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class [[ model_class_name ]]APITestCase(APITestCase):
|
||||
"""Test cases for [[ model_class_name ]] API endpoints."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data."""
|
||||
self.user = User.objects.create_user(
|
||||
username="testuser", password="testpass123"
|
||||
)
|
||||
self.client.force_authenticate(user=self.user)
|
||||
self.test_data = {"name": "Test [[ model_class_name ]]"}
|
||||
|
||||
def test_create_[[ app_name ]](self):
|
||||
"""Test creating a [[ model_class_name ]]."""
|
||||
response = self.client.post("/api/v1/[[ app_name ]]/", self.test_data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual([[ model_class_name ]].objects.count(), 1)
|
||||
|
||||
def test_list_[[ app_name ]]s(self):
|
||||
"""Test listing all [[ model_class_name ]]s."""
|
||||
[[ model_class_name ]].objects.create(**self.test_data)
|
||||
response = self.client.get("/api/v1/[[ app_name ]]/")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data), 1)
|
||||
|
||||
def test_retrieve_[[ app_name ]](self):
|
||||
"""Test retrieving a single [[ model_class_name ]]."""
|
||||
instance = [[ model_class_name ]].objects.create(**self.test_data)
|
||||
response = self.client.get(f"/api/v1/[[ app_name ]]/{instance.id}/")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["name"], self.test_data["name"])
|
||||
|
||||
def test_update_[[ app_name ]](self):
|
||||
"""Test updating a [[ model_class_name ]]."""
|
||||
instance = [[ model_class_name ]].objects.create(**self.test_data)
|
||||
updated_data = {"name": "Updated [[ model_class_name ]]"}
|
||||
response = self.client.patch(f"/api/v1/[[ app_name ]]/{instance.id}/", updated_data)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
instance.refresh_from_db()
|
||||
self.assertEqual(instance.name, updated_data["name"])
|
||||
|
||||
def test_delete_[[ app_name ]](self):
|
||||
"""Test deleting a [[ model_class_name ]]."""
|
||||
instance = [[ model_class_name ]].objects.create(**self.test_data)
|
||||
response = self.client.delete(f"/api/v1/[[ app_name ]]/{instance.id}/")
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual([[ model_class_name ]].objects.count(), 0)
|
||||
@@ -1,14 +0,0 @@
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from {{ app_module }}.views.{{ app_name }}_view import {{ model_class_name }}ViewSet
|
||||
|
||||
|
||||
app_name = "{{ app_name }}"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"{{ app_name }}", {{ model_class_name }}ViewSet, basename="{{ app_name }}")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
@@ -1,9 +0,0 @@
|
||||
from rest_framework import viewsets
|
||||
|
||||
from {{ app_module }}.models.{{ app_name }} import {{ model_class_name }}
|
||||
from {{ app_module }}.serializers.{{ app_name }}_serializer import {{ model_class_name }}Serializer
|
||||
|
||||
|
||||
class {{ model_class_name }}ViewSet(viewsets.ModelViewSet):
|
||||
queryset = {{ model_class_name }}.objects.all()
|
||||
serializer_class = {{ model_class_name }}Serializer
|
||||
@@ -0,0 +1,62 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from [[ app_module ]].models.[[ app_name ]] import [[ model_class_name ]]
|
||||
from [[ app_module ]].serializers.[[ app_name ]]_serializer import [[ model_class_name ]]Serializer
|
||||
from [[ app_module ]].services.[[ app_name ]]_service import [[ model_class_name ]]Service
|
||||
|
||||
|
||||
class [[ model_class_name ]]ViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for [[ model_class_name ]] CRUD operations."""
|
||||
|
||||
queryset = [[ model_class_name ]].objects.all()
|
||||
serializer_class = [[ model_class_name ]]Serializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def list(self, request):
|
||||
"""List all [[ model_class_name ]] instances."""
|
||||
instances = [[ model_class_name ]]Service.get_all()
|
||||
serializer = self.get_serializer(instances, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
"""Retrieve a single [[ model_class_name ]] instance."""
|
||||
instance = [[ model_class_name ]]Service.get_by_id(pk)
|
||||
if not instance:
|
||||
return Response(
|
||||
{"error": "[[ model_class_name ]] not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data)
|
||||
|
||||
def create(self, request):
|
||||
"""Create a new [[ model_class_name ]] instance."""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
instance = [[ model_class_name ]]Service.create(serializer.validated_data)
|
||||
return Response(
|
||||
self.get_serializer(instance).data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
def update(self, request, pk=None):
|
||||
"""Update an existing [[ model_class_name ]] instance."""
|
||||
instance = [[ model_class_name ]]Service.get_by_id(pk)
|
||||
if not instance:
|
||||
return Response(
|
||||
{"error": "[[ model_class_name ]] not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
updated_instance = [[ model_class_name ]]Service.update(instance, serializer.validated_data)
|
||||
return Response(self.get_serializer(updated_instance).data)
|
||||
|
||||
def destroy(self, request, pk=None):
|
||||
"""Delete a [[ model_class_name ]] instance."""
|
||||
instance = [[ model_class_name ]]Service.get_by_id(pk)
|
||||
if not instance:
|
||||
return Response(
|
||||
{"error": "[[ model_class_name ]] not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
[[ model_class_name ]]Service.delete(instance)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,14 +0,0 @@
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from apps.users.views.user_view import UserViewSet
|
||||
|
||||
|
||||
app_name = "users"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"users", UserViewSet, basename="users")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,9 +0,0 @@
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
def success(data=None, status_code=200):
|
||||
return Response({"success": True, "data": data}, status=status_code)
|
||||
|
||||
|
||||
def error(message, status_code=400):
|
||||
return Response({"success": False, "message": message}, status=status_code)
|
||||
+6
-2
@@ -1,13 +1,17 @@
|
||||
# @IF include_header
|
||||
"""generated with djinit"""
|
||||
|
||||
# @ENDIF
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
def success(data=None, status_code=200):
|
||||
"""Return a successful response."""
|
||||
# @IF include_docstrings """Return a successful response."""
|
||||
# @ENDIF
|
||||
return Response({"success": True, "data": data}, status=status_code)
|
||||
|
||||
|
||||
def error(message, status_code=400):
|
||||
"""Return an error response."""
|
||||
# @IF include_docstrings """Return an error response."""
|
||||
# @ENDIF
|
||||
return Response({"success": False, "message": message}, status=status_code)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
- name: Run Django tests
|
||||
env:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
DB_NAME: test_db
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Check migrations
|
||||
env:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
DB_NAME: test_db
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
# GitLab CI/CD configuration for {{ project_name }}
|
||||
# GitLab CI/CD configuration for [[ project_name ]]
|
||||
|
||||
stages:
|
||||
- test
|
||||
@@ -36,7 +36,7 @@ test:
|
||||
stage: test
|
||||
image: python:3.13
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
script:
|
||||
@@ -52,7 +52,7 @@ test:python310:
|
||||
stage: test
|
||||
image: python:3.10
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
script:
|
||||
@@ -69,7 +69,7 @@ test:python311:
|
||||
stage: test
|
||||
image: python:3.11
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
script:
|
||||
@@ -86,7 +86,7 @@ test:python312:
|
||||
stage: test
|
||||
image: python:3.12
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
script:
|
||||
@@ -103,7 +103,7 @@ test:python313:
|
||||
stage: test
|
||||
image: python:3.13
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ module_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ module_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
ALLOWED_HOSTS: localhost
|
||||
script:
|
||||
@@ -121,7 +121,7 @@ security:
|
||||
stage: security
|
||||
image: python:3.13
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: {{ project_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE: [[ project_name ]].settings.development
|
||||
SECRET_KEY: test-secret-key-for-ci
|
||||
script:
|
||||
- pip install uv
|
||||
@@ -1,23 +1,23 @@
|
||||
# Django settings
|
||||
DJANGO_SETTINGS_MODULE={{ project_name }}.settings.development
|
||||
DJANGO_SETTINGS_MODULE=[[ project_name ]].settings.development
|
||||
SECRET_KEY= your-secret-key
|
||||
ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com
|
||||
|
||||
{% if use_database_url %}
|
||||
# @IF use_database_url
|
||||
# Database URL (recommended for production)
|
||||
{% if database_type == 'mysql' %}
|
||||
DATABASE_URL=mysql://user:password@localhost:3306/{{ project_name }}
|
||||
{% else %}
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/{{ project_name }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# @IF database_type == 'mysql'
|
||||
DATABASE_URL=mysql://user:password@localhost:3306/[[ project_name ]]
|
||||
# @ELSE
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/[[ project_name ]]
|
||||
# @ENDIF
|
||||
# @ELSE
|
||||
# Database (individual parameters)
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
{% endif %}
|
||||
# @ENDIF
|
||||
|
||||
# Frontend URL
|
||||
FRONTEND_URL=http://frontend.your-domain.com
|
||||
File renamed without changes.
Whitespace-only changes.
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
# Justfile for {{ project_name }}
|
||||
# Justfile for [[ project_name ]]
|
||||
# Task runner for Django development
|
||||
# Install just: https://github.com/casey/just
|
||||
|
||||
@@ -108,15 +108,15 @@ dbshell:
|
||||
|
||||
# Start production server with gunicorn
|
||||
server:
|
||||
uv run gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:8000
|
||||
uv run gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:8000
|
||||
|
||||
# Start production server with gunicorn workers
|
||||
server-prod WORKERS="4":
|
||||
uv run gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:8000 --workers {{ '{{' }}WORKERS{{ '}}' }}
|
||||
uv run gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:8000 --workers {{ '{{' }}WORKERS{{ '}}' }}
|
||||
|
||||
# Start production server with gunicorn and reload on file changes
|
||||
server-watch:
|
||||
uv run gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:8000 --reload
|
||||
uv run gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:8000 --reload
|
||||
|
||||
# Clean Python cache files
|
||||
clean:
|
||||
@@ -11,7 +11,7 @@ import environ
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.production"))
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "[[ project_name ]].settings.production"))
|
||||
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
@@ -1,20 +1,20 @@
|
||||
# Procfile for {{ project_name }}
|
||||
# Procfile for [[ project_name ]]
|
||||
# Used for deploying to PaaS platforms like Heroku, Railway, Render, etc.
|
||||
|
||||
# Web server process (using Gunicorn)
|
||||
# Adjust workers based on your needs: (2 x CPU cores) + 1
|
||||
web: gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --access-logfile - --error-logfile - --log-level info
|
||||
web: gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --access-logfile - --error-logfile - --log-level info
|
||||
|
||||
# Alternative web server configurations (uncomment to use):
|
||||
# web: gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120
|
||||
# web: gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --worker-class gevent --worker-connections 1000 --timeout 120
|
||||
# web: gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120
|
||||
# web: gunicorn [[ project_name ]].wsgi:application --bind 0.0.0.0:$PORT --workers 2 --worker-class gevent --worker-connections 1000 --timeout 120
|
||||
|
||||
# Release task (runs migrations before deployment)
|
||||
release: python manage.py migrate --noinput && python manage.py collectstatic --noinput
|
||||
|
||||
# Uncomment below to run scheduled tasks (if using Celery)
|
||||
# worker: celery -A {{ project_name }} worker --loglevel=info
|
||||
# beat: celery -A {{ project_name }} beat --loglevel=info
|
||||
# worker: celery -A [[ project_name ]] worker --loglevel=info
|
||||
# beat: celery -A [[ project_name ]] beat --loglevel=info
|
||||
|
||||
# Development server (for local testing with Foreman/Overmind)
|
||||
# web: python manage.py runserver 0.0.0.0:$PORT
|
||||
+6
-2
@@ -1,5 +1,5 @@
|
||||
[project]
|
||||
name = "{{ package_name }}"
|
||||
name = "[[ package_name ]]"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
readme = "README.md"
|
||||
@@ -14,8 +14,12 @@ dependencies = [
|
||||
"drf-spectacular",
|
||||
"django-cors-headers",
|
||||
"whitenoise",
|
||||
# @IF database_type == 'mysql'
|
||||
"mysqlclient",
|
||||
# @ENDIF
|
||||
# @IF database_type == 'postgresql'
|
||||
"psycopg2-binary",
|
||||
# @ENDIF
|
||||
"gunicorn",
|
||||
]
|
||||
|
||||
@@ -39,4 +43,4 @@ ignore = [
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["{{ project_name }}"]
|
||||
known-first-party = ["[[ project_name ]]"]
|
||||
@@ -1,4 +1,4 @@
|
||||
# {{ project_name }}
|
||||
# [[ project_name ]]
|
||||
|
||||
A Django project folder structure is generated with [djinitx/djinit/dj](https://github.com/S4NKALP/djinit).
|
||||
|
||||
@@ -71,9 +71,9 @@ Run `just` to see all available commands, including:
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
{% if predefined_structure %}
|
||||
{{ project_name }}/
|
||||
├── {{ module_name }}/
|
||||
# @IF predefined_structure
|
||||
[[ project_name ]]/
|
||||
├── [[ module_name ]]/
|
||||
│ ├── settings/
|
||||
│ │ ├── base.py
|
||||
│ │ ├── development.py
|
||||
@@ -118,8 +118,8 @@ Run `just` to see all available commands, including:
|
||||
├── Procfile
|
||||
├── runtime.txt
|
||||
└── README.md
|
||||
{% elif unified_structure %}
|
||||
{{ project_name }}/
|
||||
# @ELSEIF unified_structure
|
||||
[[ project_name ]]/
|
||||
├── apps/
|
||||
│ ├── admin/
|
||||
│ │ └── __init__.py
|
||||
@@ -158,9 +158,9 @@ Run `just` to see all available commands, including:
|
||||
├── Procfile
|
||||
├── runtime.txt
|
||||
└── README.md
|
||||
{% else %}
|
||||
{{ project_name }}/
|
||||
├── {{ module_name }}/
|
||||
# @ELSE
|
||||
[[ project_name ]]/
|
||||
├── [[ module_name ]]/
|
||||
│ ├── settings/
|
||||
│ │ ├── base.py
|
||||
│ │ ├── development.py
|
||||
@@ -191,7 +191,7 @@ For example, if you have a `User` model in `models/user.py`, create:
|
||||
- `api/user/urls.py` - URL patterns for User endpoints
|
||||
|
||||
See the README files in `api/` and `models/` directories for more details.
|
||||
{% endif %}
|
||||
# @ENDIF
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
+4
-4
@@ -10,10 +10,10 @@ djangorestframework_simplejwt
|
||||
drf-spectacular
|
||||
django-cors-headers
|
||||
whitenoise
|
||||
{% if database_type == 'mysql' %}
|
||||
# @IF database_type == 'mysql'
|
||||
mysqlclient
|
||||
{% else %}
|
||||
# @ELSE
|
||||
psycopg[binary]
|
||||
{% endif %}
|
||||
# @ENDIF
|
||||
gunicorn
|
||||
{% if use_database_url %}dj-database-url{% endif %}
|
||||
dj-database-url # @IF use_database_url
|
||||
@@ -0,0 +1 @@
|
||||
python-[[ python_version ]]
|
||||
@@ -1 +0,0 @@
|
||||
python-{{ python_version }}
|
||||
Reference in new issue
Block a user