Skip to content

Commit 7a1d025

Browse files
committedSep 4, 2018
version 1.17.0
1 parent 2b14160 commit 7a1d025

File tree

5 files changed

+208
-33
lines changed

5 files changed

+208
-33
lines changed
 

‎CHANGES

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
2018.09.04 - 1.17.0
2+
- If 'name' field is defined in columns definition (JS side) then it can be used to create 'columns' and
3+
'order_columns' (see django-datatables-view-example for sample code).
4+
- Added get_filter_method method to make it possible to easily change filter from istartswith to e.g. icontains.
5+
16
2018.04.17 - 1.16.0
27
- removed exception handling - let exceptions propagate in a normal django flow (backward incompatible change!)
38
Previously django_datatables_view has handled exceptions and returned these to datatables as JSON with status code

‎README.md

+46-9
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ _django_datatables_view_ uses **GenericViews**, so your view should just inherit
2323
(there is also a DatatableMixin - pure datatables handler that can be used with the mixins of your choice, eg. django-braces). These are:
2424

2525
* **model** - the model that should be used to populate the datatable
26-
* **columns** - the columns that are going to be displayed
27-
* **order_columns** - list of column names used for sorting (eg. if user sorts by second column then second column name from this list will be used with order by clause).
28-
* **filter_queryset** - if you want to filter your datatable then override this method
26+
* **columns** - the columns that are going to be displayed. If not defined then django_datatables_view will look for 'name' in the columns definition provided in the request by DataTables, eg.: columnDefs: [{name: 'name', targets: [0]} (only works for datatables 1.10+)
27+
* **order_columns** - list of column names used for sorting (eg. if user sorts by second column then second column name from this list will be used with order by clause). If not defined then django_datatables_view will look for 'name' in the columns definition provided in the request by DataTables, eg.: columnDefs: [{name: 'name', targets: [0]} (only works for datatables 1.10+)
28+
* **filter_queryset** - if you want to filter your DataTable in some specific way then override this method. In case of older DataTables (pre 1.10) you need to override this method or there will be no filtering.
29+
* **filter_method** - returns 'istartswith' by default, you can override it to use different filtering method, e.g. icontains: return self.FILTER_ICONTAINS
2930

3031
For more advanced customisation you might want to override:
3132

3233
* **get_initial_queryset** - method that should return queryset used to populate datatable
3334
* **prepare_results** - this method should return list of lists (rows with columns) as needed by datatables
3435
* **escape_values** - you can set this attribute to False in order to not escape values returned from render_column method
3536

36-
The code is rather simple so do not hesitate to have a look at it. Method that is executed first (and that calls other methods) is **get_context_data**
37+
The code is rather simple so do not hesitate to have a look at it. Method that is executed first (and that calls other methods to execute whole logic) is **get_context_data**. Definitely have a look at this method!
3738

3839
See example below:
3940

@@ -71,12 +72,12 @@ See example below:
7172
# use parameters passed in GET request to filter queryset
7273

7374
# simple example:
74-
search = self.request.GET.get(u'search[value]', None)
75+
search = self.request.GET.get('search[value]', None)
7576
if search:
7677
qs = qs.filter(name__istartswith=search)
7778

7879
# more advanced example using extra parameters
79-
filter_customer = self.request.GET.get(u'customer', None)
80+
filter_customer = self.request.GET.get('customer', None)
8081

8182
if filter_customer:
8283
customer_parts = filter_customer.split(' ')
@@ -133,12 +134,12 @@ class OrderListJson(BaseDatatableView):
133134
# use request parameters to filter queryset
134135

135136
# simple example:
136-
search = self.request.GET.get(u'search[value]', None)
137+
search = self.request.GET.get('search[value]', None)
137138
if search:
138139
qs = qs.filter(name__istartswith=search)
139140

140141
# more advanced example
141-
filter_customer = self.request.GET.get(u'customer', None)
142+
filter_customer = self.request.GET.get('customer', None)
142143

143144
if filter_customer:
144145
customer_parts = filter_customer.split(' ')
@@ -162,4 +163,40 @@ class OrderListJson(BaseDatatableView):
162163
item.modified.strftime("%Y-%m-%d %H:%M:%S")
163164
])
164165
return json_data
165-
```
166+
```
167+
168+
## Yet another example of views.py customisation ##
169+
170+
This sample assumes that list of columns and order columns is defined on the client side (DataTables), eg.:
171+
172+
```javascript
173+
$(document).ready(function() {
174+
var dt_table = $('.datatable').dataTable({
175+
order: [[ 0, "desc" ]],
176+
columnDefs: [
177+
{
178+
name: 'name',
179+
orderable: true,
180+
searchable: true,
181+
targets: [0]
182+
},
183+
{
184+
name: 'description',
185+
orderable: true,
186+
searchable: true,
187+
targets: [1]
188+
}
189+
],
190+
searching: true,
191+
processing: true,
192+
serverSide: true,
193+
stateSave: true,
194+
ajax: TESTMODEL_LIST_JSON_URL
195+
});
196+
});
197+
```
198+
199+
```python
200+
class TestModelListJson(BaseDatatableView):
201+
model = TestModel
202+
```

‎README.rst

+61-10
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,24 @@ can be used with the mixins of your choice, eg. django-braces). These
3535
are:
3636

3737
- **model** - the model that should be used to populate the datatable
38-
- **columns** - the columns that are going to be displayed
38+
- **columns** - the columns that are going to be displayed. If not
39+
defined then django\_datatables\_view will look for 'name' in the
40+
columns definition provided in the request by DataTables, eg.:
41+
columnDefs: [{name: 'name', targets: [0]} (only works for datatables
42+
1.10+)
3943
- **order\_columns** - list of column names used for sorting (eg. if
4044
user sorts by second column then second column name from this list
41-
will be used with order by clause).
42-
- **filter\_queryset** - if you want to filter your datatable then
43-
override this method
45+
will be used with order by clause). If not defined then
46+
django\_datatables\_view will look for 'name' in the columns
47+
definition provided in the request by DataTables, eg.: columnDefs:
48+
[{name: 'name', targets: [0]} (only works for datatables 1.10+)
49+
- **filter\_queryset** - if you want to filter your DataTable in some
50+
specific way then override this method. In case of older DataTables
51+
(pre 1.10) you need to override this method or there will be no
52+
filtering.
53+
- **filter\_method** - returns 'istartswith' by default, you can
54+
override it to use different filtering method, e.g. icontains: return
55+
self.FILTER\_ICONTAINS
4456

4557
For more advanced customisation you might want to override:
4658

@@ -52,8 +64,9 @@ For more advanced customisation you might want to override:
5264
not escape values returned from render\_column method
5365

5466
The code is rather simple so do not hesitate to have a look at it.
55-
Method that is executed first (and that calls other methods) is
56-
**get\_context\_data**
67+
Method that is executed first (and that calls other methods to execute
68+
whole logic) is **get\_context\_data**. Definitely have a look at this
69+
method!
5770

5871
See example below:
5972

@@ -92,12 +105,12 @@ See example below:
92105
# use parameters passed in GET request to filter queryset
93106
94107
# simple example:
95-
search = self.request.GET.get(u'search[value]', None)
108+
search = self.request.GET.get('search[value]', None)
96109
if search:
97110
qs = qs.filter(name__istartswith=search)
98111
99112
# more advanced example using extra parameters
100-
filter_customer = self.request.GET.get(u'customer', None)
113+
filter_customer = self.request.GET.get('customer', None)
101114
102115
if filter_customer:
103116
customer_parts = filter_customer.split(' ')
@@ -156,12 +169,12 @@ Another example of views.py customisation
156169
# use request parameters to filter queryset
157170
158171
# simple example:
159-
search = self.request.GET.get(u'search[value]', None)
172+
search = self.request.GET.get('search[value]', None)
160173
if search:
161174
qs = qs.filter(name__istartswith=search)
162175
163176
# more advanced example
164-
filter_customer = self.request.GET.get(u'customer', None)
177+
filter_customer = self.request.GET.get('customer', None)
165178
166179
if filter_customer:
167180
customer_parts = filter_customer.split(' ')
@@ -185,3 +198,41 @@ Another example of views.py customisation
185198
item.modified.strftime("%Y-%m-%d %H:%M:%S")
186199
])
187200
return json_data
201+
202+
Yet another example of views.py customisation
203+
---------------------------------------------
204+
205+
This sample assumes that list of columns and order columns is defined on
206+
the client side (DataTables), eg.:
207+
208+
.. code:: javascript
209+
210+
$(document).ready(function() {
211+
var dt_table = $('.datatable').dataTable({
212+
order: [[ 0, "desc" ]],
213+
columnDefs: [
214+
{
215+
name: 'name',
216+
orderable: true,
217+
searchable: true,
218+
targets: [0]
219+
},
220+
{
221+
name: 'description',
222+
orderable: true,
223+
searchable: true,
224+
targets: [1]
225+
}
226+
],
227+
searching: true,
228+
processing: true,
229+
serverSide: true,
230+
stateSave: true,
231+
ajax: TESTMODEL_LIST_JSON_URL
232+
});
233+
});
234+
235+
.. code:: python
236+
237+
class TestModelListJson(BaseDatatableView):
238+
model = TestModel

‎django_datatables_view/base_datatable_view.py

+95-13
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,100 @@ class DatatableMixin(object):
1414
"""
1515
model = None
1616
columns = []
17+
_columns = [] # internal cache for columns definition
1718
order_columns = []
1819
max_display_length = 100 # max limit of records returned, do not allow to kill our server by huge sets of data
1920
pre_camel_case_notation = False # datatables 1.10 changed query string parameter names
2021
none_string = ''
2122
escape_values = True # if set to true then values returned by render_column will be escaped
22-
23+
columns_data = []
24+
25+
FILTER_ISTARTSWITH = 'istartswith'
26+
FILTER_ICONTAINS = 'icontains'
27+
2328
@property
2429
def _querydict(self):
2530
if self.request.method == 'POST':
2631
return self.request.POST
2732
else:
2833
return self.request.GET
2934

35+
def get_filter_method(self):
36+
""" Returns preferred filter method """
37+
return self.FILTER_ISTARTSWITH
38+
3039
def initialize(self, *args, **kwargs):
40+
""" Determine which version of DataTables is being used - there are differences in parameters sent by
41+
DataTables < 1.10.x
42+
"""
3143
if 'iSortingCols' in self._querydict:
3244
self.pre_camel_case_notation = True
3345

3446
def get_order_columns(self):
35-
""" Return list of columns used for ordering
47+
""" Return list of columns used for ordering.
48+
By default returns self.order_columns but if these are not defined it tries to get columns
49+
from the request using the columns[i][name] attribute. This requires proper client side definition of
50+
columns, eg:
51+
columnDefs: [
52+
{
53+
name: 'username',
54+
orderable: true,
55+
targets: [0]
56+
},
57+
{
58+
name: 'email',
59+
orderable: false,
60+
targets: [1]
61+
}
62+
]
3663
"""
37-
return self.order_columns
64+
if self.order_columns or self.pre_camel_case_notation:
65+
return self.order_columns
66+
67+
# try to build list of order_columns using request data
68+
order_columns = []
69+
for column_def in self.columns_data:
70+
if column_def['name']:
71+
if column_def['orderable']:
72+
order_columns.append(column_def['name'])
73+
else:
74+
order_columns.append('')
75+
else:
76+
# fallback to columns
77+
order_columns = self._columns
78+
break
79+
80+
self.order_columns = order_columns
81+
return order_columns
3882

3983
def get_columns(self):
40-
""" Returns the list of columns that are returned in the result set
84+
""" Returns the list of columns to be returned in the result set.
85+
By default returns self.columns but if these are not defined it tries to get columns
86+
from the request using the columns[i][name] attribute. This requires proper client side definition of
87+
columns, eg:
88+
89+
columnDefs: [
90+
{
91+
name: 'username',
92+
targets: [0]
93+
},
94+
{
95+
name: 'email',
96+
targets: [1]
97+
}
98+
]
4199
"""
42-
return self.columns
100+
if self.columns or self.pre_camel_case_notation:
101+
return self.columns
102+
103+
columns = []
104+
for column_def in self.columns_data:
105+
if column_def['name']:
106+
columns.append(column_def['name'])
107+
else:
108+
raise Exception('self.columns is not defined and there is no \'name\' defined in columns definition!')
109+
110+
return columns
43111

44112
def render_column(self, row, column):
45113
""" Renders a column on a row. column can be given in a module notation eg. document.invoice.type
@@ -163,30 +231,33 @@ def extract_datatables_column_data(self):
163231
return col_data
164232

165233
def filter_queryset(self, qs):
166-
""" If search['value'] is provided then filter all searchable columns using istartswith
234+
""" If search['value'] is provided then filter all searchable columns using filter_method (istartswith
235+
by default).
236+
237+
Automatic filtering only works for Datatables 1.10+. For older versions override this method
167238
"""
168-
columns = self.get_columns()
239+
columns = self._columns
169240
if not self.pre_camel_case_notation:
170241
# get global search value
171242
search = self._querydict.get('search[value]', None)
172-
col_data = self.extract_datatables_column_data()
173243
q = Q()
174-
for col_no, col in enumerate(col_data):
244+
filter_method = self.get_filter_method()
245+
for col_no, col in enumerate(self.columns_data):
175246
# apply global search to all searchable columns
176247
if search and col['searchable']:
177-
q |= Q(**{'{0}__istartswith'.format(columns[col_no].replace('.', '__')): search})
248+
q |= Q(**{'{0}__{1}'.format(columns[col_no].replace('.', '__'), filter_method): search})
178249

179250
# column specific filter
180251
if col['search.value']:
181252
qs = qs.filter(**{
182-
'{0}__istartswith'.format(columns[col_no].replace('.', '__')): col['search.value']})
253+
'{0}__{1}'.format(columns[col_no].replace('.', '__'), filter_method): col['search.value']})
183254
qs = qs.filter(q)
184255
return qs
185256

186257
def prepare_results(self, qs):
187258
data = []
188259
for item in qs:
189-
data.append([self.render_column(item, column) for column in self.get_columns()])
260+
data.append([self.render_column(item, column) for column in self._columns])
190261
return data
191262

192263
def handle_exception(self, e):
@@ -197,17 +268,28 @@ def get_context_data(self, *args, **kwargs):
197268
try:
198269
self.initialize(*args, **kwargs)
199270

271+
# prepare columns data (for DataTables 1.10+)
272+
self.columns_data = self.extract_datatables_column_data()
273+
274+
# prepare list of columns to be returned
275+
self._columns = self.get_columns()
276+
277+
# prepare initial queryset
200278
qs = self.get_initial_queryset()
201279

202-
# number of records before filtering
280+
# store the total number of records (before filtering)
203281
total_records = qs.count()
204282

283+
# apply filters
205284
qs = self.filter_queryset(qs)
206285

207286
# number of records after filtering
208287
total_display_records = qs.count()
209288

289+
# apply ordering
210290
qs = self.ordering(qs)
291+
292+
# apply pagintion
211293
qs = self.paging(qs)
212294

213295
# prepare output data

0 commit comments

Comments
 (0)
Please sign in to comment.