Использование Django ManyToManyField Through с примерами
В отношении «многие ко многим» несколько строк в таблице связаны с несколькими строками в другой таблице. Для установления отношения «многие ко многим» реляционные базы данных используют третью таблицу, называемую таблицей соединения, и создают два отношения «один ко многим» из исходных таблиц.
Обычно таблица соединений содержит значения идентификаторов исходных таблиц, чтобы строки в одной таблице могли соотноситься со строками в другой таблице.
Иногда вам может понадобиться добавить дополнительные поля в таблицу соединений. Например, каждый сотрудник может иметь несколько мест работы в течение своей карьеры. Чтобы отслеживать, когда сотрудник приступает к работе, вы можете добавить поля begin_date и end_date в таблицу соединений.
Чтобы сделать это в Python, используйте ManyToManyField в Django с аргументом through.
Например, ниже показано, как связать сотрудника с несколькими должностями с помощью назначений:
class Employee(models.Model): # ... class Job(models.Model): title = models.CharField(max_length=255) employees = models.ManyToManyField(Employee, through='Assignment') def __str__(self): return self.title class Assignment(models.Model): employee = models.ForeignKey(Employee, on_delete=models.CASCADE) position = models.ForeignKey(Job, on_delete=models.CASCADE) begin_date = models.DateField() end_date = models.DateField(default=date(9999, 12, 31))
Как это работает.
- Сначала определите модель Job, добавьте атрибут employees, который использует ManyToManyField, и передайте Assignment в качестве сквозного аргумента.
- Во-вторых, определите класс Assignment, который имеет два внешних ключа, один из которых ссылается на модель Employee, а другой — на модель Job. Также добавьте атрибуты begin_date и end_date в модель Assignment.
Запустите makemigrations, чтобы создать новые миграции:
python manage.py makemigrations
Выход:
Migrations for 'hr': hr\migrations\0005_assignment_job_assignment_job.py - Create model Assignment - Create model Job - Add field job to assignment
И выполните команду миграции, чтобы применить изменения к базе данных:
python manage.py migrate
Выход:
Operations to perform: Apply all migrations: admin, auth, contenttypes, hr, sessions Running migrations: Applying hr.0005_assignment_job_assignment_job... OK
За кулисами Django создает таблицы hr_job и hr_assignment в базе данных:
Таблица hr_assignment — это объединяемая таблица. Помимо полей employee_id и position_id, она содержит поля begin_date и end_date.
- Создание новых рабочих мест
- Создание экземпляров для промежуточных моделей
- Удаление экземпляров промежуточной модели
Создание новых рабочих мест
Сначала выполните команду shell_plus:
python manage.py shell_plus
Во-вторых, создайте три новые должности:
>>> j1 = Job(title='Software Engineer I') >>> j1.save() >>> j2 = Job(title='Software Engineer II') >>> j2.save() >>> j3 = Job(title='Software Engineer III') >>> j3.save() >>> Job.objects.all() <QuerySet [<Job: Software Engineer I>, <Job: Software Engineer II>, <Job: Software Engineer III>]>
Создание экземпляров для промежуточных моделей
Сначала найдите сотрудников с именами John Doe и Jane Doe:
>>> e1 = Employee.objects.filter(first_name='John',last_name='Doe').first() >>> e1 <Employee: John Doe> >>> e2 = Employee.objects.filter(first_name='Jane', last_name='Doe').first() >>> e2 <Employee: Jane Doe>
Во-вторых, создайте экземпляры промежуточной модели (Assignment):
>>> from datetime import date >>> a1 = Assignment(employee=e1,job=j1, begin_date=date(2019,1,1), end_date=date(2021,12,31)) >>> a1.save() >>> a2 = Assignment(employee=e1,job=j2, begin_date=date(2022,1,1)) >>> a2.save() >>> a3 = Assignment(employee=e2, job=j1, begin_date=date(2019, 3, 1)) >>> a3.save()
В-третьих, найдите сотрудников, занимающих должность Software Engineer I (p1):
>>> j1.employees.all() <QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
За кулисами Django выполняет следующий запрос:
SELECT "hr_employee"."id", "hr_employee"."first_name", "hr_employee"."last_name", "hr_employee"."contact_id", "hr_employee"."department_id" FROM "hr_employee" INNER JOIN "hr_assignment" ON("hr_employee"."id" = "hr_assignment"."employee_id") WHERE "hr_assignment"."job_id" = 1
Аналогичным образом вы можете найти всех сотрудников, занимающих должность Software Engineer II:
>>> j2.employees.all() <QuerySet [<Employee: John Doe>]>
Удаление экземпляров промежуточной модели
Сначала удалите Jane Doe (e2) из вакансии «Software Engineer II» с помощью метода remove():
>>> j2.employees.remove(e2)
Во-вторых, удалите всех сотрудников с должности Software Engineer I с помощью метода clear():
>>> j1.employees.clear()
На данный момент на вакансии j1 не должно быть сотрудников:
>>> j1.employees.all() <QuerySet []>