将 ManyToMany 字段的小部件自定义为自身具有循环外键的模型

Customizing a Widget for ManyToMany field to a Model that have a circular ForeignKey to itself

我有两个模型类别和产品。

示例:

models.py

class Product:
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=255)

class Category:
    categories = models.ForeignKey(self)
    name = models.CharField(max_length=255)

作为表单,我使用了 ModelForm:

class ProductForm(ModelForm):
    class Meta:
        model = Product
        fields = ['categories', 'name', 'short_description', 'description']
        widgets = {
            'categories': MyWidget,
        }

我想达到的目标:

我想在创建产品表单时实施条件 select(缩小选项):

  1. 只有前 parent 个类别(级别 0 A、B、C)可用
  2. 用户 select 一个 parent 类别。如果 parent 有 children 一个新的 Select 框出现他的 children(类别级别 1 A1,C1)
  3. 用户 select 级别 1 类别 (A1,C1)。如果 parent 有 children 一个新的 Select 框出现他的 children(level 2 A2)

    • 重复该过程,直到没有 children 可用(递归),用户 select 树中的 "smallest" 类别
    • 用户可以使用新按钮添加更多类别并再次开始 1-3 过程
    • 我想做 select,使用 JavaScript
    • 添加新的 select
    • 在提交表单时,我只想发送最后 children 个类别

我认为的选项:

  1. 更改 ManyToMany 对应的默认字段 - 看起来没有好的钩子 and/or 继承
  2. 使用 non-default 自定义 Field 代替 ManytoMany(如 Charfield)- 在清理、保存表单时更复杂
  3. Change/Inherit 小部件。我的问题是如何在提交时将数据发送到默认字段,并在编辑时 get/show 发送数据

实用,假设我有 7 个 Select 个盒子,每个盒子的值:

  1. 父级1->Child11->Child111
  2. 父级2->Child21
  3. 父级3->Child31->Child311

我如何告诉 Django 在浏览器提交(与其他数据)中将所有三个中的最后一个 Child 发送给 ManyToMany

我可以用 Javascript 收集它们,但我必须告诉 Django 获取这些数据,这就是你需要的。

我需要一些帮助作为代码和指示如何执行此操作。

Django 允许您将在 ForeignKeyManyToManyField 中引用的模型定义为 '<app_name>.<model_name>' 字符串,而不必导入模型并直接分配它。这解决了很多问题,尤其是循环导入。


假设您的应用程序 categories 具有 Category 模型和 products 具有 Product 模型,这:

products/models.py:

class Product:
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=255)

categories/models.py:

class Category:
    categories = models.ManyToManyField(self)
    name = models.CharField(max_length=255)

可以直接翻译成你需要的:

products/models.py:

class Product:
    categories = models.ManyToManyField('categories.Category')
    name = models.CharField(max_length=255)

categories/models.py:

class Category:
    categories = models.ManyToManyField('self')
    name = models.CharField(max_length=255)

有了这个,你可以拥有任意多的类别:

category_one = Category.create(name='Category one')
category_two = Category.create(name='Category two')
category_three = Category.create(name='Category three')
category_three.categories.add(category_one, category_two)
some_product = Product.create(name='Test Product')
some_product.categories.add(category_three)

(ManyToManyField docs)


同样重要的是要注意任何 Python class,self 不是 class 本身——它是实例。所以你不能在实例方法之外引用它,关于为什么 here 有一个很好的解释。 'self' 字符串在这里起作用的唯一原因是因为 Django 将其转换为 class 它所在的 categories.Category –– 所以用 [ 替换 self 可能是个好主意=30=] 明确地说。