Django-使用通用UpdateView编辑多对多关系的两端

时间:2018-09-24 10:19:39

标签: django django-models django-forms django-related-manager

我有一个问题,是否可以使用通用的UpdateView类来编辑多对多关系的“双方”。 我在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)

这两个都是字典类型表,用于存储我的应用程序的配置数据集。为了允许编辑字典,我使用简单的UpdateViews:

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

这很好,我得到了不错的选择,一切都完美。但是,我希望可以从SomeCategory表的一侧编辑关系,因此我可以选择将哪些SomeClass元素链接到某个SomeCategory:

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

我尝试将related_name属性添加到SomeCategory模型中,但这没有用。

任何想法都可以在不使用自定义ModelForm的情况下完成吗?

密钥库版本:

Django==1.11.8
psycopg2==2.7.4

PS:这是我关于stackoverflow的第一个问题,所以请让我知道我的帖子是否缺少任何必填元素。

1 个答案:

答案 0 :(得分:1)

您的问题在models.py文件中。您有两个班级,但是其中只有一个班级提到另一班。您可能认为这应该足够了,因为您毕竟使用ManyToManyField并假定它将自动创建双向引导的每个连接...不幸的是,事实并非如此。在数据库级别上,确实确实创建了一个单独的中间表,并引用了两个原始表中的对象,但这并不意味着它们在Django Admin或类似程序中都将自动可见。

如果您尝试简单地在someclass = models.ManyToManyField(SomeClass)类中创建另一个SomeCategory,那将会失败。 Django会尝试通过通过创建另一个单独的中介表,以在两个主表之间建立连接。但是,由于中间表的名称取决于您定义ManyToManyField连接的位置,因此第二个表将使用不同的名称创建,并且所有内容在逻辑上都将折叠(两个表具有两个单独的 default >建立ManyToMany连接的方法没有任何意义)。

解决方案是向ManyToManyField添加一个SomeCategory连接,同时还要引用最初在SomeClass类中创建的中介/穿透表。

关于Django / python / naming / programming约定的一些注释:

  • 使用您要引用的表的名称作为包含有关该连接的信息的字段的名称。这意味着SomeClass的链接到SomeCategory的字段应命名为somecategory而不是allowed_categories
  • 如果连接是一对多-使用单数形式;如果连接是多对多-使用复数。意味着在这种情况下,我们应该使用复数并使用somecategories而不是somecategory
  • Django可以自动将名称复数,但是这样做很不好-它仅在末尾添加s字母。 Mouse-> MousesCategory-> Categorys。在这种情况下,您必须通过在特殊的verbose_name_plural类中定义Meta来提供帮助。
  • 只有在先前已经在代码中定义了该类的情况下,才可以使用对其他类的引用而没有其他'。在两个类相互引用的情况下,这仅是一种方法。解决方案是将引用类的名称放在引号内,例如'SomeCategory'而不是SomeCategory。这种引用称为lazy relationship的引用在解决两个应用程序之间的循环导入依赖关系时很有用。而且由于默认情况下最好保持样式相同,并避免不必要的“浪费精力”,“我将根据课程的组织顺序来决定是否使用引号;我将不得不重做该引号”每次我决定移动一些代码段时,“我建议您每次都只使用引号。就像在学习驾驶汽车时一样,最好学会始终使用转向信号灯,而不是先环顾四周并做出是否有人从该信息中受益的单独决定。
  • “字符串化”(延迟加载)模型/类/表的名称很容易-只需添加'即可。您可能会认为,对“通过”表引用进行字符串化将以相同的简单方式工作。而且你会错的-它会给你ValueError: Invalid model reference. String model references must be of the form 'app_label.ModelName'.错误。为了引用字符串化的“通过”表,您需要:(a)在周围添加'; (b)用下划线(.)替换所有点(_); (c)删除对through的引用!。因此SomeClass.somecategories.through变成'SomeClass_somecategories'

因此解决方案是这样:

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

在此之后,应该很明显地对UpdateView类进行什么样的最终更改。