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
Next Next commit
First pass at HTML rendering for filters
  • Loading branch information
lovelydinosaur committed Aug 21, 2015
commit 5db900c625e5202eba52f375e6544fddd763f590
52 changes: 51 additions & 1 deletion rest_framework/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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 (
Expand Down Expand Up @@ -36,6 +37,7 @@ class DjangoFilterBackend(BaseFilterBackend):
A filter backend that uses django-filter.
"""
default_filter_set = FilterSet
template = 'rest_framework/filters/django_filter.html'

def __init__(self):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
Expand All @@ -57,11 +59,33 @@ 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 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 @@ -74,6 +98,15 @@ def filter_queryset(self, request, queryset, view):

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):
# The URL query parameter used for the search.
Expand Down Expand Up @@ -127,6 +160,7 @@ class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering.
ordering_param = api_settings.ORDERING_PARAM
ordering_fields = None
template = 'rest_framework/filters/ordering.html'

def get_ordering(self, request, queryset, view):
"""
Expand All @@ -152,7 +186,7 @@ def get_default_ordering(self, view):
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)

if valid_fields is None:
Expand All @@ -172,6 +206,10 @@ def remove_invalid_fields(self, queryset, fields, view):
valid_fields = [field.name for field in queryset.model._meta.fields]
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]

def filter_queryset(self, request, queryset, view):
Expand All @@ -182,6 +220,18 @@ 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),
}

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):
"""
Expand Down
21 changes: 21 additions & 0 deletions rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ class BrowsableAPIRenderer(BaseRenderer):
media_type = 'text/html'
format = 'api'
template = 'rest_framework/api.html'
filter_template = 'rest_framework/filters/base.html'
charset = 'utf-8'
form_renderer_class = HTMLFormRenderer

Expand Down Expand Up @@ -600,6 +601,24 @@ def get_description(self, view, status_code):
def get_breadcrumbs(self, 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):
"""
Returns the context used to render.
Expand Down Expand Up @@ -647,6 +666,8 @@ def get_context(self, data, accepted_media_type, renderer_context):
'delete_form': self.get_rendered_html_form(data, view, 'DELETE', 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_post_form': raw_data_post_form,
'raw_data_patch_form': raw_data_patch_form,
Expand Down
8 changes: 8 additions & 0 deletions rest_framework/static/rest_framework/css/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,11 @@ pre {
border-bottom: none;
padding-bottom: 0px;
}

#filtersModal form input[type=submit] {
width: auto;
}

#filtersModal .modal-body h2 {
margin-top: 0
}
12 changes: 12 additions & 0 deletions rest_framework/templates/rest_framework/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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 @@ -242,6 +249,11 @@ <h1>{{ name }}</h1>
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
<script src="{% static "rest_framework/js/default.js" %}"></script>
{% endblock %}

{% if filter_form %}
{{ filter_form }}
{% endif %}

</body>
{% endblock %}
</html>
16 changes: 16 additions & 0 deletions rest_framework/templates/rest_framework/filters/base.html
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load crispy_forms_tags %}

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