Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Filter HTML refinments
  • Loading branch information
lovelydinosaur committed Aug 27, 2015
commit aeb57913c9f2a7ad1ffc1db825c3e6f44b4818ee
8 changes: 8 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def distinct(queryset, base):
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
Expand Down
121 changes: 85 additions & 36 deletions rest_framework/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,57 @@
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):
Expand All @@ -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'
Expand All @@ -59,33 +99,11 @@ def get_filter_class(self, view, queryset=None):
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
Expand All @@ -111,6 +129,7 @@ def to_html(self, request, queryset, view):
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):
"""
Expand All @@ -134,7 +153,6 @@ def construct_search(self, field_name):

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:
Expand All @@ -158,6 +176,19 @@ def filter_queryset(self, request, queryset, view):
# 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.
Expand Down Expand Up @@ -200,19 +231,30 @@ def get_valid_fields(self, queryset, view):
"'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):
Expand All @@ -224,10 +266,17 @@ def filter_queryset(self, request, queryset, view):
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):
Expand Down
3 changes: 2 additions & 1 deletion rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,8 @@ def get_filter_form(self, view, request):
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
Expand Down
9 changes: 9 additions & 0 deletions rest_framework/templates/rest_framework/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -220,6 +227,8 @@ <h4 class="modal-title" id="myModalLabel">{{ error_title }}</h4>
</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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load crispy_forms_tags %}

<h2>Field filters</h2>
{% crispy filter.form %}
17 changes: 10 additions & 7 deletions rest_framework/templates/rest_framework/filters/ordering.html
Original file line number Diff line number Diff line change
@@ -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>
11 changes: 11 additions & 0 deletions rest_framework/templates/rest_framework/filters/search.html
Original file line number Diff line number Diff line change
@@ -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>