First pass at HTML rendering for filters

This commit is contained in:
Tom Christie 2015-08-21 16:13:52 +01:00
parent f601c6c1c3
commit 5db900c625
7 changed files with 122 additions and 1 deletions

View File

@ -9,6 +9,7 @@ from functools import reduce
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.utils import six from django.utils import six
from rest_framework.compat import ( from rest_framework.compat import (
@ -36,6 +37,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'
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'
@ -57,11 +59,33 @@ class DjangoFilterBackend(BaseFilterBackend):
return filter_class return filter_class
if filter_fields: 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(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
@ -74,6 +98,15 @@ class DjangoFilterBackend(BaseFilterBackend):
return queryset return queryset
def to_html(self, request, queryset, view):
cls = self.get_filter_class(view, queryset)
filter_instance = cls(request.query_params, queryset=queryset)
context = Context({
'filter': filter_instance
})
template = loader.get_template(self.template)
return template.render(context)
class SearchFilter(BaseFilterBackend): class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search. # The URL query parameter used for the search.
@ -127,6 +160,7 @@ class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering. # The URL query parameter used for the ordering.
ordering_param = api_settings.ORDERING_PARAM ordering_param = api_settings.ORDERING_PARAM
ordering_fields = None ordering_fields = None
template = 'rest_framework/filters/ordering.html'
def get_ordering(self, request, queryset, view): def get_ordering(self, request, queryset, view):
""" """
@ -152,7 +186,7 @@ class OrderingFilter(BaseFilterBackend):
return (ordering,) return (ordering,)
return ordering return ordering
def remove_invalid_fields(self, queryset, fields, view): def get_valid_fields(self, queryset, view):
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields) valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
if valid_fields is None: if valid_fields is None:
@ -172,6 +206,10 @@ class OrderingFilter(BaseFilterBackend):
valid_fields = [field.name for field in queryset.model._meta.fields] valid_fields = [field.name for field in queryset.model._meta.fields]
valid_fields += queryset.query.aggregates.keys() valid_fields += queryset.query.aggregates.keys()
return valid_fields
def remove_invalid_fields(self, queryset, fields, view):
valid_fields = 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):
@ -182,6 +220,18 @@ class OrderingFilter(BaseFilterBackend):
return queryset 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),
}
def to_html(self, request, queryset, view):
template = loader.get_template(self.template)
context = Context(self.get_template_context(request, queryset, view))
return template.render(context)
class DjangoObjectPermissionsFilter(BaseFilterBackend): class DjangoObjectPermissionsFilter(BaseFilterBackend):
""" """

View File

@ -374,6 +374,7 @@ class BrowsableAPIRenderer(BaseRenderer):
media_type = 'text/html' media_type = 'text/html'
format = 'api' format = 'api'
template = 'rest_framework/api.html' template = 'rest_framework/api.html'
filter_template = 'rest_framework/filters/base.html'
charset = 'utf-8' charset = 'utf-8'
form_renderer_class = HTMLFormRenderer form_renderer_class = HTMLFormRenderer
@ -600,6 +601,24 @@ class BrowsableAPIRenderer(BaseRenderer):
def get_breadcrumbs(self, request): def get_breadcrumbs(self, request):
return get_breadcrumbs(request.path, request) return get_breadcrumbs(request.path, request)
def get_filter_form(self, view, request):
if not hasattr(view, 'get_queryset') or not hasattr(view, 'filter_backends'):
return
queryset = view.get_queryset()
elements = []
for backend in view.filter_backends:
if hasattr(backend, 'to_html'):
html = backend().to_html(request, queryset, view)
elements.append(html)
if not elements:
return
template = loader.get_template(self.filter_template)
context = Context({'elements': elements})
return template.render(context)
def get_context(self, data, accepted_media_type, renderer_context): def get_context(self, data, accepted_media_type, renderer_context):
""" """
Returns the context used to render. Returns the context used to render.
@ -647,6 +666,8 @@ class BrowsableAPIRenderer(BaseRenderer):
'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request), 'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request),
'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request), 'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request),
'filter_form': self.get_filter_form(view, request),
'raw_data_put_form': raw_data_put_form, 'raw_data_put_form': raw_data_put_form,
'raw_data_post_form': raw_data_post_form, 'raw_data_post_form': raw_data_post_form,
'raw_data_patch_form': raw_data_patch_form, 'raw_data_patch_form': raw_data_patch_form,

View File

@ -73,3 +73,11 @@ pre {
border-bottom: none; border-bottom: none;
padding-bottom: 0px; padding-bottom: 0px;
} }
#filtersModal form input[type=submit] {
width: auto;
}
#filtersModal .modal-body h2 {
margin-top: 0
}

View File

@ -109,6 +109,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>
@ -242,6 +249,11 @@
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script> <script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
<script src="{% static "rest_framework/js/default.js" %}"></script> <script src="{% static "rest_framework/js/default.js" %}"></script>
{% endblock %} {% endblock %}
{% if filter_form %}
{{ filter_form }}
{% endif %}
</body> </body>
{% endblock %} {% endblock %}
</html> </html>

View File

@ -0,0 +1,16 @@
<div class="modal fade" id="filtersModal" tabindex="-1" role="dialog" aria-labelledby="filters" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Filters</h4>
</div>
<div class="modal-body">
{% for element in elements %}
{% if not forloop.first %}<hr/>{% endif %}
{{ element }}
{% endfor %}
</div>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,10 @@
<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>
</div>