直接向多对多集合的前向侧进行赋值是被禁止的。请使用emails_for_help.set()。

105

我刚接触Django,没有找到任何关于这个问题的参考。 当我在Django模型(models.py)中使用多对多字段时,会出现这个错误。 我猜测问题是在视图(views.py)中从表单(forms.py)中分配m2m字段导致的。

如何在视图中分配m2m字段? (Django版本2.0Python-3.5

models.py

class User(AbstractUser):
 username=models.CharField(max_length=20)
 email = models.EmailField(_('email address'), unique=True)


class Setupuser(models.Model):
 organization=models.CharField(max_length=200,blank=False,null=True)
 emails_for_help = models.ManyToManyField(User)

视图.py

class Set_user(FormView):
 template_name="pkm_templates/set_up_user.html"
 form_class = Set_User_Form
 success_url = '/thanks/'

 def form_valid(self, form):
    org = form.cleaned_data.get('organization')
    emails = form.cleaned_data.get("emails_for_help")
    instance = Setupuser(organization=org,emails_for_help=emails)
    instance.save()
    return redirect("/")

forms.py

class Set_User_Form(ModelForm):
  emails_for_help = forms.ModelMultipleChoiceField(
    queryset=User.objects.all(),
    widget=forms.CheckboxSelectMultiple
  )

  class Meta:
    model = Setupuser
    fields = ["organization","emails_for_help"]
5个回答

110
你需要获取 User 对象,然后将其添加到 emails_for_help 字段中。创建实例时无法将对象添加到 ManyToManyField 中。请查看 文档
class Set_user(FormView):
    template_name="pkm_templates/set_up_user.html"
    form_class = Set_User_Form
    success_url = '/thanks/'

    def form_valid(self, form):
        org = form.cleaned_data.get('organization')
        emails = form.cleaned_data.get("share_email_with")

        users = User.objects.filter(email__in=emails)
        instance = Setupuser.objects.create(organization=org)

        for user in users:
            instance.emails_for_help.add(user)

        return redirect("/")

另一种方法是使用.set()

class Set_user(FormView):
    template_name="pkm_templates/set_up_user.html"
    form_class = Set_User_Form
    success_url = '/thanks/'

    def form_valid(self, form):
        org = form.cleaned_data.get('organization')
        emails = form.cleaned_data.get("share_email_with")

        users = User.objects.filter(email__in=emails)
        instance = Setupuser.objects.create(organization=org)

        instance.emails_for_help.set(users)

        return redirect("/")

或者你可以简单地使用.add()来添加任意数量的对象。

class Set_user(FormView):
    template_name="pkm_templates/set_up_user.html"
    form_class = Set_User_Form
    success_url = '/thanks/'

    def form_valid(self, form):
        org = form.cleaned_data.get('organization')
        emails = form.cleaned_data.get("share_email_with")

        users = User.objects.filter(email__in=emails)
        instance = Setupuser.objects.create(organization=org)

        instance.emails_for_help.add(*users)

        return redirect("/")

我收到了异常消息:使用切片操作限制 QuerySet 的精确查找结果必须只有一个。 - conf
你在 emails 中有多个电子邮件地址吗? - MD. Khairul Basar

15

我尝试了以上所有解决方案,但在 Django 3.0 上不起作用。因此,我进行了自己的研究并得出了解决方案。 这个解决方案将会很简单。我的答案是通用的。 假设存在一个表单字段 specialFieldName,在 models.py 中定义为 ManyToManyField

为什么会出现这个错误?

用户通过“Django表单”输入的此字段选项被存储为Queryset。在这种情况下,我们无法在基于此Queryset创建对象时将此Queryset分配给ManyToManyField属性。这就是您收到上述错误的原因。

因此,我们首先根据从django-form获取的所有信息创建对象,除了specialFieldName,然后我们使用add()方法将此Queryset的所有元素添加到刚刚创建的对象中。

所以,我们需要迭代这个Queryset。

 returnedQueryset = form.cleaned_data.get('specialFieldName')
    dummyObject = ObjectModel.objects.create(..using all the info except specialFieldname..)
    for member in returnedQueryset:
        dummyObject.add(member)

不幸的是,这个循环不能迭代所有返回的查询集成员(请阅读此处)。因此,上述方法不起作用。我们需要更进一步地使用iterator()方法。

for member in returnedQueryset.iterator():
    dummyObject.add(member)

现在它运行良好。

(P.S. 这是我在Stackoverflow上的第一个答案。感谢我的所有导师 ;-))


异常值:'dummyObject'对象没有属性'add'. - Desert Camel
1
应该这样写:dummyObject.attribute.add(member) 例如: Setupuser.emails_for_help.add(member) - Desert Camel
是的@DesertCamel。这件事可能是我们现在使用它的一种更新方式。当我一年前写下这个答案时,这还不存在。 - Animesh Kumar
我有一个列表,所以出现了 **AttributeError: 'list' object has no attribute 'iterator'**。 - AnonymousUser
1
@AnonymousUser 然后尝试使用以下代码:for member in my_list: dummyObject.add(member) - Animesh Kumar

1
尝试使用此代码,因为我在使用ManyToManyField时遇到了同样的问题。
class Set_user(FormView):
 template_name="pkm_templates/set_up_user.html"
 form_class = Set_User_Form
 success_url = '/thanks/'

 def form_valid(self, form):
    org = form.cleaned_data.get('organization')
    instance = Setupuser(organization=org,emails_for_help=emails)
    instance.save()
    instance.email.add(request.user.email)
    instance.save()
    return redirect("/")

0

我可能没有添加太多内容,但在为没有表单的rest api编写单元测试时遇到了这个错误,因此我想提供一个示例来说明我是如何解决这个场景的。 我正在使用Django 4.1.7和Django Rest Framework 3.14.0。

这是一个Pokemon卡牌项目,因此有一个PokemonType模型(将类型表示为“火”,“水”,“草”等及其抗性和弱点):

class PokemonType(models.Model):
    name = models.CharField(max_length=100, blank=False)
    strong_vs = models.ManyToManyField('self', symmetrical=False, related_name="strong_versus")
    weak_vs = models.ManyToManyField('self', symmetrical=False, related_name="weak_versus")
    resistant_to = models.ManyToManyField('self', symmetrical=False, related_name="resists")
    vulnerable_to = models.ManyToManyField('self', symmetrical=False, related_name="vulnerable")

我想编写一个简单的测试以验证能否创建PokemonType,但这个测试会导致“禁止向多对多集合的前向侧直接赋值”的错误:
class PokemonTypeModelTests(TestCase):
    def test_create_pokemontype(self):
        pokemon_type = PokemonType.objects.create(
            name = 'type 1',
            strong_vs = [],
            weak_vs = [2],
            resistant_to = [3, 4],
            vulnerable_to = [2, 5]
        )
        pokemon_type_from_db = PokemonType.objects.get(id=pokemon_type.id)
        self.assertEqual(pokemon_type_from_db.name, 'type 1')

有人可能认为创建对象,然后像这样添加关系可能会有所帮助:

pokemon_type = PokemonType.objects.create(name='type 1')
pokemon_type.resistant_to.set([3, 4])

但是这会引发错误django.db.utils.IntegrityError: 表 'api_pokemontype_resistant_to' 中主键为 '1' 的行具有无效的外键:api_pokemontype_resistant_to.to_pokemontype_id 包含一个值 '3',该值在 api_pokemontype.id 中没有相应的值。

这是因为具有id 3和4的PokemonType对象尚不存在(请记住Django为测试创建了一个特殊的数据库)。多对多关系实际上是作为数据库中的单独表实现的,Django需要对象实际存在于数据库中,然后才能在它们之间添加关系。

这就是为什么在这种情况下的解决方案是首先创建所有对象,将它们添加到数据库中,然后使用set()来创建多对多关系:

def test_create_pokemontype(self):
    # this is the "main" object:
    pokemon_type = PokemonType.objects.create(name='type 1')
    
    # these are only created for the relationship fields:
    type2 = PokemonType.objects.create(name='type 2')
    type3 = PokemonType.objects.create(name='type 3')
    type4 = PokemonType.objects.create(name='type 4')
    type5 = PokemonType.objects.create(name='type 5')

    # now we can "set" these objects in each ManyToManyField:
    pokemon_type.strong_vs.set([])
    pokemon_type.weak_vs.set([type2])
    pokemon_type.resistant_to.set([type3, type4])
    pokemon_type.vulnerable_to.set([type2, type5])

    # and perform assertions with them:
    pokemon_type_from_db = PokemonType.objects.get(id=pokemon_type.id)
    self.assertEqual(pokemon_type_from_db.name, 'type 1')
    self.assertEqual(set(pokemon_type_from_db.strong_vs.all()), set())
    self.assertEqual(set(pokemon_type_from_db.weak_vs.all()), {type2})
    self.assertEqual(set(pokemon_type_from_db.resistant_to.all()), {type3, type4})
    self.assertEqual(set(pokemon_type_from_db.vulnerable_to.all()), {type2, type5})

0

我在将 Django 版本从旧版升级到 Django 2.2 LTS 时,遇到了相同的错误:Direct assignment to the forward side of a many-to-many set is prohibited. Use plan_options.set() instead

然而,当我使用 .set() 时,出现了 SyntaxError: can't assign to function call 的错误。对我来说,问题是通过添加 _set 来解决的:

existing_plan.plan_options_set = [x.pk for x in old_plan.plan_options.all()]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接