Relacionamento um-para-muitos do Django

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 ForeignKeypara criar um relacionamento um-para-muitos entre modelos Departmente :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 Departmentclasse do modelo.

Segundo, modifique a Employeeclasse 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 Departmentcomo o primeiro argumento e o on_deleteargumento da palavra-chave como models.CASCADE.

Indica on_delete=models.CASCADEque se um departamento for excluído, todos os funcionários associados ao departamento também serão excluídos.

Observe que você define o ForeignKeycampo nos muitos lados do relacionamento.

Terceiro, faça migrações usando o makemigrationscomando:

python manage.py makemigrationsLinguagem 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 Employeemodelo, definimos o departmentcampo como um campo não anulável. Portanto, o Django precisa de um valor padrão para atualizar o departmentcampo (ou department_idcoluna) das linhas existentes na tabela do banco de dados.

Mesmo que a hr_employeestabela 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:

>>> NoneLinguagem 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 employeeLinguagem de código:  texto simples  ( texto simples )

No banco de dados, o Django cria hr_departmente adiciona a department_idcoluna à hr_employeetabela. A department_idcoluna da hr_employeetabela está vinculada à idcoluna da hr_departmenttabela.

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 departmentcampo 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 makemigrationscomando:

python manage.py makemigrationsLinguagem 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 employeeLinguagem de código:  texto simples  ( texto simples )

Se a hr_employeetabela 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 migratecomando:

python manage.py migrateLinguagem 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... OKLinguagem 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_pluscomando:

python manage.py shell_plusLinguagem 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 ITdepartamento:

>>> 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 departmentobjeto do employeeobjeto:

>>> 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_setatributo 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_setpropriedade no Departmentmodelo. Internamente, o Django adicionou automaticamente a employee_setpropriedade ao Departmentmodelo quando definimos o relacionamento um-para-muitos usando o ForeignKey.

O all()método dos employee_setretornos a QuerySetque contém todos os funcionários que pertencem ao departamento.

Usando select_relacionado() para unir funcionário ao departamento

Saia do shell_pluse execute-o novamente. Desta vez adicionamos a --print-sqlopção de exibir o SQL gerado que o Django irá executar no banco de dados:

python manage.py shell_plus --print-sqlLinguagem 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 departmentatributo:

>>> 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_employeee 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 ForeignKeypara estabelecer um relacionamento um-para-muitos entre modelos no Django.
  • Defina o ForeignKeyno modelo do lado “muitos” do relacionamento.
  • Use o select_related()método para unir duas ou mais tabelas nos relacionamentos um-para-muitos.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *