Django面接対策:ORM、ミドルウェア、DRFの実践的な質問と回答
Django面接で頻出するORM最適化(select_relatedとprefetch_related)、ミドルウェアアーキテクチャ、Django REST Frameworkのシリアライザパフォーマンス、パーミッション、ページネーションパターンを解説します。

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をどう使い分けるかが問われます。
# 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つのメソッドの違いは、トラバースするリレーションの種類によって決まります。
# 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のマネージャーパターンを理解し、再利用可能なクエリインターフェースを書けるかどうかが評価ポイントです。
# 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のメソッドは最初の呼び出し後にチェーンできません。
# 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ではCompositePrimaryKeyが導入されました。これは、複数カラムのプライマリキーが必要なモデルにとって待望の機能です。この機能に関する面接質問は増加傾向にあり、特にレガシーデータベースやデータウェアハウススキーマを扱う候補者に対して頻出します。
Djangoミドルウェア面接質問:リクエスト-レスポンスパイプライン
ミドルウェアに関する質問は、Djangoのリクエスト-レスポンスのライフサイクルに対する理解度をテストします。定番の質問は「ミドルウェアがリクエストとレスポンスを処理する順序を説明してください」というものです。
その答えには明確なパターンがあります。リクエスト時には、MIDDLEWAREに定義された順番で上から下へミドルウェアクラスが実行されます。レスポンス時には、下から上へ実行されます。このオニオンレイヤーアーキテクチャにより、リスト内の最初のミドルウェアがすべてをラップする形になります。
# 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を返すことで、チェーン全体を停止できます。残りのミドルウェアとビューは実行されません。
# 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つのオプションフックメソッドをサポートしています。面接官はこれらのフックを使って、候補者の知識の深さを測ります。
# 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では発火しません。
Djangoの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
DRF面接質問:シリアライザのパフォーマンス
Django REST Frameworkのシリアライザに関する質問は、ネストされたシリアライゼーションとそのパフォーマンスへの影響に焦点を当てています。最も頻出する質問は「N+1クエリを発生させずにネストされたリレーションをどう扱いますか」というものです。
# 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でクエリセットを最適化する必要があります。
# 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面接でよくある質問として「ユーザーのロールとオブジェクトの所有者に基づいてアクセスを制限するカスタムパーミッションを書いてください」というものがあります。
# 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# 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をオーバーライドしないと、ユーザーが所有していないオブジェクトを閲覧できるセキュリティの穴が生まれます。
has_object_permissionだけに頼り、クエリセットのフィルタリングを行わないと、リストエンドポイントが保護されない状態になります。リストビューと詳細ビューの両方でアクセス制御を実施するために、オブジェクトレベルのパーミッションとフィルタリングされたget_querysetを必ず組み合わせてください。
DRFスロットリングとページネーションパターン
スロットリングとページネーションは定番のフォローアップ質問です。面接官は、候補者が本番環境向けのAPI設定を理解しているかを確認したいと考えています。
# 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,
}# 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面接質問とDjangoミドルウェア質問で練習し、本番の面接に備えましょう。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

Django 5.2 カスタムミドルウェアとシグナル処理:技術面接で差をつける実践ガイド
Django 5.2のカスタムミドルウェアとシグナル処理を技術面接の観点から徹底解説。非同期ミドルウェア、カスタムシグナル、実装パターンを網羅した実践ガイドです。

DjangoとCelery:非同期タスク処理と面接対策ガイド 2026
DjangoとCeleryを使った非同期タスク処理の実装方法を解説。タスクルーティング、Celery Beatによるスケジューリング、本番環境設定、2026年最新の技術面接質問も網羅。

DjangoとPythonの面接質問:2026年版トップ25
DjangoとPythonの面接で最も頻出する25の質問を解説します。ORM、ビュー、ミドルウェア、Django REST Framework、シグナル、パフォーマンス最適化について、詳細な回答とコード例を掲載しています。