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: except ImportError:
django_filters = None django_filters = None
# django-crispy-forms is optional
try:
import crispy_forms
except ImportError:
crispy_forms = None
if django.VERSION >= (1, 6): if django.VERSION >= (1, 6):
def clean_manytomany_helptext(text): def clean_manytomany_helptext(text):
return text return text

View File

@ -7,17 +7,57 @@ from __future__ import unicode_literals
import operator import operator
from functools import reduce from functools import reduce
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.template import Context, Template, loader from django.template import Context, Template, loader
from django.utils import six from django.utils import six
from rest_framework.compat import ( 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 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): class BaseFilterBackend(object):
@ -37,7 +77,7 @@ class DjangoFilterBackend(BaseFilterBackend):
A filter backend that uses django-filter. A filter backend that uses django-filter.
""" """
default_filter_set = FilterSet default_filter_set = FilterSet
template = 'rest_framework/filters/django_filter.html' template = filter_template
def __init__(self): def __init__(self):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed' assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
@ -59,33 +99,11 @@ class DjangoFilterBackend(BaseFilterBackend):
return filter_class return filter_class
if filter_fields: if filter_fields:
from crispy_forms.helper import FormHelper class AutoFilterSet(FilterSet):
from crispy_forms.layout import Field, Fieldset, Layout, Submit
class AutoFilterSet(self.default_filter_set):
class Meta: class Meta:
model = queryset.model model = queryset.model
fields = filter_fields 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 AutoFilterSet
return None return None
@ -111,6 +129,7 @@ class DjangoFilterBackend(BaseFilterBackend):
class SearchFilter(BaseFilterBackend): class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search. # The URL query parameter used for the search.
search_param = api_settings.SEARCH_PARAM search_param = api_settings.SEARCH_PARAM
template = 'rest_framework/filters/search.html'
def get_search_terms(self, request): def get_search_terms(self, request):
""" """
@ -134,7 +153,6 @@ class SearchFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None) search_fields = getattr(view, 'search_fields', None)
search_terms = self.get_search_terms(request) search_terms = self.get_search_terms(request)
if not search_fields or not search_terms: if not search_fields or not search_terms:
@ -158,6 +176,19 @@ class SearchFilter(BaseFilterBackend):
# in the resulting queryset. # in the resulting queryset.
return distinct(queryset, base) 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): class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering. # The URL query parameter used for the ordering.
@ -200,19 +231,30 @@ class OrderingFilter(BaseFilterBackend):
"'serializer_class' or 'ordering_fields' attribute.") "'serializer_class' or 'ordering_fields' attribute.")
raise ImproperlyConfigured(msg % self.__class__.__name__) raise ImproperlyConfigured(msg % self.__class__.__name__)
valid_fields = [ valid_fields = [
field.source or field_name (field.source or field_name, field.label)
for field_name, field in serializer_class().fields.items() 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__': elif valid_fields == '__all__':
# View explicitly allows filtering on any model field # View explicitly allows filtering on any model field
valid_fields = [field.name for field in queryset.model._meta.fields] valid_fields = [
valid_fields += queryset.query.aggregates.keys() (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 return valid_fields
def remove_invalid_fields(self, queryset, fields, view): 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] return [term for term in fields if term.lstrip('-') in valid_fields]
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
@ -224,10 +266,17 @@ class OrderingFilter(BaseFilterBackend):
return queryset return queryset
def get_template_context(self, request, queryset, view): def get_template_context(self, request, queryset, view):
#default_tuple = self.get_default_ordering() current = self.get_ordering(request, queryset, view)
#default = None if default_tuple is None else default_tuple[0] current = None if current is None else current[0]
{ options = []
'options': self.get_valid_fields(queryset, view), 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): def to_html(self, request, queryset, view):

View File

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

View File

@ -113,6 +113,13 @@
</form> </form>
{% endif %} {% 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="content-main">
<div class="page-header"> <div class="page-header">
<h1>{{ name }}</h1> <h1>{{ name }}</h1>
@ -220,6 +227,8 @@
</div> </div>
{% endif %} {% endif %}
{% if filter_form %}{{ filter_form }}{% endif %}
{% block script %} {% block script %}
<script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script> <script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
<script src="{% static "rest_framework/js/bootstrap.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 filters</h2>
<form class="form" action="" method="get">
<h2>Field search</h2> {{ filter.form.as_p }}
{% crispy filter.form %} <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> <h2>Ordering</h2>
<div class="list-group"> <div class="list-group">
<a href="." class="list-group-item active"> {% for key, label in options %}
Most recently created {% if key == current %}
<span class="glyphicon glyphicon-ok" style="float: right" aria-hidden="true"></span> <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> </a>
<a href="." class="list-group-item">Least recently created</a> {% else %}
<a href="." class="list-group-item">Username ascending</a> <a href="{% add_query_param request param key %}" class="list-group-item">{{ label }}</a>
<a href="." class="list-group-item">Username descending</a> {% endif %}
{% endfor %}
</div> </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>