В форме Django пользовательские SelectField и SelectMultipleField

Я использую Django каждый день в течение трех месяцев, и это действительно здорово. Быстрая разработка веб-приложений.

У меня есть еще одна вещь, которую я не могу сделать именно так, как я хочу. Это поле SelectField и SelectMultiple.

Я хочу иметь возможность выставить некоторые аргументы в опцию выбора.

Я, наконец, успех с optgroup:

class EquipmentField(forms.ModelChoiceField):
    def __init__(self, queryset, **kwargs):
        super(forms.ModelChoiceField, self).__init__(**kwargs)
        self.queryset = queryset
        self.to_field_name=None

        group = None
        list = []
        self.choices = []

        for equipment in queryset:
            if not group:
                group = equipment.type

            if group != equipment.type:
                self.choices.append((group.name, list))
                group = equipment.type
                list = []
            else:
                list.append((equipment.id, equipment.name))

Но для другой ModelForm я должен изменить цвет фона каждой опции, используя свойство цвета модели.

Ты знаешь, как я могу это сделать?

Спасибо.

4 ответа

Решение

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>

Что вам нужно сделать, это изменить вывод, который контролируется виджетом. По умолчанию это виджет выбора, так что вы можете создать его подкласс. Это выглядит так:

class Select(Widget):
    def __init__(self, attrs=None, choices=()):
        super(Select, self).__init__(attrs)
        # choices can be any iterable, but we may need to render this widget
        # multiple times. Thus, collapse it into a list so it can be consumed
        # more than once.
        self.choices = list(choices)

    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = ''
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select%s>' % flatatt(final_attrs)]
        options = self.render_options(choices, [value])
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

    def render_options(self, choices, selected_choices):
        def render_option(option_value, option_label):
            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</option>' % (
                escape(option_value), selected_html,
                conditional_escape(force_unicode(option_label)))
        # Normalize to strings.
        selected_choices = set([force_unicode(v) for v in selected_choices])
        output = []
        for option_value, option_label in chain(self.choices, choices):
            if isinstance(option_label, (list, tuple)):
                output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value)))
                for option in option_label:
                    output.append(render_option(*option))
                output.append(u'</optgroup>')
            else:
                output.append(render_option(option_value, option_label))
        return u'\n'.join(output)

Это много кода. Но что вам нужно сделать, это сделать свой собственный виджет с измененным методом рендеринга. Это метод рендеринга, который определяет HTML, который создается. В этом случае это render_options метод, который вы должны изменить. Здесь вы можете включить некоторые проверки, чтобы определить, когда добавить класс, который вы могли бы стилизовать.

Другое дело, что в вашем коде выше это не похоже на добавление последних вариантов группы. Также вы можете добавить order_by() на набор запросов, как вам нужно, чтобы упорядочить по типу. Вы можете сделать это в методе init, так что вам не нужно делать это заново при использовании поля формы.

Я много раз сталкивался с этим вопросом при поиске

"Как настроить / заполнить параметры Django SelectField"

Ответ, предоставленный Димитрисом Кугиумцисом, довольно прост

Надеюсь, что это может помочь кому-то, как я.

# forms.py
from django.forms import ModelForm, ChoiceField
from .models import MyChoices

class ProjectForm(ModelForm):
    choice = ChoiceField(choices=[
        (choice.pk, choice) for choice in MyChoices.objects.all()])
# admin.py
class ProjectAdmin(BaseAdmin):
    form = ProjectForm
    ....

http://code.djangoproject.com/browser/django/trunk/django/newforms/widgets.py?rev=7083

Как видно из класса Select(Widget): нет способа добавить атрибут style в тег option. Для этого вам придется создать подкласс этого виджета и добавить такую ​​функциональность.

Класс Select(Widget): определение только добавляет атрибут стиля к основному тегу выбора.

Не следует связываться с полями формы для добавления некоторых пользовательских атрибутов в отображаемый тег HTML. Но вы должны создать подкласс и добавить их в виджет.

Из документов: настройка-виджетов-экземпляров

Вы можете отправить attrs словарь для виджетов формы, которые отображаются как атрибуты на виджетах формы вывода.

class CommentForm(forms.Form):
    name = forms.CharField(
                widget=forms.TextInput(attrs={'class':'special'}))
    url = forms.URLField()
    comment = forms.CharField(
               widget=forms.TextInput(attrs={'size':'40'}))
Django will then include the extra attributes in the rendered output:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
<tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
Другие вопросы по тегам