“Disabled” option for choiceField - Django

前端 未结 7 1030
别跟我提以往
别跟我提以往 2020-12-24 14:02

I having trouble with a simple question : How to have some \"disabled\" field in a dropdown menu generated via a modelForm and choiceFied in the django Framework ?

相关标签:
7条回答
  • 2020-12-24 14:21
    field_choices = (
            ('','Make choice'),
            (1,'first'),
            (2,'second'),
            (3,'third')
        )
    
    from django.forms import Select
    
    class Select(Select):
        def create_option(self, *args,**kwargs):
            option = super().create_option(*args,**kwargs)
            if not option.get('value'):
                option['attrs']['disabled'] = 'disabled'
    
            if option.get('value') == 2:
                option['attrs']['disabled'] = 'disabled'
    
            return option
    
    0 讨论(0)
  • 2020-12-24 14:23

    This might be a late answer but this is a simplified version that can be modified using the form instance.

    You can either pass a list of values to be disabled i.e

    def __init__(self, disabled_choices, *args, **kwargs):
            self.disabled_choices = disabled_choices
    

    OR

    from django.forms import Select
    
    
    class SelectWidget(Select):
        """
        Subclass of Django's select widget that allows disabling options.
        """
        def __init__(self, *args, **kwargs):
            self._disabled_choices = []
            super(SelectWidget, self).__init__(*args, **kwargs)
    
        @property
        def disabled_choices(self):
            return self._disabled_choices
    
        @disabled_choices.setter
        def disabled_choices(self, other):
            self._disabled_choices = other
    
        def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
            option_dict = super(SelectWidget, self).create_option(
                name, value, label, selected, index, subindex=subindex, attrs=attrs
            )
            if value in self.disabled_choices:
                option_dict['attrs']['disabled'] = 'disabled'
            return option_dict
    

    To disabled an option based on a condition i.e user isn't a superuser.

    class MyForm(forms.Form):
        status = forms.ChoiceField(required=True, widget=SelectWidget, choices=(('on', 'On'), ('off', 'Off')))
    
        def __init__(self, request, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            if not request.user.is_superuser:
                self.fields['status'].widget.disabled_choices = ['off']
    
    0 讨论(0)
  • 2020-12-24 14:25

    Django's form widgets offer a way to pass a list of attributes that should be rendered on the <option> tag:

    my_choices = ( ('one', 'One'), ('two', 'Two'))
    class MyForm(forms.Form):
        some_field = forms.ChoiceField(choices=my_choices, 
                                       widget=forms.Select(attrs={'disabled':'disabled'}))
    

    Unfortunately, this won't work for you because the attribute will be applied to EVERY option tag that is rendered. Django has no way to automatically know which should be enabled and which should be disabled.

    In your case, I recommend writing a custom widget. It's pretty easy to do, and you don't have that much custom logic to apply. The docs on this are here. In short though:

    • subclass forms.Select, which is the default select renderer
    • in your subclass, implement the render(self, name, value, attrs) method. Use your custom logic to determine if the value qualifies as needing to be disabled. Have a look at the very short implementation of render in django/forms/widgets.py if you need inspriation.

    Then, define your form field to use your custom widget:

    class MyForm(forms.Form):
        some_field = forms.ChoiceField(choices=my_choices, 
                                       widget=MyWidget)
    
    0 讨论(0)
  • 2020-12-24 14:35

    Are you trying to create a menu in which the list items are separated into categories, and you don't want the categories themselves to be selectable?

    If so, you can achieve this by having your template render the field using tags, e.g.

    <select name="my_field" id="id_my_field">
    <optgroup label="-- Root 1 entry --">
        <option value="1">Elt 1</option>
        <option value="2">Elt 2</option>
        <option value="3">Elt 3</option>
    </optgroup>
    <optgroup label="--- Root 2 entry ---">
        <option value="4">Elt 4</option>
        <option value="5">Elt 5</option>
    </optgroup>
    </select>
    
    0 讨论(0)
  • 2020-12-24 14:39

    Simple questions sometimes have complex answers in Django. I spent a lot of time getting this to work well. Combining Jarrett's overview with an important note from jnns about render_option and some help from #django on freenode I have a well-working complete example solution:

    First, this example assumes a normally defined choices-type CharField in a model I call Rule. I subclass my own TimeStampedModel but you can use a models.Model:

    class Rule(TimeStampedModel):
    
        ...
    
        # Rule Type
        SHORT_TERM_RULE = 'ST'
        MAX_SIGHTINGS_PER_PERIOD_RULE = "MA"
        WHITE_LIST_RULE = "WL"
        BLACK_LIST_RULE = "BL"
        RULE_CHOICES = (
            (SHORT_TERM_RULE, 'Short Term Rule'),
            (MAX_SIGHTINGS_PER_PERIOD_RULE, 'Max Sightings Per Period Rule'),
            (WHITE_LIST_RULE, 'White List Rule'),
            (BLACK_LIST_RULE, 'Black List Rule'),
        )
        rule_type = models.CharField(
            max_length=2,
            choices=RULE_CHOICES,
            default=SHORT_TERM_RULE,
        )
    
        ...
    
    

    In forms.py, define the widget subclassing Select that accepts disabled_choices. It has a custom render_option() that adds disabled to the html output of option tags if their choice is included on the passed in disabled_choices list. Note, I left most of the render_option() code from Select as-is:

    class MySelect(Select):
        def __init__(self, attrs=None, choices=(), disabled_choices=()):
            super(MySelect, self).__init__(attrs, choices=choices)
            self.disabled_choices = disabled_choices
    
        def render_option(self, selected_choices, option_value, option_label):
            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:
                    selected_choices.remove(option_value)
            else:
                selected_html = ''
            for key, value in self.disabled_choices:
                if option_value in key:
                    return format_html('<option disabled value="{}"{}>{}</option>', option_value, selected_html,
                                       force_text(option_label))
            return format_html('<option value="{}"{}>{}</option>', option_value, selected_html, force_text(option_label))
    

    Then, in defining the form subclassing ModelForm, check for a passed-in disabled_choices list and initialize the field accordingly. In this example, I also sneak in a default choice.

    class RuleChoiceForm(ModelForm):
        class Meta:
            model = Rule
            fields = ['rule_type']
    
        # Add a default choice to the list defined in the Rule model
        default_choice = ('DF', 'Choose a Rule Type...')
        choices = list(Rule.RULE_CHOICES)
        choices.insert(0, default_choice)
        choices = tuple(choices)
    
        rule_type = forms.ChoiceField(widget=MySelect(attrs={'class': 'form-control'}, disabled_choices=[]),
                                      choices=choices)
    
        def __init__(self, *args, disabled_choices=None, **kwargs):
            super(RuleChoiceForm, self).__init__(*args, **kwargs)
            if disabled_choices:
                self.fields['rule_type'].widget.disabled_choices = disabled_choices
    

    Then in your view, define disabled_choices as a list, appending choices from your _CHOICES var in your model and pass it into the form instantiation. In my logic, I use list comprehension of RULE_CHOICES to get the tuple of the choice I want to disable. Though there may be a simpler way, please feel free to post suggestions to simplify or improve this answer.

        disabled_choices = []
        # Logic disabling choices
        if Rule.objects.filter(route=route, rule_type=Rule.SHORT_TERM_RULE).exists():
            disabled_choices.append([item for item in Rule.RULE_CHOICES if Rule.SHORT_TERM_RULE in item][0])
            disabled_choices.append([item for item in Rule.RULE_CHOICES if Rule.MAX_SIGHTINGS_PER_PERIOD_RULE in item][0])
    
        rule_choice_form = RuleChoiceForm(disabled_choices=disabled_choices)
    
    0 讨论(0)
  • 2020-12-24 14:41

    It seems django 1.1 will allow "optgroup": Django documentation

    class MyForm(forms.Form):
        some_field = forms.ChoiceField(choices=[
            ('Audio', (
                    ('vinyl', 'Vinyl'),
                    ('cd', 'CD'),
                )
            ),
            ('Video', (
                    ('vhs', 'VHS Tape'),
                    ('dvd', 'DVD'),
                )
            ),
            ('unknown', 'Unknown'),
        ])
    

    This imho is a must have.

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