How to implement `others` for a Django Model Choices-CharField?

I have a Django model that has several CharFields. A lot of these represent information of the type

  1. Item Type 1
  2. Item Type 2
  3. Others

The usual way of defining choices for a CharField is

            models.CharField(
            max_length=255,
            choices=ChoiceList.choices, # Defining Choices
        )

As you would have guessed, this fails to meet my requirements since I cannot pass it anything(Others) apart from the pre-defined choices.

I cannot discard the choices parameter since I need the model to have the choices information for each field. This will be used to fetch all legal options for a particular field.

I tried using a custom validator function

def validate_choice(value, choices):
    if value == "Others:":
        print(value, choices)
    valid_choices = [choice[0] for choice in choices]
    if not value.startswith("Others:") and value not in valid_choices:
        raise ValidationError(f"{value} is not a valid choice")

(For simplicity, I defined my logic as

  1. Either the value is in the Choice List
  2. Or it starts with Others: (To indicate that it is a deliberate Other value)

And subsequently, I modified the CharField as follows, to use the new Validator Function

        models.CharField(
            max_length=255,
            choices=IPCSections.choices,
            validators=[partial(validate_choice, choices=ChoiceList.choices)],
        )

I then noticed that The Choice validation (By Django) happens before the custom validator is called. Which, again leads the the same issue

Others:% is not a valid choice

Looking up in the Django Documentation, I found methods like

model.clean()
model.clean_all() etc.

I tried overriding these methods, but did not end up with anything useful and the documentation does not cover exactly in what order and when are they called.

It seems like you're dealing with a situation where you want to provide preset choices for a Django CharField but also allow users to input custom values like "Others: something" The problem you're facing is that Django's built-in choice validation happens before your custom validation, which stops "Others: something" from being accepted as a valid choice

One way to handle this is to preprocess the input before it gets to the Django model. For instance, you could use a form or serializer to validate and preprocess the input before saving it to the model

from django import forms from .models import YourModel

class YourModelForm(forms.ModelForm): class Meta: model = YourModel fields = ['your_char_field']

def clean_your_char_field(self):
    data = self.cleaned_data['your_char_field']
    # Check if the input starts with "Others:" and process it accordingly
    if data.startswith('Others:'):
        # Strip "Others:" and any leading/trailing whitespace
        processed_data = data[len('Others:'):].strip()
        return processed_data
    else:
        # Check if the input is in the predefined choices
        choices = dict(YourModel.YOUR_CHAR_FIELD_CHOICES)
        if data not in choices:
            raise forms.ValidationError('Invalid choice')
        return data

You would then use this form in your views to validate user input before saving it to the model

 from django.shortcuts import render, redirect
    from .forms import YourModelForm
    
    def your_view(request):
        if request.method == 'POST':
            form = YourModelForm(request.POST)
            if form.is_valid():
                instance = form.save()
                # Redirect or do something else
                return redirect('success_url')
        else:
            form = YourModelForm()
        return render(request, 'your_template.html', {'form': form})

This approach separates the input validation logic from the model itself, allowing you to handle custom input processing while still utilizing Django's form validation mechanisms

Back to Top