Skip to content

Commit

Permalink
Fix CSVWidget attrs handling (#1177)
Browse files Browse the repository at this point in the history
* Test BaseCSVWidget explicitly

* Add attrs to CSV widget tests

* Fix BaseCSVWidget test widget

* Fix CSVWidget attrs when rendering multiple values

- BaseCSVWidget creates surrogate on initialization
- CSVWidget copies attrs to surrogate
  • Loading branch information
rpkilby committed Mar 4, 2020
1 parent 400469f commit e71d235
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 14 deletions.
23 changes: 19 additions & 4 deletions django_filters/widgets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Iterable
from copy import deepcopy
from itertools import chain
from re import search, sub

Expand Down Expand Up @@ -189,6 +190,17 @@ def value_from_datadict(self, data, files, name):


class BaseCSVWidget(forms.Widget):
# Surrogate widget for rendering multiple values
surrogate = forms.TextInput

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

if isinstance(self.surrogate, type):
self.surrogate = self.surrogate()
else:
self.surrogate = deepcopy(self.surrogate)

def _isiterable(self, value):
return isinstance(value, Iterable) and not isinstance(value, str)

Expand All @@ -212,15 +224,18 @@ def render(self, name, value, attrs=None, renderer=None):

# 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_str(surrogate.format_value(v)) for v in value]
value = [force_str(self.surrogate.format_value(v)) for v in value]
value = ','.join(list(value))

return surrogate.render(name, value, attrs, renderer=renderer)
return self.surrogate.render(name, value, attrs, renderer=renderer)


class CSVWidget(BaseCSVWidget, forms.TextInput):
pass
def __init__(self, *args, attrs=None, **kwargs):
super().__init__(*args, attrs, **kwargs)

if attrs is not None:
self.surrogate.attrs.update(attrs)


class QueryArrayWidget(BaseCSVWidget, forms.TextInput):
Expand Down
65 changes: 55 additions & 10 deletions tests/test_widgets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.forms import Select, TextInput
from django.forms import NumberInput, Select, TextInput
from django.test import TestCase

from django_filters.widgets import (
Expand Down Expand Up @@ -266,23 +266,26 @@ def test_widget_value_from_datadict(self):
self.assertEqual(result, None)


class CSVWidgetTests(TestCase):
def test_widget(self):
w = CSVWidget()
class BaseCSVWidgetTests(TestCase):
def test_widget_render(self):
class NumberCSVWidget(BaseCSVWidget, NumberInput):
pass

w = NumberCSVWidget(attrs={'test': 'attr'})
self.assertHTMLEqual(w.render('price', None), """
<input type="text" name="price" />""")
<input type="number" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', ''), """
<input type="text" name="price" />""")
<input type="number" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', []), """
<input type="text" name="price" />""")
<input type="number" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', '1'), """
<input type="text" name="price" value="1" />""")
<input type="number" test="attr" name="price" value="1" />""")

self.assertHTMLEqual(w.render('price', '1,2'), """
<input type="text" name="price" value="1,2" />""")
<input type="number" test="attr" name="price" value="1,2" />""")

self.assertHTMLEqual(w.render('price', ['1', '2']), """
<input type="text" name="price" value="1,2" />""")
Expand All @@ -291,8 +294,10 @@ def test_widget(self):
<input type="text" name="price" value="1,2" />""")

def test_widget_value_from_datadict(self):
w = CSVWidget()
class NumberCSVWidget(BaseCSVWidget, NumberInput):
pass

w = NumberCSVWidget()
data = {'price': None}
result = w.value_from_datadict(data, {}, 'price')
self.assertEqual(result, None)
Expand Down Expand Up @@ -324,6 +329,46 @@ def test_widget_value_from_datadict(self):
result = w.value_from_datadict({}, {}, 'price')
self.assertEqual(result, None)

def test_surrogate_class(self):
class ClassSurrogate(BaseCSVWidget, NumberInput):
surrogate = NumberInput

w = ClassSurrogate()
self.assertIsInstance(w.surrogate, NumberInput)

def test_surrogate_instance(self):
class InstanceSurrogate(BaseCSVWidget, NumberInput):
surrogate = NumberInput()

w = InstanceSurrogate()
self.assertIsInstance(w.surrogate, NumberInput)
self.assertIsNot(InstanceSurrogate.surrogate, w.surrogate) # deepcopied


class CSVWidgetTests(TestCase):
def test_widget_render(self):
w = CSVWidget(attrs={'test': 'attr'})
self.assertHTMLEqual(w.render('price', None), """
<input type="text" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', ''), """
<input type="text" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', []), """
<input type="text" test="attr" name="price" />""")

self.assertHTMLEqual(w.render('price', '1'), """
<input type="text" test="attr" name="price" value="1" />""")

self.assertHTMLEqual(w.render('price', '1,2'), """
<input type="text" test="attr" name="price" value="1,2" />""")

self.assertHTMLEqual(w.render('price', ['1', '2']), """
<input type="text" test="attr" name="price" value="1,2" />""")

self.assertHTMLEqual(w.render('price', [1, 2]), """
<input type="text" test="attr" name="price" value="1,2" />""")


class CSVSelectTests(TestCase):
class CSVSelect(BaseCSVWidget, Select):
Expand Down

0 comments on commit e71d235

Please sign in to comment.