Conditional dropdown based on option selected in another field

Hi,

I have 2 models named process & subprocess. The subprocess model has a foreign key to the process model class. The code is as follows:

class Process(models.Model):
    process = models.CharField(max_length=60, verbose_name="Process Name")
    # ...

class SubProcess(models.Model):
    process = models.ForeignKey(
        Process,
        on_delete=models.PROTECT
    )
    # ...

Now I am creating a form where I want only process dropdown to be displayed at first, as soon a process is selected another dropdown called “sub-process” should come below the “process” dropdown.

The “sub-process” dropdown should only contain the subprocess for the particular process selected.

I have already implemented it such that 1 form passes a get method to another URL and it works fine but i would really like to do it in 1 page itself as most modern websites load it in a single page.

Can someone guide me the possible options for implementing this?

Thanks in advance.

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

That’s been a topic discussed here a couple times previously. You can search this forum for “cascade select” or “dependent select” to find those discussions and some of the options available to you.

1 Like

Thank you both for the help!!!