Filter HTML refinments

This commit is contained in:
Tom Christie 2015-08-27 14:25:44 +01:00
parent ea630bf3d1
commit aeb57913c9
8 changed files with 134 additions and 48 deletions

View File

@ -80,6 +80,14 @@ try:
except ImportError:
django_filters = None
# django-crispy-forms is optional
try:
import crispy_forms
except ImportError:
crispy_forms = None
if django.VERSION >= (1, 6):
def clean_manytomany_helptext(text):
return text

View File

@ -7,17 +7,57 @@ from __future__ import unicode_literals
import operator
from functools import reduce
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.template import Context, Template, loader
from django.utils import six
from rest_framework.compat import (
distinct, django_filters, get_model_name, guardian
crispy_forms, distinct, django_filters, get_model_name, guardian
)
from rest_framework.settings import api_settings
FilterSet = django_filters and django_filters.FilterSet or None
if 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms and django_filters:
# If django-crispy-forms is installed, use it to get a bootstrap3 rendering
# of the DjangoFilterBackend controls when displayed as HTML.
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Fieldset, Layout, Submit
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
layout_components = list(self.form.fields.keys()) + [
Submit('', 'Submit', css_class='btn-default'),
]
helper = FormHelper()
helper.form_method = 'GET'
helper.template_pack = 'bootstrap3'
helper.layout = Layout(*layout_components)
self.form.helper = helper
filter_template = 'rest_framework/filters/django_filter_crispyforms.html'
elif django_filters:
# If django-crispy-forms is not installed, use the standard
# 'form.as_p' rendering when DjangoFilterBackend is displayed as HTML.
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
filter_template = 'rest_framework/filters/django_filter.html'
else:
FilterSet = None
filter_template = None
class BaseFilterBackend(object):
@ -37,7 +77,7 @@ class DjangoFilterBackend(BaseFilterBackend):
A filter backend that uses django-filter.
"""
default_filter_set = FilterSet
template = 'rest_framework/filters/django_filter.html'
template = filter_template
def __init__(self):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
@ -59,33 +99,11 @@ class DjangoFilterBackend(BaseFilterBackend):
return filter_class
if filter_fields:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Fieldset, Layout, Submit
class AutoFilterSet(self.default_filter_set):
class AutoFilterSet(FilterSet):
class Meta:
model = queryset.model
fields = filter_fields
@property
def form(self):
self._form = super(AutoFilterSet, self).form
for field in self._form.fields.values():
field.help_text = None
layout_components = filter_fields + [
Submit('', 'Apply', css_class='btn-default'),
]
helper = FormHelper()
helper.form_method = 'get'
helper.form_action = '.'
helper.template_pack = 'bootstrap3'
helper.layout = Layout(*layout_components)
self._form.helper = helper
return self._form
return AutoFilterSet
return None
@ -111,6 +129,7 @@ class DjangoFilterBackend(BaseFilterBackend):
class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search.
search_param = api_settings.SEARCH_PARAM
template = 'rest_framework/filters/search.html'
def get_search_terms(self, request):
"""
@ -134,7 +153,6 @@ class SearchFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
@ -158,6 +176,19 @@ class SearchFilter(BaseFilterBackend):
# in the resulting queryset.
return distinct(queryset, base)
def to_html(self, request, queryset, view):
if not getattr(view, 'search_fields', None):
return ''
term = self.get_search_terms(request)
term = term[0] if term else ''
context = Context({
'param': self.search_param,
'term': term
})
template = loader.get_template(self.template)
return template.render(context)
class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering.
@ -200,19 +231,30 @@ class OrderingFilter(BaseFilterBackend):
"'serializer_class' or 'ordering_fields' attribute.")
raise ImproperlyConfigured(msg % self.__class__.__name__)
valid_fields = [
field.source or field_name
(field.source or field_name, field.label)
for field_name, field in serializer_class().fields.items()
if not getattr(field, 'write_only', False)
if not getattr(field, 'write_only', False) and not field.source == '*'
]
elif valid_fields == '__all__':
# View explicitly allows filtering on any model field
valid_fields = [field.name for field in queryset.model._meta.fields]
valid_fields += queryset.query.aggregates.keys()
valid_fields = [
(field.name, field.label)
for field in queryset.model._meta.fields
]
valid_fields += [
(key, key.title().split('__'))
for key in queryset.query.aggregates.keys()
]
else:
valid_fields = [
(item, item) if isinstance(item, six.string_types) else item
for item in valid_fields
]
return valid_fields
def remove_invalid_fields(self, queryset, fields, view):
valid_fields = self.get_valid_fields(queryset, view)
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view)]
return [term for term in fields if term.lstrip('-') in valid_fields]
def filter_queryset(self, request, queryset, view):
@ -224,10 +266,17 @@ class OrderingFilter(BaseFilterBackend):
return queryset
def get_template_context(self, request, queryset, view):
#default_tuple = self.get_default_ordering()
#default = None if default_tuple is None else default_tuple[0]
{
'options': self.get_valid_fields(queryset, view),
current = self.get_ordering(request, queryset, view)
current = None if current is None else current[0]
options = []
for key, label in self.get_valid_fields(queryset, view):
options.append((key, '%s - ascending' % label))
options.append(('-' + key, '%s - descending' % label))
return {
'request': request,
'current': current,
'param': self.ordering_param,
'options': options,
}
def to_html(self, request, queryset, view):

View File

@ -610,7 +610,8 @@ class BrowsableAPIRenderer(BaseRenderer):
for backend in view.filter_backends:
if hasattr(backend, 'to_html'):
html = backend().to_html(request, queryset, view)
elements.append(html)
if html:
elements.append(html)
if not elements:
return

View File

@ -113,6 +113,13 @@
</form>
{% endif %}
{% if filter_form %}
<button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
Filters
</button>
{% endif %}
<div class="content-main">
<div class="page-header">
<h1>{{ name }}</h1>
@ -220,6 +227,8 @@
</div>
{% endif %}
{% if filter_form %}{{ filter_form }}{% endif %}
{% block script %}
<script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>

View File

@ -1,4 +1,5 @@
{% load crispy_forms_tags %}
<h2>Field search</h2>
{% crispy filter.form %}
<h2>Field filters</h2>
<form class="form" action="" method="get">
{{ filter.form.as_p }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>

View File

@ -0,0 +1,4 @@
{% load crispy_forms_tags %}
<h2>Field filters</h2>
{% crispy filter.form %}

View File

@ -1,10 +1,13 @@
{% load rest_framework %}
<h2>Ordering</h2>
<div class="list-group">
<a href="." class="list-group-item active">
Most recently created
<span class="glyphicon glyphicon-ok" style="float: right" aria-hidden="true"></span>
</a>
<a href="." class="list-group-item">Least recently created</a>
<a href="." class="list-group-item">Username ascending</a>
<a href="." class="list-group-item">Username descending</a>
{% for key, label in options %}
{% if key == current %}
<a href="{% add_query_param request param key %}" class="list-group-item active">
<span class="glyphicon glyphicon-ok" style="float: right" aria-hidden="true"></span> {{ label }}
</a>
{% else %}
<a href="{% add_query_param request param key %}" class="list-group-item">{{ label }}</a>
{% endif %}
{% endfor %}
</div>

View File

@ -0,0 +1,11 @@
<h2>Search</h2>
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" style="width: 350px" name="{{ param }}" value="{{ term }}">
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search" aria-hidden="true"></span> Search</button>
</span>
</div>
</div>
</form>