Skip to content Skip to sidebar Skip to footer

In Django Form, Custom Selectfield And Selectmultiplefield

I am using Django everyday now for three month and it is really great. Fast web application development. I have still one thing that I cannot do exactly how I want to. It is the Se

Solution 1:

What you need to do, is to change the output which is controlled by the widget. Default is the select widget, so you can subclass it. It looks like this:

classSelect(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)

    defrender(self, name, value, attrs=None, choices=()):
        if value isNone: 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))

    defrender_options(self, choices, selected_choices):
        defrender_option(option_value, option_label):
            option_value = force_unicode(option_value)
            selected_html = (option_value in selected_choices) andu' selected="selected"'or''returnu'<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):
            ifisinstance(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))
        returnu'\n'.join(output)

It's a lot of code. But what you need to do, is to make your own widget with an altered render method. It's the render method that determines the html that is created. In this case, it's the render_options method you need to change. Here you could include some check to determine when to add a class, which you could style.

Another thing, in your code above it doesn't look like you append the last group choices. Also you might want to add an order_by() to the queryset, as you need it to be ordered by the type. You could do that in the init method, so you don't have to do it all over when you use the form field.

Solution 2:

render_option has been removed from Django 1.11 onwards. This is what I did to achieve this. A little bit of digging and this seems straightforward and neat. Works with Django 2.0+

classCustomSelect(forms.Select):
    def__init__(self, attrs=None, choices=()):
        self.custom_attrs = {}
        super().__init__(attrs, choices)

    defcreate_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        index = str(index) if subindex isNoneelse"%s_%s" % (index, subindex)
        if attrs isNone:
            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 optioniflen(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,
        }


classMyModelChoiceField(ModelChoiceField):

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

The form:

classBookingForm(forms.ModelForm):

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

The output which I needed is as:

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

Solution 3:

I run into this question many times when searching by

'how to customize/populate Django SelectField options'

The answer provided by Dimitris Kougioumtzis is quite easy

Hope it could help somebody like me.

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

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

Solution 4:

You should not mess with form fields for adding some custom attributes to the rendered html tag. But you should subclass and add a these to the Widget.

From the docs: customizing-widget-instances

You can submit attrs dictionary to the form Widgets, that render as attributes on the output form widgets.

classCommentForm(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><inputtype="text"name="name"class="special"/></td></tr>
<tr><th>Url:</th><td><inputtype="text"name="url"/></td></tr><tr><th>Comment:</th><td><inputtype="text"name="comment"size="40"/></td></tr>

Solution 5:

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

As seen under the class Select(Widget):, there is no way to add the style attribute to an option tag. To this, you will have to subclass this widget and add such functionality.

The class Select(Widget): definition only adds style attribute to the main select tag.

Post a Comment for "In Django Form, Custom Selectfield And Selectmultiplefield"