Entendendo Relações Genéricas no framework Django

wesleymorais

Wesley de Morais

Posted on April 16, 2023

Entendendo Relações Genéricas no framework Django

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.

der

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Settings

INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    'core.apps.CoreConfig',
]
Enter fullscreen mode Exit fullscreen mode

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)
            )
        ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Shell

Entrando no shell podemos testar a nossa modelagem, entre nele com seguinte comando:

python manage.py shell
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
  • 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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Repositório no Github

Referências

https://leportella.com/pt-br/relacoes-genericas-django/

https://www.linkedin.com/pulse/relações-genéricas-generic-relation-com-django-fabio-carvalho/?originalSubdomain=pt

https://www.djangoproject.com

💖 💪 🙅 🚩
wesleymorais
Wesley de Morais

Posted on April 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related