Django - 使用通用 UpdateView 编辑多对多关系的双方

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

我有一个问题,是否可以使用通用 UpdateView class 来编辑多对多关系的 "both sides"。 我在 models.py 中定义了以下 classes:

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)

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

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

这很好用,我得到了一个不错的 multi-select,一切都很完美。但是,我希望有可能从 SomeCategory table 一侧编辑关系,这样我就可以选择哪些 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:这是我在 Whosebug 上提出的第一个问题,如果我的 post 缺少任何必需元素,请告诉我。

您的问题在 models.py 文件中。您有两个 class,但其中只有一个提到了另一个。你会认为这应该足够了,因为你毕竟使用了 ManyToManyField 并假设它会自动创建每一个双向连接......不幸的是,这不是真的。在数据库级别上,它确实创建了一个单独的中介 table 并引用了两个原始 table 中的对象,但这并不意味着它们都将在 Django Admin 或类似系统中自动可见。

如果您尝试在 SomeCategory class 中简单地创建另一个 someclass = models.ManyToManyField(SomeClass),那将会失败。 Django 会尝试创建另一个单独的中介 table through 来建立两个主要 table 之间的连接。但是由于中介 table 的名称取决于您定义 ManyToManyField 连接的位置,因此第二个 table 将使用不同的名称创建,并且一切都会在逻辑上崩溃(两个 tables 有两个单独的 default 方法来建立 ManyToMany 连接是没有意义的)。

解决方案是向 SomeCategory 添加一个 ManyToManyField 连接,同时还引用最初在 SomeClass class.

一些关于 Django/python/naming/programming 约定的注意事项:

  • 使用您所引用的 table 的名称作为包含该连接信息的字段的名称。这意味着 SomeClass 的 link 到 SomeCategory 的字段应该命名为 somecategory 而不是 allowed_categories.
  • 如果连接是一对多的——使用单数形式;如果连接是多对多的——使用复数。这意味着在这种情况下我们应该使用复数并使用 somecategories 而不是 somecategory.
  • Django 可以自动将名称复数化,但它做得很糟糕——它只是在末尾添加 s 个字母。 Mouse -> MousesCategory -> Categorys。在这种情况下,您必须通过在特殊 Meta class.
  • 中定义 verbose_name_plural 来帮助它
  • 使用对其他 class 的引用而不使用额外的 ' 仅当 class 先前已在代码中定义时才有效。在两个 classes 相互引用的情况下,只有一种方式是正确的。解决办法是将引用的名称 class 放在引号中,如 'SomeCategory' 而不是 SomeCategory。这种称为 lazy relationship 的引用在解决两个应用程序之间的循环导入依赖关系时非常有用。并且由于默认情况下最好保持样式相同并避免 "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" 不必要的脑力浪费,我建议您每次都使用引号。就像学习开车一样 - 最好学会始终使用转向灯,而不是首先环顾四周,然后单独决定某人是否会从该信息中受益。
  • "Stringifying"(延迟加载)model/class/table 名称很简单 - 只需在周围添加 '。您会认为将 "through" table 引用字符串化会以同样简单的方式工作。你会错的 - 它会给你 ValueError: Invalid model reference. String model references must be of the form 'app_label.ModelName'. 错误。为了引用字符串化的 "through" table,您需要: (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 classes 进行什么样的最终更改应该是显而易见的。