Câu hỏi phỏng vấn Django: ORM, Middleware và DRF chi tiết
Câu hỏi phỏng vấn Django bao gồm tối ưu hóa ORM với select_related và prefetch_related, kiến trúc middleware, hiệu suất serializer Django REST Framework, permissions và các pattern pagination.

Câu hỏi phỏng vấn Django năm 2026 tập trung vào ba trụ cột chính giúp phân biệt ứng viên senior với những người còn lại: làm chủ ORM, kiến trúc middleware và các design pattern trong Django REST Framework (DRF). Bài viết này phân tích những câu hỏi mà nhà tuyển dụng thường đặt ra, kèm theo ví dụ code sẵn sàng cho production sử dụng Django 5.2 LTS và DRF 3.17.
Phỏng vấn Django hiếm khi hỏi về CRUD cơ bản. Trọng tâm đã chuyển sang tối ưu hóa QuerySet (select_related vs prefetch_related), thiết kế middleware tùy chỉnh và hiệu suất serializer DRF. Ứng viên có khả năng giải thích vấn đề N+1 query và viết viewset hiệu quả luôn vượt trội so với những người chỉ biết generic views.
Câu hỏi ORM Django: Tối ưu hóa QuerySet
Câu hỏi ORM Django phổ biến nhất nhắm vào vấn đề N+1. Với một model có các quan hệ foreign key, ứng viên phải chứng minh khi nào nên sử dụng select_related và khi nào nên sử dụng 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)Sự khác biệt giữa hai phương pháp này phụ thuộc vào loại quan hệ đang được truy vấn.
# 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 thực hiện SQL JOIN và hoạt động với ForeignKey cũng như OneToOneField. prefetch_related thực thi query riêng biệt và hoạt động với ManyToManyField cũng như quan hệ reverse ForeignKey. Sử dụng sai cách dẫn đến JOIN không cần thiết trên tập dữ liệu lớn hoặc vấn đề N+1 nghiêm trọng.
ORM Nâng cao: Custom Manager và QuerySet Chaining
Người phỏng vấn thường yêu cầu ứng viên viết một custom manager đóng gói logic nghiệp vụ. Mục đích là xác minh rằng ứng viên hiểu pattern manager của Django và có thể viết các giao diện query tái sử dụng được.
# 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()Câu hỏi tiếp theo then chốt: "Tại sao sử dụng custom QuerySet thay vì chỉ Manager?" Câu trả lời là chaining. Các method của custom QuerySet có thể được chain với nhau, trong khi method của Manager không thể chain sau lần gọi đầu tiên.
# 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 giới thiệu CompositePrimaryKey, một tính năng được chờ đợi từ lâu cho các model cần primary key nhiều cột. Câu hỏi phỏng vấn về tính năng này ngày càng phổ biến, đặc biệt với ứng viên làm việc với cơ sở dữ liệu legacy hoặc schema data warehouse.
Câu hỏi Middleware Django: Pipeline Request-Response
Câu hỏi middleware kiểm tra sự hiểu biết của ứng viên về vòng đời request-response trong Django. Câu hỏi tiêu chuẩn: "Giải thích thứ tự middleware xử lý request và response."
Câu trả lời tuân theo một pattern nghiêm ngặt. Trong quá trình request, các lớp middleware được thực thi từ trên xuống dưới theo thứ tự định nghĩa trong MIDDLEWARE. Trong quá trình response, chúng thực thi từ dưới lên trên. Kiến trúc lớp hành này có nghĩa là middleware đầu tiên trong danh sách bao bọc mọi thứ.
# 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 responseCâu hỏi tiếp theo phổ biến: "Làm thế nào để cắt ngang chuỗi middleware?" Trả về HttpResponse từ __call__ trước khi gọi self.get_response(request) sẽ dừng toàn bộ chuỗi. Các middleware còn lại và view sẽ không bao giờ được thực thi.
# 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 và process_template_response
Ngoài __call__, middleware Django hỗ trợ ba method hook tùy chọn. Người phỏng vấn sử dụng các câu hỏi này để đánh giá chiều sâu kiến thức.
# 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 được gọi sau khi phân giải URL nhưng trước khi view thực thi. Trả về None tiếp tục thực thi; trả về HttpResponse cắt ngang chuỗi. process_exception chỉ được gọi khi có exception chưa xử lý. process_template_response chỉ được gọi với đối tượng TemplateResponse, không phải HttpResponse thông thường.
Sẵn sàng chinh phục phỏng vấn Django?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Câu hỏi DRF: Hiệu suất Serializer
Câu hỏi serializer Django REST Framework tập trung vào serialization lồng nhau và tác động hiệu suất của các cách tiếp cận khác nhau. Câu hỏi thường gặp nhất: "Làm thế nào xử lý các quan hệ lồng nhau mà không gây ra N+1 query?"
# 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"]Riêng serializer không giải quyết được vấn đề hiệu suất. ViewSet phải tối ưu hóa queryset.
# 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")
)Nếu không có select_related và prefetch_related trong ViewSet, mỗi developer được serialize sẽ kích hoạt các query riêng lẻ cho company và skills. Trên danh sách 50 developer, điều đó có nghĩa là 1 + 50 + 50 = 101 query thay vì 3.
Custom Permissions và Authentication trong DRF
Câu hỏi DRF thường gặp: "Viết một custom permission giới hạn truy cập dựa trên role người dùng và chủ sở hữu đối tượng."
# 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Điểm tinh tế mà người phỏng vấn tìm kiếm: has_permission chạy trên mọi request (cấp danh sách), trong khi has_object_permission chỉ chạy khi get_object() được gọi (cấp chi tiết). Quên override get_queryset cho list view tạo ra lỗ hổng bảo mật nơi người dùng có thể xem đối tượng không thuộc về họ.
Chỉ dựa vào has_object_permission mà không lọc queryset sẽ khiến endpoint danh sách không được bảo vệ. Luôn kết hợp permission cấp đối tượng với get_queryset đã lọc để đảm bảo kiểm soát truy cập trên cả view danh sách và chi tiết.
Pattern Throttling và Pagination trong DRF
Throttling và pagination là các câu hỏi tiếp theo tiêu chuẩn. Người phỏng vấn muốn thấy rằng ứng viên hiểu cấu hình API sẵn sàng cho production.
# 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=abc123Pagination dựa trên cursor vượt trội hơn offset pagination trên tập dữ liệu lớn vì không cần đếm tổng số hàng. Đánh đổi: client không thể nhảy đến trang bất kỳ. Đây là câu trả lời được mong đợi khi người phỏng vấn hỏi "Tại sao bạn chọn cursor pagination thay vì page number pagination?"
Kết luận
Những điểm chính cho việc chuẩn bị phỏng vấn Django:
- Tối ưu hóa ORM: Luôn phù hợp phương pháp query với loại quan hệ.
select_relatedcho ForeignKey/OneToOne,prefetch_relatedcho ManyToMany và reverse FK. Custom QuerySet cho phép logic nghiệp vụ có thể chain và tái sử dụng. - Kiến trúc middleware: Request đi từ trên xuống dưới, response từ dưới lên trên. Cắt ngang bằng cách trả về response trước
get_response()là pattern cơ bản cho rate limiting, kiểm tra xác thực và validation request. - Hiệu suất serializer DRF: Serializer lồng nhau yêu cầu tối ưu hóa queryset rõ ràng trong ViewSet. Không có
select_related/prefetch_related, serialization gây ra N+1 query ở quy mô lớn. - Permissions DRF: Kết hợp
has_object_permissionvớiget_querysetđã lọc để đảm bảo kiểm soát truy cập trên cả endpoint danh sách và chi tiết. Permission cấp đối tượng đơn lẻ không bảo vệ list view. - Throttling và pagination: Cursor pagination có khả năng mở rộng tốt hơn offset trên bảng lớn. Cấu hình throttle rate riêng cho người dùng ẩn danh và đã xác thực.
Luyện tập các pattern này với câu hỏi phỏng vấn Django ORM và câu hỏi middleware Django trên SharpSkill để xây dựng sự tự tin trước buổi phỏng vấn thật.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Django 5.2: Middleware Tùy Chỉnh và Xử Lý Signal trong Phỏng Vấn Kỹ Thuật Python
Hướng dẫn chi tiết về custom middleware và signal handling trong Django 5.2 cho phỏng vấn kỹ thuật. Bao gồm request pipeline, async middleware, post_save, pre_save và custom signals với mã mẫu thực tế.

Django 6.0 năm 2026: Composite Primary Key, Background Task và Câu hỏi phỏng vấn
Hướng dẫn toàn diện về Django 6.0 bao gồm composite primary key, framework background task tích hợp, template partial, CSP middleware cùng ví dụ code thực tế và chuẩn bị phỏng vấn.

Django va Celery: Xu ly Tac vu Bat dong bo va Cau hoi Phong van 2026
Huong dan Django Celery day du voi ma code thuc te, task routing, lap lich Celery Beat, cau hinh production va cau hoi phong van ky thuat 2026.