# Perguntas de entrevista Django: ORM, Middleware e DRF em profundidade > Perguntas de entrevista Django sobre otimização do ORM com select_related e prefetch_related, arquitetura de middleware e desempenho de serializers no Django REST Framework, permissões e paginação. - Published: 2026-04-03 - Updated: 2026-04-03 - Author: SharpSkill - Tags: django, python, entrevista, orm, middleware, drf, rest-api - Reading time: 9 min --- As perguntas de entrevista sobre Django giram em torno de três pilares que separam os candidatos experientes dos demais: o domínio do ORM, a arquitetura de middleware e os padrões de design do Django REST Framework (DRF). Este guia detalha as perguntas exatas que os recrutadores fazem em 2026, com exemplos de código prontos para produção utilizando Django 5.2 LTS e DRF 3.17. > **O que os entrevistadores realmente avaliam** > > As entrevistas de Django raramente perguntam sobre CRUD básico. O foco mudou para otimização de QuerySets (select_related vs prefetch_related), design de middleware personalizado e desempenho de serializers DRF. Candidatos que conseguem explicar consultas N+1 e escrever viewsets eficientes superam consistentemente aqueles que conhecem apenas as views genéricas. ## Perguntas de entrevista sobre o ORM do Django: otimização de QuerySets A pergunta mais comum sobre o ORM do Django mira no problema N+1. Diante de um modelo com relações de chave estrangeira, o candidato deve demonstrar quando utilizar `select_related` versus `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) ``` A diferença entre os dois métodos está no tipo de relacionamento que está sendo percorrido. ```python # queries.py — Abordagem correta para ForeignKey (objeto único) developers = Developer.objects.select_related("company").all() # Gera UMA única consulta SQL com JOIN # SELECT developer.*, company.* FROM developer INNER JOIN company ... # Abordagem correta para ManyToMany (múltiplos objetos) developers = Developer.objects.prefetch_related("skills").all() # Gera DUAS consultas SQL: # 1. SELECT * FROM developer # 2. SELECT * FROM skill INNER JOIN developer_skills WHERE developer_id IN (...) ``` `select_related` realiza um JOIN SQL e funciona com campos ForeignKey e OneToOneField. `prefetch_related` executa uma consulta separada e funciona com ManyToManyField e relações ForeignKey inversas. Confundir os dois causa JOINs desnecessários em grandes conjuntos de dados ou o temido padrão N+1. ## ORM avançado: Managers personalizados e encadeamento de QuerySets Os entrevistadores frequentemente pedem aos candidatos que escrevam um manager personalizado que encapsule a lógica de negócio. O objetivo é verificar se o candidato compreende o padrão manager do Django e consegue escrever interfaces de consulta reutilizáveis. ```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() ``` A pergunta de acompanhamento chave: "Por que usar um QuerySet personalizado em vez de apenas um Manager?" A resposta está no encadeamento. Os métodos de um QuerySet personalizado podem ser encadeados, enquanto os métodos de um Manager não podem ser encadeados após a primeira chamada. ```python # usage.py — Encadeamento de QuerySets em ação # Isso funciona porque cada método retorna um QuerySet senior_python_devs = ( Developer.active_objects # manager personalizado .active() # método ActiveDeveloperQuerySet .senior() # encadeia outro método QuerySet .by_skill("Python") # encadeia um terceiro método .select_related("company") # métodos padrão do QuerySet continuam funcionando ) ``` > **Django 5.2 LTS: chaves primárias compostas** > > O Django 5.2 introduziu o CompositePrimaryKey, uma funcionalidade aguardada há muito tempo para modelos que precisam de chaves primárias com múltiplas colunas. Perguntas de entrevista sobre esse recurso estão se tornando cada vez mais comuns, especialmente para candidatos que trabalham com bancos de dados legados ou esquemas de data warehouse. ## Perguntas de entrevista sobre middleware Django: pipeline requisição-resposta As perguntas sobre middleware testam a compreensão do ciclo de vida requisição-resposta do Django. A pergunta padrão: "Explique a ordem em que o middleware processa uma requisição e uma resposta." A resposta segue um padrão rigoroso. Durante uma requisição, as classes de middleware são executadas de cima para baixo conforme definido em `MIDDLEWARE`. Durante uma resposta, são executadas de baixo para cima. Essa arquitetura em camadas tipo cebola significa que o primeiro middleware da lista envolve todo o restante. ```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 ``` Uma pergunta de acompanhamento comum: "Como curto-circuitar a cadeia de middleware?" Retornar uma HttpResponse de `__call__` antes de chamar `self.get_response(request)` interrompe a cadeia completamente. Os middleware restantes e a view nunca são executados. ```python # middleware.py — Rate limiting com curto-circuito 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) ``` ## Hooks de middleware: process_view, process_exception e process_template_response Além de `__call__`, o middleware do Django suporta três métodos hook opcionais. Os entrevistadores utilizam esses métodos para avaliar a profundidade do conhecimento. ```python # middleware.py — Middleware completo com todos os 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` é acionado após a resolução de URL, mas antes da view. Retornar `None` continua a execução; retornar uma `HttpResponse` curto-circuita. `process_exception` é acionado apenas em exceções não tratadas. `process_template_response` é acionado apenas para objetos `TemplateResponse`, não para `HttpResponse` regulares. ## Perguntas de entrevista DRF: desempenho de serializers As perguntas sobre serializers do [Django REST Framework](https://www.django-rest-framework.org/) focam na serialização aninhada e nas implicações de desempenho das diferentes abordagens. A pergunta mais frequente: "Como lidar com relacionamentos aninhados sem causar consultas N+1?" ```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"] ``` O serializer sozinho não resolve o desempenho. O ViewSet precisa otimizar o 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") ) ``` Sem `select_related` e `prefetch_related` no ViewSet, cada desenvolvedor serializado dispara consultas individuais para sua empresa e habilidades. Em uma lista de 50 desenvolvedores, isso significa 1 + 50 + 50 = 101 consultas em vez de 3. ## Permissões e autenticação personalizadas no DRF Uma pergunta frequente em entrevistas sobre DRF: "Escreva uma permissão personalizada que restrinja o acesso com base no papel do usuário e no proprietário do objeto." ```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 ``` A nuance que os entrevistadores procuram: `has_permission` é executado em cada requisição (nível lista), enquanto `has_object_permission` é executado apenas quando `get_object()` é chamado (nível detalhe). Esquecer de sobrescrever `get_queryset` para as views de lista cria uma brecha de segurança onde usuários podem ver objetos que não lhes pertencem. > **Erro de segurança comum no DRF** > > Depender apenas de has_object_permission sem filtrar o queryset deixa o endpoint de lista desprotegido. Sempre se deve combinar permissões a nível de objeto com um get_queryset filtrado para aplicar o controle de acesso tanto nas views de lista quanto de detalhe. ## Throttling e padrões de paginação no DRF Throttling e paginação são perguntas de acompanhamento padrão. Os entrevistadores querem ver que o candidato entende a configuração de API pronta para produção. ```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 ``` A paginação por cursor supera a paginação por offset em grandes conjuntos de dados porque não precisa contar o número total de linhas. A contrapartida: os clientes não podem pular para uma página arbitrária. Esta é a resposta esperada quando os entrevistadores perguntam "Por que escolher paginação por cursor em vez de paginação por número de página?" ## Conclusão Pontos-chave para a preparação para entrevistas Django: - **Otimização do ORM**: sempre associar o método de consulta ao tipo de relacionamento. `select_related` para ForeignKey/OneToOne, `prefetch_related` para ManyToMany e FK inversa. QuerySets personalizados permitem lógica de negócio encadeável e reutilizável. - **Arquitetura de middleware**: a requisição flui de cima para baixo, a resposta de baixo para cima. Curto-circuitar retornando uma resposta antes de `get_response()` é um padrão fundamental para rate limiting, verificações de autenticação e validação de requisições. - **Desempenho de serializers DRF**: serializers aninhados exigem otimização explícita do queryset no ViewSet. Sem `select_related`/`prefetch_related`, a serialização causa consultas N+1 em escala. - **Permissões DRF**: combinar `has_object_permission` com um `get_queryset` filtrado para aplicar controle de acesso nos endpoints de lista e detalhe. Permissões a nível de objeto sozinhas deixam as views de lista desprotegidas. - **Throttling e paginação**: a paginação por cursor escala melhor que offset em tabelas grandes. Configurar taxas de throttling separadas para usuários anônimos e autenticados. Para consolidar esses conhecimentos, recomenda-se praticar com [perguntas de entrevista sobre o ORM do Django](/technologies/django/interview-questions/django-orm-advanced) e [perguntas sobre middleware Django](/technologies/django/interview-questions/django-middleware) no SharpSkill antes da entrevista real. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/django/django-interview-orm-middleware-drf