Relacionamentos ManyToMany genéricos com Django
Publicado em
A documentação oficial do django cobre muito bem sua funcionalidade de relacionamento genérico na cardinalidade OneToMany
(um para muitos ou 1:N), porém, quando é preciso implementar um relacionamento genérico de ManyToMany
(muitos para muitos ou N:N) não há muita documentação a respeito.
Recentemente, precisei implementar um N:N com um lado genérico, e encontrei uma biblioteca chamada django-gm2m que me foi muito útil nesta tarefa, e vou descrever aqui dando dicas de como implementar evitando possíveis dores de cabeça.
Modelagem
Para demonstrar esta modelagem iremos usar a ideia de uma biblioteca digital onde os usuários podem alugar mídias em audio, video e texto e a mesma mídia pode estar alugada para mais de um usuário.
Neste caso teremos do lado N os usuários e do lado N genérico as mídias. E seus models no django sem aplicar o relacionamento são:
from django.db import models
class Usuario(models.Model):
nome = models.CharField(max_length=80)
email = models.EmailField()
class Audio(models.Model):
titulo = models.CharField(max_length=60)
num_faixas = models.PositiveSmallIntegerField()
class Video(models.Model):
titulo = models.CharField(max_length=60)
resolucao_x = models.PositiveSmallIntegerField()
resolucao_y = models.PositiveSmallIntegerField()
class Texto(models.Model):
titulo = models.CharField(max_length=60)
num_paginas = models.PositiveSmallIntegerField()
Instalação
Para instalar o django-gm2m rode em sua virtualenv ou ambiente do seu projeto:
$ pip install django-gm2m
Em seguida certifique-se de ter em seus INSTALLED_APPS
o django.contrib.contenttypes
e adicione gm2m
:
INSTALLED_APPS = [
...
'django.contrib.contenttypes',
...
'gm2m',
]
Montando o Relacionamento
Para criar o relacionamento insira do lado não genérico o campo GM2MField
:
from gm2m import GM2MField
class Usuario(models.Model):
...
midias = GM2MField(
'Video', 'Audio', 'Texto', through='Aluguel', related_name='usuarios'
)
Feito isso já estaria pronto e você adicionaria uma mídia a um usuário através de usuario.midias.add(video)
.
Porém, eventualmente será necessário adicionar algum campo no model intermediário, gerando uma migração de dados seguida de alterações no código pois quando você define um model intermediário para uma relação N:N no django, a vinculação através do .add()
é bloqueada, te obrigando a usar o manager do model.
Para evitar isso, se você possuí planos de incrementar esta modelagem (que é o nosso caso) já crie da forma que será demonstrada, que é passando o parâmetro through
para o campo e criando o model intermediário, que no nosso caso chamaremos de Aluguel
:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Usuario(models.Model):
...
midias = GM2MField(
'Video', 'Audio', 'Texto', through='Aluguel', related_name='usuarios'
)
class Aluguel(models.Model):
# campos base da modelagem
usuario = models.ForeignKey(Usuario)
midia = GenericForeignKey('midia_ct', 'midia_id')
midia_ct = models.ForeignKey(ContentType)
midia_id = models.PositiveIntegerField()
# campos extras
alugado_em = models.DateField(auto_now_add=True)
vencido_em = models.DateField(null=True)
Observe que para o lado genérico é utilizado o mesmo padrão de relacionamento genérico descrito na documentação oficial do django. Uma GenericForeignKey
composta de uma ForeignKey
para ContentType
e um PositiveIntegerField
para guardar o id da instância do model referenciado.
Para vincular um usuário a uma mídia basta criar e salvar um aluguél da seguinte forma:
>>> Aluguel(usuario=usuario, midia=video).save()
Conclusão
Relacionamento genérico é uma ferramenta muito poderosa e permite alto nível de abstração. Mas modelagens deste tipo costumam a levar a tabelas com um volume de dados muito grande, portanto analise sua arquitetura antes de implementar para ter certeza que esta é a modelagem correta para seu caso antes de implementa-la.
Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional .