Entendendo Relações Genéricas no framework Django
Tags: python, django, database, mvc
Published: 2023-04-16 20:47:23

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7v50hqjhhnqhjmz2oz8.png)

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()
```
[Repositório no Github]
(https://github.com/WesleyVitor/understand_generic_relation)
## Referências

[https://leportella.com/pt-br/relacoes-genericas-django/](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.linkedin.com/pulse/rela%C3%A7%C3%B5es-gen%C3%A9ricas-generic-relation-com-django-fabio-carvalho/?originalSubdomain=pt)

[https://www.djangoproject.com](https://www.djangoproject.com/)