Direct assignment to the forward side of a many-to-many set is prohibited. Use emails_for_help.set() instead
Asked Answered
W

5

107

I am new to Django and didn't find any reference regarding this issue. I am getting this error when i use many to many field in Django model (models.py). I guess the issue is assigning m2m field in view(views.py) from form(forms.py).

How to assign m2m field in view? (Django version 2.0, python - 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)

views.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"]
Washburn answered 25/4, 2018 at 6:15 Comment(0)
A
111

You need to get the User object and then add it to emails_for_help field. You can't add an object to ManyToManyField when creating an instance. Have a look at the doc.

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("/")

Another way of doing this is to use .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("/")

Or you can simply use .add() to add arbitrary number of objects.

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("/")
Animadversion answered 25/4, 2018 at 6:17 Comment(2)
I got exception :-The QuerySet value for an exact lookup must be limited to one result using slicing.Washburn
Do you have multiple email addresses in emails ?Animadversion
I
16

I tried all the above solutions and it doesn't work on Django 3.0 . So, I did my own research and came up with the solution. The solution is gonna be pretty simple. My answer is in general. Let us say there exists a form-field specialFieldName which is defined as a ManyToManyField in models.py .

Why did that error occur?

The options of this field which the user entered through the 'django form' are stored as a Queryset. In this scenario, we simply cannot assign this queryset to a ManyToManyField attribute during the creation of the object based on this queryset. This is the reason you are getting the above error.

So, we first create the object based on all the information we have got from the django-form, except the specialFieldName and then we use add() method to add all the elements of this queryset to the object we just created.

So, we need to iterate over this queryset.

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

Unfortunately, the loop doesn't iterate over all the members of the returnedQueryset (Read Why?). Thus, the above thing doesn't work. We gotta get a bit more advanced and use iterator() method instead.

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

Now it works fine.

(P.S. This was my first answer on Stackoverflow. Gratitude for all my mentors ;-))

Irita answered 17/5, 2020 at 14:38 Comment(5)
Exception Value: 'dummyObject' object has no attribute 'add'Tula
So it sould be : dummyObject.attribute.add(member) For the Example: Setupuser.emails_for_help.add(member)Tula
Yes @DesertCamel. This thing might be an update in the way we use it now. When I wrote this answer 1 year ago, this wasn't there.Irita
I have a list so I got AttributeError: 'list' object has no attribute 'iterator'.Gallop
@Gallop then try with just ``` for member in my_list: dummyObject.add(member) ```Irita
D
1

Try this code because i had the same problem while using 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("/")
Dresden answered 28/7, 2021 at 10:19 Comment(0)
B
0

I was getting the same error Direct assignment to the forward side of a many-to-many set is prohibited. Use plan_options.set() instead, when upgrading from an older Django version to Django 2.2 LTS.

However when I used .set() I got SyntaxError: can't assign to function call. For me the issue was fixed by adding _set:

existing_plan.plan_options_set = [x.pk for x in old_plan.plan_options.all()]
Beep answered 19/3, 2021 at 13:29 Comment(0)
B
0

I might not be adding much, but I got this error while working on unit tests for a rest api with no forms at all, so I wanted to provide an example of how I fixed it in this scenario. I'm working with Django 4.1.7 and Django Rest Framework 3.14.0.

This is a Pokemon cards project, so there's a PokemonType model (that represents types as "fire", "water", "grass", etc. and their resistances and weaknesses):

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")

I wanted to write a simple test that verifies a PokemonType can be created, but this test causes the Direct assignment to the forward side of a many-to-many set is prohibited error:

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')

One could think that creating the object and then adding the relationships like this might help:

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

but this raises error django.db.utils.IntegrityError: The row in table 'api_pokemontype_resistant_to' with primary key '1' has an invalid foreign key: api_pokemontype_resistant_to.to_pokemontype_id contains a value '3' that does not have a corresponding value in api_pokemontype.id.

This happens because the PokemonType objects with ids 3 and 4 don't exist yet (keep in mind that Django creates a special database for the purpose of testing). The many-to-many relationship is actually implemented as a separate table in the database, and Django needs the objects to actually exist in the database before being able to add relationships between them.

That's why a solution in this case was to first create all the objects, add them to the database and then use set() to create the many-to-many relationships:

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})
Blurt answered 24/3, 2023 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.