wagtail / django-modelcluster

Django extension to allow working with 'clusters' of models as a single unit, independently of the database
BSD 3-Clause "New" or "Revised" License
483 stars 66 forks source link

Does it support save ManyToMany with related name? #191

Closed gotounix closed 2 weeks ago

gotounix commented 2 weeks ago
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalManyToManyField

class Movie(ClusterableModel):
    title = models.CharField(max_length=255)
    actors = ParentalManyToManyField('Actor', related_name='movies')

class Actor(models.Model):
    name = models.CharField(max_length=255)

>>> actor= Actor.objects.create(name='Harrison Ford')
>>> move1= Movie(title='Star Wars')
>>> move2= Movie(title='Star Wars II')

>>> actor.movies= [move1, move2]
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "E:\venv\lib\site-packages\django\db\models\fields\related_descriptors.py", line 659, in __set__
    raise TypeError(
TypeError: Direct assignment to the reverse side of a many-to-many set is prohibited. Use movies.set() instead.
gasman commented 2 weeks ago

No, django-modelcluster does not support assigning to the "reverse" side of the relation like this.

django-modelcluster is built on the idea that some kinds of information are implemented as a relation, but we want them to behave as if they were stored locally to the primary object, just like plain data fields - including being able to write to them as in-memory data separately from writing to the database. Effectively, the related records "belong to" that object as children, which is where the "parental" name comes from.

A many-to-many relation can be queried in both directions, but it only makes sense for one of them to have this "belongs-to" behaviour - the cast list of a movie can either belong to the movie or the actor, but not both, and for most purposes it makes more sense for it to belong to the movie, so that's the side that the ParentalManyToManyField is defined on. If we didn't have this restriction, you could end up doing things like:

star_wars.actors = [harrison_ford]
harrison_ford.movies = [blade_runner]

and then the in-memory objects would have conflicting information.