Skip to content

Commit

Permalink
Merge pull request #503 from carltongibson/develop
Browse files Browse the repository at this point in the history
0.15.1 Release
  • Loading branch information
Carlton Gibson authored Sep 28, 2016
2 parents a02db39 + d6d1652 commit a96f93a
Show file tree
Hide file tree
Showing 20 changed files with 288 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.15.0
current_version = 0.15.1
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
Expand Down
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version 0.15.1 (2016-09-28)
---------------------------

A couple of quick bug fixes:

* #496 OrderingFilter not working with Select widget

* #498 DRF Backend Templates not loading



Version 0.15.0 (2016-09-20)
---------------------------

Expand Down
2 changes: 1 addition & 1 deletion django_filters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .filterset import FilterSet
from .filters import *

__version__ = '0.15.0'
__version__ = '0.15.1'


def parse_version(version):
Expand Down
29 changes: 27 additions & 2 deletions django_filters/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils.translation import ugettext_lazy as _

from .utils import handle_timezone
from .widgets import RangeWidget, LookupTypeWidget, CSVWidget
from .widgets import RangeWidget, LookupTypeWidget, CSVWidget, BaseCSVWidget


class RangeField(forms.MultiValueField):
Expand Down Expand Up @@ -129,7 +129,28 @@ class IntegerCSVField(BaseCSVField, filters.IntegerField):
pass
"""
widget = CSVWidget
base_widget_class = BaseCSVWidget

def __init__(self, *args, **kwargs):
widget = kwargs.get('widget') or self.widget
kwargs['widget'] = self._get_widget_class(widget)

super(BaseCSVField, self).__init__(*args, **kwargs)

def _get_widget_class(self, widget):
# passthrough, allows for override
if isinstance(widget, BaseCSVWidget) or (
isinstance(widget, type) and
issubclass(widget, BaseCSVWidget)):
return widget

# complain since we are unable to reconstruct widget instances
assert isinstance(widget, type), \
"'%s.widget' must be a widget class, not %s." \
% (self.__class__.__name__, repr(widget))

bases = (self.base_widget_class, widget, )
return type(str('CSV%s' % widget.__name__), bases, {})

def clean(self, value):
if value is None:
Expand All @@ -138,6 +159,10 @@ def clean(self, value):


class BaseRangeField(BaseCSVField):
# Force use of text input, as range must always have two inputs. A date
# input would only allow a user to input one value and would always fail.
widget = CSVWidget

default_error_messages = {
'invalid_values': _('Range query expects two values.')
}
Expand Down
43 changes: 35 additions & 8 deletions django_filters/rest_framework/backends.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@

from __future__ import absolute_import

from django.template import loader
from django.template import Template, TemplateDoesNotExist, loader
from rest_framework.compat import template_render
from rest_framework.filters import BaseFilterBackend

from .. import compat
from . import filterset


CRISPY_TEMPLATE = """
{% load crispy_forms_tags %}
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
{% crispy filter.form %}
"""


FILTER_TEMPLATE = """
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
<form class="form" action="" method="get">
{{ filter.form.as_p }}
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
</form>
"""


if compat.is_crispy:
filter_template = 'django_filters/rest_framework/crispy_form.html'
template_path = 'django_filters/rest_framework/crispy_form.html'
template_default = Template(CRISPY_TEMPLATE)

else:
filter_template = 'django_filters/rest_framework/form.html'
template_path = 'django_filters/rest_framework/form.html'
template_default = Template(FILTER_TEMPLATE)


class DjangoFilterBackend(BaseFilterBackend):
default_filter_set = filterset.FilterSet
template = filter_template
template = template_path

def get_filter_class(self, view, queryset=None):
"""
Expand Down Expand Up @@ -57,8 +80,12 @@ def to_html(self, request, queryset, view):
if not filter_class:
return None
filter_instance = filter_class(request.query_params, queryset=queryset)
context = {

try:
template = loader.get_template(self.template)
except TemplateDoesNotExist:
template = template_default

return template_render(template, context={
'filter': filter_instance
}
template = loader.get_template(self.template)
return compat.template_render(template, context)
})

This file was deleted.

This file was deleted.

26 changes: 20 additions & 6 deletions django_filters/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ def value_from_datadict(self, data, files, name):
}.get(value, None)


class CSVWidget(forms.TextInput):
class BaseCSVWidget(forms.Widget):
def _isiterable(self, value):
return isinstance(value, Iterable) and not isinstance(value, string_types)

def value_from_datadict(self, data, files, name):
value = super(CSVWidget, self).value_from_datadict(data, files, name)
value = super(BaseCSVWidget, self).value_from_datadict(data, files, name)

if value is not None:
if value == '': # empty value should parse as an empty list
Expand All @@ -152,8 +152,22 @@ def value_from_datadict(self, data, files, name):
return None

def render(self, name, value, attrs=None):
if self._isiterable(value):
value = [force_text(format_value(self, v)) for v in value]
value = ','.join(list(value))
if not self._isiterable(value):
value = [value]

return super(CSVWidget, self).render(name, value, attrs)
if len(value) <= 1:
# delegate to main widget (Select, etc...) if not multiple values
value = value[0] if value else value
return super(BaseCSVWidget, self).render(name, value, attrs)

# if we have multiple values, we need to force render as a text input
# (otherwise, the additional values are lost)
surrogate = forms.TextInput()
value = [force_text(format_value(surrogate, v)) for v in value]
value = ','.join(list(value))

return surrogate.render(name, value, attrs)


class CSVWidget(BaseCSVWidget, forms.TextInput):
pass
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
# built documents.
#
# The short X.Y version.
version = '0.15.0'
version = '0.15.1'
# The full version, including alpha/beta/rc tags.
release = '0.15.0'
release = '0.15.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 2 additions & 2 deletions docs/ref/filters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ two additional arguments that are used to build the ordering choices.
('username', 'account'),
('first_name', 'first_name'),
('last_name', 'last_name'),
},
),

# labels do not need to retain order
field_labels={
Expand All @@ -686,7 +686,7 @@ two additional arguments that are used to build the ordering choices.
model = User
fields = ['first_name', 'last_name']

>>> UserFilter().filter['o'].field.choices
>>> UserFilter().filters['o'].field.choices
[
('account', 'User account'),
('-account', 'User account (descending)'),
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
readme = f.read()
f.close()

version = '0.15.0'
version = '0.15.1'

if sys.argv[-1] == 'publish':
if os.system("pip freeze | grep wheel"):
Expand Down
1 change: 1 addition & 0 deletions tests/rest_framework/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'tests.rest_framework.apps.RestFrameworkTestConfig'
8 changes: 8 additions & 0 deletions tests/rest_framework/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

from django.apps import AppConfig


class RestFrameworkTestConfig(AppConfig):
name = 'tests.rest_framework'
label = 'drf_test_app'
verbose_name = "Rest Framework Test App"
1 change: 1 addition & 0 deletions tests/rest_framework/templates/filter_template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test
47 changes: 47 additions & 0 deletions tests/rest_framework/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,53 @@ def test_unknown_filter(self):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_html_rendering(self):
"""
Make sure response renders w/ backend
"""
view = FilterFieldsRootView.as_view()
request = factory.get('/')
request.META['HTTP_ACCEPT'] = 'text/html'
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_backend_output(self):
"""
Ensure backend renders default if template path does not exist
"""
view = FilterFieldsRootView()
backend = view.filter_backends[0]
request = view.initialize_request(factory.get('/'))
html = backend().to_html(request, view.get_queryset(), view)

self.assertHTMLEqual(html, """
<h2>Field filters</h2>
<form class="form" action="" method="get">
<p>
<label for="id_decimal">Decimal:</label>
<input id="id_decimal" name="decimal" step="any" type="number" />
<span class="helptext">Filter</span>
</p>
<p>
<label for="id_date">Date:</label>
<input id="id_date" name="date" type="text" />
<span class="helptext">Filter</span>
</p>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
""")

def test_template_path(self):
view = FilterFieldsRootView()

class Backend(view.filter_backends[0]):
template = 'filter_template.html'

request = view.initialize_request(factory.get('/'))
html = Backend().to_html(request, view.get_queryset(), view)

self.assertHTMLEqual(html, "Test")


@override_settings(ROOT_URLCONF='tests.rest_framework.test_backends')
class IntegrationTestDetailFiltering(CommonFilteringTestCase):
Expand Down
9 changes: 6 additions & 3 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@

INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.staticfiles',
'django.contrib.auth',
'django_filters',
'tests',
'rest_framework',
'tests.rest_framework',
'tests',
)

MIDDLEWARE = []

ROOT_URLCONF = 'tests.urls'

USE_TZ = True
Expand All @@ -25,7 +28,7 @@
}]


MIDDLEWARE = []
STATIC_URL = '/static/'


# help verify that DEFAULTS is importable from conf.
Expand Down
23 changes: 20 additions & 3 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@

from datetime import datetime, time, timedelta, tzinfo
import decimal
import unittest

import django
from django import forms
from django.test import TestCase, override_settings
from django.utils.timezone import make_aware, get_default_timezone

from django_filters.widgets import RangeWidget
from django_filters.widgets import BaseCSVWidget, CSVWidget, RangeWidget
from django_filters.fields import (
Lookup, LookupTypeField, BaseCSVField, BaseRangeField, RangeField,
DateRangeField, DateTimeRangeField, TimeRangeField, IsoDateTimeField
Expand Down Expand Up @@ -196,6 +194,25 @@ def test_validation_error(self):
with self.assertRaises(forms.ValidationError):
self.field.clean(['a', 'b', 'c'])

def test_derived_widget(self):
with self.assertRaises(AssertionError) as excinfo:
BaseCSVField(widget=RangeWidget())

msg = str(excinfo.exception)
self.assertIn("'BaseCSVField.widget' must be a widget class", msg)
self.assertIn("RangeWidget", msg)

widget = CSVWidget()
field = BaseCSVField(widget=widget)
self.assertIs(field.widget, widget)

field = BaseCSVField(widget=CSVWidget)
self.assertIsInstance(field.widget, CSVWidget)

field = BaseCSVField(widget=forms.Select)
self.assertIsInstance(field.widget, forms.Select)
self.assertIsInstance(field.widget, BaseCSVWidget)


class BaseRangeFieldTests(TestCase):
def setUp(self):
Expand Down
Loading

0 comments on commit a96f93a

Please sign in to comment.