djangodjango-modelsdjango-formsdjango-related-manager

Django - edit both sides of a many-to-many relation with generic UpdateView


I have a question whether or not it is possible to use the generic UpdateView class to edit "both sides" of a many-to-many relationship. I have the following classes defined in models.py:

class SomeCategory(models.Model):
    code = models.CharField(max_length=5)
    name = models.CharField(max_length=40)    


class SomeClass(models.Model):
    code = models.CharField(max_length=3, unique=True)
    name = models.CharField(max_length=30, unique=False)
    age = models.IntegerField(null=False)
    allowed_categories = models.ManyToManyField(SomeCategory)

These are both dictionary type tables that store sets of configuration data for my application. To allow editing the dictionaries I use simple UpdateViews:

class SomeClassUpdate(UpdateView):
    model = SomeClass
    template_name = 'admin/edit_class.html'
    fields = ['code', 'name', 'age', 'allowed_categories']
    ordering = ['code']

This works fine, I get a nice multi-select and everything is perfect. However, I would like to have the possibility to edit the relationship from the side of the SomeCategory table, so I can choose which SomeClass elements are linked to a certain SomeCategory:

class SomeCategoryUpdate(UpdateView):
    model = SomeCategory
    template_name = 'admin/edit_category.html'
    fields = ['code', 'name',  ??????? ]
    ordering = ['code']

I have tried adding the related_name attribute to the SomeCategory model, but that did not work.

Any ideas if this can be done without using a custom ModelForm?

Key library versions:

Django==1.11.8
psycopg2==2.7.4

PS: this is my very first question asked on stackoverflow, so please let me know if my post is missing any mandatory elements.


Solution

  • Your issue is in the models.py file. You have two classes, but only one of them mentions the other one. You would think that this should be enough since you are using ManyToManyField after all and assume that it would automatically create every connection leading both ways... Unfortunately this is not true. On the database level it does indeed create a separate intermediary table with references to objects in both original tables, but that doesn't mean that both of them will be automatically visible in Django Admin or similar.

    If you would attempt to simply create another someclass = models.ManyToManyField(SomeClass) in the SomeCategory class that would fail. Django would try to create another separate intermediary table through which the connection between two main tables is established. But because the name of the intermediary table depends on where you define the ManyToManyField connection, the second table would be created with a different name and everything would just logically collapse (two tables having two separate default ways to have a ManyToMany connection makes no sense).

    The solution is to add a ManyToManyField connection to SomeCategory while also referencing that intermediary/through table that was originally created in the SomeClass class.

    A couple of notes about Django/python/naming/programming conventions:

    Therefore the solution is this:

    class SomeCategory(models.Model):
        code = models.CharField(max_length=5)
        name = models.CharField(max_length=40)
        someclasses = models.ManyToManyField('SomeClass', through='SomeClass_somecategories', blank=True)
    
        class Meta:
            verbose_name_plural = 'SomeCategories'
    
    
    class SomeClass(models.Model):
        code = models.CharField(max_length=3, unique=True)
        name = models.CharField(max_length=30, unique=False)
        age = models.IntegerField(null=False)
        somecategories = models.ManyToManyField('SomeCategory')
    

    After this it should be obvious what kind of final changes to make to your UpdateView classes.