Commits

Luke Plant committed 17d0e08

Made use of MultiValueDict/QueryDict to handle multiple values.

This eliminates the need to use commas or other hacks to indicate multiple
values - we use the facilities built in to query strings for that instead.

Comments (0)

Files changed (3)

django_easyfilters/filters.py

             qs = qs.filter(**{self.field: p_val.pop()})
         return qs
 
+    def to_python(self, param):
+        return self.field_obj.to_python(param)
+
     def choices_from_params(self, params):
         """
         For the params passed in (i.e. from query string), retrive a list of
         already 'chosen' options.
         """
-        raise NotImplementedError()
+        return [self.to_python(i) for i in params.getlist(self.query_param)]
 
-    def param_from_choices(self, choice):
+    def param_from_choices(self, choices):
         """
         For a list of choices, return the parameter that should be created.
         """
-        raise NotImplementedError()
+        return map(unicode, choices)
 
     def build_params(self, params, add=None, remove=None):
         params = params.copy()
             if add not in chosen:
                 chosen.append(add)
         if chosen:
-            params[self.query_param] = self.param_from_choices(chosen)
+            params.setlist(self.query_param, self.param_from_choices(chosen))
         else:
             del params[self.query_param]
         params.pop('page', None) # links should reset paging
 
 class SingleValueFilterMixin(object):
 
-    def choices_from_params(self, params):
-        if self.query_param in params:
-            return [params[self.query_param]]
-        else:
-            return []
-
-    def param_from_choices(self, choices):
-        # There can be only one
-        return unicode(choices[0])
-
     def get_values_counts(self, qs, params):
         """
         Returns a SortedDict dictionary of {value: count}.
 
 class MultiValueFilterMixin(object):
 
-    def choices_from_params(self, params):
-        if self.query_param in params:
-            return map(int, params[self.query_param].split(','))
-        else:
-            return []
-
-    def param_from_choices(self, choices):
-        return ','.join(map(unicode, choices))
-
     def get_choices(self, qs, params):
         # In general, can filter multiple times, so we can have multiple remove
         # links, and multiple add links, at the same time.
         super(ManyToManyFilter, self).__init__(*args, **kwargs)
         self.rel_model = self.field_obj.rel.to
 
+    def to_python(self, param):
+        return self.field_obj.rel.get_related_field().to_python(param)
+
     def get_choices_add(self, qs, params):
         # It is easiest to base queries around the intermediate table, in order
         # to get counts.
 
         # We want to preserve order of items in params, so use a dict:
         obj_dict = dict([(obj.pk, obj) for obj in objs])
-
         return [FilterChoice(unicode(obj_dict[choice]),
                              None, # Don't need count for removing
                              self.build_params(params, remove=choice),

django_easyfilters/filterset.py

 from django import template
+from django.http import QueryDict
 from django.utils.safestring import mark_safe
 from django.utils.html import escape
-from django.utils.http import urlencode
 from django.utils.text import capfirst
 
 from django_easyfilters.filters import FILTER_ADD, FILTER_REMOVE, FILTER_ONLY_CHOICE, \
 """
 
     def __init__(self, queryset, params):
-        self.params = dict(params.items())
+        self.params = params
         self.initial_queryset = queryset
         self.model = queryset.model
         self.filters = self.setup_filters()
         choices = filter_.get_choices(qs, params)
         ctx = {'filterlabel': capfirst(field_obj.verbose_name)}
         ctx['remove_choices'] = [dict(label=non_breaking_spaces(c.label),
-                                      url=u'?' + urlencode(c.params))
+                                      url=u'?' + c.params.urlencode())
                                  for c in choices if c.link_type == FILTER_REMOVE]
         ctx['add_choices'] = [dict(label=non_breaking_spaces(c.label),
-                                   url=u'?' + urlencode(c.params),
+                                   url=u'?' + c.params.urlencode(),
                                    count=c.count)
                               for c in choices if c.link_type == FILTER_ADD]
         ctx['only_choices'] = [dict(label=non_breaking_spaces(c.label),

django_easyfilters/tests/filterset.py

 import decimal
 import operator
 
+from django.http import QueryDict
 from django.test import TestCase
+from django.utils.datastructures import MultiValueDict
+
 from django_easyfilters.filterset import FilterSet
 from django_easyfilters.filters import FilterOptions, \
     FILTER_ADD, FILTER_REMOVE, FILTER_ONLY_CHOICE, \
             fields = []
 
         qs = Book.objects.all()
-        data = {}
+        data = QueryDict('')
         f = BookFilterSet(qs, data)
         self.assertEqual(qs.count(), f.qs.count())
 
                 ]
 
         qs = Book.objects.all()
-        fs = BookFilterSet(qs, {})
+        fs = BookFilterSet(qs, QueryDict(''))
         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]
+        choice = fs.filters[0].get_choices(qs, QueryDict(''))[0]
         fs_filtered = BookFilterSet(qs, choice.params)
         rendered_2 = fs_filtered.render()
         self.assertTrue('Genre' in rendered_2)
                 'authors',
                 ]
 
-        fs = BookFilterSet(Book.objects.all(), {})
+        fs = BookFilterSet(Book.objects.all(), QueryDict(''))
         self.assertEqual(ForeignKeyFilter, type(fs.filters[0]))
         self.assertEqual(ValuesFilter, type(fs.filters[1]))
         self.assertEqual(ChoicesFilter, type(fs.filters[2]))
 
         filter_ = ForeignKeyFilter('genre', Book)
         qs = Book.objects.all()
-        data = {}
+        data = MultiValueDict()
 
         choices = [(c.label, c.count) for c in filter_.get_choices(qs, data)]
 
         limited by that filter.
         """
         qs = Book.objects.all()
-        data = {}
+        data = MultiValueDict()
         filter_ = ForeignKeyFilter('genre', Book)
         choices = filter_.get_choices(qs, data)
 
         """
         filter_ = ForeignKeyFilter('genre', Book)
         qs = Book.objects.all()
-        data = {}
+        data = MultiValueDict()
         choices = filter_.get_choices(qs, data)
         choice = choices[0]
+
         qs_filtered = filter_.apply_filter(qs, choice.params)
         choices2 = filter_.get_choices(qs_filtered, choice.params)
 
         # We combine the tests for brevity
         filter_ = ValuesFilter('edition', Book)
         qs = Book.objects.all()
-        choices = filter_.get_choices(qs, {})
+        choices = filter_.get_choices(qs, MultiValueDict())
 
         for choice in choices:
             count = Book.objects.filter(edition=choice.params.values()[0]).count()
         """
         filter_ = ChoicesFilter('binding', Book)
         qs = Book.objects.all()
-        choices = filter_.get_choices(qs, {})
+        choices = filter_.get_choices(qs, MultiValueDict())
         # Check:
         # - order is correct.
         # - all values present (guaranteed by fixture data)
         # which can be removed individually.
 
         # First level:
-        choices = filter_.get_choices(qs, {})
+        choices = filter_.get_choices(qs, MultiValueDict())
 
         # Check list is full, and in right order
         self.assertEqual([unicode(v) for v in Author.objects.all()],
                          [choice.label for choice in choices])
 
-        param_to_list = lambda param: map(int, param.split(','))
-
         for choice in choices:
             # For single choice, param will be single integer:
             param = int(choice.params[filter_.query_param])
 
         # If we select 'emily' as an author:
 
-        data =  {'authors':str(emily.pk)}
+        data =  MultiValueDict({'authors':[str(emily.pk)]})
         qs_emily = filter_.apply_filter(qs, data)
 
         # ...we should get a qs that includes Poems and Wuthering Heights.
         self.assertTrue(unicode(emily) in [c.label for c in choices if c.link_type is FILTER_REMOVE])
 
         # If we select again:
-        data =  {'authors': ','.join([str(emily.pk), str(anne.pk)])}
+        data =  MultiValueDict({'authors': [str(emily.pk), str(anne.pk)]})
 
         qs_emily_anne = filter_.apply_filter(qs, data)
 
         """
         filter1 = ForeignKeyFilter('genre', Book, order_by_count=True)
         qs = Book.objects.all()
-        choices1 = filter1.get_choices(qs, {})
+        choices1 = filter1.get_choices(qs, MultiValueDict())
 
         # Should be same after sorting by 'count'
         self.assertEqual(choices1, sorted(choices1, key=operator.attrgetter('count'), reverse=True))
 
         filter2 = ForeignKeyFilter('genre', Book, order_by_count=False)
-        choices2 = filter2.get_choices(qs, {})
+        choices2 = filter2.get_choices(qs, MultiValueDict())
 
         # Should be same after sorting by 'label' (that is equal to Genre.name,
         # and Genre ordering is by that field)