Search code examples
pythondjangomany-to-manyfield-names

Update many-to-many field in django by fieldname


I am building an API with Django that allows an external app to send posts to my API an then it inserts the received data into my database.

Here are my simplified models:

class Region(models.Model):
    name = models.CharField(max_length=250)

class Event(models.Model):
    name = models.CharField(max_length=250)
    region = models.ManyToManyField(Region)

I receive from the api some new Event data like this:

data = {
    "name": "First Event",
    "region": "4"  # this is the ID of the region
}

I'm trying to create a new event like this:

Event.objects.create(**data)

But I get the following error:

Direct assignment to the forward side of a many-to-many set is prohibited. Use region.set() instead. 

The problem is that I can not use new_event.region.add(4) cause region is a string and, since this is an API, the field is always retrieved as a json key.

I read this question https://stackoverflow.com/questions/8360867/update-many-to-many-field-in-django-with-field-name-in-variable#= which is asking exactly the same, but the given answer is not working cause when I write:

new.__getattribute__(relation_name) = my_authors

as suggested there, i get the following error, which makes sense:

SyntaxError: can't assign to function call

So how can I add a manytoMany field value by the fieldName?


Solution

  • The given answer in the linked question does not work, because it's nine years old and back then assigning m2m field used to be like this: new_event.region = [4]

    However this has been deprecated long time ago, and now the syntax is:

    new_event.region.set(4)

    that's exactly why you get the following error:

    Direct assignment to the forward side of a many-to-many set is prohibited. Use region.set() instead.`

    Because what you are actually trying to do is:

    ```new_event.region = 4`

    Of course this won't work.

    However the rest of the code should be fine (i.e. the __getattribute__ part). So what you need to do is call the set/add method of what __getattribute__ returns. i.e.

    new_event.__getattribute__("region").add(4)

    but instead of using the __getattribute__ dunder method, you can simplify it to:

    getattr(new_event, "region").add(4)