Skip to content

Commit

Permalink
Merge pull request #21 from lucaswiman/20_add_queryset_api
Browse files Browse the repository at this point in the history
Add filter, get and exclude methods to P
  • Loading branch information
lucaswiman committed Feb 6, 2016
2 parents ead8dad + ba42014 commit bf37975
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ queryset filter statements:
qs = MyModel.objects.filter(p)
P objects also support ``QuerySet``-like filtering operations that can be
applied to an arbitrary iterable: ``P.get(iterable)``, ``P.filter(iterable)``,
and ``P.exclude(iterable)``:

.. code-block:: python
model_instance = MyModel(some_field="hello there", age=21)
other_model_instance = MyModel(some_field="hello there", age=10)
p.filter([model_instance, other_model_instance]) == [model_instance]
>>> True
p.filter([model_instance, other_model_instance]) == model_instance
>>> True
p.filter([model_instance, other_model_instance]) == other_model_instance
>>> True
If you have a situation where you want to use querysets and predicates based on
the same conditions, it is far better to start with the predicate. Because of
the way querysets assume a SQL context, it is non-trivial to reverse engineer
Expand Down
2 changes: 1 addition & 1 deletion predicate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# flake8: noqa
from .predicate import P

__version__ = '1.1.0'
__version__ = '1.2.0'
__all__ = ['P']

33 changes: 33 additions & 0 deletions predicate/predicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core.exceptions import FieldDoesNotExist
except ImportError: # Django <1.8
from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models import Manager
Expand Down Expand Up @@ -58,6 +59,38 @@ def eval(self, instance):
else:
return ret

def filter(self, iterable):
"""
Returns a filtered list of applying self to the elements of iterable.
This is a similar API to QuerySet.filter.
"""
return filter(self.eval, iterable)

def exclude(self, iterable):
"""
Returns a filtered list of applying ~self to the elements of iterable.
This is a similar API to QuerySet.exclude.
"""
return filter((~self).eval, iterable)

def get(self, iterable):
"""
Gets the unique element of iterable that matches self.
This follows the QuerySet.get() api, raising ObjectDoesNotExist if no
element matches and MultipleObjectsReturned if multiple objects match.
"""
filtered = self.filter(iterable)
if len(filtered) == 0:
raise ObjectDoesNotExist('Object matching query does not exist.')
elif len(filtered) > 1:
raise MultipleObjectsReturned(
'get() returned more than one object -- it returned %s!' % len(filtered))
else:
return filtered[0]


class LookupNotFound(Exception):
pass
Expand Down
68 changes: 68 additions & 0 deletions tests/testapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import timedelta
from random import choice, random

from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.test import skipIfDBFeature
from django.test import TestCase
Expand Down Expand Up @@ -523,3 +524,70 @@ def test_reverse_one_to_one_relation(self):
values_list = self.get_values_list_and_assert_orm_invariants(
test_obj, 'onetoonemodel__pk', flat=True)
self.assertEqual(values_list, [None])


class TestFilteringMethods(TestCase):
def setUp(self):
TestObj.objects.all().delete()
self.obj1 = TestObj.objects.create(int_value=1)
self.obj2 = TestObj.objects.create(int_value=2)
self.objects = [self.obj1, self.obj2]

def test_get_no_objects(self):
predicate = OrmP(int_value=3)
with self.assertRaises(ObjectDoesNotExist):
TestObj.objects.get(predicate)
with self.assertRaises(ObjectDoesNotExist):
predicate.get(self.objects)

def test_get_return_value(self):
predicate = OrmP(int_value=1)
self.assertEqual(TestObj.objects.get(predicate), self.obj1)
self.assertEqual(predicate.get(self.objects), self.obj1)

def test_get_multiple_objects(self):
predicate = OrmP(int_value__in=[1, 2])
with self.assertRaises(MultipleObjectsReturned):
TestObj.objects.get(predicate)
with self.assertRaises(MultipleObjectsReturned):
predicate.get(self.objects)

def test_filter(self):
predicate = OrmP(int_value=3)
self.assertEqual(set(TestObj.objects.filter(predicate)), set())
self.assertEqual(set(predicate.filter(self.objects)), set())

predicate = OrmP(int_value=1)
self.assertEqual(set(TestObj.objects.filter(predicate)), {self.obj1})
self.assertEqual(set(predicate.filter(self.objects)), {self.obj1})

predicate = OrmP(int_value=2)
self.assertEqual(set(TestObj.objects.filter(predicate)), {self.obj2})
self.assertEqual(set(predicate.filter(self.objects)), {self.obj2})

predicate = OrmP(int_value__in=[1, 2])
self.assertEqual(
set(TestObj.objects.filter(predicate)), {self.obj1, self.obj2})
self.assertEqual(
set(predicate.filter(self.objects)), {self.obj1, self.obj2})

def test_exclude(self):
predicate = OrmP(int_value=3)
self.assertEqual(
set(TestObj.objects.exclude(predicate)),
{self.obj1, self.obj2})
self.assertEqual(
set(predicate.exclude(self.objects)),
{self.obj1, self.obj2})

predicate = OrmP(int_value=1)
self.assertEqual(set(TestObj.objects.exclude(predicate)), {self.obj2})
self.assertEqual(set(predicate.exclude(self.objects)), {self.obj2})

predicate = OrmP(int_value=2)
self.assertEqual(set(TestObj.objects.exclude(predicate)), {self.obj1})
self.assertEqual(set(predicate.exclude(self.objects)), {self.obj1})

predicate = OrmP(int_value__in=[1, 2])
self.assertEqual(set(TestObj.objects.exclude(predicate)), set())
self.assertEqual(set(predicate.exclude(self.objects)), set())

0 comments on commit bf37975

Please sign in to comment.