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
- Item Type 1
- Item Type 2
- 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
- Either the value is in the Choice List
- Or it starts with
Others:
(To indicate that it is a deliberateOther
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