Commits

Waldemar Kornewald  committed 047054b

moved djangotoolbox into subfolder and added a setup.py script

  • Participants
  • Parent commits a3ee519

Comments (0)

Files changed (38)

 syntax: glob
+build
+dist
+*.egg-info
 .project
 .pydevproject
 .settings

File __init__.py

Empty file removed.

File db/__init__.py

Empty file removed.

File db/base.py

-import datetime
-from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations, \
-    BaseDatabaseWrapper, BaseDatabaseClient, BaseDatabaseValidation, \
-    BaseDatabaseIntrospection
-
-from .creation import NonrelDatabaseCreation
-
-class NonrelDatabaseFeatures(BaseDatabaseFeatures):
-    def __init__(self, connection):
-        self.connection = connection
-        super(NonrelDatabaseFeatures, self).__init__()
-
-    distinguishes_insert_from_update = False
-    supports_deleting_related_objects = False
-    string_based_auto_field = False
-
-class NonrelDatabaseOperations(BaseDatabaseOperations):
-    def __init__(self, connection):
-        self.connection = connection
-        super(NonrelDatabaseOperations, self).__init__()
-
-    def quote_name(self, name):
-        return name
-
-    def value_to_db_date(self, value):
-        # value is a date here, no need to check it
-        return value
-
-    def value_to_db_datetime(self, value):
-        # value is a datetime here, no need to check it
-        return value
-
-    def value_to_db_time(self, value):
-        # value is a time here, no need to check it
-        return value
-
-    def prep_for_like_query(self, value):
-        return value
-
-    def check_aggregate_support(self, aggregate):
-        # TODO: Only COUNT(*) should be supported, by default.
-        # Raise NotImplementedError in all other cases.
-        pass
-
-    def year_lookup_bounds(self, value): 
-        return [datetime.datetime(value, 1, 1, 0, 0, 0, 0),
-                datetime.datetime(value+1, 1, 1, 0, 0, 0, 0)]
-
-    def value_to_db_auto(self, value):
-        """
-        Transform a value to an object compatible with the AutoField required
-        by the backend driver for auto columns.
-        """
-        if self.connection.features.string_based_auto_field:
-            if value is None:
-                return None
-            return unicode(value)
-        return super(NonrelDatabaseOperations, self).value_to_db_auto(value)
-
-class NonrelDatabaseClient(BaseDatabaseClient):
-    pass
-
-class NonrelDatabaseValidation(BaseDatabaseValidation):
-    pass
-
-class NonrelDatabaseIntrospection(BaseDatabaseIntrospection):
-    def table_names(self):
-        """Returns a list of names of all tables that exist in the database."""
-        return self.django_table_names()
-
-class FakeCursor(object):
-    def __getattribute__(self, name):
-        raise NotImplementedError('Cursors not supported')
-
-    def __setattr__(self, name, value):
-        raise NotImplementedError('Cursors not supported')
-
-class NonrelDatabaseWrapper(BaseDatabaseWrapper):
-    def _cursor(self):
-        return FakeCursor()

File db/basecompiler.py

-from django.conf import settings
-from django.db.models.sql import aggregates as sqlaggregates
-from django.db.models.sql.compiler import SQLCompiler
-from django.db.models.sql.constants import LOOKUP_SEP, MULTI, SINGLE
-from django.db.models.sql.where import AND, OR
-from django.db.utils import DatabaseError, IntegrityError
-from django.utils.tree import Node
-import random
-
-EMULATED_OPS = {
-    'exact': lambda x, y: y in x if isinstance(x, (list,tuple)) else x == y,
-    'iexact': lambda x, y: x.lower() == y.lower(),
-    'startswith': lambda x, y: x.startswith(y),
-    'istartswith': lambda x, y: x.lower().startswith(y.lower()),
-    'isnull': lambda x, y: x is None if y else x is not None,
-    'in': lambda x, y: x in y,
-    'lt': lambda x, y: x < y,
-    'lte': lambda x, y: x <= y,
-    'gt': lambda x, y: x > y,
-    'gte': lambda x, y: x >= y,
-}
-
-class NonrelQuery(object):
-    # ----------------------------------------------
-    # Public API
-    # ----------------------------------------------
-    def __init__(self, compiler, fields):
-        self.fields = fields
-        self.compiler = compiler
-        self.connection = compiler.connection
-        self.query = self.compiler.query
-        self._negated = False
-
-    def fetch(self, low_mark=0, high_mark=None):
-        raise NotImplementedError('Not implemented')
-
-    def count(self, limit=None):
-        raise NotImplementedError('Not implemented')
-
-    def delete(self):
-        raise NotImplementedError('Not implemented')
-
-    def order_by(self, ordering):
-        raise NotImplementedError('Not implemented')
-
-    # Used by add_filters()
-    def add_filter(self, column, lookup_type, negated, db_type, value):
-        raise NotImplementedError('Not implemented')
-
-    # This is just a default implementation. You might want to override this
-    # in case your backend supports OR queries
-    def add_filters(self, filters):
-        """Traverses the given Where tree and adds the filters to this query"""
-        if filters.negated:
-            self._negated = not self._negated
-
-        if not self._negated and filters.connector != AND:
-            raise DatabaseError('Only AND filters are supported')
-
-        # Remove unneeded children from tree
-        children = self._get_children(filters.children)
-
-        if self._negated and filters.connector != OR and len(children) > 1:
-            raise DatabaseError("When negating a whole filter subgroup "
-                                "(e.g., a Q object) the subgroup filters must "
-                                "be connected via OR, so the non-relational "
-                                "backend can convert them like this: "
-                                '"not (a OR b) => (not a) AND (not b)".')
-
-        for child in children:
-            if isinstance(child, Node):
-                self.add_filters(child)
-                continue
-
-            column, lookup_type, db_type, value = self._decode_child(child)
-            self.add_filter(column, lookup_type, self._negated, db_type, value)
-
-        if filters.negated:
-            self._negated = not self._negated
-
-    # ----------------------------------------------
-    # Internal API for reuse by subclasses
-    # ----------------------------------------------
-    def _decode_child(self, child):
-        constraint, lookup_type, annotation, value = child
-        packed, value = constraint.process(lookup_type, value, self.connection)
-        alias, column, db_type = packed
-        value = self._normalize_lookup_value(value, annotation, lookup_type)
-        return column, lookup_type, db_type, value
-
-    def _normalize_lookup_value(self, value, annotation, lookup_type):
-        # Django fields always return a list (see Field.get_db_prep_lookup)
-        # except if get_db_prep_lookup got overridden by a subclass
-        if lookup_type not in ('in', 'range', 'year') and isinstance(value, (tuple, list)):
-            if len(value) > 1:
-                raise DatabaseError('Filter lookup type was: %s. Expected the '
-                                'filters value not to be a list. Only "in"-filters '
-                                'can be used with lists.'
-                                % lookup_type)
-            elif lookup_type == 'isnull':
-                value = annotation
-            else:
-                value = value[0]
-
-        if isinstance(value, unicode):
-            value = unicode(value)
-        elif isinstance(value, str):
-            value = str(value)
-
-        if lookup_type in ('startswith', 'istartswith'):
-            value = value[:-1]
-        elif lookup_type in ('endswith', 'iendswith'):
-            value = value[1:]
-        elif lookup_type in ('contains', 'icontains'):
-            value = value[1:-1]
-
-        return value
-
-    def _get_children(self, children):
-        # Filter out nodes that were automatically added by sql.Query, but are
-        # not necessary with emulated negation handling code
-        result = []
-        for child in children:
-            if isinstance(child, Node) and child.negated and \
-                    len(child.children) == 1 and \
-                    isinstance(child.children[0], tuple):
-                node, lookup_type, annotation, value = child.children[0]
-                if lookup_type == 'isnull' and value == True and node.field is None:
-                    continue
-            result.append(child)
-        return result
-
-    def _matches_filters(self, entity, filters):
-        # Filters without rules match everything
-        if not filters.children:
-            return True
-
-        result = filters.connector == AND
-
-        for child in filters.children:
-            if isinstance(child, Node):
-                submatch = self._matches_filters(entity, child)
-            else:
-                constraint, lookup_type, annotation, value = child
-                packed, value = constraint.process(lookup_type, value, self.connection)
-                alias, column, db_type = packed
-
-                # Django fields always return a list (see Field.get_db_prep_lookup)
-                # except if get_db_prep_lookup got overridden by a subclass
-                if lookup_type != 'in' and isinstance(value, (tuple, list)):
-                    if len(value) > 1:
-                        raise DatabaseError('Filter lookup type was: %s. '
-                            'Expected the filters value not to be a list. '
-                            'Only "in"-filters can be used with lists.'
-                            % lookup_type)
-                    elif lookup_type == 'isnull':
-                        value = annotation
-                    else:
-                        value = value[0]
-
-                submatch = EMULATED_OPS[lookup_type](entity[column], value)
-
-            if filters.connector == OR and submatch:
-                result = True
-                break
-            elif filters.connector == AND and not submatch:
-                result = False
-                break
-
-        if filters.negated:
-            return not result
-        return result
-
-    def _order_in_memory(self, lhs, rhs):
-        for order in self.compiler._get_ordering():
-            if LOOKUP_SEP in order:
-                raise DatabaseError("JOINs in ordering not supported (%s)" % order)
-            if order == '?':
-                result = random.choice([1, 0, -1])
-            else:
-                column = order.lstrip('-')
-                result = cmp(lhs.get(column), rhs.get(column))
-                if order.startswith('-'):
-                    result *= -1
-            if result != 0:
-                return result
-        return 0
-
-    def convert_value_from_db(self, db_type, value):
-        return self.compiler.convert_value_from_db(db_type, value)
-
-    def convert_value_for_db(self, db_type, value):
-        return self.compiler.convert_value_for_db(db_type, value)
-
-class NonrelCompiler(SQLCompiler):
-    """
-    Base class for non-relational compilers. Provides in-memory filter matching
-    and ordering. Entities are assumed to be dictionaries where the keys are
-    column names.
-    """
-
-    # ----------------------------------------------
-    # Public API
-    # ----------------------------------------------
-    def results_iter(self):
-        """
-        Returns an iterator over the results from executing this query.
-        """
-        fields = self.get_fields()
-        low_mark = self.query.low_mark
-        high_mark = self.query.high_mark
-        for entity in self.build_query(fields).fetch(low_mark, high_mark):
-            yield self._make_result(entity, fields)
-
-    def _make_result(self, entity, fields):
-        result = []
-        for field in fields:
-            if not field.null and entity.get(field.column,
-                    field.get_default()) is None:
-                raise DatabaseError("Non-nullable field %s can't be None!" % field.name)
-            result.append(self.convert_value_from_db(field.db_type(
-                connection=self.connection), entity.get(field.column, field.get_default())))
-        return result
-
-    def has_results(self):
-        return self.get_count(check_exists=True)
-
-    def execute_sql(self, result_type=MULTI):
-        """
-        Handles aggregate/count queries
-        """
-        aggregates = self.query.aggregate_select.values()
-        # Simulate a count()
-        if aggregates:
-            assert len(aggregates) == 1
-            aggregate = aggregates[0]
-            assert isinstance(aggregate, sqlaggregates.Count)
-            meta = self.query.get_meta()
-            assert aggregate.col == '*' or aggregate.col == (meta.db_table, meta.pk.column)
-            count = self.get_count()
-            if result_type is SINGLE:
-                return [count]
-            elif result_type is MULTI:
-                return [[count]]
-        raise NotImplementedError('The database backend only supports count() queries')
-
-    # ----------------------------------------------
-    # Additional NonrelCompiler API
-    # ----------------------------------------------
-    def get_count(self, check_exists=False):
-        """
-        Counts matches using the current filter constraints.
-        """
-        if check_exists:
-            high_mark = 1
-        else:
-            high_mark = self.query.high_mark
-        return self.build_query().count(high_mark)
-
-    def build_query(self, fields=None):
-        if fields is None:
-            fields = self.get_fields()
-        query = self.query_class(self, fields)
-        query.add_filters(self.query.where)
-        query.order_by(self._get_ordering())
-
-        # This at least satisfies the most basic unit tests
-        if settings.DEBUG:
-            self.connection.queries.append({'sql': repr(query)})
-        return query
-
-    def get_fields(self):
-        """
-        Returns the fields which should get loaded from the backend by self.query        
-        """
-        # We only set this up here because
-        # related_select_fields isn't populated until
-        # execute_sql() has been called.
-        if self.query.select_fields:
-            fields = self.query.select_fields + self.query.related_select_fields
-        else:
-            fields = self.query.model._meta.fields
-        # If the field was deferred, exclude it from being passed
-        # into `resolve_columns` because it wasn't selected.
-        only_load = self.deferred_to_columns()
-        if only_load:
-            db_table = self.query.model._meta.db_table
-            fields = [f for f in fields if db_table in only_load and
-                      f.column in only_load[db_table]]
-        return fields
-
-    def _get_ordering(self):
-        if not self.query.default_ordering:
-            ordering = self.query.order_by
-        else:
-            ordering = self.query.order_by or self.query.get_meta().ordering
-        result = []
-        for order in ordering:
-            if LOOKUP_SEP in order:
-                raise DatabaseError("Ordering can't span tables on non-relational backends (%s)" % order)
-            if order == '?':
-                raise DatabaseError("Randomized ordering isn't supported by the backend")
-
-            order = order.lstrip('+')
-
-            descending = order.startswith('-')
-            name = order.lstrip('-')
-            if name == 'pk':
-                name = self.query.get_meta().pk.name
-                order = '-' + name if descending else name
-
-            if self.query.standard_ordering:
-                result.append(order)
-            else:
-                if descending:
-                    result.append(name)
-                else:
-                    result.append('-' + name)
-        return result
-
-class NonrelInsertCompiler(object):
-    def execute_sql(self, return_id=False):
-        data = {}
-        for (field, value), column in zip(self.query.values, self.query.columns):
-            if field is not None:
-                if not field.null and value is None:
-                    raise DatabaseError("You can't set %s (a non-nullable "
-                                        "field) to None!" % field.name)
-                value = self.convert_value_for_db(field.db_type(connection=self.connection),
-                    value)
-            data[column] = value
-        return self.insert(data, return_id=return_id)
-
-class NonrelUpdateCompiler(object):
-    def execute_sql(self, result_type=MULTI):
-        # TODO: We don't yet support QuerySet.update() in Django-nonrel
-        raise NotImplementedError('No updates')
-
-class NonrelDeleteCompiler(object):
-    def execute_sql(self, result_type=MULTI):
-        self.build_query([self.query.get_meta().pk]).delete()

File db/creation.py

-from django.db.backends.creation import BaseDatabaseCreation
-
-class NonrelDatabaseCreation(BaseDatabaseCreation):
-    data_types = {
-        'DateTimeField':     'datetime',
-        'DateField':         'date',
-        'TimeField':         'time',
-        'FloatField':        'float',
-        'EmailField':        'text',
-        'URLField':          'text',
-        'BooleanField':      'bool',
-        'NullBooleanField':  'bool',
-        'CharField':         'text',
-        'CommaSeparatedIntegerField': 'text',
-        'IPAddressField':    'text',
-        'SlugField':         'text',
-        'FileField':         'text',
-        'FilePathField':     'text',
-        'TextField':         'longtext',
-        'XMLField':          'longtext',
-        'IntegerField':      'integer',
-        'SmallIntegerField': 'integer',
-        'PositiveIntegerField': 'integer',
-        'PositiveSmallIntegerField': 'integer',
-        'BigIntegerField':   'long',
-        'AutoField':         'integer',
-        'OneToOneField':     'integer',
-        'DecimalField':      'decimal:%(max_digits)s,%(decimal_places)s',
-        'BlobField':         'blob',
-#        'ImageField':
-    }

File dbproxy/__init__.py

Empty file removed.

File dbproxy/base.py

-from django.db import connections
-
-class Proxy(object):
-    def __init__(self, target):
-        self.__target = target
-    
-    def __getattr__(self, name):
-        return getattr(self.__target, name)
-
-class DatabaseWrapper(Proxy):
-    def __init__(self, settings_dict, *args, **kwds):
-        super(DatabaseWrapper, self).__init__(
-            connections[settings_dict['TARGET_BACKEND']])

File dbproxy/compiler.py

-from .base import Proxy
-from django.conf import settings
-from django.db.models.sql import aggregates as sqlaggregates
-from django.db.models.sql.constants import LOOKUP_SEP, MULTI, SINGLE
-from django.db.models.sql.where import AND, OR
-from django.db.utils import DatabaseError, IntegrityError
-from django.utils.tree import Node
-
-class SQLCompiler(Proxy):
-    def execute_sql(self, result_type=MULTI):
-        """
-        Handles aggregate/count queries
-        """
-        pass
-
-    def results_iter(self):
-        """
-        Returns an iterator over the results from executing this query.
-        """
-        pass
-
-    def has_results(self):
-        pass
-
-class SQLInsertCompiler(Proxy):
-    def execute_sql(self, return_id=False):
-        pass
-
-class SQLUpdateCompiler(Proxy):
-    def execute_sql(self, result_type=MULTI):
-        pass
-
-class SQLDeleteCompiler(Proxy):
-    def execute_sql(self, result_type=MULTI):
-        pass

File djangotoolbox/__init__.py

Empty file added.

File djangotoolbox/db/__init__.py

Empty file added.

File djangotoolbox/db/base.py

+import datetime
+from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations, \
+    BaseDatabaseWrapper, BaseDatabaseClient, BaseDatabaseValidation, \
+    BaseDatabaseIntrospection
+
+from .creation import NonrelDatabaseCreation
+
+class NonrelDatabaseFeatures(BaseDatabaseFeatures):
+    def __init__(self, connection):
+        self.connection = connection
+        super(NonrelDatabaseFeatures, self).__init__()
+
+    distinguishes_insert_from_update = False
+    supports_deleting_related_objects = False
+    string_based_auto_field = False
+
+class NonrelDatabaseOperations(BaseDatabaseOperations):
+    def __init__(self, connection):
+        self.connection = connection
+        super(NonrelDatabaseOperations, self).__init__()
+
+    def quote_name(self, name):
+        return name
+
+    def value_to_db_date(self, value):
+        # value is a date here, no need to check it
+        return value
+
+    def value_to_db_datetime(self, value):
+        # value is a datetime here, no need to check it
+        return value
+
+    def value_to_db_time(self, value):
+        # value is a time here, no need to check it
+        return value
+
+    def prep_for_like_query(self, value):
+        return value
+
+    def check_aggregate_support(self, aggregate):
+        # TODO: Only COUNT(*) should be supported, by default.
+        # Raise NotImplementedError in all other cases.
+        pass
+
+    def year_lookup_bounds(self, value): 
+        return [datetime.datetime(value, 1, 1, 0, 0, 0, 0),
+                datetime.datetime(value+1, 1, 1, 0, 0, 0, 0)]
+
+    def value_to_db_auto(self, value):
+        """
+        Transform a value to an object compatible with the AutoField required
+        by the backend driver for auto columns.
+        """
+        if self.connection.features.string_based_auto_field:
+            if value is None:
+                return None
+            return unicode(value)
+        return super(NonrelDatabaseOperations, self).value_to_db_auto(value)
+
+class NonrelDatabaseClient(BaseDatabaseClient):
+    pass
+
+class NonrelDatabaseValidation(BaseDatabaseValidation):
+    pass
+
+class NonrelDatabaseIntrospection(BaseDatabaseIntrospection):
+    def table_names(self):
+        """Returns a list of names of all tables that exist in the database."""
+        return self.django_table_names()
+
+class FakeCursor(object):
+    def __getattribute__(self, name):
+        raise NotImplementedError('Cursors not supported')
+
+    def __setattr__(self, name, value):
+        raise NotImplementedError('Cursors not supported')
+
+class NonrelDatabaseWrapper(BaseDatabaseWrapper):
+    def _cursor(self):
+        return FakeCursor()

File djangotoolbox/db/basecompiler.py

+from django.conf import settings
+from django.db.models.sql import aggregates as sqlaggregates
+from django.db.models.sql.compiler import SQLCompiler
+from django.db.models.sql.constants import LOOKUP_SEP, MULTI, SINGLE
+from django.db.models.sql.where import AND, OR
+from django.db.utils import DatabaseError, IntegrityError
+from django.utils.tree import Node
+import random
+
+EMULATED_OPS = {
+    'exact': lambda x, y: y in x if isinstance(x, (list,tuple)) else x == y,
+    'iexact': lambda x, y: x.lower() == y.lower(),
+    'startswith': lambda x, y: x.startswith(y),
+    'istartswith': lambda x, y: x.lower().startswith(y.lower()),
+    'isnull': lambda x, y: x is None if y else x is not None,
+    'in': lambda x, y: x in y,
+    'lt': lambda x, y: x < y,
+    'lte': lambda x, y: x <= y,
+    'gt': lambda x, y: x > y,
+    'gte': lambda x, y: x >= y,
+}
+
+class NonrelQuery(object):
+    # ----------------------------------------------
+    # Public API
+    # ----------------------------------------------
+    def __init__(self, compiler, fields):
+        self.fields = fields
+        self.compiler = compiler
+        self.connection = compiler.connection
+        self.query = self.compiler.query
+        self._negated = False
+
+    def fetch(self, low_mark=0, high_mark=None):
+        raise NotImplementedError('Not implemented')
+
+    def count(self, limit=None):
+        raise NotImplementedError('Not implemented')
+
+    def delete(self):
+        raise NotImplementedError('Not implemented')
+
+    def order_by(self, ordering):
+        raise NotImplementedError('Not implemented')
+
+    # Used by add_filters()
+    def add_filter(self, column, lookup_type, negated, db_type, value):
+        raise NotImplementedError('Not implemented')
+
+    # This is just a default implementation. You might want to override this
+    # in case your backend supports OR queries
+    def add_filters(self, filters):
+        """Traverses the given Where tree and adds the filters to this query"""
+        if filters.negated:
+            self._negated = not self._negated
+
+        if not self._negated and filters.connector != AND:
+            raise DatabaseError('Only AND filters are supported')
+
+        # Remove unneeded children from tree
+        children = self._get_children(filters.children)
+
+        if self._negated and filters.connector != OR and len(children) > 1:
+            raise DatabaseError("When negating a whole filter subgroup "
+                                "(e.g., a Q object) the subgroup filters must "
+                                "be connected via OR, so the non-relational "
+                                "backend can convert them like this: "
+                                '"not (a OR b) => (not a) AND (not b)".')
+
+        for child in children:
+            if isinstance(child, Node):
+                self.add_filters(child)
+                continue
+
+            column, lookup_type, db_type, value = self._decode_child(child)
+            self.add_filter(column, lookup_type, self._negated, db_type, value)
+
+        if filters.negated:
+            self._negated = not self._negated
+
+    # ----------------------------------------------
+    # Internal API for reuse by subclasses
+    # ----------------------------------------------
+    def _decode_child(self, child):
+        constraint, lookup_type, annotation, value = child
+        packed, value = constraint.process(lookup_type, value, self.connection)
+        alias, column, db_type = packed
+        value = self._normalize_lookup_value(value, annotation, lookup_type)
+        return column, lookup_type, db_type, value
+
+    def _normalize_lookup_value(self, value, annotation, lookup_type):
+        # Django fields always return a list (see Field.get_db_prep_lookup)
+        # except if get_db_prep_lookup got overridden by a subclass
+        if lookup_type not in ('in', 'range', 'year') and isinstance(value, (tuple, list)):
+            if len(value) > 1:
+                raise DatabaseError('Filter lookup type was: %s. Expected the '
+                                'filters value not to be a list. Only "in"-filters '
+                                'can be used with lists.'
+                                % lookup_type)
+            elif lookup_type == 'isnull':
+                value = annotation
+            else:
+                value = value[0]
+
+        if isinstance(value, unicode):
+            value = unicode(value)
+        elif isinstance(value, str):
+            value = str(value)
+
+        if lookup_type in ('startswith', 'istartswith'):
+            value = value[:-1]
+        elif lookup_type in ('endswith', 'iendswith'):
+            value = value[1:]
+        elif lookup_type in ('contains', 'icontains'):
+            value = value[1:-1]
+
+        return value
+
+    def _get_children(self, children):
+        # Filter out nodes that were automatically added by sql.Query, but are
+        # not necessary with emulated negation handling code
+        result = []
+        for child in children:
+            if isinstance(child, Node) and child.negated and \
+                    len(child.children) == 1 and \
+                    isinstance(child.children[0], tuple):
+                node, lookup_type, annotation, value = child.children[0]
+                if lookup_type == 'isnull' and value == True and node.field is None:
+                    continue
+            result.append(child)
+        return result
+
+    def _matches_filters(self, entity, filters):
+        # Filters without rules match everything
+        if not filters.children:
+            return True
+
+        result = filters.connector == AND
+
+        for child in filters.children:
+            if isinstance(child, Node):
+                submatch = self._matches_filters(entity, child)
+            else:
+                constraint, lookup_type, annotation, value = child
+                packed, value = constraint.process(lookup_type, value, self.connection)
+                alias, column, db_type = packed
+
+                # Django fields always return a list (see Field.get_db_prep_lookup)
+                # except if get_db_prep_lookup got overridden by a subclass
+                if lookup_type != 'in' and isinstance(value, (tuple, list)):
+                    if len(value) > 1:
+                        raise DatabaseError('Filter lookup type was: %s. '
+                            'Expected the filters value not to be a list. '
+                            'Only "in"-filters can be used with lists.'
+                            % lookup_type)
+                    elif lookup_type == 'isnull':
+                        value = annotation
+                    else:
+                        value = value[0]
+
+                submatch = EMULATED_OPS[lookup_type](entity[column], value)
+
+            if filters.connector == OR and submatch:
+                result = True
+                break
+            elif filters.connector == AND and not submatch:
+                result = False
+                break
+
+        if filters.negated:
+            return not result
+        return result
+
+    def _order_in_memory(self, lhs, rhs):
+        for order in self.compiler._get_ordering():
+            if LOOKUP_SEP in order:
+                raise DatabaseError("JOINs in ordering not supported (%s)" % order)
+            if order == '?':
+                result = random.choice([1, 0, -1])
+            else:
+                column = order.lstrip('-')
+                result = cmp(lhs.get(column), rhs.get(column))
+                if order.startswith('-'):
+                    result *= -1
+            if result != 0:
+                return result
+        return 0
+
+    def convert_value_from_db(self, db_type, value):
+        return self.compiler.convert_value_from_db(db_type, value)
+
+    def convert_value_for_db(self, db_type, value):
+        return self.compiler.convert_value_for_db(db_type, value)
+
+class NonrelCompiler(SQLCompiler):
+    """
+    Base class for non-relational compilers. Provides in-memory filter matching
+    and ordering. Entities are assumed to be dictionaries where the keys are
+    column names.
+    """
+
+    # ----------------------------------------------
+    # Public API
+    # ----------------------------------------------
+    def results_iter(self):
+        """
+        Returns an iterator over the results from executing this query.
+        """
+        fields = self.get_fields()
+        low_mark = self.query.low_mark
+        high_mark = self.query.high_mark
+        for entity in self.build_query(fields).fetch(low_mark, high_mark):
+            yield self._make_result(entity, fields)
+
+    def _make_result(self, entity, fields):
+        result = []
+        for field in fields:
+            if not field.null and entity.get(field.column,
+                    field.get_default()) is None:
+                raise DatabaseError("Non-nullable field %s can't be None!" % field.name)
+            result.append(self.convert_value_from_db(field.db_type(
+                connection=self.connection), entity.get(field.column, field.get_default())))
+        return result
+
+    def has_results(self):
+        return self.get_count(check_exists=True)
+
+    def execute_sql(self, result_type=MULTI):
+        """
+        Handles aggregate/count queries
+        """
+        aggregates = self.query.aggregate_select.values()
+        # Simulate a count()
+        if aggregates:
+            assert len(aggregates) == 1
+            aggregate = aggregates[0]
+            assert isinstance(aggregate, sqlaggregates.Count)
+            meta = self.query.get_meta()
+            assert aggregate.col == '*' or aggregate.col == (meta.db_table, meta.pk.column)
+            count = self.get_count()
+            if result_type is SINGLE:
+                return [count]
+            elif result_type is MULTI:
+                return [[count]]
+        raise NotImplementedError('The database backend only supports count() queries')
+
+    # ----------------------------------------------
+    # Additional NonrelCompiler API
+    # ----------------------------------------------
+    def get_count(self, check_exists=False):
+        """
+        Counts matches using the current filter constraints.
+        """
+        if check_exists:
+            high_mark = 1
+        else:
+            high_mark = self.query.high_mark
+        return self.build_query().count(high_mark)
+
+    def build_query(self, fields=None):
+        if fields is None:
+            fields = self.get_fields()
+        query = self.query_class(self, fields)
+        query.add_filters(self.query.where)
+        query.order_by(self._get_ordering())
+
+        # This at least satisfies the most basic unit tests
+        if settings.DEBUG:
+            self.connection.queries.append({'sql': repr(query)})
+        return query
+
+    def get_fields(self):
+        """
+        Returns the fields which should get loaded from the backend by self.query        
+        """
+        # We only set this up here because
+        # related_select_fields isn't populated until
+        # execute_sql() has been called.
+        if self.query.select_fields:
+            fields = self.query.select_fields + self.query.related_select_fields
+        else:
+            fields = self.query.model._meta.fields
+        # If the field was deferred, exclude it from being passed
+        # into `resolve_columns` because it wasn't selected.
+        only_load = self.deferred_to_columns()
+        if only_load:
+            db_table = self.query.model._meta.db_table
+            fields = [f for f in fields if db_table in only_load and
+                      f.column in only_load[db_table]]
+        return fields
+
+    def _get_ordering(self):
+        if not self.query.default_ordering:
+            ordering = self.query.order_by
+        else:
+            ordering = self.query.order_by or self.query.get_meta().ordering
+        result = []
+        for order in ordering:
+            if LOOKUP_SEP in order:
+                raise DatabaseError("Ordering can't span tables on non-relational backends (%s)" % order)
+            if order == '?':
+                raise DatabaseError("Randomized ordering isn't supported by the backend")
+
+            order = order.lstrip('+')
+
+            descending = order.startswith('-')
+            name = order.lstrip('-')
+            if name == 'pk':
+                name = self.query.get_meta().pk.name
+                order = '-' + name if descending else name
+
+            if self.query.standard_ordering:
+                result.append(order)
+            else:
+                if descending:
+                    result.append(name)
+                else:
+                    result.append('-' + name)
+        return result
+
+class NonrelInsertCompiler(object):
+    def execute_sql(self, return_id=False):
+        data = {}
+        for (field, value), column in zip(self.query.values, self.query.columns):
+            if field is not None:
+                if not field.null and value is None:
+                    raise DatabaseError("You can't set %s (a non-nullable "
+                                        "field) to None!" % field.name)
+                value = self.convert_value_for_db(field.db_type(connection=self.connection),
+                    value)
+            data[column] = value
+        return self.insert(data, return_id=return_id)
+
+class NonrelUpdateCompiler(object):
+    def execute_sql(self, result_type=MULTI):
+        # TODO: We don't yet support QuerySet.update() in Django-nonrel
+        raise NotImplementedError('No updates')
+
+class NonrelDeleteCompiler(object):
+    def execute_sql(self, result_type=MULTI):
+        self.build_query([self.query.get_meta().pk]).delete()

File djangotoolbox/db/creation.py

+from django.db.backends.creation import BaseDatabaseCreation
+
+class NonrelDatabaseCreation(BaseDatabaseCreation):
+    data_types = {
+        'DateTimeField':     'datetime',
+        'DateField':         'date',
+        'TimeField':         'time',
+        'FloatField':        'float',
+        'EmailField':        'text',
+        'URLField':          'text',
+        'BooleanField':      'bool',
+        'NullBooleanField':  'bool',
+        'CharField':         'text',
+        'CommaSeparatedIntegerField': 'text',
+        'IPAddressField':    'text',
+        'SlugField':         'text',
+        'FileField':         'text',
+        'FilePathField':     'text',
+        'TextField':         'longtext',
+        'XMLField':          'longtext',
+        'IntegerField':      'integer',
+        'SmallIntegerField': 'integer',
+        'PositiveIntegerField': 'integer',
+        'PositiveSmallIntegerField': 'integer',
+        'BigIntegerField':   'long',
+        'AutoField':         'integer',
+        'OneToOneField':     'integer',
+        'DecimalField':      'decimal:%(max_digits)s,%(decimal_places)s',
+        'BlobField':         'blob',
+#        'ImageField':
+    }

File djangotoolbox/dbproxy/__init__.py

Empty file added.

File djangotoolbox/dbproxy/base.py

+from django.db import connections
+
+class Proxy(object):
+    def __init__(self, target):
+        self.__target = target
+    
+    def __getattr__(self, name):
+        return getattr(self.__target, name)
+
+class DatabaseWrapper(Proxy):
+    def __init__(self, settings_dict, *args, **kwds):
+        super(DatabaseWrapper, self).__init__(
+            connections[settings_dict['TARGET_BACKEND']])

File djangotoolbox/dbproxy/compiler.py

+from .base import Proxy
+from django.conf import settings
+from django.db.models.sql import aggregates as sqlaggregates
+from django.db.models.sql.constants import LOOKUP_SEP, MULTI, SINGLE
+from django.db.models.sql.where import AND, OR
+from django.db.utils import DatabaseError, IntegrityError
+from django.utils.tree import Node
+
+class SQLCompiler(Proxy):
+    def execute_sql(self, result_type=MULTI):
+        """
+        Handles aggregate/count queries
+        """
+        pass
+
+    def results_iter(self):
+        """
+        Returns an iterator over the results from executing this query.
+        """
+        pass
+
+    def has_results(self):
+        pass
+
+class SQLInsertCompiler(Proxy):
+    def execute_sql(self, return_id=False):
+        pass
+
+class SQLUpdateCompiler(Proxy):
+    def execute_sql(self, result_type=MULTI):
+        pass
+
+class SQLDeleteCompiler(Proxy):
+    def execute_sql(self, result_type=MULTI):
+        pass

File djangotoolbox/errorviews.py

+from django import http
+from django.template import Context, RequestContext, loader
+
+def server_error(request, template_name='500.html'):
+    """
+    500 error handler.
+
+    Templates: `500.html`
+    Context:
+        request_path
+            The path of the requested URL (e.g., '/app/pages/bad_page/')
+    """
+    t = loader.get_template(template_name) # You need to create a 500.html template.
+    return http.HttpResponseServerError(t.render(RequestContext(request, {'request_path': request.path})))

File djangotoolbox/fields.py

+from django.db import models
+
+class ListField(models.Field):
+    def __init__(self, field_type, *args, **kwargs):
+        self.field_type = field_type
+        kwargs['default'] = lambda: None if self.null else []
+        super(ListField, self).__init__(*args, **kwargs)
+
+    def db_type(self, connection):
+        return 'ListField:' + self.field_type.db_type(connection=connection)
+
+    def call_for_each(self, function_name, values, *args, **kwargs):
+        if isinstance(values, (list, tuple)) and len(values):
+            for i, value in enumerate(values):
+                values[i] = getattr(self.field_type, function_name)(value, *args,
+                    **kwargs)
+        return values
+
+    def to_python(self, value):
+        return self.call_for_each( 'to_python', value)
+
+    def get_prep_value(self, value):
+        return self.call_for_each('get_prep_value', value)
+
+    def get_db_prep_value(self, value, connection, prepared=False):
+        return self.call_for_each('get_db_prep_value', value, connection=connection,
+            prepared=prepared)
+
+    def get_db_prep_save(self, value, connection):
+        return self.call_for_each('get_db_prep_save', value, connection=connection)
+
+    def formfield(self, **kwargs):
+        return None
+
+class BlobField(models.Field):
+    """
+    A field for storing blobs of binary data
+    """
+    def get_internal_type(self):
+        return 'BlobField'
+
+    def formfield(self, **kwargs):
+        # A file widget is provided, but use  model FileField or ImageField 
+        # for storing specific files most of the time
+        from .widgets import BlobWidget
+        from django.forms import FileField
+        defaults = {'form_class': FileField, 'widget': BlobWidget}
+        defaults.update(kwargs)
+        return super(BlobField, self).formfield(**defaults)
+
+    def get_db_prep_value(self, value, connection, prepared=False):        
+        try:
+            # Sees if the object passed in is file-like
+            return value.read()
+        except:
+            return str(value)
+
+    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
+        raise TypeError("BlobFields do not support lookups")
+
+    def value_to_string(self, obj):
+        return str(self._get_val_from_obj(obj))

File djangotoolbox/http.py

+from django.conf import settings
+from django.core.serializers.json import DjangoJSONEncoder
+from django.http import HttpResponse
+from django.utils import simplejson
+from django.utils.encoding import force_unicode
+from django.utils.functional import Promise
+
+class LazyEncoder(DjangoJSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, Promise):
+            return force_unicode(obj)
+        return super(LazyEncoder, self).default(obj)
+
+class JSONResponse(HttpResponse):
+    def __init__(self, pyobj, **kwargs):
+        super(JSONResponse, self).__init__(
+            simplejson.dumps(pyobj, cls=LazyEncoder),
+            content_type='application/json; charset=%s' %
+                            settings.DEFAULT_CHARSET,
+            **kwargs)
+
+class TextResponse(HttpResponse):
+    def __init__(self, string='', **kwargs):
+        super(TextResponse, self).__init__(string,
+            content_type='text/plain; charset=%s' % settings.DEFAULT_CHARSET,
+            **kwargs)

File djangotoolbox/middleware.py

+from django.conf import settings
+from django.http import HttpResponseRedirect
+from django.utils.cache import patch_cache_control
+
+LOGIN_REQUIRED_PREFIXES = getattr(settings, 'LOGIN_REQUIRED_PREFIXES', ())
+NO_LOGIN_REQUIRED_PREFIXES = getattr(settings, 'NO_LOGIN_REQUIRED_PREFIXES', ())
+
+class LoginRequiredMiddleware(object):
+    """
+    Redirects to login page if request path begins with a
+    LOGIN_REQURED_PREFIXES prefix. You can also specify
+    NO_LOGIN_REQUIRED_PREFIXES which take precedence.
+    """
+    def process_request(self, request):
+        for prefix in NO_LOGIN_REQUIRED_PREFIXES:
+            if request.path.startswith(prefix):
+                return None
+        for prefix in LOGIN_REQUIRED_PREFIXES:
+            if request.path.startswith(prefix) and \
+                    not request.user.is_authenticated():
+                from django.contrib.auth.views import redirect_to_login
+                return redirect_to_login(request.get_full_path())
+        return None
+
+class RedirectMiddleware(object):
+    """
+    A static redirect middleware. Mostly useful for hosting providers that
+    automatically setup an alternative domain for your website. You might
+    not want anyone to access the site via those possibly well-known URLs.
+    """
+    def process_request(self, request):
+        host = request.get_host().split(':')[0]
+        # Turn off redirects when in debug mode, running unit tests, or
+        # when handling an App Engine cron job.
+        if settings.DEBUG or host == 'testserver' or \
+                not getattr(settings, 'ALLOWED_DOMAINS', None) or \
+                request.META.get('HTTP_X_APPENGINE_CRON') == 'true':
+            return
+        if host not in settings.ALLOWED_DOMAINS:
+            return HttpResponseRedirect('http://' + settings.ALLOWED_DOMAINS[0])
+
+class NoHistoryCacheMiddleware(object):
+    """
+    If user is authenticated we disable browser caching of pages in history.
+    """
+    def process_response(self, request, response):
+        if 'Expires' not in response and \
+                'Cache-Control' not in response and \
+                hasattr(request, 'session') and \
+                request.user.is_authenticated():
+            patch_cache_control(response,
+                no_store=True, no_cache=True, must_revalidate=True, max_age=0)
+        return response

File djangotoolbox/models.py

Empty file added.

File djangotoolbox/test.py

+from .utils import object_list_to_table, equal_lists
+from django.test import TestCase
+
+class ModelTestCase(TestCase):
+    """
+    A test case for models that provides an easy way to validate the DB
+    contents against a given list of row-values.
+
+    You have to specify the model to validate using the 'model' attribute:
+    class MyTestCase(ModelTestCase):
+        model = MyModel
+    """
+    def validate_state(self, columns, *state_table):
+        """
+        Validates that the DB contains exactly the values given in the state
+        table. The list of columns is given in the columns tuple.
+
+        Example:
+        self.validate_state(
+            ('a', 'b', 'c'),
+            (1, 2, 3),
+            (11, 12, 13),
+        )
+        validates that the table contains exactly two rows and that their
+        'a', 'b', and 'c' attributes are 1, 2, 3 for one row and 11, 12, 13
+        for the other row. The order of the rows doesn't matter.
+        """
+        current_state = object_list_to_table(columns,
+            self.model.all())[1:]
+        if not equal_lists(current_state, state_table):
+            print 'DB state not valid:'
+            print 'Current state:'
+            print columns
+            for state in current_state:
+                print state
+            print 'Should be:'
+            for state in state_table:
+                print state
+            self.fail('DB state not valid')

File djangotoolbox/tests.py

+from .fields import ListField
+from django.db import models
+from django.db.models import Q
+#from django.db.utils import DatabaseError
+from django.test import TestCase
+
+class ListModel(models.Model):
+    floating_point = models.FloatField()
+    names = ListField(models.CharField(max_length=500))
+    names_with_default = ListField(models.CharField(max_length=500), default=[])
+    names_nullable = ListField(models.CharField(max_length=500), null=True)
+
+class FilterTest(TestCase):
+    floats = [5.3, 2.6, 9.1, 1.58]
+    names = [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]
+
+    def setUp(self):
+        for i, float in enumerate(FilterTest.floats):
+            ListModel(floating_point=float, names=FilterTest.names[:i+1]).save()
+
+    def test_startswith(self):
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names__startswith='Sa')],
+                          [['Kakashi', 'Naruto', 'Sasuke',],
+                            ['Kakashi', 'Naruto', 'Sasuke', 'Sakura',]])
+
+    def test_options(self):
+        self.assertEquals([entity.names_with_default for entity in
+                           ListModel.objects.filter(names__startswith='Sa')],
+                          [[], []])
+
+        # TODO: should it be NULL or None here?
+        self.assertEquals([entity.names_nullable for entity in
+                           ListModel.objects.filter(names__startswith='Sa')],
+                          [None, None])
+
+    def test_gt(self):
+        # test gt on list
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names__gt='Kakashi')],
+                          [[u'Kakashi', u'Naruto',],
+                            [u'Kakashi', u'Naruto', u'Sasuke',],
+                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+    def test_lt(self):
+        # test lt on list
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names__lt='Naruto')],
+                          [[u'Kakashi',], [u'Kakashi', u'Naruto',],
+                            [u'Kakashi', u'Naruto', u'Sasuke',],
+                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+    def test_gte(self):
+        # test gte on list
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names__gte='Sakura')],
+                          [[u'Kakashi', u'Naruto', u'Sasuke',],
+                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+    def test_lte(self):
+        # test lte on list
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names__lte='Kakashi')],
+                          [[u'Kakashi',], [u'Kakashi', u'Naruto',],
+                            [u'Kakashi', u'Naruto', u'Sasuke',],
+                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+    def test_equals(self):
+        # test equality filter on list
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.filter(names='Sakura')],
+                          [[u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+        # test with additonal pk filter (for DBs that have special pk queries)
+        query = ListModel.objects.filter(names='Sakura')
+        self.assertEquals(query.get(pk=query[0].pk).names,
+                          [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',])
+
+    def test_is_null(self):
+        self.assertEquals(ListModel.objects.filter(
+            names__isnull=True).count(), 0)
+
+    def test_exclude(self):
+        self.assertEquals([entity.names for entity in
+                           ListModel.objects.all().exclude(
+                            names__lt='Sakura')],
+                          [[u'Kakashi', u'Naruto', u'Sasuke',],
+                           [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
+
+    def test_chained_filter(self):
+        self.assertEquals([entity.names for entity in
+                          ListModel.objects.filter(names='Sasuke').filter(
+                            names='Sakura')], [['Kakashi', 'Naruto',
+                            'Sasuke', 'Sakura'],])
+
+        self.assertEquals([entity.names for entity in
+                          ListModel.objects.filter(names__startswith='Sa').filter(
+                            names='Sakura')], [['Kakashi', 'Naruto',
+                            'Sasuke', 'Sakura']])
+
+        # test across multiple columns. On app engine only one filter is allowed
+        # to be an inequality filter
+        self.assertEquals([entity.names for entity in
+                          ListModel.objects.filter(floating_point=9.1).filter(
+                            names__startswith='Sa')], [['Kakashi', 'Naruto',
+                            'Sasuke',],])
+
+# passes on production but not on sdk
+#    def test_Q_objects(self):
+#        self.assertEquals([entity.names for entity in
+#            ListModel.objects.exclude(Q(names__lt='Sakura') | Q(names__gte='Sasuke'))],
+#                [['Kakashi', 'Naruto', 'Sasuke', 'Sakura'], ])

File djangotoolbox/utils.py

+def make_tls_property(default=None):
+    """Creates a class-wide instance property with a thread-specific value."""
+    class TLSProperty(object):
+        def __init__(self):
+            from threading import local
+            self.local = local()
+
+        def __get__(self, instance, cls):
+            if not instance:
+                return self
+            return self.value
+
+        def __set__(self, instance, value):
+            self.value = value
+
+        def _get_value(self):
+            return getattr(self.local, 'value', default)
+        def _set_value(self, value):
+            self.local.value = value
+        value = property(_get_value, _set_value)
+
+    return TLSProperty()
+
+def getattr_by_path(obj, attr, *default):
+    """Like getattr(), but can go down a hierarchy like 'attr.subattr'"""
+    value = obj
+    for part in attr.split('.'):
+        if not hasattr(value, part) and len(default):
+            return default[0]
+        value = getattr(value, part)
+        if callable(value):
+            value = value()
+    return value
+
+def subdict(data, *attrs):
+    """Returns a subset of the keys of a dictionary."""
+    result = {}
+    result.update([(key, data[key]) for key in attrs])
+    return result
+
+def equal_lists(left, right):
+    """
+    Compares two lists and returs True if they contain the same elements, but
+    doesn't require that they have the same order.
+    """
+    right = list(right)
+    if len(left) != len(right):
+        return False
+    for item in left:
+        if item in right:
+            del right[right.index(item)]
+        else:
+            return False
+    return True
+
+def object_list_to_table(headings, dict_list):
+    """
+    Converts objects to table-style list of rows with heading:
+
+    Example:
+    x.a = 1
+    x.b = 2
+    x.c = 3
+    y.a = 11
+    y.b = 12
+    y.c = 13
+    object_list_to_table(('a', 'b', 'c'), [x, y])
+    results in the following (dict keys reordered for better readability):
+    [
+        ('a', 'b', 'c'),
+        (1, 2, 3),
+        (11, 12, 13),
+    ]
+    """
+    return [headings] + [tuple([getattr_by_path(row, heading, None)
+                                for heading in headings])
+                         for row in dict_list]
+
+def dict_list_to_table(headings, dict_list):
+    """
+    Converts dict to table-style list of rows with heading:
+
+    Example:
+    dict_list_to_table(('a', 'b', 'c'),
+        [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}])
+    results in the following (dict keys reordered for better readability):
+    [
+        ('a', 'b', 'c'),
+        (1, 2, 3),
+        (11, 12, 13),
+    ]
+    """
+    return [headings] + [tuple([row[heading] for heading in headings])
+                         for row in dict_list]

File djangotoolbox/views.py

+from copy import deepcopy
+import functools, re
+
+PATTERNS = {
+    'day':   r'\d{1,2}',
+    'id':    r'\d+',
+    'num':   r'\d+',
+    'month': r'\d{1,2}',
+    'mon':   r'[a-z]{3}', # jan, feb, etc...
+    'slug':  r'[\w-]+',
+    'tag':   r'\w+',
+    'year':  r'\d{4}',
+}
+
+# <name[:pattern]>
+VARIABLE = re.compile(r'<(?P<name>\w+)(?::?(?P<pattern>[^>]+))?>')
+
+class URLPatternGenerator(object):
+    def __init__(self, patterns=None, default=r'\d+',
+                 append_slash=True, anchor=True, terminate=True):
+        self.patterns = patterns or PATTERNS.copy()
+        self.default = default              # default pattern
+        self.append_slash = append_slash    # trailing /
+        self.anchor = anchor                # prepend ^
+        self.terminate = terminate          # append $
+
+    def add(self, name, pattern):
+        self.patterns[name] = pattern
+
+    def _replace(self, match, url):
+        regexp = None
+        name = match.group('name')
+        pattern = match.group('pattern')
+        # pattern may be a name or regexp
+        if pattern:
+            regexp = self.patterns.get(pattern, pattern)
+        # use pattern for name, or default
+        else:
+            regexp = self.patterns.get(name, self.default)
+        segment = '(?P<%s>%s)' % (name, regexp)
+        return segment
+
+    def __call__(self, path, *args, **kw):
+        # replacement for django.conf.urls.defaults.url
+        from django.conf.urls.defaults import url
+        return url(self.regex(path), *args, **kw)
+
+    def regex(self, url, **kw):
+        r = VARIABLE.sub(functools.partial(self._replace, url=url), url)
+        if kw.get('anchor', self.anchor) and r[:1] != '^':
+            r = '^' + r
+        # special-case so '^$' doesn't end up with a '/' in it
+        if url and kw.get('append_slash', self.append_slash) and r[-1:] not in ('$','/'):
+            r += '/'
+        if kw.get('terminate', self.terminate) and r[-1:] != '$':
+            r += '$'
+        return r
+
+easyurl = URLPatternGenerator()
+regex = easyurl.regex
+
+class ClassFunction(object):
+    def __new__(cls, *args, **kwargs):
+        obj = super(ClassFunction, cls).__new__(cls)
+        return obj(*args, **kwargs)
+
+    def __call__(self, *args, **kwargs):
+        pass
+
+class Views(object):
+    urlpatterns = patterns('', )
+
+    def __call__(self, request, *args, **kwargs):
+        pass
+
+    def render_html(self, request, template, data):
+        from django.views.generic.simple import direct_to_template
+        return direct_to_template(request, template, extra_context=data)

File djangotoolbox/widgets.py

+from django.forms import widgets
+from django.template.defaultfilters import filesizeformat
+from django.utils.safestring import mark_safe
+
+class BlobWidget(widgets.FileInput):
+    def render(self, name, value, attrs=None):
+        try:
+            blob_size = len(value)
+        except:
+            blob_size = 0
+
+        blob_size = filesizeformat(blob_size)
+        original = super(BlobWidget, self).render(name, value, attrs=None)
+        return mark_safe('%s<p>Current size: %s</p>' % (original, blob_size))

File errorviews.py

-from django import http
-from django.template import Context, RequestContext, loader
-
-def server_error(request, template_name='500.html'):
-    """
-    500 error handler.
-
-    Templates: `500.html`
-    Context:
-        request_path
-            The path of the requested URL (e.g., '/app/pages/bad_page/')
-    """
-    t = loader.get_template(template_name) # You need to create a 500.html template.
-    return http.HttpResponseServerError(t.render(RequestContext(request, {'request_path': request.path})))

File fields.py

-from django.db import models
-
-class ListField(models.Field):
-    def __init__(self, field_type, *args, **kwargs):
-        self.field_type = field_type
-        kwargs['default'] = lambda: None if self.null else []
-        super(ListField, self).__init__(*args, **kwargs)
-
-    def db_type(self, connection):
-        return 'ListField:' + self.field_type.db_type(connection=connection)
-
-    def call_for_each(self, function_name, values, *args, **kwargs):
-        if isinstance(values, (list, tuple)) and len(values):
-            for i, value in enumerate(values):
-                values[i] = getattr(self.field_type, function_name)(value, *args,
-                    **kwargs)
-        return values
-
-    def to_python(self, value):
-        return self.call_for_each( 'to_python', value)
-
-    def get_prep_value(self, value):
-        return self.call_for_each('get_prep_value', value)
-
-    def get_db_prep_value(self, value, connection, prepared=False):
-        return self.call_for_each('get_db_prep_value', value, connection=connection,
-            prepared=prepared)
-
-    def get_db_prep_save(self, value, connection):
-        return self.call_for_each('get_db_prep_save', value, connection=connection)
-
-    def formfield(self, **kwargs):
-        return None
-
-class BlobField(models.Field):
-    """
-    A field for storing blobs of binary data
-    """
-    def get_internal_type(self):
-        return 'BlobField'
-
-    def formfield(self, **kwargs):
-        # A file widget is provided, but use  model FileField or ImageField 
-        # for storing specific files most of the time
-        from .widgets import BlobWidget
-        from django.forms import FileField
-        defaults = {'form_class': FileField, 'widget': BlobWidget}
-        defaults.update(kwargs)
-        return super(BlobField, self).formfield(**defaults)
-
-    def get_db_prep_value(self, value, connection, prepared=False):        
-        try:
-            # Sees if the object passed in is file-like
-            return value.read()
-        except:
-            return str(value)
-
-    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
-        raise TypeError("BlobFields do not support lookups")
-
-    def value_to_string(self, obj):
-        return str(self._get_val_from_obj(obj))

File http.py

-from django.conf import settings
-from django.core.serializers.json import DjangoJSONEncoder
-from django.http import HttpResponse
-from django.utils import simplejson
-from django.utils.encoding import force_unicode
-from django.utils.functional import Promise
-
-class LazyEncoder(DjangoJSONEncoder):
-    def default(self, obj):
-        if isinstance(obj, Promise):
-            return force_unicode(obj)
-        return super(LazyEncoder, self).default(obj)
-
-class JSONResponse(HttpResponse):
-    def __init__(self, pyobj, **kwargs):
-        super(JSONResponse, self).__init__(
-            simplejson.dumps(pyobj, cls=LazyEncoder),
-            content_type='application/json; charset=%s' %
-                            settings.DEFAULT_CHARSET,
-            **kwargs)
-
-class TextResponse(HttpResponse):
-    def __init__(self, string='', **kwargs):
-        super(TextResponse, self).__init__(string,
-            content_type='text/plain; charset=%s' % settings.DEFAULT_CHARSET,
-            **kwargs)

File middleware.py

-from django.conf import settings
-from django.http import HttpResponseRedirect
-from django.utils.cache import patch_cache_control
-
-LOGIN_REQUIRED_PREFIXES = getattr(settings, 'LOGIN_REQUIRED_PREFIXES', ())
-NO_LOGIN_REQUIRED_PREFIXES = getattr(settings, 'NO_LOGIN_REQUIRED_PREFIXES', ())
-
-class LoginRequiredMiddleware(object):
-    """
-    Redirects to login page if request path begins with a
-    LOGIN_REQURED_PREFIXES prefix. You can also specify
-    NO_LOGIN_REQUIRED_PREFIXES which take precedence.
-    """
-    def process_request(self, request):
-        for prefix in NO_LOGIN_REQUIRED_PREFIXES:
-            if request.path.startswith(prefix):
-                return None
-        for prefix in LOGIN_REQUIRED_PREFIXES:
-            if request.path.startswith(prefix) and \
-                    not request.user.is_authenticated():
-                from django.contrib.auth.views import redirect_to_login
-                return redirect_to_login(request.get_full_path())
-        return None
-
-class RedirectMiddleware(object):
-    """
-    A static redirect middleware. Mostly useful for hosting providers that
-    automatically setup an alternative domain for your website. You might
-    not want anyone to access the site via those possibly well-known URLs.
-    """
-    def process_request(self, request):
-        host = request.get_host().split(':')[0]
-        # Turn off redirects when in debug mode, running unit tests, or
-        # when handling an App Engine cron job.
-        if settings.DEBUG or host == 'testserver' or \
-                not getattr(settings, 'ALLOWED_DOMAINS', None) or \
-                request.META.get('HTTP_X_APPENGINE_CRON') == 'true':
-            return
-        if host not in settings.ALLOWED_DOMAINS:
-            return HttpResponseRedirect('http://' + settings.ALLOWED_DOMAINS[0])
-
-class NoHistoryCacheMiddleware(object):
-    """
-    If user is authenticated we disable browser caching of pages in history.
-    """
-    def process_response(self, request, response):
-        if 'Expires' not in response and \
-                'Cache-Control' not in response and \
-                hasattr(request, 'session') and \
-                request.user.is_authenticated():
-            patch_cache_control(response,
-                no_store=True, no_cache=True, must_revalidate=True, max_age=0)
-        return response

File models.py

Empty file removed.
+from setuptools import setup, find_packages
+import os
+
+DESCRIPTION = "Djangotoolbox for Django-nonrel"
+
+LONG_DESCRIPTION = None
+try:
+    LONG_DESCRIPTION = open('README.rst').read()
+except:
+    pass
+
+init = os.path.join(os.path.dirname(__file__), 'djangotoolbox', '__init__.py')
+
+CLASSIFIERS = [
+    'Development Status :: 4 - Beta',
+    'Intended Audience :: Developers',
+    'License :: OSI Approved :: MIT License',
+    'Operating System :: OS Independent',
+    'Programming Language :: Python',
+    'Framework :: Django',
+    'Topic :: Database',
+    'Topic :: Software Development :: Libraries :: Python Modules',
+]
+
+setup(name='djangotoolbox',
+      packages=find_packages(),
+      author='Waldemar Kornewald',
+      url='http://www.allbuttonspressed.com/projects/djangotoolbox',
+      include_package_data=True,
+      description=DESCRIPTION,
+      long_description=LONG_DESCRIPTION,
+      platforms=['any'],
+      classifiers=CLASSIFIERS,
+      install_requires=[],
+)

File test.py

-from .utils import object_list_to_table, equal_lists
-from django.test import TestCase
-
-class ModelTestCase(TestCase):
-    """
-    A test case for models that provides an easy way to validate the DB
-    contents against a given list of row-values.
-
-    You have to specify the model to validate using the 'model' attribute:
-    class MyTestCase(ModelTestCase):
-        model = MyModel
-    """
-    def validate_state(self, columns, *state_table):
-        """
-        Validates that the DB contains exactly the values given in the state
-        table. The list of columns is given in the columns tuple.
-
-        Example:
-        self.validate_state(
-            ('a', 'b', 'c'),
-            (1, 2, 3),
-            (11, 12, 13),
-        )
-        validates that the table contains exactly two rows and that their
-        'a', 'b', and 'c' attributes are 1, 2, 3 for one row and 11, 12, 13
-        for the other row. The order of the rows doesn't matter.
-        """
-        current_state = object_list_to_table(columns,
-            self.model.all())[1:]
-        if not equal_lists(current_state, state_table):
-            print 'DB state not valid:'
-            print 'Current state:'
-            print columns
-            for state in current_state:
-                print state
-            print 'Should be:'
-            for state in state_table:
-                print state
-            self.fail('DB state not valid')

File tests.py

-from .fields import ListField
-from django.db import models
-from django.db.models import Q
-#from django.db.utils import DatabaseError
-from django.test import TestCase
-
-class ListModel(models.Model):
-    floating_point = models.FloatField()
-    names = ListField(models.CharField(max_length=500))
-    names_with_default = ListField(models.CharField(max_length=500), default=[])
-    names_nullable = ListField(models.CharField(max_length=500), null=True)
-
-class FilterTest(TestCase):
-    floats = [5.3, 2.6, 9.1, 1.58]
-    names = [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]
-
-    def setUp(self):
-        for i, float in enumerate(FilterTest.floats):
-            ListModel(floating_point=float, names=FilterTest.names[:i+1]).save()
-
-    def test_startswith(self):
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names__startswith='Sa')],
-                          [['Kakashi', 'Naruto', 'Sasuke',],
-                            ['Kakashi', 'Naruto', 'Sasuke', 'Sakura',]])
-
-    def test_options(self):
-        self.assertEquals([entity.names_with_default for entity in
-                           ListModel.objects.filter(names__startswith='Sa')],
-                          [[], []])
-
-        # TODO: should it be NULL or None here?
-        self.assertEquals([entity.names_nullable for entity in
-                           ListModel.objects.filter(names__startswith='Sa')],
-                          [None, None])
-
-    def test_gt(self):
-        # test gt on list
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names__gt='Kakashi')],
-                          [[u'Kakashi', u'Naruto',],
-                            [u'Kakashi', u'Naruto', u'Sasuke',],
-                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-    def test_lt(self):
-        # test lt on list
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names__lt='Naruto')],
-                          [[u'Kakashi',], [u'Kakashi', u'Naruto',],
-                            [u'Kakashi', u'Naruto', u'Sasuke',],
-                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-    def test_gte(self):
-        # test gte on list
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names__gte='Sakura')],
-                          [[u'Kakashi', u'Naruto', u'Sasuke',],
-                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-    def test_lte(self):
-        # test lte on list
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names__lte='Kakashi')],
-                          [[u'Kakashi',], [u'Kakashi', u'Naruto',],
-                            [u'Kakashi', u'Naruto', u'Sasuke',],
-                            [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-    def test_equals(self):
-        # test equality filter on list
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.filter(names='Sakura')],
-                          [[u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-        # test with additonal pk filter (for DBs that have special pk queries)
-        query = ListModel.objects.filter(names='Sakura')
-        self.assertEquals(query.get(pk=query[0].pk).names,
-                          [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',])
-
-    def test_is_null(self):
-        self.assertEquals(ListModel.objects.filter(
-            names__isnull=True).count(), 0)
-
-    def test_exclude(self):
-        self.assertEquals([entity.names for entity in
-                           ListModel.objects.all().exclude(
-                            names__lt='Sakura')],
-                          [[u'Kakashi', u'Naruto', u'Sasuke',],
-                           [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura',]])
-
-    def test_chained_filter(self):
-        self.assertEquals([entity.names for entity in
-                          ListModel.objects.filter(names='Sasuke').filter(
-                            names='Sakura')], [['Kakashi', 'Naruto',
-                            'Sasuke', 'Sakura'],])
-
-        self.assertEquals([entity.names for entity in
-                          ListModel.objects.filter(names__startswith='Sa').filter(
-                            names='Sakura')], [['Kakashi', 'Naruto',
-                            'Sasuke', 'Sakura']])
-
-        # test across multiple columns. On app engine only one filter is allowed
-        # to be an inequality filter
-        self.assertEquals([entity.names for entity in
-                          ListModel.objects.filter(floating_point=9.1).filter(
-                            names__startswith='Sa')], [['Kakashi', 'Naruto',
-                            'Sasuke',],])
-
-# passes on production but not on sdk
-#    def test_Q_objects(self):
-#        self.assertEquals([entity.names for entity in
-#            ListModel.objects.exclude(Q(names__lt='Sakura') | Q(names__gte='Sasuke'))],
-#                [['Kakashi', 'Naruto', 'Sasuke', 'Sakura'], ])

File utils.py

-def make_tls_property(default=None):
-    """Creates a class-wide instance property with a thread-specific value."""
-    class TLSProperty(object):
-        def __init__(self):
-            from threading import local
-            self.local = local()
-
-        def __get__(self, instance, cls):
-            if not instance:
-                return self
-            return self.value
-
-        def __set__(self, instance, value):
-            self.value = value
-
-        def _get_value(self):
-            return getattr(self.local, 'value', default)
-        def _set_value(self, value):
-            self.local.value = value
-        value = property(_get_value, _set_value)
-
-    return TLSProperty()
-
-def getattr_by_path(obj, attr, *default):
-    """Like getattr(), but can go down a hierarchy like 'attr.subattr'"""
-    value = obj
-    for part in attr.split('.'):
-        if not hasattr(value, part) and len(default):
-            return default[0]
-        value = getattr(value, part)
-        if callable(value):
-            value = value()
-    return value
-
-def subdict(data, *attrs):
-    """Returns a subset of the keys of a dictionary."""
-    result = {}
-    result.update([(key, data[key]) for key in attrs])
-    return result
-
-def equal_lists(left, right):
-    """
-    Compares two lists and returs True if they contain the same elements, but
-    doesn't require that they have the same order.
-    """
-    right = list(right)
-    if len(left) != len(right):
-        return False
-    for item in left:
-        if item in right:
-            del right[right.index(item)]
-        else:
-            return False
-    return True
-
-def object_list_to_table(headings, dict_list):
-    """
-    Converts objects to table-style list of rows with heading:
-
-    Example:
-    x.a = 1
-    x.b = 2
-    x.c = 3
-    y.a = 11
-    y.b = 12
-    y.c = 13
-    object_list_to_table(('a', 'b', 'c'), [x, y])
-    results in the following (dict keys reordered for better readability):
-    [
-        ('a', 'b', 'c'),
-        (1, 2, 3),
-        (11, 12, 13),
-    ]
-    """
-    return [headings] + [tuple([getattr_by_path(row, heading, None)
-                                for heading in headings])
-                         for row in dict_list]
-
-def dict_list_to_table(headings, dict_list):
-    """
-    Converts dict to table-style list of rows with heading:
-
-    Example:
-    dict_list_to_table(('a', 'b', 'c'),
-        [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}])
-    results in the following (dict keys reordered for better readability):
-    [
-        ('a', 'b', 'c'),
-        (1, 2, 3),
-        (11, 12, 13),
-    ]
-    """
-    return [headings] + [tuple([row[heading] for heading in headings])
-                         for row in dict_list]

File views.py

-from copy import deepcopy
-import functools, re
-
-PATTERNS = {
-    'day':   r'\d{1,2}',
-    'id':    r'\d+',
-    'num':   r'\d+',
-    'month': r'\d{1,2}',
-    'mon':   r'[a-z]{3}', # jan, feb, etc...
-    'slug':  r'[\w-]+',
-    'tag':   r'\w+',
-    'year':  r'\d{4}',
-}
-
-# <name[:pattern]>
-VARIABLE = re.compile(r'<(?P<name>\w+)(?::?(?P<pattern>[^>]+))?>')
-
-class URLPatternGenerator(object):
-    def __init__(self, patterns=None, default=r'\d+',
-                 append_slash=True, anchor=True, terminate=True):
-        self.patterns = patterns or PATTERNS.copy()
-        self.default = default              # default pattern
-        self.append_slash = append_slash    # trailing /
-        self.anchor = anchor                # prepend ^
-        self.terminate = terminate          # append $
-
-    def add(self, name, pattern):
-        self.patterns[name] = pattern
-
-    def _replace(self, match, url):
-        regexp = None
-        name = match.group('name')
-        pattern = match.group('pattern')
-        # pattern may be a name or regexp
-        if pattern: