Skip to content

Commit

Permalink
Merge pull request #82 from alex/0.6-wip
Browse files Browse the repository at this point in the history
v0.6 prep and bugfix
  • Loading branch information
apollo13 committed Mar 12, 2013
2 parents a152e2e + 6f3fa15 commit d21d850
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
*.pyc
*.egg-info
docs/_build
26 changes: 26 additions & 0 deletions CHANGES.rst
@@ -0,0 +1,26 @@
Version 0.6 (Unreleased)
------------------------

* raised minimum Django version to 1.4.x

* added Python 3.2 and Python 3.3 support

* added Django 1.5 support and initial 1.6 compatability

* FEATURE: recognition of custom model field subclasses

* FEATURE: allow optional display names for order_by values

* FEATURE: addition of class-based FilterView

* FEATURE: addition of count() method on FilterSet to prevent pagination
from loading entire queryset

* FIXED: attempts to filter on reverse side of m2m, o2o or fk would
raise an error


Version 0.5.4 (2012-11-16)
--------------------------

* project brought back to life
4 changes: 4 additions & 0 deletions MANIFEST.in
@@ -1,5 +1,9 @@
include AUTHORS.txt
include CHANGES.rst
include LICENSE
include README.rst
include runshell.py
include runtests.py
recursive-include docs *
recursive-include requirements *
recursive-include test_django_filters/templates/test_django_filters *
27 changes: 22 additions & 5 deletions django_filters/filterset.py
Expand Up @@ -65,12 +65,11 @@ 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
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:
Expand All @@ -82,7 +81,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
Expand Down Expand Up @@ -117,7 +119,8 @@ def __new__(cls, name, bases, attrs):
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
Expand Down Expand Up @@ -340,6 +343,20 @@ def filter_for_field(cls, f, name):
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.all()
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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -6,7 +6,7 @@

setup(
name='django-filter',
version='0.5.5a1',
version='0.6a1',
description=('Django-filter is a reusable Django application for allowing'
' users to filter querysets dynamically.'),
long_description=readme,
Expand Down
2 changes: 1 addition & 1 deletion test_django_filters/filter_tests.py
Expand Up @@ -35,7 +35,7 @@ def test_creation(self):
def test_creation_order(self):
f = Filter()
f2 = Filter()
self.assert_(f2.creation_counter > f.creation_counter)
self.assertTrue(f2.creation_counter > f.creation_counter)

def test_default_field(self):
f = Filter()
Expand Down
147 changes: 99 additions & 48 deletions test_django_filters/filtering_tests.py
Expand Up @@ -32,6 +32,8 @@
from .models import Location
from .models import Account
from .models import Profile
from .models import Node
from .models import DirectedNode
from .models import STATUS_CHOICES


Expand Down Expand Up @@ -224,7 +226,9 @@ class Meta:

# this is how it would come through a browser
f = F({'published': check_dt}, queryset=qs)
self.assertEqual(len(f.qs), 1,
self.assertEqual(
len(f.qs),
1,
"%s isn't matching %s when cleaned" % (check_dt, ten_min_ago))
self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk)

Expand Down Expand Up @@ -494,11 +498,17 @@ class Meta:
self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk)

def test_reverse_o2o_relation(self):
with self.assertRaises(AttributeError):
class F(FilterSet):
class Meta:
model = Account
fields = ('profile',)
class F(FilterSet):
class Meta:
model = Account
fields = ('profile',)

f = F()
self.assertEqual(f.qs.count(), 4)

f = F({'profile': 1})
self.assertEqual(f.qs.count(), 1)
self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk)

def test_o2o_relation_attribute(self):
class F(FilterSet):
Expand Down Expand Up @@ -589,26 +599,24 @@ def test_reverse_fk_relation(self):
Comment.objects.create(text='comment 3',
author=jacob, time=time, date=date)

with self.assertRaises(AttributeError):
class F(FilterSet):
class Meta:
model = User
fields = ['comments']
class F(FilterSet):
class Meta:
model = User
fields = ['comments']

# qs = User.objects.all()
# f = F({'comment': 2}, queryset=qs)
# self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
qs = User.objects.all()
f = F({'comments': [2]}, queryset=qs)
self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)

with self.assertRaises(AttributeError):
class F(FilterSet):
comment = AllValuesFilter()
class F(FilterSet):
comments = AllValuesFilter()

class Meta:
model = User
fields = ['comments']
class Meta:
model = User
fields = ['comments']

# f = F({'comments': 2}, queryset=qs)
# self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
f = F({'comments': 2}, queryset=qs)
self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)

def test_fk_relation_attribute(self):
now_dt = now()
Expand Down Expand Up @@ -728,28 +736,26 @@ class Meta:
self.assertQuerysetEqual(f.qs, [], lambda o: o.username)

def test_reverse_m2m_relation(self):
with self.assertRaises(AttributeError):
class F(FilterSet):
class Meta:
model = Book
fields = ['lovers']

# qs = User.objects.all()
# f = F({'lovers': [1]}, queryset=qs)
# self.assertQuerysetEqual(
# f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)
class F(FilterSet):
class Meta:
model = Book
fields = ['lovers']

qs = Book.objects.all().order_by('title')
f = F({'lovers': [1]}, queryset=qs)
self.assertQuerysetEqual(
f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)

with self.assertRaises(AttributeError):
class F(FilterSet):
lovers = AllValuesFilter()
class F(FilterSet):
lovers = AllValuesFilter()

class Meta:
model = Book
fields = ['lovers']
class Meta:
model = Book
fields = ['lovers']

# f = F({'lovers': 1}, queryset=qs)
# self.assertQuerysetEqual(
# f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)
f = F({'lovers': 1}, queryset=qs)
self.assertQuerysetEqual(
f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)

def test_m2m_relation_attribute(self):
class F(FilterSet):
Expand Down Expand Up @@ -872,15 +878,60 @@ def test_fk_relation_attribute_on_m2m_relation(self):
pass


class SelfReferentialRelationshipTests(TestCase):
class SymmetricalSelfReferentialRelationshipTests(TestCase):

@unittest.skip('todo')
def test_self_referential_m2m_relation(self):
pass
def setUp(self):
n1 = Node.objects.create(name='one')
n2 = Node.objects.create(name='two')
n3 = Node.objects.create(name='three')
n4 = Node.objects.create(name='four')
n1.adjacents.add(n2)
n2.adjacents.add(n3)
n2.adjacents.add(n4)
n4.adjacents.add(n1)

@unittest.skip('todo')
def test_reverse_self_referential_m2m_relation(self):
pass
def test_relation(self):
class F(FilterSet):
class Meta:
model = Node
fields = ['adjacents']

qs = Node.objects.all().order_by('pk')
f = F({'adjacents': ['1']}, queryset=qs)
self.assertQuerysetEqual(f.qs, [2, 4], lambda o: o.pk)


class NonSymmetricalSelfReferentialRelationshipTests(TestCase):

def setUp(self):
n1 = DirectedNode.objects.create(name='one')
n2 = DirectedNode.objects.create(name='two')
n3 = DirectedNode.objects.create(name='three')
n4 = DirectedNode.objects.create(name='four')
n1.outbound_nodes.add(n2)
n2.outbound_nodes.add(n3)
n2.outbound_nodes.add(n4)
n4.outbound_nodes.add(n1)

def test_forward_relation(self):
class F(FilterSet):
class Meta:
model = DirectedNode
fields = ['outbound_nodes']

qs = DirectedNode.objects.all().order_by('pk')
f = F({'outbound_nodes': ['1']}, queryset=qs)
self.assertQuerysetEqual(f.qs, [4], lambda o: o.pk)

def test_reverse_relation(self):
class F(FilterSet):
class Meta:
model = DirectedNode
fields = ['inbound_nodes']

qs = DirectedNode.objects.all().order_by('pk')
f = F({'inbound_nodes': ['1']}, queryset=qs)
self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk)


class MiscFilterSetTests(TestCase):
Expand Down

0 comments on commit d21d850

Please sign in to comment.