home / skills / sergio-bershadsky / ai / django-dev
npx playbooks add skill sergio-bershadsky/ai --skill django-devReview the files below or copy the command above to add this skill to your agents.
---
name: django-dev
description: |
This skill should be used when the user asks to "create a django project", "new django app",
"django models", "django forms", "set up django", "configure django settings", or mentions
Django project structure, model organization, or Dynaconf configuration. Provides opinionated
Django development patterns with 1-file-per-model organization and consistent naming conventions.
---
# Django Development Patterns
Opinionated Django development toolkit enforcing consistent, production-ready patterns.
## Core Principles
1. **One file = one model/form** - Each model and form lives in its own file
2. **Consistent prefixes** - Abstract (`Base*`), virtual (`Virtual*`), proxy (`Proxy*`)
3. **UUID primary keys** - All models use UUID instead of auto-increment
4. **Timestamps everywhere** - All models inherit created_at/updated_at
5. **Soft delete by default** - Use deleted_at instead of hard deletes
6. **Dynaconf for config** - Never use plain settings.py
7. **uv + pyproject.toml** - Use uv for package management with split deps
8. **Class member ordering** - Strict ordering for readability
9. **Docker in /docker** - All Docker artifacts in `/docker` folder
## Project Setup (uv + pyproject.toml)
Always use uv for package management with split dependencies:
```bash
# Initialize project
uv init myproject
cd myproject
# Add dependencies by group
uv add django dynaconf django-unfold django-ninja
uv add --group dev ruff mypy django-stubs
uv add --group test pytest pytest-django factory-boy pytest-cov
```
`pyproject.toml`:
```toml
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"django>=5.0",
"dynaconf[toml]>=3.2",
"django-unfold>=0.30",
"django-ninja>=1.0",
"psycopg[binary]>=3.1",
"whitenoise>=6.6",
]
[dependency-groups]
dev = [
"ruff>=0.3",
"mypy>=1.8",
"django-stubs>=4.2",
"ipython>=8.0",
]
test = [
"pytest>=8.0",
"pytest-django>=4.8",
"factory-boy>=3.3",
"pytest-cov>=4.1",
"freezegun>=1.4",
]
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.mypy]
plugins = ["mypy_django_plugin.main"]
strict = true
```
## Project Structure
Standard Django project layout:
```
project/
├── pyproject.toml # Dependencies (uv)
├── uv.lock # Lock file
├── docker/
│ ├── Dockerfile # Main Dockerfile
│ ├── Dockerfile.dev # Development Dockerfile
│ ├── docker-compose.yml # Main compose
│ ├── docker-compose.dev.yml
│ ├── nginx/
│ │ └── nginx.conf
│ └── scripts/
│ ├── entrypoint.sh
│ └── wait-for-it.sh
├── config/
│ ├── __init__.py
│ ├── settings.py # Dynaconf integration
│ ├── .secrets.toml # Gitignored secrets
│ └── settings.toml # Environment config
├── apps/
│ └── myapp/
│ ├── models/ # Package, not single file
│ │ ├── __init__.py
│ │ ├── base.py # Base classes
│ │ └── user.py # One model per file
│ ├── forms/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── managers/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── api/ # Django Ninja (see django-dev-ninja)
│ └── admin/ # Unfold admin (see django-dev-unfold)
├── tests/ # See django-dev-test
└── manage.py
```
## Docker Configuration
All Docker artifacts in `/docker` folder:
```dockerfile
# docker/Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
# Install dependencies
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
# Copy application
COPY . .
# Collect static files
RUN uv run python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["uv", "run", "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
```
```yaml
# docker/docker-compose.yml
services:
web:
build:
context: ..
dockerfile: docker/Dockerfile
ports:
- "8000:8000"
environment:
- DJANGO_ENV=production
env_file:
- ../.env
depends_on:
- db
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=myproject
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
postgres_data:
```
## Model Organization
### Base Classes
Create base classes in `models/base.py`:
```python
import uuid
from django.db import models
from django.utils import timezone
class BaseTimeStamped(models.Model):
"""Adds created_at and updated_at timestamps."""
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
get_latest_by = "created_at"
class BaseSoftDelete(models.Model):
"""Adds soft delete capability with deleted_at field."""
deleted_at = models.DateTimeField(null=True, blank=True, db_index=True)
class Meta:
abstract = True
def delete(self, using=None, keep_parents=False):
self.deleted_at = timezone.now()
self.save(update_fields=["deleted_at"])
def hard_delete(self):
super().delete()
@property
def is_deleted(self) -> bool:
return self.deleted_at is not None
class BaseUUID(models.Model):
"""Uses UUID as primary key instead of auto-increment."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class Meta:
abstract = True
class BaseModel(BaseUUID, BaseTimeStamped, BaseSoftDelete):
"""Standard base model with UUID, timestamps, and soft delete."""
class Meta:
abstract = True
```
### Naming Conventions
| Prefix | Type | Example |
|--------|------|---------|
| `Base*` | Abstract base class | `BaseTimeStamped`, `BaseModel` |
| `Virtual*` | In-memory only (not persisted) | `VirtualCart`, `VirtualSession` |
| `Proxy*` | Proxy model | `ProxyActiveUser`, `ProxyAdmin` |
| (none) | Regular model | `User`, `Product`, `Order` |
### Class Member Ordering
All classes follow strict member ordering:
1. **`class Meta`** - ALWAYS FIRST in the class
2. **Fields** - Class attributes (model fields)
3. **Managers** - `objects = Manager()`
4. **Properties** (`@property`) - Alphabetical order
5. **Private/dunder methods** (`_method`, `__str__`) - Alphabetical order
6. **Public methods** - Alphabetical order
```python
class User(BaseModel):
"""User account model."""
# 1. class Meta - ALWAYS FIRST
class Meta:
db_table = "users"
ordering = ["-created_at"]
# 2. Fields
email = models.EmailField(unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
# 3. Manager
objects = UserManager()
# 4. Properties (alphabetical)
@property
def display_name(self) -> str:
return self.name or self.email.split("@")[0]
@property
def is_verified(self) -> bool:
return self.email_verified_at is not None
# 5. Private/dunder methods (alphabetical)
def __repr__(self) -> str:
return f"<User {self.email}>"
def __str__(self) -> str:
return self.email
def _calculate_score(self) -> int:
return len(self.orders.all())
def _validate_status(self) -> bool:
return self.is_active
# 6. Public methods (alphabetical)
def activate(self) -> None:
self.is_active = True
self.save(update_fields=["is_active"])
def can_place_order(self) -> bool:
return self.is_active and not self.is_deleted
def deactivate(self) -> None:
self.is_active = False
self.save(update_fields=["is_active"])
```
### Model File Template
Each model in its own file (`models/user.py`):
```python
from django.db import models
from .base import BaseModel
from ..managers.user import UserManager
class User(BaseModel):
"""User account model."""
# 1. class Meta - ALWAYS FIRST
class Meta:
db_table = "users"
verbose_name = "User"
verbose_name_plural = "Users"
ordering = ["-created_at"]
# 2. Fields
email = models.EmailField(unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
# 3. Manager
objects = UserManager()
# 4. Properties
@property
def display_name(self) -> str:
return self.name or self.email.split("@")[0]
# 5. Private/dunder methods
def __str__(self) -> str:
return self.email
```
### Model Package Init
Re-export all models in `models/__init__.py`:
```python
from .base import BaseModel, BaseTimeStamped, BaseSoftDelete, BaseUUID
from .user import User
from .product import Product
__all__ = [
"BaseModel",
"BaseTimeStamped",
"BaseSoftDelete",
"BaseUUID",
"User",
"Product",
]
```
## Custom Managers
Place managers in `managers/` package:
```python
# managers/user.py
from django.db import models
class UserQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True, deleted_at__isnull=True)
def by_email(self, email: str):
return self.filter(email__iexact=email)
class UserManager(models.Manager):
def get_queryset(self) -> UserQuerySet:
return UserQuerySet(self.model, using=self._db)
def active(self):
return self.get_queryset().active()
def by_email(self, email: str):
return self.get_queryset().by_email(email)
```
## Dynaconf Configuration
Always use Dynaconf for Django settings. See `references/dynaconf.md` for complete setup.
Quick setup:
```bash
pip install dynaconf
dynaconf init -f toml
```
Update `config/settings.py`:
```python
from dynaconf import Dynaconf
settings = Dynaconf(
envvar_prefix="DJANGO",
settings_files=["settings.toml", ".secrets.toml"],
environments=True,
env_switcher="DJANGO_ENV",
)
```
## Form Organization
Forms follow the same 1-file-per-form pattern. See `references/forms.md` for details.
```python
# forms/user.py
from django import forms
from ..models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ["email", "name"]
```
## Creating a New App
To create a new Django app with proper structure:
1. Create app directory with packages:
```bash
mkdir -p apps/myapp/{models,forms,managers,api,admin}
touch apps/myapp/__init__.py
touch apps/myapp/{models,forms,managers,api,admin}/__init__.py
```
2. Create base classes in `models/base.py`
3. Add app to `INSTALLED_APPS` using Dynaconf
4. Create initial models following conventions
## Additional Resources
### Reference Files
For detailed patterns and setup guides:
- **`references/models.md`** - Advanced model patterns, relationships, constraints
- **`references/forms.md`** - Form organization, validation, widgets
- **`references/dynaconf.md`** - Complete Dynaconf setup and environment configuration
### Related Skills
- **django-dev-ninja** - API development with Django Ninja
- **django-dev-unfold** - Admin customization with Unfold
- **django-dev-test** - Testing with pytest and factories