Avinash Raj
Avinash Raj

Reputation: 174836

How to override a Model Form widget in Django?

I'm trying to override render_option method present inside Select Widget class from my forms.py file. So I have added the method with the same name inside the corresponding Model form class. But it won't work (this method fails to override). My forms.py file looks like,

class CustomSelectMultiple(Select):

    allow_multiple_selected = True

    def render_option(self, selected_choices, option_value, option_label):
        print 'Inside custom render_option\n\n'
        if option_value is None:
            option_value = ''
        option_value = force_text(option_value)
        if option_value in selected_choices:
            selected_html = mark_safe(' selected="selected"')
            if not self.allow_multiple_selected:
                # Only allow for a single selection.
                selected_choices.remove(option_value)
        else:
            selected_html = ''
        return format_html('<option value="{}" data-img-src="www.foo.com" {}>{}</option>',
                           option_value,
                           selected_html,
                           force_text(option_label))

    def render_options(self, choices, selected_choices):
        print 'Inside custom render_options\n\n'
        print self
        print choices
        # Normalize to strings.
        selected_choices = set(force_text(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(format_html('<optgroup label="{}">', force_text(option_value)))
                for option in option_label:
                    output.append(self.render_option(selected_choices, *option))
                output.append('</optgroup>')
            else:
                output.append(self.render_option(selected_choices, option_value, option_label))
        #print output
        return '\n'.join(output)

    def render(self, name, value, attrs=None, choices=()):
        print 'Inside custom render\n\n'
        if value is None:
            value = []
        final_attrs = self.build_attrs(attrs, name=name)
        output = [format_html('<select multiple="multiple"{}>', flatatt(final_attrs))]
        options = self.render_options(choices, value)
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe('\n'.join(output))

    def value_from_datadict(self, data, files, name):
        if isinstance(data, MultiValueDict):
            return data.getlist(name)
        return data.get(name)


class GuideUpdateForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(GuideUpdateForm, self).__init__(*args, **kwargs)
        self.fields['date_modified'].widget = HiddenInput()
        self.fields['point_of_interest'].widget = CustomSelectMultiple()

    class Meta:
        fields = ('name', 'image', 'point_of_interest', 'date_modified', )
        model = Guide

I also tried changing my Meta class like,

class Meta:
        fields = ('name', 'image', 'point_of_interest', 'date_modified', )
        model = Guide
        widgets = {
            'point_of_interest': SelectMultiple(attrs={'data-img-src': 'www.foo.com'}),
        }

But it add's the attribute data-img-src only to the select tag but not to all the option tags present inside the select tag.

Note that SelectMultiple class invokes the renderoptions method of Select class which further invokes the renderoption method which don't have attrs=None keyword argument.

Upvotes: 0

Views: 2005

Answers (2)

Sayse
Sayse

Reputation: 43330

Judging off your own solution it looks like you may have been looking for a ModelChoiceField

self.fields['point_of_interest'] = forms.ModelChoiceField(widget=CustomSelectMultiple(),
                                                          queryset=poi.objects.all())

The queryset parameter consists of "A QuerySet of model objects from which the choices for the field will be derived, and which will be used to validate the user’s selection."

does it create a list of tuples of ids, names? Because I want the option tag to look like option value="id">name</option>

I'm pretty sure the default is id, __str__ where __str__ is the string representation of the model. If you wanted this to be specific to the name then you could override this field and set label_from_instance

class MyModelChoiceField(ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.name

Upvotes: 3

Avinash Raj
Avinash Raj

Reputation: 174836

I managed to solve this problem by passing db values to choices kwargs.

from models import poi
class GuideUpdateForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(GuideUpdateForm, self).__init__(*args, **kwargs)
        self.fields['date_modified'].widget = HiddenInput()
        self.fields['point_of_interest'] = forms.ChoiceField(widget=CustomSelectMultiple(), choices=[(i.id,i.name) for i in poi.objects.all()])

Upvotes: 1

Related Questions