Merge pull request #501 from rpkilby/fix-ordering-select

Fix ordering select widget
This commit is contained in:
Carlton Gibson 2016-09-27 20:14:12 +02:00 committed by GitHub
commit 153d25a29d
6 changed files with 172 additions and 12 deletions

View File

@ -11,7 +11,7 @@ from django.utils.encoding import force_str
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):
@ -129,7 +129,28 @@ class BaseCSVField(forms.Field):
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:
@ -138,6 +159,10 @@ class BaseCSVField(forms.Field):
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.')
}

View File

@ -138,12 +138,12 @@ class BooleanWidget(forms.Select):
}.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
@ -152,8 +152,22 @@ class CSVWidget(forms.TextInput):
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

View File

@ -3,14 +3,12 @@ from __future__ import unicode_literals
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
@ -196,6 +194,25 @@ class BaseCSVFieldTests(TestCase):
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):

View File

@ -6,6 +6,7 @@ import mock
import unittest
import django
from django import forms
from django.test import TestCase, override_settings
from django.utils import six
from django.utils.timezone import now
@ -24,6 +25,7 @@ from django_filters.filters import DurationFilter
from django_filters.filters import MultipleChoiceFilter
from django_filters.filters import ModelMultipleChoiceFilter
from django_filters.filters import NumberFilter
from django_filters.filters import OrderingFilter
from django_filters.filters import RangeFilter
from django_filters.filters import TimeRangeFilter
# from django_filters.widgets import LinkWidget
@ -1586,6 +1588,46 @@ class CSVFilterTests(TestCase):
self.assertEqual(f.qs.count(), 2)
class OrderingFilterTests(TestCase):
def setUp(self):
User.objects.create(username='alex', status=1)
User.objects.create(username='jacob', status=2)
User.objects.create(username='aaron', status=2)
User.objects.create(username='carl', status=0)
def test_ordering(self):
class F(FilterSet):
o = OrderingFilter(
fields=('username', )
)
class Meta:
model = User
fields = ['username']
qs = User.objects.all()
f = F({'o': 'username'}, queryset=qs)
names = f.qs.values_list('username', flat=True)
self.assertEqual(list(names), ['aaron', 'alex', 'carl', 'jacob'])
def test_ordering_with_select_widget(self):
class F(FilterSet):
o = OrderingFilter(
widget=forms.Select,
fields=('username', )
)
class Meta:
model = User
fields = ['username']
qs = User.objects.all()
f = F({'o': 'username'}, queryset=qs)
names = f.qs.values_list('username', flat=True)
self.assertEqual(list(names), ['aaron', 'alex', 'carl', 'jacob'])
class MiscFilterSetTests(TestCase):
def setUp(self):

View File

@ -9,7 +9,7 @@ import warnings
from django import forms
from django.test import TestCase, override_settings
from django_filters import filters
from django_filters import filters, widgets
from django_filters.fields import (
Lookup,
RangeField,
@ -1097,3 +1097,10 @@ class OrderingFilterTests(TestCase):
with self.assertRaises(AssertionError) as ctx:
f([0, 1, 2])
self.assertEqual(str(ctx.exception), "'fields' must contain strings or (field name, param name) pairs.")
def test_widget(self):
f = OrderingFilter()
widget = f.field.widget
self.assertIsInstance(widget, widgets.BaseCSVWidget)
self.assertIsInstance(widget, forms.Select)

View File

@ -5,6 +5,7 @@ from django.test import TestCase
from django.forms import TextInput, Select
from django_filters.widgets import BooleanWidget
from django_filters.widgets import BaseCSVWidget
from django_filters.widgets import CSVWidget
from django_filters.widgets import RangeWidget
from django_filters.widgets import LinkWidget
@ -182,6 +183,9 @@ class CSVWidgetTests(TestCase):
self.assertHTMLEqual(w.render('price', ''), """
<input type="text" name="price" />""")
self.assertHTMLEqual(w.render('price', '1'), """
<input type="text" name="price" value="1" />""")
self.assertHTMLEqual(w.render('price', '1,2'), """
<input type="text" name="price" value="1,2" />""")
@ -224,3 +228,54 @@ class CSVWidgetTests(TestCase):
result = w.value_from_datadict({}, {}, 'price')
self.assertEqual(result, None)
class CSVSelectTests(TestCase):
class CSVSelect(BaseCSVWidget, Select):
pass
def test_widget(self):
w = self.CSVSelect(choices=((1, 'a'), (2, 'b')))
self.assertHTMLEqual(
w.render('price', None),
"""
<select name="price">
<option value="1">a</option>
<option value="2">b</option>
</select>
"""
)
self.assertHTMLEqual(
w.render('price', ''),
"""
<select name="price">
<option value="1">a</option>
<option value="2">b</option>
</select>
""")
self.assertHTMLEqual(
w.render('price', '1'),
"""
<select name="price">
<option selected="selected" value="1">a</option>
<option value="2">b</option>
</select>
""")
self.assertHTMLEqual(
w.render('price', '1,2'),
"""
<select name="price">
<option value="1">a</option>
<option value="2">b</option>
</select>
"""
)
self.assertHTMLEqual(w.render('price', ['1', '2']), """
<input type="text" name="price" value="1,2" />""")
self.assertHTMLEqual(w.render('price', [1, 2]), """
<input type="text" name="price" value="1,2" />""")