Commits

Luke Plant committed 66c0ecc

Added 'ranges' option for NumericRangeFilter, and fixed associated bugs.

Comments (0)

Files changed (4)

django_easyfilters/filters.py

 
     def __init__(self, field, model, params, **kwargs):
         self.max_links = kwargs.pop('max_links', 5)
+        self.ranges = kwargs.pop('ranges', None)
         field_obj = model._meta.get_field(field)
         self.choice_type = make_numeric_range_choice(field_obj.to_python, str)
         super(NumericRangeFilter, self).__init__(field, model, params, **kwargs)
         chosen = list(self.chosen)
         range_type = None
 
+        if self.ranges is not None and len(chosen) > 0:
+            return []
+
         all_vals = qs.values_list(self.field).distinct()
 
         num = all_vals.count()
 
         choices = []
-        if num <= self.max_links:
+        if num <= self.max_links and self.ranges is None:
             val_counts = value_counts(qs, self.field)
             for v, count in val_counts.items():
                 choice = self.choice_type([RangeEnd(v, True)])
                                             self.build_params(add=choice),
                                             FILTER_ADD))
         else:
-            val_range = qs.aggregate(lower=models.Min(self.field),
-                                     upper=models.Max(self.field))
-            lower = val_range['lower']
-            upper = val_range['upper']
+            if self.ranges is None:
+                val_range = qs.aggregate(lower=models.Min(self.field),
+                                         upper=models.Max(self.field))
+                lower = val_range['lower']
+                upper = val_range['upper']
 
-            # TODO - round to produce nice looking ranges.
-            step = (upper - lower)/self.max_links
-            ranges = [(lower + step * i, lower + step * (i+1)) for i in xrange(self.max_links)]
+                # TODO - round to produce nice looking ranges.
+                step = (upper - lower)/self.max_links
+                ranges = [(lower + step * i, lower + step * (i+1)) for i in xrange(self.max_links)]
+            else:
+                ranges = self.ranges
 
             val_counts = numeric_range_counts(qs, self.field, ranges)
             for i, (vals, count) in enumerate(val_counts.items()):

django_easyfilters/queries.py

 
         # Build up case expression.
         clause = (['CASE '] +
-                  ['WHEN %s >= %s AND %s < %s THEN %s ' % (col, val[0], col, val[1], i)
+                  ['WHEN %s > %s AND %s <= %s THEN %s ' % (col, val[0], col, val[1], i)
                    for i, val in enumerate(self.ranges)] +
+                  # An inclusive lower limit for the first item in ranges:
+                  ['WHEN %s = %s THEN 0 ' % (col, self.ranges[0][0])] +
                   ['ELSE %s END ' % len(self.ranges)] +
                   ['as %s' % self.alias])
         return ''.join(clause)

django_easyfilters/tests/filterset.py

                          list(qs.filter(price__gte=Decimal('3.50'),
                                         price__lte=Decimal('4.00'))))
 
+    def test_numericrange_filter_manual_ranges(self):
+        """
+        Test we can specify 'ranges' and it works as expected.
+        """
+        # Also tests that aggregation works as expected with regards to
+        # lower/upper limits.
+        qs = Book.objects.all()
+
+        ranges = [(Decimal('3.50'), Decimal('5.00')),
+                  (Decimal('5.00'), Decimal('6.00'))]
+        # There are books with prices exactly equal to 3.50/5.00/6.00 which
+        # makes this test real.
+
+        self.assertTrue(qs.filter(price=Decimal('3.50')).exists())
+        self.assertTrue(qs.filter(price=Decimal('5.00')).exists())
+        self.assertTrue(qs.filter(price=Decimal('6.00')).exists())
+
+        filter1 = NumericRangeFilter('price', Book, MultiValueDict(), ranges=ranges)
+        choices = filter1.get_choices(qs)
+        self.assertEqual(choices[0].count, qs.filter(price__gte=Decimal('3.50'), price__lte=Decimal('5.00')).count())
+        self.assertEqual(choices[1].count, qs.filter(price__gt=Decimal('5.00'), price__lte=Decimal('6.00')).count())
+
+
+    def test_numericrange_filter_manual_ranges_no_drill_down(self):
+        # We shouldn't get drilldown if ranges is specified manually.
+
+        ranges = [(Decimal('3.50'), Decimal('5.00')),
+                  (Decimal('5.00'), Decimal('6.00'))]
+        params1 = MultiValueDict({'price': ['3.50i..5.00i']})
+        filter1 = NumericRangeFilter('price', Book, params1, ranges=ranges)
+
+        qs = Book.objects.all()
+        qs_filtered1 = filter1.apply_filter(qs)
+        choices = filter1.get_choices(qs_filtered1)
+        self.assertEqual(len(choices), 1)
+        self.assertEqual(choices[0].link_type, FILTER_REMOVE)
+
+
     def test_order_by_count(self):
         """
         Tests the 'order_by_count' option.

django_easyfilters/tests/fixtures/django_easyfilters_tests.json

     "fields": {
       "name": "Pride and Prejudice", 
       "edition": 3, 
-      "price": "5.99", 
+      "price": "5", 
       "binding": "P", 
       "date_published": "1813-01-28", 
       "authors": [
     "fields": {
       "name": "The Tenant of Wildfell Hall", 
       "edition": 1, 
-      "price": "6.5", 
+      "price": "6", 
       "binding": "P", 
       "date_published": "1848-10-04", 
       "authors": [