In this tutorial, we are going to create a nice crispy range slider using django-crispy-forms for an integer filter provided by django-filters. The tutorial can be split into four sections. In the first or prerequisite section, we will install the required packages and initiate the Django project. In the next section, we will create a simple app with a model, view, and template. In the third section, we will create the simple range filter using package without a slider. In the fourth and last section, we will describe how to create a range-slider and integrate it into our Django app created in the third section. django-filters Prerequisite First things first, let’s create a directory with the working environment: $ mkdir myproject $ myproject $ pipenv shell cd Then, install packages that are required in this tutorial using pip: $ pip install Django $ pip install django-crispy-forms $ pip install django-filter Next, create a new Django project called : myproject $ django-admin startproject myproject $ mv myproject src Similarly, create a new Django app called : myapp $ python manage.py startapp myapp In the following sections, you are going to need to generate sample data for our model. Hence, let’s create a new Django admin super-user using the following command: $ python manage.py createsuperuser To enable packages in Django project, add the following lines to the in : INSTALLED_APPS /src/myproject/settings.py INSTALLED_APPS = [ ... , , , ] 'django.forms' # Required in the last section. 'django_filters' # Django-filter 'crispy_forms' 'myapp' Then, add the following line to in : TEMPLATES /src/myproject/settings.py TEMPLATES = [ { ... : [BASE_DIR / ], ... }, ] 'DIRS' 'templates' Next, add the path to in to enable the CSS and JS files, which will be required in upcoming sections: /src/myproject/static STATICFILES_DIRS /src/myproject/settings.py ... STATICFILES_DIRS = [ BASE_DIR / ] ... 'static' Finally, add the following line of code to to enable widget customization in our Django project. /src/myproject/settings.py ... FORM_RENDERER = ... 'django.forms.renderers.TemplatesSetting' Getting Ready In this section, we will create a model called People, a view for this model, and a template for that view. The Model Create a model called using three fields , and . The target filter-field is named : People name surname, age IntegerField age name = models.CharField(null= ,blank= ,max_length= ) surname = models.CharField(null= ,blank= ,max_length= ) age = models.IntegerField() : class People (models.Model) True True 50 True True 50 Run and then to apply the change to the default SQLite database: makemigrations migrate $ python manage.py makemigrations $ python manage.py migrate Then, register the model in Django admin by adding the following code to file : /src/myapp/admin.py django.contrib admin .models People admin.site.register(People, PeopleAdmin) from import from import : class PeopleAdmin (admin.ModelAdmin) pass Add some items to the database from the Django admin page at . NOTE: http://127.0.0.1:8000/admin/ The View Now let’s create a simple view that will print all instances of model in . People /src/myapp/views.py django.shortcuts render .models People all_people = People.objects.all() render(request, , { :all_people}) from import from import : def index (request) return 'index.html' 'all_people' URLs Create a URL path by adding the following line to the file : /src/myproject/urls.py ... myapp.views index urlpatterns = [ ... path( , index), ] from import '' The Template In order to create the simplest template to render all instances, create a file with the following content: People /src/templates/index.html Name Surname Age {% for person in all_people %} {{ person.name }} {{ person.surname }} {{ person.age }} {% endfor %} < = = > table border '1' style "width:100%; text-align:center" < > thead < > tr < > th </ > th < > th </ > th < > th </ > th </ > tr </ > thead < > tbody < > tr < > td </ > td < > td </ > td < > td </ > td </ > tr </ > tbody </ > table Recap In this section, we created a simple view and template to print database records for the model. Executing should make available the following screen: People $ python manage.py runserver Naive Range Filter In order to ensure coherence let’s first create a simple(or naive) provide by package. RangeFilter django-filters The Filter Create a new file and insert the following code: /src/myapp/filters.py django_filters .models People age = django_filters.AllValuesFilter() model = People fields = [ ] import from import : class PeopleFilter (django_filters.FilterSet) : class Meta 'age' This will create a simple range-filter with minimum and maximum value requirements for field of model. age People The View Now that filtering logic is ready, let’s add the filtering feature to the main view in . /src/myapp/views.py django.shortcuts render .filters PeopleFilter people_filter = RangeFilter(request.GET) render(request, , { :people_filter}) from import from import : def index (request) return 'index.html' 'people_filter' In the above code, instantiation takes as a single parameter since our form is set to mode. RangeFilter request.GET GET The Template With our filter ready, we can add filter controls in the front. Once again change the primary template file to look like this: /src/template/index.html {{ people_filter.form.as_p }} Name Surname Age {% for person in people_filter.qs %} {{ person.name }} {{ person.surname }} {{ person.age }} {% endfor %} < = > form method "get" < = /> input type "submit" </ > form < = = > table border '1' style "width:100%; text-align:center" < > thead < > tr < > th </ > th < > th </ > th < > th </ > th </ > tr </ > thead < > tbody < > tr < > td </ > td < > td </ > td < > td </ > td </ > tr </ > tbody </ > table Notice that we now have an additional filtering form provided by . In addition, observe that for statement loops over instead of . The stands for query set, which is self-explanatory. django-filters people_filter.qs all_people .qs Recap In this section, we created the simplest or naive filter. The final result should look like this: Crispy Range-Slider To create a working crispy range-slider we need the following: Front-end Backbone: Actual range-slider with HTML, CSS and JavaScript. Django Widget: Custom Django widget for the actual range-slider backbone. Crispy Form: Crispy form with a layout template for the custom widget. Range Filter: Custom filter from package that utilizes the range-slider widget with the Crispy form. django-filters Each point will be described in details so let’s move step-by-step: The Front End The first and obvious step is to create an actual slider. Since range-slider is fancy filtering “thing” and not a real HTML form element, let’s use a popular trick to make such a fancy “thing” act like an HTML form element. Particularly, we use feature to make our range-slider work. Here is a sample HTML blueprint for our slider: jQuery’s range slider [Lower Value] - [Upper Value] < = = = = = = > div id "my-numeric-slider" class "form-group numeric-slider" data-range_min "[Min. Possible Value]" data-range_max "[Max. Possible Value]" data-cur_min "[Current Min. Value]" data-cur_max "[Current Max. Value]" < = > div class "numeric-slider-range ui-slider ui-slider-horizontal ui-slider-range" </ > div < = = > span class "numeric-slider-range_text" id 'my-numeric-slider_text' </ > span < = = = /> input type 'hidden' id 'my-numeric-slider_min' name 'slider-min' < = = = /> input type 'hidden' id 'my-numeric-slider_max' name 'slider-max' </ > div The above HTML markup is comprised of outer element where the first two attributes represent possible minimum and maximum values of the range filter, last two attributes represent current lower and upper values of the range filter that were established when the page is loaded. Likewise, the first inner elements with the class is the main element transformed to the range slider by jQuery when the page is loaded. The last two hidden form-elements represent the primary means by which the data is passed from the client to the server-side when the form is submitted. Additionally, the above HTML markup requires a JS script to make the slider work and a CSS markup to render the elements properly. Both can be found in . Finally, apply the last template code bellow to the file : Div data- data- Div numeric-slider-range Input GitHub repo /src/templates/index.html {% load static %} {% load crispy_forms_tags %} # CSS of our range-slider. {% crispy people_filter.form %} Name Surname Age {% for person in people_filter.qs %} {{ person.name }} {{ person.surname }} {{ person.age }} {% endfor %} # JS of our range-slider. < > head < = = > link rel "stylesheet" href "{% static 'custom_slider.css' %}" < = = > link rel "stylesheet" href "//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" < = > script src "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js" </ > script < = > script src "https://code.jquery.com/ui/1.12.1/jquery-ui.js" </ > script </ > head < > body < = = > table border '1' style "width:100%; text-align:center" < > thead < > tr < > th </ > th < > th </ > th < > th </ > th </ > tr </ > thead < > tbody < > tr < > td </ > td < > td </ > td < > td </ > td </ > tr </ > tbody </ > table < = > script src "{% static 'custom_slider.js' %}" </ > script </ > body Notice that in the HTML above we load the and use instead of . Doing so we let package handle our form rendering. crispy_forms_tags {% crispy people_filter.form %} .as_p django-crispy-forms NOTE: The django-crispy-forms package provides two ways to render crispy forms. Common way is to use |crispy filter to render the form but it expects to be wrapped in <form>...</form> HTML tag. In our tutorial, we use {% crispy %} tag because we will generate our form using the FormHelper . The Django Widget Django uses widgets to render form-fields and present them as final HTML markup. Therefore, to let Django handle the rendering of our front-end backbone, we need a working widget. In Django, a widget consists of two parts: a widget-template that represents the final HTML a class that inherits Django’s class with method. Widget render() Widget Class The package provides working with a predefined that we seamlessly/automagically used in the previous section. This widget uses two text-fields associated with two text-input HTML form-elements. Notice that it is similar to hidden input-elements required in our case. To make our widget work we will simply rewrite the default provided by to be compatible with our range-slider. For simplicity let's call it the : django-filters RangeFilter RangeWidget RangeWidget django-filter CustomRangeWidget django.forms.widgets HiddenInput django_filters.widgets RangeWidget template_name = widgets = (HiddenInput(), HiddenInput()) super(RangeWidget, self).__init__(widgets, attrs) ctx = super().get_context(name, value, attrs) cur_min, cur_max = value cur_min : cur_min = ctx[ ][ ][ ] cur_max : cur_max = ctx[ ][ ][ ] ctx[ ][ ].update({ :cur_min, :cur_max}) base_id = ctx[ ][ ][ ] swx, subwidget enumerate(ctx[ ][ ]): subwidget[ ][ ] = base_id + + self.suffixes[swx] ctx[ ][ ] = .format(cur_min,cur_max) ctx from import from import : class CustomRangeWidget (RangeWidget) 'forms/widgets/range-slider.html' : def __init__ (self, attrs=None) : def get_context (self, name, value, attrs) if is None 'widget' 'attrs' 'data-range_min' if is None 'widget' 'attrs' 'data-range_max' 'widget' 'attrs' 'data-cur_min' 'data-cur_max' 'widget' 'attrs' 'id' for in 'widget' 'subwidgets' 'attrs' 'id' "_" 'widget' 'value_text' "{} - {}" return Widget Template The widget also requires an associated template. Let’s create a file in and insert the following content. /src/templates/forms/widgets/range-slider.html {{ widget.value_text }} {% for widget in widget.subwidgets %} {% include widget.template_name %} {% endfor %} < = {% " / / / " %}> div class "form-group numeric-slider" include django forms widgets attrs.html < = > div class "numeric-slider-range ui-slider ui-slider-horizontal ui-slider-range" </ > div < = = > span class "numeric-slider-range_text" id '{{ widget.attrs.id }}_text' </ > span </ > div In the above widget-template, we use to let Django handle the widget attributes. It does so by parsing the dictionary from the previous part. Likewise, the loop adds widget’s elements. {% include "django/forms/widgets/attrs.html" %} ctx['widget']['attrs'] for HiddenInput The Crispy Form At last, we have our actual widget ready and now we can create a crispy-form with a special template for our slider. This crispy layout template basically helps our widget to fit the Bootstrap markup logic. In other words, it makes it . crispy Crispy Template Create a new file . Then add the following template code: /src/templates/forms/fields/range-slider.html {% load crispy_forms_field %} {{ field.label|safe }} {% if field.field.required %} * {% endif %} {% crispy_field field %} < = > div class "form-group{% if 'form-horizontal' in form_class %} row{% endif %}" < = = > label for "{{ field.id_for_label }}" class "{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}" < = > span class "asteriskField" </ > span </ > label </ > div NOTE: the above code is based on django-crispy-forms’s bootstrap4 templates and was not tested in bootstrap3 or other crispy template-engine. Crispy Form Helper Once the crispy template is ready we need a form where the template will be utilized. Create a file and add the following code: /src/myapp/forms.py crispy_forms.helper FormHelper crispy_forms.bootstrap StrictButton crispy_forms.layout Field, Layout django forms django_filters.fields RangeField super().__init__(*args, **kwargs) self.helper = FormHelper(self) self.helper.form_method = layout_fields = [] field_name, field self.fields.items(): isinstance(field, RangeField): layout_field = Field(field_name, template= ) : layout_field = Field(field_name) layout_fields.append(layout_field) layout_fields.append(StrictButton( , name= , type= , css_class= )) self.helper.layout = Layout(*layout_fields) from import from import from import from import from import : class PeopleFilterFormHelper (forms.Form) : def __init__ (self, *args, **kwargs) 'get' for in if "forms/fields/range-slider.html" else "Submit" 'submit' 'submit' 'btn btn-fill-out btn-block mt-1' In the code above the class is nothing different than a simple Django form with a fancy name. However, instead of a common way of constructing Django form we use the Crispy approach with its . PeopleFilterFormHelper FormHelper NOTE: the FormHelper simply helps you to create a fancy form, which would most certainly be possible to create with the same Django means but with more effort. Our form is rather basic for the sake of clarity of our tutorial, so it is not obvious. Range Filter At last, we have everything ready except the actual filtering logic. Custom Range Filter Insert following final filter code to the file . /src/myapp/filters.py django_filters FilterSet django_filters.filters RangeFilter .models People .forms PeopleFilterFormHelper .widgets CustomRangeWidget super().__init__(*args, **kwargs) values = [p.age p People.objects.all()] min_value = min(values) max_value = max(values) self.extra[ ] = CustomRangeWidget(attrs={ :min_value, :max_value}) age = AllRangeFilter() model = People fields = [ ] form = PeopleFilterFormHelper from import from import from import from import from import : class AllRangeFilter (RangeFilter) : def __init__ (self, *args, **kwargs) for in 'widget' 'data-range_min' 'data-range_max' : class PeopleFilter (FilterSet) : class Meta 'age' In the code above, represents the set of filters for the model . Notice the line , which overrides the default form builder of to our custom . The actual filter is the class, which is a customized version of the original package's . We override its method and initiate our custom widget with initial minimum and maximum values of all possible age values from model's field. PeopleFilter People form = PeopleFilterFormHelper django-filters PeopleFilterFormHelper AllRangeFilter django-filters RangeFilter __init__ CustomRangeWidget People age NOTE: I totally agree that list comprehension is far not the best way to get the min. and max. values but this is a tutorial and its for only for educational purpose. Recap At last, we created our range filter with a fancy slider that should look like this: Summary In this tutorial, you learned how to create a fancy jQuery range slider with the custom widget for the custom range filter provided by package. Moreover, you learned how to use render the custom widget using the package. The source code for this tutorial can be found on my . I hope this tutorial was helpful to the reader and eased his suffering while learning these amazing Django packages. django-filters django-crispy-forms GitHub repo Previously published at https://www.mmtechslv.com/tutorials/django-crispy-range-slider/