Relacionamento individual do Django

Resumo : neste tutorial, você aprenderá sobre o relacionamento um-para-um do Django e como ele funciona nos bastidores.

Este tutorial começa onde o  tutorial Django ORM  parou.

Introdução ao relacionamento Django One-To-One

O relacionamento um-para-um define um link entre duas tabelas, onde cada linha de uma tabela aparece uma vez em outra tabela.

Por exemplo, cada funcionário possui um contato e cada contato pertence a um funcionário. Portanto, o relacionamento entre funcionários e contatos é um relacionamento individual.

Para criar um relacionamento um-para-um, você usa a OneToOneFieldclasse:

OneToOneField(to, on_delete, parent_link=False, **options)Linguagem de código:  Python  ( python )

Nesta sintaxe:

  • toparâmetro define o nome do modelo.
  • on_deleteespecifica uma ação no contato quando o funcionário é excluído.

O exemplo a seguir usa uma OneToOneFieldclasse para definir um relacionamento um-para-um entre modelos Contacte Employeeno models.py:

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 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)

    def __str__(self):
        return f'{self.first_name} {self.last_name}'Linguagem de código:  Python  ( python )

A Employeeclasse possui o contactatributo que faz referência a uma instância da OneToOneFieldclasse.

No OneToOneField, especificamos o Contactmodelo e a on_deleteopção que define o comportamento quando um objeto funcionário é excluído.

A opção on_delete= models.CASCADEsignifica que se um Employeeobjeto for excluído, o Contactobjeto associado a ele Employeetambém será excluído automaticamente.

Observe que o Django cria uma restrição de chave estrangeira no banco de dados sem ON DELETE CASCADEopção. Em vez disso, o Django trata da exclusão manualmente na aplicação. Observe que esta implementação interna pode mudar no futuro.

Migrando modelos para o banco de dados

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

python manage.py makemigrationsLinguagem de código:  Python  ( python )
Migrations for 'hr':
  hr\migrations\0002_contact_employee_contact.py
    - Create model Contact
    - Add field contact to employeeLinguagem de código:  Python  ( python )

Segundo, aplique as migrações ao banco de dados usando o migratecomando:

python manage.py migrateLinguagem de código:  Python  ( python )
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
  Applying hr.0002_contact_employee_contact... OKLinguagem de código:  Python  ( python )

Nos bastidores, o Django cria duas tabelas hr_contacte hr_employeeno banco de dados:

A hr_employeetabela possui a contact_idcoluna que é uma chave estrangeira vinculada ao id ( chave primária ) da hr_contacttabela.

Associar um contato a um funcionário

Para interagir com os modelos Employeee Contact, você executa o shell_pluscomando com a --print-sqlopção:

python manage.py shell_plus --print-sqlLinguagem de código:  Python  ( python )

A --print-sqlopção gera o comando SQL que o Django executa.

Primeiro, crie um novo Employeeobjeto e salve-o no banco de dados:

>>> e = Employee(first_name='John',last_name='Doe')
>>> e.save()Linguagem de código:  Python  ( python )

Django executa o seguinte comando SQL:

INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id")
VALUES ('John', 'Doe', NULL) 
RETURNING "hr_employee"."id"Linguagem de código:  Python  ( python )

Segundo, crie e salve um novo contato no banco de dados:

>>> c = Contact(phone='40812345678', address='101 N 1st Street, San Jose, CA')
>>> c.save()Linguagem de código:  Python  ( python )

Django também executa o seguinte INSERTcomando:

INSERT INTO "hr_contact" ("phone", "address")
VALUES ('40812345678', '101 N 1st Street, San Jose, CA') 
RETURNING "hr_contact"."id"Linguagem de código:  Python  ( python )

Terceiro, associe um contato a um funcionário:

>>> e.contact = c
>>> e.save()Linguagem de código:  Python  ( python )

Django atualiza o valor da contact_idcoluna da hr_employeetabela para o valor da idcoluna da hr_contacttabela.

UPDATE "hr_employee"
   SET "first_name" = 'John',
       "last_name" = 'Doe',
       "contact_id" = 1
 WHERE "hr_employee"."id" = 3Linguagem de código:  Python  ( python )

Obtendo dados de um relacionamento um-para-um

Primeiro, pegue o funcionário com o nome John Doe:

>>> e = Employee.objects.filter(first_name='John',last_name='Doe').first()
<Employee: John Doe>Linguagem de código:  Python  ( python )

Django executa a SELECTinstrução que obtém a linha com first_name 'John'and last_name 'Doe'.

Podem hr_employeeter vários funcionários com o mesmo nome e sobrenome. Portanto, o filter()retorno a QuerySet.

Para obter a primeira linha do QuerySet, usamos o first()método. O first()método retorna uma única instância da Employeeclasse.

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id"
  FROM "hr_employee"
 WHERE ("hr_employee"."first_name" = 'John' AND "hr_employee"."last_name" = 'Doe')
 ORDER BY "hr_employee"."id" ASC
 LIMIT 1Linguagem de código:  Python  ( python )

Observe que a consulta não obtém os dados de contato da hr_contacttabela. Ele apenas obtém dados da hr_employeetabela.

Ao acessar o contactatributo do funcionário:

>>> e.contactLinguagem de código:  Python  ( python )

… Django executa a segunda SELECTinstrução para obter dados da hr_contacttabela:

SELECT "hr_contact"."id",
       "hr_contact"."phone",
       "hr_contact"."address"
  FROM "hr_contact"
 WHERE "hr_contact"."id" = 1
 LIMIT 21
<Contact: 40812345678>Linguagem de código:  Python  ( python )

O seguinte obtém o contato com id 1:

>>> c = Contact.objects.get(id=1)Linguagem de código:  Python  ( python )

Ao associar um contato a um funcionário, você pode acessar a employeepartir do contactobjeto:

>>> c.employee
<Employee: John Doe>Linguagem de código:  Python  ( python )

Django executa uma SELECTinstrução para obter dados da hr_employeetabela:

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id"
  FROM "hr_employee"
 WHERE "hr_employee"."contact_id" = 1
 LIMIT 21Linguagem de código:  Python  ( python )

Observe que a Contactclasse não possui o employeeatributo. No entanto, você pode acessar employeese o contato estiver associado a um.

Vamos criar outro contato que não esteja associado a nenhum funcionário:

>>> c = Contact(phone='4081111111',address='202 N 1st Street, San Jose, CA')
>>> c.save()Linguagem de código:  Python  ( python )

Django executará a seguinte INSERTinstrução:

INSERT INTO "hr_contact" ("phone", "address")
VALUES ('4081111111', '202 N 1st Street, San Jose, CA') 
RETURNING "hr_contact"."id"Linguagem de código:  Python  ( python )

Se você encontrar um contato que não esteja associado a nenhum funcionário e acessar o funcionário, você receberá uma RelatedObjectDoesNotExistexceção.

Selecionando objetos relacionados

Primeiro, crie um novo funcionário:

>>> e = Employee(first_name='Jane',last_name='Doe')
>>> e.save()
INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id")
VALUES ('Jane', 'Doe', NULL) RETURNING "hr_employee"."id"
Execution time: 0.003079s [Database: default]Linguagem de código:  Python  ( python )

Em segundo lugar, faça com que todos os funcionários:

>>> Employee.objects.all()Linguagem de código:  Python  ( python )

Django retorna dois funcionários:

<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>Linguagem de código:  Python  ( python )

Se você precisar exibir todos os funcionários e seus contatos na mesma página, terá o problema de consulta N+1:

  • Primeiro, você precisa de uma consulta para obter todos os funcionários (N funcionários).
  • Segundo, você precisa de N consultas para selecionar o contato relacionado de cada funcionário.

Para evitar isso, você pode consultar todos os funcionários e contatos usando uma única consulta usando o select_related()método:

>>> Employee.objects.select_related('contact').all()Linguagem de código:  Python  ( python )

Neste caso, o Django executa a seguinte LEFT JOINinstrução:

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id",
       "hr_contact"."id",
       "hr_contact"."phone",
       "hr_contact"."address"
  FROM "hr_employee"
  LEFT OUTER JOIN "hr_contact"
    ON ("hr_employee"."contact_id" = "hr_contact"."id")
 LIMIT 21Linguagem de código:  Python  ( python )

O Django usa LEFT JOINque retorna todos os funcionários da hr_employeetabela e contatos associados aos funcionários selecionados:

<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>Linguagem de código:  Python  ( python )

Baixe o código fonte do projeto aqui

Resumo

  • Use OneToOneFielda classe para estabelecer um relacionamento um-para-um.

Deixe um comentário

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