Resumo : neste tutorial, você aprenderá sobre relacionamentos um-para-muitos no Django e como usar ForeignKey para modelá-los.
Introdução aos relacionamentos um-para-muitos do Django
Em um relacionamento um-para-muitos, uma linha de uma tabela está associada a uma ou mais linhas de outra tabela. Por exemplo, um departamento pode ter um ou mais funcionários e cada funcionário pertencer a um departamento.
O relacionamento entre departamentos e funcionários é um relacionamento um-para-muitos. Por outro lado, o relacionamento entre funcionários e departamentos é um relacionamento de muitos para um.
Para criar um relacionamento um-para-muitos no Django, você usa ForeignKey
. Por exemplo, o seguinte usa ForeignKey
para criar um relacionamento um-para-muitos entre modelos Department
e :Employee
from django.db import models
class Contact(models.Model):
phone = models.CharField(max_length=50, unique=True)
address = models.CharField(max_length=50)
def __str__(self):
return self.phone
class Department(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class Employee(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
contact = models.OneToOneField(
Contact,
on_delete=models.CASCADE,
null=True
)
department = models.ForeignKey(
Department,
on_delete=models.CASCADE
)
def __str__(self):
return f'{self.first_name} {self.last_name}'
Linguagem de código: Python ( python )
Como funciona.
Primeiro, defina a Department
classe do modelo.
Segundo, modifique a Employee
classe adicionando o relacionamento um-para-muitos usando ForeignKey
:
department = models.ForeignKey(
Department,
on_delete=models.CASCADE
)
Linguagem de código: Python ( python )
No ForeignKey
, passamos the Department
como o primeiro argumento e o on_delete
argumento da palavra-chave como models.CASCADE
.
Indica on_delete=models.CASCADE
que se um departamento for excluído, todos os funcionários associados ao departamento também serão excluídos.
Observe que você define o ForeignKey
campo nos muitos lados do relacionamento.
Terceiro, faça migrações usando o makemigrations
comando:
python manage.py makemigrations
Linguagem de código: texto simples ( texto simples )
Django emitirá a seguinte mensagem:
It is impossible to add a non-nullable field 'department' to employee without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option:
Linguagem de código: texto simples ( texto simples )
No Employee
modelo, definimos o department
campo como um campo não anulável. Portanto, o Django precisa de um valor padrão para atualizar o department
campo (ou department_id
coluna) das linhas existentes na tabela do banco de dados.
Mesmo que a hr_employees
tabela não tenha nenhuma linha, o Django também solicita que você forneça um valor padrão.
Conforme mostrado claramente na mensagem, o Django oferece duas opções. Você precisa inserir 1 ou 2 para selecionar a opção correspondente.
Na primeira opção, o Django solicita um valor padrão único. Se você inserir 1, o Django mostrará a linha de comando do Python:
>>>
Linguagem de código: Python ( python )
Neste caso, você pode usar qualquer valor válido em Python, por exemplo None
:
>>> None
Linguagem de código: Python ( python )
Depois de fornecer um valor padrão, o Django fará as migrações assim:
Migrations for 'hr':
hr\migrations\0002_department_employee_department.py
- Create model Department
- Add field department to employee
Linguagem de código: texto simples ( texto simples )
No banco de dados, o Django cria hr_department
e adiciona a department_id
coluna à hr_employee
tabela. A department_id
coluna da hr_employee
tabela está vinculada à id
coluna da hr_department
tabela.
Se você selecionar a segunda opção digitando o número 2, o Django permitirá que você defina manualmente o valor padrão para o campo departamento. Nesse caso, você pode adicionar um valor padrão ao department
campo assim models.py
:
department = models.ForeignKey(
Department,
on_delete=models.CASCADE,
default=None
)
Linguagem de código: Python ( python )
Depois disso, você pode fazer as migrações usando o makemigrations
comando:
python manage.py makemigrations
Linguagem de código: texto simples ( texto simples )
Ele mostrará a seguinte saída:
Migrations for 'hr':
hr\migrations\0002_department_employee_department.py
- Create model Department
- Add field department to employee
Linguagem de código: texto simples ( texto simples )
Se a hr_employee
tabela tiver alguma linha, você precisará remover todas elas antes de migrar as novas migrações:
python manage.py shell_plus
>>> Employee.objects.all().delete()
Linguagem de código: CSS ( css )
Então você pode fazer as alterações no banco de dados usando o migrate
comando:
python manage.py migrate
Linguagem de código: texto simples ( texto simples )
Saída:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
Applying hr.0002_department_employee_department... OK
Linguagem de código: texto simples ( texto simples )
Outra maneira de lidar com isso é redefinir as migrações que abordaremos no tutorial de redefinição de migrações.
Interagindo com modelos
Primeiro, execute o shell_plus
comando:
python manage.py shell_plus
Linguagem de código: texto simples ( texto simples )
Segundo, crie um novo departamento com o nome IT
:
>>> d = Department(name='IT',description='Information Technology')
>>> d.save()
Linguagem de código: Python ( python )
Terceiro, crie dois funcionários e atribua-os ao IT
departamento:
>>> e = Employee(first_name='John',last_name='Doe',department=d)
>>> e.save()
>>> e = Employee(first_name='Jane',last_name='Doe',department=d)
>>> e.save()
Linguagem de código: Python ( python )
Quarto, acesse o department
objeto do employee
objeto:
>>> e.department
<Department: IT>
>>> e.department.description
'Information Technology'
Linguagem de código: Python ( python )
Quinto, faça com que todos os funcionários de um departamento usem o employee_set
atributo assim:
>>> d.employee_set.all()
<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Linguagem de código: Python ( python )
Observe que não definimos a employee_set
propriedade no Department
modelo. Internamente, o Django adicionou automaticamente a employee_set
propriedade ao Department
modelo quando definimos o relacionamento um-para-muitos usando o ForeignKey
.
O all()
método dos employee_set
retornos a QuerySet
que contém todos os funcionários que pertencem ao departamento.
Usando select_relacionado() para unir funcionário ao departamento
Saia do shell_plus
e execute-o novamente. Desta vez adicionamos a --print-sql
opção de exibir o SQL gerado que o Django irá executar no banco de dados:
python manage.py shell_plus --print-sql
Linguagem de código: texto simples ( texto simples )
O seguinte retorna o primeiro funcionário:
>>> e = Employee.objects.first()
SELECT "hr_employee"."id",
"hr_employee"."first_name",
"hr_employee"."last_name",
"hr_employee"."contact_id",
"hr_employee"."department_id"
FROM "hr_employee"
ORDER BY "hr_employee"."id" ASC
LIMIT 1
Execution time: 0.003000s [Database: default]
Linguagem de código: SQL (linguagem de consulta estruturada) ( sql )
Para acessar o departamento do primeiro funcionário, você utiliza o department
atributo:
>>> e.department
SELECT "hr_department"."id",
"hr_department"."name",
"hr_department"."description"
FROM "hr_department"
WHERE "hr_department"."id" = 1
LIMIT 21
Execution time: 0.013211s [Database: default]
<Department: IT>
Linguagem de código: SQL (linguagem de consulta estruturada) ( sql )
Neste caso, o Django executa duas consultas. A primeira consulta seleciona o primeiro funcionário e a segunda consulta seleciona o departamento do funcionário selecionado.
Se você selecionar N funcionários para exibi-los em uma página da web, será necessário executar a consulta N + 1 para obter os funcionários e seus departamentos. A primeira consulta (1) seleciona os N funcionários e as N consultas selecionam N departamentos para cada funcionário. Esse problema é conhecido como problema de consulta N + 1.
Para corrigir o problema da consulta N + 1, você pode usar o select_related()
método para selecionar funcionários e departamentos usando uma única consulta. Por exemplo:
>>> Employee.objects.select_related('department').all()
SELECT "hr_employee"."id",
"hr_employee"."first_name",
"hr_employee"."last_name",
"hr_employee"."contact_id",
"hr_employee"."department_id",
"hr_department"."id",
"hr_department"."name",
"hr_department"."description"
FROM "hr_employee"
INNER JOIN "hr_department"
ON ("hr_employee"."department_id" = "hr_department"."id")
LIMIT 21
Execution time: 0.012124s [Database: default]
<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Linguagem de código: SQL (linguagem de consulta estruturada) ( sql )
Neste exemplo, o Django executa apenas uma consulta que une as tabelas hr_employee
e hr_department
.
Baixe o código-fonte do relacionamento um-para-muitos do Django
Resumo
- Em um relacionamento um-para-muitos, uma linha de uma tabela está associada a uma ou mais linhas de outra tabela.
- Use
ForeignKey
para estabelecer um relacionamento um-para-muitos entre modelos no Django. - Defina o
ForeignKey
no modelo do lado “muitos” do relacionamento. - Use o
select_related()
método para unir duas ou mais tabelas nos relacionamentos um-para-muitos.