Skip to content

Commit aeb5791

Browse files
committed
Filter HTML refinments
1 parent ea630bf commit aeb5791

File tree

8 files changed

+134
-48
lines changed

8 files changed

+134
-48
lines changed

rest_framework/compat.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ def distinct(queryset, base):
8080
except ImportError:
8181
django_filters = None
8282

83+
84+
# django-crispy-forms is optional
85+
try:
86+
import crispy_forms
87+
except ImportError:
88+
crispy_forms = None
89+
90+
8391
if django.VERSION >= (1, 6):
8492
def clean_manytomany_helptext(text):
8593
return text

rest_framework/filters.py

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,57 @@
77
import operator
88
from functools import reduce
99

10+
from django.conf import settings
1011
from django.core.exceptions import ImproperlyConfigured
1112
from django.db import models
1213
from django.template import Context, Template, loader
1314
from django.utils import six
1415

1516
from rest_framework.compat import (
16-
distinct, django_filters, get_model_name, guardian
17+
crispy_forms, distinct, django_filters, get_model_name, guardian
1718
)
1819
from rest_framework.settings import api_settings
1920

20-
FilterSet = django_filters and django_filters.FilterSet or None
21+
22+
if 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms and django_filters:
23+
# If django-crispy-forms is installed, use it to get a bootstrap3 rendering
24+
# of the DjangoFilterBackend controls when displayed as HTML.
25+
from crispy_forms.helper import FormHelper
26+
from crispy_forms.layout import Field, Fieldset, Layout, Submit
27+
28+
class FilterSet(django_filters.FilterSet):
29+
def __init__(self, *args, **kwargs):
30+
super(FilterSet, self).__init__(*args, **kwargs)
31+
for field in self.form.fields.values():
32+
field.help_text = None
33+
34+
layout_components = list(self.form.fields.keys()) + [
35+
Submit('', 'Submit', css_class='btn-default'),
36+
]
37+
38+
helper = FormHelper()
39+
helper.form_method = 'GET'
40+
helper.template_pack = 'bootstrap3'
41+
helper.layout = Layout(*layout_components)
42+
43+
self.form.helper = helper
44+
45+
filter_template = 'rest_framework/filters/django_filter_crispyforms.html'
46+
47+
elif django_filters:
48+
# If django-crispy-forms is not installed, use the standard
49+
# 'form.as_p' rendering when DjangoFilterBackend is displayed as HTML.
50+
class FilterSet(django_filters.FilterSet):
51+
def __init__(self, *args, **kwargs):
52+
super(FilterSet, self).__init__(*args, **kwargs)
53+
for field in self.form.fields.values():
54+
field.help_text = None
55+
56+
filter_template = 'rest_framework/filters/django_filter.html'
57+
58+
else:
59+
FilterSet = None
60+
filter_template = None
2161

2262

2363
class BaseFilterBackend(object):
@@ -37,7 +77,7 @@ class DjangoFilterBackend(BaseFilterBackend):
3777
A filter backend that uses django-filter.
3878
"""
3979
default_filter_set = FilterSet
40-
template = 'rest_framework/filters/django_filter.html'
80+
template = filter_template
4181

4282
def __init__(self):
4383
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
@@ -59,33 +99,11 @@ def get_filter_class(self, view, queryset=None):
5999
return filter_class
60100

61101
if filter_fields:
62-
from crispy_forms.helper import FormHelper
63-
from crispy_forms.layout import Field, Fieldset, Layout, Submit
64-
65-
class AutoFilterSet(self.default_filter_set):
102+
class AutoFilterSet(FilterSet):
66103
class Meta:
67104
model = queryset.model
68105
fields = filter_fields
69106

70-
@property
71-
def form(self):
72-
self._form = super(AutoFilterSet, self).form
73-
for field in self._form.fields.values():
74-
field.help_text = None
75-
layout_components = filter_fields + [
76-
Submit('', 'Apply', css_class='btn-default'),
77-
]
78-
79-
helper = FormHelper()
80-
helper.form_method = 'get'
81-
helper.form_action = '.'
82-
helper.template_pack = 'bootstrap3'
83-
84-
helper.layout = Layout(*layout_components)
85-
86-
self._form.helper = helper
87-
return self._form
88-
89107
return AutoFilterSet
90108

91109
return None
@@ -111,6 +129,7 @@ def to_html(self, request, queryset, view):
111129
class SearchFilter(BaseFilterBackend):
112130
# The URL query parameter used for the search.
113131
search_param = api_settings.SEARCH_PARAM
132+
template = 'rest_framework/filters/search.html'
114133

115134
def get_search_terms(self, request):
116135
"""
@@ -134,7 +153,6 @@ def construct_search(self, field_name):
134153

135154
def filter_queryset(self, request, queryset, view):
136155
search_fields = getattr(view, 'search_fields', None)
137-
138156
search_terms = self.get_search_terms(request)
139157

140158
if not search_fields or not search_terms:
@@ -158,6 +176,19 @@ def filter_queryset(self, request, queryset, view):
158176
# in the resulting queryset.
159177
return distinct(queryset, base)
160178

179+
def to_html(self, request, queryset, view):
180+
if not getattr(view, 'search_fields', None):
181+
return ''
182+
183+
term = self.get_search_terms(request)
184+
term = term[0] if term else ''
185+
context = Context({
186+
'param': self.search_param,
187+
'term': term
188+
})
189+
template = loader.get_template(self.template)
190+
return template.render(context)
191+
161192

162193
class OrderingFilter(BaseFilterBackend):
163194
# The URL query parameter used for the ordering.
@@ -200,19 +231,30 @@ def get_valid_fields(self, queryset, view):
200231
"'serializer_class' or 'ordering_fields' attribute.")
201232
raise ImproperlyConfigured(msg % self.__class__.__name__)
202233
valid_fields = [
203-
field.source or field_name
234+
(field.source or field_name, field.label)
204235
for field_name, field in serializer_class().fields.items()
205-
if not getattr(field, 'write_only', False)
236+
if not getattr(field, 'write_only', False) and not field.source == '*'
206237
]
207238
elif valid_fields == '__all__':
208239
# View explicitly allows filtering on any model field
209-
valid_fields = [field.name for field in queryset.model._meta.fields]
210-
valid_fields += queryset.query.aggregates.keys()
240+
valid_fields = [
241+
(field.name, field.label)
242+
for field in queryset.model._meta.fields
243+
]
244+
valid_fields += [
245+
(key, key.title().split('__'))
246+
for key in queryset.query.aggregates.keys()
247+
]
248+
else:
249+
valid_fields = [
250+
(item, item) if isinstance(item, six.string_types) else item
251+
for item in valid_fields
252+
]
211253

212254
return valid_fields
213255

214256
def remove_invalid_fields(self, queryset, fields, view):
215-
valid_fields = self.get_valid_fields(queryset, view)
257+
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view)]
216258
return [term for term in fields if term.lstrip('-') in valid_fields]
217259

218260
def filter_queryset(self, request, queryset, view):
@@ -224,10 +266,17 @@ def filter_queryset(self, request, queryset, view):
224266
return queryset
225267

226268
def get_template_context(self, request, queryset, view):
227-
#default_tuple = self.get_default_ordering()
228-
#default = None if default_tuple is None else default_tuple[0]
229-
{
230-
'options': self.get_valid_fields(queryset, view),
269+
current = self.get_ordering(request, queryset, view)
270+
current = None if current is None else current[0]
271+
options = []
272+
for key, label in self.get_valid_fields(queryset, view):
273+
options.append((key, '%s - ascending' % label))
274+
options.append(('-' + key, '%s - descending' % label))
275+
return {
276+
'request': request,
277+
'current': current,
278+
'param': self.ordering_param,
279+
'options': options,
231280
}
232281

233282
def to_html(self, request, queryset, view):

rest_framework/renderers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@ def get_filter_form(self, view, request):
610610
for backend in view.filter_backends:
611611
if hasattr(backend, 'to_html'):
612612
html = backend().to_html(request, queryset, view)
613-
elements.append(html)
613+
if html:
614+
elements.append(html)
614615

615616
if not elements:
616617
return

rest_framework/templates/rest_framework/admin.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@
113113
</form>
114114
{% endif %}
115115

116+
{% if filter_form %}
117+
<button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
118+
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
119+
Filters
120+
</button>
121+
{% endif %}
122+
116123
<div class="content-main">
117124
<div class="page-header">
118125
<h1>{{ name }}</h1>
@@ -220,6 +227,8 @@ <h4 class="modal-title" id="myModalLabel">{{ error_title }}</h4>
220227
</div>
221228
{% endif %}
222229

230+
{% if filter_form %}{{ filter_form }}{% endif %}
231+
223232
{% block script %}
224233
<script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
225234
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
{% load crispy_forms_tags %}
2-
3-
<h2>Field search</h2>
4-
{% crispy filter.form %}
1+
<h2>Field filters</h2>
2+
<form class="form" action="" method="get">
3+
{{ filter.form.as_p }}
4+
<button type="submit" class="btn btn-primary">Submit</button>
5+
</form>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{% load crispy_forms_tags %}
2+
3+
<h2>Field filters</h2>
4+
{% crispy filter.form %}
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
{% load rest_framework %}
12
<h2>Ordering</h2>
23
<div class="list-group">
3-
<a href="." class="list-group-item active">
4-
Most recently created
5-
<span class="glyphicon glyphicon-ok" style="float: right" aria-hidden="true"></span>
6-
</a>
7-
<a href="." class="list-group-item">Least recently created</a>
8-
<a href="." class="list-group-item">Username ascending</a>
9-
<a href="." class="list-group-item">Username descending</a>
4+
{% for key, label in options %}
5+
{% if key == current %}
6+
<a href="{% add_query_param request param key %}" class="list-group-item active">
7+
<span class="glyphicon glyphicon-ok" style="float: right" aria-hidden="true"></span> {{ label }}
8+
</a>
9+
{% else %}
10+
<a href="{% add_query_param request param key %}" class="list-group-item">{{ label }}</a>
11+
{% endif %}
12+
{% endfor %}
1013
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<h2>Search</h2>
2+
<form class="form-inline">
3+
<div class="form-group">
4+
<div class="input-group">
5+
<input type="text" class="form-control" style="width: 350px" name="{{ param }}" value="{{ term }}">
6+
<span class="input-group-btn">
7+
<button class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search" aria-hidden="true"></span> Search</button>
8+
</span>
9+
</div>
10+
</div>
11+
</form>

0 commit comments

Comments
 (0)