django-dev

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Django Development Patterns

Django开发范式

Opinionated Django development toolkit enforcing consistent, production-ready patterns.
约定式Django开发工具包,强制执行一致的、可用于生产环境的开发模式。

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
  1. 一个文件对应一个模型/表单 - 每个模型和表单单独存放在一个文件中
  2. 统一前缀规则 - 抽象类(
    Base*
    )、虚拟类(
    Virtual*
    )、代理类(
    Proxy*
  3. UUID主键 - 所有模型使用UUID而非自增ID
  4. 全局时间戳 - 所有模型均继承created_at/updated_at字段
  5. 默认软删除 - 使用deleted_at字段而非硬删除
  6. 用Dynaconf管理配置 - 绝不使用原生settings.py
  7. uv + pyproject.toml - 使用uv进行包管理,拆分依赖项
  8. 类成员排序 - 严格的排序规则以提升可读性
  9. Docker文件存放在/docker目录 - 所有Docker相关文件均放在
    /docker
    文件夹中

Project Setup (uv + pyproject.toml)

项目搭建(uv + pyproject.toml)

Always use uv for package management with split dependencies:
bash
undefined
始终使用uv进行包管理,并拆分依赖项:
bash
undefined

Initialize project

Initialize project

uv init myproject cd myproject
uv init myproject cd myproject

Add dependencies by group

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
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
标准Django项目布局:
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

Docker配置

All Docker artifacts in
/docker
folder:
dockerfile
undefined
所有Docker相关文件均放在
/docker
目录中:
dockerfile
undefined

docker/Dockerfile

docker/Dockerfile

FROM python:3.12-slim
WORKDIR /app
FROM python:3.12-slim
WORKDIR /app

Install uv

Install uv

COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv

Install dependencies

Install dependencies

COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev
COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev

Copy application

Copy application

COPY . .
COPY . .

Collect static files

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
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

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:
undefined
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:
undefined

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
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

命名规范

PrefixTypeExample
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
前缀类型示例
Base*
抽象基类
BaseTimeStamped
,
BaseModel
Virtual*
仅内存存在(不持久化)
VirtualCart
,
VirtualSession
Proxy*
代理模型
ProxyActiveUser
,
ProxyAdmin
(无)常规模型
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"])
所有类均遵循严格的成员排序:
  1. class Meta
    - 必须放在类的最开头
  2. 字段 - 类属性(模型字段)
  3. 管理器 -
    objects = Manager()
  4. 属性(
    @property
    - 按字母顺序排列
  5. 私有/双下划线方法
    _method
    ,
    __str__
    ) - 按字母顺序排列
  6. 公共方法 - 按字母顺序排列
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
每个模型单独存放在一个文件中(
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

模型包的__init__.py文件

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",
]
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
undefined
将管理器放在
managers/
包中:
python
undefined

managers/user.py

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)
undefined
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)
undefined

Dynaconf Configuration

Dynaconf配置

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",
)
始终使用Dynaconf管理Django设置。完整搭建指南请参考
references/dynaconf.md
快速搭建步骤:
bash
pip install dynaconf
dynaconf init -f toml
更新
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
undefined
表单遵循与模型相同的“一个文件对应一个表单”的模式。详细内容请参考
references/forms.md
python
undefined

forms/user.py

forms/user.py

from django import forms from ..models import User
class UserForm(forms.ModelForm): class Meta: model = User fields = ["email", "name"]
undefined
from django import forms from ..models import User
class UserForm(forms.ModelForm): class Meta: model = User fields = ["email", "name"]
undefined

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
  1. Create base classes in
    models/base.py
  2. Add app to
    INSTALLED_APPS
    using Dynaconf
  3. Create initial models following conventions
按照规范结构创建新的Django应用:
  1. 创建应用目录及子包:
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
  1. models/base.py
    中创建基类
  2. 通过Dynaconf将应用添加到
    INSTALLED_APPS
  3. 遵循规范创建初始模型

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
如需详细的模式和搭建指南:
  • references/models.md
    - 高级模型模式、关联关系、约束
  • references/forms.md
    - 表单组织、验证、小部件
  • references/dynaconf.md
    - 完整的Dynaconf搭建和环境配置

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
  • django-dev-ninja - 使用Django Ninja进行API开发
  • django-dev-unfold - 使用Unfold自定义Admin后台
  • django-dev-test - 使用pytest和工厂进行测试