【问题标题】:How to add attributes to option tags in django ?如何在 django 中为选项标签添加属性?
【发布时间】:2011-09-22 14:24:33
【问题描述】:

我必须在 ModelChoiceField 的选项中添加 title 属性。这是我的管理代码:

class LocModelForm(forms.ModelForm):
        def __init__(self,*args,**kwargs):
            super(LocModelForm,self).__init__(*args,**kwargs)
            self.fields['icons'] = forms.ModelChoiceField(queryset = Photo.objects.filter(galleries__title_slug = "markers"))
            self.fields['icons'].widget.attrs['class'] = 'mydds'


        class Meta:
            model = Loc
            widgets = {
                'icons' : forms.Select(attrs={'id':'mydds'}), 
                }

        class Media:
            css = {
                "all":("/media/css/dd.css",)
                }
            js=(
                '/media/js/dd.js',
                )

class LocAdmin(admin.ModelAdmin):
    form = LocModelForm

我可以添加任何属性来选择小部件,但我不知道如何将属性添加到选项标签。任何想法 ?

【问题讨论】:

标签: django widget drop-down-menu admin options


【解决方案1】:

首先,不要修改__init__中的字段,如果你想覆盖小部件使用Meta内部类,如果你想覆盖表单字段,像普通(非模型)表单一样声明它们.

如果Select 小部件不能满足您的需求,则只需制作您自己的小部件。原始小部件使用render_option 方法来获取单个选项的 HTML 表示 - 创建一个子类,覆盖它,然后添加任何你想要的。

class MySelect(forms.Select):
    def render_option(self, selected_choices, option_value, option_label):
        # look at the original for something to start with
        return u'<option whatever>...</option>'

class LocModelForm(forms.ModelForm):
    icons = forms.ModelChoiceField(
        queryset = Photo.objects.filter(galleries__title_slug = "markers"),
        widget = MySelect(attrs = {'id': 'mydds'})
    )

    class Meta:
        # ...
        # note that if you override the entire field, you don't have to override
        # the widget here
    class Media:
        # ...

【讨论】:

  • Meta 内部类而不是__init__ 方法中修改字段是否有任何特殊原因?同样的推理是否适用于修改/添加字段的小部件属性?
  • @hellsgate 在大多数情况下,没有理由不覆盖__init__ 来设置widget.attrs。在大多数用例中,例如通过 __init__ 覆盖修改其他默认小部件 not 上的 html 属性违反 DRY。不幸的是,对于 OP 来说,他将不得不定义一个自定义小部件,因为 &lt;option&gt; 标签是由 Select 小部件类的 render_option 方法呈现的。
  • 这种方法也可用于扩展SelectMultiple 小部件。只需继承 SelectMultiple 并将其传递给自定义的 MySelect 小部件。
  • render_option 已在 1.11 btw docs.djangoproject.com/en/2.1/releases/1.11/… 中删除
【解决方案2】:

我遇到了类似的问题,我需要为每个选项动态添加自定义属性。但是在 Django 2.0 中,html 渲染被移到了 Widget 基类中,因此修改 render_option 不再起作用。这是对我有用的解决方案:

from django import forms

class CustomSelect(forms.Select):
    def __init__(self, *args, **kwargs):
        self.src = kwargs.pop('src', {})
        super().__init__(*args, **kwargs)

    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        options = super(CustomSelect, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
        for k, v in self.src.items():
            options['attrs'][k] = v[options['value']]
        return options

class CustomForm(forms.Form):
    def __init__(self, *args, **kwargs):
        src = kwargs.pop('src', {})
        choices = kwargs.pop('choices', ())
        super().__init__(*args, **kwargs)
        if choices:
            self.fields['custom_field'].widget = CustomSelect(attrs={'class': 'some-class'}, src=src, choices=choices)

    custom_field = forms.CharField(max_length=100)

然后在视图中,使用{'form': CustomForm(choices=choices, src=src)} 渲染上下文,其中src 是这样的字典:{'attr-name': {'option_value': 'attr_value'}}

【讨论】:

    【解决方案3】:

    这是我创建的一个继承自 forms.Select 的类(感谢 Cat Plus Plus 让我开始使用它)。初始化时,提供 option_title_field 参数,指示将哪个字段用于&lt;option&gt; title 属性。

    from django import forms
    from django.utils.html import escape
    
    class SelectWithTitle(forms.Select):
        def __init__(self, attrs=None, choices=(), option_title_field=''):
            self.option_title_field = option_title_field
            super(SelectWithTitle, self).__init__(attrs, choices)
    
        def render_option(self, selected_choices, option_value, option_label, option_title=''):
            print option_title
            option_value = forms.util.force_unicode(option_value)
            if option_value in selected_choices:
                selected_html = u' selected="selected"'
                if not self.allow_multiple_selected:
                    # Only allow for a single selection.
                    selected_choices.remove(option_value)
            else:
                selected_html = ''
            return u'<option title="%s" value="%s"%s>%s</option>' % (
                escape(option_title), escape(option_value), selected_html,
                forms.util.conditional_escape(forms.util.force_unicode(option_label)))
    
        def render_options(self, choices, selected_choices):
                # Normalize to strings.
                selected_choices = set(forms.util.force_unicode(v) for v in selected_choices)
                choices = [(c[0], c[1], '') for c in choices]
                more_choices = [(c[0], c[1]) for c in self.choices]
                try:
                    option_title_list = [val_list[0] for val_list in self.choices.queryset.values_list(self.option_title_field)]
                    if len(more_choices) > len(option_title_list):
                        option_title_list = [''] + option_title_list # pad for empty label field
                    more_choices = [(c[0], c[1], option_title_list[more_choices.index(c)]) for c in more_choices]
                except:
                    more_choices = [(c[0], c[1], '') for c in more_choices] # couldn't get title values
                output = []
                for option_value, option_label, option_title in chain(more_choices, choices):
                    if isinstance(option_label, (list, tuple)):
                        output.append(u'<optgroup label="%s">' % escape(forms.util.force_unicode(option_value)))
                        for option in option_label:
                            output.append(self.render_option(selected_choices, *option, **dict(option_title=option_title)))
                        output.append(u'</optgroup>')
                    else: # option_label is just a string
                        output.append(self.render_option(selected_choices, option_value, option_label, option_title))
                return u'\n'.join(output)
    
    class LocModelForm(forms.ModelForm):
        icons = forms.ModelChoiceField(
            queryset = Photo.objects.filter(galleries__title_slug = "markers"),
            widget = SelectWithTitle(option_title_field='FIELD_NAME_HERE')
        )
    

    【讨论】:

      【解决方案4】:

      如果要使用实例设置属性值,这里有一个解决方案。

      class IconSelectWidget(forms.Select):
          def create_option(self, name, value, *args, **kwargs):
              option = super().create_option(name, value, *args, **kwargs)
              if value:
                  icon = self.choices.queryset.get(pk=value)  # get icon instance
                  option['attrs']['title'] = icon.title  # set option attribute
              return option
      
      class LocModelForm(forms.ModelForm):
          icons = forms.ModelChoiceField(
              queryset=Photo.objects.filter(galleries__title_slug='markers'),
              widget=IconSelectWidget
          )
      

      【讨论】:

      • 正是我需要的@p14z。我需要添加一个值相似的引导类。价值是:信息。引导类是:文本信息。我不需要图标 var,但选项 attrs 的 tweek 是我从几个小时的搜索中节省下来的恩典:option['attrs']['class'] = f'text-{value}'
      • 我正在使用 v5 Bootstrap,我认为它还不能完全支持。另外,这是我唯一需要的东西,所以只为选择选项安装整个包在我看来有点矫枉过正。不过感谢您的建议。
      【解决方案5】:

      从 django 1.11 及更高版本中删除了 render_option 方法。看这个链接:https://docs.djangoproject.com/en/1.11/releases/1.11/#changes-due-to-the-introduction-of-template-based-widget-rendering

      这是一个对我有用的解决方案,与 Kayoz 的不同。我没有像示例中那样调整名称,但我希望它仍然清楚。在模型表单中,我覆盖了该字段:

      class MyForm(forms.ModelForm):
          project = ProjectModelChoiceField(label=_('Project'), widget=ProjectSelect())
      

      然后我声明上面的类和一个额外的,迭代器:

      class ProjectModelChoiceIterator(django.forms.models.ModelChoiceIterator):
          def choice(self, obj):
              # return (self.field.prepare_value(obj), self.field.label_from_instance(obj)) #it used to be like this, but we need the extra context from the object not just the label. 
              return (self.field.prepare_value(obj), obj)
      
      class ProjectModelChoiceField(django.forms.models.ModelChoiceField):
         def _get_choices(self):
             if hasattr(self, '_choices'):
                 return self._choices
             return ProjectModelChoiceIterator(self)
      
      
      class ProjectSelect(django.forms.Select):
      
          def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
              context = super(ProjectSelect, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
      
              context['attrs']['extra-attribute'] = label.extra_attribute #label is now an object, not just a string.
              return context
      

      【讨论】:

      • 我可以使用您的解决方案,但减少了您的 ProjectModelChoiceField 类,我不必覆盖 _get_choices 方法,而是分配了迭代器属性:class ProjectModelChoiceField(..): iterator = ProjectModelChoiceIterator 这也是 django 版本 1.11
      【解决方案6】:

      使用 Django 1.11 我发现了另一种使用文档化 API 的方法。如果您覆盖get_context 并深入研究结构,您将在context['widget']['optgroups'][1][option_idx]['attrs'] 中看到各个选项属性。例如,在我的子类中,我有以下代码:

      class SelectWithData(widgets.Select):
          option_data = {}
      
          def __init__(self, attrs=None, choices=(), option_data={}):
              super(SelectWithData, self).__init__(attrs, choices)
              self.option_data = option_data
      
          def get_context(self, name, value, attrs):
              context = super(SelectWithData, self).get_context(name, value, attrs)
              for optgroup in context['widget'].get('optgroups', []):
                  for option in optgroup[1]:
                      for k, v in six.iteritems(self.option_data.get(option['value'], {})):
                          option['attrs']['data-' + escape(k)] = escape(v)
              return context
      

      【讨论】:

        猜你喜欢
        • 2017-09-17
        • 2022-10-04
        • 2014-12-03
        • 1970-01-01
        • 2019-03-21
        • 1970-01-01
        • 2018-10-06
        • 1970-01-01
        相关资源
        最近更新 更多