Django表单字段选择,添加属性

时间:2011-02-23 10:05:30

标签: django django-forms

PLANNING_CHOICES = (
    ('0',u'Every morning'),
    ('1',u'Every night'),
    ('2',u'Never'),
)

    planning = forms.ChoiceField(
                                required=True,    
                                choices = PLANNING_CHOICES,
                                )

有一个名为planning的表单字段,我需要在选项中添加title属性,最后呈现为:

                <select>
                    <option value="0" title="bla1">Every morning</option>
                    <option value="1" title="bla2">Every night</option>
                    <option value="2" title="bla3">Never</option>
                </select>

如何实现?

6 个答案:

答案 0 :(得分:20)

您必须对该字段进行子类化,以采用任何方式指定您想要的标题,并使用小部件来显示新属性。

如果您有这样的事情(注意:完全未经测试):

from django import forms
from django.utils.html import escape
from django.utils.encoding import force_unicode

class SelectWithTitles(forms.Select):
    def __init__(self, *args, **kwargs):
        super(SelectWithTitles, self).__init__(*args, **kwargs)
        # Ensure the titles dict exists
        self.titles = {}

    def render_option(self, selected_choices, option_value, option_label):
        title_html = (option_label in self.titles) and \
            u' title="%s" ' % escape(force_unicode(self.titles[option_label])) or ''
        option_value = force_unicode(option_value)
        selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
        return u'<option value="%s"%s%s>%s</option>' % (
            escape(option_value), title_html, selected_html,
            conditional_escape(force_unicode(option_label)))

class ChoiceFieldWithTitles(forms.ChoiceField):
    widget = SelectWithTitles

    def __init__(self, choices=(), *args, **kwargs):
        choice_pairs = [(c[0], c[1]) for c in choices]
        super(ChoiceFieldWithTitles, self).__init__(choices=choice_pairs, *args, **kwargs)
        self.widget.titles = dict([(c[1], c[2]) for c in choices])

......你应该能够做到这一点:

PLANNING_CHOICES_WITH_TITLES = (
    ('0', 'Every morning', 'bla1'),
    ('1', 'Every night',   'bla2'),
    ('2', 'Never',         'bla3'),
)

planning = forms.ChoiceFieldWithTitles(
    required=True, choices=PLANNING_CHOICES_WITH_TITLES)

答案 1 :(得分:6)

这就是我解决问题的方法

#models.py
class Publisher(models.Model):
    slug = models.Slugfield()

class Book(forms.ModelForm):
    publisher = models.ForeignKey(Publisher)

#forms.py
from django import forms
from django.utils.encoding import force_unicode
from django.utils.html import escape, conditional_escape

class SelectWithData(forms.Select):
    def render_option(self, selected_choices, option_value, option_label):
        obj_data = {
            obj.id: {
                data_attr: getattr(obj, data_attr) for data_attr in self.data_attrs
            } for obj in self.queryset
        }

        data_text = u''
        for data_attr in self.data_attrs:
            data_text += u' data-{}="{}" '.format(
                data_attr, 
                escape(force_unicode(obj_data[option_value][data_attr]))
            )

        option_value = force_unicode(option_value)
        selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
        return u'<option value="{}"{}{}>{}</option>'.format(
            escape(option_value),
            data_text,
            selected_html,
            conditional_escape(force_unicode(option_label))
        )


class ModelChoiceFieldWithData(forms.ModelChoiceField):
    widget = SelectWithData

    def __init__(self, queryset, **kwargs):
        data_attrs = kwargs.pop('data_attrs')
        super(ModelChoiceFieldWithData, self).__init__(queryset, **kwargs)
        self.widget.queryset = queryset
        self.widget.data_attrs = data_attrs


class BookForm(forms.ModelForm):

    publisher = ModelChoiceFieldWithData(
        queryset=Publisher.objects.all(),
        data_attrs=('slug',),
    )

#html

<select  id="id_publisher" name="publisher" required="required" title="">
    <option value="1" data-slug="first" selected="selected">First Publisher</option>
    <option value="2" data-slug="second">Second Publisher</option>
</select>

答案 2 :(得分:3)

你做不到。至少,并非没有太多的hackery。

如果您想要对生成的标记进行更多控制,那么使用form_utils.BetterForm(外部包而非django.forms.Form可能会很好。

答案 3 :(得分:1)

render_option已从Django 1.11开始删除。这就是我为实现这一目标所做的。一点点挖掘,这看起来很简单。适用于Django 2.0 +

class CustomSelect(forms.Select):
    def __init__(self, attrs=None, choices=()):
        self.custom_attrs = {}
        super().__init__(attrs, choices)

    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        index = str(index) if subindex is None else "%s_%s" % (index, subindex)
        if attrs is None:
            attrs = {}
        option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
        if selected:
            option_attrs.update(self.checked_attribute)
        if 'id' in option_attrs:
            option_attrs['id'] = self.id_for_label(option_attrs['id'], index)

        # setting the attributes here for the option
        if len(self.custom_attrs) > 0:
            if value in self.custom_attrs:
                custom_attr = self.custom_attrs[value]
                for k, v in custom_attr.items():
                    option_attrs.update({k: v})

        return {
            'name': name,
            'value': value,
            'label': label,
            'selected': selected,
            'index': index,
            'attrs': option_attrs,
            'type': self.input_type,
            'template_name': self.option_template_name,
        }


class MyModelChoiceField(ModelChoiceField):

    # custom method to label the option field
    def label_from_instance(self, obj):
        # since the object is accessible here you can set the extra attributes
        if hasattr(obj, 'type'):
            self.widget.custom_attrs.update({obj.pk: {'type': obj.type}})
        return obj.get_display_name()

表格:

class BookingForm(forms.ModelForm):

    customer = MyModelChoiceField(required=True,
                                  queryset=Customer.objects.filter(is_active=True).order_by('name'),
                                  widget=CustomSelect(attrs={'class': 'chosen-select'}))

我需要的输出是:

  <select name="customer" class="chosen-select" required="" id="id_customer">
      <option value="" selected="">---------</option>
      <option value="242" type="CNT">AEC Transcolutions Private Limited</option>
      <option value="243" type="CNT">BBC FREIGHT CARRIER</option>
      <option value="244" type="CNT">Blue Dart Express Limited</option>

答案 4 :(得分:0)

这是允许在Select小部件,SelectMultiple小部件以及Select2小部件中的选项中使用属性的通用解决方案。

它已经在Django 2.1上进行了测试,并且也可以在其他版本上使用(在注释中告诉我)。

from django.forms.widgets import Select, SelectMultiple

class SelectWOA(Select):
    """
    Select With Option Attributes:
        subclass of Django's Select widget that allows attributes in options, 
        like disabled="disabled", title="help text", class="some classes",
              style="background: color;"...

    Pass a dict instead of a string for its label:
        choices = [ ('value_1', 'label_1'),
                    ...
                    ('value_k', {'label': 'label_k', 'foo': 'bar', ...}),
                    ... ]
    The option k will be rendered as:
        <option value="value_k" foo="bar" ...>label_k</option>
    """

    def create_option(self, name, value, label, selected, index, 
                      subindex=None, attrs=None):
        if isinstance(label, dict):
            opt_attrs = label.copy()
            label = opt_attrs.pop('label')
        else: 
            opt_attrs = {}
        option_dict = super(SelectWOA, self).create_option(name, value, 
            label, selected, index, subindex=subindex, attrs=attrs)
        for key,val in opt_attrs.items():
            option_dict['attrs'][key] = val
        return option_dict

以下是您可以在表格中尝试的示例。py:

choices = [('b', 'blue'),
           ('g', {'label': 'green', 'disabled': 'disabled'}),
           ('c', {'label': 'cyan', 
                  'title': 'Kind of violet',
                  'style': 'background: cyan;',
                 }),
           ('r', 'red'), ]

colors = forms.ChoiceField(
    label="Colors",
    choices=choices,
    widget=SelectWOA)

colors字段可以在Django shell中呈现以检查结果:

(myvenv) $ ./manage.py shell
>>> from myapp.forms import *
>>> choices = ...
>>> colors = ...
>>> colors.widget.render('mycolors','')
'''<select name="mycolors">
      <option value="b">blue</option>
      <option value="g" disabled="disabled">green</option>
      <option value="c" title="Kind of violet" style="background: cyan;">cyan</option>
      <option value="r">red</option>
 </select>'''

要允许多项选择,请添加以下内容:

class SelectMultipleWOA(SelectWOA, SelectMultiple):
    """ 
    SelectMultipleWOA widget works like SelectMultiple, with options attrs.
    See SelectWOA.
    """
    pass

colors = forms.MultipleChoiceField(
    label="Colors",
    choices=choices,
    widget=SelectMultipleWOA)

它将呈现<select name="mycolors" multiple>...<\select>

您可以使用SelectWOA和SelectMultipleWOA来扩展Select2小部件:

from django_select2.forms import Select2Mixin

class Select2MultipleWidgetWOA(Select2Mixin, SelectMultipleWOA):
    """
    Select2 drop in widget for multiple select.
    Works just like Select2MultipleWidget but with options attrs.
    """
    pass

colors = forms.MultipleChoiceField(
    label="Colors",
    choices=choices,
    widget=Select2MultipleWidgetWOA(
        attrs={'data-placeholder': 'Any color',
               'data-close-on-select': 'false',
               'style': 'width:280px; height:36px;',
               'title': 'Type a word to filter the menu',}
    )
)

它将呈现如下内容:

'''<select name="mycolors" data-placeholder="Any color" 
    class="django-select2" data-minimum-input-length="0" multiple 
    style="width:280px; height:36px;" data-close-on-select="false" 
    data-allow-clear="false" title="Type a word to filter the menu">
       <option value="b">blue</option>
       <option value="g" disabled="disabled">green</option>
       <option value="c" title="Kind of violet" style="background: cyan;">cyan</option>
       <option value="r">red</option>
    </select>'''

答案 5 :(得分:0)

我批准了Nihal Sharma对Django 2+的回答,它仍然很hacky,但是更漂亮:

class CustomSelect(forms.Select):
    def __init__(self, attrs=None, choices=()):
        self.custom_attrs = {}
        super().__init__(attrs, choices)

    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        option = super().create_option(name, value, label, selected, index, subindex, attrs)

        # setting the attributes here for the option
        if value in self.custom_attrs:
            option['attrs'].update({k: v for k, v in self.custom_attrs[value].items()})

        return option

class ModelChoiceFieldWithData(ModelChoiceField):
    widget = CustomSelect

    def __init__(self, *args, **kwargs):
        self.additional_data = kwargs.pop('additional_data')
        super().__init__(*args, **kwargs)

    # custom method to label the option field
    def label_from_instance(self, obj):
        # since the object is accessible here you can set the extra attributes
        self.widget.custom_attrs[obj.pk] = {f'data-{attr}': getattr(obj, attr) for attr in self.additional_data}
    return super().label_from_instance(obj)

表格:

class BookingForm(forms.ModelForm):
    customer = ModelChoiceFieldWithData(queryset=Customer.objects.filter(is_active=True),
                                        additional_data=('my_field',))