Repository: carltongibson/django-filter

Issue:
Required BaseInFilters aren't required
I'm not sure if this also applies to forms or not, I only tested in DRF.

```python
class StringInFilter(filters.BaseInFilter, filters.CharFilter):
    pass

class ThingFilterSet(filters.FilterSet):
    foo = StringInFilter(field_name="foo", required=True)
    ...
```

If I just use a regular `CharFilter` it works, and will show an error when accessing the view. But using `BaseInFilter`, it doesn't seem to care if it's missing.


Discussion:
Woops, this is a duplicate of https://github.com/carltongibson/django-filter/issues/869 which I'll reopen instead. Edit: or not, since apparently I can't reopen other people's issues.

Resolving patch:
diff --git a/django_filters/fields.py b/django_filters/fields.py
--- a/django_filters/fields.py
+++ b/django_filters/fields.py
@@ -184,6 +184,9 @@ def _get_widget_class(self, widget):
         return type(str('CSV%s' % widget.__name__), bases, {})
 
     def clean(self, value):
+        if value in self.empty_values and self.required:
+            raise forms.ValidationError(self.error_messages['required'], code='required')
+
         if value is None:
             return None
         return [super(BaseCSVField, self).clean(v) for v in value]


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -227,25 +227,35 @@ def test_datetime_wrong_format(self):
 
 
 class BaseCSVFieldTests(TestCase):
-    def setUp(self):
-        class DecimalCSVField(BaseCSVField, forms.DecimalField):
-            pass
-
-        self.field = DecimalCSVField()
+    class DecimalCSVField(BaseCSVField, forms.DecimalField):
+        pass
 
     def test_clean(self):
-        self.assertEqual(self.field.clean(None), None)
-        self.assertEqual(self.field.clean(''), [])
-        self.assertEqual(self.field.clean(['1']), [1])
-        self.assertEqual(self.field.clean(['1', '2']), [1, 2])
-        self.assertEqual(self.field.clean(['1', '2', '3']), [1, 2, 3])
+        # Filter class sets required=False by default
+        field = self.DecimalCSVField(required=False)
+
+        self.assertEqual(field.clean(None), None)
+        self.assertEqual(field.clean(''), [])
+        self.assertEqual(field.clean(['1']), [1])
+        self.assertEqual(field.clean(['1', '2']), [1, 2])
+        self.assertEqual(field.clean(['1', '2', '3']), [1, 2, 3])
 
     def test_validation_error(self):
-        with self.assertRaises(forms.ValidationError):
-            self.field.clean([''])
+        field = self.DecimalCSVField()
+
+        msg = 'Enter a number.'
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean(['a', 'b', 'c'])
+
+    def test_required_error(self):
+        field = self.DecimalCSVField(required=True)
+
+        msg = 'This field is required.'
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean(None)
 
-        with self.assertRaises(forms.ValidationError):
-            self.field.clean(['a', 'b', 'c'])
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean([''])
 
     def test_derived_widget(self):
         with self.assertRaises(AssertionError) as excinfo:
@@ -269,24 +279,34 @@ def test_derived_widget(self):
 
 
 class BaseRangeFieldTests(TestCase):
-    def setUp(self):
-        class DecimalRangeField(BaseRangeField, forms.DecimalField):
-            pass
-
-        self.field = DecimalRangeField()
+    class DecimalRangeField(BaseRangeField, forms.DecimalField):
+        pass
 
     def test_clean(self):
-        self.assertEqual(self.field.clean(None), None)
-        self.assertEqual(self.field.clean(''), [])
-        self.assertEqual(self.field.clean([]), [])
-        self.assertEqual(self.field.clean(['1', '2']), [1, 2])
+        # Filter class sets required=False by default
+        field = self.DecimalRangeField(required=False)
+
+        self.assertEqual(field.clean(None), None)
+        self.assertEqual(field.clean(''), [])
+        self.assertEqual(field.clean([]), [])
+        self.assertEqual(field.clean(['1', '2']), [1, 2])
 
     def test_validation_error(self):
-        with self.assertRaises(forms.ValidationError):
-            self.field.clean([''])
+        field = self.DecimalRangeField()
+
+        msg = 'Range query expects two values.'
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean(['1'])
+
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean(['1', '2', '3'])
+
+    def test_required_error(self):
+        field = self.DecimalRangeField(required=True)
 
-        with self.assertRaises(forms.ValidationError):
-            self.field.clean(['1'])
+        msg = 'This field is required.'
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean(None)
 
-        with self.assertRaises(forms.ValidationError):
-            self.field.clean(['1', '2', '3'])
+        with self.assertRaisesMessage(forms.ValidationError, msg):
+            field.clean([''])