Skip to content

Commit fd50afd

Browse files
committed
Example project: add "side filters" sample
1 parent 3db727d commit fd50afd

File tree

7 files changed

+243
-7
lines changed

7 files changed

+243
-7
lines changed

README.rst

+20
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,26 @@ then:
480480
return queryset
481481
482482
483+
Add a sidebar with custom filters
484+
---------------------------------
485+
486+
Sometimes you need to provide complex or very specific filters to let the user control
487+
the content of the table in an advanced manner.
488+
489+
In those cases, the global or column filters provided by AjaxDatatableView,
490+
which are based on simple <input> and <select> widgets, may not be enought.
491+
492+
Still, you can easily add a sidebar with custom filters, and apply to them
493+
the concepts explained in the previous paragraph (`Provide "extra data" to narrow down the initial queryset`_).
494+
495+
An example of this technique has been added to the Example project; the result
496+
and a detailed explanation is presented here:
497+
498+
http://django-ajax-datatable-demo.brainstorm.it/side_filters/
499+
500+
.. image:: screenshots/side_filters.png
501+
502+
483503
Automatic addition of table row ID
484504
----------------------------------
485505

example/frontend/ajax_datatable_views.py

+32
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from django.contrib.humanize.templatetags.humanize import intcomma
2424
from django.template.defaultfilters import truncatechars
2525
from django.contrib.auth.models import Permission
26+
from django.db.models import Q
2627

2728
from project.query_debugger import query_debugger
2829
from backend.models import Track
@@ -109,6 +110,7 @@ class TrackAjaxDatatableView(AjaxDatatableView):
109110
initial_order = [["name", "asc"], ]
110111
length_menu = [[10, 20, 50, 100, -1], [10, 20, 50, 100, 'all']]
111112
search_values_separator = '+'
113+
show_date_filters = False
112114

113115
column_defs = [
114116
AjaxDatatableView.render_row_tools_column_def(),
@@ -147,10 +149,39 @@ class AlbumAjaxDatatableView(AjaxDatatableView):
147149
AjaxDatatableView.render_row_tools_column_def(),
148150
{'name': 'pk', 'visible': False, },
149151
{'name': 'name', 'visible': True, },
152+
{'name': 'release_date', 'visible': True, },
150153
{'name': 'year', 'visible': True, },
151154
{'name': 'artist', 'title':'Artist', 'foreign_field': 'artist__name', 'visible': True, 'choices': True, 'autofilter': True, },
152155
]
153156

157+
def get_initial_queryset(self, request=None):
158+
159+
def get_numeric_param(key):
160+
try:
161+
value = int(request.POST.get(key))
162+
except:
163+
value = None
164+
return value
165+
166+
queryset = super().get_initial_queryset(request=request)
167+
168+
check_year_null = get_numeric_param('check_year_null')
169+
if check_year_null is not None:
170+
if check_year_null == 0:
171+
queryset = queryset.filter(year=None)
172+
elif check_year_null == 1:
173+
queryset = queryset.exclude(year=None)
174+
175+
from_year = get_numeric_param('from_year')
176+
if from_year is not None:
177+
queryset = queryset.filter(year__gte=from_year)
178+
179+
to_year = get_numeric_param('to_year')
180+
if to_year is not None:
181+
queryset = queryset.filter(year__lte=to_year)
182+
183+
return queryset
184+
154185

155186
class ArtistAjaxDatatableView(AjaxDatatableView):
156187

@@ -160,6 +191,7 @@ class ArtistAjaxDatatableView(AjaxDatatableView):
160191
initial_order = [["name", "asc"], ]
161192
length_menu = [[10, 20, 50, 100, -1], [10, 20, 50, 100, 'all']]
162193
search_values_separator = '+'
194+
show_date_filters = False
163195

164196
column_defs = [
165197
AjaxDatatableView.render_row_tools_column_def(),

example/frontend/templates/frontend/minimal.html

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@
1616
A minimal example using AjaxDatatableView.
1717
</p>
1818

19-
<div class="table-responsive">
20-
<table id="datatable" width="100%" class="table table-striped table-bordered dt-responsive compact nowrap">
21-
</table>
22-
</div>
23-
2419
<h3>How it works</h3>
2520

2621
<p>An AjaxDatatableView-derived view describes the desired table content and behaviour as follows:</p>
@@ -87,6 +82,11 @@ <h3>How it works</h3>
8782
]
8883
</pre>
8984

85+
<div class="table-responsive">
86+
<table id="datatable" width="100%" class="table table-striped table-bordered dt-responsive compact nowrap">
87+
</table>
88+
</div>
89+
9090
{% endblock content %}
9191

9292

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
{% extends 'base.html' %}
2+
{% load i18n %}
3+
4+
{% block page-title %}{% trans 'Side filters' %}{% endblock page-title %}
5+
6+
{% block extrastyle %}
7+
<style>
8+
pre { font-size: 11px; font-family: monospace; background-color: #fff; padding: 10px 0 0 10px;}
9+
input[type="number"] { width: 100px; }
10+
.filters {
11+
border: 1px solid #999;
12+
padding-top: 10px;
13+
}
14+
</style>
15+
{% endblock extrastyle %}
16+
17+
18+
{% block content %}
19+
20+
<p>Example with Side filters</p>
21+
22+
<p>
23+
Sometimes you need to provide to the user more control over the queryset
24+
used to populate the table, using interactive and specific filters.
25+
</p>
26+
27+
<p>
28+
In those cases, you can easily add a sidebar with the required widgets,
29+
then collect and apply user selections at every table refresh.
30+
</p>
31+
32+
<h3>How it works</h3>
33+
34+
<p>
35+
First, add to the template all required input widgets, and schedule a table redraw
36+
whenever a filter has been changed (or when you believe is more appropriate)
37+
</p>
38+
39+
<pre>
40+
&lt;div class="col-sm-2 filters"&gt;
41+
&lt;h4&gt;Filters&lt;/h4&gt;
42+
&lt;label for="from_year"&gt;From year:&lt;/label&gt;&lt;input type="number" id="from_year"&gt;
43+
&lt;label for="to_year"&gt;To year:&lt;/label&gt;&lt;input type="number" id="to_year"&gt;
44+
...
45+
</pre>
46+
47+
<pre>
48+
$(document).ready(function() {
49+
50+
$('.filters input').on('change paste keyup', function() {
51+
// redraw the table
52+
$('#datatable').DataTable().ajax.reload(null, false);
53+
});
54+
55+
});
56+
</pre>
57+
58+
<p>
59+
At table initialization, register in the "extra data" section the callbacks needed
60+
to collect user selections
61+
</p>
62+
63+
<pre>
64+
$(document).ready(function() {
65+
66+
AjaxDatatableViewUtils.initialize_table(
67+
...
68+
}, {
69+
// extra_data
70+
from_year: function() { return $('input#from_year').val() },
71+
to_year: function() { return $('input#to_year').val() },
72+
check_year_null: function() { return $("input[name='check_year_null']:checked").val() }
73+
},
74+
);
75+
76+
});
77+
</pre>
78+
79+
<p>
80+
Those "extra data" values will be sent to the AjaxDatatableView with
81+
every request; you can take advantage of those by filtering the queryset
82+
in the `get_initial_queryset()` override.
83+
</p>
84+
85+
<pre>
86+
class AlbumAjaxDatatableView(AjaxDatatableView):
87+
88+
...
89+
90+
def get_initial_queryset(self, request=None):
91+
92+
def get_numeric_param(key):
93+
try:
94+
value = int(request.POST.get(key))
95+
except:
96+
value = None
97+
return value
98+
99+
queryset = super().get_initial_queryset(request=request)
100+
101+
check_year_null = get_numeric_param('check_year_null')
102+
if check_year_null is not None:
103+
if check_year_null == 0:
104+
queryset = queryset.filter(year=None)
105+
elif check_year_null == 1:
106+
queryset = queryset.exclude(year=None)
107+
108+
from_year = get_numeric_param('from_year')
109+
if from_year is not None:
110+
queryset = queryset.filter(year__gte=from_year)
111+
112+
to_year = get_numeric_param('to_year')
113+
if to_year is not None:
114+
queryset = queryset.filter(year__lte=to_year)
115+
116+
return queryset
117+
</pre>
118+
119+
<div class="container">
120+
<div class="row">
121+
<div class="col-sm-2 filters">
122+
<h4>Filters</h4>
123+
<label for="from_year">From year:</label><input type="number" id="from_year">
124+
<label for="to_year">To year:</label><input type="number" id="to_year">
125+
<br />
126+
<br />
127+
<label>Provided:</label><br />
128+
<input type="radio" id="year_null" name="check_year_null" value="0">
129+
<label for="year_null">year is NULL</label><br>
130+
<input type="radio" id="year_not_null" name="check_year_null" value="1">
131+
<label for="year_not_null">year is not NULL</label><br>
132+
<input type="radio" id="year_any" name="check_year_null" value="-1">
133+
<label for="year_any">any</label>
134+
</div>
135+
<div class="col-sm-10">
136+
<div class="table-responsive">
137+
<table id="datatable" width="100%" class="table table-striped table-bordered dt-responsive compact nowrap">
138+
</table>
139+
</div>
140+
</div>
141+
</div>
142+
</div>
143+
144+
145+
{% endblock content %}
146+
147+
148+
{% block extrajs %}
149+
150+
{{ block.super }}
151+
<script language="javascript">
152+
153+
$(document).ready(function() {
154+
155+
AjaxDatatableViewUtils.initialize_table(
156+
$('#datatable'),
157+
"{% url 'frontend:ajax_datatable_album' %}",
158+
{
159+
// extra_options (example)
160+
processing: false,
161+
autoWidth: false,
162+
full_row_select: true,
163+
scrollX: false
164+
}, {
165+
// extra_data
166+
// ...
167+
from_year: function() { return $('input#from_year').val() },
168+
to_year: function() { return $('input#to_year').val() },
169+
check_year_null: function() { return $("input[name='check_year_null']:checked").val() }
170+
},
171+
);
172+
173+
$('.filters input').on('change paste keyup', function() {
174+
// redraw the table
175+
$('#datatable').DataTable().ajax.reload(null, false);
176+
});
177+
});
178+
179+
</script>
180+
{% endblock %}
181+

example/frontend/templates/navbar.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Examples</a>
2323
<div class="dropdown-menu" aria-labelledby="dropdown01">
2424
<a class="dropdown-item" href="{% url 'frontend:index' %}">List tracks</a>
25-
<a class="dropdown-item" href="{% url 'frontend:minimal-list' %}">Minimal example</a>
25+
<a class="dropdown-item" href="{% url 'frontend:minimal' %}">Minimal example</a>
2626
<a class="dropdown-item" href="{% url 'frontend:custompks-list' %}">Custom PK</a>
27+
<a class="dropdown-item" href="{% url 'frontend:side_filters' %}">Side filters</a>
2728
</div>
2829
</li>
2930

example/frontend/urls.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
path('ajax_datatable/artist/', ajax_datatable_views.ArtistAjaxDatatableView.as_view(), name="ajax_datatable_artist"),
2020

2121
path('ajax_datatable/permissions/', ajax_datatable_views.PermissionAjaxDatatableView.as_view(), name="ajax_datatable_permissions"),
22-
path('minimal/', TemplateView.as_view(template_name='frontend/minimal/list.html'), name="minimal-list"),
22+
path('minimal/', TemplateView.as_view(template_name='frontend/minimal.html'), name="minimal"),
2323

2424
path('custompks/', TemplateView.as_view(template_name='frontend/custompk/list.html'), name="custompks-list"),
2525
path('ajax_datatable/custompks/', ajax_datatable_views.CustomPkAjaxDatatableView.as_view(), name="ajax_datatable_custompk"),
26+
27+
path('side_filters/', TemplateView.as_view(template_name='frontend/side_filters.html'), name="side_filters"),
2628
]

screenshots/side_filters.png

221 KB
Loading

0 commit comments

Comments
 (0)