คำถามสัมภาษณ์งาน Django: ORM, Middleware และ DRF เจาะลึก

คำถามสัมภาษณ์งาน Django ครอบคลุมการปรับแต่ง ORM ด้วย select_related และ prefetch_related, สถาปัตยกรรม middleware, ประสิทธิภาพ serializer ของ Django REST Framework, permissions และรูปแบบ pagination

เตรียมสัมภาษณ์งาน Django ครอบคลุม ORM, Middleware และแนวคิด Django REST Framework

คำถามสัมภาษณ์งาน Django ในปี 2026 มุ่งเน้นไปที่สามเสาหลักที่แยกผู้สมัครระดับ senior ออกจากคนอื่น: ความชำนาญด้าน ORM, สถาปัตยกรรม middleware และ design pattern ของ Django REST Framework (DRF) บทความนี้วิเคราะห์คำถามที่ผู้สัมภาษณ์มักถามบ่อย พร้อมตัวอย่างโค้ดที่พร้อมใช้งานจริงโดยใช้ Django 5.2 LTS และ DRF 3.17

สิ่งที่ผู้สัมภาษณ์ทดสอบจริง

การสัมภาษณ์งาน Django ไม่ค่อยถามเรื่อง CRUD พื้นฐาน จุดเน้นได้เปลี่ยนไปที่การปรับแต่ง QuerySet (select_related vs prefetch_related) การออกแบบ middleware แบบกำหนดเอง และประสิทธิภาพ serializer ของ DRF ผู้สมัครที่สามารถอธิบายปัญหา N+1 query และเขียน viewset ที่มีประสิทธิภาพได้อย่างสม่ำเสมอ จะโดดเด่นกว่าผู้ที่รู้จักเพียง generic views

คำถาม ORM Django: การปรับแต่ง QuerySet

คำถาม ORM Django ที่พบบ่อยที่สุดมุ่งเน้นไปที่ปัญหา N+1 เมื่อกำหนด model ที่มีความสัมพันธ์แบบ foreign key ผู้สมัครต้องแสดงให้เห็นว่าควรใช้ 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)

ความแตกต่างระหว่างสองวิธีนี้ขึ้นอยู่กับประเภทของความสัมพันธ์ที่กำลังเข้าถึง

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 รัน query แยกต่างหากและใช้งานได้กับ ManyToManyField และความสัมพันธ์ reverse ForeignKey การใช้ผิดวิธีจะทำให้เกิด JOIN ที่ไม่จำเป็นบนชุดข้อมูลขนาดใหญ่ หรือเกิดปัญหา N+1 ที่ร้ายแรง

ORM ขั้นสูง: Custom Manager และ QuerySet Chaining

ผู้สัมภาษณ์มักขอให้ผู้สมัครเขียน custom manager ที่ห่อหุ้ม business logic เป้าหมายคือการตรวจสอบว่าผู้สมัครเข้าใจ manager pattern ของ Django และสามารถเขียน query interface ที่นำกลับมาใช้ได้

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

คำถามต่อเนื่องที่สำคัญ: "ทำไมใช้ custom QuerySet แทนที่จะใช้แค่ Manager?" คำตอบคือ chaining method ของ custom QuerySet สามารถ chain ต่อกันได้ แต่ method ของ Manager ไม่สามารถ chain หลังจากการเรียกครั้งแรก

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: Composite Primary Keys

Django 5.2 เปิดตัว CompositePrimaryKey ซึ่งเป็นฟีเจอร์ที่รอคอยมานานสำหรับ model ที่ต้องการ primary key หลายคอลัมน์ คำถามสัมภาษณ์เกี่ยวกับฟีเจอร์นี้เพิ่มมากขึ้นเรื่อย โดยเฉพาะสำหรับผู้สมัครที่ทำงานกับฐานข้อมูล legacy หรือ schema ของ data warehouse

คำถาม Middleware Django: Pipeline Request-Response

คำถามเกี่ยวกับ middleware ทดสอบความเข้าใจของผู้สมัครเกี่ยวกับวงจรชีวิต request-response ของ Django คำถามมาตรฐานคือ: "อธิบายลำดับการทำงานของ middleware เมื่อประมวลผล request และ response"

คำตอบเป็นไปตามรูปแบบที่แน่นอน ในขั้นตอน request คลาส middleware จะทำงานจากบนลงล่างตามลำดับที่กำหนดใน MIDDLEWARE ในขั้นตอน response จะทำงานจากล่างขึ้นบน สถาปัตยกรรมแบบชั้นหัวหอมนี้หมายความว่า 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

คำถามต่อเนื่องที่พบบ่อย: "ทำอย่างไรให้ตัดสายการทำงานของ middleware chain?" การคืนค่า HttpResponse จาก __call__ ก่อนเรียก self.get_response(request) จะหยุดทั้งสายทันที middleware ที่เหลือและ view จะไม่ถูกเรียกใช้งาน

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)

Hook ของ Middleware: process_view, process_exception และ process_template_response

นอกจาก __call__ แล้ว middleware ของ Django ยังรองรับ hook method เสริมอีกสามตัว ผู้สัมภาษณ์ใช้คำถามเหล่านี้เพื่อวัดความลึกของความรู้

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 แต่ก่อนที่ view จะทำงาน การคืนค่า None จะทำงานต่อไป การคืนค่า HttpResponse จะตัดสายการทำงาน process_exception จะถูกเรียกเฉพาะเมื่อเกิด exception ที่ยังไม่ได้จัดการ process_template_response จะถูกเรียกเฉพาะกับออบเจกต์ TemplateResponse ไม่ใช่ HttpResponse ทั่วไป

พร้อมที่จะพิชิตการสัมภาษณ์ Django แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

คำถาม DRF: ประสิทธิภาพ Serializer

คำถาม serializer ของ Django REST Framework มุ่งเน้นไปที่ nested serialization และผลกระทบด้านประสิทธิภาพของแต่ละวิธี คำถามที่พบบ่อยที่สุด: "จะจัดการความสัมพันธ์แบบซ้อนอย่างไรโดยไม่ทำให้เกิด N+1 query?"

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

Serializer เพียงอย่างเดียวไม่สามารถแก้ปัญหาประสิทธิภาพได้ ViewSet ต้องปรับแต่ง queryset ด้วย

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

หากไม่มี select_related และ prefetch_related ใน ViewSet developer แต่ละคนที่ถูก serialize จะสร้าง query แยกต่างหากสำหรับ company และ skills สำหรับรายการ 50 developer นั่นหมายถึง 1 + 50 + 50 = 101 query แทนที่จะเป็น 3

Custom Permissions และ Authentication ใน DRF

คำถาม DRF ที่พบบ่อย: "เขียน custom permission ที่จำกัดการเข้าถึงตาม role ของผู้ใช้และเจ้าของออบเจกต์"

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 ทำงานทุก request (ระดับรายการ) ในขณะที่ has_object_permission ทำงานเฉพาะเมื่อ get_object() ถูกเรียก (ระดับรายละเอียด) การลืม override get_queryset สำหรับ list view จะสร้างช่องโหว่ด้านความปลอดภัยที่ผู้ใช้สามารถเห็นออบเจกต์ที่ไม่ใช่ของตนเอง

ข้อผิดพลาดด้านความปลอดภัยที่พบบ่อยใน DRF

การพึ่งพา has_object_permission เพียงอย่างเดียวโดยไม่กรอง queryset จะทำให้ endpoint รายการไม่ได้รับการป้องกัน ควรใช้ permission ระดับออบเจกต์ร่วมกับ get_queryset ที่กรองแล้วเสมอเพื่อควบคุมการเข้าถึงทั้งใน view รายการและรายละเอียด

รูปแบบ Throttling และ Pagination ใน DRF

Throttling และ pagination เป็นคำถามต่อเนื่องที่มาตรฐาน ผู้สัมภาษณ์ต้องการเห็นว่าผู้สมัครเข้าใจการตั้งค่า API สำหรับงาน production

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

Cursor-based pagination มีประสิทธิภาพดีกว่า offset pagination บนชุดข้อมูลขนาดใหญ่เพราะไม่ต้องนับจำนวนแถวทั้งหมด ข้อแลกเปลี่ยนคือ client ไม่สามารถกระโดดไปยังหน้าใดก็ได้ นี่คือคำตอบที่คาดหวังเมื่อผู้สัมภาษณ์ถามว่า "ทำไมถึงเลือก cursor pagination แทน page number pagination?"

สรุป

ประเด็นสำคัญสำหรับการเตรียมสัมภาษณ์งาน Django:

  • การปรับแต่ง ORM: เลือกวิธี query ให้ตรงกับประเภทความสัมพันธ์เสมอ select_related สำหรับ ForeignKey/OneToOne และ prefetch_related สำหรับ ManyToMany และ reverse FK Custom QuerySet ช่วยให้สร้าง business logic ที่ chain และนำกลับมาใช้ได้
  • สถาปัตยกรรม middleware: Request ไหลจากบนลงล่าง response จากล่างขึ้นบน การตัดสายโดยการคืนค่า response ก่อน get_response() เป็น pattern พื้นฐานสำหรับ rate limiting การตรวจสอบสิทธิ์ และการ validate request
  • ประสิทธิภาพ serializer DRF: Nested serializer ต้องการการปรับแต่ง queryset อย่างชัดเจนใน ViewSet หากไม่มี select_related/prefetch_related การ serialize จะทำให้เกิด N+1 query ในระดับที่ใหญ่ขึ้น
  • Permissions DRF: ใช้ has_object_permission ร่วมกับ get_queryset ที่กรองแล้วเพื่อควบคุมการเข้าถึงทั้ง endpoint รายการและรายละเอียด Permission ระดับออบเจกต์เพียงอย่างเดียวไม่ป้องกัน list view
  • Throttling และ pagination: Cursor pagination รองรับการขยายตัวได้ดีกว่า offset บนตารางขนาดใหญ่ ตั้งค่า throttle rate แยกต่างหากสำหรับผู้ใช้ที่ไม่ได้ยืนยันตัวและผู้ใช้ที่ยืนยันแล้ว

ฝึกฝน pattern เหล่านี้กับ คำถามสัมภาษณ์ Django ORM และ คำถาม middleware Django บน SharpSkill เพื่อสร้างความมั่นใจก่อนเข้าสัมภาษณ์จริง

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#django
#python
#interview
#orm
#middleware
#drf
#rest-api

แชร์

บทความที่เกี่ยวข้อง

Django 5.2 custom middleware และ signal handling คู่มือเตรียมสัมภาษณ์

Django 5.2 Custom Middleware และ Signal Handling: คู่มือเตรียมสัมภาษณ์เชิงเทคนิค

คู่มือเชิงลึก Django 5.2 custom middleware และ signal handling สำหรับการสัมภาษณ์เชิงเทคนิค ครอบคลุม request pipeline, async middleware, post_save, pre_save, custom signals และแนวทางปฏิบัติที่ดีสำหรับ production

บทช่วยสอน Django 6.0 composite primary key และ background task

Django 6.0 ในปี 2026: Composite Primary Key, Background Task และคำถามสัมภาษณ์งาน

คู่มือฉบับสมบูรณ์เกี่ยวกับ Django 6.0 ครอบคลุม composite primary key, framework background task ในตัว, template partial, CSP middleware พร้อมตัวอย่างโค้ดจริงและการเตรียมตัวสัมภาษณ์

Django และ Celery การประมวลผลงานแบบอะซิงโครนัส

Django และ Celery: การประมวลผลงานแบบอะซิงโครนัสและคำถามสัมภาษณ์ 2026

คู่มือ Django Celery ฉบับสมบูรณ์พร้อมตัวอย่างโค้ดจริง, task routing, การตั้งเวลา Celery Beat, การกำหนดค่า production และคำถามสัมภาษณ์ทางเทคนิค 2026