DEV Community

Wesley de Morais
Wesley de Morais

Posted on

2

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

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay