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 %}