[Answered ]-How to filter a model through multiple forms?

1👍

First point: model forms are for creating / editing models, so you should use plain forms here. Your error comes from having the make field in your ModelSelectForm but not setting its value anywhere. Also, ModelChoiceField is meant to retrieve a model instance, not field’s value, so you really want a ChoiceField here.

Second point, since your goal is to display filtered informations – not to create or edit anything -, you should use GET queries (like for any “search” feature actually).

For your second form to work as expected (once ported to a plain Form with a single model field), you’ll need to pass the make value to the form and, in the form’s __init__(), update the model fields choices to the filtered queryset.

Also since you’ll be using GET as form’s method, you’ll have to check wether the form has been submitted at all before deciding to instanciate it with or without the request.GET data, else the users would get error messages on first display before they even had a chance to submit anything. This is usually solved using either a name and value for the form’s submit button or a hidden field in the form itself:

Forms:

class  ModelSelectForm(forms.Form):
    model = forms.ChoiceField()

    def __init__(self, *args, **kwargs):
        make = kwargs.pop("make", None)
        if not make:
            raise ValueError("expected a 'make' keyword arg")
        super(ModelSelectForm, self).__init__(*args, **kwargs)
        qs = Car.objects.filter(make=make).values_list('model',flat=True).distinct()
        choices = [(value, value) for value in qs]
        self.fields["model"].choices = choices

Views:

def model_select_view(request, make):
    model = None
    if request.GET.get("submitted", None):
        form = ModelSelectForm(request.GET, make=make)
        if form.is_valid():
            model = form.cleaned_data['model']
    else:
        form = ModelSelectForm(make=make)
    context = {"form": form, "model": model, "make: make}
    return render(request, "reviews/modelselect.html", context)

Templates:

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

wrt/ your question about “passing ‘make’ to the second view”: there’s nowhere in your code snippet where you direct the user to the model-select view, but I assume that what you want is the user being redirected to it once he successfully selected the “make” in the first view. If yes, your first view’s code should handle the case on successful form submission, ie:

def make_select_view(request):
    if request.GET.get("submitted", None):
        form = MakeSelectForm(request.GET)
        if form.is_valid():
            make = form.cleaned_data['make']
            # send the user to the model selection view
            return redirect("reviews:model-select", make=make)

    else:
        form = MakeSelectForm()
    context = {"form": form}
    return render(request, "reviews/makeselect.html", context)

1👍

Since the formatting of the snippet that I posted in comment got messed up so, I am writing that as an answer here.

def __init__(self, *args, **kwargs):
    self.request = kwargs.pop('request', None)
    super(GameForm, self).__init__(*args, **kwargs)
    if not self.request.user.is_staff:
        self.fields['publisher'].queryset = Publisher.objects.filter(id=self.request.user.id)

Leave a comment