django-drf

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Critical Patterns

核心开发模式

  • ALWAYS separate serializers by operation: Read / Create / Update / Include
  • ALWAYS use
    filterset_class
    for complex filtering (not
    filterset_fields
    )
  • ALWAYS validate unknown fields in write serializers (inherit
    BaseWriteSerializer
    )
  • ALWAYS use
    select_related
    /
    prefetch_related
    in
    get_queryset()
    to avoid N+1
  • ALWAYS handle
    swagger_fake_view
    in
    get_queryset()
    for schema generation
  • ALWAYS use
    @extend_schema_field
    for OpenAPI docs on
    SerializerMethodField
  • NEVER put business logic in serializers - use services/utils
  • NEVER use auto-increment PKs - use UUIDv4 or UUIDv7
  • NEVER use trailing slashes in URLs (
    trailing_slash=False
    )
Note:
swagger_fake_view
is specific to drf-spectacular for OpenAPI schema generation.

  • 始终按操作类型拆分序列化器:读取/创建/更新/包含
  • 始终使用
    filterset_class
    实现复杂过滤(不要使用
    filterset_fields
  • 始终在写入型序列化器中验证未知字段(继承
    BaseWriteSerializer
  • 始终在
    get_queryset()
    中使用
    select_related
    /
    prefetch_related
    避免N+1查询问题
  • 始终在
    get_queryset()
    中处理
    swagger_fake_view
    以支持Schema生成
  • 始终为
    SerializerMethodField
    使用
    @extend_schema_field
    生成OpenAPI文档
  • 绝对不要在序列化器中编写业务逻辑 - 应使用服务/工具类
  • 绝对不要使用自增主键 - 使用UUIDv4或UUIDv7
  • 绝对不要在URL中使用尾部斜杠(设置
    trailing_slash=False
注意:
swagger_fake_view
drf-spectacular用于生成OpenAPI Schema的特定标识。

Implementation Checklist

实现检查清单

When implementing a new endpoint, review these patterns in order:
#PatternReferenceKey Points
1Models
api/models.py
UUID PK,
inserted_at
/
updated_at
,
JSONAPIMeta.resource_name
2ViewSets
api/base_views.py
,
api/v1/views.py
Inherit
BaseRLSViewSet
,
get_queryset()
with N+1 prevention
3Serializers
api/v1/serializers.py
Separate Read/Create/Update/Include, inherit
BaseWriteSerializer
4Filters
api/filters.py
Use
filterset_class
, inherit base filter classes
5Permissions
api/base_views.py
required_permissions
,
set_required_permissions()
6Pagination
api/pagination.py
Custom pagination class if needed
7URL Routing
api/v1/urls.py
trailing_slash=False
, kebab-case paths
8OpenAPI Schema
api/v1/views.py
@extend_schema_view
with drf-spectacular
9Tests
api/tests/test_views.py
JSON:API content type, fixture patterns
Full file paths: See references/file-locations.md

在实现新接口时,按以下顺序检查这些模式:
序号模式参考文件核心要点
1模型
api/models.py
UUID主键、
inserted_at
/
updated_at
字段、
JSONAPIMeta.resource_name
2ViewSets
api/base_views.py
,
api/v1/views.py
继承
BaseRLSViewSet
,在
get_queryset()
中避免N+1查询
3序列化器
api/v1/serializers.py
拆分读取/创建/更新/包含类型,继承
BaseWriteSerializer
4过滤器
api/filters.py
使用
filterset_class
,继承基础过滤器类
5权限控制
api/base_views.py
required_permissions
set_required_permissions()
6分页
api/pagination.py
必要时使用自定义分页类
7URL路由
api/v1/urls.py
trailing_slash=False
、短横线命名路径
8OpenAPI Schema
api/v1/views.py
结合drf-spectacular使用
@extend_schema_view
9测试
api/tests/test_views.py
JSON:API内容类型、固定数据模式
完整文件路径:查看 references/file-locations.md

Decision Trees

决策树

Which Serializer?

如何选择序列化器?

GET list/retrieve → <Model>Serializer
POST create       → <Model>CreateSerializer
PATCH update      → <Model>UpdateSerializer
?include=...      → <Model>IncludeSerializer
GET list/retrieve → <Model>Serializer
POST create       → <Model>CreateSerializer
PATCH update      → <Model>UpdateSerializer
?include=...      → <Model>IncludeSerializer

Which Base Serializer?

如何选择基础序列化器?

Read-only serializer   → BaseModelSerializerV1
Create with tenant_id  → RLSSerializer + BaseWriteSerializer (auto-injects tenant_id on create)
Update with validation → BaseWriteSerializer (tenant_id already exists on object)
Non-model data         → BaseSerializerV1
只读序列化器   → BaseModelSerializerV1
带tenant_id的创建操作 → RLSSerializer + BaseWriteSerializer(创建时自动注入tenant_id)
带验证的更新操作 → BaseWriteSerializer(对象已存在tenant_id)
非模型数据     → BaseSerializerV1

Which Filter Base?

如何选择基础过滤器?

Direct FK to Provider  → BaseProviderFilter
FK via Scan           → BaseScanProviderFilter
No provider relation  → FilterSet
直接关联Provider外键 → BaseProviderFilter
通过Scan关联Provider外键 → BaseScanProviderFilter
无Provider关联 → FilterSet

Which Base ViewSet?

如何选择基础ViewSet?

RLS-protected model  → BaseRLSViewSet (most common)
Tenant operations    → BaseTenantViewset
User operations      → BaseUserViewset
No RLS required      → BaseViewSet (rare)
受RLS保护的模型  → BaseRLSViewSet(最常用)
租户操作    → BaseTenantViewset
用户操作      → BaseUserViewset
无需RLS保护      → BaseViewSet(罕见)

Resource Name Format?

资源名称格式?

Single word model     → plural lowercase           (Provider → providers)
Multi-word model      → plural lowercase kebab     (ProviderGroup → provider-groups)
Through/join model    → parent-child pattern       (UserRoleRelationship → user-roles)
Aggregation/overview  → descriptive kebab plural   (ComplianceOverview → compliance-overviews)

单单词模型     → 复数小写形式           (Provider → providers)
多单词模型      → 复数小写短横线形式     (ProviderGroup → provider-groups)
关联模型    → 父子命名模式       (UserRoleRelationship → user-roles)
聚合/概览模型  → 描述性复数短横线形式   (ComplianceOverview → compliance-overviews)

Serializer Patterns

序列化器模式

Base Class Hierarchy

基类层级结构

python
undefined
python
undefined

Read serializer (most common)

Read serializer (most common)

class ProviderSerializer(RLSSerializer): class Meta: model = Provider fields = ["id", "provider", "uid", "alias", "connected", "inserted_at"]
class ProviderSerializer(RLSSerializer): class Meta: model = Provider fields = ["id", "provider", "uid", "alias", "connected", "inserted_at"]

Write serializer (validates unknown fields)

Write serializer (validates unknown fields)

class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer): class Meta: model = Provider fields = ["provider", "uid", "alias"]
class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer): class Meta: model = Provider fields = ["provider", "uid", "alias"]

Include serializer (sparse fields for ?include=)

Include serializer (sparse fields for ?include=)

class ProviderIncludeSerializer(RLSSerializer): class Meta: model = Provider fields = ["id", "alias"] # Minimal fields
undefined
class ProviderIncludeSerializer(RLSSerializer): class Meta: model = Provider fields = ["id", "alias"] # Minimal fields
undefined

SerializerMethodField with OpenAPI

结合OpenAPI的SerializerMethodField

python
from drf_spectacular.utils import extend_schema_field

class ProviderSerializer(RLSSerializer):
    connection = serializers.SerializerMethodField(read_only=True)

    @extend_schema_field({
        "type": "object",
        "properties": {
            "connected": {"type": "boolean"},
            "last_checked_at": {"type": "string", "format": "date-time"},
        },
    })
    def get_connection(self, obj):
        return {
            "connected": obj.connected,
            "last_checked_at": obj.connection_last_checked_at,
        }
python
from drf_spectacular.utils import extend_schema_field

class ProviderSerializer(RLSSerializer):
    connection = serializers.SerializerMethodField(read_only=True)

    @extend_schema_field({
        "type": "object",
        "properties": {
            "connected": {"type": "boolean"},
            "last_checked_at": {"type": "string", "format": "date-time"},
        },
    })
    def get_connection(self, obj):
        return {
            "connected": obj.connected,
            "last_checked_at": obj.connection_last_checked_at,
        }

Included Serializers (JSON:API)

包含式序列化器(JSON:API)

python
class ScanSerializer(RLSSerializer):
    included_serializers = {
        "provider": "api.v1.serializers.ProviderIncludeSerializer",
    }
python
class ScanSerializer(RLSSerializer):
    included_serializers = {
        "provider": "api.v1.serializers.ProviderIncludeSerializer",
    }

Sensitive Data Masking

敏感数据掩码

python
def to_representation(self, instance):
    data = super().to_representation(instance)
    # Mask by default, expose only on explicit request
    fields_param = self.context.get("request").query_params.get("fields[my-model]", "")
    if "api_key" in fields_param:
        data["api_key"] = instance.api_key_decoded
    else:
        data["api_key"] = "****" if instance.api_key else None
    return data

python
def to_representation(self, instance):
    data = super().to_representation(instance)
    # Mask by default, expose only on explicit request
    fields_param = self.context.get("request").query_params.get("fields[my-model]", "")
    if "api_key" in fields_param:
        data["api_key"] = instance.api_key_decoded
    else:
        data["api_key"] = "****" if instance.api_key else None
    return data

ViewSet Patterns

ViewSet模式

get_queryset() with N+1 Prevention

避免N+1查询的get_queryset()

Always combine
swagger_fake_view
check with
select_related
/
prefetch_related
:
python
def get_queryset(self):
    # REQUIRED: Return empty queryset for OpenAPI schema generation
    if getattr(self, "swagger_fake_view", False):
        return Provider.objects.none()

    # N+1 prevention: eager load relationships
    return Provider.objects.select_related(
        "tenant",
    ).prefetch_related(
        "provider_groups",
        Prefetch("tags", queryset=ProviderTag.objects.filter(tenant_id=self.request.tenant_id)),
    )
Why swagger_fake_view? drf-spectacular introspects ViewSets to generate OpenAPI schemas. Without this check, it executes real queries and can fail without request context.
务必同时结合
swagger_fake_view
检查与
select_related
/
prefetch_related
python
def get_queryset(self):
    # REQUIRED: Return empty queryset for OpenAPI schema generation
    if getattr(self, "swagger_fake_view", False):
        return Provider.objects.none()

    # N+1 prevention: eager load relationships
    return Provider.objects.select_related(
        "tenant",
    ).prefetch_related(
        "provider_groups",
        Prefetch("tags", queryset=ProviderTag.objects.filter(tenant_id=self.request.tenant_id)),
    )
为什么需要swagger_fake_view? drf-spectacular会通过解析ViewSet生成OpenAPI Schema。如果没有此检查,它会执行真实查询,在缺少请求上下文时可能失败。

Action-Specific Serializers

按操作选择序列化器

python
def get_serializer_class(self):
    if self.action == "create":
        return ProviderCreateSerializer
    elif self.action == "partial_update":
        return ProviderUpdateSerializer
    elif self.action in ["connection", "destroy"]:
        return TaskSerializer
    return ProviderSerializer
python
def get_serializer_class(self):
    if self.action == "create":
        return ProviderCreateSerializer
    elif self.action == "partial_update":
        return ProviderUpdateSerializer
    elif self.action in ["connection", "destroy"]:
        return TaskSerializer
    return ProviderSerializer

Dynamic Permissions per Action

按操作动态配置权限

python
class ProviderViewSet(BaseRLSViewSet):
    required_permissions = [Permissions.MANAGE_PROVIDERS]

    def set_required_permissions(self):
        if self.action in ["list", "retrieve"]:
            self.required_permissions = []  # Read-only = no permission
        else:
            self.required_permissions = [Permissions.MANAGE_PROVIDERS]
python
class ProviderViewSet(BaseRLSViewSet):
    required_permissions = [Permissions.MANAGE_PROVIDERS]

    def set_required_permissions(self):
        if self.action in ["list", "retrieve"]:
            self.required_permissions = []  # Read-only = no permission
        else:
            self.required_permissions = [Permissions.MANAGE_PROVIDERS]

Cache Decorator

缓存装饰器

python
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control

CACHE_DECORATOR = cache_control(
    max_age=django_settings.CACHE_MAX_AGE,
    stale_while_revalidate=django_settings.CACHE_STALE_WHILE_REVALIDATE,
)

@method_decorator(CACHE_DECORATOR, name="list")
@method_decorator(CACHE_DECORATOR, name="retrieve")
class ProviderViewSet(BaseRLSViewSet):
    pass
python
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control

CACHE_DECORATOR = cache_control(
    max_age=django_settings.CACHE_MAX_AGE,
    stale_while_revalidate=django_settings.CACHE_STALE_WHILE_REVALIDATE,
)

@method_decorator(CACHE_DECORATOR, name="list")
@method_decorator(CACHE_DECORATOR, name="retrieve")
class ProviderViewSet(BaseRLSViewSet):
    pass

Custom Actions

自定义操作

python
undefined
python
undefined

Detail action (operates on single object)

Detail action (operates on single object)

@action(detail=True, methods=["post"], url_name="connection") def connection(self, request, pk=None): instance = self.get_object() # Process instance...
@action(detail=True, methods=["post"], url_name="connection") def connection(self, request, pk=None): instance = self.get_object() # Process instance...

List action (operates on collection)

List action (operates on collection)

@action(detail=False, methods=["get"], url_name="metadata") def metadata(self, request): queryset = self.filter_queryset(self.get_queryset()) # Aggregate over queryset...

---
@action(detail=False, methods=["get"], url_name="metadata") def metadata(self, request): queryset = self.filter_queryset(self.get_queryset()) # Aggregate over queryset...

---

Filter Patterns

过滤器模式

Base Filter Classes

基础过滤器类

python
class BaseProviderFilter(FilterSet):
    """For models with direct FK to Provider"""
    provider_id = UUIDFilter(field_name="provider__id", lookup_expr="exact")
    provider_id__in = UUIDInFilter(field_name="provider__id", lookup_expr="in")
    provider_type = ChoiceFilter(field_name="provider__provider", choices=Provider.ProviderChoices.choices)

class BaseScanProviderFilter(FilterSet):
    """For models with FK to Scan (Scan has FK to Provider)"""
    provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact")
python
class BaseProviderFilter(FilterSet):
    """For models with direct FK to Provider"""
    provider_id = UUIDFilter(field_name="provider__id", lookup_expr="exact")
    provider_id__in = UUIDInFilter(field_name="provider__id", lookup_expr="in")
    provider_type = ChoiceFilter(field_name="provider__provider", choices=Provider.ProviderChoices.choices)

class BaseScanProviderFilter(FilterSet):
    """For models with FK to Scan (Scan has FK to Provider)"""
    provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact")

Custom Multi-Value Filters

自定义多值过滤器

python
class UUIDInFilter(BaseInFilter, UUIDFilter):
    pass

class CharInFilter(BaseInFilter, CharFilter):
    pass

class ChoiceInFilter(BaseInFilter, ChoiceFilter):
    pass
python
class UUIDInFilter(BaseInFilter, UUIDFilter):
    pass

class CharInFilter(BaseInFilter, CharFilter):
    pass

class ChoiceInFilter(BaseInFilter, ChoiceFilter):
    pass

ArrayField Filtering

ArrayField过滤

python
undefined
python
undefined

Single value contains

Single value contains

region = CharFilter(method="filter_region")
def filter_region(self, queryset, name, value): return queryset.filter(resource_regions__contains=[value])
region = CharFilter(method="filter_region")
def filter_region(self, queryset, name, value): return queryset.filter(resource_regions__contains=[value])

Multi-value overlap

Multi-value overlap

region__in = CharInFilter(field_name="resource_regions", lookup_expr="overlap")
undefined
region__in = CharInFilter(field_name="resource_regions", lookup_expr="overlap")
undefined

Date Range Validation

日期范围验证

python
def filter_queryset(self, queryset):
    # Require date filter for performance
    if not (date_filters_provided):
        raise ValidationError([{
            "detail": "At least one date filter is required",
            "status": 400,
            "source": {"pointer": "/data/attributes/inserted_at"},
            "code": "required",
        }])

    # Validate max range
    if date_range > settings.FINDINGS_MAX_DAYS_IN_RANGE:
        raise ValidationError(...)

    return super().filter_queryset(queryset)
python
def filter_queryset(self, queryset):
    # Require date filter for performance
    if not (date_filters_provided):
        raise ValidationError([{
            "detail": "At least one date filter is required",
            "status": 400,
            "source": {"pointer": "/data/attributes/inserted_at"},
            "code": "required",
        }])

    # Validate max range
    if date_range > settings.FINDINGS_MAX_DAYS_IN_RANGE:
        raise ValidationError(...)

    return super().filter_queryset(queryset)

Dynamic FilterSet Selection

动态选择FilterSet

python
def get_filterset_class(self):
    if self.action in ["latest", "metadata_latest"]:
        return LatestFindingFilter
    return FindingFilter
python
def get_filterset_class(self):
    if self.action in ["latest", "metadata_latest"]:
        return LatestFindingFilter
    return FindingFilter

Enum Field Override

枚举字段覆盖

python
class Meta:
    model = Finding
    filter_overrides = {
        FindingDeltaEnumField: {"filter_class": CharFilter},
        StatusEnumField: {"filter_class": CharFilter},
        SeverityEnumField: {"filter_class": CharFilter},
    }

python
class Meta:
    model = Finding
    filter_overrides = {
        FindingDeltaEnumField: {"filter_class": CharFilter},
        StatusEnumField: {"filter_class": CharFilter},
        SeverityEnumField: {"filter_class": CharFilter},
    }

Performance Patterns

性能优化模式

PaginateByPkMixin

PaginateByPkMixin

For large querysets with expensive joins:
python
class PaginateByPkMixin:
    def paginate_by_pk(self, request, base_queryset, manager,
                       select_related=None, prefetch_related=None):
        # 1. Get PKs only (cheap)
        pk_list = base_queryset.values_list("id", flat=True)
        page = self.paginate_queryset(pk_list)

        # 2. Fetch full objects for just the page
        queryset = manager.filter(id__in=page)
        if select_related:
            queryset = queryset.select_related(*select_related)
        if prefetch_related:
            queryset = queryset.prefetch_related(*prefetch_related)

        # 3. Re-sort to preserve DB ordering
        queryset = sorted(queryset, key=lambda obj: page.index(obj.id))
        return self.get_paginated_response(self.get_serializer(queryset, many=True).data)
针对包含复杂关联的大型数据集:
python
class PaginateByPkMixin:
    def paginate_by_pk(self, request, base_queryset, manager,
                       select_related=None, prefetch_related=None):
        # 1. Get PKs only (cheap)
        pk_list = base_queryset.values_list("id", flat=True)
        page = self.paginate_queryset(pk_list)

        # 2. Fetch full objects for just the page
        queryset = manager.filter(id__in=page)
        if select_related:
            queryset = queryset.select_related(*select_related)
        if prefetch_related:
            queryset = queryset.prefetch_related(*prefetch_related)

        # 3. Re-sort to preserve DB ordering
        queryset = sorted(queryset, key=lambda obj: page.index(obj.id))
        return self.get_paginated_response(self.get_serializer(queryset, many=True).data)

Prefetch in Serializers

序列化器中的预加载优化

python
def get_tags(self, obj):
    # Use prefetched tags if available
    if hasattr(obj, "prefetched_tags"):
        return {tag.key: tag.value for tag in obj.prefetched_tags}
    # Fallback (causes N+1 if not prefetched)
    return obj.get_tags(self.context.get("tenant_id"))

python
def get_tags(self, obj):
    # Use prefetched tags if available
    if hasattr(obj, "prefetched_tags"):
        return {tag.key: tag.value for tag in obj.prefetched_tags}
    # Fallback (causes N+1 if not prefetched)
    return obj.get_tags(self.context.get("tenant_id"))

Naming Conventions

命名规范

EntityPatternExample
Serializer (read)
<Model>Serializer
ProviderSerializer
Serializer (create)
<Model>CreateSerializer
ProviderCreateSerializer
Serializer (update)
<Model>UpdateSerializer
ProviderUpdateSerializer
Serializer (include)
<Model>IncludeSerializer
ProviderIncludeSerializer
Filter
<Model>Filter
ProviderFilter
ViewSet
<Model>ViewSet
ProviderViewSet

实体命名模式示例
读取型序列化器
<Model>Serializer
ProviderSerializer
创建型序列化器
<Model>CreateSerializer
ProviderCreateSerializer
更新型序列化器
<Model>UpdateSerializer
ProviderUpdateSerializer
包含型序列化器
<Model>IncludeSerializer
ProviderIncludeSerializer
过滤器
<Model>Filter
ProviderFilter
ViewSet
<Model>ViewSet
ProviderViewSet

OpenAPI Documentation

OpenAPI文档

python
from drf_spectacular.utils import extend_schema, extend_schema_view

@extend_schema_view(
    list=extend_schema(tags=["Provider"], summary="List all providers"),
    retrieve=extend_schema(tags=["Provider"], summary="Retrieve provider"),
    create=extend_schema(tags=["Provider"], summary="Create provider"),
)
@extend_schema(tags=["Provider"])
class ProviderViewSet(BaseRLSViewSet):
    pass

python
from drf_spectacular.utils import extend_schema, extend_schema_view

@extend_schema_view(
    list=extend_schema(tags=["Provider"], summary="List all providers"),
    retrieve=extend_schema(tags=["Provider"], summary="Retrieve provider"),
    create=extend_schema(tags=["Provider"], summary="Create provider"),
)
@extend_schema(tags=["Provider"])
class ProviderViewSet(BaseRLSViewSet):
    pass

API Security Patterns

API安全模式

Full examples: See assets/security_patterns.py
PatternKey Points
Input ValidationUse
validate_<field>()
for sanitization,
validate()
for cross-field
Prevent Mass AssignmentALWAYS use explicit
fields
list, NEVER
__all__
or
exclude
Object-Level PermissionsImplement
has_object_permission()
for ownership checks
Rate LimitingConfigure
DEFAULT_THROTTLE_RATES
, use per-view throttles for sensitive endpoints
Prevent Info DisclosureGeneric error messages, return 404 not 403 for unauthorized (prevents enumeration)
SQL InjectionALWAYS use ORM parameterization, NEVER string interpolation in raw SQL
完整示例:查看 assets/security_patterns.py
模式核心要点
输入验证使用
validate_<field>()
进行字段校验,使用
validate()
进行跨字段校验
防止批量赋值始终使用显式的
fields
列表,绝对不要使用
__all__
exclude
对象级权限控制实现
has_object_permission()
进行归属权校验
请求频率限制配置
DEFAULT_THROTTLE_RATES
,为敏感接口使用视图级频率限制
防止信息泄露使用通用错误提示,未授权时返回404而非403(防止枚举攻击)
SQL注入防护始终使用ORM参数化查询,绝对不要在原生SQL中使用字符串拼接

Quick Reference

快速参考

python
undefined
python
undefined

Input validation in serializer

Input validation in serializer

def validate_uid(self, value): value = value.strip().lower() if not re.match(r'^[a-z0-9-]+$', value): raise serializers.ValidationError("Invalid format") return value
def validate_uid(self, value): value = value.strip().lower() if not re.match(r'^[a-z0-9-]+$', value): raise serializers.ValidationError("Invalid format") return value

Explicit fields (prevent mass assignment)

Explicit fields (prevent mass assignment)

class Meta: fields = ["name", "email"] # GOOD: whitelist read_only_fields = ["id", "inserted_at"] # System fields
class Meta: fields = ["name", "email"] # GOOD: whitelist read_only_fields = ["id", "inserted_at"] # System fields

Object permission

Object permission

class IsOwnerOrReadOnly(BasePermission): def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS: return True return obj.owner == request.user
class IsOwnerOrReadOnly(BasePermission): def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS: return True return obj.owner == request.user

Throttling for sensitive endpoints

Throttling for sensitive endpoints

class BurstRateThrottle(UserRateThrottle): rate = "10/minute"
class BurstRateThrottle(UserRateThrottle): rate = "10/minute"

Safe error messages (prevent enumeration)

Safe error messages (prevent enumeration)

def get_object(self): try: return super().get_object() except Http404: raise NotFound("Resource not found") # Generic, no internal IDs

---
def get_object(self): try: return super().get_object() except Http404: raise NotFound("Resource not found") # Generic, no internal IDs

---

Commands

命令

bash
undefined
bash
undefined

Development

Development

cd api && poetry run python src/backend/manage.py runserver cd api && poetry run python src/backend/manage.py shell
cd api && poetry run python src/backend/manage.py runserver cd api && poetry run python src/backend/manage.py shell

Database

Database

cd api && poetry run python src/backend/manage.py makemigrations cd api && poetry run python src/backend/manage.py migrate
cd api && poetry run python src/backend/manage.py makemigrations cd api && poetry run python src/backend/manage.py migrate

Testing

Testing

cd api && poetry run pytest -x --tb=short cd api && poetry run make lint

---
cd api && poetry run pytest -x --tb=short cd api && poetry run make lint

---

Resources

参考资源

Local References

本地参考

  • File Locations: See references/file-locations.md
  • JSON:API Conventions: See references/json-api-conventions.md
  • Security Patterns: See assets/security_patterns.py
  • 文件位置:查看 references/file-locations.md
  • JSON:API规范:查看 references/json-api-conventions.md
  • 安全模式:查看 assets/security_patterns.py

Context7 MCP (Recommended)

Context7 MCP(推荐)

Prerequisite: Install Context7 MCP server for up-to-date documentation lookup.
When implementing or debugging, query these libraries via
mcp_context7_query-docs
:
LibraryContext7 IDUse For
Django
/websites/djangoproject_en_5_2
Models, ORM, migrations
DRF
/websites/django-rest-framework
ViewSets, serializers, permissions
drf-spectacular
/tfranzel/drf-spectacular
OpenAPI schema,
@extend_schema
Example queries:
mcp_context7_query-docs(libraryId="/websites/django-rest-framework", query="ViewSet get_queryset best practices")
mcp_context7_query-docs(libraryId="/tfranzel/drf-spectacular", query="extend_schema examples for custom actions")
mcp_context7_query-docs(libraryId="/websites/djangoproject_en_5_2", query="model constraints and indexes")
Note: Use
mcp_context7_resolve-library-id
first if you need to find the correct library ID.
前置条件:安装Context7 MCP服务器以获取最新文档查询能力。
在实现或调试时,可通过
mcp_context7_query-docs
查询以下库:
Context7 ID适用场景
Django
/websites/djangoproject_en_5_2
模型、ORM、迁移
DRF
/websites/django-rest-framework
ViewSets、序列化器、权限
drf-spectacular
/tfranzel/drf-spectacular
OpenAPI Schema、
@extend_schema
查询示例
mcp_context7_query-docs(libraryId="/websites/django-rest-framework", query="ViewSet get_queryset best practices")
mcp_context7_query-docs(libraryId="/tfranzel/drf-spectacular", query="extend_schema examples for custom actions")
mcp_context7_query-docs(libraryId="/websites/djangoproject_en_5_2", query="model constraints and indexes")
注意:如果需要查找正确的库ID,请先使用
mcp_context7_resolve-library-id

External Docs

外部文档