| name | django |
| description | Django modern patterns and best practices for views, forms, URL routing, and management commands. Trigger: When implementing or refactoring Django views, forms, URLs, or commands.
|
| license | Apache-2.0 |
| metadata | {"author":"Carlos","version":"1.1","scope":["root"],"auto_invoke":["Writing Django views/forms/URLs","Implementing survey form views"]} |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task |
⚠️ CRITICAL: Class-Based Views ONLY
This project uses EXCLUSIVELY Class-Based Views (CBV). Function-Based Views (FBV) are PROHIBITED.
Why? Better reusability, built-in mixins (permissions, login), cleaner GET/POST separation, easier to extend.
Django Forms with Type Hints
from django import forms
from django.core.exceptions import ValidationError
from typing import Any
class UserForm(forms.ModelForm):
"""Type-safe ModelForm with validation."""
class Meta:
model = User
fields = ["name", "email", "status"]
widgets = {"email": forms.EmailInput(attrs={"class": "form-control"})}
labels = {"name": "Full Name"}
help_texts = {"email": "We'll never share your email."}
def clean_email(self) -> str:
"""Validate unique email."""
email = self.cleaned_data.get("email", "")
qs = User.objects.filter(email=email)
if self.instance and self.instance.pk:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise ValidationError("Email already exists")
return email.lower()
def clean(self) -> dict[str, Any]:
"""Cross-field validation."""
cleaned_data = super().clean()
return cleaned_data
Common field types: CharField, IntegerField, ChoiceField, MultipleChoiceField, BooleanField, DateField, FileField, EmailField
Class-Based Views (CBV)
from django.views.generic import ListView, CreateView, UpdateView, DetailView, TemplateView, FormView
from django.urls import reverse_lazy
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.shortcuts import redirect, get_object_or_404
from typing import Any
class HomeView(TemplateView):
template_name = "home.html"
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
context["total_users"] = User.objects.count()
return context
class UserListView(ListView):
model = User
template_name = "users/list.html"
context_object_name = "users"
paginate_by = 20
def get_queryset(self):
return User.objects.filter(status="active")
class UserDetailView(DetailView):
model = User
template_name =
context_object_name =
pk_url_kwarg =
():
model = User
form_class = UserForm
template_name =
success_url = reverse_lazy()
():
messages.success(.request, )
().form_valid(form)
():
model = User
form_class = UserForm
template_name =
pk_url_kwarg =
():
messages.success(.request, )
().form_valid(form)
():
reverse_lazy(, kwargs={: ..pk})
():
template_name =
form_class = ContactForm
success_url = reverse_lazy()
():
form.send_email()
messages.success(.request, )
().form_valid(form)
(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
template_name =
permission_required =
login_url =
() -> [, ]:
context = ().get_context_data(**kwargs)
context[] = .request.tenant
context
Key Methods to Override:
get_context_data() - Add extra context
get_queryset() - Filter queryset
form_valid() - Handle valid form submission
form_invalid() - Handle invalid form submission
get_success_url() - Dynamic success URL
dispatch() - Pre-process request
URL Configuration
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
path("", views.UserListView.as_view(), name="user_list"),
path("<int:user_id>/", views.UserDetailView.as_view(), name="user_detail"),
path("create/", views.UserCreateView.as_view(), name="user_create"),
path("<int:user_id>/update/", views.UserUpdateView.as_view(), name="user_update"),
]
success_url = reverse_lazy("users:user_detail", kwargs={"user_id": 123})
Multi-Tenancy with django-tenants
TENANT_MODEL = "organization.Organization"
TENANT_DOMAIN_MODEL = "organization.Domain"
PUBLIC_SCHEMA_URLCONF = "buzon_quejas.urls_public"
ROOT_URLCONF = "buzon_quejas.urls_tenant"
urlpatterns = [
path("", views.LandingView.as_view(), name="landing"),
path("login/", views.LoginView.as_view(), name="login"),
]
urlpatterns = [
path("", views.DashboardView.as_view(), name="dashboard"),
path("users/", include("apps.users.urls")),
]
class DashboardView(LoginRequiredMixin, TemplateView):
template_name = "dashboard.html"
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
tenant = self.request.tenant
context["tenant_name"] = tenant.name
return context
Management Commands
from django.core.management.base import BaseCommand, CommandError
from django_tenants.utils import schema_context
from typing import Any
class Command(BaseCommand):
help = "Command description"
def add_arguments(self, parser) -> None:
parser.add_argument("count", type=int, help="Number to process")
parser.add_argument("--schema", type=str, help="Tenant schema")
def handle(self, *args: Any, **options: Any) -> None:
count = options["count"]
schema = options.get("schema")
if schema:
with schema_context(schema):
self._process(count)
else:
self._process(count)
def _process(self, count: int) -> None:
try:
.stdout.write(.style.SUCCESS())
Exception e:
CommandError()
Project-Specific: Survey Form Pattern
URL Structure:
/survey/ - Unit selector (auto-redirect if only 1 unit)
/survey/<transit_number>/ - Survey form for specific unit
/survey/<transit_number>/submit/ - Submit survey
/survey/thank-you/ - Thank you page
class SelectUnitForSurveyView(TemplateView):
"""Auto-redirect if 1 unit, show selector if multiple."""
template_name = "interview/select_unit.html"
def get(self, request, *args, **kwargs):
units = Unit.objects.all()
if units.count() == 0:
self.template_name = "interview/no_units.html"
elif units.count() == 1:
return redirect("interview:survey_form", transit_number=units.first().transit_number)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
context["units"] = Unit.objects.all()
return context
class SurveyFormView(TemplateView):
"""Display survey form for specific unit."""
template_name = "interview/form_section.html"
def get_context_data(self, transit_number: str, **kwargs: Any) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
unit = get_object_or_404(Unit, transit_number=transit_number)
context.update({
: unit,
: Question.objects.(is_active=).order_by(),
})
context
():
form_class = SurveyForm
template_name =
():
request.method != :
redirect(, transit_number=kwargs[])
().dispatch(request, *args, **kwargs)
():
kwargs = ().get_form_kwargs()
kwargs[] = get_object_or_404(Unit, transit_number=.kwargs[])
kwargs
():
form.save()
messages.success(.request, )
redirect()
():
messages.error(.request, )
().form_invalid(form)
app_name =
urlpatterns = [
path(, views.SelectUnitForSurveyView.as_view(), name=),
path(, views.SurveyFormView.as_view(), name=),
path(, views.SubmitSurveyView.as_view(), name=),
path(, views.SurveyThankYouView.as_view(), name=),
]
Admin Customization (django-jazzmin)
INSTALLED_APPS = [
"jazzmin",
"django.contrib.admin",
]
JAZZMIN_SETTINGS = {
"site_title": "Admin Panel",
"site_header": "Tu Voz en Ruta",
"site_logo": "images/logo.png",
"icons": {
"auth.user": "fas fa-user",
"transport.Unit": "fas fa-bus",
},
"theme": "flatly",
}
from django.contrib.admin import AdminSite
class TenantAdminSite(AdminSite):
site_header = "Panel de Administración"
site_title = "Admin"
tenant_admin_site = TenantAdminSite(name="tenant_admin")
@admin.register(Unit, site=tenant_admin_site)
class UnitAdmin(admin.ModelAdmin):
list_display = ["transit_number", "route", "is_active"]
list_filter = ["is_active", "route"]
search_fields = ["transit_number"]
Best Practices Checklist
ALWAYS:
- ✅ Use Class-Based Views (CBV) - MANDATORY
- ✅ Type hints on all views and forms
- ✅ Use
get_object_or_404 instead of try/except
- ✅ Use
reverse_lazy in CBV (not reverse)
- ✅ Add
app_name in urls.py for namespacing
- ✅ Use Django messages framework for feedback
- ✅ Validate data in forms, not views
- ✅ Separate public and tenant URLs
- ✅ Access tenant via
request.tenant
- ✅ Use mixins:
LoginRequiredMixin, PermissionRequiredMixin
NEVER:
- ❌ Use Function-Based Views (FBV) - PROHIBITED
- ❌ Hard-code URLs (use
reverse_lazy())
- ❌ Put business logic in views (use models/managers/services)
- ❌ Skip form validation
- ❌ Mix public and tenant logic
- ❌ Use
reverse() in class attributes (use reverse_lazy())