Proxy Models: quase uma bala de prata
Há alguns acertos de design que são praticamente incontestáveis.
O novo recurso de classes de modelo do tipo "proxy = True" é um desses acertos.
Esta é uma das novidades da versão 1.1 (há, de fato, várias outras novidades na QuerySet que resolvem diversos outros problemas) que vão me ajudar bastante, do tipo que dá a tentação de reescrever muita coisa pra ficar mais elegante e compatível com a novidade.
Vamos partir da seguinte classe de modelo, que aliás, é um caso bastante comum em diversas situações:
from django.db import models
class Funcionario(models.Model):
nome = models.CharField(max_length=50)
vendedor = models.BooleanField(blank=True, default=False)
gerente = models.BooleanField(blank=True, default=False)
def __unicode__(self):
return self.nome
Para registrar a classe "Funcionario" no Admin, você pode criar a classe "AdminFuncionario" no módulo admin.py da aplicação:
class AdminFuncionario(ModelAdmin):
list_display = ('nome','vendedor','gerente')
list_filter = ('vendedor','gerente')
Veja, temos aí um caso comum: um funcionário tem um nome, e pode ser, ora vendedor, ora gerente, ora ambos ou ora nenhum dos dois.
A consequência conhecida é que sempre é necessário usar de filtros, managers e outros meios para filtrar quando se deseja um dos 4 variantes de funcionários. Dentre as situações mais comuns que precisamos resolver estão:
- Filtro através de ForeignKey em widgets de forms;
- Registro da classe no Admin limitado a apenas uma vez;
- Filtro através do manager (exemplo: Funcionario.objects.filter(vendedor=True));
- Necessidade de forçar o valor de um dos campos de flag ("vendedor" e "gerente") sempre que for criar um novo objeto;
- Filtros no Admin quando se deseja mostrar apenas vendedores ou gerentes;
- Necessidade de forçar valores default quando criar ModelForms específicos para essa classe de modelo.
Bom, é provável que você já tenha passado por pelo menos uma dessas situações, e as soluções em geral envolvem todo tipo de gato, desde scripts utilizando JavaScript até métodos em managers, widgets, forms, etc.
Agora veja a solução, quando se cria classes de modelo proxy para resolver essa situação:
from django.db import models
class Funcionario(models.Model):
nome = models.CharField(max_length=50)
vendedor = models.BooleanField(blank=True, default=False)
gerente = models.BooleanField(blank=True, default=False)
def __unicode__(self):
return self.nome
class VendedorManager(models.Manager):
def get_query_set(self):
qs = super(VendedorManager, self).get_query_set()
qs = qs.filter(vendedor=True)
return qs
class Vendedor(Funcionario):
class Meta:
proxy = True
objects = VendedorManager()
def save(self, *args, **kwargs):
self.vendedor = True
return super(Vendedor, self).save(*args, **kwargs)
class GerenteManager(models.Manager):
def get_query_set(self):
qs = super(GerenteManager, self).get_query_set()
qs = qs.filter(gerente=True)
return qs
class Gerente(Funcionario):
class Meta:
proxy = True
objects = GerenteManager()
def save(self, *args, **kwargs):
self.gerente = True
return super(Gerente, self).save(*args, **kwargs)
Observe que nada foi modificado na classe de modelo "Funcionario". Nem mesmo foi necessária alguma importação adicional.
Feito isso, você pode usar tranquilamente as novas classes "Gerente" e "Vendedor" naturalmente, em qualquer lugar. A distinção entre elas inclui:
- Signals
- Registro da classe no Admin
- Forms
- Filtros em fields de forms, como ModelChoiceField (que também vale para o Admin)
- Filtros no Admin
- Manager
Já tudo o mais que não for determinado manualmente, irá trabalhar de forma específica e transparente, vinculado à classe herdada.
Não serão criadas tabelas no banco de dados para esta classe, ela é totalmente abstrata, porém, tem o papel de trabalhar como interface para uma classe numa situação peculiar.
Veja como fica por exemplo o "admin.py" (agora o arquivo completo):
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from django import forms
from models import Funcionario, Vendedor, Gerente
class AdminFuncionario(ModelAdmin):
list_display = ('nome','vendedor','gerente')
list_filter = ('vendedor','gerente')
class FormGerente(forms.ModelForm):
class Meta:
model = Gerente
exclude = ('gerente',)
class AdminGerente(ModelAdmin):
form = FormGerente
class FormVendedor(forms.ModelForm):
class Meta:
model = Vendedor
exclude = ('vendedor',)
class AdminVendedor(ModelAdmin):
form = FormVendedor
admin.site.register(Funcionario, AdminFuncionario)
admin.site.register(Vendedor, AdminVendedor)
admin.site.register(Gerente, AdminGerente)
Veja que agora cada uma delas tem seu próprio Admin ocultando os campos que forem desnecessários e completamente customizável (o que vale também para os templates customizáveis para o Admin).
É ou não é fantástico?