# Django面接対策:ORM、ミドルウェア、DRFの実践的な質問と回答 > Django面接で頻出するORM最適化(select_relatedとprefetch_related)、ミドルウェアアーキテクチャ、Django REST Frameworkのシリアライザパフォーマンス、パーミッション、ページネーションパターンを解説します。 - Published: 2026-04-03 - Updated: 2026-04-03 - Author: SharpSkill - Tags: django, python, interview, orm, middleware, drf, rest-api - Reading time: 9 min --- Django面接では、シニアエンジニアとそうでない候補者を分ける3つの柱が存在します。ORM最適化、ミドルウェアアーキテクチャ、そしてDjango REST Framework(DRF)の設計パターンです。本記事では、2026年の採用面接で実際に出題される質問を、Django 5.2 LTSとDRF 3.17の本番レベルのコード例とともに解説します。 > **面接官が本当にテストしていること** > > Django面接で基本的なCRUDが問われることはほとんどありません。現在の面接では、QuerySetの最適化(select_related vs prefetch_related)、カスタムミドルウェアの設計、DRFシリアライザのパフォーマンスが重点的にテストされます。N+1クエリの問題を説明でき、効率的なViewSetを書ける候補者は、ジェネリックビューの知識だけの候補者を圧倒的に上回ります。 ## Django ORM面接質問:QuerySetの最適化 Django ORMの面接で最も頻出する質問は、N+1問題に関するものです。外部キーのリレーションを持つモデルに対して、`select_related`と`prefetch_related`をどう使い分けるかが問われます。 ```python # models.py from django.db import models class Company(models.Model): name = models.CharField(max_length=200) founded_year = models.IntegerField() class Developer(models.Model): name = models.CharField(max_length=200) company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="developers") skills = models.ManyToManyField("Skill", related_name="developers") class Skill(models.Model): name = models.CharField(max_length=100) category = models.CharField(max_length=50) ``` 2つのメソッドの違いは、トラバースするリレーションの種類によって決まります。 ```python # queries.py — Correct approach for ForeignKey (single object) developers = Developer.objects.select_related("company").all() # Generates ONE SQL query with JOIN # SELECT developer.*, company.* FROM developer INNER JOIN company ... # Correct approach for ManyToMany (multiple objects) developers = Developer.objects.prefetch_related("skills").all() # Generates TWO SQL queries: # 1. SELECT * FROM developer # 2. SELECT * FROM skill INNER JOIN developer_skills WHERE developer_id IN (...) ``` `select_related`はSQL JOINを実行し、ForeignKeyとOneToOneFieldで動作します。`prefetch_related`は別のクエリを実行し、ManyToManyFieldと逆方向のForeignKeyリレーションで動作します。これらを混同すると、大規模データセットで不要なJOINが発生するか、厄介なN+1パターンが発生します。 ## 高度なORM:カスタムManagerとQuerySetチェーン 面接官は、ビジネスロジックをカプセル化するカスタムManagerの実装を求めることが多いです。Djangoのマネージャーパターンを理解し、再利用可能なクエリインターフェースを書けるかどうかが評価ポイントです。 ```python # managers.py from django.db import models from django.utils import timezone class ActiveDeveloperQuerySet(models.QuerySet): def active(self): """Filter developers who logged in within the last 30 days.""" cutoff = timezone.now() - timezone.timedelta(days=30) return self.filter(last_login__gte=cutoff) def senior(self): """Filter developers with 5+ years of experience.""" return self.filter(years_experience__gte=5) def by_skill(self, skill_name): """Filter developers by a specific skill.""" return self.filter(skills__name=skill_name) class ActiveDeveloperManager(models.Manager): def get_queryset(self): return ActiveDeveloperQuerySet(self.model, using=self._db) def active(self): return self.get_queryset().active() ``` よくあるフォローアップの質問は「なぜManagerだけでなく、カスタムQuerySetを使うのか」というものです。答えはチェーンにあります。カスタムQuerySetのメソッドは連鎖して呼び出せますが、Managerのメソッドは最初の呼び出し後にチェーンできません。 ```python # usage.py — QuerySet chaining in action # This works because each method returns a QuerySet senior_python_devs = ( Developer.active_objects # custom manager .active() # ActiveDeveloperQuerySet method .senior() # chains another QuerySet method .by_skill("Python") # chains a third method .select_related("company") # standard QuerySet method still works ) ``` > **Django 5.2 LTS:複合プライマリキー** > > Django 5.2ではCompositePrimaryKeyが導入されました。これは、複数カラムのプライマリキーが必要なモデルにとって待望の機能です。この機能に関する面接質問は増加傾向にあり、特にレガシーデータベースやデータウェアハウススキーマを扱う候補者に対して頻出します。 ## Djangoミドルウェア面接質問:リクエスト-レスポンスパイプライン ミドルウェアに関する質問は、Djangoのリクエスト-レスポンスのライフサイクルに対する理解度をテストします。定番の質問は「ミドルウェアがリクエストとレスポンスを処理する順序を説明してください」というものです。 その答えには明確なパターンがあります。リクエスト時には、`MIDDLEWARE`に定義された順番で上から下へミドルウェアクラスが実行されます。レスポンス時には、下から上へ実行されます。このオニオンレイヤーアーキテクチャにより、リスト内の最初のミドルウェアがすべてをラップする形になります。 ```python # middleware.py import time import logging from django.http import JsonResponse logger = logging.getLogger(__name__) class RequestTimingMiddleware: """Logs the time taken to process each request.""" def __init__(self, get_response): self.get_response = get_response # next middleware or view def __call__(self, request): start_time = time.monotonic() response = self.get_response(request) # passes to next layer duration_ms = (time.monotonic() - start_time) * 1000 logger.info( "method=%s path=%s status=%d duration=%.2fms", request.method, request.path, response.status_code, duration_ms, ) return response ``` よくあるフォローアップは「ミドルウェアチェーンをショートサーキットするにはどうしますか」という質問です。`__call__`内で`self.get_response(request)`を呼ぶ前にHttpResponseを返すことで、チェーン全体を停止できます。残りのミドルウェアとビューは実行されません。 ```python # middleware.py — Rate limiting with short-circuit from django.core.cache import cache class RateLimitMiddleware: """Blocks requests exceeding 100 per minute per IP.""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): ip = request.META.get("REMOTE_ADDR") cache_key = f"rate_limit:{ip}" request_count = cache.get(cache_key, 0) if request_count >= 100: return JsonResponse( # short-circuits — view never executes {"error": "Rate limit exceeded. Try again in 60 seconds."}, status=429, ) cache.set(cache_key, request_count + 1, timeout=60) return self.get_response(request) ``` ## ミドルウェアフック:process_view、process_exception、process_template_response `__call__`以外に、Djangoミドルウェアは3つのオプションフックメソッドをサポートしています。面接官はこれらのフックを使って、候補者の知識の深さを測ります。 ```python # middleware.py — Full middleware with all hooks class AuditMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): """Called after URL resolution, before the view executes.""" logger.info("Calling view: %s", view_func.__name__) return None # returning None continues normal processing def process_exception(self, request, exception): """Called only if the view raises an exception.""" logger.error("View exception: %s", exception, exc_info=True) return None # returning None lets Django's default handling proceed def process_template_response(self, request, response): """Called if the response has a render() method (TemplateResponse).""" response.context_data["audit_timestamp"] = time.time() return response # must return a response with render() ``` `process_view`はURL解決後、ビュー実行前に発火します。`None`を返すと実行が続行し、`HttpResponse`を返すとショートサーキットされます。`process_exception`は未処理の例外が発生した場合のみ発火します。`process_template_response`は`TemplateResponse`オブジェクトに対してのみ発火し、通常の`HttpResponse`では発火しません。 ## DRF面接質問:シリアライザのパフォーマンス [Django REST Framework](https://www.django-rest-framework.org/)のシリアライザに関する質問は、ネストされたシリアライゼーションとそのパフォーマンスへの影響に焦点を当てています。最も頻出する質問は「N+1クエリを発生させずにネストされたリレーションをどう扱いますか」というものです。 ```python # serializers.py from rest_framework import serializers from .models import Developer, Company, Skill class SkillSerializer(serializers.ModelSerializer): class Meta: model = Skill fields = ["name", "category"] class CompanySerializer(serializers.ModelSerializer): class Meta: model = Company fields = ["name", "founded_year"] class DeveloperSerializer(serializers.ModelSerializer): company = CompanySerializer(read_only=True) # nested FK skills = SkillSerializer(many=True, read_only=True) # nested M2M class Meta: model = Developer fields = ["id", "name", "company", "skills"] ``` シリアライザだけではパフォーマンスの問題は解決しません。ViewSetでクエリセットを最適化する必要があります。 ```python # views.py from rest_framework import viewsets from .models import Developer from .serializers import DeveloperSerializer class DeveloperViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = DeveloperSerializer def get_queryset(self): return ( Developer.objects .select_related("company") # JOIN for FK .prefetch_related("skills") # separate query for M2M .order_by("-id") ) ``` ViewSetで`select_related`と`prefetch_related`を指定しない場合、シリアライズされる各developerごとにcompanyとskillsの個別クエリが発行されます。50人のdeveloperのリストでは、3クエリで済むはずが1 + 50 + 50 = 101クエリになります。 ## DRFカスタムパーミッションと認証 DRF面接でよくある質問として「ユーザーのロールとオブジェクトの所有者に基づいてアクセスを制限するカスタムパーミッションを書いてください」というものがあります。 ```python # permissions.py from rest_framework.permissions import BasePermission class IsOwnerOrAdmin(BasePermission): """ Object-level permission: - Admin users can access any object - Regular users can only access objects they own """ message = "Access restricted to the object owner or admin users." def has_object_permission(self, request, view, obj): if request.user.is_staff: return True # Assumes the model has an 'owner' field pointing to User return obj.owner == request.user ``` ```python # views.py — Applying custom permissions from rest_framework import viewsets, permissions from .permissions import IsOwnerOrAdmin class ProjectViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated, IsOwnerOrAdmin] def get_queryset(self): # Non-admin users only see their own projects if self.request.user.is_staff: return Project.objects.all() return Project.objects.filter(owner=self.request.user) def perform_create(self, serializer): serializer.save(owner=self.request.user) # auto-assign owner ``` 面接官が注目するポイントは次の通りです。`has_permission`はすべてのリクエストで実行されます(リストレベル)。一方、`has_object_permission`は`get_object()`が呼ばれたときにのみ実行されます(詳細レベル)。リストビューで`get_queryset`をオーバーライドしないと、ユーザーが所有していないオブジェクトを閲覧できるセキュリティの穴が生まれます。 > **DRFでよくあるセキュリティミス** > > has_object_permissionだけに頼り、クエリセットのフィルタリングを行わないと、リストエンドポイントが保護されない状態になります。リストビューと詳細ビューの両方でアクセス制御を実施するために、オブジェクトレベルのパーミッションとフィルタリングされたget_querysetを必ず組み合わせてください。 ## DRFスロットリングとページネーションパターン スロットリングとページネーションは定番のフォローアップ質問です。面接官は、候補者が本番環境向けのAPI設定を理解しているかを確認したいと考えています。 ```python # settings.py — Production DRF configuration REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "20/minute", # unauthenticated users "user": "200/minute", # authenticated users }, "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination", "PAGE_SIZE": 25, } ``` ```python # pagination.py — Custom cursor pagination for consistent ordering from rest_framework.pagination import CursorPagination class CreatedAtCursorPagination(CursorPagination): page_size = 25 ordering = "-created_at" # must be a unique, sequential field cursor_query_param = "cursor" # ?cursor=abc123 ``` カーソルベースのページネーションは、大規模データセットにおいてオフセットページネーションよりも優れたパフォーマンスを発揮します。総行数のカウントが不要だからです。トレードオフとして、クライアントは任意のページにジャンプできません。「なぜページ番号ベースのページネーションではなくカーソルページネーションを選ぶのか」と聞かれたとき、これが期待される回答です。 ## まとめ Django面接対策の重要ポイントは以下の通りです。 - **ORM最適化**:クエリメソッドをリレーションの種類に合わせることが必須です。ForeignKey/OneToOneには`select_related`、ManyToManyと逆方向FKには`prefetch_related`を使用します。カスタムQuerySetはチェーン可能で再利用可能なビジネスロジックを実現します。 - **ミドルウェアアーキテクチャ**:リクエストは上から下へ、レスポンスは下から上へ流れます。`get_response()`の前にレスポンスを返すショートサーキットは、レート制限、認証チェック、リクエストバリデーションの基本パターンです。 - **DRFシリアライザのパフォーマンス**:ネストされたシリアライザには、ViewSetでの明示的なクエリセット最適化が必要です。`select_related`/`prefetch_related`なしでは、シリアライゼーションがスケール時にN+1クエリを発生させます。 - **DRFパーミッション**:リストビューと詳細ビューの両方でアクセス制御を実施するために、`has_object_permission`とフィルタリングされた`get_queryset`を組み合わせます。オブジェクトレベルのパーミッションだけでは、リストビューは保護されません。 - **スロットリングとページネーション**:カーソルページネーションは大規模テーブルでオフセットよりもスケールします。匿名ユーザーと認証済みユーザーで別々にスロットルレートを設定します。 SharpSkillの[Django ORM面接質問](/technologies/django/interview-questions/django-orm-advanced)と[Djangoミドルウェア質問](/technologies/django/interview-questions/django-middleware)で練習し、本番の面接に備えましょう。 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/django/django-interview-orm-middleware-drf