Banco de Dados
Dados são Quaisquer informações/valores que estamos manipulando em nossa aplicação! Tipos de dados são: temporários (variáveis, listas etc), persistentes (banco de dados, arquivos) e semipersistente (sessão, cookies, cache).
O dois principais tipos de dados persistentes são SQL, tem por base tabelas, e NoSql, que tem por base documentos em coleções.
No django ao criar um projeto ele cria automaticamente um arquivo chamado: db.sqlite3 (banco de dados sqlite) que é um arquivo de banco de dados que não precisa de configurações extras nem software, pois Django suporta sqlite nativamente.
ORM - Models (Modelos)
O ORM (Object-Relational Mapping), ou Mapeamento Objeto-Relacional, é uma técnica que conecta a programação orientada a objetos com bancos de dados relacionais. Ele permite que você interaja com o banco de dados usando objetos e métodos em vez de escrever diretamente comandos SQL.
No contexto do Django, o ORM é um componente poderoso que abstrai o banco de dados, permitindo que você trabalhe com tabelas como se fossem classes Python e registros como objetos dessas classes. Esse recurso integrado é também chamado de Models. Em cada aplicação tem um arquivo chamado models.py, onde serão inseridos os modelos do banco de dados. O django fornece uma classe Model que deve ser herdada pelos nossos modelos. Em Django, uma classe que herda de models.Model é tratada como um modelo para criar tabelas no banco de dados.
SINTAXE
Importar classe modelo: from django.db import models. Herdar Classe Modelo: class Modelo(models.Model): ...
O nome da classe será convertido em tabela em meu banco de dados e em letras minúsculas e plural (pluralidade). ou seja o modelo Livro será converitdo numa tabela chamada livros. O campo id é criado automaticamente.
Tipos de Campos no Django Models
| Campo | Descrição | Exemplo |
|---|---|---|
SlugField |
Armazena strings em formato amigável para URLs (ex: meu-artigo). |
slug = models.SlugField(max_length=50) |
CharField |
Armazena strings de tamanho limitado. | nome = models.CharField(max_length=100) |
IntegerField |
Armazena números inteiros. | idade = models.IntegerField() |
BooleanField |
Armazena valores booleanos (True ou False). |
ativo = models.BooleanField(default=True) |
ForeignKey |
Relaciona um modelo com outro em uma relação de muitos-para-um. | categoria = models.ForeignKey(Categoria, on_delete=models.CASCADE) |
ManyToManyField |
Relaciona um modelo com outro em uma relação de muitos-para-muitos. | tags = models.ManyToManyField(Tag) |
OneToOneField |
Relaciona um modelo com outro em uma relação de um-para-um. | perfil = models.OneToOneField(Perfil, on_delete=models.CASCADE) |
DateTimeField |
Armazena data e hora. Pode ser configurado para registrar automaticamente a data de criação. | criado_em = models.DateTimeField(auto_now_add=True) |
TextField |
Armazena strings grandes, sem limite fixo de tamanho. | descricao = models.TextField() |
FloatField |
Armazena números de ponto flutuante. | preco = models.FloatField() |
EmailField |
Armazena endereços de e-mail. Valida automaticamente os formatos corretos. | email = models.EmailField() |
Principais Parâmetros nos Modelos Django
| Parâmetro | Descrição | Exemplo |
|---|---|---|
on_delete |
Define o comportamento quando o objeto relacionado é excluído. Ex: CASCADE, SET_NULL. |
ForeignKey(Categoria, on_delete=models.CASCADE) |
related_name |
Nome da relação reversa usada para acessar o modelo relacionado. | ForeignKey(Categoria, related_name='produtos') |
max_length |
Define o tamanho máximo de um campo de string. | CharField(max_length=100) |
db_index |
Cria um índice no banco de dados para o campo. | CharField(max_length=100, db_index=True) |
default |
Define um valor padrão para o campo. | BooleanField(default=True) |
blank |
Permite que o campo seja opcional nos formulários. | CharField(max_length=100, blank=True) |
editable |
Determina se o campo aparece no admin do Django e em formulários. | BooleanField(editable=False) |
unique |
Garante que os valores no campo sejam únicos no banco de dados. | CharField(max_length=100, unique=True) |
null |
Permite que o campo armazene valores nulos no banco de dados. | CharField(max_length=100, null=True) |
choices |
Define um conjunto de opções para o campo, exibidas em forma de seleção. | status = models.CharField(max_length=10, choices=[('A', 'Ativo'), ...]) |
primary_key |
Indica que o campo é a chave primária do modelo. | id = models.IntegerField(primary_key=True) |
Tipos de on_delete no Django
| Tipo | Descrição |
|---|---|
CASCADE |
Exclui os objetos relacionados automaticamente quando o objeto pai é excluído. |
PROTECT |
Impede a exclusão do objeto pai se houver objetos relacionados. Lança uma exceção. |
SET_NULL |
Define o campo relacionado como NULL quando o objeto pai é excluído. Requer null=True. |
SET_DEFAULT |
Define o campo relacionado com o valor padrão especificado quando o objeto pai é excluído. |
SET() |
Define o campo relacionado com um valor ou função personalizada quando o objeto pai é excluído. |
DO_NOTHING |
Não executa nenhuma ação ao excluir o objeto pai. Pode causar erros de integridade no banco de dados. |
get_absolute_url é um método usado no Django para gerar a URL canônica (principal) de um objeto. Ele é especialmente útil em aplicações web, onde queremos criar links dinâmicos para visualizar, editar ou realizar ações sobre um determinado objeto. Pode ser usado em suas paginas {{obj.get_absolute_url}}. A URL está vinculada diretamente ao modelo, tornando o código mais organizado. É possivel chamar esse método para obter a URL do objeto diretamente no template. Geralmente usa a função reverse() do Django para gerar a URL.
from django.db import models
from django.urls import reverse
# Importa duas classes para validação de mínimo e máximo permitido nos campos
from django.core.validators import MinValueValidator, MaxValueValidator
# Define a uma classe do tipo Model
class Autor(models.Model):
nome = models.CharField(null=True, max_length=100)
sobrenome = models.CharField(null=True, max_length=100)
endereco = models.OneToOneField(
Endereco, on_delete=models.CASCADE, null=True)
class Livros(models.Model):
titulo = models.CharField(max_length=50)
# Define o valor minimo e maximo do campo
avaliacao = models.IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)])
# Define o atributo em autor para relacionar todos os livros
autor = models.ForeignKey(
Autor, on_delete=models.CASCADE, null=True, related_name="livros")
is_bestselling = models.BooleanField(default=False)
slug = models.SlugField(default="", blank=True, db_index=True)
published_countries = models.ManyToManyField(Country)
# sobrescreve o método get_absolute_url
def get_absolute_url(self):
return reverse("book-detail", args={self.slug})
# Sobrescreve o método string
def __str__(self):
return f"{self.title} ({self.rating})"
class Endereco(models.Model):
rua = models.CharField(max_length=80)
codigo_postal = models.CharField(max_length=5)
cidade = models.CharField(max_length=50)
Define o campo autor como chave estrangeira para Autor ("Tabela"), uso foreigney para relação muitos para um e define o tipo de exclusão (cascade, se apagar o autor apaga todos os livros).
O metodo save() de models pode ser sobrescrito. Nesse caso abaixo ele é sbrescrito para editar o slug. O uso do args e kwargs é usado para manter a flexibilidade, caso um programador queira mandar um argumento a mais. Se não for usado pode quebrar. ex.: obj.save(force_insert=True) lançaria um erro sem `*args, **kwargs`.
slugify é uma função do Django que converte strings em slugs. Nesse caso o campo titulo é usado para produzir um slug.
from django.db import models
from django.utils.text import slugify
Class Teste(models.Model):
titulo = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.titulo)
# Chama o método save da classe pai, para concluir a tarefa de salvamento
super().save(*args, **kwargs)
Migrações (Migrations)
Migrations no Django são arquivos que registram as alterações feitas nos seus modelos e as traduzem para comandos SQL, permitindo que essas alterações sejam aplicadas ao banco de dados de forma organizada e controlada. Quando você cria ou modifica um modelo no Django, é necessário gerar uma migration para refletir essas mudanças no banco de dados. As migrações estarão na pasta migrations de cada um dos aplicativos.
Criar Arquivo de Migração
Na pasta raiz do projeto crie o arquivo de migração, com o comando abaixo. Assim é criado um arquivo chamado 0001_intial.py (Toda alteração na estrutura gerará um novo arquivo sequencial na pasta migrations: 0002_...) com todas as configurações para serem inseridas no banco de dados posteriormente. "TODA VEZ QUE ALTERAR/atualizar SEUS MODELOS FAÇA A MIGRAÇÃO"
$ python manage.py makemigrations
Executar o Arquivo de Migração
O comando migrate, verificará todos os arquivos de migração da sua pasta migrations, e executará todas as migrações ainda não executadas. A primeira vez que esse comando é executado ele migra todos os app instalados em settings.py. Para inserir essas migrações em seu banco de dados, execute o comando abaixo:
$ python manage.py migrate
Comandos Extras Migrações
$ python manage.py migrate --fake app_name zero # Apagar/limpar as tabelas (drop)
$ python manage.py sqlmigrate app_name 0001 # mostra o SQL que será executado para a migração número 0001 da aplicação chamada app_name
CRUD com ORM
O CRUD no contexto do Django refere-se às operações básicas que podem ser realizadas em um banco de dados usando os modelos do framework. CRUD é um acrônimo para Create (Criar), Read (Ler), Update (Atualizar) e Delete (Excluir), que representam as ações principais para manipulação de dados. As consultas no django são lazy, se você não usa-las ela não será executada. Mas cuidado que ao comentar uma consulta em um template html como: <!-- {{books.title}} --> O Django interpreta todas as expressões antes de entregar o HTML final, mesmo dentro de um comentário html. para evitar faça comentários assim: {{ }}{# {{ books.title }} #}.
pk é um argumento especial que simboliza a chave primária da tabela no banco de dados.
Principais Métodos CRUD no Django Models
| Operação | Método | Descrição | Exemplo de Uso |
|---|---|---|---|
| Create | Model.objects.create() |
Cria e salva um novo objeto no banco de dados. | Livro.objects.create(titulo="Novo Livro", autor="Autor") |
instance.save() |
Salva o objeto atual no banco de dados. Útil após criar ou modificar um objeto. | livro = Livro(titulo="Título") livro.save() |
|
| Read | Model.objects.all() |
Recupera todos os objetos do modelo. | Livro.objects.all() |
Model.objects.get() |
Recupera um único objeto que corresponde ao filtro. Lança um erro se nenhum ou mais de um objeto for encontrado. | Livro.objects.get(id=1) |
|
Model.objects.filter() |
Recupera objetos que atendem a critérios específicos. | Livro.objects.filter(autor="Autor") |
|
Model.objects.values() |
Retorna um dicionário com os valores das colunas do banco de dados. | Livro.objects.values('titulo', 'autor') |
|
Model.objects.values_list() |
Retorna uma lista de tuplas com os valores das colunas do banco de dados. | Livro.objects.values_list('titulo', 'autor') |
|
Model.objects.count() |
Retorna o número de objetos no conjunto de consulta. | Livro.objects.filter(autor="Autor").count() |
|
Model.objects.exists() |
Retorna True se o conjunto de consulta retornar algum resultado. |
Livro.objects.filter(autor="Autor").exists() |
|
| Update | instance.save() |
Atualiza os campos de um objeto existente e salva no banco de dados. | livro = Livro.objects.get(id=1) livro.titulo = "Título Atualizado" livro.save() |
Model.objects.filter().update() |
Atualiza múltiplos objetos que atendem a um filtro específico. | Livro.objects.filter(autor="Autor").update(autor="Novo Autor") |
|
| Delete | instance.delete() |
Remove o objeto atual do banco de dados. | livro = Livro.objects.get(id=1) livro.delete() |
Model.objects.filter().delete() |
Remove múltiplos objetos que atendem a um filtro. | Livro.objects.filter(autor="Autor").delete() |
commit de save() tem o valor True por padrão (ou seja, salva diretamente no banco de dados). Com commit=False, o método save cria uma instância do modelo, mas não a salva no banco de dados. Isso é útil quando você precisa adicionar ou alterar campos que não foram preenchidos pelo formulário ou deseja realizar validações ou ajustes antes de salvar no banco.Field lookups (pesquisas de campo - ex.: icontains) são a forma como você especifica o conteúdo de uma WHERE cláusula SQL. veja mais em fields lookups.Abrir Terminal Interativo Django
Abrir um terminal interativo do django em sua aplicação, para manipular o banco de dados e outros recursos pertencente a ela.
$ python manage.py shell
>from book_outlet.models import Book
# Inicializa um novo objeto
novo_autor = Autor(name="Diogo Marcelo")
# Salva no banco de dados
novo_autor.save()
# Nesse caso do autor, o django salva automaticamente o id desse objeto (novo_author)
novo_livro_acao = Livro(titulo="Harry potter", avaliacao=5, autor=novo_autor)
novo_livro_ação.save()
# Maneira rápida de criar mais um registro
Book.objects.create(title="Harry potter", rating=5)
livro_romance = Livro.objects.get(id=1)
# Obtendo o país "Alemanha"
alemanha = Pais.objects.get(nome="Alemanha")
# O Django insere um registro na tabela intermediária, associando o ID do livro ao ID do país.
livro_romance.paises_publicados.add(alemanha)
Sempre import o seu modelo, para usa-lo. Quando vc cria uma instancia de seu modelo, precisa inicializar com atributos equivalentes aos campos.
O método add() é utilizado para adicionar associações entre os objetos relacionados em uma relação Many-to-Many. Ele espera uma instância do modelo relacionado.
from biblioteca.models import Livro
from django.db.models import Q
# retornar uma lista contendo de todos os objetos, (Sobrescreva o método str em models.py)
livros = Livro.objects.all()
# retorna uma lista de objetos de todos os livros contendo apenas os campos especificados (mais leve)
livros_campos_especificos = Livro.objects.values('id','author', 'editora__title') # Só id e o autor, como editora é apenas o id fazendo __title trariamos o nome dela num join interno
# retorna a string sql executada pelo ORM django internamente
print(str(livros.query))
# É possível acessar uma variável do objeto usando o ponto, e definindo o índice. nesse caso é acessado uma tabela relacionada.
Livro.objects.all()[1].autor.name
# Retorna um único valor/registro, se houver valores repetidos dará um erro
Livro.objects.get("id=3")
# Retorna ordenado por titulo, caso queira decrescente adicione ou "-". ex.: ("-titulo")
livros_ordenados = Livro.objects.all().order_by("titulo")
# Retorna uma lista de objetos que atendam as parâmetros informados, (a virgula é um AND)
Livro.objects.filter(is_bestselling=True, avaliacao=5)
# Usa-se o __ para filtro com condições, lte (lower than equal - maior ou igual)
Livro.objects.filter(rating__lte=4)
Livro.objects.filter(title__icontains="Ha")
# Busca com relacionamento
# Nesse caso autor é a chave estrangeira (id) da tabela autor
livros_por_diogo = Livro.objects.filter(autor__name="Diogo")
# Procura os livros que contem ogo no nome do author
livros_por_diogo = Livro.objects.filter(autor__name__contains="ogo")
diogo = Autor.objects.get("id=1")
# livro_set é um atributo criado pelo django automaticamente(na tabela Autor), usando o nome da classe/tabela em minúsculo e _set em seguida. por causa da relação de chave estrangeira na criação do modelo
diogo.livro_set.all()
Livro.objects.filter(Q(avaliacao_lt=4) | Q(autor__icontains="H"), titulo__icontains"p"))
objects, campo estático herdado do objeto Models, que aponta para metodos de consultas, como all().contains seria para bancos que não são case sensitive. Se for use o icontains.Autor (nome) , tabela Livro (autor (id), titulo, avaliacao)OU/OR import a classe Q (obs, as condições Q (OU) devem vir antes das condições and)from django.db.models import F
# Comparar campos diretamente no banco de dados.
Produto.objects.filter(quantidade_em_estoque__lt=F('quantidade_minima')) # equivale à SELECT * FROM produto WHERE quantidade_em_estoque < quantidade_minima;
# Atualização baseada em campos
Produto.objects.filter(id=1).update(quantidade_em_estoque=F('quantidade_em_estoque') - 1)
# Expressões aritméticas
from django.db.models import F
Produto.objects.update(preco=F('preco') * 1.1) # Aumenta preço em 10%
F em Django serve para fazer referência aos valores de campos diretamente no banco de dados, permitindo comparar ou atualizar registros baseados em outros campos do mesmo modelo. Com F você altera ou compara diretamente no banco, de forma atômica, sem carregar o objeto.Usado para:-
Quando precisa atualizar campos baseados em seu valor atual (ex: decrementos, aumentos, cópias).
-
Quando precisa filtrar registros comparando dois campos do mesmo modelo.
-
Quando quer evitar múltiplas consultas (leitura + escrita), economizando recursos.
# Sem o select_related para cada livro, uma consulta extra ao banco para buscar o autor.
livros = Livro.objects.all()
for livro in livros:
print(livro.titulo, livro.autor.nome)
# Com o select_related, só 1 consulta no banco para trazer todos os livros e seus autores!
livros = Livro.objects.select_related('autor').all()
for livro in livros:
print(livro.titulo, livro.autor.nome)
select_related(chave_estrangeira) é metodo fornecido pelo django que usa uma única query fazendo um JOIN interno que traz os resultados. É indicado para relações ForeignKey ou OneToOneField. Sem select_related, o Django faz N+1 queries: 1 query para o model e 1 query extra para cada chave estrangeira relacionada. Usando essa função, você evita múltiplas consultas desnecessárias ao banco.
prefetch_related tambem pode ser usado mas em relacionamentos ManyToMany, reversos de FK → múltiplas queries otimizadas. Ele internamente não faz joins, mas faz algumas consultas de uma vez só.
from biblioteca.models import Livro
# Salva a consulta em uma variável mas não executa
bestsellers = livro.objects.filter(is_bestselling=True)
# Pesquisa dentro dos resultados da consulta anterior
melhores_livros = bestsellers.filter(rating_gt=4)
# Nesse momento a primeira consulta será feita no DB e salva em cache (até que haja alterações no DB, para refazer). Se fizer outra consulta da mesma o django já terá o resultado (em cache)
print(bestsellers)
# Essa segunda consulta é feita com base na primeira, e não em uma nova consulta no banco
print(melhores_livros)
# Esse tipo de consulta direta, fará o Django consultar no banco de dados toda vez que efetuar, esse comando, o que é ruim para desempenho
print(Livro.objects.filter(is_bestselling=True))
Desempenho de consultas (as consultas so são feitas posteriormente, o que é bom para o desempenho). Para melhorar performace salve em variaveis!
from biblioteca.models import Livro
harry_potter = Livro.objects.all()[0]
# Insere dados "Na memória" em um campo
harry_potter.autor = "J.k Rowling"
# Salva os dados no banco de dados
harry_potter.save()
Quando você chama um objeto que já existe no banco de dados o DJANGO atualiza esse objeto (ao invés de criar um novo).
from biblioteca.models import Livro
harry_potter = Livro.objects.all()[0]
# Deleta o objeto do banco de dados
>harry_potter.delete()
O método get_object_or_404 do Django é um atalho para buscar um objeto no banco de dados. Caso o objeto não seja encontrado, ele retorna automaticamente uma resposta HTTP 404. O método get_object_or_404 combina a funcionalidade de objects.get() e a verificação de existência. Para não precisar usar o try except.
from django.shortcuts import get_object_or_404
from .models import Livro
def detalhe_livro(request, id):
livro = get_object_or_404(Livro, id=id)
return render(request, 'livro_detalhes.html', {'livro': livro})
# Substitui o trecho abaixo
try:
livro = Livro.objects.get(id=1)
except Livro.DoesNotExist:
raise Http404("Livro não encontrado")
Existe também o get_list_or_404, que serve para uma lista de objetos (list de QuerySet), diferente do get_object_or_404, que espera um objeto, esse espera uma lista. Mas uma observação importante é que se existir retornará uma lista python e não mais uma queryset.
Métodos de agregação, anotação e ordenação
annotate cria um campo calculado temporário que existe apenas no queryset retornado — ele não altera o banco de dados.from django.db.models import Avg
# O método aggregate retorna um dicionário com os resultados
avg_avaliacao = livros.aggregate(Avg("avaliacao"))
print(avg_avaliacao) # Saída: {'avaliacao__avg': 4.2}
from biblioteca.models import Autor
# cria um campo temporário chamado media_avaliacao
autores = Autor.objects.annotate(media_avaliacao=Avg('livro__avaliacao'))
for autor in autores:
print(f'Autor: {autor.nome}, Média de Avaliação: {autor.media_avaliacao}')
O nome da chave segue o padrão nome_do_campo__nome_do_agregador. Nesse caso, avaliacao__avg representa a média do campo avaliacao. Aggregate coleta valores de toda a queryset e aplica os agregadores como Avg, Sum, Max, etc. Ele produz um único resultado, como a média de todos os ratings.
models.Manager e permite criar:- Métodos de consulta personalizados
-
Filtros recorrentes já encapsulados
-
Consultas complexas e reutilizáveis
from django.db import models
class LivroQuerySet(models.QuerySet):
def publicados(self):
return self.filter(publicado=True)
def por_autor(self, nome):
return self.filter(autor__nome__icontains=nome)
class LivroManager(models.Manager):
def get_queryset(self):
return LivroQuerySet(self.model, using=self._db)
def publicados(self):
return self.get_queryset().publicados()
def por_autor(self, nome):
return self.get_queryset().por_autor(nome)
class Livro(models.Model):
titulo = models.CharField(max_length=200)
publicado = models.BooleanField(default=False)
autor = models.ForeignKey('Autor', on_delete=models.CASCADE)
objects = LivroManager() # Usa o manager customizado
# Usando
livros_publicados = Livro.objects.publicados()
livros_por_diogo = Livro.objects.por_autor('Diogo')
O objects é o manager padrão que o Django cria automaticamente para cada model.
ContentType
Um framework interno do Django que permite criar relações genéricas e dinâmicas entre modelos, possibilitando que um único model (ex.: Comentário) se relacione com qualquer outro model do projeto sem depender diretamente deles. Permite o desaclopamente de aplicativos django, tornando-os mais independentes e reutilizaveis. Sem ContentType, cada app precisaria criar ForeignKeys específicas ou depender de imports diretos de outros apps — tornando-os fortemente acoplados.
| Sem ContentType | Com ContentType | |
|---|---|---|
| Acoplamento | Fortemente acoplado a modelos fixos | Nenhum acoplamento |
| Flexibilidade | Precisa alterar o model para cada novo tipo | Suporta qualquer model do projeto |
| Reutilização | Difícil reutilizar | Fácil usar o app em outros projetos |
| Código | Muitos campos específicos | Um campo genérico |
# SEM CONTENTTYPE
class Comment(models.Model):
text = models.TextField()
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True, blank=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True)
# COM CONTENTTYPE - COMPLETO
# models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Post(models.Model):
title = models.CharField(max_length=100)
def __str__(self):
return self.title
class Product(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Comment(models.Model):
text = models.TextField()
# O model que queremos relacionar
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
# O id do objeto, ou seja, a linha do banco de dados
object_id = models.PositiveIntegerField()
# Liga o tipo e a linha
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return f'Comment on {self.content_object} -> {self.text}'
# UTILIZAÇÃO SHELL
from app.models import Post, Product, Comment
from django.contrib.contenttypes.models import ContentType
# Criando um Post e um Product
post = Post.objects.create(title='Meu primeiro post')
product = Product.objects.create(name='Notebook XYZ')
# Comentando no Post
comment1 = Comment.objects.create(text='Comentário no post', content_object=post)
# Comentando no Product
comment2 = Comment.objects.create(text='Comentário no produto', content_object=product)
# Listando comentários
for comment in Comment.objects.all():
print(comment.text, '->', comment.content_object) # Saída: Comentário no post -> Meu primeiro post
# Comentário no produto -> Notebook XYZ
# OUTRO MODO DE USAR
# models
from django.contrib.contenttypes.fields import GenericRelation
from comment.models import Comment
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
# adicionando o campo genérico ao modelo
comments = GenericRelation(Comment, related_query_name='comments')
def __str__(self):
return self.title
-
Permite comentar em posts
-
Permite comentar em produtos