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.

Perguntas de entrevista Django: ORM, Middleware e DRF

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.

Pronto para mandar bem nas entrevistas de Django?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Perguntas de entrevista DRF: desempenho de serializers

As perguntas sobre serializers do Django REST Framework 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 e perguntas sobre middleware Django no SharpSkill antes da entrevista real.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

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

Compartilhar

Artigos relacionados