Commits

Luke Plant committed ccbe273

Added FilterSet.title attribute

  • Participants
  • Parent commits db32c34

Comments (0)

Files changed (5)

+Version 0.3
+-----------
+
+* Added the ``FilterSet.title`` attribute, and the ``title_fields`` attribute
+  that can be used to control it.
+
 Version 0.2
 -----------
 

File django_easyfilters/filterset.py

     return mark_safe(u' '.join(escape(part) for part in val.split(u' ')))
 
 
+class cached_property(object):
+    """
+    Decorator that creates converts a method with a single
+    self argument into a property cached on the instance.
+    """
+    def __init__(self, func):
+        self.func = func
+
+    def __get__(self, instance, type):
+        res = instance.__dict__[self.func.__name__] = self.func(instance)
+        return res
+
+
 class FilterSet(object):
 
     template = """
 {% endfor %}
 </div>
 """
+    title_fields = None
 
     def __init__(self, queryset, params):
         self.params = params
         self.filters = self.setup_filters()
         self.qs = self.apply_filters(queryset)
 
+    @cachedproperty
+    def title(self):
+        return self.make_title()
+
+    def get_filter_choices(self, filter_field):
+        if not hasattr(self, '_cached_filter_choices'):
+            self._cached_filter_choices = dict((f.field, f.get_choices(self.qs))
+                                               for f in self.filters)
+        return self._cached_filter_choices[filter_field]
+
     def apply_filters(self, queryset):
         for f in self.filters:
             queryset = f.apply_filter(queryset)
         return queryset
 
-    def render_filter(self, filter_, qs):
+    def render_filter(self, filter_):
         field_obj = self.model._meta.get_field(filter_.field)
-        choices = filter_.get_choices(qs)
+        choices = self.get_filter_choices(filter_.field)
         ctx = {'filterlabel': capfirst(field_obj.verbose_name)}
         ctx['choices'] = [dict(label=non_breaking_spaces(c.label),
                                url=u'?' + c.params.urlencode() \
         return template.Template(self.template)
 
     def render(self):
-        return mark_safe(u'\n'.join(self.render_filter(f, self.qs) for f in self.filters))
+        return mark_safe(u'\n'.join(self.render_filter(f) for f in self.filters))
 
     def get_fields(self):
         return self.fields
             filters.append(klass(field_name, self.model, self.params, **opts))
         return filters
 
+    def make_title(self):
+        if self.title_fields is None:
+            title_fields = [filter_.field for filter_ in self.filters]
+        else:
+            title_fields = self.title_fields
+        return u", ".join(c.label
+                          for f in title_fields
+                          for c in self.get_filter_choices(f)
+                          if c.link_type != FILTER_ADD)
+
     def __unicode__(self):
         return self.render()

File django_easyfilters/tests/filterset.py

         fs = AuthorFilterSet(Author.objects.all(), QueryDict(''))
         self.assertEqual(NumericRangeFilter, type(fs.filters[0]))
 
+    def test_default_title(self):
+        class BookFilterSet(FilterSet):
+            fields = [
+                'genre',
+                'binding',
+                'authors',
+                ]
+
+        qs = Book.objects.all()
+        data = QueryDict('binding=H&genre=6')
+        f = BookFilterSet(qs, data)
+        self.assertEqual(f.title, "Classics, Hardback")
+
+    def test_custom_title(self):
+        class BookFilterSet(FilterSet):
+            fields = [
+                'genre',
+                'binding',
+                'authors',
+                ]
+            title_fields = [
+                'genre',
+                ]
+
+        qs = Book.objects.all()
+        data = QueryDict('binding=H&genre=6')
+        f = BookFilterSet(qs, data)
+        self.assertEqual(f.title, "Classics")
+
 
 class TestFilters(TestCase):
     fixtures = ['django_easyfilters_tests']

File docs/filterset.rst

       This attribute contains the input QuerySet filtered according to the data
       in ``params``.
 
+   .. attribute:: title
+
+      This attribute contains a title summarising the filters that have
+      been selected.
+
    In addition, there are methods/attributes that can be overridden to customise
    the FilterSet:
 
 
       A string containing a Django template, used to render all the filters.  It
       is used by the default ``get_template`` method, see above.
+
+   .. attribute:: title_fields
+
+      By default, the fields used to create the ``title`` attribute are all
+      fields specified in the ``fields`` attribute, in that order. Specify
+      ``title_fields`` to override this.

File docs/overview.rst

        {# etc #}
     {% endfor %}
 
+The ``FilterSet`` also provides a 'title' attribute that can be used to provide
+a simple summary of what has been selected so far. It is made up of a comma
+separated list of chosen fields. For example, if the user has selected genre
+'Classics' and binding 'Hardback' in the example above, you would get the following::
+
+    >>> books = Book.objects.all()
+    >>> booksfilter = BookFilterSet(books, request.GET)
+    >>> booksfilter.title
+    "Harback, Classics"
+
+The fields used for the ``title`` attribute, and the order they are used, can be
+customised by adding a ``title_fields`` attribute to your ``FilterSet``:
+
+.. code-block:: python
+
+    class BookFilterSet(FilterSet):
+        fields = [
+            'binding',
+            'authors',
+            'genre',
+            'price',
+            ]
+
+        title_fields = ['genre', 'binding']
+
 Customisation of the filters can be done in various ways - see :doc:`the
 FilterSet documentation <filterset>` for how to do this, and :doc:`the Filters
 documentation <filters>` for options that can be specified.