Permalink
Browse files

Stored filters extracted from grnaular_access as independent app.

  • Loading branch information...
0 parents commit 89efb3bfa03ed8ec2123c8309ad3d5007ffc650c @l0kix2 committed Nov 25, 2012
@@ -0,0 +1,3 @@
+.idea
+*.pyc
+
21 LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 Kirill Sibirev <k.sibirev@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
@@ -0,0 +1,21 @@
+
+Stored filters is django app for storing model filters in database using json
+representation.
+
+
+Quick start
+-----------
+
+1. Add "stored_filters" to your INSTALLED_APPS setting like this::
+
+ INSTALLED_APPS = (
+ ...
+ 'stored_filters',
+ )
+
+2. Run south command for createing tables in database::
+
+ ./manage.py migrate stored_filters
+
+
+
@@ -0,0 +1,3 @@
+django>=1.3
+south
+-e git://github.com/bradjasper/django-jsonfield.git
@@ -0,0 +1,33 @@
+import os
+from distutils.core import setup
+
+
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
+README_FILEPATH = os.path.join(CURRENT_DIR, 'README.rst')
+
+
+setup(
+ name='django-stored-filters',
+ version='0.1',
+ packages=[
+ 'stored_filters',
+ 'stored_filters.tests',
+ 'stored_filters.migrations'
+ ],
+ author='Kirill Sibirev',
+ author_email='k.sibirev@gmail.com',
+ url='https://github.com/l0kix2/django-stored_filters',
+ description='Django model filters, stored in json',
+ long_description=open(README_FILEPATH).read(),
+ keywords=['django', 'filters', 'queryset', 'json'],
+ license='MIT',
+ classifiers=[
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Programming Language :: Python',
+ 'Intended Audience :: Developers',
+ 'Development Status :: 4 - Beta',
+ 'Operating System :: OS Independent',
+ 'License :: OSI Approved :: MIT License',
+ ],
+)
@@ -0,0 +1,2 @@
+# coding: utf-8
+from __future__ import unicode_literals
@@ -0,0 +1,7 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from django.contrib import admin
+from .models import Filter
+
+admin.site.register(Filter)
@@ -0,0 +1,2 @@
+# coding: utf-8
+from __future__ import unicode_literals
@@ -0,0 +1,27 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from django.db import models
+from jsonfield import JSONField
+
+class Filter(models.Model):
+ description = models.CharField(
+ verbose_name='short description',
+ max_length=255,
+ blank=True,
+ )
+ conditions = JSONField(
+ verbose_name='filter conditions',
+ blank=True, null=True,
+ )
+ exclusions = JSONField(
+ verbose_name='exclude conditions',
+ blank=True, null=True,
+ )
+ content_type = models.ForeignKey(
+ verbose_name='content type',
+ to='contenttypes.ContentType',
+ )
+
+ def __unicode__(self):
+ return "[%s] %s" % (self.content_type, self.description)
@@ -0,0 +1,60 @@
+# coding: utf-8
+from __future__ import unicode_literals
+import re
+import operator
+
+from django.db.models import Q
+
+class QueryBuilder(object):
+
+ def __init__(self, user):
+ self.user = user
+
+ def convert_lookups_to_Q(self, lookups):
+ lookups = self.exclude_blank(lookups)
+ if not lookups:
+ return None
+
+ return reduce(
+ operator.or_,
+ map(self.merge_conditions_and_exclusions_to_Q, lookups),
+ Q()
+ )
+
+ def exclude_blank(self, lookups):
+ def is_not_empty(pair):
+ return (pair['conditions'] is not None or
+ pair['exclusions'] is not None)
+
+ return filter(is_not_empty, lookups)
+
+ def merge_conditions_and_exclusions_to_Q(self, lookup_pair):
+ return (
+ self.convert_lookup_dicts_list_to_Q(lookup_pair['conditions']) &
+ ~self.convert_lookup_dicts_list_to_Q(lookup_pair['exclusions'])
+ )
+
+ def convert_lookup_dicts_list_to_Q(self, lookup_dicts):
+ if not isinstance(lookup_dicts, (list, tuple)):
+ lookup_dicts = [lookup_dicts]
+ return reduce(
+ operator.or_,
+ map(self.convert_lookup_dict_to_Q, lookup_dicts),
+ Q()
+ )
+
+ def convert_lookup_dict_to_Q(self, lookup_dict):
+ if lookup_dict is None:
+ return Q()
+ self.process_macros(lookup_dict)
+ return Q(**lookup_dict)
+
+ def process_macros(self, lookup_dict):
+ macro_regex = re.compile(r'^%(?P<attribute>\w+)%$')
+ for key, value in lookup_dict.iteritems():
+ match = macro_regex.match(value)
+ if match:
+ attribute = match.group('attribute')
+ user_attribute_value = getattr(self.user, attribute)
+ lookup_dict[key] = user_attribute_value
+
@@ -0,0 +1,4 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from .query import *
@@ -0,0 +1,8 @@
+# coding: utf-8
+from __future__ import unicode_literals
+from django.test import TestCase
+
+class BaseTest(TestCase):
+ def assertQuerysetEqualsList(self, queryset, list_):
+ self.assertEqual(set(queryset.values_list('id', flat=True)),
+ set(obj.id for obj in list_))
@@ -0,0 +1,141 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from django.contrib.auth.models import User
+
+from .base import BaseTest
+from ..query import QueryBuilder
+
+
+class LookupConvertionTest(BaseTest):
+ def setUp(self):
+ self.batman = User.objects.create_user(username='batman',
+ email='batman@gotham.us')
+ self.joker = User.objects.create_user(username='joker',
+ email='joker@gotham.us')
+ self.builder = QueryBuilder(user=self.batman)
+
+ def test_process_macros(self):
+ lookup_dict = {'username': '%username%',
+ 'email': '%email%',
+ 'some_field': 'some_value'}
+
+ self.builder.process_macros(lookup_dict)
+
+ self.assertEqual(lookup_dict, {
+ 'username': 'batman',
+ 'email': 'batman@gotham.us',
+ 'some_field': 'some_value'
+ })
+
+ def test_convert_lookup_dict_to_Q(self):
+ lookup_dict = {'username__startswith': 'bat',
+ 'email__endswith': 'us'}
+
+ query = self.builder.convert_lookup_dict_to_Q(lookup_dict)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman]
+ )
+
+ def test_convert_lookup_dicts_list_to_Q(self):
+ lookup_dicts = [
+ {'username__startswith': 'bat'},
+ {'email__endswith': 'gotham.us'},
+ ]
+ query = self.builder.convert_lookup_dicts_list_to_Q(lookup_dicts)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman, self.joker]
+ )
+
+ def test_convert_lookup_dicts_list_to_Q_not_list(self):
+ alone_dict = {'username__startswith': 'bat'}
+
+ query = self.builder.convert_lookup_dicts_list_to_Q(alone_dict)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman]
+ )
+
+ def test_merge_conditions_and_exclusions_to_Q(self):
+ lookup_pair = {
+ 'conditions': [{'email__endswith': 'gotham.us'}],
+ 'exclusions': [{'username__startswith': 'bat'}],
+ }
+ query = self.builder.merge_conditions_and_exclusions_to_Q(lookup_pair)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.joker]
+ )
+
+ def test_merge_conditions_and_exclusions_to_Q_conditions_only(self):
+ lookup_pair = {
+ 'conditions': [{'email__endswith': 'gotham.us'}],
+ 'exclusions': None,
+ }
+ query = self.builder.merge_conditions_and_exclusions_to_Q(lookup_pair)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman, self.joker]
+ )
+
+ def test_merge_conditions_and_exclusions_to_Q_exclusions_only(self):
+ lookup_pair = {
+ 'conditions': None,
+ 'exclusions': [{'username__startswith': 'bat'}],
+ }
+
+ query = self.builder.merge_conditions_and_exclusions_to_Q(lookup_pair)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.joker]
+ )
+
+# def test_merge_conditions_and_exclusions_to_Q_blank_filters(self):
+# lookup_pair = {
+# 'conditions': None,
+# 'exclusions': None,
+# }
+#
+# query = self.builder.merge_conditions_and_exclusions_to_Q(lookup_pair)
+#
+# self.assertIsNone(query)
+
+ def test_convert_lookups_to_Q(self):
+ lookups = [{
+ 'conditions': [{'username__startswith': 'batman'}],
+ 'exclusions': [{'email__startswith': 'j'}],
+ },{
+ 'conditions': [{'username__startswith': 'joker'}],
+ 'exclusions': None,
+ }]
+
+ query = self.builder.convert_lookups_to_Q(lookups)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman, self.joker]
+ )
+
+ def test_convert_lookups_to_Q_with_blank_filters(self):
+ lookups = [{
+ 'conditions': [{'username__startswith': 'batman'}],
+ 'exclusions': [{'email__startswith': 'j'}],
+ },{
+ 'conditions': None,
+ 'exclusions': None,
+ }]
+
+ query = self.builder.convert_lookups_to_Q(lookups)
+
+ self.assertQuerysetEqualsList(
+ User.objects.filter(query),
+ [self.batman]
+ )

0 comments on commit 89efb3b

Please sign in to comment.