Django UtilidadesMarinho Brandão

Jan/08 10

Utilizando GenericRelations na prática

django
Publicado há 12 months por Marinho Brandao

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

Links sociais


Comentários


Tiago Bastos
comentou há 5 months, 4 weeks:

Ótimo post Marinho, não sabia que django tinha geric relations (que gosto tanto em rails) Abraços!


Rafael C. de B.
comentou há 5 months, 4 weeks:

Bem interessante isso, quando eu voltar das minhas ferias vou dar uma olhada melhor nisso, eu usei o ContentType no meu blog para os comentarios mas para ser sincero ainda nao sei a utilidade disso hehehe mas agora eu tive uma noção do pra q q serve.[]'s


semente
comentou há 5 months, 4 weeks:

Muito bom!!!


Escreva o seu


.net adoradores ajax android apple banco de dados blogosfera brasil django emprego família gadgets google inovação java linux lua microsoft musica opensocial opinião publicidade python rails religiao screencast seguranca software-livre tdd web windows yadsel

Artigos recentes