Отношение Django «многие ко многим» в Python на примерах
В этом руководстве вы узнаете, как использовать ManyToManyField для моделирования в Django отношения «Many-to-Many» в Python.
- Что такое связь «Many-to-Many» в Django?
- Создание данных
- Добавление компенсаций работникам
- Отмена компенсаций сотрудникам
Что такое связь «Many-to-Many» в Django?
В отношении «Many-to-Many» (многие ко многим) несколько строк в таблице связаны с несколькими строками в другой таблице.
Например, у сотрудника может быть несколько программ компенсаций, а одна программа компенсаций может принадлежать нескольким сотрудникам.
Таким образом, несколько строк в таблице сотрудников связаны с несколькими строками в таблице компенсаций. Следовательно, связь между сотрудниками и программами компенсаций является связью «многие ко многим».
Обычно реляционные базы данных не реализуют прямую связь «многие ко многим» между двумя таблицами. Вместо этого они используют третью таблицу, таблицу соединений, для установления двух связей «один ко многим» между двумя таблицами и таблицей соединений.
Следующая диаграмма иллюстрирует связи «многие ко многим» в базе данных между таблицами hr_employee и hr_compensation:
Таблица hr_employee_compensations является объединенной таблицей. Она имеет два внешних ключа employee_id и compartment_id. Внешний ключ employee_id ссылается на идентификатор таблицы hr_employee, а внешний ключ offset_id ссылается на идентификатор в таблице hr_compensation.
Обычно вам не нужен столбец id в таблице hr_employee_compensations в качестве первичного ключа, и вы используете как employee_id, так и compartment_id в качестве составного первичного ключа. Однако Django всегда создает столбец id в качестве первичного ключа для таблицы join.
Также Django создает уникальное ограничение, которое включает столбцы employee_id и compartment_id. Другими словами, в таблице hr_employee_compensations не будет дублирующихся пар значений employee_id и compression_id.
Чтобы создать отношение «многие ко многим» в Django, вы используете ManyToManyField. Например, ниже ManyToManyField используется для создания отношения «многие ко многим» между моделями Employee и Compensation:
# ... class Compensation(models.Model): name = models.CharField(max_length=255) 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 ) compensations = models.ManyToManyField(Compensation) def __str__(self): return f'{self.first_name} {self.last_name}'
Как это работает.
- Сначала определите новый класс модели компенсации, который расширяет класс models.Model.
- Во-вторых, добавьте поле компенсаций в класс Employee. Поле компенсаций использует ManyToManyField для установления связи «многие ко многим» между классами Employee и Compensation.
Чтобы распространить изменения моделей на базу данных, выполните команду makemigrations:
python manage.py makemigrations
Выведется что-то вроде этого:
Migrations for 'hr': hr\migrations\0004_compensation_employee_compensations.py - Create model Compensation - Add field compensations to employee
И выполните команду миграции:
python manage.py migrate
Выход:
Operations to perform: Apply all migrations: admin, auth, contenttypes, hr, sessions Running migrations: Applying hr.0004_compensation_employee_compensations... OK
Django создал две новые таблицы hr_compensation и объединенную таблицу hr_employee_compensations следующим образом:
Создание данных
Сначала выполните команду shell_plus:
python manage.py shell_plus
Во-вторых, создайте три программы компенсаций, включая акции, бонусы и распределение прибыли:
>>> c1 = Compensation(name='Stock') >>> c1.save() >>> c2 = Compensation(name='Bonuses') >>> c2.save() >>> c3 = Compensation(name='Profit Sharing') >>> c3.save() >>> Compensation.objects.all() <QuerySet [<Compensation: Stock>, <Compensation: Bonuses>, <Compensation: Profit Sharing>]>
В-третьих, найдите сотрудника с именем John и фамилией Doe:
>>> e = Employee.objects.filter(first_name='John',last_name='Doe').first() >>> e <Employee: John Doe>
Добавление компенсаций работникам
Сначала зарегистрируйте John Doe в программах компенсаций акциями (c1) и бонусами (c2), используя метод add() атрибута компенсаций и метод save() объекта Employee:
>>> e.compensations.add(c1) >>> e.compensations.add(c2) >>> e.save()
Во-вторых, получите доступ ко всем программам компенсаций John Doe, используя метод all() атрибута компенсаций:
>>> e.compensations.all() <QuerySet [<Compensation: Stock>, <Compensation: Bonuses>]>
Как ясно видно из результатов, у John Doe есть две программы компенсации.
В-третьих, зарегистрируйте John Doe в трех программах компенсаций, включая акции, бонусы и распределение прибыли:
>>> e = Employee.objects.filter(first_name='Jane',last_name='Doe').first() >>> e <Employee: Jane Doe> >>> e.compensations.add(c1) >>> e.compensations.add(c2) >>> e.compensations.add(c3) >>> e.save() >>> e.compensations.all() <QuerySet [<Compensation: Stock>, <Compensation: Bonuses>, <Compensation: Profit Sharing>]>
Внутренне Django вставил идентификаторы сотрудников и компенсаций в таблицу соединений:
id | employee_id | compensation_id ----+-------------+----------------- 1 | 5 | 1 2 | 5 | 2 3 | 6 | 1 4 | 6 | 2 5 | 6 | 3 (5 rows)
В-четвертых, найдите всех сотрудников, которые были зачислены в план компенсации акциями, используя атрибут employee_set объекта Compensation:
>>> c1 <Compensation: Stock> >>> c1.employee_set.all() <QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Как и ожидалось, вернулись двое сотрудников.
В-пятых, вы можете использовать атрибут employee_set, чтобы найти всех сотрудников, имеющих программу компенсации с разделением прибыли:
>>> c3 <Compensation: Profit Sharing> >>> c3.employee_set.all() <QuerySet [<Employee: Jane Doe>]>
Вернулся один сотрудник.
Django позволяет вам делать запросы по всем отношениям. Например, вы можете найти всех сотрудников, которые имеют компенсацию с идентификатором 1:
>>> Employee.objects.filter(compensations__id=1) <QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Или с названием «Profit Sharing»:
>>> Employee.objects.filter(compensations__name="Profit Sharing") <QuerySet [<Employee: Jane Doe>]>
Отмена компенсаций сотрудникам
Чтобы удалить программу компенсаций сотрудника, используйте метод remove() атрибута компенсаций объекта Employee. Например:
Сначала найдем сотрудника по имени John Doe:
>>> e = Employee.objects.filter(first_name='Jane',last_name='Doe').first() >>> e <Employee: Jane Doe>
Во-вторых, удалите компенсацию за распределение прибыли(c3) у John Doe и сохраните изменения в базе данных:
>>> e.compensations.remove(c3) >>> e.save()
В-третьих, получите все программы компенсаций John Doe:
>>> e.compensations.all() <QuerySet [<Compensation: Stock>, <Compensation: Bonuses>]>
Теперь у John Doe осталось две программы компенсации.