Skip to content

Commit

Permalink
Filters can accept fields on right part.
Browse files Browse the repository at this point in the history
In CollectionManager, left part of a filter is the field's name, and
the right part is the value, used to get the key holding its index.
In ExtendedCollectionManager, the right part can also be a
SingleValueField (StringField, HashableField or PKField), and the value
will be fetched from Redis only when the collection is really called.
  • Loading branch information
twidi committed Sep 15, 2012
1 parent 43b0572 commit cdd16d9
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1401,4 +1401,5 @@ Now you can do this also in collection (if you use ExtendedCollectionManager_):

- the `by` argument of the `sort` method can be a field, and not only a field name
- the `by_score` arguement of the `sort` method can be a SortedSetField_ (attached to an instance), not only the key of a Redis_ sorted set
- arguments of the `intersect` method can be python list(etc...) but also multi-values RedisField_
- arguments of the `intersect` method can be python list(etc...) but also multi-values `RedisField`
- the right part of filters (passed when calling `collection` or `filter`) can also be a `RedisField`, not only a value. If a `RedisField` (specifically a `SingleValueField`), its value will be fetched from Redis_ only when the collection will be really called
45 changes: 43 additions & 2 deletions limpyd/contrib/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from itertools import islice, chain

from limpyd.collection import CollectionManager
from limpyd.fields import SetField, ListField, SortedSetField, MultiValuesField, RedisField
from limpyd.fields import (SetField, ListField, SortedSetField, MultiValuesField,
RedisField, SingleValueField, PKField)
from limpyd.contrib.database import PipelineDatabase


Expand Down Expand Up @@ -88,6 +89,12 @@ def _prepare_sets(self, sets):
for set_ in sets:
if isinstance(set_, basestring):
all_sets.add(set_)
elif isinstance(set_, SingleValueField):
# If a simple field, we retrieve the actual value to get the
# set to use
value = set_.proxy_get()
key = set_.index_key(value)
all_sets.add(key)
elif isinstance(set_, SetField):
# Use the set key. If we need to intersect, we'll use
# sunionstore, and if not, store accepts set
Expand Down Expand Up @@ -298,7 +305,7 @@ def _prepare_results(self, results):
retrieve, or slice)
"""
# if we want a result sorted by a score, and if we have a full result
# (no slice or values), we can do it know, by creating keys for each
# (no slice or values), we can do it know, by creating keys for each
# values with the sorted set score, and sort on them
if self._sort_by_sortedset and not (self._slice or self._values) and len(results) > 1:
conn = self.cls.get_connection()
Expand Down Expand Up @@ -353,3 +360,37 @@ def _get_final_set(self, sets, pk, sort_options):
keys_to_delete_later += tmp_keys

return final_set, keys_to_delete_later

def _add_filters(self, **filters):
"""
In addition to the normal _add_filters, this one accept RedisField objects
on the right part of a filter. The value will be fetched from redis when
calling the collection.
"""
string_filters = filters.copy()

for field_name, value in filters.iteritems():
if isinstance(value, RedisField):
if not isinstance(value, SingleValueField) or getattr(value, '_instance', None) is None:
raise ValueError('The right part of a filter must be a '
'a value, or a simple value field attached '
'to an instance')
if isinstance(value, PKField):
self._lazy_collection['pks'].add(value)
else:
self._lazy_collection['sets'].add(value)
string_filters.pop(field_name)

super(ExtendedCollectionManager, self)._add_filters(**string_filters)

return self

def _get_pk(self):
"""
Override the default _get_pk method to retrieve the real pk value if we
have a PKField instead of a pk value
"""
pk = super(ExtendedCollectionManager, self)._get_pk()
if pk is not None and isinstance(pk, PKField):
pk = pk.get()
return pk
15 changes: 14 additions & 1 deletion tests/contrib/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_extended_collection_should_work_as_simple_one(self):
self.assertEqual(active_names, ['bar', 'foo'])


class EnhancementTest(BaseTest):
class FieldOrValueTest(BaseTest):

def test_sort_should_accept_field_or_fieldname(self):
# test with field name
Expand All @@ -91,6 +91,19 @@ def test_sort_should_accept_field_or_fieldname(self):
groups = list(Group.collection().sort(by=name_field, alpha=True).values_list('name', flat=True))
self.assertEqual(groups, ['bar', 'baz', 'foo', 'qux'])

def test_filter_should_accept_field_or_value(self):
group = Group(name='aaa')
collection = Group.collection(name=group.name) # pass the name, but value will be get when calling the collection
group.name.hset('foo')
attended = set(['1', group.pk.get()])
self.assertEqual(set(collection), attended)

def test_filter_should_accept_pkfield_or_pkvalue(self):
group = Group()
collection = Group.collection(pk=group.pk) # pass the pk, but value will be get when calling the collection
group.name.hset('aaa') # create a pk for the object
self.assertEqual(list(collection), [group.pk.get()])


class FilterTest(BaseTest):

Expand Down

0 comments on commit cdd16d9

Please sign in to comment.