Как я могу получить доступ к ManyToManyField в методе сохранения модели? - PullRequest
4 голосов
/ 06 октября 2019

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

Если у меня есть model с field это ManyToManyField;Я знаю, что могу получить выбранные объекты до сохранения , но это не поможет, если выбранные объекты изменились.

models.py:

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


class Invoice(models.Model):
    additional_options = models.ManyToManyField(AddOn)

    def save(self, *args, **kwargs):
        super().save()
        # this prints the objects that were selected prior to the save.
        # this isn't helpful if the selected objects have changed.
        print(self.additional_options.all())

1 Ответ

0 голосов
/ 06 октября 2019

Исходя из того, как вы сформулировали свой вопрос, я думаю, что вы не понимаете, что на самом деле происходит за кулисами с ManyToManyField (я буду обозначать это как M2M с этого момента), поэтому я попытаюсьобъясните это следующими моделями:

class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

Когда мы объявляем поле M2M, за кулисами Django создает сквозную модель , которая в данном случае выглядит следующим образом:

class ThroughModel(models.Model):
    topping = models.ForeignKey(Topping)
    pizza models.ForeignKey(Pizza)

Используется для связывания моделей и не сохраняется в базе данных, если вы не используете ключевое слово through в поле M2M для создания собственного.

Прежде чем вы действительно сможете связать модели Pizza и Topping в M2M, у вас должны уже сохранены экземпляры:

>>> t1 = Topping(name='pepperoni')
>>> t2 = Topping(name='sausage')
>>> t3 = Topping(name='ham')
>>> Topping.objects.bulk_create([
>>> t1, t2, t3
>>> ])
>>>
>>> pizza = Pizza(name='meat lovers')

Теперь, как мы можем добавить данные вполе M2M? Как это:

>>> pizza.toppings.add(t1, t2, t3)

Но, как есть, это вызовет:

ValueError: Экземпляр 'Pizza' должен иметь значение первичного ключа перед отношением многие ко многимможно использовать.

Поскольку экземпляр pizza не был сохранен .

>>> pizza.save()
>>> pizza.toppings.add(t1, t2, t3)
>>>
>>> print('no error thrown now!')

Вы также можете добавить toppings, передавпервичный ключ вместо экземпляра:

>>> pk = Toppings.objects.first().pk
>>> pk
1
>>> pizza = Pizza.objects.create(name='pepperoni pizza')
>>> pizza.toppings.add(pk)

Наконец, чтобы получить toppings из данного экземпляра пиццы:

>>> pizza.toppings.all()
<QuerySet [<Toppings: Toppings object (1)>, <Toppings: Toppings object (2)>, <Toppings: Toppings object (3)>]>

Важно отметить, что выборка значений из M2M требуют 2 запросов к базе данных, и на самом деле нет никакого способа обойти это.

Наконец, чтобы действительно ответить на ваш вопрос, вы можете сделать что-то подобное для доступа к M2Mполе внутри save:

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def save(self, *args): # args will be M2M data
        super().save() # now that the instance is saved, we can access toppings
        if args:
            self.toppings.add(*args)

        print(self.toppings.all())

Пример использования:

>>> pizza = Pizza(name='sausage and ham')
>>> pizza.save(2, 3)
<QuerySet [<Toppings: Toppings object (2)>, <Toppings: Toppings object (3)>]>
# this is from the print inside of save

Мои примеры моделей взяты из раздела prefetch_related документации, но ярекомендую прочитать этот раздел, если вы хотите узнать больше о ManyToManyFields.

...