# 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. - Published: 2026-04-03 - Updated: 2026-04-03 - Author: SharpSkill - Tags: django, python, interview, orm, middleware, drf, rest-api - Reading time: 9 min --- 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. > **Người phỏng vấn thực sự kiểm tra gì** > > 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`. ```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) ``` 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. ```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` 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. ```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() ``` 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. ```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 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ứ. ```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 ``` Câ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. ```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 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. ```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` đượ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. ## Câu hỏi DRF: Hiệu suất Serializer Câu hỏi serializer [Django REST Framework](https://www.django-rest-framework.org/) 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?" ```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"] ``` 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. ```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") ) ``` 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." ```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 ``` Đ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ọ. > **Lỗi bảo mật DRF phổ biến** > > 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. ```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 ``` Pagination 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_related` cho ForeignKey/OneToOne, `prefetch_related` cho 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_permission` với `get_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](/technologies/django/interview-questions/django-orm-advanced) và [câu hỏi middleware Django](/technologies/django/interview-questions/django-middleware) trên SharpSkill để xây dựng sự tự tin trước buổi phỏng vấn thật. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/django/django-interview-orm-middleware-drf