问题
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.
回答1:
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:
- Use the name of the table you are referencing to, as the name of the field that is containing the info about that connection. Meaning that
SomeClass
's field with a link toSomeCategory
should be namedsomecategory
instead ofallowed_categories
. - If the connection is one-to-many - use singular form; if the connection is many-to-many - use plural. Meaning that in this case we should use plural and use
somecategories
instead ofsomecategory
. - Django can automatically pluralize names, but it does it badly - it simply adds
s
letter to the end.Mouse
->Mouses
,Category
->Categorys
. In those kind of cases you have to help it by defining theverbose_name_plural
in the specialMeta
class. - Using references to other classes without extra
'
s works only if the the class was already defined previously in the code. In the case of two classes referring to each other that is true only one way. The solution is to put the name of the referred class in the quotation marks like'SomeCategory'
instead ofSomeCategory
. This sort of reference, called alazy relationship
, can be useful when resolving circular import dependencies between two applications. And since by default it's better to keep the style the same and to avoid unnecessary brain energy wasting of "I will decide whether or not to use quotation marks depending on the order the classes have been organized; I will have to redo this quotation marks thingie every time I decide to move some code pieces around" I recommend that you simply use quotation marks every time. Just like when learning to drive a car - it's better to learn to always use turn signals instead of first looking around and making a separate decision of whether someone would benefit from that information. - "Stringifying" (lazy loading) model/class/table name is easy - just add
'
s around. You would think that stringifying the "through" table reference would work the same easy way. And you would be wrong - it will give you theValueError: Invalid model reference. String model references must be of the form 'app_label.ModelName'.
error. In order to reference the stringified "through" table you need to: (a) add'
s around; (b) replace all dots (.
) with underscores (_
); (c) delete the reference tothrough
!.. SoSomeClass.somecategories.through
becomes'SomeClass_somecategories'
.
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.
来源:https://stackoverflow.com/questions/52477191/django-edit-both-sides-of-a-many-to-many-relation-with-generic-updateview