Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

How to filter ModelChoiceField by fields?

I have a Car model that contains several data:

class Car(models.Model):
    make = models.CharField(max_length=50)
    model = models.CharField(max_length=50)
    platform = models.CharField(max_length=50)
(...)

For instance: [make="BMW", model="5 Series", platform="E39"]

My forms.py:

from django import forms
from .models import Car

class CarForm(forms.ModelForm):
    make = forms.ModelChoiceField(queryset=Car.objects.values_list('make',flat=True).distinct())
    model = forms.ModelChoiceField(queryset=Car.objects.values_list('model',flat=True).filter(make=make).distinct())
    plaform = forms.ModelChoiceField(queryset=Car.objects.values_list('platform',flat=True).filter(make=make, model=model).distinct())

    class Meta:
        model = Car
        fields = ["make", "model", "platform"]

My views.py:

from django.shortcuts import render
from django.views.generic import View
from .models import Car
from .forms import CarForm

class CarFormView(View):
    form_class = CarForm
    template_name = "reviews/car.html"

    def get(self, request):
        form = self.form_class(None)
        return render(request, self.template_name, {"form": form})

I want to filter all the instances of Car model by 3 consecutive ModelChoiceField's:

  • 1st one only shows distinct makes (done),
  • 2nd one only shows models with make=first step's choice,
  • 3rd one only shows platform with make=first step's choice & model=second step's choice.

How do I make such a dependent filter in my forms? Without jQuery, if possible.

Thank you!

2 Answers

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,441 Points

The question to ask is "How does the form know to change the second and third choice list based on the results of the first choice?" You obviously can not pre-populate the second and third choices until the first choice has been made.

Two main paths for this:

  • POST the form when the first answer is chosen, then based on the post info, redisplay the form with the first choice set and the second selection updated based on the first choice. Repeat to update the third choice selections after second choice has been made. This will make the view more complicated because it will have to decide which stage the form in: first, second, or third choice selection.
  • Use a jQuery/javascript/ajax call to dynamically get the updated choices from the server.

Sorry there isn't a simple cookie cutter solution for this. Hints may be found in these examples: this one or this one or Django extensions like django-autocomplete-light or django-smart-selects

Disclaimer: I haven't validated any of the suggested hints (you mileage may vary!) Good Luck!!

Thank you for your reply. I tried Django Autocomplete Light but couldn't make the "forward" widget work. And Django Smart Select only works with chained foreignkeys, but I've got single model.

So I'm following first solution. I couldn't figure out how to filter make, model and plaform on the same page with one view. My forms.py:

class MakeSelectForm(forms.ModelForm):
    make = forms.ModelChoiceField(queryset=Car.objects.values_list('make',flat=True).distinct())

    class Meta:
        model = Car
        fields = ["make"]


class ModelSelectForm(forms.ModelForm):
    model = forms.ModelChoiceField(queryset=Car.objects.values_list('model',flat=True).distinct())

    class Meta:
        model = Car
        fields = ["make", "model"]

Views.py:

class MakeSelectView(FormView):
    form_class = MakeSelectForm
    template_name = "reviews/makeselect.html"


class ModelSelectView(FormView):
    form_class = ModelSelectForm
    template_name = "reviews/modelselect.html"

Makeselect.html:

{% extends "base.html" %}

{% block body %}

<form action="{% url 'reviews:model-select' make %}" method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Select" />
</form>

{% endblock %}

URL's:

urlpatterns = [
    url(r'^$', views.MakeSelectView.as_view(), name="make-select"),
    url(r'^(?P<make>\w+)/$', views.ModelSelectView.as_view(), name="model-select"),
]

I'm getting this error:

NoReverseMatch at /
Reverse for 'model-select' with arguments '('',)' not found. 1 pattern(s) tried: ['(?P<make>\\w+)/$']

So... I can not pass "make" argument to my model-select URL. Also, should I edit my ModelSelectForm so that it initiliazes with make=first step's selection?

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,441 Points

Old post, but if you haven't solved it, have you tried:

<form action="{% url 'reviews:model-select' make=make %}" method="POST">

When passing parameters to a URL you need to include the keyword