Overriding model's choices in form __init__

I’m having an issue where I want to override the choices on a CharField dynamically request by request. I’m setting .choices on the field in the form’s __init__ method. This updates the choices that are rendered into the form but when the form is used to validate request.POST, the value seems to be getting validated against the original list of choices from the model instead of the override choices.

Here are some example files that demonstrate the issue:

models.py

from django.db import models


class Widget(models.Model):
    name = models.CharField(max_length=50)
    country_code = models.CharField(max_length=2, choices=[
        ('GB', 'United Kingdom'), ('US', 'United States'), ('CA', 'Canada'), ('AU', 'Australia'),
    ])

forms.py

from django import forms

from .models import Widget


class WidgetForm(forms.ModelForm):
    class Meta:
        model = Widget
        fields = [
            'name',
            'country_code',
        ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields['country_code'].choices = [
            ('DE', 'Germany'),
            ('FR', 'France'),
            ('ES', 'Spain'),
            ('IT', 'Italy'),
        ]

Am I going about this wrong? Any help would be much appreciated! :slight_smile:

hey there!
Are you getting any errors? The code is failing when you try to save, or when is_valid is returning False?

It would help if you also provided the view here.

is_valid is returning False when I set country code to one of the ones specified in the form’s __init__ method.

Here’s the view I’m using. is_valid returns False when I set country_code to one of the ones specified in the form’s __init__ method in request.POST.

views.py

from django.shortcuts import redirect, render

from .forms import WidgetForm
from .models import Widget


def index(request):
    form = WidgetForm()
    if request.method == 'POST':
        form = WidgetForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(request.path_info)

    widgets = Widget.objects.all()
    return render(request, 'app/index.html', {'form': form, 'widgets': widgets})

The issue here appears to be that while the form validation would pass, the model validation fails. The submitted data must pass both validators to be considered valid.

See Model field reference | Django documentation | Django and Model instance reference | Django documentation | Django for more details.

As a result, about the most you can do at the form level would be to restrict the list of available choices, not replace them.

I didn’t think the choices on the model field did any validation by itself. I don’t get any sort of error if I manually set widget.country_code to something that’s not in the list of choices defined on the model.

Does the form do two rounds of validation? One round against the choices defined on the form field itself and another round against the choices defined on the form field’s associated model field’s choices?

Your questions are all addressed in the two docs referenced in my previous response.

I apologise for not fully reading the links. I had no idea that models could have any sort of validation built in. I also see that, unless used manually, the model’s clean methods are only ever used by ModelForm.

I guess I can get round this either by defining all the possible choices at model level and only reducing them down in the form or by only defining the choices at the form level and not giving the model’s CharField choices.

Thanks for your help!