djangoformsdjango-forms

How to change the empty_label value in Django's ModelChoiceField?


In Django, I’m using a ModelChoiceField with the empty_label parameter to allow users to unselect a value in a dropdown. Here’s the code:

department = forms.ModelChoiceField(
    queryset=Department.objects.all(),
    label="Department",
    required=False,
    empty_label="------"  # Add an empty option
)

This works fine, but the default behavior assigns an empty string ("") as the value for the empty_label option. Unfortunately, one of the JavaScript libraries I’m using does not handle empty string values properly.

I need to assign a custom value (e.g., -1) for the empty_label option, but I still want to retain its functionality to "unselect" the current object.

I’ve tried overriding the choices in the init method, like this:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields['department'].widget.choices = [("-1", "------")] + list(
        self.fields['department'].queryset.values_list('id', 'name')
    )

However, when I do this, the functionality doesn’t work as expected — it doesn’t allow the "unselect" option as it does with the empty_label.

How can I change the empty_label value (from the empty string) to -1 and still maintain the same behavior of unselecting the option?

Thanks in advance for your help!


Solution

  • You could write your own with a patch of the ModelChoiceIterator:

    from django.forms.models import ModelChoiceIterator
    
    
    class MyModelChoiceIterator(ModelChoiceIterator):
        def __iter__(self):
            if self.field.empty_label is not None:
                yield ('-1', self.field.empty_label)
            queryset = self.queryset
            if not queryset._prefetch_related_lookups:
                queryset = queryset.iterator()
            for obj in queryset:
                yield self.choice(obj)

    Then plug this in as in a subclass of ModelChoiceField: MyModelChoiceField:

    from django.forms.models import ModelChoiceField
    
    
    class MyModelChoiceField(ModelChoiceField):
        iterator = MyModelChoiceIterator
        empty_values = [*ModelChoiceField.empty_values, '-1']

    and then plug in this model field in your form.