How to add attributes to option tags in django ?

后端 未结 6 1996
闹比i
闹比i 2020-12-14 11:01

I have to add title attribute to options of the ModelChoiceField. Here is my admin code for that:

class LocModelForm(forms.ModelForm):
        def __init__(s         


        
相关标签:
6条回答
  • 2020-12-14 11:19

    First of all, don't modify fields in __init__, if you want to override widgets use Meta inner class, if you want to override form fields, declare them like in a normal (non-model) form.

    If the Select widget does not do what you want, then simply make your own. Original widget uses render_option method to get HTML representation for a single option — make a subclass, override it, and add whatever you want.

    class MySelect(forms.Select):
        def render_option(self, selected_choices, option_value, option_label):
            # look at the original for something to start with
            return u'<option whatever>...</option>'
    
    class LocModelForm(forms.ModelForm):
        icons = forms.ModelChoiceField(
            queryset = Photo.objects.filter(galleries__title_slug = "markers"),
            widget = MySelect(attrs = {'id': 'mydds'})
        )
    
        class Meta:
            # ...
            # note that if you override the entire field, you don't have to override
            # the widget here
        class Media:
            # ...
    
    0 讨论(0)
  • 2020-12-14 11:21

    From django 1.11 and above the render_option method was removed. see this link: https://docs.djangoproject.com/en/1.11/releases/1.11/#changes-due-to-the-introduction-of-template-based-widget-rendering

    Here is a solution that worked for me different than Kayoz's. I did not adapt the names as in the example but i hope it is still clear. In the model form I overwrite the field:

    class MyForm(forms.ModelForm):
        project = ProjectModelChoiceField(label=_('Project'), widget=ProjectSelect())
    

    Then I declare the classes from above and one extra, the iterator:

    class ProjectModelChoiceIterator(django.forms.models.ModelChoiceIterator):
        def choice(self, obj):
            # return (self.field.prepare_value(obj), self.field.label_from_instance(obj)) #it used to be like this, but we need the extra context from the object not just the label. 
            return (self.field.prepare_value(obj), obj)
    
    class ProjectModelChoiceField(django.forms.models.ModelChoiceField):
       def _get_choices(self):
           if hasattr(self, '_choices'):
               return self._choices
           return ProjectModelChoiceIterator(self)
    
    
    class ProjectSelect(django.forms.Select):
    
        def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
            context = super(ProjectSelect, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
    
            context['attrs']['extra-attribute'] = label.extra_attribute #label is now an object, not just a string.
            return context
    
    0 讨论(0)
  • 2020-12-14 11:28

    Here is a solution if you want to use the instance to set the attribute value.

    class IconSelectWidget(forms.Select):
        def create_option(self, name, value, *args, **kwargs):
            option = super().create_option(name, value, *args, **kwargs)
            if value:
                icon = self.choices.queryset.get(pk=value)  # get icon instance
                option['attrs']['title'] = icon.title  # set option attribute
            return option
    
    class LocModelForm(forms.ModelForm):
        icons = forms.ModelChoiceField(
            queryset=Photo.objects.filter(galleries__title_slug='markers'),
            widget=IconSelectWidget
        )
    
    0 讨论(0)
  • 2020-12-14 11:30

    Here's a class I made that inherits from forms.Select (thanks to Cat Plus Plus for getting me started with this). On initialization, provide the option_title_field parameter indicating which field to use for the <option> title attribute.

    from django import forms
    from django.utils.html import escape
    
    class SelectWithTitle(forms.Select):
        def __init__(self, attrs=None, choices=(), option_title_field=''):
            self.option_title_field = option_title_field
            super(SelectWithTitle, self).__init__(attrs, choices)
    
        def render_option(self, selected_choices, option_value, option_label, option_title=''):
            print option_title
            option_value = forms.util.force_unicode(option_value)
            if option_value in selected_choices:
                selected_html = u' selected="selected"'
                if not self.allow_multiple_selected:
                    # Only allow for a single selection.
                    selected_choices.remove(option_value)
            else:
                selected_html = ''
            return u'<option title="%s" value="%s"%s>%s</option>' % (
                escape(option_title), escape(option_value), selected_html,
                forms.util.conditional_escape(forms.util.force_unicode(option_label)))
    
        def render_options(self, choices, selected_choices):
                # Normalize to strings.
                selected_choices = set(forms.util.force_unicode(v) for v in selected_choices)
                choices = [(c[0], c[1], '') for c in choices]
                more_choices = [(c[0], c[1]) for c in self.choices]
                try:
                    option_title_list = [val_list[0] for val_list in self.choices.queryset.values_list(self.option_title_field)]
                    if len(more_choices) > len(option_title_list):
                        option_title_list = [''] + option_title_list # pad for empty label field
                    more_choices = [(c[0], c[1], option_title_list[more_choices.index(c)]) for c in more_choices]
                except:
                    more_choices = [(c[0], c[1], '') for c in more_choices] # couldn't get title values
                output = []
                for option_value, option_label, option_title in chain(more_choices, choices):
                    if isinstance(option_label, (list, tuple)):
                        output.append(u'<optgroup label="%s">' % escape(forms.util.force_unicode(option_value)))
                        for option in option_label:
                            output.append(self.render_option(selected_choices, *option, **dict(option_title=option_title)))
                        output.append(u'</optgroup>')
                    else: # option_label is just a string
                        output.append(self.render_option(selected_choices, option_value, option_label, option_title))
                return u'\n'.join(output)
    
    class LocModelForm(forms.ModelForm):
        icons = forms.ModelChoiceField(
            queryset = Photo.objects.filter(galleries__title_slug = "markers"),
            widget = SelectWithTitle(option_title_field='FIELD_NAME_HERE')
        )
    
    0 讨论(0)
  • 2020-12-14 11:42

    Working with Django 1.11 I discovered another way to this this using the documented APIs. If you override get_context and dig down enough into the structure you'll see the individual option attributes in context['widget']['optgroups'][1][option_idx]['attrs']. For example, in my subclass I have this code:

    class SelectWithData(widgets.Select):
        option_data = {}
    
        def __init__(self, attrs=None, choices=(), option_data={}):
            super(SelectWithData, self).__init__(attrs, choices)
            self.option_data = option_data
    
        def get_context(self, name, value, attrs):
            context = super(SelectWithData, self).get_context(name, value, attrs)
            for optgroup in context['widget'].get('optgroups', []):
                for option in optgroup[1]:
                    for k, v in six.iteritems(self.option_data.get(option['value'], {})):
                        option['attrs']['data-' + escape(k)] = escape(v)
            return context
    
    0 讨论(0)
  • 2020-12-14 11:43

    I had a similar problem, where I needed to add a custom attribute to each option dynamically. But in Django 2.0, the html rendering was moved into the Widget base class, so modifying render_option no longer works. Here is the solution that worked for me:

    from django import forms
    
    class CustomSelect(forms.Select):
        def __init__(self, *args, **kwargs):
            self.src = kwargs.pop('src', {})
            super().__init__(*args, **kwargs)
    
        def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
            options = super(CustomSelect, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
            for k, v in self.src.items():
                options['attrs'][k] = v[options['value']]
            return options
    
    class CustomForm(forms.Form):
        def __init__(self, *args, **kwargs):
            src = kwargs.pop('src', {})
            choices = kwargs.pop('choices', ())
            super().__init__(*args, **kwargs)
            if choices:
                self.fields['custom_field'].widget = CustomSelect(attrs={'class': 'some-class'}, src=src, choices=choices)
    
        custom_field = forms.CharField(max_length=100)
    

    Then in views, render a context with {'form': CustomForm(choices=choices, src=src)} where src is a dictionary like this: {'attr-name': {'option_value': 'attr_value'}}.

    0 讨论(0)
提交回复
热议问题