Django UtilidadesMarinho Brandão

Proxy Models: quase uma bala de prata

django Publicado há 9 meses, 2 semanas

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?

 
django python

Artigos recentes