Utilizando GenericRelations na prática
Para se ter aplicações plugáveis é fundamental mantêr o máximo de independência de cada uma em seu próprio domínio. Isso tem melhorado cada dia mais no Django.
Uma das coisas mais bacanas que surgiram nos últimos temos foram as Generic Relations. Mas o que é isso? Generic Relations tratam-se de um recurso do framework para se criar relacionamento evitando-se acoplamento no banco de dados, e um mínimo de acoplamento pelo lado da aplicação.
Vamos supor que você possua as seguintes classes de modelo em um mini-CRM, como estas abaixo:
class EMail(models.Model):
assunto = models.CharField(max_length=200)
data_recebimento = models.DateTimeField(default=datetime.now, blank=True)
de = models.TextField()
para = models.TextField()
cc = models.TextField(blank=True)
bcc = models.TextField(blank=True)
corpo = models.TextField()
class Cliente(models.Model):
nome = models.CharField(max_length=100)
class Fornecedor(models.Model):
nome = models.CharField(max_length=100)
class Funcionario(models.Model):
nome = models.CharField(max_length=100)Como já deve ter percebido, a intenção aqui é vincular um E-Mail a Clientes, Fornecedores e Funcionarios sem que haja um relacionamento direto entre eles. Isto se faz necessários normalmente em dois casos:
- Quando se quer relacionar uma classe a varias outras sob um mesmo atributo (como é o nosso caso)
- Quando se quer evitar um relacionamento direto através do banco de dados (também o o nosso caso)
Como então faremos isso?
Primeiramente é necessário criar na classe "genérica" (que em nosso caso é o EMail) o suporte para que receba relacionamentos genéricos. Isto se faz através do tipo de atributo GenericForeignKey que deve ser importado do pacote django.contrib.contenttypes.generic. No entanto, isto não é suficiente, pois serão necessários dois campos para armazenar o relacionamento genérico, o que inclui a importação de outra classe, chamada ContentType. Veja abaixo:
from django.contrib.contenttypes.generic import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class EMail(models.Model):
...
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
enviado_para = GenericForeignKey()Isso cria a interface necessária para que vários elementos de tipos diferentes possam ser relacionados sob o atributo enviado_para. Os campos content_type e object_id sempre deverão possuir esses nomes, ao passo que o atributo enviado_para é de sua escolha.
Agora vá até as classes que deseja que sejam relacionadas ao EMail. Isso se faz através da classe GenericRelation, contida no mesmo pacote. Veja como fica:
from django.contrib.contenttypes.generic import GenericRelation
class Cliente(models.Model):
...
emails = GenericRelation(EMail)
class Fornecedor(models.Model):
...
emails = GenericRelation(EMail)
class Funcionario(models.Model):
...
emails = GenericRelation(EMail)Como pode notar, utilizamos um mesmo padrão de atributos porque é nossa intenção generalizar o modelo para que esteja seja mais acessível em prováveis templates e outras formas de interação entre os elementos relacionados. Portanto, você pode variar estes nomes como preferir, mas não se esqueça de que coerência em seu código (especialmente respeitando as interfaces) é essencial.
Agora veja como ficou a definição do banco de dados quando rodo o comando python manage.py sqlall myapp1:
BEGIN;
CREATE TABLE "myapp1_funcionario" (
"id" integer NOT NULL PRIMARY KEY,
"nome" varchar(100) NOT NULL
)
;
CREATE TABLE "myapp1_fornecedor" (
"id" integer NOT NULL PRIMARY KEY,
"nome" varchar(100) NOT NULL
)
;
CREATE TABLE "myapp1_email" (
"id" integer NOT NULL PRIMARY KEY,
"assunto" varchar(200) NOT NULL,
"data_recebimento" datetime NOT NULL,
"de" text NOT NULL,
"para" text NOT NULL,
"cc" text NOT NULL,
"bcc" text NOT NULL,
"corpo" text NOT NULL,
"content_type_id" integer NOT NULL REFERENCES "django_content_type" ("id"),
"object_id" integer unsigned NOT NULL
)
;
CREATE TABLE "myapp1_cliente" (
"id" integer NOT NULL PRIMARY KEY,
"nome" varchar(100) NOT NULL
)
;
COMMIT;Você pode notar que no banco de dados não há qualquer referência entre tabelas relacionadas. No entanto, o relacionamento é possível.
Ao navegar pelo Admin, você também não irá notar os relacionamentos, isso porque eles ainda não foram de fato suportados pelo Admin, especialmente por este estar em um momento de redesign. Mas caso acesse o IPython através do comando python manage.py shell poderá fazer coisas como estas abaixo:
>>> from myapp1.models import EMail, Cliente
>>> email1 = EMail()
>>> email1.assunto = 'Minha mensagem'
>>> email1.de = 'marinho@teste.com'
>>> email1.para = 'cliente@teste.com'
>>> email1.corpo = 'Ola'
>>> email1.save()
>>> cliente1 = Cliente.objects.create(nome='Rodolfo')
>>> cliente1.emails.add(email1)
>>> email1.enviado_para
Para se utilizar Generic Relations é fundamental ter os seguintes contribs instalados em seu projeto:
- django.contrib.contenttypes
- django.contrib.sites
Isso porque, de fato, o Django irá utilizar os recursos desses dois contribs para identificar as classes de modelo no projeto e suas respectivas instâncias (registros da tabelas das classes EMail, Cliente, Funcionario e Fornecedor).
Marinho Brandão
comentou há 5 months, 4 weeks:
Ótimo post Marinho, não sabia que django tinha geric relations (que gosto tanto em rails) Abraços!