Выбор поля формы Django, добавление атрибута - PullRequest
16 голосов
/ 23 февраля 2011
PLANNING_CHOICES = (
    ('0',u'Every morning'),
    ('1',u'Every night'),
    ('2',u'Never'),
)

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

Имея такое поле формы, которое называется планированием, мне нужно добавить атрибут заголовка к выбору и в конце отобразить его так:

                <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>

Как этого достичь?

Ответы [ 5 ]

21 голосов
/ 23 февраля 2011

Вы должны были бы создать подкласс для поля, чтобы использовать любые средства для указания заголовка и виджета для отображения нового атрибута.

Если у вас было что-то подобное (примечание: полностью не проверено):

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)
6 голосов
/ 31 октября 2014

Вот так я решил проблему

#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>
3 голосов
/ 23 февраля 2011

Вы не можете. По крайней мере, не слишком много хакеров.

Вы, вероятно, хорошо используете form_utils.BetterForm , внешний пакет, а не django.forms.Form, если вы хотите иметь этот и больший контроль над созданной разметкой.

2 голосов
/ 29 марта 2018

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>
0 голосов
/ 12 мая 2019

Вот общее решение для разрешения атрибутов в опциях в виджете 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

Вот пример, который вы можете попробовать в своем файле forms.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 для проверки результата:

(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>'''
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...