Sumário
- Introdução a relações genéricas
- Estudo de caso com django
- Criando aplicação sem Relação genérica
- Refatorando para ter relação genérica
- Referências
Introdução a relações genéricas
Para entender o que são relações genéricas devemos antes entender algumas associações de banco de dados. Quando temos modelos como Post e Curtida podemos ter uma relação muitos para um, pois um Post pode ter muitas Curtidas, e uma Curtida é de um Post.
Caso tivermos um modelo Comentário que também pode ter curtida, então não seria de bacana utilizar o modelo Curtida anterior, assim criaríamos um modelo CurtidaComentário, mas podemos entender que ambos os modelos, Curtida e CurtidaComentario, tem o mesmo objetivo, porém para modelos diferentes.
Assim, podemos usar relação genérica, pois podemos fazer uso do famoso polimorfismo para o modelo Curtida ser tanto usado pelo Post quanto pelo Comentário.
Estudo de caso com Django
Vamos utilizar o nosso problema da sessão anterior para entender na prática, então podemos fazer uso de um diagrama entidade relacionamento para clarear a mente.
Na imagem acima temos, 1 Post tem muitos comentários, 1 comentário pode ter muitas curtidas e 1 Post também pode ter muitas curtidas, porém quando se curte um comentário, este tem relação com um post, só que a instância de curtida de um comentário deve se relacionar com o comentário, mas a curtida de um post não deve se relacionar com um comentário, pois a curtida foi do post, e não do comentário, só que vai ser utilizado o mesmo modelo.
Criando aplicação sem Relação genérica
Dependências
Primeiramente, crie uma ambiente virtual para instalar as dependências da aplicação. Dentro do ambiente virtual instale as seguintes dependências:
mkdir understand_generic_relation
python -m venv venv
source venv/bin/activate
pip install django
Instalamos o django e django rest framework para fazer a criação da nossa aplicação e dentro da pasta do ambiente virtual digite o comando para criar um projeto e a nossa aplicação:
django-admin startproject understand_generic_relation .
django-admin startapp core
Settings
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
'core.apps.CoreConfig',
]
Models
from django.db import models
from django.utils import timezone
# Create your models here.
class Post(models.Model):
text = models.TextField(max_length=400)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.DO_NOTHING)
text = models.TextField(max_length=130)
def __str__(self) -> str:
return f"{(self.post.id)}-comment#{self.id}"
class Like(models.Model):
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.DO_NOTHING, related_name="likes_post")
comment = models.ForeignKey(Comment,null=True, blank=True, on_delete=models.DO_NOTHING, related_name="likes_comment")
created_at = models.DateTimeField(default=timezone.now)
class Meta:
# The instance of like is to Post exclusive or Comment
constraints = [
models.UniqueConstraint(
fields=['post'],
name="unique_like_to_post",
condition=models.Q(post__isnull=False)
),
models.UniqueConstraint(
fields=['comment'],
name="unique_like_to_comment",
condition=models.Q(comment__isnull=False)
)
]
Vamos criar 3 modelos, sendo um Post, um Comment que tem relação com Post e um Like que tem relação com ou post ou comentário.
Crie a migração e execute-as com o seguinte comando no terminal
python manage.py makemigrations
python manage.py migrate
Shell
Entrando no shell podemos testar a nossa modelagem, entre nele com seguinte comando:
python manage.py shell
Criando uma postagem e seus comentários:
>>> from core.models import *
>>> post = Post(text="Atualização do Etherium vai ser boa?")
>>> post.save()
>>> comment1 = Comment(text="Claro que vai!", post=post)
>>> comment1.save()
>>> comment2 = Comment(text="Já deu bom!", post=post)
>>> comment2.save()
Podemos agora criar instâncias de Like para uma postagem ou um comentário
>>> like_to_post1 = Like(post=post)
>>> like_to_post1.save()
>>> like_to_comment1 = Like(comment=comment1)
>>> like_to_comment1.save()
Quando tentarmos criar algo como “Like(post=post, comment=comment1).save()” receberemos a seguinte mensagem de erro:
django.db.utils.IntegrityError: UNIQUE constraint failed: core_like.post_id
Assim nosso objetivo foi concluído, mas temos o seguinte o problema. Caso necessitarmos no nosso sistema de mais elementos que podem serem curtidos, o nosso modelo teria muitas chaves estrangeiras nulas para cada instância criada, e também aumentaríamos o tamanho dele.
Refatorando para ter relação genérica
Antes de ir para as relações genéricas, vamos entender o modelo ContentType, toda vez que um modelo é criado, é criado uma instância de contenttype para aquele modelo, assim este guarda o identificador do modelo criado. Esse modelo vai nos ajudar a saber se uma curtida é de um modelo Comment ou Post.
Flush
Vamos apagar os dados no banco com o seguinte comando:
python manage.py flush
Models
from django.db import models
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
# Create your models here.
class Post(models.Model):
text = models.TextField(max_length=400)
likes = GenericRelation("Like")
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.DO_NOTHING)
text = models.TextField(max_length=130)
likes = GenericRelation("Like")
def __str__(self) -> str:
return f"{(self.post.id)}-comment#{self.id}"
class Like(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING, default=None, null=True)
object_id = models.PositiveIntegerField(default=None, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
created_at = models.DateTimeField(default=timezone.now)
- Criação do atributo content_type vai fazer a relação entre os modelos, pois é uma chave estrangeira de ContentType
- Criação do atributo object_id vai guardar o valor da instância criada seja de um comentário ou de uma postagem
- Criação do atributo content_object vai guardar a instância em si, dessa forma quando passarmos a instância para este atributo, então ele vai pegar o id do content_type relacionado e atribuir ao nosso atributo, e também vai pegar o id da instância e atribuir ao nosso atributo.
- Afim de conseguir pegar todas as instâncias de um post, criamos o atributo likes do tipo GenericRelation no modelo de Post
- Afim de conseguir pegar todas as instâncias de um comentário, criamos o atributo likes do tipo GenericRelation no modelo de Comment ### Shell
Criando uma postagem e seus comentários:
>>> from core.models import *
>>> post = Post(text="Atualização do Etherium vai ser boa?")
>>> post.save()
>>> comment1 = Comment(text="Claro que vai!", post=post)
>>> comment1.save()
>>> comment2 = Comment(text="Já deu bom!", post=post)
>>> comment2.save()
Podemos agora criar instâncias de Like para uma postagem ou um comentário
>>> like_to_post1 = Like(content_object=post)
>>> like_to_post1.save()
>>> like_to_comment1 = Like(content_object=comment1)
>>> like_to_comment1.save()
Top comments (0)