1. Luke Plant
  2. django-easyfilters

Commits

Luke Plant  committed 7924a67

Implemented 'remove' links for filters

  • Participants
  • Parent commits 9e96c14
  • Branches default

Comments (0)

Files changed (3)

File django_easyfilters/__init__.py

View file
-from filterset import FilterSet, FilterOptions
+from filterset import FilterSet, FilterOptions, FILTER_ADD, FILTER_REMOVE
+

File django_easyfilters/filterset.py

View file
 from django.utils.http import urlencode
 from django.utils.text import capfirst
 
-FilterChoice = namedtuple('FilterChoice', 'label count params')
+FILTER_ADD = 'add'
+FILTER_REMOVE = 'remove'
+
+FilterChoice = namedtuple('FilterChoice', 'label count params link_type')
 
 
 class FilterOptions(object):
         else:
             return qs.filter(**{self.field: p_val})
 
-    def build_params(self, params, filter_val):
+    def build_params(self, params, add=None, remove=False):
         params = params.copy()
-        params[self.query_param] = filter_val
+        if remove:
+            del params[self.query_param]
+        else:
+            params[self.query_param] = add
         params.pop('page', None) # links should reset paging
         return params
 
         (label (as a string), count, url)
         """
         field_obj = qs.model._meta.get_field(self.field)
+        rel_model = field_obj.rel.to
+        rel_field = field_obj.rel.get_related_field()
+
+        if self.query_param in params:
+            # Already filtered on this, there is just one object.
+            lookup = {rel_field.attname: params[self.query_param]}
+            obj = rel_model.objects.get(**lookup)
+            return [FilterChoice(unicode(obj),
+                                 None, # Don't need count for removing
+                                 self.build_params(params, remove=True),
+                                 FILTER_REMOVE)]
+
+        # Not filtered on this yet, need counts
 
         # First get the IDs and counts in one query.
         ids_counts = qs.values_list(self.field).order_by(self.field).annotate(models.Count(self.field))
 
         # Then get the instances, so that we can get the unicode() of them,
         # using their normal manager to get them in normal order.
-        rel_model = field_obj.rel.to
-        rel_field = field_obj.rel.get_related_field()
         count_dict = {}
         for id, count in ids_counts:
             count_dict[id] = count
         choices = []
 
         for o in objs:
-            id = getattr(o, rel_field.attname)
+            pk = getattr(o, rel_field.attname)
             choices.append(FilterChoice(unicode(o),
-                                        count_dict[id],
-                                        self.build_params(params, id)))
+                                        count_dict[pk],
+                                        self.build_params(params, add=pk),
+                                        FILTER_ADD))
         return choices
 
     def get_remove_url(self, params, request=None):
         field_obj = qs.model._meta.get_field(filter_.field)
         label = capfirst(field_obj.verbose_name)
         for c in filter_.get_choices(qs, params):
-            out.append(u'<a href="%s">%s</a> (%d) &nbsp;&nbsp;' % (escape('?%s' % urlencode(c.params)), escape(c.label), c.count))
+            if c.link_type == FILTER_REMOVE:
+                out.append(u'%s <a href="%s" title="Remove filter" class="removefilter">[x]</a> ' % (escape(c.label), escape('?%s' % urlencode(c.params))))
+            else:
+                out.append(u'<a href="%s" class="addfilter">%s</a> (%d) &nbsp;&nbsp;' % (escape('?%s' % urlencode(c.params)), escape(c.label), c.count))
         return u'<div>%s: %s</div>' % (escape(label), u''.join(out))
 
     def render(self):

File django_easyfilters/tests/filterset.py

View file
 import decimal
 
 from django.test import TestCase
-from django_easyfilters import FilterSet
+from django_easyfilters import FilterSet, FILTER_ADD, FILTER_REMOVE
 
 from models import Book, Genre
 
                 self.assertEqual(unicode(book.genre), choice.label)
         self.assertTrue(reached)
 
+    def test_foreignkey_remove_link(self):
+        """
+        Ensure that a ForeignKey Filter will turn into a 'remove' link when an
+        item has been selected.
+        """
+        class BookFilterSet(FilterSet):
+            fields = [
+                'genre',
+                ]
+
+        qs = Book.objects.all()
+        data = {}
+        fs = BookFilterSet(qs, data)
+        choices = fs.filters[0].get_choices(qs, data)
+        choice = choices[0]
+        fs_filtered = BookFilterSet(qs, choice.params)
+        qs_filtered = fs_filtered.qs
+        choices2 = fs_filtered.filters[0].get_choices(qs_filtered, choice.params)
+
+        # Should have one item
+        self.assertEqual(1, len(choices2))
+        self.assertEqual(choices2[0].link_type, FILTER_REMOVE)
+
+        # 'Clicking' should remove filtering
+        fs_reverted = BookFilterSet(qs, choices2[0].params)
+        self.assertEqual(qs, fs_reverted.qs)
 
     def test_filterset_render(self):
         """
             fields = [
                 'genre',
                 ]
-        fs = BookFilterSet(Book.objects.all(), {})
+        qs = Book.objects.all()
+        fs = BookFilterSet(qs, {})
         rendered = fs.render()
         self.assertTrue('Genre' in rendered)
         self.assertEqual(rendered, unicode(fs))
+
+        # And when in 'already filtered' mode:
+        choice = fs.filters[0].get_choices(qs, {})[0]
+        fs_filtered = BookFilterSet(qs, choice.params)
+        rendered_2 = fs_filtered.render()
+        self.assertTrue('Genre' in rendered_2)