Рубрики
Без рубрики

Правильный способ использовать многотоманное поле в Джанго

Когда вы разрабатываете базу данных для большого продукта, неизбежно прийти к точке, где у вас есть … Tagged с Python, Django.

Когда вы разрабатываете базу данных для большого продукта, неизбежно прийти к точке, когда у вас есть две модели, которые связаны друг с другом таким образом, что не решается, используя только иностранец.

Хорошим примером многих ко многим отношениям является связь между бутербродом и соусом. Мне нравится бутерброд с курицей терияки Но только если он содержит соус для барбекю, а также соус майонеза. Таким образом, один и тот же бутерброд может иметь несколько соусов. В то же время я хочу, чтобы соус майонеза также появился на бутерброде с индейкой, так что один и тот же соус можно использовать в разных видах бутербродов.

Это отличное место для использования The Mantomanyfield, предлагаемого Джанго вместо обычной иностранной кладки. К сожалению, то, как Джанго имеет дело с этим, немного неинтуитивно и может сбивать с толку, поэтому я подумал, что было бы лучше продемонстрировать, как многие со многими отношениями работают под капюшоном.

Отношения многих ко многим в базе данных

Чтобы поддерживать отношения между двумя таблицами в базе данных, единственный способ-иметь третью таблицу, в которой есть ссылки на обе эти таблицы. Эта таблица называется таблицей «через», и каждая запись в этой таблице будет подключать исходную таблицу (сэндвич в данном случае) и целевой таблицу (соус в данном случае).

Это именно то, что делает Джанго под капюшоном, когда вы используете многотоманное поле. Он создает модель, которая не видно для пользователя ORM, и всякий раз, когда нужно приносить все бутерброды, которые используют конкретный соус, данный только название соуса, приведенные выше 3 таблицы соединены.

Присоединение 3 таблиц может быть не очень эффективным, поэтому, если вы запросите эту информацию, используя идентификатор соуса вместо имени, Django внутренне соединяет только 2 таблицы (Sandwiches_sauces и бутерброды). Эти операции соединения невидимы для пользователя, но помогает узнать, что происходит в базе данных, чтобы запросы могли быть сделаны максимально эффективными.

Используя многопоманфилд

Теперь, когда мы знаем, что происходит внутри, давайте посмотрим, как Mantomanyfield помогает абстрагировать всю эту сложность и обеспечивает простой интерфейс для человека, пишущего код.

Давайте создадим новый проект Django и приложение внутри него под названием Sandwiches. В моделях.py определите эти две модели.

from django.db import models

class Sauce(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Sandwich(models.Model):
    name = models.CharField(max_length=100)
    sauces = models.ManyToManyField(Sauce)

    def __str__(self):
        return self.name

И это все. Запуск миграции базы данных на этих моделях создаст сэндвич -таблицу, таблицу соуса и таблицу, соединяющую их. Давайте теперь посмотрим, как мы можем получить доступ к этой информации, используя Django Orm.

Привлечение всех бутербродов и соусов, используя друг друга

Давайте создадим некоторые данные в моделях сэндвича и соуса и посмотрим, как мы можем их извлечь.

>>> chicken_teriyaki_sandwich = Sandwich.objects.create(name="Chicken Teriyaki Sandwich")
>>> bbq_sauce = Sauce.objects.create(name="Barbeque")
>>> mayo_sauce = Sauce.objects.create(name="Mayonnaise")
>>> 
>>> chicken_teriyaki_sandwich.sauces.add(bbq_sauce)
>>> chicken_teriyaki_sandwich.sauces.add(mayo_sauce)
>>> 
>>> chicken_teriyaki_sandwich.sauces.all()
, ]>
>>> 

Запуск sandwich.sauces.all () дает нам все соусы, нанесенные на этот бутерброд, но если мы хотим выполнить обратное действие, то есть получить все бутерброды, которые используют конкретный соус, это можно сделать, выполнив одну и ту же операцию на цели объект с помощью _набор.

>>> bbq_sauce = Sauce.objects.get(name="Barbeque sauce")
>>> 
>>> bbq_sauce.sandwich.all()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'Sauce' object has no attribute 'sandwich'
>>> 
>>> 
>>> bbq_sauce.sandwich_set.all()
]>
>>> 

Как вы можете видеть, попытка выполнить BBQ_SAUCE.Sandwich.all () бросил атрибут, но bbq_sauce.sandwich_set.all () работал. Это связано с тем, что Django внутренне ссылается на целевое многолетнее место в качестве набора. Такое поведение может быть переопределено, предоставив опцию insulity_name для целевого поля.

class Sandwich(models.Model):
    name = models.CharField(max_length=100)
    sauces = models.ManyToManyField(Sauce, related_name="sandwiches")

    def __str__(self):
        return self.name

Теперь мы можем выполнить предыдущий запрос, используя бутерброды вместо Sandwiches_Set.

>>> 
>>> 
>>> bbq_sauce = Sauce.objects.get(name="Barbeque sauce")
>>> bbq_sauce.sandwiches.all()
]>
>>> 
>>> 

Более простыми словами, связанная с ним имя – это фраза, которую вы хотели бы использовать вместо *_set.

Рекомендуемый способ выбрать соответствующее имя состоит в том, чтобы использовать форму множественного числа имени модели в нижнем регистре.

Привлечение определенных бутербродов и соусов, используя друг друга

В приведенных выше примерах мы получали все записи в таблице, используя. () Функция, но в большинстве практических случаев мы хотели бы запросить подмножество данных. Например, мы можем захотеть запросить все бутерброды, которые используют соус для барбекю. Это можно сделать с таким запросом, как это:

>>> 
>>> Sandwich.objects.filter(sauces__name="Barbeque sauce")
]>
>>> 
>>> 

Но, как я упоминал ранее, чтобы выполнить этот запрос, Джанго, внутренне, должен присоединиться к всем 3 таблицам. Мы можем сделать это более эффективным, запрашивая, используя идентификатор соуса вместо имени. Это позволит Джанго присоединиться только к сэндвич -столу и сквозному столу.

>>> 
>>> Sandwich.objects.filter(sauces__id=1)
]>
>>> 
>>> 
>>> 

Вы также можете запросить эту информацию в обратном направлении, то есть принести все соусы, которые помещаются на конкретный бутерброд.

>>> 
>>> Sauce.objects.filter(sandwich__name="Chicken Teriyaki")
, ]>
>>> 
>>> 
>>> 

Даже в этом случае я бы порекомендовал запросить использование идентификатора сэндвича, чтобы сделать этот запрос более эффективным.

Добавление предметов с обеих сторон отношений

До сих пор мы добавляли соусы к модели сэндвича, но мы также можем легко обойтись наоборот. Django Внутренне заботится о том, что необходимо создать записи таблицы баз данных, чтобы это произошло.

Единственная GotCha – если вы не планируете использовать связанный с собой aMe, вам придется добавить элемент в атрибут *_set.

>>> 
>>> 
>>> sandwich = Sandwich.objects.get(name="Turkey")
>>> 
>>> mayo_sauce = Sauce.objects.get(name="Mayonnaise sauce")
>>> 
>>> mayo_sauce.sandwich_set.add(sandwich)
>>> 
>>> 

Использование пользовательской модели «через»

Несмотря на то, что Джанго позаботится о создании модели через свою собственную модель и сохраняет это невидимым для пользователя, иногда становится необходимым использовать пользовательскую модель, чтобы добавить некоторые дополнительные поля в эту модель.

Например, рассмотрим отношения между учеником и учителем. Учитель может научить нескольких учеников, и ученика могут преподавать несколько учителей, тем самым квалифицируя это для многих ко многим отношениям.

Однако в этом случае просто иметь таблицу, которая соединяет эти две сущности, недостаточно, потому что нам потребуется дополнительная информация, такая как:

  • Дата, когда учитель начал преподавать ученика.
  • Предмет, который преподается учителем ученику.
  • Продолжительность курса.

Подводя итог, нам требуется таблица «курса», которая не только соединяет ученика и учителя, но и содержит эту дополнительную информацию.

Чтобы это произошло, необходимо переопределить по умолчанию таблицу, которую Джанго создает, и вместо этого использовать пользовательскую таблицу.

Дополнительный соус, пожалуйста!

ОК, разговоры о студентах и учителях, давайте вернемся к теме, которая, вероятно, заинтересовалась вам в этом блоге в первую очередь – еда!

Во всех вышеперечисленных примерах бутербродов и соусов единственная информация, которую мы имеем, – это то, что соусы идут на какие бутерброды, но что, если кто -то хочет поставить лишний соус такого же вида на бутерброд?

Вы можете попробовать добавить одну и ту же модель соуса в сэндвич -модель несколько раз, но Джанго просто игнорирует ее, так как функция добавления идентична. Вы можете добавить конкретный соус в бутерброд только один раз. Чтобы решить эту проблему, мы можем использовать пользовательскую модель.

Примечание : Имейте в виду, что если вы хотите пойти по этой дороге, вы должны сделать это с самого начала или быть в порядке с отбросом вашей базы данных и начинающим с нуля, потому что Django не позволяет вам создавать пользовательскую модель после ранее, используя по умолчанию через модель. Если вы попробуете это, вы можете увидеть странные ошибки, подобные этой:

raise ValueError(
ValueError: Cannot alter field sandwiches.Sandwich.sauces into sandwiches.Sandwich.sauces - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)

Создание пользовательской модели:

from django.db import models

class Sauce(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Sandwich(models.Model):
    name = models.CharField(max_length=100)
    sauces = models.ManyToManyField(Sauce, through='SauceQuantity')

    def __str__(self):
        return self.name

class SauceQuantity(models.Model):
    sauce = models.ForeignKey(Sauce, on_delete=models.CASCADE)
    sandwich = models.ForeignKey(Sandwich, on_delete=models.CASCADE)
    extra_sauce = models.BooleanField(default=False)

    def __str__(self):
        return "{}_{}".format(self.sandwich.__str__(), self.sauce.__str__())

С помощью пользовательской модели вы не сможете добавить соусы в бутерброд, как вы раньше. Вместо этого вам придется явно создать записи модели соуса, как показано ниже.

>>> from sandwiches.models import *
>>> 
>>> 
>>> chicken_teriyaki_sandwich = Sandwich.objects.create(name="Chicken Teriyaki with mayo and extra bbq sauce")
>>> 
>>> 
>>> bbq_sauce = Sauce.objects.create(name="Barbeque")
>>> 
>>> SauceQuantity.objects.create(sandwich=chicken_teriyaki_sandwich, sauce=bbq_sauce, extra_sauce=True)

>>> 
>>> SauceQuantity.objects.create(sandwich=chicken_teriyaki_sandwich, sauce=mayo_sauce, extra_sauce=False)

>>>

Вы все еще можете получить доступ к соусу из сэндвича и бутерброда из соуса, как вы раньше.

>>> 
>>> chicken_teriyaki_sandwich.sauces.all()
, ]>
>>> 
>>> bbq_sauce.sandwich_set.all()
]>
>>> 
>>> 

Чтобы узнать, что все соусы используются на сэндвиче, и в каких количествах мы можем итерации через соусы бутерброда и извлечь информацию из модели соуса для каждого из соусов, как показано ниже.

>>> 
>>> 
>>> for sauce in chicken_teriyaki_sandwich.sauces.all():
...  saucequantity = SauceQuantity.objects.get(sauce=sauce, sandwich=chicken_teriyaki_sandwich)
...  print("{}{}".format("Extra " if saucequantity.extra_sauce else "", sauce))
... 
Extra Barbeque
Mayonnaise
>>> 

Модель соуса также может быть расширена дальше, чтобы включить такие вещи, как сэндвич разрезан пополам, тип используемого хлеба и т. Д.

Заключительные заметки

Многотоманфилд может сбить с толку, но это очень удобно. Любой тип путаницы, который у вас может быть, может быть разрешен с помощью правильной документации. Вот некоторые, которые действительно помогли мне.

  • Официальная документация Django, которая охватывает отношения многих ко многим, которые вы можете найти здесь.
  • Отличный пример, когда использовать пользовательскую через модель, найденную здесь.

Первоначально опубликовано в моем блоге

Оригинал: “https://dev.to/sankalpjonna/the-right-way-to-use-a-manytomanyfield-in-django-4b2l”