group filter fields with meta option 'together'

This commit is contained in:
Diogo Machado 2015-03-16 21:54:20 +00:00
parent 13d4fe30e5
commit d3c80b9d34
5 changed files with 83 additions and 2 deletions

View File

@ -18,4 +18,5 @@ Vladimir Sidorenko
Tom Christie
Remco Wendt
Axel Haustant
Brad Erickson
Brad Erickson
Diogo Laginha

View File

@ -141,6 +141,8 @@ class FilterSetOptions(object):
self.order_by = getattr(options, 'order_by', False)
self.form = getattr(options, 'form', forms.Form)
self.together = getattr(options, 'together', None)
class FilterSetMetaclass(type):
@ -352,6 +354,22 @@ class BaseFilterSet(object):
@property
def form(self):
def full_clean(form):
super(form.__class__, form).full_clean()
message = 'Following fields must be together: %s'
together = self._meta.together
cleaned_data = form.cleaned_data
if isinstance(together[0], (list, tuple)):
for each in together:
count = len([i for i in each if cleaned_data.get(i)])
if 0 < count < len(each):
return form.add_error(None, message % ','.join(each))
else:
count = len([i for i in together if cleaned_data.get(i)])
if 0 < count < len(together):
return form.add_error(None, message % ','.join(together))
if not hasattr(self, '_form'):
fields = OrderedDict([
(name, filter_.field)
@ -359,6 +377,8 @@ class BaseFilterSet(object):
fields[self.order_by_field] = self.ordering_field
Form = type(str('%sForm' % self.__class__.__name__),
(self._meta.form,), fields)
if self._meta.together:
Form.full_clean = full_clean
if self.is_bound:
self._form = Form(self.data, prefix=self.form_prefix)
else:

View File

@ -199,6 +199,24 @@ The inner ``Meta`` class also takes an optional ``form`` argument. This is a
form class from which ``FilterSet.form`` will subclass. This works similar to
the ``form`` option on a ``ModelAdmin.``
Group fields with ``together``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The inner ``Meta`` class also takes an optional ``together`` argument. This
is a list of lists, each containing field names. For convenience can be a
single list/tuple when dealing with a single set of fields. Fields within a
field set must either be all or none present in the request for
``FilterSet.form`` to be valid.
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['price', 'release_date', 'rating']
together = ['rating', 'price']
Non-Meta options
----------------

View File

@ -16,7 +16,7 @@ if sys.argv[-1] == 'publish':
setup(
name='django-filter',
version='0.9.2',
version='0.9.3',
description=('Django-filter is a reusable Django application for allowing'
' users to filter querysets dynamically.'),
long_description=readme,

View File

@ -628,3 +628,45 @@ class FilterSetOrderingTests(TestCase):
f = F({'o': 'status'}, queryset=self.qs)
self.assertQuerysetEqual(
f.qs, ['carl', 'alex', 'aaron', 'jacob'], lambda o: o.username)
class FilterSetTogetherTests(TestCase):
def setUp(self):
self.alex = User.objects.create(username='alex', status=1)
self.jacob = User.objects.create(username='jacob', status=2)
self.qs = User.objects.all().order_by('id')
def test_fields_set(self):
class F(FilterSet):
class Meta:
model = User
fields = ['username', 'status', 'is_active', 'first_name']
together = [
('username', 'status'),
('first_name', 'is_active'),
]
f = F({}, queryset=self.qs)
self.assertEqual(f.qs.count(), 2)
f = F({'username': 'alex'}, queryset=self.qs)
self.assertEqual(f.qs.count(), 0)
f = F({'username': 'alex', 'status': 1}, queryset=self.qs)
self.assertEqual(f.qs.count(), 1)
self.assertQuerysetEqual(f.qs, [self.alex.pk], lambda o: o.pk)
def test_single_fields_set(self):
class F(FilterSet):
class Meta:
model = User
fields = ['username', 'status']
together = ['username', 'status']
f = F({}, queryset=self.qs)
self.assertEqual(f.qs.count(), 2)
f = F({'username': 'alex'}, queryset=self.qs)
self.assertEqual(f.qs.count(), 0)
f = F({'username': 'alex', 'status': 1}, queryset=self.qs)
self.assertEqual(f.qs.count(), 1)
self.assertQuerysetEqual(f.qs, [self.alex.pk], lambda o: o.pk)