Skip to content

Instantly share code, notes, and snippets.

@nkryptic
Last active December 12, 2015 12:19
Show Gist options
  • Save nkryptic/4771045 to your computer and use it in GitHub Desktop.
Save nkryptic/4771045 to your computer and use it in GitHub Desktop.
found out that django-filter doesn't handle the reverse side of model relationships... it looks like there was some intent to, but the code breaks when attempting it and there are no tests which try it. this is a patch which handles those special cases. pretty rough though, at this point.
# updated models.py
class User(models.Model):
username = models.CharField(max_length=255)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
status = models.IntegerField(choices=STATUS_CHOICES, default=0)
is_active = models.BooleanField()
# modified m2m to have related_name
favorite_books = models.ManyToManyField('Book', related_name='likers')
def __unicode__(self):
return self.username
# new test
class FilterSetM2MTest(TestCase):
fixtures = ['test_data']
def test_reverse_m2m_filter(self):
class F(FilterSet):
class Meta:
model = Book
fields = ['likers']
qs = Book.objects.all()
f = F({'likers': ['1']}, queryset=qs)
self.assertQuerysetEqual(f.qs, [1, 2], lambda o: o.pk)
diff --git a/django_filters/filterset.py b/django_filters/filterset.py
index 32553f7..b3d077b 100644
--- a/django_filters/filterset.py
+++ b/django_filters/filterset.py
@@ -64,12 +64,13 @@ def get_model_field(model, f):
rel, model, direct, m2m = opts.get_field_by_name(parts[-1])
except FieldDoesNotExist:
return None
- if not direct:
- return rel.field.rel.to_field
+ # if not direct:
+ # return rel.field.rel.to_field
return rel
-def filters_for_model(model, fields=None, exclude=None, filter_for_field=None):
+def filters_for_model(model, fields=None, exclude=None, filter_for_field=None,
+ filter_for_reverse_field=None):
field_dict = SortedDict()
opts = model._meta
if fields is None:
@@ -81,7 +82,10 @@ def filters_for_model(model, fields=None, exclude=None, filter_for_field=None):
if field is None:
field_dict[f] = None
continue
- filter_ = filter_for_field(field, f)
+ if isinstance(field, RelatedObject):
+ filter_ = filter_for_reverse_field(field, f)
+ else:
+ filter_ = filter_for_field(field, f)
if filter_:
field_dict[f] = filter_
return field_dict
@@ -116,7 +120,8 @@ class FilterSetMetaclass(type):
getattr(new_class, 'Meta', None))
if opts.model:
filters = filters_for_model(opts.model, opts.fields, opts.exclude,
- new_class.filter_for_field)
+ new_class.filter_for_field,
+ new_class.filter_for_reverse_field)
filters.update(declared_filters)
else:
filters = declared_filters
@@ -340,6 +345,20 @@ class BaseFilterSet(object):
if filter_class is not None:
return filter_class(**default)
+ @classmethod
+ def filter_for_reverse_field(cls, f, name):
+ rel = f.field.rel
+ queryset = f.model._default_manager
+ default = {
+ 'name': name,
+ 'label': capfirst(rel.related_name),
+ 'queryset': queryset,
+ }
+ if rel.multiple:
+ return ModelMultipleChoiceFilter(**default)
+ else:
+ return ModelChoiceFilter(**default)
+
class FilterSet(six.with_metaclass(FilterSetMetaclass, BaseFilterSet)):
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment