Dica 19.4 - Modelagem - Abstract Inheritance - Herança Abstrata
Versões usadas no vídeo: Django 4.1.3, Python 3.10, PostgreSQL 14 (no Docker) e django-seed 0.3.1.
Código: https://github.com/rg3915/dicas-de-django (branch aula22)
Este vídeo é um corte da live Segredos do ORM do Django e é a quarta parte da série sobre modelagem. Agora não é mais um relacionamento entre tabelas, e sim herança entre models. Começamos pela herança abstrata (abstract inheritance): um model base, marcado como abstract = True, que não vira tabela; os campos dele são copiados para cada model que herda dele.
Pré-requisitos
O projeto das dicas anteriores (Dica 19.1 a Dica 19.3), com o app crm já criado e registrado em INSTALLED_APPS ('backend.crm'), mas ainda sem models, e o django-seed instalado.
Abstract Inheritance - Herança Abstrata

Temos dois tipos de pessoa no CRM: o cliente (Customer) e o vendedor (Seller). Os dois têm nome, sobrenome e e-mail. Além disso, o cliente tem LinkedIn e tags, e o vendedor tem um booleano que diz se ele é interno e um DecimalField com a comissão.
Em vez de repetir os três campos comuns nos dois models, criamos um model Person com eles e colocamos abstract = True na class Meta. Só isso. O Person não vira tabela no banco; os campos dele são copiados para as tabelas crm_customer e crm_seller:

# backend/crm/models.py
from django.db import models
class Person(models.Model):
first_name = models.CharField('nome', max_length=100)
last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True) # noqa E501
email = models.EmailField('e-mail', max_length=50, unique=True)
class Meta:
abstract = True
ordering = ('first_name',)
@property
def full_name(self):
return f'{self.first_name} {self.last_name or ""}'.strip()
def __str__(self):
return self.full_name
class Customer(Person):
linkedin = models.URLField(max_length=255, null=True, blank=True)
tags = models.TextField(null=True, blank=True)
class Meta:
verbose_name = 'cliente'
verbose_name_plural = 'clientes'
class Seller(Person):
internal = models.BooleanField('interno', default=True)
commission = models.DecimalField('comissão', max_digits=7, decimal_places=2, default=0) # noqa E501
class Meta:
verbose_name = 'vendedor'
verbose_name_plural = 'vendedores'
CustomereSellerherdam dePersonem vez demodels.Model, e acrescentam só os campos próprios.- Além dos campos, eles herdam a propriedade
full_namee o__str__. - Repare que o
emailcomunique=Truevale para cada tabela separadamente: o mesmo e-mail pode existir uma vez emcrm_customere outra emcrm_seller.
O admin:
# backend/crm/admin.py
from django.contrib import admin
from .models import Customer, Seller
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ('__str__', 'email', 'linkedin')
search_fields = ('first_name', 'last_name', 'email')
@admin.register(Seller)
class SellerAdmin(admin.ModelAdmin):
list_display = ('__str__', 'email', 'internal', 'commission')
search_fields = ('first_name', 'last_name', 'email')
list_filter = ('internal',)
python manage.py makemigrations
python manage.py migrate
Migrations for 'crm':
backend/crm/migrations/0001_initial.py
- Create model Customer
- Create model Seller
...
Applying crm.0001_initial... OK
Não existe Create model Person: o model abstrato não gera tabela.
TimeStampedModel
Um abstract muito comum é o TimeStampedModel, que acrescenta em qualquer model as datas de criação (created) e de modificação (modified), como as que já usamos em Book.

Como ele vai ser usado em vários apps, ele fica no app principal, o core, no início do core/models.py (antes do Profile da Dica 19.2):
# backend/core/models.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from backend.accounts.models import User
class TimeStampedModel(models.Model):
created = models.DateTimeField(
'criado em',
auto_now_add=True,
auto_now=False
)
modified = models.DateTimeField(
'modificado em',
auto_now_add=False,
auto_now=True
)
class Meta:
abstract = True
class Profile(models.Model):
...
auto_now_add=True: preenchecreatedcom a data e hora no momento da criação.auto_now=True: atualizamodifiedtoda vez que o objeto é salvo.
E podemos herdar, por exemplo, em crm nos models Customer e Seller. Um model pode herdar de mais de um model abstrato:
# backend/crm/models.py
from django.db import models
from backend.core.models import TimeStampedModel
class Person(models.Model):
...
class Customer(Person, TimeStampedModel):
...
class Seller(Person, TimeStampedModel):
...
python manage.py makemigrations
Como as tabelas já existem, o Django pergunta que valor colocar no created dos registros existentes (o auto_now_add não tem default):
It is impossible to add the field 'created' with 'auto_now_add=True' to customer without providing a default. This is because the database needs something to populate existing rows.
1) Provide a one-off default now which will be set on all existing rows
2) Quit and manually define a default value in models.py.
Select an option: 1
Please enter the default value as valid Python.
Accept the default 'timezone.now' by pressing 'Enter' or provide another value.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
[default: timezone.now] >>>
Escolha a opção 1 e aperte Enter para aceitar o timezone.now, ou seja, a data e hora de agora. A mesma pergunta aparece de novo para o seller; responda do mesmo jeito. (O modified não pergunta nada, porque com auto_now=True o Django já sabe preencher.)
Migrations for 'crm':
backend/crm/migrations/0002_customer_created_customer_modified_seller_created_and_more.py
- Add field created to customer
- Add field modified to customer
- Add field created to seller
- Add field modified to seller
python manage.py migrate
Django seed
Vamos gerar 5 clientes e 5 vendedores com o django-seed (instalado na Dica 19.1):
python manage.py seed crm --number=5
Seeding 5 Customers
Seeding 5 Sellers
Model Customer generated record with primary key 1
...
Model Seller generated record with primary key 5
No shell_plus (ou no Jupyter Notebook, reiniciando o kernel):
python manage.py shell_plus
Customer.objects.all()
# <QuerySet [<Customer: Justin Farley>, <Customer: Shari Morales>, <Customer: Timothy Prince>, <Customer: Jennifer Morales>, <Customer: John Hill>]>
sellers = Seller.objects.all()
for seller in sellers:
print(seller.first_name, seller.created)
Bryan 2008-09-13 20:06:49+00:00
Patrick 2005-07-15 22:12:00+00:00
Heather 2000-02-14 10:39:39+00:00
Kelly 1990-02-16 17:26:53+00:00
Stephanie 1998-01-03 06:16:29+00:00
Mas o django-seed altera o campo created também: ele preenche todos os campos com valores aleatórios, inclusive as datas, que não têm nada a ver com hoje. Então façamos os registros na mão, com uma pausa entre um e outro para as datas ficarem diferentes:
from time import sleep
names = ['Huguinho', 'Zezinho', 'Luizinho']
for name in names:
Seller.objects.create(
first_name=name,
last_name='Donald',
email=f'{name.lower()}@email.com'
)
sleep(1.2) # Só um testezinho de delay
sellers = Seller.objects.filter(last_name='Donald')
for seller in sellers:
print(seller.first_name, seller.created)
Huguinho 2022-12-19 16:09:37.591917+00:00
Zezinho 2022-12-19 16:09:38.804876+00:00
Luizinho 2022-12-19 16:09:40.016960+00:00
Agora o created tem a data e hora reais da criação de cada registro, com pouco mais de um segundo de diferença entre eles.
Uma pegadinha: o ordering do model abstrato
Repare que nem os clientes nem os vendedores saíram em ordem alfabética, embora o Person tenha ordering = ('first_name',). Isso acontece porque Customer e Seller declaram a própria class Meta, e ela substitui a Meta do Person. Só os campos, métodos e propriedades são herdados; para herdar também as opções da Meta, a Meta do filho precisa herdar da Meta do pai:
class Customer(Person, TimeStampedModel):
...
class Meta(Person.Meta):
verbose_name = 'cliente'
verbose_name_plural = 'clientes'
O Django tira o abstract = True da Meta herdada, então o Customer continua sendo um model normal, agora ordenado por first_name. O vídeo e o repositório mantêm a versão sem (Person.Meta).
Resumindo: use a herança abstrata quando vários models compartilham os mesmos campos, mas cada um deve ter a sua tabela. O model abstrato não existe no banco e não pode ser consultado (não existe Person.objects). Se você precisar de uma tabela para a classe base, o caminho é a herança multi-tabela, assunto da próxima dica.
Próxima dica: Dica 19.5 - Modelagem - Multi-table Inheritance.