Repository: flask-admin/flask-admin

Issue:
peewee BooleanField filter doesn't work
BooleanFields are not properly filtered when added to `column_filters`. Regardless of the value selected, it gets coerced to `1` for the query.

Reproduce by adding a BooleanField to a model in `examples/peewee/app.py` and add the field to the `column_filters` list. After creating some objects, you'll see you can't properly filter for models with the field set to false.

Potential fix is to add the following to the peewee boolean filter class(es)

``` python
def clean(self, value):
    return int(value)
```

Not sure how/if other backends are affected.



Discussion:

Resolving patch:
diff --git a/flask_admin/contrib/peewee/filters.py b/flask_admin/contrib/peewee/filters.py
--- a/flask_admin/contrib/peewee/filters.py
+++ b/flask_admin/contrib/peewee/filters.py
@@ -116,11 +116,13 @@ def operation(self):
 
 # Customized type filters
 class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter):
-    pass
+    def clean(self, value):
+        return int(value)
 
 
 class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
-    pass
+    def clean(self, value):
+        return int(value)
 
 
 class IntEqualFilter(FilterEqual, filters.BaseIntFilter):


Test patch:
diff --git a/flask_admin/tests/mongoengine/test_basic.py b/flask_admin/tests/mongoengine/test_basic.py
--- a/flask_admin/tests/mongoengine/test_basic.py
+++ b/flask_admin/tests/mongoengine/test_basic.py
@@ -59,8 +59,8 @@ def fill_db(Model1, Model2):
     Model1('test1_val_4', 'test2_val_4').save()
     Model1(None, 'empty_obj').save()
 
-    Model2('string_field_val_1', None, None).save()
-    Model2('string_field_val_2', None, None).save()
+    Model2('string_field_val_1', None, None, True).save()
+    Model2('string_field_val_2', None, None, False).save()
     Model2('string_field_val_3', 5000, 25.9).save()
     Model2('string_field_val_4', 9000, 75.5).save()
     Model2('string_field_val_5', 6169453081680413441).save()
@@ -466,6 +466,49 @@ def test_column_filters():
     ok_('string_field_val_3' not in data)
     ok_('string_field_val_4' not in data)
 
+    # Test boolean filter
+    view = CustomModelView(Model2, column_filters=['bool_field'],
+                           endpoint="_bools")
+    admin.add_view(view)
+
+    eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Bool Field']],
+        [
+            (0, 'equals'),
+            (1, 'not equal'),
+        ])
+
+    # boolean - equals - Yes
+    rv = client.get('/admin/_bools/?flt0_0=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('string_field_val_1' in data)
+    ok_('string_field_val_2' not in data)
+    #ok_('string_field_val_3' not in data)
+
+    # boolean - equals - No
+    rv = client.get('/admin/_bools/?flt0_0=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('string_field_val_1' not in data)
+    ok_('string_field_val_2' in data)
+    #ok_('string_field_val_3' in data)
+
+    # boolean - not equals - Yes
+    rv = client.get('/admin/_bools/?flt0_1=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('string_field_val_1' not in data)
+    ok_('string_field_val_2' in data)
+    #ok_('string_field_val_3' in data)
+
+    # boolean - not equals - No
+    rv = client.get('/admin/_bools/?flt0_1=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('string_field_val_1' in data)
+    ok_('string_field_val_2' not in data)
+    #ok_('string_field_val_3' not in data)
+
     # Test float filter
     view = CustomModelView(Model2, column_filters=['float_field'],
                            endpoint="_float")
diff --git a/flask_admin/tests/peeweemodel/test_basic.py b/flask_admin/tests/peeweemodel/test_basic.py
--- a/flask_admin/tests/peeweemodel/test_basic.py
+++ b/flask_admin/tests/peeweemodel/test_basic.py
@@ -94,8 +94,8 @@ def fill_db(Model1, Model2):
     Model1('test1_val_4', 'test2_val_4').save()
     Model1(None, 'empty_obj').save()
 
-    Model2('char_field_val_1', None, None).save()
-    Model2('char_field_val_2', None, None).save()
+    Model2('char_field_val_1', None, None, bool_field=True).save()
+    Model2('char_field_val_2', None, None, bool_field=False).save()
     Model2('char_field_val_3', 5000, 25.9).save()
     Model2('char_field_val_4', 9000, 75.5).save()
     Model2('char_field_val_5', 6169453081680413441).save()
@@ -496,6 +496,49 @@ def test_column_filters():
     ok_('char_field_val_3' not in data)
     ok_('char_field_val_4' not in data)
 
+    # Test boolean filter
+    view = CustomModelView(Model2, column_filters=['bool_field'],
+                           endpoint="_bools")
+    admin.add_view(view)
+
+    eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Bool Field']],
+        [
+            (0, 'equals'),
+            (1, 'not equal'),
+        ])
+
+    # boolean - equals - Yes
+    rv = client.get('/admin/_bools/?flt0_0=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('char_field_val_1' in data)
+    ok_('char_field_val_2' not in data)
+    ok_('char_field_val_3' not in data)
+
+    # boolean - equals - No
+    rv = client.get('/admin/_bools/?flt0_0=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('char_field_val_1' not in data)
+    ok_('char_field_val_2' in data)
+    ok_('char_field_val_3' in data)
+
+    # boolean - not equals - Yes
+    rv = client.get('/admin/_bools/?flt0_1=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('char_field_val_1' not in data)
+    ok_('char_field_val_2' in data)
+    ok_('char_field_val_3' in data)
+
+    # boolean - not equals - No
+    rv = client.get('/admin/_bools/?flt0_1=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('char_field_val_1' in data)
+    ok_('char_field_val_2' not in data)
+    ok_('char_field_val_3' not in data)
+
     # Test float filter
     view = CustomModelView(Model2, column_filters=['float_field'],
                            endpoint="_float")
diff --git a/flask_admin/tests/sqla/test_basic.py b/flask_admin/tests/sqla/test_basic.py
--- a/flask_admin/tests/sqla/test_basic.py
+++ b/flask_admin/tests/sqla/test_basic.py
@@ -93,7 +93,7 @@ def __init__(self, string_field=None, int_field=None, bool_field=None,
 
 def fill_db(db, Model1, Model2):
     model1_obj1 = Model1('test1_val_1', 'test2_val_1', bool_field=True)
-    model1_obj2 = Model1('test1_val_2', 'test2_val_2')
+    model1_obj2 = Model1('test1_val_2', 'test2_val_2', bool_field=False)
     model1_obj3 = Model1('test1_val_3', 'test2_val_3')
     model1_obj4 = Model1('test1_val_4', 'test2_val_4')
 
@@ -819,6 +819,49 @@ def test_column_filters():
     ok_('test2_val_3' not in data)
     ok_('test2_val_4' not in data)
 
+    # Test boolean filter
+    view = CustomModelView(Model1, db.session, column_filters=['bool_field'],
+                           endpoint="_bools")
+    admin.add_view(view)
+
+    eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Bool Field']],
+        [
+            (0, 'equals'),
+            (1, 'not equal'),
+        ])
+
+    # boolean - equals - Yes
+    rv = client.get('/admin/_bools/?flt0_0=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('test2_val_1' in data)
+    ok_('test2_val_2' not in data)
+    ok_('test2_val_3' not in data)
+
+    # boolean - equals - No
+    rv = client.get('/admin/_bools/?flt0_0=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('test2_val_1' not in data)
+    ok_('test2_val_2' in data)
+    ok_('test2_val_3' in data)
+
+    # boolean - not equals - Yes
+    rv = client.get('/admin/_bools/?flt0_1=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('test2_val_1' not in data)
+    ok_('test2_val_2' in data)
+    ok_('test2_val_3' in data)
+
+    # boolean - not equals - No
+    rv = client.get('/admin/_bools/?flt0_1=0')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('test2_val_1' in data)
+    ok_('test2_val_2' not in data)
+    ok_('test2_val_3' not in data)
+
     # Test float filter
     view = CustomModelView(Model2, db.session, column_filters=['float_field'],
                            endpoint="_float")