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

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

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

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

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

การสัมภาษณ์งาน Django ไม่ค่อยถามเรื่อง CRUD พื้นฐาน จุดเน้นได้เปลี่ยนไปที่การปรับแต่ง QuerySet (select_related vs prefetch_related, โหมด FETCH_PEERS ใหม่) การออกแบบ 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 ที่ร้ายแรง

Django 6.1: โหมด FETCH_PEERS สำหรับการป้องกัน N+1 อัตโนมัติ

Django 6.1 เปิดตัวระบบ fetch mode ใหม่ที่แก้ไขปัญหา N+1 query ในระดับ ORM เมื่อทำการวนซ้ำบน queryset และเข้าถึง field ที่มีความสัมพันธ์ FETCH_PEERS จะดึง field นั้นสำหรับทุก instance ใน queryset ด้วย query เพิ่มเติมเพียงครั้งเดียว

python
# views.py — Django 6.1 FETCH_PEERS mode
from django.db import models
from .models import Book

# Traditional approach: requires explicit prefetch_related
books = Book.objects.prefetch_related("author").all()

# Django 6.1 approach: automatic peer fetching
books = Book.objects.fetch_mode(models.FETCH_PEERS)
for book in books:
    print(book.author.name)  # Only 2 queries total: books + all authors

มี fetch mode สามแบบ:

  • FETCH_ONE (ค่าเริ่มต้น): ดึง field ที่หายไปสำหรับ instance ปัจจุบันเท่านั้น
  • FETCH_PEERS: ดึง field สำหรับทุก instance จาก QuerySet เดียวกัน
  • FETCH_RAISE: โยน FieldFetchBlocked สำหรับโค้ดที่ต้องการประสิทธิภาพสูงซึ่งต้องป้องกัน lazy loading

สำหรับวัตถุประสงค์การสัมภาษณ์ ผู้สมัครควรรู้ทั้งสองวิธี: select_related/prefetch_related แบบชัดเจนสำหรับการควบคุมที่ละเอียด และ FETCH_PEERS สำหรับการปรับแต่งอัตโนมัติเมื่อวนซ้ำบน queryset

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 6.0: CompositePrimaryKey และ Background Tasks

Django 6.0 เปิดตัว CompositePrimaryKey สำหรับ model ที่ต้องการ primary key หลายคอลัมน์ รวมถึง framework background tasks ในตัว คำถามสัมภาษณ์เกี่ยวกับฟีเจอร์เหล่านี้เพิ่มมากขึ้น โดยเฉพาะสำหรับผู้สมัครที่ทำงานกับฐานข้อมูล legacy หรือความต้องการประมวลผลแบบ async

คำถาม 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 โหมด FETCH_PEERS ของ Django 6.1 ให้การป้องกัน N+1 อัตโนมัติเมื่อวนซ้ำบน queryset
  • Custom QuerySets: ช่วยให้สร้าง business logic ที่ chain และนำกลับมาใช้ได้ method ของ custom Manager ไม่สามารถ chain หลังจากการเรียกครั้งแรก แต่ method ของ QuerySet ทำได้
  • สถาปัตยกรรม 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 เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 13 กันยายน 2569

แท็ก

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

แชร์

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

ไดอะแกรมสถาปัตยกรรม Django middleware แสดงการไหลของ request-response

Django Middleware ขั้นสูง 2026: Custom Middleware, Logging และคำถามสัมภาษณ์งาน

คู่มือฉบับสมบูรณ์เกี่ยวกับ Django middleware ในปี 2026 ครอบคลุมการสร้าง custom middleware การใช้งาน logging อย่างมีประสิทธิภาพ และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Django ระดับ senior

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

เจาะลึก Serializer ใน Django REST Framework: Validation, Nested และการแก้ปัญหา N+1

เจาะลึก Serializer ใน Django REST Framework: Validation, Nested และการแก้ปัญหา N+1

คู่มือครบถ้วนสำหรับการใช้งาน DRF serializers: pipeline validation, nested serializers, การจัดการความสัมพันธ์ที่ซับซ้อน และเทคนิคการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงปัญหา N+1 queries