Why don't the options for my cascading dropdown form appear?

81 Views Asked by At

I am building a car repair estimator and need to create a dependent dropdown that will allow users to select from a variety of Models that depend on the Make that they've chosen. I'm able to see and select different Makes, but I am unable to see the different Models. I am using Django framework and have stored the Makes, and Models in a Postgresql database

Here is what is happening with my app right now: Ford models are supposed to be here

Here is a snapshot of my working director of my app, AutoBuddy: Working Directory

Here is the code from my ABDjango/templates/models/home.html file:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dependent Dropdown in Django</title>
</head>
<body>
<h2>Car Form</h2>

<form method="post" id="modelForm" data-models-url="{% url 'ajax_load_models' %}">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Submit">
</form>

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
    $("#id_model").change(function () {
        const url = $("#modelForm").attr("data-models-url");  // get the url of the `load_cities` view
        const modelId = $(this).val();  // get the selected country ID from the HTML input
        $.ajax({    
                               // initialize an AJAX request
            url: url,                    // set the url of the request (= /persons/ajax/load-cities/ )
            data: {
                'model_id': modelId       // add the country id to the GET parameters
            },
            success: function (data) {   // `data` is the return of the `load_cities` view function
                $("#id_model").html(data);  // replace the contents of the city input with the data that came from the server
                /*
                let html_data = '<option value="">---------</option>';
                data.forEach(function (city) {
                    html_data += `<option value="${city.id}">${city.name}</option>`
                });
                console.log(html_data);
                $("#id_city").html(html_data);
                */
            }
        });

    });
</script>

</body>
</html>

Here is the code from my ABDjango/templates/models/model_dropdown_list_options.html file:

<option value="">---------</option>
{% for model in models %}
<option value="{{ model.pk }}">{{ model.name }}</option>
{% endfor %}

Here is the code from my ABDjango/urls.py file:

from django.contrib import admin
from django.urls import path
from django.conf.urls import include

urlpatterns = [
    path('users/', include('autobuddyapp.urls')),
    path('admin/', admin.site.urls)

Here is the code from my autobuddy/models.py file:

from django.db import models


   class Make (models.Model):
      name = models.CharField(max_length=40)

      def __str__(self):
         return self.name


   class Model(models.Model):
      make = models.ForeignKey(Make, on_delete=models.CASCADE)
      name = models.CharField(max_length=40)

      def __str__(self):
         return self.name


   class Car(models.Model):
      year = models.CharField(max_length=124)
      make = models.ForeignKey(Make, on_delete=models.SET_NULL, blank=True, null=True)
      model = models.ForeignKey(Model, on_delete=models.SET_NULL, blank=True, null=True)

      def __str__(self):
         return self.year + " " + str(self.make) + " " + str(self.model)

Here's the code from my autobuddy/urls.py file:

from django.urls import path
from . import views

urlpatterns = [
    path('add/', views.model_create_view, name='model_add'),
    path('<int:pk>/', views.model_update_view, name='model_change'),
    path('ajax/load-models/', views.load_models, name='ajax_load_models'), # AJAX
]

Here's autobuddy/views.py:

from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404

from .forms import ModelCreationForm
from .models import Make, Model, Car


def model_create_view(request):
    form = ModelCreationForm()
    if request.method == 'POST':
        form = ModelCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('model_add')
    return render(request, 'models/home.html', {'form': form})


def model_update_view(request, pk):
    car = get_object_or_404(Car, pk=pk)
    form = ModelCreationForm(instance=car)
    if request.method == 'POST':
        form = ModelCreationForm(request.POST, instance=car)
        if form.is_valid():
            form.save()
            return redirect('model_change', pk=pk)
    return render(request, 'models/home.html', {'form': form})


# AJAX
def load_models(request):
    model_id = request.GET.get('model_id')
    models = Model.objects.filter(model_id = model_id).all()
    return render(request, 'models/model_dropdown_list_options.html', {'models': models})
    # return JsonResponse(list(cities.values('id', 'name')), safe=False)

Autobuddy/forms.py

from django import forms

from autobuddyapp.models import Make, Model, Car

class ModelCreationForm(forms.ModelForm):
    class Meta:
        model = Car
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['model'].queryset = Model.objects.none()

        if 'make' in self.data:
            try:
                make_id = int(self.data.get('make'))
                self.fields['model'].queryset = Model.objects.filter(make_id=make_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['model'].queryset = self.instance.model.make_set.order_by('name')
1

There are 1 best solutions below

0
Niko On

First problem is right on the first line of your script:

<script>
    $("#id_model").change(function () {
        ...

You are trying to capture change events on the wrong select, it should be on id_make. There is also problems in the response method and in the way you are trying to update the selector.

Response should be an JSON object. And to update the selector we need to create options and add to the specific element, in this case id_model. (Also, we need to clear before populating it again)

views.py

def load_models(request):
    make_id = request.GET.get('make_id')
    models = list(Model.objects.filter(make__id = make_id).values())
    return JsonResponse({'models': models})

template.html

<script>
    $("#id_make").change(function () {
        const url = $("#modelForm").attr("data-models-url");  // get the url of the `load_cities` view
        const makeId = $(this).val();  // get the selected country ID from the HTML input
        $.ajax({
                               // initialize an AJAX request
            url: url,                    // set the url of the request (= /persons/ajax/load-cities/ )
            data: {
                'make_id': makeId       // add the country id to the GET parameters
            },
            success: function (data) {   // `data` is the return of the `load_cities` view function
                $('#id_model').empty();
                for (let i = 0; i < data.models.length; i++) {
                    var option = $('<option/>');
                    option.attr({ 'value': data.models[i].id }).text(data.models[i].name);
                    $('#id_model').append(option);
                }
            }
        });

    });
</script>

Everything else, remains untouched.