Django 中的分组复选框选择多个选项

发布于 2024-10-20 13:11:22 字数 786 浏览 6 评论 0原文

在我的 Django 应用程序中,我有以下模型:

class SuperCategory(models.Model):
  name = models.CharField(max_length=100,)
  slug = models.SlugField(unique=True,)

class Category(models.Model):
  name            = models.CharField(max_length=100,)
  slug            = models.SlugField(unique=True,)
  super_category  = models.ForeignKey(SuperCategory)

我试图在 Django 的管理界面中完成的是使用小部件 CheckboxSelectMultiple 呈现 Category ,但 Category 以某种方式按 < 分组。强>超级类别,如下所示:


类别:

体育:<- 超级类别项目
[ ] 足球 <- 类别项目
[ ] 棒球 <- 类别项目
[ ] ...

政治:<- 超级类别的另一项
[ ] 拉丁美洲
[ ] 北美
[ ] ...


有人对如何做到这一点有好的建议吗?

非常感谢。

In my Django App I have the following model:

class SuperCategory(models.Model):
  name = models.CharField(max_length=100,)
  slug = models.SlugField(unique=True,)

class Category(models.Model):
  name            = models.CharField(max_length=100,)
  slug            = models.SlugField(unique=True,)
  super_category  = models.ForeignKey(SuperCategory)

What I'm trying to accomplish in Django's Admin Interface is the rendering of Category using widget CheckboxSelectMultiple but with Category somehow grouped by SuperCategory, like this:


Category:

Sports: <- Item of SuperCategory
[ ] Soccer <- Item of Category
[ ] Baseball <- Item of Category
[ ] ...

Politics: <- Another item of SuperCategory
[ ] Latin America
[ ] North america
[ ] ...


Does anybody have a nice suggestion on how to do this?

Many thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风渺 2024-10-27 13:11:22

经过一番挣扎,这就是我得到的。

首先,让 ModelAdmin 调用 ModelForm:

class OptionAdmin(admin.ModelAdmin):

   form = forms.OptionForm

然后,在表单中,使用自定义小部件来渲染:

category = forms.ModelMultipleChoiceField(queryset=models.Category.objects.all(),widget=AdminCategoryBySupercategory)    

最后,小部件:

class AdminCategoryBySupercategory(forms.CheckboxSelectMultiple):

     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         has_id = attrs and 'id' in attrs
         final_attrs = self.build_attrs(attrs, name=name)
         output = [u'<ul>']
         # Normalize to strings
         str_values = set([force_unicode(v) for v in value])
         supercategories = models.SuperCategory.objects.all()
         for supercategory in supercategories:
             output.append(u'<li>%s</li>'%(supercategory.name))
             output.append(u'<ul>')
             del self.choices
             self.choices = []
             categories = models.Category.objects.filter(super_category=supercategory)
             for category in categories:
                 self.choices.append((category.id,category.name))
             for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
                 if has_id:
                     final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
                     label_for = u' for="%s"' % final_attrs['id']
                 else:
                     label_for = ''
                 cb = forms.CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
                 option_value = force_unicode(option_value)
                 rendered_cb = cb.render(name, option_value)
                 option_label = conditional_escape(force_unicode(option_label))
                 output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
             output.append(u'</ul>')
             output.append(u'</li>')
         output.append(u'</ul>')
         return mark_safe(u'\n'.join(output))

不是最优雅的解决方案,但是嘿,它有效。

After some struggle, here is what I got.

First, make ModelAdmin call a ModelForm:

class OptionAdmin(admin.ModelAdmin):

   form = forms.OptionForm

Then, in the form, use use a custom widget to render:

category = forms.ModelMultipleChoiceField(queryset=models.Category.objects.all(),widget=AdminCategoryBySupercategory)    

Finally, the widget:

class AdminCategoryBySupercategory(forms.CheckboxSelectMultiple):

     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         has_id = attrs and 'id' in attrs
         final_attrs = self.build_attrs(attrs, name=name)
         output = [u'<ul>']
         # Normalize to strings
         str_values = set([force_unicode(v) for v in value])
         supercategories = models.SuperCategory.objects.all()
         for supercategory in supercategories:
             output.append(u'<li>%s</li>'%(supercategory.name))
             output.append(u'<ul>')
             del self.choices
             self.choices = []
             categories = models.Category.objects.filter(super_category=supercategory)
             for category in categories:
                 self.choices.append((category.id,category.name))
             for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
                 if has_id:
                     final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
                     label_for = u' for="%s"' % final_attrs['id']
                 else:
                     label_for = ''
                 cb = forms.CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
                 option_value = force_unicode(option_value)
                 rendered_cb = cb.render(name, option_value)
                 option_label = conditional_escape(force_unicode(option_label))
                 output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
             output.append(u'</ul>')
             output.append(u'</li>')
         output.append(u'</ul>')
         return mark_safe(u'\n'.join(output))

Not the most elegant solution, but hey, it worked.

成熟稳重的好男人 2024-10-27 13:11:22

我的情况略有不同,但我希望我能根据 OP 情况正确调整代码。那么下面的代码应该可以在 Django 4.2 中实现这个技巧:

# Custom UI Component
class GroupedCheckboxSelectMultiple(CheckboxSelectMultiple):
    def render(self, name, value, attrs=None, renderer=None):
        widget_id = f"id_{name}"
        html = ""
        html += f'<div id="{widget_id}" class="grouped-checkbox-select-multiple">'
        # sort since groupby needs sorted data to work "properly"
        sorted_choices = sorted(self.choices, key=lambda choice: choice.super_category or "")
        id_count = 0
        for group, choices in itertools.groupby(
            sorted_choices, lambda choice: choice.super_category
        ):
            html += '<div class="choice-group">'
            html += f'<span class="group-title">{group}</span>'
            for choice in choices:
                identifier = f"{widget_id}_{id_count}"
                html += f"""<div class="choice-wrapper">
                            <input id="{identifier}" name="{name}" type="checkbox" value={choice.name}>"""
                html += f'      <label for="{identifier}">{choice.name}</label>'
                html += "</div>"
                id_count += 1
            html += "</div>"
        html += "</div>"
        return html

然后我以如下形式使用它:

class ProjectDataForm(Form):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        raw_choices = fetch_choices()
        choices = list(map(lambda item: (item.name, item.name), raw_choices))
        self.fields["category"].choices = choices
        self.fields["category"].widget.choices = raw_choices

    category = MultipleChoiceField(widget=GroupedCheckboxSelectMultiple())

I had a slightly different case, but I hope I adapted the code properly to the OPs case. Then the following should do the trick in Django 4.2:

# Custom UI Component
class GroupedCheckboxSelectMultiple(CheckboxSelectMultiple):
    def render(self, name, value, attrs=None, renderer=None):
        widget_id = f"id_{name}"
        html = ""
        html += f'<div id="{widget_id}" class="grouped-checkbox-select-multiple">'
        # sort since groupby needs sorted data to work "properly"
        sorted_choices = sorted(self.choices, key=lambda choice: choice.super_category or "")
        id_count = 0
        for group, choices in itertools.groupby(
            sorted_choices, lambda choice: choice.super_category
        ):
            html += '<div class="choice-group">'
            html += f'<span class="group-title">{group}</span>'
            for choice in choices:
                identifier = f"{widget_id}_{id_count}"
                html += f"""<div class="choice-wrapper">
                            <input id="{identifier}" name="{name}" type="checkbox" value={choice.name}>"""
                html += f'      <label for="{identifier}">{choice.name}</label>'
                html += "</div>"
                id_count += 1
            html += "</div>"
        html += "</div>"
        return html

which I then use in a form like this:

class ProjectDataForm(Form):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        raw_choices = fetch_choices()
        choices = list(map(lambda item: (item.name, item.name), raw_choices))
        self.fields["category"].choices = choices
        self.fields["category"].widget.choices = raw_choices

    category = MultipleChoiceField(widget=GroupedCheckboxSelectMultiple())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文