Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter a Django form select element based on a previously selected element

Tags:

django

Let's consider the following models

models.py

Class Brand(models.Model):
    company_name = models.CharField(max_length=100)


class CarModel(models.Model):
    brand = models.ForeignKey(Brand)
    name = models.CharField(max_length=100)


Class FleetCars(models.Model):
    model_car = models.Foreignkey(CarModel)

What is the best way to solve this problem in django? Suppose a form (for insertions in FleetCars) consists of two select elements, like this:

Image example

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>

<br />Brand:
<select>
   <option value="Brand1">Brand1</option>
   <option value="Brand2">Brand2</option>
</select>
<br />
<br />Model:
<select>
   <option value="Model1_B1">Model1_B1</option>
   <option value="Model1_B2">Model1_B2</option>
</select>

</body>
</html>

In this case, I want the options in the second select to depend on the value selected in the first. For example, if the user chose Brand1 for a Brand in the first select, the second select would be filtered with only cars whose Brand was Brand1, that is, only "Model1_B1".

Obs. I saw many solutions with forms.ModelChoiceField, but only works with edit and since the user do not change the brand.

like image 718
Sidon Avatar asked Mar 15 '17 21:03

Sidon


People also ask

What is the purpose of filter () method in Django?

The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.

What is forms ModelForm in Django?

Django Model Form It is a class which is used to create an HTML form by using the Model. It is an efficient way to create a form without writing HTML code. Django automatically does it for us to reduce the application development time.

Which package contains filters in Django?

Django REST framework filters package.


2 Answers

After hours and hours of research, without success, I decided to try to solve on my own. The solution that I found maybe don't be the best or the more elegant, but is working. (For download full Django project, click on this repo => https://github.com/Sidon/djfkf/.)


models.py

from django.db import models

class Brand(models.Model):
    company_name = models.CharField(max_length=100)

    def __str__(self):
        return self.company_name


class Car(models.Model):
    brand = models.ForeignKey(Brand)
    name = models.CharField(max_length=100)

    def brand_name(self):
        return self.brand.company_name

    def __str__(self):
        return self.name


class Fleet(models.Model):
    car = models.ForeignKey(Car)
    description = models.CharField(max_length=100)

    def car_name(self):
        return self.car.name

    def brand(self):
        return self.car.brand.company_name

    def __str__(self):
        return self.description

The goal is to register cars on the fleet. The only fields that are will be recorded: Car (foreign key) and description. On the form, there will be one select element for brands that will work just only as a helper for to filter the car's combo box.


forms.py

import json
from django import forms
from .models import *

class RegCarForm(forms.ModelForm):

    dcars = {}
    list_cars = []
    for car in Car.objects.all():
        if car.brand.company_name in dcars:
            dcars[car.brand.company_name].append(car.name)
        else:
            dcars[car.brand.company_name] = [car.name]
        list_cars.append((car.name,car.name))

    brands = [str(brand) for brand in Brand.objects.all()]

    brand_select = forms.ChoiceField(choices=([(brand, brand) for brand in brands]))
    car_select = forms.ChoiceField(choices=(list_cars))

    brands = json.dumps(brands)
    cars = json.dumps(dcars)

    class Meta:
        model = Fleet
        fields = ('brand_select', 'car_select', 'description',)

RegCarForm is a form for register cars, there are three fields: brand_select, car_select, and description. In addition, I defined two JSON attributes: 1) a dictionary whose keys are brands (strings) and values are lists of respective's cars and 2) A list of strings that represent the brands. Those two attributes will work as helpers for JS functions.


views.py

from django.shortcuts import render
from .forms import RegCarForm
from .models import *

def regcar(request):
    if request.method == 'POST':
        car_form = RegCarForm(data=request.POST)

        if car_form.is_valid():
            cdata = car_form.cleaned_data.get
            car_selected = Car.objects.filter(name=cdata('car_select'))
            reg1 = Fleet(car_id=car_selected[0].id, description=cdata('description'))
            reg1.save()
        else:
            print ('Invalid')

    else:
        car_form = RegCarForm()
    return render(request, 'core/regcar.html', {'car_form': car_form})

The view is practically auto-explanatory. Assigns the Form to the car_form variable, render the template core/regcar.html and, after Post, make the validation of the form and save the data.


regcar.html (template django)

{% extends "base.html" %}

{% block head %}
{% endblock %}

{% block content %}
    <h1>Registering cars on the fleet. <br />(Populate one drop down based on selection in another)</h1>
    <p>Change the contents of drop down Car based on the selection in dropdown Brand, using Django-forms + Javascritp</p>
    <div class="select-style">
        <form action="." method="post">
            {% csrf_token %}
            {{ car_form.as_p }}
            <p><input type="submit" value="Register a car"></p>
        </form>
    </div>
{% endblock %}

{% block js %}
    {% include "js1.html" %}
{% endblock %}

The template only just renders the form and load the script JS. Nothing else.


Finally, the js script, that makes the hard work.

{% block js %}
    <script language="javascript">
        $('#id_brand_select').change(function() {populateCar(this)});
        $('#id_description').addClass('descriptions');
        cars = {{ car_form.cars | safe }}
        brands = {{ car_form.brands | safe}};
        populateBrand();
        $("#id_car_select").empty();
        $("#id_car_select").append('<option value="" disabled selected>First select a brand</option>');
        function populateBrand() {
            $('#id_brand_select').empty();
            $("#id_brand_select").append('<option value="" disabled selected>Select your option</option>');
            $.each(brands, function(v) {
                $('#id_brand_select')
                    .append($("<option></option>")
                    .attr("value", brands[v])
                    .text(brands[v]));
            });
        }

        function populateCar(event) {
            brand = $("#id_brand_select option:selected").text();
            $("#id_car_select").empty();
            $("#id_car_select").append('<option value="" disabled selected>Select your option</option>');
            for (let [b, bcars] of Object.entries(cars)) {
                if (b == brand) {
                    //alert(b);
                    for (car in bcars) {
                        $('#id_car_select')
                            .append($("<option></option>")
                                .attr("value", bcars[car])
                                .text(bcars[car]));
                    }
                }
            }
        }
    </script>
{% endblock %}

When the document is loaded, this script assigns the change event of brand_select (combo for selection of brand) to the function poplulateCar, assign the form's JASON attributes (cars and brands) to a JS variables and call the populateBrand function.

Links:

Full project in Django:
https://github.com/Sidon/djfkf/

like image 96
Sidon Avatar answered Oct 24 '22 19:10

Sidon


class Country(models.Model):
   country_name=models.CharField(max_length=10, blank=True, null=True)

class State(models.Model):
   state_name=models.CharField(max_length=10, blank=True, null=True)

class MyCustomModal(models.Model):
   country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)
   state = models.ForeignKey(State, on_delete=models.CASCADE, null=True, blank=True)

Here is my Form

class MyCustomForm(forms.ModelForm):
    class Meta:
        model = MyCustomModal
        fields = [
            'country',
            'state',           
        ]        

    def __init__(self, *args, **kwargs):
        super(MyCustomForm, self).__init__(*args, **kwargs)        
        self.fields['country'] = forms.ChoiceField(choices=[('1','india'),('2','US')])
        self.fields['state'].queryset = State.objects.filter(pk=2)
like image 32
Nids Barthwal Avatar answered Oct 24 '22 20:10

Nids Barthwal