Python Admin

Django em Python: MVT, ORM, Admin e Estrutura de Projetos: Do Básico ao Avançado Já leu

Entendendo o MVT: A Arquitetura do Django Django segue um padrão arquitetural chamado MVT (Model-View-Template), 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. 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. Models: Definindo sua Estrutura de Dados 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

Entendendo o MVT: A Arquitetura do Django

Django segue um padrão arquitetural chamado MVT (Model-View-Template), 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.

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.

Models: Definindo sua Estrutura de Dados

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.

# 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

O ForeignKey cria um relacionamento um-para-muitos: cada livro pertence a um autor, mas um autor pode ter múltiplos livros. O parâmetro on_delete=models.CASCADE define que livros serão deletados quando seu autor for removido. O related_name='livros' permite acessar os livros de um autor através de autor.livros.all().

Views: A Lógica da Sua Aplicação

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.

# 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')

A diferença crucial: com ListaAutores, você herda toda a lógica de paginação, ordenação e renderização. O select_related na FBV é uma otimização que reduz consultas ao banco — sem ele, Django faria uma query para cada livro ao acessar o autor.

Templates: Renderizando o HTML

Templates são arquivos HTML com sintaxe especial do Django que permitem inserir dados dinâmicos. Você acessa variáveis com {{ }}, executa lógica com {% %}, e itera sobre listas com {% for %}.

<!-- 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 %}

O {% extends 'base.html' %} herda de um template pai, evitando duplicação de código HTML. O {% url 'detalhe_livro' livro.pk %} gera URLs dinamicamente baseado no nome das rotas. O get_genero_display é um método automático que retorna o label legível de um campo choices.

ORM Django: Consultas Sem SQL

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.

Querysets e Métodos Fundamentais

Um QuerySet é uma coleção de objetos do banco de dados. Ele é lazy — não executa a query até você realmente precisar dos dados (iterando, convertendo para lista, ou chamando métodos como count()). Isso melhora performance ao permitir otimizações automáticas.

# 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()

Relacionamentos e Joins

Trabalhar com relacionamentos é onde o ORM brilha. Você navega entre models intuitivamente, sem pensar em JOINs SQL.

# 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

O select_related() otimiza queries ao fazer JOINs internos para relacionamentos ForeignKey. O prefetch_related() faz buscas separadas para ManyToMany e reverse ForeignKey, depois agrupa em Python — mais eficiente em alguns casos.

# 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)

Admin Django: Interface Administrativa Automática

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.

Configuração Básica

Para expor um Model no admin, basta registrá-lo em admin.py. Sem nenhuma customização, o Django já oferece uma interface funcional.

# 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'

O list_display define quais campos aparecem na listagem. list_filter adiciona filtros laterais. search_fields ativa busca por esses campos. list_editable permite editar campos diretamente da listagem sem entrar no detalhe. O decorador @admin.register() é equivalente a admin.site.register(Livro, LivroAdmin), mas mais legível.

Customizações Avançadas

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.

# 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'

O TabularInline 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 format_html() permite renderizar HTML customizado nas listas (evita XSS automaticamente).

Estrutura de Projetos Django: Organização Profissional

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.

Estrutura Recomendada

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

O diretório config centraliza configurações. Cada app em apps/ é independente e reutilizável. O static/ contém assets que não mudam (você executa collectstatic em produção). O media/ armazena arquivos enviados por usuários. Templates globais ficam na raiz, templates específicos de app dentro de apps/app/templates/app/.

Configuração de URLs Modular

URLs devem ser organizadas por app, não em um único arquivo monolítico. A configuração do projeto só importa rotas das apps.

# 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'),
]

O app_name evita conflitos de nomes entre apps. Na template, você usa {% url 'livros:detalhe' livro.pk %} em vez de apenas {% url 'detalhe' livro.pk %}.

Configurações Seguras e Escaláveis

Settings devem ser diferentes entre desenvolvimento, testes e produção. A abordagem profissional usa variáveis de ambiente.

# 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'

Use um arquivo .env para variáveis sensíveis (nunca commita no Git):

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

E instale python-decouple para ler essas variáveis:

pip install python-decouple

Conclusão

Você aprendeu que Django MVT separa responsabilidades de forma clara: Models definem dados, Views contêm lógica, Templates renderizam HTML. Isso torna projetos organizados e fáceis de escalar. O ORM elimina SQL manual através de uma sintaxe Pythônica expressiva que protege contra injeção de dados e funciona com qualquer banco relacional. O Admin Django oferece CRUD automático sem você escrever uma linha de interface, economizando dias de desenvolvimento. Finalmente, uma estrutura modular com apps independentes 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.

Referências


Artigos relacionados