django-drf
Django REST Frameworkのパターンを提供します。ViewSet・シリアライザー・ルーター・パーミッション・フィルターセットなど、汎用的なDRF APIを実装する際にトリガーされます。Prowler API固有の機能(RLS/RBAC/プロバイダー)には、prowler-apiスキルも併用してください。
description の原文を見る
> Django REST Framework patterns. Trigger: When implementing generic DRF APIs (ViewSets, serializers, routers, permissions, filtersets). For Prowler API specifics (RLS/RBAC/Providers), also use prowler-api.
SKILL.md 本文
重要なパターン
- シリアライザーは常に操作ごとに分離する: Read / Create / Update / Include
- 複雑なフィルタリングには常に
filterset_classを使用する (filterset_fieldsではなく) - 書き込みシリアライザーで常に不明なフィールドを検証する (
BaseWriteSerializerを継承) get_queryset()で常にselect_related/prefetch_relatedを使用して N+1 を回避する- スキーマ生成のために常に
get_queryset()でswagger_fake_viewを処理する SerializerMethodFieldの OpenAPI ドキュメント用に常に@extend_schema_fieldを使用する- シリアライザーにビジネスロジックを絶対に置かない - services/utils を使用する
- 自動増分 PK を絶対に使用しない - UUIDv4 または UUIDv7 を使用する
- URL で末尾スラッシュを絶対に使用しない (
trailing_slash=False)
注:
swagger_fake_viewは OpenAPI スキーマ生成のための drf-spectacular に固有です。
実装チェックリスト
新しいエンドポイントを実装する場合、これらのパターンを順序に従って確認してください:
| # | パターン | リファレンス | 主要ポイント |
|---|---|---|---|
| 1 | Models | api/models.py | UUID PK、inserted_at/updated_at、JSONAPIMeta.resource_name |
| 2 | ViewSets | api/base_views.py、api/v1/views.py | BaseRLSViewSet を継承、N+1 防止を伴う get_queryset() |
| 3 | Serializers | api/v1/serializers.py | Read/Create/Update/Include を分離、BaseWriteSerializer を継承 |
| 4 | Filters | api/filters.py | filterset_class を使用、基本フィルタークラスを継承 |
| 5 | Permissions | api/base_views.py | required_permissions、set_required_permissions() |
| 6 | Pagination | api/pagination.py | 必要に応じてカスタムページング クラス |
| 7 | URL Routing | api/v1/urls.py | trailing_slash=False、ケバブケース パス |
| 8 | OpenAPI Schema | api/v1/views.py | drf-spectacular を使用した @extend_schema_view |
| 9 | Tests | 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
どの基本シリアライザーを選ぶか?
読み取り専用シリアライザー → BaseModelSerializerV1
tenant_id での作成 → RLSSerializer + BaseWriteSerializer (作成時に tenant_id を自動注入)
検証を伴う更新 → BaseWriteSerializer (tenant_id は既にオブジェクトに存在)
非モデル データ → BaseSerializerV1
どのフィルター基本クラスを選ぶか?
Provider への直接 FK → BaseProviderFilter
Scan 経由の FK → BaseScanProviderFilter
Provider 関連なし → FilterSet
どの基本 ViewSet を選ぶか?
RLS保護モデル → BaseRLSViewSet (最も一般的)
テナント操作 → BaseTenantViewset
ユーザー操作 → BaseUserViewset
RLS不要 → BaseViewSet (稀)
リソース名の形式は?
単語モデル → 複数形小文字 (Provider → providers)
複合単語モデル → 複数形小文字ケバブ (ProviderGroup → provider-groups)
Through/join → 親子パターン (UserRoleRelationship → user-roles)
集約/概要 → 説明的なケバブ複数形 (ComplianceOverview → compliance-overviews)
シリアライザー パターン
基本クラス階層
# 読み取りシリアライザー (最も一般的)
class ProviderSerializer(RLSSerializer):
class Meta:
model = Provider
fields = ["id", "provider", "uid", "alias", "connected", "inserted_at"]
# 書き込みシリアライザー (不明なフィールドを検証)
class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer):
class Meta:
model = Provider
fields = ["provider", "uid", "alias"]
# Include シリアライザー (?include= 用の疎なフィールド)
class ProviderIncludeSerializer(RLSSerializer):
class Meta:
model = Provider
fields = ["id", "alias"] # 最小限のフィールド
SerializerMethodField と OpenAPI
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,
}
インクルード シリアライザー (JSON:API)
class ScanSerializer(RLSSerializer):
included_serializers = {
"provider": "api.v1.serializers.ProviderIncludeSerializer",
}
機密データのマスキング
def to_representation(self, instance):
data = super().to_representation(instance)
# デフォルトでマスク、明示的なリクエスト時のみ公開
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 パターン
get_queryset() と N+1 防止
常に swagger_fake_view チェックと select_related/prefetch_related を組み合わせる:
def get_queryset(self):
# 必須: OpenAPI スキーマ生成のための空のクエリセットを返す
if getattr(self, "swagger_fake_view", False):
return Provider.objects.none()
# N+1 防止: 関連を先読みする
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 スキーマを生成します。このチェックがないと、リクエスト コンテキストなしで実際のクエリを実行し、失敗することができます。
アクション固有のシリアライザー
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 = [] # 読み取り専用 = パーミッション不要
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
カスタム アクション
# 詳細アクション (単一オブジェクトで動作)
@action(detail=True, methods=["post"], url_name="connection")
def connection(self, request, pk=None):
instance = self.get_object()
# インスタンスを処理...
# リスト アクション (コレクション上で動作)
@action(detail=False, methods=["get"], url_name="metadata")
def metadata(self, request):
queryset = self.filter_queryset(self.get_queryset())
# クエリセット上で集約...
フィルター パターン
基本フィルター クラス
class BaseProviderFilter(FilterSet):
"""Provider への直接 FK を持つモデル用"""
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):
"""Scan への FK を持つモデル用 (Scan は Provider への FK を持つ)"""
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 フィルタリング
# 単一値を含む
region = CharFilter(method="filter_region")
def filter_region(self, queryset, name, value):
return queryset.filter(resource_regions__contains=[value])
# 複数値の重複
region__in = CharInFilter(field_name="resource_regions", lookup_expr="overlap")
日付範囲検証
def filter_queryset(self, queryset):
# パフォーマンスのため日付フィルターを必須にする
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",
}])
# 最大範囲を検証
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. PK のみ取得 (低コスト)
pk_list = base_queryset.values_list("id", flat=True)
page = self.paginate_queryset(pk_list)
# 2. ページ用の完全なオブジェクトのみフェッチ
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. DB の順序を保持するため再ソート
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):
# 利用可能な場合はプリフェッチされたタグを使用
if hasattr(obj, "prefetched_tags"):
return {tag.key: tag.value for tag in obj.prefetched_tags}
# フォールバック (プリフェッチされていない場合 N+1 が発生)
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 で文字列補間を絶対に使用しない |
クイック リファレンス
# シリアライザーでの入力検証
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
# 明示的なフィールド (マスアサインメント防止)
class Meta:
fields = ["name", "email"] # 良い: ホワイトリスト
read_only_fields = ["id", "inserted_at"] # システムフィールド
# オブジェクト パーミッション
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 BurstRateThrottle(UserRateThrottle):
rate = "10/minute"
# 安全なエラー メッセージ (列挙防止)
def get_object(self):
try:
return super().get_object()
except Http404:
raise NotFound("Resource not found") # 一般的で内部 ID なし
コマンド
# 開発
cd api && uv run python src/backend/manage.py runserver
cd api && uv run python src/backend/manage.py shell
# データベース
cd api && uv run python src/backend/manage.py makemigrations
cd api && uv run python src/backend/manage.py migrate
# テスト
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 | ViewSets、シリアライザー、パーミッション |
| 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/
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- prowler-cloud
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/prowler-cloud/prowler / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。