<h2>Entendendo o MVT: A Arquitetura do Django</h2>
<p>Django segue um padrão arquitetural chamado <strong>MVT (Model-View-Template)</strong>, que é uma variação do popular MVC (Model-View-Controller). A diferença fundamental está na responsabilidade da "View": enquanto em MVC a View é apenas a apresentação visual, no Django a View é a lógica de negócio, e o Template é responsável pela renderização do HTML. Isso permite separação clara de responsabilidades e facilita a manutenção do código.</p>
<p>O fluxo é simples: quando um cliente faz uma requisição, o Django a roteia para uma View através das URLs, a View interage com o banco de dados via Models, e retorna um Template renderizado com os dados. Cada componente tem um propósito bem definido, o que torna o desenvolvimento mais organizado e escalável.</p>
<h3>Models: Definindo sua Estrutura de Dados</h3>
<p>Os Models são classes Python que representam tabelas no banco de dados. Django converte essas classes em SQL através do ORM, eliminando a necessidade de escrever queries manualmente. Cada atributo da classe se torna uma coluna na tabela, e Django infere automaticamente os tipos de dados SQL apropriados.</p>
<pre><code class="language-python"># models.py
from django.db import models
class Autor(models.Model):
nome = models.CharField(max_length=100)
email = models.EmailField(unique=True)
data_criacao = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-data_criacao']
verbose_name_plural = 'Autores'
def __str__(self):
return self.nome
class Livro(models.Model):
GENEROS = [
('ficcao', 'Ficção'),
('tecnico', 'Técnico'),
('romance', 'Romance'),
]
titulo = models.CharField(max_length=200)
autor = models.ForeignKey(Autor, on_delete=models.CASCADE, related_name='livros')
genero = models.CharField(max_length=20, choices=GENEROS)
paginas = models.IntegerField()
publicado_em = models.DateField()
ativo = models.BooleanField(default=True)
def __str__(self):
return self.titulo</code></pre>
<p>O <code>ForeignKey</code> cria um relacionamento um-para-muitos: cada livro pertence a um autor, mas um autor pode ter múltiplos livros. O parâmetro <code>on_delete=models.CASCADE</code> define que livros serão deletados quando seu autor for removido. O <code>related_name='livros'</code> permite acessar os livros de um autor através de <code>autor.livros.all()</code>.</p>
<h3>Views: A Lógica da Sua Aplicação</h3>
<p>As Views são funções ou classes que recebem uma requisição HTTP e retornam uma resposta. Django oferece duas abordagens: views baseadas em funções (FBV) e views baseadas em classes (CBV). As CBVs são mais poderosas para operações CRUD complexas, enquanto FBVs são diretas para lógicas simples.</p>
<pre><code class="language-python"># views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView
from django.urls import reverse_lazy
from .models import Livro, Autor
View baseada em função
def lista_livros(request):
livros = Livro.objects.filter(ativo=True).select_related('autor')
return render(request, 'livros/lista.html', {'livros': livros})
def detalhe_livro(request, pk):
livro = get_object_or_404(Livro, pk=pk, ativo=True)
return render(request, 'livros/detalhe.html', {'livro': livro})
Views baseadas em classes
class ListaAutores(ListView):
model = Autor
template_name = 'autores/lista.html'
context_object_name = 'autores'
paginate_by = 10
def get_queryset(self):
return Autor.objects.annotate(
total_livros=models.Count('livros')
).order_by('-total_livros')
class DetalheLivro(DetailView):
model = Livro
template_name = 'livros/detalhe.html'
context_object_name = 'livro'
class CriarLivro(CreateView):
model = Livro
fields = ['titulo', 'autor', 'genero', 'paginas', 'publicado_em']
template_name = 'livros/criar.html'
success_url = reverse_lazy('lista_livros')</code></pre>
<p>A diferença crucial: com <code>ListaAutores</code>, você herda toda a lógica de paginação, ordenação e renderização. O <code>select_related</code> na FBV é uma otimização que reduz consultas ao banco — sem ele, Django faria uma query para cada livro ao acessar o autor.</p>
<h3>Templates: Renderizando o HTML</h3>
<p>Templates são arquivos HTML com sintaxe especial do Django que permitem inserir dados dinâmicos. Você acessa variáveis com <code>{{ }}</code>, executa lógica com <code>{% %}</code>, e itera sobre listas com <code>{% for %}</code>.</p>
<pre><code class="language-html"><!-- livros/lista.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Livros Disponíveis</h1>
{% if livros %}
<table class="tabela">
<thead>
<tr>
<th>Título</th>
<th>Autor</th>
<th>Gênero</th>
<th>Páginas</th>
</tr>
</thead>
<tbody>
{% for livro in livros %}
<tr>
<td>
<a href="{% url 'detalhe_livro' livro.pk %}">
{{ livro.titulo }}
</a>
</td>
<td>{{ livro.autor.nome }}</td>
<td>{{ livro.get_genero_display }}</td>
<td>{{ livro.paginas }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>Nenhum livro encontrado.</p>
{% endif %}
{% endblock %}</code></pre>
<p>O <code>{% extends 'base.html' %}</code> herda de um template pai, evitando duplicação de código HTML. O <code>{% url 'detalhe_livro' livro.pk %}</code> gera URLs dinamicamente baseado no nome das rotas. O <code>get_genero_display</code> é um método automático que retorna o label legível de um campo <code>choices</code>.</p>
<h2>ORM Django: Consultas Sem SQL</h2>
<p>O ORM (Object-Relational Mapping) é a joia da coroa do Django. Você trabalha com objetos Python em vez de escrever SQL, mantendo o código independente do banco de dados e seguro contra injeção SQL. A sintaxe é intuitiva e expressiva, permitindo consultas complexas com encadeamento de métodos.</p>
<h3>Querysets e Métodos Fundamentais</h3>
<p>Um QuerySet é uma coleção de objetos do banco de dados. Ele é <strong>lazy</strong> — não executa a query até você realmente precisar dos dados (iterando, convertendo para lista, ou chamando métodos como <code>count()</code>). Isso melhora performance ao permitir otimizações automáticas.</p>
<pre><code class="language-python"># Exemplos de QuerySets
from django.db.models import Q, Count, Sum, F
Filtros simples
livros_ativos = Livro.objects.filter(ativo=True)
livros_tecnico = Livro.objects.filter(genero='tecnico')
Exclusões
livros_exceto_ficção = Livro.objects.exclude(genero='ficcao')
Obtendo um único objeto
primeiro_livro = Livro.objects.first()
livro_especifico = Livro.objects.get(pk=1) # Lança DoesNotExist se não encontrar
Ordenação
livros_ordenados = Livro.objects.all().order_by('-paginas', 'titulo')
Slicing (funciona como listas Python)
primeiros_cinco = Livro.objects.all()[:5]
Contagem
total_livros = Livro.objects.filter(ativo=True).count()
Verificação de existência
existe_livro = Livro.objects.filter(titulo__icontains='Django').exists()</code></pre>
<h3>Relacionamentos e Joins</h3>
<p>Trabalhar com relacionamentos é onde o ORM brilha. Você navega entre models intuitivamente, sem pensar em JOINs SQL.</p>
<pre><code class="language-python"># Acessando relacionamentos
autor = Autor.objects.get(pk=1)
livros_do_autor = autor.livros.all() # related_name definido no Model
Filtrando através de relacionamentos
livros_do_fulano = Livro.objects.filter(autor__nome='Fulano')
autores_com_livros_tecnicos = Autor.objects.filter(livros__genero='tecnico').distinct()
Anotações e agregações
autores_com_contagem = Autor.objects.annotate(
total_livros=Count('livros'),
paginas_totais=Sum('livros__paginas')
)
for autor in autores_com_contagem:
print(f"{autor.nome}: {autor.total_livros} livros, {autor.paginas_totais} páginas")
Queries complexas com Q
livros_especiais = Livro.objects.filter(
Q(genero='tecnico') | Q(paginas__gt=500)
).filter(ativo=True)
Expressões F para operações no banco
Livro.objects.all().update(paginas=F('paginas') + 10) # Incrementa páginas</code></pre>
<p>O <code>select_related()</code> otimiza queries ao fazer JOINs internos para relacionamentos ForeignKey. O <code>prefetch_related()</code> faz buscas separadas para ManyToMany e reverse ForeignKey, depois agrupa em Python — mais eficiente em alguns casos.</p>
<pre><code class="language-python"># Otimizações de performance
livros_otimizado = Livro.objects.select_related('autor').all()
autores_otimizado = Autor.objects.prefetch_related('livros').all()
Combinado
consulta_pesada = Livro.objects.select_related('autor').prefetch_related(
'comentarios'
).filter(ativo=True)</code></pre>
<h2>Admin Django: Interface Administrativa Automática</h2>
<p>O Django Admin é uma das maiores produtividades da framework. Com pouquíssimas linhas de código, você ganha uma interface web completa e segura para gerenciar seus dados — criar, ler, atualizar e deletar registros sem escrever uma linha de HTML ou JavaScript.</p>
<h3>Configuração Básica</h3>
<p>Para expor um Model no admin, basta registrá-lo em <code>admin.py</code>. Sem nenhuma customização, o Django já oferece uma interface funcional.</p>
<pre><code class="language-python"># admin.py
from django.contrib import admin
from .models import Autor, Livro
@admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
list_display = ['nome', 'email', 'data_criacao']
list_filter = ['data_criacao']
search_fields = ['nome', 'email']
readonly_fields = ['data_criacao']
fieldsets = (
('Informações Pessoais', {
'fields': ('nome', 'email')
}),
('Metadados', {
'fields': ('data_criacao',),
'classes': ('collapse',)
}),
)
@admin.register(Livro)
class LivroAdmin(admin.ModelAdmin):
list_display = ['titulo', 'autor', 'genero', 'paginas', 'ativo']
list_filter = ['genero', 'ativo', 'publicado_em']
search_fields = ['titulo', 'autor__nome']
list_editable = ['ativo']
readonly_fields = ['data_criacao_display']
fieldsets = (
('Detalhes do Livro', {
'fields': ('titulo', 'autor', 'genero', 'paginas', 'publicado_em')
}),
('Status', {
'fields': ('ativo',)
}),
)
def data_criacao_display(self, obj):
return obj.publicado_em.strftime('%d/%m/%Y')
data_criacao_display.short_description = 'Publicado em'</code></pre>
<p>O <code>list_display</code> define quais campos aparecem na listagem. <code>list_filter</code> adiciona filtros laterais. <code>search_fields</code> ativa busca por esses campos. <code>list_editable</code> permite editar campos diretamente da listagem sem entrar no detalhe. O decorador <code>@admin.register()</code> é equivalente a <code>admin.site.register(Livro, LivroAdmin)</code>, mas mais legível.</p>
<h3>Customizações Avançadas</h3>
<p>O admin é extremamente customizável. Você pode adicionar ações em massa, inline models para relacionamentos, validações personalizadas e até redirecionar após salvar.</p>
<pre><code class="language-python"># admin.py avançado
from django.utils.html import format_html
class LivroInline(admin.TabularInline):
model = Livro
extra = 1
fields = ['titulo', 'genero', 'paginas', 'ativo']
@admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
list_display = ['nome', 'total_livros_link', 'email']
inlines = [LivroInline]
def total_livros_link(self, obj):
count = obj.livros.count()
return format_html(
'<span style="color: green; font-weight: bold;">{}</span>',
count
)
total_livros_link.short_description = 'Livros'
@admin.register(Livro)
class LivroAdmin(admin.ModelAdmin):
list_display = ['titulo', 'autor', 'status_badge', 'paginas']
actions = ['marcar_como_inativo', 'marcar_como_ativo']
def status_badge(self, obj):
if obj.ativo:
color = 'green'
texto = 'Ativo'
else:
color = 'red'
texto = 'Inativo'
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; '
'border-radius: 3px;">{}</span>',
color, texto
)
status_badge.short_description = 'Status'
def marcar_como_inativo(self, request, queryset):
queryset.update(ativo=False)
marcar_como_inativo.short_description = 'Marcar selecionados como inativos'
def marcar_como_ativo(self, request, queryset):
queryset.update(ativo=True)
marcar_como_ativo.short_description = 'Marcar selecionados como ativos'</code></pre>
<p>O <code>TabularInline</code> permite editar Livros (relacionados via ForeignKey) diretamente dentro da página do Autor. Ações em massa economizam cliques quando você precisa atualizar múltiplos registros. O <code>format_html()</code> permite renderizar HTML customizado nas listas (evita XSS automaticamente).</p>
<h2>Estrutura de Projetos Django: Organização Profissional</h2>
<p>Um projeto Django bem estruturado cresce sem dor. A organização padrão separa preocupações por aplicações (apps), cada uma com responsabilidades específicas. Um projeto pode ter múltiplas apps, cada uma podendo ser reutilizável em outros projetos.</p>
<h3>Estrutura Recomendada</h3>
<pre><code>meu_projeto/
├── manage.py
├── requirements.txt
├── config/ # Configurações do projeto
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
├── apps/
│ ├── livros/ # App de domínio
│ │ ├── migrations/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── urls.py
│ │ ├── forms.py
│ │ └── tests.py
│ ├── usuarios/ # Outra app
│ │ ├── migrations/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── urls.py
│ │ └── tests.py
│ └── comum/ # App com utilities reutilizáveis
│ ├── middleware.py
│ ├── decorators.py
│ └── utils.py
├── static/ # CSS, JS, imagens
│ └── css/
│ └── js/
├── media/ # Arquivos upload de usuários
├── templates/ # Templates globais
│ ├── base.html
│ ├── 404.html
│ └── 500.html
└── tests/ # Testes da aplicação
├── __init__.py
├── test_models.py
└── test_views.py</code></pre>
<p>O diretório <code>config</code> centraliza configurações. Cada app em <code>apps/</code> é independente e reutilizável. O <code>static/</code> contém assets que não mudam (você executa <code>collectstatic</code> em produção). O <code>media/</code> armazena arquivos enviados por usuários. Templates globais ficam na raiz, templates específicos de app dentro de <code>apps/app/templates/app/</code>.</p>
<h3>Configuração de URLs Modular</h3>
<p>URLs devem ser organizadas por app, não em um único arquivo monolítico. A configuração do projeto só importa rotas das apps.</p>
<pre><code class="language-python"># config/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('livros/', include('apps.livros.urls')),
path('usuarios/', include('apps.usuarios.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
apps/livros/urls.py
from django.urls import path
from . import views
app_name = 'livros'
urlpatterns = [
path('', views.lista_livros, name='lista'),
path('<int:pk>/', views.detalhe_livro, name='detalhe'),
path('criar/', views.CriarLivro.as_view(), name='criar'),
path('<int:pk>/editar/', views.EditarLivro.as_view(), name='editar'),
path('<int:pk>/deletar/', views.DeletarLivro.as_view(), name='deletar'),
]
apps/usuarios/urls.py
from django.urls import path
from . import views
app_name = 'usuarios'
urlpatterns = [
path('registro/', views.RegistroView.as_view(), name='registro'),
path('login/', views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'),
]</code></pre>
<p>O <code>app_name</code> evita conflitos de nomes entre apps. Na template, você usa <code>{% url 'livros:detalhe' livro.pk %}</code> em vez de apenas <code>{% url 'detalhe' livro.pk %}</code>.</p>
<h3>Configurações Seguras e Escaláveis</h3>
<p>Settings devem ser diferentes entre desenvolvimento, testes e produção. A abordagem profissional usa variáveis de ambiente.</p>
<pre><code class="language-python"># config/settings.py
import os
from pathlib import Path
from decouple import config
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = config('SECRET_KEY', default='dev-insecuro-change-in-production')
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
Terceiros
'rest_framework',
'corsheaders',
Apps locais
'apps.livros',
'apps.usuarios',
'apps.comum',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'America/Sao_Paulo'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
Segurança
if not DEBUG:
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'</code></pre>
<p>Use um arquivo <code>.env</code> para variáveis sensíveis (nunca commita no Git):</p>
<pre><code>SECRET_KEY=sua-chave-aleatoria-super-secreta
DEBUG=False
ALLOWED_HOSTS=seudominio.com,www.seudominio.com
DB_ENGINE=django.db.backends.postgresql
DB_NAME=biblioteca_db
DB_USER=postgres
DB_PASSWORD=senha_super_secreta
DB_HOST=localhost
DB_PORT=5432</code></pre>
<p>E instale <code>python-decouple</code> para ler essas variáveis:</p>
<pre><code class="language-bash">pip install python-decouple</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>Django MVT separa responsabilidades</strong> de forma clara: Models definem dados, Views contêm lógica, Templates renderizam HTML. Isso torna projetos organizados e fáceis de escalar. O <strong>ORM elimina SQL manual</strong> através de uma sintaxe Pythônica expressiva que protege contra injeção de dados e funciona com qualquer banco relacional. O <strong>Admin Django oferece CRUD automático</strong> sem você escrever uma linha de interface, economizando dias de desenvolvimento. Finalmente, uma <strong>estrutura modular com apps independentes</strong> permite que você reutilize componentes, organize código por domínio e trabalhe em equipe sem conflitos. Com esses pilares, você está pronto para construir aplicações web robustas em Django.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.djangoproject.com/" target="_blank" rel="noopener noreferrer">Documentação Oficial do Django</a></li>
<li><a href="https://docs.djangoproject.com/en/stable/topics/db/models/" target="_blank" rel="noopener noreferrer">Django Models - Documentação Oficial</a></li>
<li><a href="https://realpython.com/django-orm/" target="_blank" rel="noopener noreferrer">Django ORM - Real Python</a></li>
<li><a href="https://www.feldroy.com/books/two-scoops-of-django-3-x" target="_blank" rel="noopener noreferrer">Two Scoops of Django - Daniel Roy Greenfeld e Audrey Roy Greenfeld</a></li>
<li><a href="https://lincolnloop.com/blog/" target="_blank" rel="noopener noreferrer">Django Best Practices - Lincoln Loop</a></li>
</ul>
<p><!-- FIM --></p>