This content is only available in Portuguese.

Not translated yet for this language.

Code & Development

Django: O Framework "Batteries-Included" Que Superou o JavaScript

Construir APIs REST e aplicações web escaláveis exige escolhas robustas. Descubra por que Django, o framework Python "Batteries-Included", é a solução ideal. Este guia detalha desde a configuração inicial, passando pelo ORM poderoso, até a criação de APIs completas com Django REST Framework, focando em produtividade e segurança.

Equipe Blueprintblog24 min
Django: O Framework "Batteries-Included" Que Superou o JavaScript

Olá, desenvolvedores! 👋

Se você está construindo APIs REST, aplicações web complexas ou sistemas que precisam escalar rapidamente, provavelmente já ouviu falar do Django. Talvez você esteja vindo do ecossistema JavaScript (Node.js, Express, Next.js) e se perguntando: "Por que eu deveria considerar Python e Django?"

Hoje vou mostrar não apenas por que o Django é relevante, mas por que ele é superior em diversos cenários. Vamos explorar código real, comparações práticas e os recursos que tornam o Django uma máquina de produtividade.

O Que É Django?

Django é um framework web de alto nível escrito em Python que incentiva o desenvolvimento rápido e design limpo e pragmático. Criado em 2005, ele segue o princípio "Batteries-Included" (Baterias Inclusas) - tudo o que você precisa já vem na caixa.

Por Que Isso Importa

Antes de mergulharmos no código, vamos entender o problema fundamental:

text
// ❌ Ecossistema JavaScript - Stack Overflow de Decisões
// Para construir uma API completa, você precisa escolher e configurar:

const express = require('express');           // Framework básico
const helmet = require('helmet');             // Segurança
const cors = require('cors');                 // CORS
const morgan = require('morgan');             // Logging
const { Sequelize } = require('sequelize');   // ORM
const bcrypt = require('bcrypt');             // Hash de senhas
const jwt = require('jsonwebtoken');          // Autenticação
const joi = require('joi');                   // Validação
const multer = require('multer');             // Upload de arquivos
// ... e mais 20+ pacotes para funcionalidades básicas

// Depois de instalar tudo, você ainda precisa:
// 1. Configurar cada pacote individualmente
// 2. Garantir compatibilidade entre versões
// 3. Implementar segurança manualmente
// 4. Criar toda estrutura de usuários, permissões, admin...
// 5. Escrever migrations manualmente ou configurar ferramenta
text
# ✅ Django - Pronto Para Produção em Minutos

# pip install django djangorestframework

# Você já tem:
# - ORM poderoso integrado
# - Sistema de autenticação completo
# - Admin panel automático
# - Sistema de migrations
# - Proteção CSRF/XSS/SQL Injection
# - Framework de forms e validação
# - Sistema de templates
# - Middleware de segurança
# - Cache framework
# - Sistema de sinais (signals)
# - Framework de testes
# E MUITO mais...

A diferença é gritante. Com Django, você foca na lógica de negócio desde o dia 1.

Quando Você Deve Usar Django?

Casos de uso ideais:

  • APIs REST complexas com múltiplos recursos e relacionamentos
  • Aplicações CRUD que precisam de um admin panel robusto
  • Sistemas multi-tenant com isolamento de dados por cliente
  • Projetos que precisam escalar rapidamente do MVP para produção
  • Aplicações que exigem segurança rigorosa (fintech, healthcare, gov)

Quando NÃO usar Django:

  • Microserviços extremamente leves onde cada milissegundo conta (considere FastAPI ou Go)
  • Aplicações real-time intensivas como jogos multiplayer (WebSockets não são o forte nativo)
  • Serverless functions com cold start crítico (melhor usar Node.js ou Python puro)
  • Projetos experimentais onde a flexibilidade total é mais importante que convenção

Configurando Seu Primeiro Projeto Django

Vamos construir isso passo a passo, explicando cada decisão.

Passo 1: Instalação e Setup

Opção 1: Usando Poetry (Recomendado)

text
# Instalar Poetry (gerenciador de dependências moderno)
curl -sSL https://install.python-poetry.org | python3 -

# Criar novo projeto
poetry new meu_projeto
cd meu_projeto

# Adicionar Django
poetry add django djangorestframework python-decouple psycopg2-binary

# Criar projeto Django
poetry run django-admin startproject config .

Opção 2: Usando pip e venv (Tradicional)

text
# Criar ambiente virtual
python -m venv venv
source venv/bin/activate  # No Windows: venv\Scripts\activate

# Instalar Django
pip install django djangorestframework python-decouple psycopg2-binary

# Criar projeto
django-admin startproject config .

Estrutura de pastas criada:

text
meu_projeto/
├── config/              # Configurações principais
│   ├── __init__.py
│   ├── settings.py     # Todas as configurações do projeto
│   ├── urls.py         # Roteamento principal
│   ├── asgi.py         # Para deploy assíncrono
│   └── wsgi.py         # Para deploy tradicional
├── manage.py           # CLI do Django (seu melhor amigo)
└── db.sqlite3          # Banco de dados (será criado)

Passo 2: Configuração Inicial Segura

Vamos configurar o projeto seguindo as melhores práticas desde o início:

text
# config/settings.py
import os
from pathlib import Path
from decouple import config  # Para variáveis de ambiente

BASE_DIR = Path(__file__).resolve().parent.parent

# NUNCA comite SECRET_KEY em produção
SECRET_KEY = config('SECRET_KEY', default='dev-key-change-in-production')

# DEBUG deve ser False em produção
DEBUG = config('DEBUG', default=False, cast=bool)

ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')

# Aplicações instaladas
INSTALLED_APPS = [
    'django.contrib.admin',          # Admin panel automático
    'django.contrib.auth',           # Sistema de autenticação
    'django.contrib.contenttypes',   # Sistema de tipos de conteúdo
    'django.contrib.sessions',       # Gerenciamento de sessões
    'django.contrib.messages',       # Framework de mensagens
    'django.contrib.staticfiles',    # Gerenciamento de arquivos estáticos
    'rest_framework',                # Django REST Framework
    'rest_framework.authtoken',      # Autenticação via token
    # Suas apps aqui
]

# Middleware - a ordem importa!
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',      # Segurança
    'django.contrib.sessions.middleware.SessionMiddleware',  # Sessões
    'django.middleware.common.CommonMiddleware',          # Comum
    'django.middleware.csrf.CsrfViewMiddleware',          # Proteção CSRF
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # Auth
    'django.contrib.messages.middleware.MessageMiddleware',  # Mensagens
    'django.middleware.clickjacking.XFrameOptionsMiddleware',  # Clickjacking
]

# Banco de dados - PostgreSQL em produção
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config('DB_NAME', default='meu_projeto'),
        'USER': config('DB_USER', default='postgres'),
        'PASSWORD': config('DB_PASSWORD', default=''),
        'HOST': config('DB_HOST', default='localhost'),
        'PORT': config('DB_PORT', default='5432'),
    }
}

# REST Framework Configuration
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

Por que essa configuração é tão boa:

  • Segurança desde o início: Usa variáveis de ambiente para dados sensíveis
  • Middleware protege automaticamente: CSRF, XSS, Clickjacking já configurados
  • PostgreSQL pronto: O banco de dados mais robusto para produção
  • REST Framework integrado: API pronta para começar

Passo 3: Criando Sua Primeira App

Django organiza código em "apps" - módulos reutilizáveis. Vamos criar uma API de tarefas:

text
# Criar app de tarefas
python manage.py startapp tasks

# Estrutura criada:
# tasks/
# ├── __init__.py
# ├── admin.py       # Configuração do admin panel
# ├── apps.py        # Configuração da app
# ├── models.py      # Modelos de dados (ORM)
# ├── tests.py       # Testes
# ├── views.py       # Views (lógica de negócio)
# └── migrations/    # Migrations do banco de dados

Agora registre a app em settings.py:

text
INSTALLED_APPS = [
    # ... apps Django padrão
    'rest_framework',
    'tasks',  # Nossa nova app
]

O ORM do Django: Poder em Python Puro

O ORM (Object-Relational Mapper) do Django é uma das suas maiores forças. Vamos construir modelos progressivamente.

3.1: Modelo Básico de Task

text
# tasks/models.py
from django.db import models
from django.contrib.auth.models import User

class Task(models.Model):
    """
    Modelo básico de tarefa.
    Cada campo se torna uma coluna no banco de dados automaticamente.
    """
    # Campos básicos
    title = models.CharField(
        max_length=200,
        help_text="Título da tarefa"
    )
    description = models.TextField(
        blank=True,  # Pode ser vazio
        help_text="Descrição detalhada da tarefa"
    )
    completed = models.BooleanField(
        default=False,
        help_text="Tarefa concluída?"
    )
    
    # Relacionamento com usuário (Foreign Key)
    created_by = models.ForeignKey(
        User,
        on_delete=models.CASCADE,  # Se usuário deletado, deleta tasks
        related_name='tasks'  # User.tasks.all() retorna todas as tasks
    )
    
    # Timestamps automáticos
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-created_at']  # Mais recentes primeiro
        verbose_name = 'Tarefa'
        verbose_name_plural = 'Tarefas'
    
    def __str__(self):
        """Representação em string do modelo"""
        return f"{self.title} - {'✓' if self.completed else '✗'}"

Agora crie e aplique a migration:

text
# Criar migration (Django analisa seu modelo e cria SQL)
python manage.py makemigrations

# Aplicar migration (Django executa o SQL)
python manage.py migrate

Por que isso é poderoso:

  • Zero SQL escrito: Django gerou todo o SQL necessário
  • Type-safe: Python analisa os tipos em tempo de desenvolvimento
  • Relacionamentos simples: Foreign Keys são triviais de definir
  • Migrations versionadas: Controle de versão do schema do banco

3.2: Adicionando Relacionamentos Complexos

text
# tasks/models.py
class Project(models.Model):
    """Projeto que contém múltiplas tarefas"""
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    owner = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='owned_projects'
    )
    members = models.ManyToManyField(
        User,
        related_name='projects',
        blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.name

class Task(models.Model):
    # ... campos anteriores ...
    
    # Adicionar relacionamento com Project
    project = models.ForeignKey(
        Project,
        on_delete=models.CASCADE,
        related_name='tasks',
        null=True,  # Tarefas podem não ter projeto
        blank=True
    )
    
    # Tags usando ManyToMany
    tags = models.ManyToManyField(
        'Tag',  # String reference (lazy)
        related_name='tasks',
        blank=True
    )
    
    # Choices (enums)
    PRIORITY_CHOICES = [
        ('low', 'Baixa'),
        ('medium', 'Média'),
        ('high', 'Alta'),
    ]
    priority = models.CharField(
        max_length=10,
        choices=PRIORITY_CHOICES,
        default='medium'
    )

class Tag(models.Model):
    """Tags para categorizar tarefas"""
    name = models.CharField(max_length=50, unique=True)
    color = models.CharField(max_length=7, default='#000000')  # Hex color
    
    def __str__(self):
        return self.name

Diferenças importantes:

  • ForeignKey (1-para-N): Um projeto tem muitas tasks, cada task tem um projeto
  • ManyToManyField (N-para-N): Uma task pode ter várias tags, uma tag pode estar em várias tasks
  • Choices: Implementa enums de forma elegante
  • related_name: Permite navegação reversa (project.tasks.all())

3.3: Queries Poderosas com o ORM

Agora que temos modelos, vamos ver como consultá-los:

text
# Django shell: python manage.py shell

from tasks.models import Task, Project, Tag
from django.contrib.auth.models import User

# ✅ CRUD Básico

# Criar
task = Task.objects.create(
    title="Implementar API",
    description="API REST com Django",
    created_by=User.objects.first(),
    priority='high'
)

# Ler (queries)
all_tasks = Task.objects.all()
high_priority = Task.objects.filter(priority='high')
completed = Task.objects.filter(completed=True)
my_tasks = Task.objects.filter(created_by=request.user)

# Atualizar
task.completed = True
task.save()

# Ou atualizar em massa
Task.objects.filter(priority='low').update(completed=True)

# Deletar
task.delete()

# ✅ Queries Complexas

# Busca por texto
tasks = Task.objects.filter(title__icontains='api')  # Case-insensitive

# Relacionamentos (JOINs automáticos)
tasks_in_project = Task.objects.filter(project__name='Projeto Alpha')

# Múltiplos filtros (AND)
urgent = Task.objects.filter(
    priority='high',
    completed=False,
    created_at__gte=timezone.now() - timedelta(days=7)
)

# OR queries
from django.db.models import Q
tasks = Task.objects.filter(
    Q(priority='high') | Q(tags__name='urgente')
)

# ✅ Agregações e Anotações

from django.db.models import Count, Avg

# Contar tasks por usuário
user_stats = User.objects.annotate(
    total_tasks=Count('tasks'),
    completed_tasks=Count('tasks', filter=Q(tasks__completed=True))
)

# Projetos com mais de 10 tasks
popular_projects = Project.objects.annotate(
    task_count=Count('tasks')
).filter(task_count__gt=10)

# ✅ Prefetch e Select Related (Otimização)

# ❌ N+1 problem (1 query + N queries)
tasks = Task.objects.all()
for task in tasks:
    print(task.created_by.username)  # Query para cada task!

# ✅ Select Related (1 query com JOIN)
tasks = Task.objects.select_related('created_by', 'project').all()
for task in tasks:
    print(task.created_by.username)  # Sem queries adicionais!

# ✅ Prefetch Related (para ManyToMany)
tasks = Task.objects.prefetch_related('tags').all()
for task in tasks:
    print(task.tags.all())  # Apenas 2 queries total!

Por que o ORM do Django é excepcional:

  • Pythônico: Queries parecem Python natural
  • Type-safe: IDE pode autocompletar e verificar tipos
  • Otimização fácil: select_related e prefetch_related resolvem N+1
  • Complexidade gerenciável: Queries SQL complexas ficam legíveis

Criando Uma API REST Completa

Vamos construir uma API REST usando Django REST Framework (DRF).

4.1: Serializers - Transformando Modelos em JSON

text
# tasks/serializers.py
from rest_framework import serializers
from .models import Task, Project, Tag
from django.contrib.auth.models import User

class TagSerializer(serializers.ModelSerializer):
    """Serializer para Tags"""
    class Meta:
        model = Tag
        fields = ['id', 'name', 'color']

class UserSerializer(serializers.ModelSerializer):
    """Serializer básico de usuário"""
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name']
        read_only_fields = ['id']

class TaskSerializer(serializers.ModelSerializer):
    """
    Serializer completo para Tasks.
    Inclui relacionamentos e campos calculados.
    """
    # Nested serializers (read-only)
    created_by = UserSerializer(read_only=True)
    tags = TagSerializer(many=True, read_only=True)
    
    # Write-only fields
    tag_ids = serializers.ListField(
        child=serializers.IntegerField(),
        write_only=True,
        required=False
    )
    
    # Campos calculados (SerializerMethodField)
    days_since_creation = serializers.SerializerMethodField()
    is_overdue = serializers.SerializerMethodField()
    
    class Meta:
        model = Task
        fields = [
            'id', 'title', 'description', 'completed', 
            'priority', 'created_by', 'project', 'tags',
            'tag_ids', 'created_at', 'updated_at',
            'days_since_creation', 'is_overdue'
        ]
        read_only_fields = ['id', 'created_at', 'updated_at', 'created_by']
    
    def get_days_since_creation(self, obj):
        """Calcula dias desde criação"""
        from django.utils import timezone
        delta = timezone.now() - obj.created_at
        return delta.days
    
    def get_is_overdue(self, obj):
        """Verifica se está atrasada (exemplo: mais de 30 dias)"""
        return self.get_days_since_creation(obj) > 30 and not obj.completed
    
    def create(self, validated_data):
        """Sobrescrever create para lidar com tags"""
        tag_ids = validated_data.pop('tag_ids', [])
        task = Task.objects.create(**validated_data)
        
        if tag_ids:
            tags = Tag.objects.filter(id__in=tag_ids)
            task.tags.set(tags)
        
        return task
    
    def update(self, instance, validated_data):
        """Sobrescrever update para lidar com tags"""
        tag_ids = validated_data.pop('tag_ids', None)
        
        # Atualizar campos normais
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        
        # Atualizar tags se fornecidas
        if tag_ids is not None:
            tags = Tag.objects.filter(id__in=tag_ids)
            instance.tags.set(tags)
        
        return instance

class ProjectSerializer(serializers.ModelSerializer):
    """Serializer para Projects com tasks aninhadas"""
    owner = UserSerializer(read_only=True)
    members = UserSerializer(many=True, read_only=True)
    tasks = TaskSerializer(many=True, read_only=True)
    task_count = serializers.IntegerField(read_only=True)
    
    class Meta:
        model = Project
        fields = [
            'id', 'name', 'description', 'owner', 
            'members', 'tasks', 'task_count', 'created_at'
        ]

Por que Serializers são poderosos:

  • Validação automática: DRF valida dados contra o modelo
  • Nested serializers: Representa relacionamentos facilmente
  • Campos calculados: SerializerMethodField para lógica customizada
  • Read/Write separation: Diferentes representações para GET vs POST

4.2: ViewSets - Controllers com Superpoderes

text
# tasks/views.py
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.db.models import Count, Q
from .models import Task, Project, Tag
from .serializers import TaskSerializer, ProjectSerializer, TagSerializer

class TaskViewSet(viewsets.ModelViewSet):
    """
    ViewSet completo para Tasks.
    Fornece automaticamente: list, create, retrieve, update, destroy
    """
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['title', 'description']
    ordering_fields = ['created_at', 'priority', 'completed']
    
    def get_queryset(self):
        """
        Customizar queryset:
        - Apenas tasks do usuário logado
        - Otimizar com select_related
        """
        return Task.objects.filter(
            created_by=self.request.user
        ).select_related('created_by', 'project').prefetch_related('tags')
    
    def perform_create(self, serializer):
        """Automaticamente atribuir usuário logado"""
        serializer.save(created_by=self.request.user)
    
    # ✅ Custom Actions - Endpoints customizados
    
    @action(detail=False, methods=['get'])
    def completed(self, request):
        """GET /api/tasks/completed/ - Retorna tasks completas"""
        tasks = self.get_queryset().filter(completed=True)
        serializer = self.get_serializer(tasks, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def high_priority(self, request):
        """GET /api/tasks/high_priority/ - Retorna tasks de alta prioridade"""
        tasks = self.get_queryset().filter(
            priority='high',
            completed=False
        )
        serializer = self.get_serializer(tasks, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post'])
    def complete(self, request, pk=None):
        """POST /api/tasks/{id}/complete/ - Marca task como completa"""
        task = self.get_object()
        task.completed = True
        task.save()
        serializer = self.get_serializer(task)
        return Response(serializer.data)
    
    @action(detail=False, methods=['post'])
    def bulk_update(self, request):
        """POST /api/tasks/bulk_update/ - Atualização em massa"""
        task_ids = request.data.get('task_ids', [])
        updates = request.data.get('updates', {})
        
        if not task_ids:
            return Response(
                {'error': 'task_ids é obrigatório'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        tasks = self.get_queryset().filter(id__in=task_ids)
        updated_count = tasks.update(**updates)
        
        return Response({
            'updated': updated_count,
            'message': f'{updated_count} tasks atualizadas'
        })

class ProjectViewSet(viewsets.ModelViewSet):
    """ViewSet para Projects com estatísticas"""
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        """Apenas projetos onde usuário é owner ou member"""
        user = self.request.user
        return Project.objects.filter(
            Q(owner=user) | Q(members=user)
        ).annotate(
            task_count=Count('tasks')
        ).distinct().prefetch_related('members', 'tasks')
    
    def perform_create(self, serializer):
        """Owner automático é o usuário logado"""
        serializer.save(owner=self.request.user)
    
    @action(detail=True, methods=['post'])
    def add_member(self, request, pk=None):
        """POST /api/projects/{id}/add_member/ - Adiciona membro"""
        project = self.get_object()
        user_id = request.data.get('user_id')
        
        try:
            from django.contrib.auth.models import User
            user = User.objects.get(id=user_id)
            project.members.add(user)
            return Response({'message': f'{user.username} adicionado'})
        except User.DoesNotExist:
            return Response(
                {'error': 'Usuário não encontrado'},
                status=status.HTTP_404_NOT_FOUND
            )
    
    @action(detail=True, methods=['get'])
    def statistics(self, request, pk=None):
        """GET /api/projects/{id}/statistics/ - Estatísticas do projeto"""
        project = self.get_object()
        tasks = project.tasks.all()
        
        stats = {
            'total_tasks': tasks.count(),
            'completed_tasks': tasks.filter(completed=True).count(),
            'high_priority_tasks': tasks.filter(priority='high').count(),
            'tasks_by_priority': {
                'high': tasks.filter(priority='high').count(),
                'medium': tasks.filter(priority='medium').count(),
                'low': tasks.filter(priority='low').count(),
            }
        }
        
        return Response(stats)

Por que ViewSets são incríveis:

  • CRUD automático: list, create, retrieve, update, destroy prontos
  • Custom actions: @action decorator para endpoints customizados
  • Filters integrados: Search e ordering out-of-the-box
  • Permissions: Sistema de permissões granular

4.3: URLs - Conectando Tudo

text
# tasks/urls.py
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet, ProjectViewSet

router = DefaultRouter()
router.register(r'tasks', TaskViewSet, basename='task')
router.register(r'projects', ProjectViewSet, basename='project')

urlpatterns = router.urls

# Isso gera automaticamente:
# GET    /tasks/                  - Lista tasks
# POST   /tasks/                  - Cria task
# GET    /tasks/{id}/             - Detalhe da task
# PUT    /tasks/{id}/             - Atualiza task (completo)
# PATCH  /tasks/{id}/             - Atualiza task (parcial)
# DELETE /tasks/{id}/             - Deleta task
# GET    /tasks/completed/        - Custom action
# POST   /tasks/{id}/complete/    - Custom action
# ... e todas as rotas de projects
text
# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('tasks.urls')),
    path('api-auth/', include('rest_framework.urls')),  # Login UI
]

O Admin Panel: A Killer Feature

Registre seus modelos no admin e tenha uma interface completa de gerenciamento:

text
# tasks/admin.py
from django.contrib import admin
from .models import Task, Project, Tag

@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    """Admin customizado para Tasks"""
    list_display = ['title', 'priority', 'completed', 'created_by', 'created_at']
    list_filter = ['completed', 'priority', 'created_at']
    search_fields = ['title', 'description']
    date_hierarchy = 'created_at'
    ordering = ['-created_at']
    
    # Campos readonly
    readonly_fields = ['created_at', 'updated_at']
    
    # Organização dos campos no form
    fieldsets = [
        ('Informações Básicas', {
            'fields': ['title', 'description', 'completed']
        }),
        ('Categorização', {
            'fields': ['priority', 'project', 'tags']
        }),
        ('Metadados', {
            'fields': ['created_by', 'created_at', 'updated_at'],
            'classes': ['collapse']  # Seção colapsável
        }),
    ]
    
    # Ações em massa
    actions = ['mark_as_completed', 'mark_as_incomplete']
    
    def mark_as_completed(self, request, queryset):
        updated = queryset.update(completed=True)
        self.message_user(request, f'{updated} tasks marcadas como completas')
    mark_as_completed.short_description = 'Marcar como completas'
    
    def mark_as_incomplete(self, request, queryset):
        updated = queryset.update(completed=False)
        self.message_user(request, f'{updated} tasks marcadas como incompletas')
    mark_as_incomplete.short_description = 'Marcar como incompletas'

@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
    list_display = ['name', 'owner', 'task_count', 'created_at']
    search_fields = ['name', 'description']
    filter_horizontal = ['members']  # Interface melhor para ManyToMany
    
    def task_count(self, obj):
        return obj.tasks.count()
    task_count.short_description = 'Total de Tasks'

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ['name', 'color']
    search_fields = ['name']

Acesse http://localhost:8000/admin/ e você tem uma interface completa de gerenciamento!

Segurança: Django vs JavaScript

Proteção CSRF

tsx
# ✅ Django - Automático

# Em qualquer formulário HTML:
<form method="post">
    {% csrf_token %}  <!-- Django injeta token automaticamente -->
    <!-- campos do form -->
</form>

# No frontend (React/Vue):
# Django envia cookie CSRF automaticamente
# Basta incluir o header:
headers: {
    'X-CSRFToken': getCookie('csrftoken')
}
javascript
// ❌ Node.js/Express - Manual

const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.post('/api/tasks', csrfProtection, (req, res) => {
    // Você precisa manualmente:
    // 1. Configurar middleware
    // 2. Enviar token para frontend
    // 3. Validar em cada rota
    // 4. Lidar com errors
});

Prevenção de SQL Injection

text
# ✅ Django ORM - Impossível de injetar SQL

# Isso é SEGURO (parametrizado automaticamente)
username = request.GET.get('username')
user = User.objects.filter(username=username).first()

# Até mesmo raw SQL é parametrizado:
user = User.objects.raw(
    'SELECT * FROM auth_user WHERE username = %s',
    [username]  # Django parametriza automaticamente
)[0]
text
// ❌ Node.js - Você precisa ser cuidadoso

// PERIGOSO (vulnerável a SQL injection)
const username = req.query.username;
const user = await db.query(`SELECT * FROM users WHERE username = '${username}'`);

// SEGURO (precisa usar ? manualmente)
const user = await db.query(
    'SELECT * FROM users WHERE username = ?',
    [username]
);

Autenticação e Autorização

Django vem com um sistema completo de auth:

text
# tasks/permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Permission customizada:
    - Leitura: Todos autenticados
    - Escrita: Apenas o dono
    """
    def has_object_permission(self, request, view, obj):
        # Leitura permitida para todos
        if request.method in permissions.SAFE_METHODS:
            return True
        
        # Escrita apenas para o dono
        return obj.created_by == request.user

class IsProjectMember(permissions.BasePermission):
    """Apenas membros do projeto podem ver/editar"""
    def has_object_permission(self, request, view, obj):
        return (
            obj.owner == request.user or
            request.user in obj.members.all()
        )
text
# Usar nas views:
class TaskViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    # ...

Sistema de Grupos e Permissões

text
# Criar grupos e permissões
from django.contrib.auth.models import Group, Permission

# Criar grupo
managers = Group.objects.create(name='Managers')

# Adicionar permissões
permissions = Permission.objects.filter(
    codename__in=['add_task', 'change_task', 'delete_task', 'view_task']
)
managers.permissions.set(permissions)

# Adicionar usuário ao grupo
user.groups.add(managers)

# Verificar permissões
if user.has_perm('tasks.delete_task'):
    # Usuário pode deletar tasks
    pass

Tarefas Assíncronas com Celery

Para processar tarefas em background (envio de emails, processamento de arquivos, etc):

text
# tasks/tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_task_notification(task_id):
    """Envia notificação quando task é criada"""
    from .models import Task
    
    task = Task.objects.get(id=task_id)
    
    send_mail(
        subject=f'Nova tarefa: {task.title}',
        message=task.description,
        from_email='noreply@app.com',
        recipient_list=[task.created_by.email],
    )
    
    return f'Email enviado para {task.created_by.email}'

@shared_task
def generate_project_report(project_id):
    """Gera relatório do projeto em background"""
    from .models import Project
    import time
    
    project = Project.objects.get(id=project_id)
    
    # Processamento pesado
    time.sleep(5)  # Simula processamento
    
    report = {
        'project': project.name,
        'total_tasks': project.tasks.count(),
        'completed': project.tasks.filter(completed=True).count(),
    }
    
    return report
text
# Chamar a task (não bloqueia)
from .tasks import send_task_notification

def perform_create(self, serializer):
    task = serializer.save(created_by=self.request.user)
    
    # Executa em background
    send_task_notification.delay(task.id)

Testes: Framework Integrado

text
# tasks/tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from .models import Task, Project

class TaskModelTest(TestCase):
    """Testes do modelo Task"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
    
    def test_task_creation(self):
        """Teste de criação de task"""
        task = Task.objects.create(
            title='Test Task',
            description='Description',
            created_by=self.user
        )
        
        self.assertEqual(task.title, 'Test Task')
        self.assertFalse(task.completed)
        self.assertEqual(task.priority, 'medium')
    
    def test_task_str(self):
        """Teste do método __str__"""
        task = Task.objects.create(
            title='Test',
            created_by=self.user
        )
        
        self.assertEqual(str(task), 'Test - ✗')

class TaskAPITest(APITestCase):
    """Testes da API de Tasks"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.client.force_authenticate(user=self.user)
    
    def test_create_task(self):
        """Teste de criação via API"""
        data = {
            'title': 'API Test Task',
            'description': 'Created via API',
            'priority': 'high'
        }
        
        response = self.client.post('/api/tasks/', data)
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Task.objects.count(), 1)
        self.assertEqual(Task.objects.first().title, 'API Test Task')
    
    def test_list_tasks(self):
        """Teste de listagem"""
        Task.objects.create(
            title='Task 1',
            created_by=self.user
        )
        Task.objects.create(
            title='Task 2',
            created_by=self.user
        )
        
        response = self.client.get('/api/tasks/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 2)
    
    def test_complete_task(self):
        """Teste de custom action complete"""
        task = Task.objects.create(
            title='Task',
            created_by=self.user
        )
        
        response = self.client.post(f'/api/tasks/{task.id}/complete/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        task.refresh_from_db()
        self.assertTrue(task.completed)

Execute os testes:

text
# Rodar todos os testes
python manage.py test

# Rodar testes de uma app específica
python manage.py test tasks

# Rodar com coverage
pip install coverage
coverage run --source='.' manage.py test
coverage report

Armadilhas Comuns e Como Evitá-las

1. N+1 Query Problem

O problema: Fazer queries dentro de loops

text
# ❌ MUITO LENTO - N+1 queries
tasks = Task.objects.all()  # 1 query
for task in tasks:
    print(task.created_by.username)  # 1 query para cada task!
    print(task.project.name)  # Mais 1 query para cada!
# Total: 1 + 2N queries

# ✅ RÁPIDO - 2 queries total
tasks = Task.objects.select_related(
    'created_by',
    'project'
).all()
for task in tasks:
    print(task.created_by.username)  # Sem query adicional
    print(task.project.name)  # Sem query adicional
# Total: 1 query otimizada

Por que isso importa: N+1 pode tornar sua API 100x mais lenta. Sempre use select_related para ForeignKey e prefetch_related para ManyToMany.

2. Não Usar Transactions

O problema: Operações complexas sem atomicidade

text
# ❌ PERIGOSO - Se falhar no meio, dados ficam inconsistentes
def transfer_tasks(from_user, to_user):
    tasks = Task.objects.filter(created_by=from_user)
    for task in tasks:
        task.created_by = to_user
        task.save()  # Se falhar aqui, algumas tasks foram transferidas!

# ✅ SEGURO - Tudo ou nada
from django.db import transaction

@transaction.atomic
def transfer_tasks(from_user, to_user):
    tasks = Task.objects.filter(created_by=from_user)
    for task in tasks:
        task.created_by = to_user
        task.save()
    # Se qualquer operação falhar, TUDO é revertido

Prevenção: Use @transaction.atomic para operações que modificam múltiplos registros.

3. Expor Dados Sensíveis no Serializer

O problema: Retornar campos que não devem ser públicos

text
# ❌ PERIGOSO - Expõe senha e email
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'  # NUNCA use __all__!

# ✅ SEGURO - Apenas campos permitidos
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'first_name', 'last_name']
        # Senha e email não são incluídos

Red flags: Nunca use fields = '__all__' em serializers que retornam dados para o frontend.

Quando NÃO Usar Django

Não use Django quando:

  • Microsserviços extremamente leves: Django tem overhead. Para funções Lambda ou microsserviços minimalistas, FastAPI ou Flask são melhores
  • Real-time intensivo: WebSockets não são o forte do Django. Para jogos ou chat, considere Node.js com Socket.io ou Go
  • Machine Learning inference: Para servir modelos ML, FastAPI é mais eficiente
  • Protótipos de 1 hora: Se você só precisa de um endpoint rápido, Flask é mais simples
text
# ❌ Overkill para isso
# Django com todo seu ecosistema para servir um endpoint simples

# ✅ Flask é melhor para casos ultra-simples
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/health')
def health():
    return jsonify({'status': 'ok'})

Django vs Alternativas

Django vs FastAPI

FeatureDjangoFastAPI
VelocidadeBoa (sync)Excelente (async)
Admin PanelIncluídoNão tem
ORMDjango ORMSQLAlchemy ou Tortoise
Learning CurveMédiaBaixa
Use Case IdealApps CRUD complexasAPIs de alta performance

Django é melhor quando: Você precisa de admin panel, auth completa, e desenvolvimento rápido
FastAPI é melhor quando: Performance é crítica e você não precisa de admin

Django vs Express.js

FeatureDjangoExpress.js
Batteries-IncludedSimNão
SegurançaAutomáticaManual
ORMIncluídoPrecisa escolher
AdminIncluídoPrecisa construir
Performance I/OBoaExcelente

Django é melhor quando: Desenvolvimento rápido, segurança e estrutura são prioridades
Express é melhor quando: Você precisa de máxima flexibilidade e performance de I/O

Conclusão

Django não é apenas relevante - é superior para a maioria dos casos de uso de aplicações web modernas. Sua filosofia "Batteries-Included" economiza semanas de desenvolvimento e entrega segurança robusta por padrão.

Principais vantagens:

  • Velocidade de desenvolvimento: Do zero à produção em dias, não semanas
  • Segurança automática: CSRF, XSS, SQL Injection protegidos por padrão
  • Admin panel gratuito: Economiza centenas de horas de desenvolvimento
  • ORM poderoso: Queries complexas em Python legível
  • Ecossistema maduro: 20 anos de melhores práticas estabelecidas
  • Escalabilidade comprovada: Instagram, Spotify e Pinterest usam Django

Próximos passos:

  1. Clone o projeto exemplo e explore o código
  2. Construa uma API REST seguindo este guia
  3. Explore Django REST Framework e suas features avançadas
  4. Aprenda sobre deploy com Gunicorn + Nginx + PostgreSQL

A próxima vez que você for construir uma aplicação web complexa, considere Django. Seu futuro eu (e sua equipe) agradecerão pela produtividade e robustez.


Recursos

Article tags

Related articles

Get the latest articles delivered to your inbox.

Follow Us: