Commit 5db900c6 by Tom Christie

First pass at HTML rendering for filters

parent f601c6c1
...@@ -9,6 +9,7 @@ from functools import reduce ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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):
""" """
......
...@@ -374,6 +374,7 @@ class BrowsableAPIRenderer(BaseRenderer): ...@@ -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): ...@@ -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): ...@@ -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,
......
...@@ -73,3 +73,11 @@ pre { ...@@ -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
}
...@@ -109,6 +109,13 @@ ...@@ -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 @@ ...@@ -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>
<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>
{% load crispy_forms_tags %}
<h2>Field search</h2>
{% crispy filter.form %}
<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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment