如何在模型的保存方法中访问 ManyToManyField?

Joh*_*rry 7 django django-models

我知道我不可能是第一个问这个问题的人,但我找不到答案,所以请不要吃我。

如果我有 amodel和 afield那就是 a 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())
Run Code Online (Sandbox Code Playgroud)

Rei*_*ica 2

根据您提出问题的方式,我认为您对幕后实际发生的情况感到困惑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)
Run Code Online (Sandbox Code Playgroud)

当我们声明一个M2M字段时,Django 在幕后创建一个直通模型,在本例中如下所示:

class ThroughModel(models.Model):
    topping = models.ForeignKey(Topping)
    pizza models.ForeignKey(Pizza)
Run Code Online (Sandbox Code Playgroud)

这用于将模型链接在一起,并且不会存储在数据库中,除非您throughM2M字段中使用关键字来创建自己的模型。

在实际链接中的Pizza和模型之前,您 必须已经保存了实例:ToppingM2M

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

现在,我们如何向M2M字段添加数据?像这样:

>>> pizza.toppings.add(t1, t2, t3)
Run Code Online (Sandbox Code Playgroud)

但事实上,这会引发:

ValueError:“Pizza”实例需要有主键值才能使用多对多关系。

因为pizza实例没有保存

>>> pizza.save()
>>> pizza.toppings.add(t1, t2, t3)
>>>
>>> print('no error thrown now!')
Run Code Online (Sandbox Code Playgroud)

toppings您还可以通过传入主键而不是实例来添加:

>>> pk = Toppings.objects.first().pk
>>> pk
1
>>> pizza = Pizza.objects.create(name='pepperoni pizza')
>>> pizza.toppings.add(pk)
Run Code Online (Sandbox Code Playgroud)

toppings最后,从给定的披萨实例中获取:

>>> pizza.toppings.all()
<QuerySet [<Toppings: Toppings object (1)>, <Toppings: Toppings object (2)>, <Toppings: Toppings object (3)>]>
Run Code Online (Sandbox Code Playgroud)

需要注意的是,从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())
Run Code Online (Sandbox Code Playgroud)

用法示例:

>>> 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
Run Code Online (Sandbox Code Playgroud)

我的示例模型来自prefetch_related文档部分,但如果您想了解有关ManyToManyFields.