Bypass choice validation when filling select via AJAX

Hi,

I’m using a model view (CreateView) for a model with a ‘state’ field and a ‘county’ field, where the ‘state’ field generates a select widget with the 50 US states. For the county field, I don’t want options for the several thousand counties in the US, so I set the county field choices in the model to an empty list and use AJAX to set the county options when the user selects a state. This works fine, but when I try to save, I get a validation error (because the selected choice was not specified in the model). I would like to bypass validation for this field. I tried this:

class AjaxChoiceField(forms.ChoiceField):
    def valid_value(self, value):
        return True

class FarmYearCreateView(CreateView):
    model = FarmYear
    fields = ['farm_name', 'state', 'county_code']
    county_code = AjaxChoiceField()

…but I still get the validation error. I’d appreciate any help on this.

Thanks!
Dow

That custom field needs to be part of the form and not the view. This means you want to define the form for that view and make your form modifications in it.

Great – thanks, Ken, for getting me on track here!

Here’s the working solution for reference.

in models.py:

class County(models.Model):
    id = models.IntegerField(primary_key=True)
    code = models.SmallIntegerField()
    name = models.CharField(max_length=40)
    state = models.ForeignKey('State', on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.name}, {self.state.abbr}'

    class Meta:
        managed = False
        verbose_name_plural = 'counties'

    @classmethod
    def code_and_name_for_state_id(cls, state_id):
        return list(cls.objects.filter(state_id=state_id)
                    .order_by('name').values_list('code', 'name'))

class FarmYear(models.Model):
    """
    Holds non-crop-specific values for a crop year for a farm
    """
    farm_name = models.CharField(max_length=60)
    county_code = models.SmallIntegerField(
        verbose_name="primary county",
        help_text="The county where most farm acres are located")
    state = models.ForeignKey(State, on_delete=models.CASCADE)

in forms.py:

from django.forms import ModelForm
from django import forms
from .models import FarmYear 

class AjaxChoiceField(forms.ChoiceField):
    def valid_value(self, value):
        # TODO: ensure value can be parsed to int and int value in range
        return True

class FarmYearForm(ModelForm):
    county_code = AjaxChoiceField()

    class Meta:
        model = FarmYear
        fields = ['farm_name', 'state', 'county_code']

in views.py:

class FarmYearCreateView(CreateView):
    model = FarmYear
    form_class = FarmYearForm

    def get_success_url(self):
        return reverse('dashboard')

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)


class GetCountyView(View):
    def get(self, request, state_id, *args, **kwargs):
        print("got ajax request")
        counties = County.code_and_name_for_state_id(state_id)
        print(type(counties))
        return JsonResponse({'data': counties})

In urls.py

urlpatterns = [
    path('dashboard/', views.dashboard, name='dashboard'),
    path('farmyear/create', FarmYearCreateView.as_view(), name='farmyear_create'),
    path('farmyear/counties_for_state/<int:state_id>',
         GetCountyView.as_view(), name="get_counties")
]

in cropyear_form.html:

{% block content %}
<div class="p-6 max-w-med mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
    <div>
        <h1 class="block text-xl mb-2">Add a farm for the current crop year</h1>
        <form action="" method="post">
            {% csrf_token %}
            {{ form|crispy }}
            <button type="submit" class="btn-primary">Create farm</button>
        </form>
    </div>
</div>   
<script>
 (() => {
    let httpRequest;
    document
      .getElementById("state")
         .addEventListener("change", (event) => {
             makeRequest(event.target.value)
         });

    function makeRequest(state_id) {
      httpRequest = new XMLHttpRequest();

      if (!httpRequest) {
        alert("Giving up :( Cannot create an XMLHTTP instance");
        return false;
      }
      httpRequest.onreadystatechange = updateCounties;
      const url = `counties_for_state/${state_id}`
        httpRequest.open("GET", url);
      httpRequest.send();
    }

    function updateCounties() {
      if (httpRequest.readyState === XMLHttpRequest.DONE) {
        if (httpRequest.status === 200) {
            let options = '';
            const resp = JSON.parse(httpRequest.responseText);
            resp.data.forEach(cty => {
                options += `<option value=${cty[0]}>${cty[1]}</option>\n`;
            });
            document.getElementById("county_code").innerHTML = options;
        } else {
          alert("Request for counties failed.");
        }
      }
    }
  })();
</script>
{% endblock content %}