django-drf について
django-drfは、Django REST FrameworkでAPIを開発する方向けのコーディングスキルです。シリアライザー、ViewSet、フィルター、権限、性能、安全性、OpenAPI文書化の実装パターンを扱います。
主な機能
- 01操作別シリアライザーパターン
- 02ViewSetと権限のガイダンス
- 03フィルターとクエリ最適化
- 04OpenAPIとセキュリティの実践
- 05JSON:API実装リファレンス
活用シーン
- 01新しいDRFエンドポイントの設計
- 02APIの検証と権限のレビュー
- 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.pyreferences/file-locations.mdjson-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.py | UUID PK、inserted_at/updated_at、JSONAPIMeta.resource_name |
| 2 | ViewSet | api/base_views.py、api/v1/views.py | BaseRLSViewSet を継承し、get_queryset() で N+1 を防止 |
| 3 | シリアライザー | api/v1/serializers.py | Read/Create/Update/Include を分け、BaseWriteSerializer を継承 |
| 4 | フィルター | api/filters.py | filterset_class を使用し、基底フィルタークラスを継承 |
| 5 | 権限 | api/base_views.py | required_permissions、set_required_permissions() |
| 6 | ページネーション | api/pagination.py | 必要に応じてカスタムページネーションクラスを使用 |
| 7 | URL ルーティング | api/v1/urls.py | trailing_slash=False、ケバブケースのパス |
| 8 | OpenAPI スキーマ | api/v1/views.py | drf-spectacular を使用した @extend_schema_view |
| 9 | テスト | api/tests/test_views.py | JSON: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>Serializer | ProviderSerializer |
| シリアライザー(作成) | <Model>CreateSerializer | ProviderCreateSerializer |
| シリアライザー(更新) | <Model>UpdateSerializer | ProviderUpdateSerializer |
| シリアライザー(Include) | <Model>IncludeSerializer | ProviderIncludeSerializer |
| フィルター | <Model>Filter | ProviderFilter |
| ViewSet | <Model>ViewSet | ProviderViewSet |
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
リソース
ローカルリファレンス
- ファイルの場所:references/file-locations.mdを参照してください
- JSON:API の規約:references/json-api-conventions.mdを参照してください
- セキュリティパターン:assets/security_patterns.pyを参照してください
Context7 MCP(推奨)
**前提条件:**最新のドキュメントを検索するには、Context7 MCP サーバーをインストールしてください。
実装やデバッグの際は、mcp_context7_query-docs を介して以下のライブラリを検索してください。
| ライブラリ | Context7 ID | 用途 |
|---|---|---|
| Django | /websites/djangoproject_en_5_2 | モデル、ORM、マイグレーション |
| DRF | /websites/django-rest-framework | ViewSet、シリアライザー、権限 |
| drf-spectacular | /tfranzel/drf-spectacular | OpenAPI スキーマ、@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を使用してください。
外部ドキュメント
- DRF ドキュメント:https://www.django-rest-framework.org/
- DRF JSON:API:https://django-rest-framework-json-api.readthedocs.io/
- drf-spectacular:https://drf-spectacular.readthedocs.io/
- django-filter:https://django-filter.readthedocs.io/