Conditional dropdown based on option selected in another field

Not really django related but use a javascript event listener? and on change of the first <select>, call the “data source” to generate the options of the second <select>?

For example?

<form>
<select name="process" required>
{% for process in processes %}
  <option 
    value="{{ process.pk }}"
    data-url="{% url 'subprocesses' %}?process={{ process.pk }}"
  >{{ process.name }}</option>
{% endfor %}
</select>
<select name="subprocess" disabled required>
</select>
</form>

<script>
(function(w, d){
  d.querySelector('select["name="process"]').addEventListener('change', function(e){
    var option = this.options[this.selectedIndex];
    var url = option.dataset.url;
    fetch(url).then(function(response) {return response.json();}).then(data){
        d.querySelector('select["name="subprocess"]').innerHTML = data.markup;
    });
  }, false);
})(window, document);
</script>

and somewhere in your python code:

# urls
urlpatterns = [
    #....
    path(
        '',
        SubprocessesView.as_view(),
        name='subprocesses'
    ),
    #....
]

# views
# FormView to validate the process GET parameter
class SubprocessesView(FormView):
    def form_valid(self, form):
        subprocesses = SubProcess.objects.filter(process=form.cleaned_data.get('process'))
        return JsonResponse({
             'markup': render_to_string(
                 'processes/subprocesses_options.html', 
                 context={
                     'subprocesses': subprocesses 
                 },
                 request=self.request
             ),
        })

processes/subprocesses_options.html:

{% for subprocess in subprocesses %}
<option value="{{ subprocess.pk }}">{{ subprocess.name }}</option>
{% endfor %}

Of course your “main final form” should then validate that the right subprocess was sent alongside its proper parent. I.e. it is very easy to edit the value that will eventually get sent via the final POST/GET.

why use render_to_string? Because then you have a template that can be used as in the quick example above, but also to render the list of options on initial load, without the need for yet another javascript call.
For example, consider your main form page with the following parameters:

/form?process=12

you could then make the page load initially with a convenient list of subprocesses already populating the subprocess select.

<select name="subprocess" disabled required>
{% if subprocesses %}
{% include 'processes/subprocesses_options.html' with subprocesses=subprocesses%}
{% endif %}
</select>
1 Like