mcpField
D
django-drf

django-drf

github.com/prowler-cloud開発15.1k482 回インストール今日更新
リポジトリ

django-drf について

django-drfは、Django REST FrameworkでAPIを開発する方向けのコーディングスキルです。シリアライザー、ViewSet、フィルター、権限、性能、安全性、OpenAPI文書化の実装パターンを扱います。

主な機能

  1. 01操作別シリアライザーパターン
  2. 02ViewSetと権限のガイダンス
  3. 03フィルターとクエリ最適化
  4. 04OpenAPIとセキュリティの実践
  5. 05JSON:API実装リファレンス

活用シーン

  1. 01新しいDRFエンドポイントの設計
  2. 02APIの検証と権限のレビュー
  3. 03N+1データベースクエリの削減

MCP 対応

Context7 MCPを使って、Django、DRF、drf-spectacularの最新ドキュメントを参照する方法を推奨しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/prowler-cloud/prowler --skill django-drf

インストール後、Claude Code で/django-drfを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
security_patterns.py
references/
file-locations.md
json-api-conventions.md

詳細

重要なパターン

  • シリアライザーは操作ごとに必ず分けてください:Read / Create / Update / Include
  • 複雑なフィルタリングには必ず filterset_class を使用してください(filterset_fields は使用しないでください)
  • 書き込み用シリアライザーでは、必ず未知のフィールドを検証してください(BaseWriteSerializer を継承します)
  • N+1 を防ぐため、get_queryset() では必ず select_related/prefetch_related を使用してください
  • スキーマ生成のため、get_queryset() では必ず swagger_fake_view を処理してください
  • SerializerMethodField の OpenAPI ドキュメントには、必ず @extend_schema_field を使用してください
  • ビジネスロジックをシリアライザーに記述しないでください。サービス/ユーティリティを使用してください
  • 自動インクリメントの PK は使用しないでください。UUIDv4 または UUIDv7 を使用してください
  • URL に末尾のスラッシュを使用しないでください(trailing_slash=False)

注: swagger_fake_view は、OpenAPI スキーマ生成用の drf-spectacular 固有の機能です。


実装チェックリスト

新しいエンドポイントを実装するときは、以下のパターンを順に確認してください。

#パターン参照先要点
1モデルapi/models.pyUUID PK、inserted_at/updated_at、JSONAPIMeta.resource_name
2ViewSetapi/base_views.py、api/v1/views.pyBaseRLSViewSet を継承し、get_queryset() で N+1 を防止
3シリアライザーapi/v1/serializers.pyRead/Create/Update/Include を分け、BaseWriteSerializer を継承
4フィルターapi/filters.pyfilterset_class を使用し、基底フィルタークラスを継承
5権限api/base_views.pyrequired_permissions、set_required_permissions()
6ページネーションapi/pagination.py必要に応じてカスタムページネーションクラスを使用
7URL ルーティングapi/v1/urls.pytrailing_slash=False、ケバブケースのパス
8OpenAPI スキーマapi/v1/views.pydrf-spectacular を使用した @extend_schema_view
9テストapi/tests/test_views.pyJSON:API のコンテンツタイプ、フィクスチャのパターン

ファイルのフルパス:references/file-locations.md を参照してください。


判断フロー

どのシリアライザーを使いますか?
GET list/retrieve → <Model>Serializer
POST create       → <Model>CreateSerializer
PATCH update      → <Model>UpdateSerializer
?include=...      → <Model>IncludeSerializer
どの基底シリアライザーを使いますか?
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
どの基底フィルターを使いますか?
Direct FK to Provider  → BaseProviderFilter
FK via Scan           → BaseScanProviderFilter
No provider relation  → FilterSet
どの基底 ViewSet を使いますか?
RLS-protected model  → BaseRLSViewSet (most common)
Tenant operations    → BaseTenantViewset
User operations      → BaseUserViewset
No RLS required      → BaseViewSet (rare)
リソース名の形式は?
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)

シリアライザーのパターン

基底クラスの階層
# Read serializer (most common)
class ProviderSerializer(RLSSerializer):
    class Meta:
        model = Provider
        fields = ["id", "provider", "uid", "alias", "connected", "inserted_at"]

# Write serializer (validates unknown fields)
class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer):
    class Meta:
        model = Provider
        fields = ["provider", "uid", "alias"]

# Include serializer (sparse fields for ?include=)
class ProviderIncludeSerializer(RLSSerializer):
    class Meta:
        model = Provider
        fields = ["id", "alias"]  # Minimal fields
OpenAPI での SerializerMethodField
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,
        }
Include シリアライザー(JSON:API)
class ScanSerializer(RLSSerializer):
    included_serializers = {
        "provider": "api.v1.serializers.ProviderIncludeSerializer",
    }
機密データのマスキング
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 のパターン

N+1 を防止する get_queryset()

swagger_fake_view のチェックと select_related/prefetch_related を必ず組み合わせてください。

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 は OpenAPI スキーマを生成するため、ViewSet をイントロスペクションします。このチェックがないと、実際のクエリが実行され、リクエストコンテキストがない場合に失敗する可能性があります。

アクションごとのシリアライザー
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
アクションごとの動的な権限
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]
キャッシュデコレーター
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
カスタムアクション
# 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...

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

フィルターのパターン

基底フィルタークラス
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")
カスタム複数値フィルター
class UUIDInFilter(BaseInFilter, UUIDFilter):
    pass

class CharInFilter(BaseInFilter, CharFilter):
    pass

class ChoiceInFilter(BaseInFilter, ChoiceFilter):
    pass
ArrayField のフィルタリング
# Single value contains
region = CharFilter(method="filter_region")

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

# Multi-value overlap
region__in = CharInFilter(field_name="resource_regions", lookup_expr="overlap")
日付範囲の検証
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)
動的な FilterSet の選択
def get_filterset_class(self):
    if self.action in ["latest", "metadata_latest"]:
        return LatestFindingFilter
    return FindingFilter
Enum フィールドのオーバーライド
class Meta:
    model = Finding
    filter_overrides = {
        FindingDeltaEnumField: {"filter_class": CharFilter},
        StatusEnumField: {"filter_class": CharFilter},
        SeverityEnumField: {"filter_class": CharFilter},
    }

パフォーマンスのパターン

PaginateByPkMixin

結合コストの高い大規模なクエリーセットの場合:

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

命名規則

エンティティパターン例
シリアライザー(読み取り)<Model>SerializerProviderSerializer
シリアライザー(作成)<Model>CreateSerializerProviderCreateSerializer
シリアライザー(更新)<Model>UpdateSerializerProviderUpdateSerializer
シリアライザー(Include)<Model>IncludeSerializerProviderIncludeSerializer
フィルター<Model>FilterProviderFilter
ViewSet<Model>ViewSetProviderViewSet

OpenAPI ドキュメント

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 セキュリティパターン

完全な例:assets/security_patterns.pyを参照してください。

パターン要点
入力検証サニタイズには validate_<field>()、複数フィールド間の検証には validate() を使用します
一括代入の防止必ず明示的な fields リストを使用し、__all__ や exclude は絶対に使用しないでください
オブジェクトレベルの権限所有権の確認には has_object_permission() を実装します
レート制限DEFAULT_THROTTLE_RATES を設定し、機密性の高いエンドポイントにはビューごとのスロットリングを使用します
情報漏えいの防止汎用的なエラーメッセージを使用し、権限のないリクエストには 403 ではなく 404 を返します(リソースの列挙を防止します)
SQL インジェクション必ず ORM のパラメーター化を使用し、生の SQL で文字列補間を絶対に使用しないでください
クイックリファレンス
# 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

# Explicit fields (prevent mass assignment)
class Meta:
    fields = ["name", "email"]  # GOOD: whitelist
    read_only_fields = ["id", "inserted_at"]  # System fields

# 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

# Throttling for sensitive endpoints
class BurstRateThrottle(UserRateThrottle):
    rate = "10/minute"

# 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

コマンド

# Development
cd api && uv run python src/backend/manage.py runserver
cd api && uv run python src/backend/manage.py shell

# Database
cd api && uv run python src/backend/manage.py makemigrations
cd api && uv run python src/backend/manage.py migrate

# Testing
cd api && uv run pytest -x --tb=short
cd api && uv run make lint

リソース

ローカルリファレンス
Context7 MCP(推奨)

**前提条件:**最新のドキュメントを検索するには、Context7 MCP サーバーをインストールしてください。

実装やデバッグの際は、mcp_context7_query-docs を介して以下のライブラリを検索してください。

ライブラリContext7 ID用途
Django/websites/djangoproject_en_5_2モデル、ORM、マイグレーション
DRF/websites/django-rest-frameworkViewSet、シリアライザー、権限
drf-spectacular/tfranzel/drf-spectacularOpenAPI スキーマ、@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 を使用してください。

外部ドキュメント

コメント