├── djangotoolbox
├── models.py
├── __init__.py
├── db
│ ├── __init__.py
│ ├── utils.py
│ ├── creation.py
│ ├── basecompiler.py
│ └── base.py
├── sites
│ ├── __init__.py
│ └── dynamicsite.py
├── widgets.py
├── errorviews.py
├── admin.py
├── http.py
├── middleware.py
├── utils.py
├── test.py
├── fields.py
└── tests.py
├── MANIFEST.in
├── .gitignore
├── README.rst
├── setup.py
├── LICENSE
└── CHANGELOG.rst
/djangotoolbox/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djangotoolbox/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djangotoolbox/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djangotoolbox/sites/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include CHANGELOG.rst
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | *.egg-info
4 | .project
5 | .pydevproject
6 | .settings
7 | *~
8 | *.orig
9 | *.pyc
10 | *.pyo
11 | *.swp
12 | *.tmp
13 | desktop.ini
14 | nbproject
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/djangotoolbox/widgets.py:
--------------------------------------------------------------------------------
1 | from django.forms import widgets
2 | from django.template.defaultfilters import filesizeformat
3 | from django.utils.safestring import mark_safe
4 |
5 |
6 | class BlobWidget(widgets.FileInput):
7 |
8 | def render(self, name, value, attrs=None):
9 | try:
10 | blob_size = len(value)
11 | except:
12 | blob_size = 0
13 |
14 | blob_size = filesizeformat(blob_size)
15 | original = super(BlobWidget, self).render(name, value, attrs=None)
16 | return mark_safe("%s
Current size: %s
" % (original, blob_size))
17 |
--------------------------------------------------------------------------------
/djangotoolbox/errorviews.py:
--------------------------------------------------------------------------------
1 | from django import http
2 | from django.template import RequestContext, loader
3 |
4 |
5 | def server_error(request, template_name='500.html'):
6 | """
7 | 500 error handler.
8 |
9 | Templates: `500.html`
10 | Context:
11 | request_path
12 | The path of the requested URL (e.g., '/app/pages/bad_page/')
13 | """
14 |
15 | # You need to create a 500.html template.
16 | t = loader.get_template(template_name)
17 |
18 | return http.HttpResponseServerError(
19 | t.render(RequestContext(request, {'request_path': request.path})))
20 |
--------------------------------------------------------------------------------
/djangotoolbox/admin.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib import admin
3 | from django.contrib.admin.sites import NotRegistered
4 | from django.contrib.auth.admin import UserAdmin
5 | from django.contrib.auth.models import User, Group
6 |
7 |
8 | class UserForm(forms.ModelForm):
9 |
10 | class Meta:
11 | model = User
12 | fields = ('username', 'email', 'first_name', 'last_name', 'is_active',
13 | 'is_staff', 'is_superuser')
14 |
15 |
16 | class CustomUserAdmin(UserAdmin):
17 | fieldsets = None
18 | form = UserForm
19 | search_fields = ('=username',)
20 | list_filter = ('is_staff', 'is_superuser', 'is_active')
21 |
22 | try:
23 | admin.site.unregister(User)
24 | admin.site.register(User, CustomUserAdmin)
25 | except NotRegistered:
26 | pass
27 |
28 | try:
29 | admin.site.unregister(Group)
30 | except NotRegistered:
31 | pass
32 |
--------------------------------------------------------------------------------
/djangotoolbox/http.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.core.serializers.json import DjangoJSONEncoder
3 | from django.http import HttpResponse
4 | from django.utils import simplejson
5 | from django.utils.encoding import force_unicode
6 | from django.utils.functional import Promise
7 |
8 |
9 | class LazyEncoder(DjangoJSONEncoder):
10 |
11 | def default(self, obj):
12 | if isinstance(obj, Promise):
13 | return force_unicode(obj)
14 | return super(LazyEncoder, self).default(obj)
15 |
16 |
17 | class JSONResponse(HttpResponse):
18 |
19 | def __init__(self, pyobj, **kwargs):
20 | super(JSONResponse, self).__init__(
21 | simplejson.dumps(pyobj, cls=LazyEncoder),
22 | content_type='application/json; charset=%s' %
23 | settings.DEFAULT_CHARSET,
24 | **kwargs)
25 |
26 |
27 | class TextResponse(HttpResponse):
28 |
29 | def __init__(self, string='', **kwargs):
30 | super(TextResponse, self).__init__(
31 | string,
32 | content_type='text/plain; charset=%s' % settings.DEFAULT_CHARSET,
33 | **kwargs)
34 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Djangotoolbox provides a common API for running Django on
2 | non-relational/NoSQL databases (currently via Django-nonrel_).
3 |
4 | In ``djangotoolbox.db`` you can find base classes for writing
5 | non-relational DB backends. Read
6 | `Writing a non-relational Django backend`_
7 | for more information.
8 |
9 | In ``djangotoolbox.fields`` you can find several common field
10 | types for non-relational DB backends (``ListField``, ``SetField``,
11 | ``DictField``, ``RawField``, ``BlobField``).
12 |
13 | The ``djangotoolbox.admin`` module provides admin overrides for
14 | making ``django.contrib.auth`` work correctly in the admin UI.
15 | Simply add ``'djangotoolbox'`` to ``INSTALLED_APPS`` **after**
16 | ``django.contrib.admin``. This will disable features that
17 | require JOINs. If you still need permission handling you should
18 | use the `nonrel permission backend`_.
19 |
20 | .. _Django-nonrel: http://django-nonrel.org/
21 | .. _Writing a non-relational Django backend: http://www.allbuttonspressed.com/blog/django/2010/04/Writing-a-non-relational-Django-backend
22 | .. _nonrel permission backend: https://github.com/django-nonrel/django-permission-backend-nonrel
23 |
--------------------------------------------------------------------------------
/djangotoolbox/db/utils.py:
--------------------------------------------------------------------------------
1 | from django.db.backends.util import format_number
2 |
3 |
4 | def decimal_to_string(value, max_digits=16, decimal_places=0):
5 | """
6 | Converts decimal to a unicode string for storage / lookup by nonrel
7 | databases that don't support decimals natively.
8 |
9 | This is an extension to `django.db.backends.util.format_number`
10 | that preserves order -- if one decimal is less than another, their
11 | string representations should compare the same (as strings).
12 |
13 | TODO: Can't this be done using string.format()?
14 | Not in Python 2.5, str.format is backported to 2.6 only.
15 | """
16 |
17 | # Handle sign separately.
18 | if value.is_signed():
19 | sign = u'-'
20 | value = abs(value)
21 | else:
22 | sign = u''
23 |
24 | # Let Django quantize and cast to a string.
25 | value = format_number(value, max_digits, decimal_places)
26 |
27 | # Pad with zeroes to a constant width.
28 | n = value.find('.')
29 | if n < 0:
30 | n = len(value)
31 | if n < max_digits - decimal_places:
32 | value = u'0' * (max_digits - decimal_places - n) + value
33 | return sign + value
34 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | DESCRIPTION = "Djangotoolbox for Django-nonrel"
5 | LONG_DESCRIPTION = None
6 | try:
7 | LONG_DESCRIPTION = open('README.rst').read()
8 | except:
9 | pass
10 |
11 | setup(name='djangotoolbox',
12 | version='1.8.0',
13 | description=DESCRIPTION,
14 | long_description=LONG_DESCRIPTION,
15 | author='Waldemar Kornewald',
16 | author_email='wkornewald@gmail.com',
17 | url='https://github.com/django-nonrel/djangotoolbox',
18 | packages=find_packages(),
19 | license='3-clause BSD',
20 | zip_safe=False,
21 | classifiers=[
22 | 'Development Status :: 5 - Production/Stable',
23 | 'Environment :: Web Environment',
24 | 'Framework :: Django',
25 | 'Intended Audience :: Developers',
26 | 'License :: OSI Approved :: BSD License',
27 | 'Operating System :: OS Independent',
28 | 'Programming Language :: Python',
29 | 'Programming Language :: Python :: 2.5',
30 | 'Programming Language :: Python :: 2.6',
31 | 'Programming Language :: Python :: 2.7',
32 | 'Programming Language :: Python :: 3.4',
33 | 'Topic :: Database',
34 | 'Topic :: Software Development :: Libraries :: Python Modules',
35 | ],
36 | )
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Waldemar Kornewald, Thomas Wanschik, and all contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of All Buttons Pressed nor
15 | the names of its contributors may be used to endorse or promote products
16 | derived from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/djangotoolbox/sites/dynamicsite.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.core.cache import cache
3 | from django.contrib.sites.models import Site
4 | from djangotoolbox.utils import make_tls_property
5 |
6 |
7 | _default_site_id = getattr(settings, 'SITE_ID', None)
8 | SITE_ID = settings.__class__.SITE_ID = make_tls_property()
9 |
10 |
11 | class DynamicSiteIDMiddleware(object):
12 | """Sets settings.SITE_ID based on request's domain."""
13 |
14 | def process_request(self, request):
15 | # Ignore port if it's 80 or 443
16 | if ':' in request.get_host():
17 | domain, port = request.get_host().split(':')
18 | if int(port) not in (80, 443):
19 | domain = request.get_host()
20 | else:
21 | domain = request.get_host().split(':')[0]
22 |
23 | # Domains are case insensitive
24 | domain = domain.lower()
25 |
26 | # We cache the SITE_ID
27 | cache_key = 'Site:domain:%s' % domain
28 | site = cache.get(cache_key)
29 | if site:
30 | SITE_ID.value = site
31 | else:
32 | try:
33 | site = Site.objects.get(domain=domain)
34 | except Site.DoesNotExist:
35 | site = None
36 |
37 | if not site:
38 | # Fall back to with/without 'www.'
39 | if domain.startswith('www.'):
40 | fallback_domain = domain[4:]
41 | else:
42 | fallback_domain = 'www.' + domain
43 |
44 | try:
45 | site = Site.objects.get(domain=fallback_domain)
46 | except Site.DoesNotExist:
47 | site = None
48 |
49 | # Add site if it doesn't exist
50 | if not site and getattr(settings, 'CREATE_SITES_AUTOMATICALLY',
51 | True):
52 | site = Site(domain=domain, name=domain)
53 | site.save()
54 |
55 | # Set SITE_ID for this thread/request
56 | if site:
57 | SITE_ID.value = site.pk
58 | else:
59 | SITE_ID.value = _default_site_id
60 |
61 | cache.set(cache_key, SITE_ID.value, 5 * 60)
62 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | Version 1.8.0 (Jul 19, 2015)
5 | -------------
6 |
7 | * Added support for Django 1.8 in the db package (Thanks @kavdev)
8 |
9 | Version 1.7.0 (Jul 12, 2015)
10 | -------------
11 |
12 | * Add support for Django 1.7
13 |
14 | Version 1.6.2 (Dec 20, 2013)
15 | -------------
16 |
17 | * Fixed broken query in User admin
18 |
19 | Version 1.6.1 (Nov 29, 2013)
20 | -------------
21 |
22 | * Fixed packaging issues
23 |
24 | Version 1.6.0
25 | -------------
26 |
27 | Note: This is release includes support for Django versions 1.4, 1.5 and 1.6.
28 | You no longer need to use the separate branches for each Django version.
29 |
30 | * Added support for Django 1.6
31 |
32 | Version 1.5.0
33 | -------------
34 |
35 | * Added support for Django 1.5
36 |
37 | Version 1.4.0
38 | -------------
39 |
40 | * Added support for Django 1.4
41 |
42 | Version 0.9.2
43 | -------------
44 |
45 | * Moved all code required for value conversion for / deconversion from
46 | database to ``DatabaseOperations``
47 | * Moved nonrel fields' values handling to toolbox, allowing backends to
48 | just select an appropriate storage type (including string and binary)
49 | * Moved decimal-to-string implementation preserving comparisons to
50 | toolbox (previously in the GAE backend)
51 | * Let backends use the QuerySet's ``standard_ordering`` when no field
52 | ordering is defined
53 | * Fixed conversion of values for ``EmbeddedModelField`` subfields
54 | * Fixed preparation of lookup arguments for ``List/Set/DictField``
55 | * Fixed value comparisons in in-memory filtering (only used by GAE)
56 | * Fixed ``update`` for ``EmbeddedModelField`` nested in a nonrel field
57 |
58 | Version 0.9.1
59 | -------------
60 |
61 | * Added lazy model lookups to EmbeddedModelField
62 | * Simplified CapturingTestSuiteRunner by using Django's integrated unittest2 package
63 | * Several new unit tests
64 |
65 | Version 0.8.1
66 | -------------
67 |
68 | * Added default implementation for ``check_aggregate_support()``. Contributed by Jonas Haag
69 | * Added ``ListField``/etc. support for fields that require ``SubfieldBase``
70 |
71 | Version 0.8
72 | -----------
73 |
74 | This release unifies the field types of all existing nonrel backends.
75 |
76 | * Merged with ``ListField`` from MongoDB backend. Contributed by Jonas Haag
77 | * Added ``SetField``, ``DictField``, and ``RawField``. Contributed by Jonas Haag
78 | * Fixed support for proxy models. Contributed by Vladimir Mihailenco
79 | * Several cleanups and minor bug fixes
80 |
--------------------------------------------------------------------------------
/djangotoolbox/middleware.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponseRedirect
3 | from django.utils.cache import patch_cache_control
4 |
5 |
6 | LOGIN_REQUIRED_PREFIXES = getattr(settings, 'LOGIN_REQUIRED_PREFIXES', ())
7 | NO_LOGIN_REQUIRED_PREFIXES = getattr(settings,
8 | 'NO_LOGIN_REQUIRED_PREFIXES', ())
9 |
10 | ALLOWED_DOMAINS = getattr(settings, 'ALLOWED_DOMAINS', None)
11 | NON_REDIRECTED_PATHS = getattr(settings, 'NON_REDIRECTED_PATHS', ())
12 | NON_REDIRECTED_BASE_PATHS = tuple(path.rstrip('/') + '/'
13 | for path in NON_REDIRECTED_PATHS)
14 |
15 |
16 | class LoginRequiredMiddleware(object):
17 | """
18 | Redirects to login page if request path begins with a
19 | LOGIN_REQURED_PREFIXES prefix. You can also specify
20 | NO_LOGIN_REQUIRED_PREFIXES which take precedence.
21 | """
22 |
23 | def process_request(self, request):
24 | for prefix in NO_LOGIN_REQUIRED_PREFIXES:
25 | if request.path.startswith(prefix):
26 | return None
27 | for prefix in LOGIN_REQUIRED_PREFIXES:
28 | if request.path.startswith(prefix) and \
29 | not request.user.is_authenticated():
30 | from django.contrib.auth.views import redirect_to_login
31 | return redirect_to_login(request.get_full_path())
32 | return None
33 |
34 |
35 | class RedirectMiddleware(object):
36 | """
37 | A static redirect middleware. Mostly useful for hosting providers
38 | that automatically setup an alternative domain for your website.
39 | You might not want anyone to access the site via those possibly
40 | well-known URLs.
41 | """
42 |
43 | def process_request(self, request):
44 | host = request.get_host().split(':')[0]
45 | # Turn off redirects when in debug mode, running unit tests, or
46 | # when handling an App Engine cron job.
47 | if (settings.DEBUG or host == 'testserver' or
48 | not ALLOWED_DOMAINS or
49 | request.META.get('HTTP_X_APPENGINE_CRON') == 'true' or
50 | request.path.startswith('/_ah/') or
51 | request.path in NON_REDIRECTED_PATHS or
52 | request.path.startswith(NON_REDIRECTED_BASE_PATHS)):
53 | return
54 | if host not in settings.ALLOWED_DOMAINS:
55 | return HttpResponseRedirect(
56 | 'http://' + settings.ALLOWED_DOMAINS[0] + request.path)
57 |
58 |
59 | class NoHistoryCacheMiddleware(object):
60 | """
61 | If user is authenticated we disable browser caching of pages in
62 | history.
63 | """
64 |
65 | def process_response(self, request, response):
66 | if 'Expires' not in response and \
67 | 'Cache-Control' not in response and \
68 | hasattr(request, 'session') and \
69 | request.user.is_authenticated():
70 | patch_cache_control(response,
71 | no_store=True, no_cache=True, must_revalidate=True, max_age=0)
72 | return response
73 |
--------------------------------------------------------------------------------
/djangotoolbox/utils.py:
--------------------------------------------------------------------------------
1 | def make_tls_property(default=None):
2 | """
3 | Creates a class-wide instance property with a thread-specific
4 | value.
5 | """
6 |
7 | class TLSProperty(object):
8 |
9 | def __init__(self):
10 | from threading import local
11 | self.local = local()
12 |
13 | def __get__(self, instance, cls):
14 | if not instance:
15 | return self
16 | return self.value
17 |
18 | def __set__(self, instance, value):
19 | self.value = value
20 |
21 | def _get_value(self):
22 | return getattr(self.local, 'value', default)
23 |
24 | def _set_value(self, value):
25 | self.local.value = value
26 | value = property(_get_value, _set_value)
27 |
28 | return TLSProperty()
29 |
30 |
31 | def getattr_by_path(obj, attr, *default):
32 | """
33 | Like getattr(), but can go down a hierarchy like "attr.subattr".
34 | """
35 | value = obj
36 | for part in attr.split('.'):
37 | if not hasattr(value, part) and len(default):
38 | return default[0]
39 | value = getattr(value, part)
40 | if callable(value):
41 | value = value()
42 | return value
43 |
44 |
45 | def subdict(data, *attrs):
46 | """Returns a subset of the keys of a dictionary."""
47 | result = {}
48 | result.update([(key, data[key]) for key in attrs])
49 | return result
50 |
51 |
52 | def equal_lists(left, right):
53 | """
54 | Compares two lists and returs True if they contain the same
55 | elements, but doesn't require that they have the same order.
56 | """
57 | right = list(right)
58 | if len(left) != len(right):
59 | return False
60 | for item in left:
61 | if item in right:
62 | del right[right.index(item)]
63 | else:
64 | return False
65 | return True
66 |
67 |
68 | def object_list_to_table(headings, dict_list):
69 | """
70 | Converts objects to table-style list of rows with heading:
71 |
72 | Example:
73 | x.a = 1
74 | x.b = 2
75 | x.c = 3
76 | y.a = 11
77 | y.b = 12
78 | y.c = 13
79 | object_list_to_table(('a', 'b', 'c'), [x, y])
80 | results in the following (dict keys reordered for better readability):
81 | [
82 | ('a', 'b', 'c'),
83 | (1, 2, 3),
84 | (11, 12, 13),
85 | ]
86 | """
87 | return [headings] + [tuple([getattr_by_path(row, heading, None)
88 | for heading in headings])
89 | for row in dict_list]
90 |
91 |
92 | def dict_list_to_table(headings, dict_list):
93 | """
94 | Converts dict to table-style list of rows with heading:
95 |
96 | Example:
97 | dict_list_to_table(('a', 'b', 'c'),
98 | [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}])
99 | results in the following (dict keys reordered for better readability):
100 | [
101 | ('a', 'b', 'c'),
102 | (1, 2, 3),
103 | (11, 12, 13),
104 | ]
105 | """
106 | return [headings] + [tuple([row[heading] for heading in headings])
107 | for row in dict_list]
108 |
--------------------------------------------------------------------------------
/djangotoolbox/test.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.utils.unittest import TextTestResult, TextTestRunner
3 |
4 | try:
5 | from django.test.runner import DiscoverRunner as TestRunner
6 | except ImportError:
7 | from django.test.simple import DjangoTestSuiteRunner as TestRunner
8 |
9 | from .utils import object_list_to_table
10 |
11 | import re
12 |
13 |
14 | class ModelTestCase(TestCase):
15 | """
16 | A test case for models that provides an easy way to validate the DB
17 | contents against a given list of row-values.
18 |
19 | You have to specify the model to validate using the 'model'
20 | attribute:
21 |
22 | class MyTestCase(ModelTestCase):
23 | model = MyModel
24 | """
25 |
26 | def validate_state(self, columns, *state_table):
27 | """
28 | Validates that the DB contains exactly the values given in the
29 | state table. The list of columns is given in the columns tuple.
30 |
31 | Example:
32 | self.validate_state(
33 | ('a', 'b', 'c'),
34 | (1, 2, 3),
35 | (11, 12, 13),
36 | )
37 | validates that the table contains exactly two rows and that
38 | their 'a', 'b', and 'c' attributes are 1, 2, 3 for one row and
39 | 11, 12, 13 for the other row. The order of the rows doesn't
40 | matter.
41 | """
42 | current_state = object_list_to_table(
43 | columns, self.model.all())[1:]
44 | if not equal_lists(current_state, state_table):
45 | print("DB state not valid:")
46 | print("Current state:")
47 | print(columns)
48 | for state in current_state:
49 | print(state)
50 | print("Should be:")
51 | for state in state_table:
52 | print(state)
53 | self.fail("DB state not valid.")
54 |
55 |
56 | class CapturingTestSuiteRunner(TestRunner):
57 | """
58 | Captures stdout/stderr during test and shows them next to
59 | tracebacks.
60 | """
61 |
62 | def run_suite(self, suite, **kwargs):
63 | return TextTestRunner(verbosity=self.verbosity,
64 | failfast=self.failfast,
65 | buffer=True).run(suite)
66 |
67 | _EXPECTED_ERRORS = [
68 | r"This query is not supported by the database\.",
69 | r"Multi-table inheritance is not supported by non-relational DBs\.",
70 | r"TextField is not indexed, by default, so you can't filter on it\.",
71 | r"First ordering property must be the same as inequality filter property",
72 | r"This database doesn't support filtering on non-primary key ForeignKey fields\.",
73 | r"Only AND filters are supported\.",
74 | r"MultiQuery does not support keys_only\.",
75 | r"You can't query against more than 30 __in filter value combinations\.",
76 | r"Only strings and positive integers may be used as keys on GAE\.",
77 | r"This database does not support aggregates\.",
78 | r"Subqueries are not supported \(yet\)\.",
79 | r"Cursors are not supported\.",
80 | r"This database backend only supports count\(\) queries on the primary key\.",
81 | r"AutoField \(default primary key\) values must be strings representing an ObjectId on MongoDB",
82 | ]
83 |
84 |
85 | class NonrelTestResult(TextTestResult):
86 | def __init__(self, *args, **kwargs):
87 | super(NonrelTestResult, self).__init__(*args, **kwargs)
88 | self._compiled_exception_matchers = [re.compile(expr) for expr in _EXPECTED_ERRORS]
89 |
90 | def __match_exception(self, exc):
91 | for exc_match in self._compiled_exception_matchers:
92 | if exc_match.search(str(exc)):
93 | return True
94 | return False
95 |
96 | def addError(self, test, err):
97 | exc = err[1]
98 | if self.__match_exception(exc):
99 | super(NonrelTestResult, self).addExpectedFailure(test, err)
100 | else:
101 | super(NonrelTestResult, self).addError(test, err)
102 |
103 |
104 | class NonrelTestSuiteRunner(TestRunner):
105 | def run_suite(self, suite, **kwargs):
106 | return TextTestRunner(
107 | verbosity=self.verbosity,
108 | failfast=self.failfast,
109 | resultclass=NonrelTestResult,
110 | buffer=False
111 | ).run(suite)
112 |
--------------------------------------------------------------------------------
/djangotoolbox/db/creation.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | if django.VERSION < (1, 8):
4 | from django.db.backends.creation import BaseDatabaseCreation
5 | else:
6 | from django.db.backends.base.creation import BaseDatabaseCreation
7 |
8 |
9 | class NonrelDatabaseCreation(BaseDatabaseCreation):
10 |
11 | # "Types" used by database conversion methods to decide how to
12 | # convert data for or from the database. Type is understood here
13 | # a bit differently than in vanilla Django -- it should be read
14 | # as an identifier of an encoding / decoding procedure rather than
15 | # just a database column type.
16 | data_types = {
17 |
18 | # NoSQL databases often have specific concepts of entity keys.
19 | # For example, GAE has the db.Key class, MongoDB likes to use
20 | # ObjectIds, Redis uses strings, while Cassandra supports
21 | # different types (including binary data).
22 | 'AutoField': 'key',
23 | 'RelatedAutoField': 'key',
24 | 'ForeignKey': 'key',
25 | 'OneToOneField': 'key',
26 | 'ManyToManyField': 'key',
27 |
28 | # Standard field types, more or less suitable for a database
29 | # (or its client / driver) being able to directly store or
30 | # process Python objects.
31 | 'BigIntegerField': 'long',
32 | 'BooleanField': 'bool',
33 | 'CharField': 'string',
34 | 'CommaSeparatedIntegerField': 'string',
35 | 'DateField': 'date',
36 | 'DateTimeField': 'datetime',
37 | 'DecimalField': 'decimal',
38 | 'EmailField': 'string',
39 | 'FileField': 'string',
40 | 'FilePathField': 'string',
41 | 'FloatField': 'float',
42 | 'ImageField': 'string',
43 | 'IntegerField': 'integer',
44 | 'IPAddressField': 'string',
45 | 'NullBooleanField': 'bool',
46 | 'PositiveIntegerField': 'integer',
47 | 'PositiveSmallIntegerField': 'integer',
48 | 'SlugField': 'string',
49 | 'SmallIntegerField': 'integer',
50 | 'TextField': 'string',
51 | 'TimeField': 'time',
52 | 'URLField': 'string',
53 |
54 | # You may use "list" for SetField, or even DictField and
55 | # EmbeddedModelField (if your database supports nested lists).
56 | # All following fields also support "string" and "bytes" as
57 | # their storage types -- which work by serializing using pickle
58 | # protocol 0 or 2 respectively.
59 | # Please note that if you can't support the "natural" storage
60 | # type then the order of field values will be undetermined, and
61 | # lookups or filters may not work as specified (e.g. the same
62 | # set or dict may be represented by different lists, with
63 | # elements in different order, so the same two instances may
64 | # compare one way or the other).
65 | 'AbstractIterableField': 'list',
66 | 'ListField': 'list',
67 | 'SetField': 'set',
68 | 'DictField': 'dict',
69 | 'EmbeddedModelField': 'dict',
70 |
71 | # RawFields ("raw" db_type) are used when type is not known
72 | # (untyped collections) or for values that do not come from
73 | # a field at all (model info serialization), only do generic
74 | # processing for them (if any). On the other hand, anything
75 | # using the "bytes" db_type should be converted to a database
76 | # blob type or stored as binary data.
77 | 'RawField': 'raw',
78 | 'BlobField': 'bytes',
79 | }
80 |
81 | def db_type(self, field):
82 | """
83 | Allows back-ends to override db_type determined by the field.
84 |
85 | This has to be called instead of the Field.db_type, because we
86 | may need to override a db_type a custom field returns directly,
87 | and need more freedom in handling types of primary keys and
88 | related fields.
89 |
90 | :param field: A field we want to know the storage type of
91 |
92 | TODO: Field.db_type (as of 1.3.1) is used mostly for generating
93 | SQL statements (through a couple of methods in
94 | DatabaseCreation and DatabaseOperations.field_cast_sql)
95 | or within back-end implementations -- nonrel is not
96 | dependend on any of these; but there are two cases that
97 | might need to be fixed, namely:
98 | -- management/createcachetable (calls field.db_type),
99 | -- and contrib/gis (defines its own geo_db_type method).
100 | """
101 | return field.db_type(connection=self.connection)
102 |
103 | def sql_create_model(self, model, style, known_models=set()):
104 | """
105 | Most NoSQL databases are mostly schema-less, no data
106 | definitions are needed.
107 | """
108 | return [], {}
109 |
110 | def sql_indexes_for_model(self, model, style):
111 | """
112 | Creates all indexes needed for local (not inherited) fields of
113 | a model.
114 | """
115 | return []
116 |
--------------------------------------------------------------------------------
/djangotoolbox/fields.py:
--------------------------------------------------------------------------------
1 | # All fields except for BlobField written by Jonas Haag
2 |
3 | from django.core.exceptions import ValidationError
4 | from django.utils.importlib import import_module
5 | from django.db import models
6 | from django.db.models.fields.subclassing import Creator
7 | from django.db.utils import IntegrityError
8 | from django.db.models.fields.related import add_lazy_relation
9 |
10 |
11 | __all__ = ('RawField', 'ListField', 'SetField', 'DictField',
12 | 'EmbeddedModelField', 'BlobField')
13 |
14 |
15 | EMPTY_ITER = ()
16 |
17 |
18 | class _FakeModel(object):
19 | """
20 | An object of this class can pass itself off as a model instance
21 | when used as an arguments to Field.pre_save method (item_fields
22 | of iterable fields are not actually fields of any model).
23 | """
24 |
25 | def __init__(self, field, value):
26 | setattr(self, field.attname, value)
27 |
28 |
29 | class RawField(models.Field):
30 | """
31 | Generic field to store anything your database backend allows you
32 | to. No validation or conversions are done for this field.
33 | """
34 |
35 | def get_internal_type(self):
36 | """
37 | Returns this field's kind. Nonrel fields are meant to extend
38 | the set of standard fields, so fields subclassing them should
39 | get the same internal type, rather than their own class name.
40 | """
41 | return 'RawField'
42 |
43 |
44 | class AbstractIterableField(models.Field):
45 | """
46 | Abstract field for fields for storing iterable data type like
47 | ``list``, ``set`` and ``dict``.
48 |
49 | You can pass an instance of a field as the first argument.
50 | If you do, the iterable items will be piped through the passed
51 | field's validation and conversion routines, converting the items
52 | to the appropriate data type.
53 | """
54 |
55 | def __init__(self, item_field=None, *args, **kwargs):
56 | default = kwargs.get(
57 | 'default', None if kwargs.get('null') else EMPTY_ITER)
58 |
59 | # Ensure a new object is created every time the default is
60 | # accessed.
61 | if default is not None and not callable(default):
62 | kwargs['default'] = lambda: self._type(default)
63 |
64 | super(AbstractIterableField, self).__init__(*args, **kwargs)
65 |
66 | # Either use the provided item_field or a RawField.
67 | if item_field is None:
68 | item_field = RawField()
69 | elif callable(item_field):
70 | item_field = item_field()
71 | self.item_field = item_field
72 |
73 | # We'll be pretending that item_field is a field of a model
74 | # with just one "value" field.
75 | assert not hasattr(self.item_field, 'attname')
76 | self.item_field.set_attributes_from_name('value')
77 |
78 | def contribute_to_class(self, cls, name):
79 | self.item_field.model = cls
80 | self.item_field.name = name
81 | super(AbstractIterableField, self).contribute_to_class(cls, name)
82 |
83 | # If items' field uses SubfieldBase we also need to.
84 | item_metaclass = getattr(self.item_field, '__metaclass__', None)
85 | if item_metaclass and issubclass(item_metaclass, models.SubfieldBase):
86 | setattr(cls, self.name, Creator(self))
87 |
88 | if isinstance(self.item_field, models.ForeignKey) and isinstance(self.item_field.rel.to, basestring):
89 | """
90 | If rel.to is a string because the actual class is not yet defined, look up the
91 | actual class later. Refer to django.models.fields.related.RelatedField.contribute_to_class.
92 | """
93 | def _resolve_lookup(_, resolved_model, __):
94 | self.item_field.rel.to = resolved_model
95 | self.item_field.do_related_class(self, cls)
96 |
97 | add_lazy_relation(cls, self, self.item_field.rel.to, _resolve_lookup)
98 |
99 | def _map(self, function, iterable, *args, **kwargs):
100 | """
101 | Applies the function to items of the iterable and returns
102 | an iterable of the proper type for the field.
103 |
104 | Overriden by DictField to only apply the function to values.
105 | """
106 | return self._type(function(element, *args, **kwargs)
107 | for element in iterable)
108 |
109 | def to_python(self, value):
110 | """
111 | Passes value items through item_field's to_python.
112 | """
113 | if value is None:
114 | return None
115 | return self._map(self.item_field.to_python, value)
116 |
117 | def pre_save(self, model_instance, add):
118 | """
119 | Gets our value from the model_instance and passes its items
120 | through item_field's pre_save (using a fake model instance).
121 | """
122 | value = getattr(model_instance, self.attname)
123 | if value is None:
124 | return None
125 | return self._map(
126 | lambda item: self.item_field.pre_save(
127 | _FakeModel(self.item_field, item), add),
128 | value)
129 |
130 | def get_db_prep_save(self, value, connection):
131 | """
132 | Applies get_db_prep_save of item_field on value items.
133 | """
134 | if value is None:
135 | return None
136 | return self._map(self.item_field.get_db_prep_save, value,
137 | connection=connection)
138 |
139 | def get_db_prep_lookup(self, lookup_type, value, connection,
140 | prepared=False):
141 | """
142 | Passes the value through get_db_prep_lookup of item_field.
143 | """
144 |
145 | # TODO/XXX: Remove as_lookup_value() once we have a cleaner
146 | # solution for dot-notation queries.
147 | # See: https://groups.google.com/group/django-non-relational/browse_thread/thread/6056f8384c9caf04/89eeb9fb22ad16f3).
148 | if hasattr(value, 'as_lookup_value'):
149 | value = value.as_lookup_value(self, lookup_type, connection)
150 |
151 | return self.item_field.get_db_prep_lookup(
152 | lookup_type, value, connection=connection, prepared=prepared)
153 |
154 | def validate(self, values, model_instance):
155 | try:
156 | iter(values)
157 | except TypeError:
158 | raise ValidationError("Value of type %r is not iterable." %
159 | type(values))
160 |
161 | def formfield(self, **kwargs):
162 | raise NotImplementedError("No form field implemented for %r." %
163 | type(self))
164 |
165 |
166 | class ListField(AbstractIterableField):
167 | """
168 | Field representing a Python ``list``.
169 |
170 | If the optional keyword argument `ordering` is given, it must be a
171 | callable that is passed to :meth:`list.sort` as `key` argument. If
172 | `ordering` is given, the items in the list will be sorted before
173 | sending them to the database.
174 | """
175 | _type = list
176 |
177 | def __init__(self, *args, **kwargs):
178 | self.ordering = kwargs.pop('ordering', None)
179 | if self.ordering is not None and not callable(self.ordering):
180 | raise TypeError("'ordering' has to be a callable or None, "
181 | "not of type %r." % type(self.ordering))
182 | super(ListField, self).__init__(*args, **kwargs)
183 |
184 | def get_internal_type(self):
185 | return 'ListField'
186 |
187 | def pre_save(self, model_instance, add):
188 | value = getattr(model_instance, self.attname)
189 | if value is None:
190 | return None
191 | if value and self.ordering:
192 | value.sort(key=self.ordering)
193 | return super(ListField, self).pre_save(model_instance, add)
194 |
195 |
196 | class SetField(AbstractIterableField):
197 | """
198 | Field representing a Python ``set``.
199 | """
200 | _type = set
201 |
202 | def get_internal_type(self):
203 | return 'SetField'
204 |
205 | def value_to_string(self, obj):
206 | """
207 | Custom method for serialization, as JSON doesn't support
208 | serializing sets.
209 | """
210 | return list(self._get_val_from_obj(obj))
211 |
212 |
213 | class DictField(AbstractIterableField):
214 | """
215 | Field representing a Python ``dict``.
216 |
217 | Type conversions described in :class:`AbstractIterableField` only
218 | affect values of the dictionary, not keys. Depending on the
219 | back-end, keys that aren't strings might not be allowed.
220 | """
221 | _type = dict
222 |
223 | def get_internal_type(self):
224 | return 'DictField'
225 |
226 | def _map(self, function, iterable, *args, **kwargs):
227 | return self._type((key, function(value, *args, **kwargs))
228 | for key, value in iterable.iteritems())
229 |
230 | def validate(self, values, model_instance):
231 | if not isinstance(values, dict):
232 | raise ValidationError("Value is of type %r. Should be a dict." %
233 | type(values))
234 |
235 |
236 | class EmbeddedModelField(models.Field):
237 | """
238 | Field that allows you to embed a model instance.
239 |
240 | :param embedded_model: (optional) The model class of instances we
241 | will be embedding; may also be passed as a
242 | string, similar to relation fields
243 |
244 | TODO: Make sure to delegate all signals and other field methods to
245 | the embedded instance (not just pre_save, get_db_prep_* and
246 | to_python).
247 | """
248 | __metaclass__ = models.SubfieldBase
249 |
250 | def __init__(self, embedded_model=None, *args, **kwargs):
251 | self.embedded_model = embedded_model
252 | kwargs.setdefault('default', None)
253 | super(EmbeddedModelField, self).__init__(*args, **kwargs)
254 |
255 | def get_internal_type(self):
256 | return 'EmbeddedModelField'
257 |
258 |
259 | def _set_model(self, model):
260 | """
261 | Resolves embedded model class once the field knows the model it
262 | belongs to.
263 |
264 | If the model argument passed to __init__ was a string, we need
265 | to make sure to resolve that string to the corresponding model
266 | class, similar to relation fields.
267 | However, we need to know our own model to generate a valid key
268 | for the embedded model class lookup and EmbeddedModelFields are
269 | not contributed_to_class if used in iterable fields. Thus we
270 | rely on the collection field telling us its model (by setting
271 | our "model" attribute in its contribute_to_class method).
272 | """
273 | self._model = model
274 | if model is not None and isinstance(self.embedded_model, basestring):
275 |
276 | def _resolve_lookup(self_, resolved_model, model):
277 | self.embedded_model = resolved_model
278 |
279 | add_lazy_relation(model, self, self.embedded_model, _resolve_lookup)
280 |
281 | model = property(lambda self: self._model, _set_model)
282 |
283 |
284 | def stored_model(self, column_values):
285 | """
286 | Returns the fixed embedded_model this field was initialized
287 | with (typed embedding) or tries to determine the model from
288 | _module / _model keys stored together with column_values
289 | (untyped embedding).
290 |
291 | We give precedence to the field's definition model, as silently
292 | using a differing serialized one could hide some data integrity
293 | problems.
294 |
295 | Note that a single untyped EmbeddedModelField may process
296 | instances of different models (especially when used as a type
297 | of a collection field).
298 | """
299 | module = column_values.pop('_module', None)
300 | model = column_values.pop('_model', None)
301 | if self.embedded_model is not None:
302 | return self.embedded_model
303 | elif module is not None:
304 | return getattr(import_module(module), model)
305 | else:
306 | raise IntegrityError("Untyped EmbeddedModelField trying to load "
307 | "data without serialized model class info.")
308 |
309 | def to_python(self, value):
310 | """
311 | Passes embedded model fields' values through embedded fields
312 | to_python methods and reinstiatates the embedded instance.
313 |
314 | We expect to receive a field.attname => value dict together
315 | with a model class from back-end database deconversion (which
316 | needs to know fields of the model beforehand).
317 | """
318 |
319 | # Either the model class has already been determined during
320 | # deconverting values from the database or we've got a dict
321 | # from a deserializer that may contain model class info.
322 | if isinstance(value, tuple):
323 | embedded_model, attribute_values = value
324 | elif isinstance(value, dict):
325 | embedded_model = self.stored_model(value)
326 | attribute_values = value
327 | else:
328 | return value
329 |
330 | # Pass values through respective fields' to_python, leaving
331 | # fields for which no value is specified uninitialized.
332 | attribute_values = dict(
333 | (field.attname, field.to_python(attribute_values[field.attname]))
334 | for field in embedded_model._meta.fields
335 | if field.attname in attribute_values)
336 |
337 | # Create the model instance.
338 | instance = embedded_model(**attribute_values)
339 | instance._state.adding = False
340 | return instance
341 |
342 | def get_db_prep_save(self, embedded_instance, connection):
343 | """
344 | Applies pre_save and get_db_prep_save of embedded instance
345 | fields and passes a field => value mapping down to database
346 | type conversions.
347 |
348 | The embedded instance will be saved as a column => value dict
349 | in the end (possibly augmented with info about instance's model
350 | for untyped embedding), but because we need to apply database
351 | type conversions on embedded instance fields' values and for
352 | these we need to know fields those values come from, we need to
353 | entrust the database layer with creating the dict.
354 | """
355 | if embedded_instance is None:
356 | return None
357 |
358 | # The field's value should be an instance of the model given in
359 | # its declaration or at least of some model.
360 | embedded_model = self.embedded_model or models.Model
361 | if not isinstance(embedded_instance, embedded_model):
362 | raise TypeError("Expected instance of type %r, not %r." %
363 | (embedded_model, type(embedded_instance)))
364 |
365 | # Apply pre_save and get_db_prep_save of embedded instance
366 | # fields, create the field => value mapping to be passed to
367 | # storage preprocessing.
368 | field_values = {}
369 | add = embedded_instance._state.adding
370 | for field in embedded_instance._meta.fields:
371 | value = field.get_db_prep_save(
372 | field.pre_save(embedded_instance, add), connection=connection)
373 |
374 | # Exclude unset primary keys (e.g. {'id': None}).
375 | if field.primary_key and value is None:
376 | continue
377 |
378 | field_values[field] = value
379 |
380 | # Let untyped fields store model info alongside values.
381 | # We use fake RawFields for additional values to avoid passing
382 | # embedded_instance to database conversions and to give
383 | # back-ends a chance to apply generic conversions.
384 | if self.embedded_model is None:
385 | module_field = RawField()
386 | module_field.set_attributes_from_name('_module')
387 | model_field = RawField()
388 | model_field.set_attributes_from_name('_model')
389 | field_values.update(
390 | ((module_field, embedded_instance.__class__.__module__),
391 | (model_field, embedded_instance.__class__.__name__)))
392 |
393 | # This instance will exist in the database soon.
394 | # TODO.XXX: Ensure that this doesn't cause race conditions.
395 | embedded_instance._state.adding = False
396 |
397 | return field_values
398 |
399 | # TODO/XXX: Remove this once we have a cleaner solution.
400 | def get_db_prep_lookup(self, lookup_type, value, connection,
401 | prepared=False):
402 | if hasattr(value, 'as_lookup_value'):
403 | value = value.as_lookup_value(self, lookup_type, connection)
404 | return value
405 |
406 |
407 | class BlobField(models.Field):
408 | """
409 | A field for storing blobs of binary data.
410 |
411 | The value might either be a string (or something that can be
412 | converted to a string), or a file-like object.
413 |
414 | In the latter case, the object has to provide a ``read`` method
415 | from which the blob is read.
416 | """
417 |
418 | def get_internal_type(self):
419 | return 'BlobField'
420 |
421 | def formfield(self, **kwargs):
422 | """
423 | A file widget is provided, but use model FileField or
424 | ImageField for storing specific files most of the time.
425 | """
426 | from .widgets import BlobWidget
427 | from django.forms import FileField
428 | defaults = {'form_class': FileField, 'widget': BlobWidget}
429 | defaults.update(kwargs)
430 | return super(BlobField, self).formfield(**defaults)
431 |
432 | def get_db_prep_save(self, value, connection):
433 | if hasattr(value, 'read'):
434 | return value.read()
435 | else:
436 | return str(value)
437 |
438 | def get_db_prep_lookup(self, lookup_type, value, connection,
439 | prepared=False):
440 | raise TypeError("BlobFields do not support lookups.")
441 |
442 | def value_to_string(self, obj):
443 | return str(self._get_val_from_obj(obj))
444 |
--------------------------------------------------------------------------------
/djangotoolbox/db/basecompiler.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | import django
4 | from django.conf import settings
5 | from django.db.models.fields import NOT_PROVIDED
6 | from django.db.models.query import QuerySet
7 | from django.db.models.sql.compiler import SQLCompiler
8 | from django.db.models.sql.constants import MULTI, SINGLE
9 | from django.db.models.sql.where import AND, OR
10 | from django.db.utils import DatabaseError, IntegrityError
11 | from django.utils.tree import Node
12 | from django.db import connections
13 |
14 | try:
15 | from django.db.models.sql.where import SubqueryConstraint
16 | except ImportError:
17 | SubqueryConstraint = None
18 |
19 | try:
20 | from django.db.models.sql.datastructures import EmptyResultSet
21 | except ImportError:
22 | class EmptyResultSet(Exception):
23 | pass
24 |
25 |
26 | if django.VERSION >= (1, 5):
27 | from django.db.models.constants import LOOKUP_SEP
28 | else:
29 | from django.db.models.sql.constants import LOOKUP_SEP
30 |
31 | if django.VERSION >= (1, 6):
32 | def get_selected_fields(query):
33 | if query.select:
34 | return [info.field for info in (query.select +
35 | query.related_select_cols)]
36 | else:
37 | return query.model._meta.fields
38 | else:
39 | def get_selected_fields(query):
40 | if query.select_fields:
41 | return (query.select_fields + query.related_select_fields)
42 | else:
43 | return query.model._meta.fields
44 |
45 | EMULATED_OPS = {
46 | 'exact': lambda x, y: y in x if isinstance(x, (list, tuple)) else x == y,
47 | 'iexact': lambda x, y: x.lower() == y.lower(),
48 | 'startswith': lambda x, y: x.startswith(y[0]),
49 | 'istartswith': lambda x, y: x.lower().startswith(y[0].lower()),
50 | 'isnull': lambda x, y: x is None if y else x is not None,
51 | 'in': lambda x, y: x in y,
52 | 'lt': lambda x, y: x < y,
53 | 'lte': lambda x, y: x <= y,
54 | 'gt': lambda x, y: x > y,
55 | 'gte': lambda x, y: x >= y,
56 | }
57 |
58 |
59 | class NonrelQuery(object):
60 | """
61 | Base class for nonrel queries.
62 |
63 | Compilers build a nonrel query when they want to fetch some data.
64 | They work by first allowing sql.compiler.SQLCompiler to partly build
65 | a sql.Query, constructing a NonrelQuery query on top of it, and then
66 | iterating over its results.
67 |
68 | This class provides in-memory filtering and ordering and a
69 | framework for converting SQL constraint tree built by Django to a
70 | "representation" more suitable for most NoSQL databases.
71 |
72 | TODO: Replace with FetchCompiler, there are too many query concepts
73 | around, and it isn't a good abstraction for NoSQL databases.
74 |
75 | TODO: Nonrel currently uses constraint's tree built by Django for
76 | its SQL back-ends to handle filtering. However, Django
77 | intermingles translating its lookup / filtering abstraction
78 | to a logical formula with some preprocessing for joins and
79 | this results in hacks in nonrel. It would be a better to pull
80 | out SQL-specific parts from the constraints preprocessing.
81 | """
82 |
83 | # ----------------------------------------------
84 | # Public API
85 | # ----------------------------------------------
86 |
87 | def __init__(self, compiler, fields):
88 | self.compiler = compiler
89 | self.connection = compiler.connection
90 | self.ops = compiler.connection.ops
91 | self.query = compiler.query # sql.Query
92 | self.fields = fields
93 | self._negated = False
94 |
95 | def fetch(self, low_mark=0, high_mark=None):
96 | """
97 | Returns an iterator over some part of query results.
98 | """
99 | raise NotImplementedError
100 |
101 | def count(self, limit=None):
102 | """
103 | Returns the number of objects that would be returned, if
104 | this query was executed, up to `limit`.
105 | """
106 | raise NotImplementedError
107 |
108 | def delete(self):
109 | """
110 | Called by NonrelDeleteCompiler after it builds a delete query.
111 | """
112 | raise NotImplementedError
113 |
114 | def order_by(self, ordering):
115 | """
116 | Reorders query results or execution order. Called by
117 | NonrelCompilers during query building.
118 |
119 | :param ordering: A list with (field, ascending) tuples or a
120 | boolean -- use natural ordering, if any, when
121 | the argument is True and its reverse otherwise
122 | """
123 | raise NotImplementedError
124 |
125 | def add_filter(self, field, lookup_type, negated, value):
126 | """
127 | Adds a single constraint to the query. Called by add_filters for
128 | each constraint leaf in the WHERE tree built by Django.
129 |
130 | :param field: Lookup field (instance of Field); field.column
131 | should be used for database keys
132 | :param lookup_type: Lookup name (e.g. "startswith")
133 | :param negated: Is the leaf negated
134 | :param value: Lookup argument, such as a value to compare with;
135 | already prepared for the database
136 | """
137 | raise NotImplementedError
138 |
139 | def add_filters(self, filters):
140 | """
141 | Converts a constraint tree (sql.where.WhereNode) created by
142 | Django's SQL query machinery to nonrel style filters, calling
143 | add_filter for each constraint.
144 |
145 | This assumes the database doesn't support alternatives of
146 | constraints, you should override this method if it does.
147 |
148 | TODO: Simulate both conjunctions and alternatives in general
149 | let GAE override conjunctions not to split them into
150 | multiple queries.
151 | """
152 | if filters.negated:
153 | self._negated = not self._negated
154 |
155 | if not self._negated and filters.connector != AND:
156 | raise DatabaseError("Only AND filters are supported.")
157 |
158 | # Remove unneeded children from the tree.
159 | children = self._get_children(filters.children)
160 |
161 | if self._negated and filters.connector != OR and len(children) > 1:
162 | raise DatabaseError("When negating a whole filter subgroup "
163 | "(e.g. a Q object) the subgroup filters must "
164 | "be connected via OR, so the non-relational "
165 | "backend can convert them like this: "
166 | "'not (a OR b) => (not a) AND (not b)'.")
167 |
168 | # Recursively call the method for internal tree nodes, add a
169 | # filter for each leaf.
170 | for child in children:
171 | if isinstance(child, Node):
172 | self.add_filters(child)
173 | continue
174 | field, lookup_type, value = self._decode_child(child)
175 | self.add_filter(field, lookup_type, self._negated, value)
176 |
177 | if filters.negated:
178 | self._negated = not self._negated
179 |
180 | # ----------------------------------------------
181 | # Internal API for reuse by subclasses
182 | # ----------------------------------------------
183 |
184 | def _decode_child(self, child):
185 | """
186 | Produces arguments suitable for add_filter from a WHERE tree
187 | leaf (a tuple).
188 | """
189 |
190 | if django.VERSION < (1, 7):
191 | # TODO: Call get_db_prep_lookup directly, constraint.process
192 | # doesn't do much more.
193 | constraint, lookup_type, annotation, value = child
194 | packed, value = constraint.process(lookup_type, value, self.connection)
195 | alias, column, db_type = packed
196 | field = constraint.field
197 | else:
198 | rhs, rhs_params = child.process_rhs(self.compiler, self.connection)
199 |
200 | lookup_type = child.lookup_name
201 |
202 | # Since NoSql databases generally don't support aggregation or
203 | # annotation we simply pass true in this case unless the query has a
204 | # get_aggregation method defined. It's a little troubling however that
205 | # the _nomalize_lookup_value method seems to only use this value in the case
206 | # that the value is an iterable and the lookup_type equals isnull.
207 | if hasattr(self, 'get_aggregation'):
208 | annotation = self.get_aggregation(using=self.connection)[None]
209 | else:
210 | annotation = True
211 |
212 | value = rhs_params
213 |
214 | packed = child.lhs.get_group_by_cols()[0]
215 |
216 | if django.VERSION < (1, 8):
217 | alias, column = packed
218 | else:
219 | alias = packed.alias
220 | column = packed.target.column
221 | field = child.lhs.output_field
222 |
223 | opts = self.query.model._meta
224 | if alias and alias != opts.db_table:
225 | raise DatabaseError("This database doesn't support JOINs "
226 | "and multi-table inheritance.")
227 |
228 | # For parent.child_set queries the field held by the constraint
229 | # is the parent's primary key, while the field the filter
230 | # should consider is the child's foreign key field.
231 | if column != field.column:
232 | if not field.primary_key:
233 | raise DatabaseError("This database doesn't support filtering "
234 | "on non-primary key ForeignKey fields.")
235 |
236 | field = (f for f in opts.fields if f.column == column).next()
237 | assert field.rel is not None
238 |
239 | value = self._normalize_lookup_value(
240 | lookup_type, value, field, annotation)
241 |
242 | return field, lookup_type, value
243 |
244 | def _normalize_lookup_value(self, lookup_type, value, field, annotation):
245 | """
246 | Undoes preparations done by `Field.get_db_prep_lookup` not
247 | suitable for nonrel back-ends and passes the lookup argument
248 | through nonrel's `value_for_db`.
249 |
250 | TODO: Blank `Field.get_db_prep_lookup` and remove this method.
251 | """
252 |
253 | # Undo Field.get_db_prep_lookup putting most values in a list
254 | # (a subclass may override this, so check if it's a list) and
255 | # losing the (True / False) argument to the "isnull" lookup.
256 | if lookup_type not in ('in', 'range', 'year') and \
257 | isinstance(value, (tuple, list)):
258 | if len(value) > 1:
259 | raise DatabaseError("Filter lookup type was %s; expected the "
260 | "filter argument not to be a list. Only "
261 | "'in'-filters can be used with lists." %
262 | lookup_type)
263 | elif lookup_type == 'isnull':
264 | value = annotation
265 | else:
266 | value = value[0]
267 |
268 | # Remove percents added by Field.get_db_prep_lookup (useful
269 | # if one were to use the value in a LIKE expression).
270 | if lookup_type in ('startswith', 'istartswith'):
271 | value = value[:-1]
272 | elif lookup_type in ('endswith', 'iendswith'):
273 | value = value[1:]
274 | elif lookup_type in ('contains', 'icontains'):
275 | value = value[1:-1]
276 |
277 | # Prepare the value for a database using the nonrel framework.
278 | return self.ops.value_for_db(value, field, lookup_type)
279 |
280 | def _get_children(self, children):
281 | """
282 | Filters out nodes of the given contraint tree not needed for
283 | nonrel queries; checks that given constraints are supported.
284 | """
285 | result = []
286 | for child in children:
287 |
288 | if SubqueryConstraint is not None and isinstance(child, SubqueryConstraint):
289 | raise DatabaseError("Subqueries are not supported.")
290 |
291 | if isinstance(child, tuple):
292 | constraint, lookup_type, _, value = child
293 |
294 | # When doing a lookup using a QuerySet Django would use
295 | # a subquery, but this won't work for nonrel.
296 | # TODO: Add a supports_subqueries feature and let
297 | # Django evaluate subqueries instead of passing
298 | # them as SQL strings (QueryWrappers) to
299 | # filtering.
300 | if isinstance(value, QuerySet):
301 | raise DatabaseError("Subqueries are not supported.")
302 |
303 | # Remove leafs that were automatically added by
304 | # sql.Query.add_filter to handle negations of outer
305 | # joins.
306 | if lookup_type == 'isnull' and constraint.field is None:
307 | continue
308 |
309 | result.append(child)
310 | return result
311 |
312 | def _matches_filters(self, entity, filters):
313 | """
314 | Checks if an entity returned by the database satisfies
315 | constraints in a WHERE tree (in-memory filtering).
316 | """
317 |
318 | # Filters without rules match everything.
319 | if not filters.children:
320 | return True
321 |
322 | result = filters.connector == AND
323 |
324 | for child in filters.children:
325 |
326 | # Recursively check a subtree,
327 | if isinstance(child, Node):
328 | submatch = self._matches_filters(entity, child)
329 |
330 | # Check constraint leaf, emulating a database condition.
331 | else:
332 | field, lookup_type, lookup_value = self._decode_child(child)
333 | entity_value = entity[field.column]
334 |
335 | if entity_value is None:
336 | if isinstance(lookup_value, (datetime.datetime, datetime.date,
337 | datetime.time)):
338 | submatch = lookup_type in ('lt', 'lte')
339 | elif lookup_type in (
340 | 'startswith', 'contains', 'endswith', 'iexact',
341 | 'istartswith', 'icontains', 'iendswith'):
342 | submatch = False
343 | else:
344 | submatch = EMULATED_OPS[lookup_type](
345 | entity_value, lookup_value)
346 | else:
347 | submatch = EMULATED_OPS[lookup_type](
348 | entity_value, lookup_value)
349 |
350 | if filters.connector == OR and submatch:
351 | result = True
352 | break
353 | elif filters.connector == AND and not submatch:
354 | result = False
355 | break
356 |
357 | if filters.negated:
358 | return not result
359 | return result
360 |
361 | def _order_in_memory(self, lhs, rhs):
362 | for field, ascending in self.compiler._get_ordering():
363 | column = field.column
364 |
365 | # TOOD: cmp is removed in python 3. Rewrite this logic to leverage
366 | # the __eq__() special function.
367 | a = lhs.get(column)
368 | b = rhs.get(column)
369 |
370 | result = (a > b) - (a < b)
371 | if result != 0:
372 | return result if ascending else -result
373 | return 0
374 |
375 |
376 | class NonrelCompiler(SQLCompiler):
377 | """
378 | Base class for data fetching back-end compilers.
379 |
380 | Note that nonrel compilers derive from sql.compiler.SQLCompiler and
381 | thus hold a reference to a sql.Query, not a NonrelQuery.
382 |
383 | TODO: Separate FetchCompiler from the abstract NonrelCompiler.
384 | """
385 |
386 | def __init__(self, query, connection, using):
387 | """
388 | Initializes the underlying SQLCompiler.
389 | """
390 | super(NonrelCompiler, self).__init__(query, connection, using)
391 | self.ops = self.connection.ops
392 |
393 | # ----------------------------------------------
394 | # Public API
395 | # ----------------------------------------------
396 |
397 | def results_iter(self, results=None):
398 | """
399 | Returns an iterator over the results from executing query given
400 | to this compiler. Called by QuerySet methods.
401 | """
402 |
403 | if results is None:
404 | fields = self.get_fields()
405 | try:
406 | results = self.build_query(fields).fetch(
407 | self.query.low_mark, self.query.high_mark)
408 | except EmptyResultSet:
409 | results = []
410 |
411 | for entity in results:
412 | yield self._make_result(entity, fields)
413 |
414 | def has_results(self):
415 | return self.get_count(check_exists=True)
416 |
417 | def execute_sql(self, result_type=MULTI):
418 | """
419 | Handles SQL-like aggregate queries. This class only emulates COUNT
420 | by using abstract NonrelQuery.count method.
421 | """
422 | self.pre_sql_setup()
423 |
424 | aggregates = self.query.aggregate_select.values()
425 |
426 | # Simulate a count().
427 | if aggregates:
428 | assert len(aggregates) == 1
429 | aggregate = aggregates[0]
430 | if django.VERSION < (1, 8):
431 | if aggregate.sql_function != 'COUNT':
432 | raise NotImplementedError("The database backend only supports count() queries.")
433 | else:
434 | if aggregate.function != 'COUNT':
435 | raise NotImplementedError("The database backend only supports count() queries.")
436 |
437 | opts = self.query.get_meta()
438 |
439 | if django.VERSION < (1, 8):
440 | if aggregate.col != '*' and aggregate.col != (opts.db_table, opts.pk.column):
441 | raise DatabaseError("This database backend only supports "
442 | "count() queries on the primary key.")
443 | else:
444 | # Fair warning: the latter part of this or statement hasn't been tested
445 | if aggregate.input_field.value != '*' and aggregate.input_field != (opts.db_table, opts.pk.column):
446 | raise DatabaseError("This database backend only supports "
447 | "count() queries on the primary key.")
448 |
449 | count = self.get_count()
450 | if result_type is SINGLE:
451 | return [count]
452 | elif result_type is MULTI:
453 | return [[count]]
454 |
455 | # ----------------------------------------------
456 | # Additional NonrelCompiler API
457 | # ----------------------------------------------
458 |
459 | def _make_result(self, entity, fields):
460 | """
461 | Decodes values for the given fields from the database entity.
462 |
463 | The entity is assumed to be a dict using field database column
464 | names as keys. Decodes values using `value_from_db` as well as
465 | the standard `convert_values`.
466 | """
467 | result = []
468 | for field in fields:
469 | value = entity.get(field.column, NOT_PROVIDED)
470 | if value is NOT_PROVIDED:
471 | value = field.get_default()
472 | else:
473 | value = self.ops.value_from_db(value, field)
474 | # This is the default behavior of ``query.convert_values``
475 | # until django 1.8, where multiple converters are a thing.
476 | value = self.connection.ops.convert_values(value, field)
477 | if value is None and not field.null:
478 | raise IntegrityError("Non-nullable field %s can't be None!" %
479 | field.name)
480 | result.append(value)
481 | return result
482 |
483 | def check_query(self):
484 | """
485 | Checks if the current query is supported by the database.
486 |
487 | In general, we expect queries requiring JOINs (many-to-many
488 | relations, abstract model bases, or model spanning filtering),
489 | using DISTINCT (through `QuerySet.distinct()`, which is not
490 | required in most situations) or using the SQL-specific
491 | `QuerySet.extra()` to not work with nonrel back-ends.
492 | """
493 | if hasattr(self.query, 'is_empty') and self.query.is_empty():
494 | raise EmptyResultSet()
495 | if (len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) > 1
496 | or self.query.distinct or self.query.extra or self.query.having):
497 | raise DatabaseError("This query is not supported by the database.")
498 |
499 | def get_count(self, check_exists=False):
500 | """
501 | Counts objects matching the current filters / constraints.
502 |
503 | :param check_exists: Only check if any object matches
504 | """
505 | if check_exists:
506 | high_mark = 1
507 | else:
508 | high_mark = self.query.high_mark
509 | try:
510 | return self.build_query().count(high_mark)
511 | except EmptyResultSet:
512 | return 0
513 |
514 | def build_query(self, fields=None):
515 | """
516 | Checks if the underlying SQL query is supported and prepares
517 | a NonrelQuery to be executed on the database.
518 | """
519 | self.check_query()
520 | if fields is None:
521 | fields = self.get_fields()
522 | query = self.query_class(self, fields)
523 | query.add_filters(self.query.where)
524 | query.order_by(self._get_ordering())
525 |
526 | # This at least satisfies the most basic unit tests.
527 | if django.VERSION < (1, 8):
528 | if connections[self.using].use_debug_cursor or (connections[self.using].use_debug_cursor is None and settings.DEBUG):
529 | self.connection.queries.append({'sql': repr(query)})
530 | else:
531 | if connections[self.using].force_debug_cursor or (connections[self.using].force_debug_cursor is None and settings.DEBUG):
532 | self.connection.queries.append({'sql': repr(query)})
533 | return query
534 |
535 | def get_fields(self):
536 | """
537 | Returns fields which should get loaded from the back-end by the
538 | current query.
539 | """
540 |
541 | # We only set this up here because related_select_fields isn't
542 | # populated until execute_sql() has been called.
543 | fields = get_selected_fields(self.query)
544 |
545 | # If the field was deferred, exclude it from being passed
546 | # into `resolve_columns` because it wasn't selected.
547 | only_load = self.deferred_to_columns()
548 | if only_load:
549 | db_table = self.query.model._meta.db_table
550 | only_load = dict((k, v) for k, v in only_load.items()
551 | if v or k == db_table)
552 | if len(only_load.keys()) > 1:
553 | raise DatabaseError("Multi-table inheritance is not "
554 | "supported by non-relational DBs %s." %
555 | repr(only_load))
556 | fields = [f for f in fields if db_table in only_load and
557 | f.column in only_load[db_table]]
558 |
559 | query_model = self.query.model
560 | if query_model._meta.proxy:
561 | query_model = query_model._meta.proxy_for_model
562 |
563 | for field in fields:
564 | if field.model._meta != query_model._meta:
565 | raise DatabaseError("Multi-table inheritance is not "
566 | "supported by non-relational DBs.")
567 | return fields
568 |
569 | def _get_ordering(self):
570 | """
571 | Returns a list of (field, ascending) tuples that the query
572 | results should be ordered by. If there is no field ordering
573 | defined returns just the standard_ordering (a boolean, needed
574 | for MongoDB "$natural" ordering).
575 | """
576 | opts = self.query.get_meta()
577 | if not self.query.default_ordering:
578 | ordering = self.query.order_by
579 | else:
580 | ordering = self.query.order_by or opts.ordering
581 |
582 | if not ordering:
583 | return self.query.standard_ordering
584 |
585 | field_ordering = []
586 | for order in ordering:
587 | if LOOKUP_SEP in order:
588 | raise DatabaseError("Ordering can't span tables on "
589 | "non-relational backends (%s)." % order)
590 | if order == '?':
591 | raise DatabaseError("Randomized ordering isn't supported by "
592 | "the backend.")
593 |
594 | ascending = not order.startswith('-')
595 | if not self.query.standard_ordering:
596 | ascending = not ascending
597 |
598 | name = order.lstrip('+-')
599 | if name == 'pk':
600 | name = opts.pk.name
601 |
602 | field_ordering.append((opts.get_field(name), ascending))
603 | return field_ordering
604 |
605 |
606 | class NonrelInsertCompiler(NonrelCompiler):
607 | """
608 | Base class for all compliers that create new entities or objects
609 | in the database. It has to define execute_sql method due to being
610 | used in place of a SQLInsertCompiler.
611 |
612 | TODO: Analyze if it's always true that when field is None we should
613 | use the PK from self.query (check if the column assertion
614 | below ever fails).
615 | """
616 |
617 | def execute_sql(self, return_id=False):
618 | self.pre_sql_setup()
619 |
620 | to_insert = []
621 | pk_field = self.query.get_meta().pk
622 | for obj in self.query.objs:
623 | field_values = {}
624 | for field in self.query.fields:
625 | value = field.get_db_prep_save(
626 | getattr(obj, field.attname) if self.query.raw else field.pre_save(obj, obj._state.adding),
627 | connection=self.connection
628 | )
629 | if value is None and not field.null and not field.primary_key:
630 | raise IntegrityError("You can't set %s (a non-nullable "
631 | "field) to None!" % field.name)
632 |
633 | # Prepare value for database, note that query.values have
634 | # already passed through get_db_prep_save.
635 | value = self.ops.value_for_db(value, field)
636 |
637 | field_values[field.column] = value
638 | to_insert.append(field_values)
639 |
640 | key = self.insert(to_insert, return_id=return_id)
641 |
642 | # Pass the key value through normal database deconversion.
643 | return self.ops.convert_values(self.ops.value_from_db(key, pk_field), pk_field)
644 |
645 | def insert(self, values, return_id):
646 | """
647 | Creates a new entity to represent a model.
648 |
649 | Note that the returned key will go through the same database
650 | deconversions that every value coming from the database does
651 | (`convert_values` and `value_from_db`).
652 |
653 | :param values: The model object as a list of (field, value)
654 | pairs; each value is already prepared for the
655 | database
656 | :param return_id: Whether to return the id or key of the newly
657 | created entity
658 | """
659 | raise NotImplementedError
660 |
661 |
662 | class NonrelUpdateCompiler(NonrelCompiler):
663 |
664 | def execute_sql(self, result_type):
665 | self.pre_sql_setup()
666 |
667 | values = []
668 | for field, _, value in self.query.values:
669 | if hasattr(value, 'prepare_database_save'):
670 | value = value.prepare_database_save(field)
671 | else:
672 | value = field.get_db_prep_save(value,
673 | connection=self.connection)
674 | value = self.ops.value_for_db(value, field)
675 | values.append((field, value))
676 | return self.update(values)
677 |
678 | def update(self, values):
679 | """
680 | Changes an entity that already exists in the database.
681 |
682 | :param values: A list of (field, new-value) pairs
683 | """
684 | raise NotImplementedError
685 |
686 |
687 | class NonrelDeleteCompiler(NonrelCompiler):
688 |
689 | def execute_sql(self, result_type=MULTI):
690 | try:
691 | self.build_query([self.query.get_meta().pk]).delete()
692 | except EmptyResultSet:
693 | pass
694 |
695 |
696 | class NonrelAggregateCompiler(NonrelCompiler):
697 | pass
698 |
699 |
700 | class NonrelDateCompiler(NonrelCompiler):
701 | pass
702 |
703 |
704 | class NonrelDateTimeCompiler(NonrelCompiler):
705 | pass
706 |
--------------------------------------------------------------------------------
/djangotoolbox/db/base.py:
--------------------------------------------------------------------------------
1 | from django.utils.six.moves import cPickle as pickle
2 | import datetime
3 |
4 | from django.conf import settings
5 |
6 | import django
7 |
8 | if django.VERSION < (1, 8):
9 | from django.db.backends import (
10 | BaseDatabaseFeatures,
11 | BaseDatabaseOperations,
12 | BaseDatabaseWrapper,
13 | BaseDatabaseClient,
14 | BaseDatabaseValidation,
15 | BaseDatabaseIntrospection)
16 | else:
17 | from django.db.backends.base.base import BaseDatabaseWrapper
18 | from django.db.backends.base.client import BaseDatabaseClient
19 | from django.db.backends.base.features import BaseDatabaseFeatures
20 | from django.db.backends.base.validation import BaseDatabaseValidation
21 | from django.db.backends.base.introspection import BaseDatabaseIntrospection
22 | from django.db.backends.base.operations import BaseDatabaseOperations
23 |
24 | from django.db.utils import DatabaseError
25 | from django.utils import timezone
26 | from django.utils.functional import Promise
27 |
28 | if django.VERSION < (1, 5):
29 | from django.utils.encoding import (smart_unicode as smart_text,
30 | smart_str as smart_bytes)
31 | else:
32 | from django.utils.encoding import smart_text, smart_bytes
33 |
34 | if django.VERSION < (1, 5):
35 | from django.utils.safestring import (SafeString as SafeBytes,
36 | SafeUnicode as SafeText,
37 | EscapeString as EscapeBytes,
38 | EscapeUnicode as EscapeText)
39 | else:
40 | from django.utils.safestring import SafeBytes, SafeText, EscapeBytes, EscapeText
41 |
42 | from .creation import NonrelDatabaseCreation
43 |
44 |
45 | class NonrelDatabaseFeatures(BaseDatabaseFeatures):
46 | # Most NoSQL databases don't have true transaction support.
47 | supports_transactions = False
48 |
49 | # NoSQL databases usually return a key after saving a new object.
50 | can_return_id_from_insert = True
51 |
52 | # TODO: Doesn't seem necessary in general, move to back-ends.
53 | # Mongo: see PyMongo's FAQ; GAE: see: http://timezones.appspot.com/.
54 | supports_date_lookup_using_string = False
55 | supports_timezones = False
56 |
57 | # Features that are commonly not available on nonrel databases.
58 | supports_joins = False
59 | supports_select_related = False
60 | supports_deleting_related_objects = False
61 |
62 | # Having to decide whether to use an INSERT or an UPDATE query is
63 | # specific to SQL-based databases.
64 | distinguishes_insert_from_update = False
65 |
66 | # Can primary_key be used on any field? Without encoding usually
67 | # only a limited set of types is acceptable for keys. This is a set
68 | # of all field kinds (internal_types) for which the primary_key
69 | # argument may be used.
70 | # TODO: Use during model validation.
71 | # TODO: Move to core and use to skip unsuitable Django tests.
72 | supports_primary_key_on = set(NonrelDatabaseCreation.data_types.keys()) - \
73 | set(('ForeignKey', 'OneToOneField', 'ManyToManyField', 'RawField',
74 | 'AbstractIterableField', 'ListField', 'SetField', 'DictField',
75 | 'EmbeddedModelField', 'BlobField'))
76 |
77 | # Django 1.4 compatibility
78 | def _supports_transactions(self):
79 | return False
80 |
81 |
82 | class NonrelDatabaseOperations(BaseDatabaseOperations):
83 | """
84 | Override all database conversions normally done by fields (through
85 | `get_db_prep_value/save/lookup`) to make it possible to pass Python
86 | values directly to the database layer. On the other hand, provide a
87 | framework for making type-based conversions -- drivers of NoSQL
88 | database either can work with Python objects directly, sometimes
89 | representing one type using a another or expect everything encoded
90 | in some specific manner.
91 |
92 | Django normally handles conversions for the database by providing
93 | `BaseDatabaseOperations.value_to_db_*` / `convert_values` methods,
94 | but there are some problems with them:
95 | -- some preparations need to be done for all values or for values
96 | of a particular "kind" (e.g. lazy objects evaluation or casting
97 | strings wrappers to standard types);
98 | -- some conversions need more info about the field or model the
99 | value comes from (e.g. key conversions, embedded deconversion);
100 | -- there are no value_to_db_* methods for some value types (bools);
101 | -- we need to handle collecion fields (list, set, dict): they
102 | need to differentiate between deconverting from database and
103 | deserializing (so single to_python is inconvenient) and need to
104 | do some recursion, so a single `value_for_db` is better than one
105 | method for each field kind.
106 | Don't use these standard methods in nonrel, `value_for/from_db` are
107 | more elastic and keeping all conversions in one place makes the
108 | code easier to analyse.
109 |
110 | Please note, that after changes to type conversions, data saved
111 | using preexisting methods needs to be handled; and also that Django
112 | does not expect any special database driver exceptions, so any such
113 | exceptions should be reraised as django.db.utils.DatabaseError.
114 |
115 | TODO: Consider replacing all `value_to_db_*` and `convert_values`
116 | with just `BaseDatabaseOperations.value_for/from_db` and also
117 | moving there code from `Field.get_db_prep_lookup` (and maybe
118 | `RelatedField.get_db_prep_lookup`).
119 | """
120 |
121 | def pk_default_value(self):
122 | """
123 | Returns None, to be interpreted by back-ends as a request to
124 | generate a new key for an "inserted" object.
125 | """
126 | return None
127 |
128 | def quote_name(self, name):
129 | """
130 | Does not do any quoting, as it is not needed for most NoSQL
131 | databases.
132 | """
133 | return name
134 |
135 | def prep_for_like_query(self, value):
136 | """
137 | Does no conversion, parent string-cast is SQL specific.
138 | """
139 | return value
140 |
141 | def prep_for_iexact_query(self, value):
142 | """
143 | Does no conversion, parent string-cast is SQL specific.
144 | """
145 | return value
146 |
147 | def value_to_db_auto(self, value):
148 | """
149 | Assuming that the database has its own key type, leaves any
150 | conversions to the back-end.
151 |
152 | This method is added my nonrel to allow various types to be
153 | used for automatic primary keys. `AutoField.get_db_prep_value`
154 | calls it to prepare field's value for the database.
155 |
156 | Note that Django can pass a string representation of the value
157 | instead of the value itself (after receiving it as a query
158 | parameter for example), so you'll likely need to limit
159 | your `AutoFields` in a way that makes `str(value)` reversible.
160 |
161 | TODO: This could become a part of `value_for_db` if it makes
162 | to Django (with a `field_kind` condition).
163 | """
164 | return value
165 |
166 | def value_to_db_date(self, value):
167 | """
168 | Unlike with SQL database clients, it's better to assume that
169 | a date can be stored directly.
170 | """
171 | return value
172 |
173 | def value_to_db_datetime(self, value):
174 | """
175 | We may pass a datetime object to a database driver without
176 | casting it to a string.
177 | """
178 | return value
179 |
180 | def value_to_db_time(self, value):
181 | """
182 | Unlike with SQL database clients, we may assume that a time can
183 | be stored directly.
184 | """
185 | return value
186 |
187 | def value_to_db_decimal(self, value, max_digits, decimal_places):
188 | """
189 | We may assume that a decimal can be passed to a NoSQL database
190 | driver directly.
191 | """
192 | return value
193 |
194 | # Django 1.4 compatibility
195 | def year_lookup_bounds(self, value):
196 | return self.year_lookup_bounds_for_datetime_field(value)
197 |
198 | def year_lookup_bounds_for_date_field(self, value):
199 | """
200 | Converts year bounds to date bounds as these can likely be
201 | used directly, also adds one to the upper bound as it should be
202 | natural to use one strict inequality for BETWEEN-like filters
203 | for most nonrel back-ends.
204 | """
205 | first = datetime.date(value, 1, 1)
206 | second = datetime.date(value + 1, 1, 1)
207 | return [first, second]
208 |
209 | def year_lookup_bounds_for_datetime_field(self, value):
210 | """
211 | Converts year bounds to datetime bounds.
212 | """
213 | first = datetime.datetime(value, 1, 1, 0, 0, 0, 0)
214 | second = datetime.datetime(value + 1, 1, 1, 0, 0, 0, 0)
215 | if settings.USE_TZ:
216 | tz = timezone.get_current_timezone()
217 | first = timezone.make_aware(first, tz)
218 | second = timezone.make_aware(second, tz)
219 | return [first, second]
220 |
221 | def convert_values(self, value, field):
222 | """
223 | We may assume that values returned by the database are standard
224 | Python types suitable to be passed to fields.
225 | """
226 | return value
227 |
228 | def check_aggregate_support(self, aggregate):
229 | """
230 | Nonrel back-ends are only expected to implement COUNT in
231 | general.
232 | """
233 | from django.db.models.sql.aggregates import Count
234 | if not isinstance(aggregate, Count):
235 | raise NotImplementedError("This database does not support %r "
236 | "aggregates." % type(aggregate))
237 |
238 | def value_for_db(self, value, field, lookup=None):
239 | """
240 | Does type-conversions needed before storing a value in the
241 | the database or using it as a filter parameter.
242 |
243 | This is a convience wrapper that only precomputes field's kind
244 | and a db_type for the field (or the primary key of the related
245 | model for ForeignKeys etc.) and knows that arguments to the
246 | `isnull` lookup (`True` or `False`) should not be converted,
247 | while some other lookups take a list of arguments.
248 | In the end, it calls `_value_for_db` to do the real work; you
249 | should typically extend that method, but only call this one.
250 |
251 | :param value: A value to be passed to the database driver
252 | :param field: A field the value comes from
253 | :param lookup: None if the value is being prepared for storage;
254 | lookup type name, when its going to be used as a
255 | filter argument
256 | """
257 | field, field_kind, db_type = self._convert_as(field, lookup)
258 |
259 | # Argument to the "isnull" lookup is just a boolean, while some
260 | # other lookups take a list of values.
261 | if lookup == 'isnull':
262 | return value
263 | elif lookup in ('in', 'range', 'year'):
264 | return [self._value_for_db(subvalue, field,
265 | field_kind, db_type, lookup)
266 | for subvalue in value]
267 | else:
268 | return self._value_for_db(value, field,
269 | field_kind, db_type, lookup)
270 |
271 | def value_from_db(self, value, field):
272 | """
273 | Performs deconversions defined by `_value_from_db`.
274 |
275 | :param value: A value received from the database client
276 | :param field: A field the value is meant for
277 | """
278 | return self._value_from_db(value, *self._convert_as(field))
279 |
280 | def _convert_as(self, field, lookup=None):
281 | """
282 | Computes parameters that should be used for preparing the field
283 | for the database or deconverting a database value for it.
284 | """
285 | # We need to compute db_type using the original field to allow
286 | # GAE to use different storage for primary and foreign keys.
287 | db_type = self.connection.creation.db_type(field)
288 |
289 | if field.rel is not None:
290 | field = field.rel.get_related_field()
291 | field_kind = field.get_internal_type()
292 |
293 | # Values for standard month / day queries are integers.
294 | if (field_kind in ('DateField', 'DateTimeField') and
295 | lookup in ('month', 'day')):
296 | db_type = 'integer'
297 |
298 | return field, field_kind, db_type
299 |
300 | def _value_for_db(self, value, field, field_kind, db_type, lookup):
301 | """
302 | Converts a standard Python value to a type that can be stored
303 | or processed by the database driver.
304 |
305 | This implementation only converts elements of iterables passed
306 | by collection fields, evaluates Django's lazy objects and
307 | marked strings and handles embedded models.
308 | Currently, we assume that dict keys and column, model, module
309 | names (strings) of embedded models require no conversion.
310 |
311 | We need to know the field for two reasons:
312 | -- to allow back-ends having separate key spaces for different
313 | tables to create keys refering to the right table (which can
314 | be the field model's table or the table of the model of the
315 | instance a ForeignKey or other relation field points to).
316 | -- to know the field of values passed by typed collection
317 | fields and to use the proper fields when deconverting values
318 | stored for typed embedding field.
319 | Avoid using the field in any other way than by inspecting its
320 | properties, it may not hold any value or hold a value other
321 | than the one you're asked to convert.
322 |
323 | You may want to call this method before doing other back-end
324 | specific conversions.
325 |
326 | :param value: A value to be passed to the database driver
327 | :param field: A field having the same properties as the field
328 | the value comes from; instead of related fields
329 | you'll get the related model primary key, as the
330 | value usually needs to be converted using its
331 | properties
332 | :param field_kind: Equal to field.get_internal_type()
333 | :param db_type: Same as creation.db_type(field)
334 | :param lookup: None if the value is being prepared for storage;
335 | lookup type name, when its going to be used as a
336 | filter argument
337 | """
338 |
339 | # Back-ends may want to store empty lists or dicts as None.
340 | if value is None:
341 | return None
342 |
343 | # Force evaluation of lazy objects (e.g. lazy translation
344 | # strings).
345 | # Some back-ends pass values directly to the database driver,
346 | # which may fail if it relies on type inspection and gets a
347 | # functional proxy.
348 | # This code relies on unicode cast in django.utils.functional
349 | # just evaluating the wrapped function and doing nothing more.
350 | # TODO: This has been partially fixed in vanilla with:
351 | # https://code.djangoproject.com/changeset/17698, however
352 | # still fails for proxies in lookups; reconsider in 1.4.
353 | # Also research cases of database operations not done
354 | # through the sql.Query.
355 | if isinstance(value, Promise):
356 | value = smart_text(value)
357 |
358 | # Django wraps strings marked as safe or needed escaping,
359 | # convert them to just strings for type-inspecting back-ends.
360 | if isinstance(value, (SafeBytes, EscapeBytes)):
361 | value = smart_bytes(value)
362 | elif isinstance(value, (SafeText, EscapeText)):
363 | value = smart_text(value)
364 |
365 | # Convert elements of collection fields.
366 | if field_kind in ('ListField', 'SetField', 'DictField',):
367 | value = self._value_for_db_collection(value, field,
368 | field_kind, db_type, lookup)
369 |
370 | # Store model instance fields' values.
371 | elif field_kind == 'EmbeddedModelField':
372 | value = self._value_for_db_model(value, field,
373 | field_kind, db_type, lookup)
374 |
375 | return value
376 |
377 | def _value_from_db(self, value, field, field_kind, db_type):
378 | """
379 | Converts a database type to a type acceptable by the field.
380 |
381 | If you encoded a value for storage in the database, reverse the
382 | encoding here. This implementation only recursively deconverts
383 | elements of collection fields and handles embedded models.
384 |
385 | You may want to call this method after any back-end specific
386 | deconversions.
387 |
388 | :param value: A value to be passed to the database driver
389 | :param field: A field having the same properties as the field
390 | the value comes from
391 | :param field_kind: Equal to field.get_internal_type()
392 | :param db_type: Same as creation.db_type(field)
393 |
394 | Note: lookup values never get deconverted.
395 | """
396 |
397 | # We did not convert Nones.
398 | if value is None:
399 | return None
400 |
401 | # Deconvert items or values of a collection field.
402 | if field_kind in ('ListField', 'SetField', 'DictField',):
403 | value = self._value_from_db_collection(value, field,
404 | field_kind, db_type)
405 |
406 | # Reinstatiate a serialized model.
407 | elif field_kind == 'EmbeddedModelField':
408 | value = self._value_from_db_model(value, field,
409 | field_kind, db_type)
410 |
411 | return value
412 |
413 | def _value_for_db_collection(self, value, field, field_kind, db_type,
414 | lookup):
415 | """
416 | Recursively converts values from AbstractIterableFields.
417 |
418 | Note that collection lookup values are plain values rather than
419 | lists, sets or dicts, but they still should be converted as a
420 | collection item (assuming all items or values are converted in
421 | the same way).
422 |
423 | We base the conversion on field class / kind and assume some
424 | knowledge about field internals (e.g. that the field has an
425 | "item_field" property that gives the right subfield for any of
426 | its values), to avoid adding a framework for determination of
427 | parameters for items' conversions; we do the conversion here
428 | rather than inside get_db_prep_save/lookup for symmetry with
429 | deconversion (which can't be in to_python because the method is
430 | also used for data not coming from the database).
431 |
432 | Returns a list, set, dict, string or bytes according to the
433 | db_type given.
434 | If the "list" db_type used for DictField, a list with keys and
435 | values interleaved will be returned (list of pairs is not good,
436 | because lists / tuples may need conversion themselves; the list
437 | may still be nested for dicts containing collections).
438 | The "string" and "bytes" db_types use serialization with pickle
439 | protocol 0 or 2 respectively.
440 | If an unknown db_type is specified, returns a generator
441 | yielding converted elements / pairs with converted values.
442 | """
443 | subfield, subkind, db_subtype = self._convert_as(field.item_field,
444 | lookup)
445 |
446 | # Do convert filter parameters.
447 | if lookup:
448 | # Special case where we are looking for an empty list
449 | if lookup == 'exact' and db_type == 'list' and value == u'[]':
450 | return []
451 | value = self._value_for_db(value, subfield,
452 | subkind, db_subtype, lookup)
453 |
454 | # Convert list/set items or dict values.
455 | else:
456 | if field_kind == 'DictField':
457 |
458 | # Generator yielding pairs with converted values.
459 | value = (
460 | (key, self._value_for_db(subvalue, subfield,
461 | subkind, db_subtype, lookup))
462 | for key, subvalue in value.iteritems())
463 |
464 | # Return just a dict, a once-flattened list;
465 | if db_type == 'dict':
466 | return dict(value)
467 | elif db_type == 'list':
468 | return list(item for pair in value for item in pair)
469 |
470 | else:
471 |
472 | # Generator producing converted items.
473 | value = (
474 | self._value_for_db(subvalue, subfield,
475 | subkind, db_subtype, lookup)
476 | for subvalue in value)
477 |
478 | # "list" may be used for SetField.
479 | if db_type in 'list':
480 | return list(value)
481 | elif db_type == 'set':
482 | # assert field_kind != 'ListField'
483 | return set(value)
484 |
485 | # Pickled formats may be used for all collection fields,
486 | # the fields "natural" type is serialized (something
487 | # concrete is needed, pickle can't handle generators :-)
488 | if db_type == 'bytes':
489 | return pickle.dumps(field._type(value), protocol=2)
490 | elif db_type == 'string':
491 | return pickle.dumps(field._type(value))
492 |
493 | # If nothing matched, pass the generator to the back-end.
494 | return value
495 |
496 | def _value_from_db_collection(self, value, field, field_kind, db_type):
497 | """
498 | Recursively deconverts values for AbstractIterableFields.
499 |
500 | Assumes that all values in a collection can be deconverted
501 | using a single field (Field.item_field, possibly a RawField).
502 |
503 | Returns a value in a format proper for the field kind (the
504 | value will normally not go through to_python).
505 | """
506 | subfield, subkind, db_subtype = self._convert_as(field.item_field)
507 |
508 | # Unpickle (a dict) if a serialized storage is used.
509 | if db_type == 'bytes' or db_type == 'string':
510 | value = pickle.loads(value)
511 |
512 | if field_kind == 'DictField':
513 |
514 | # Generator yielding pairs with deconverted values, the
515 | # "list" db_type stores keys and values interleaved.
516 | if db_type == 'list':
517 | value = zip(value[::2], value[1::2])
518 | else:
519 | value = value.iteritems()
520 |
521 | # DictField needs to hold a dict.
522 | return dict(
523 | (key, self._value_from_db(subvalue, subfield,
524 | subkind, db_subtype))
525 | for key, subvalue in value)
526 | else:
527 |
528 | # Generator yielding deconverted items.
529 | value = (
530 | self._value_from_db(subvalue, subfield,
531 | subkind, db_subtype)
532 | for subvalue in value)
533 |
534 | # The value will be available from the field without any
535 | # further processing and it has to have the right type.
536 | if field_kind == 'ListField':
537 | return list(value)
538 | elif field_kind == 'SetField':
539 | return set(value)
540 |
541 | # A new field kind? Maybe it can take a generator.
542 | return value
543 |
544 | def _value_for_db_model(self, value, field, field_kind, db_type, lookup):
545 | """
546 | Converts a field => value mapping received from an
547 | EmbeddedModelField the format chosen for the field storage.
548 |
549 | The embedded instance fields' values are also converted /
550 | deconverted using value_for/from_db, so any back-end
551 | conversions will be applied.
552 |
553 | Returns (field.column, value) pairs, possibly augmented with
554 | model info (to be able to deconvert the embedded instance for
555 | untyped fields) encoded according to the db_type chosen.
556 | If "dict" db_type is given a Python dict is returned.
557 | If "list db_type is chosen a list with columns and values
558 | interleaved will be returned. Note that just a single level of
559 | the list is flattened, so it still may be nested -- when the
560 | embedded instance holds other embedded models or collections).
561 | Using "bytes" or "string" pickles the mapping using pickle
562 | protocol 0 or 2 respectively.
563 | If an unknown db_type is used a generator yielding (column,
564 | value) pairs with values converted will be returned.
565 |
566 | TODO: How should EmbeddedModelField lookups work?
567 | """
568 | if lookup:
569 | # raise NotImplementedError("Needs specification.")
570 | return value
571 |
572 | # Convert using proper instance field's info, change keys from
573 | # fields to columns.
574 | # TODO/XXX: Arguments order due to Python 2.5 compatibility.
575 | value = (
576 | (subfield.column, self._value_for_db(
577 | subvalue, lookup=lookup, *self._convert_as(subfield, lookup)))
578 | for subfield, subvalue in value.iteritems())
579 |
580 | # Cast to a dict, interleave columns with values on a list,
581 | # serialize, or return a generator.
582 | if db_type == 'dict':
583 | value = dict(value)
584 | elif db_type == 'list':
585 | value = list(item for pair in value for item in pair)
586 | elif db_type == 'bytes':
587 | value = pickle.dumps(dict(value), protocol=2)
588 | elif db_type == 'string':
589 | value = pickle.dumps(dict(value))
590 |
591 | return value
592 |
593 | def _value_from_db_model(self, value, field, field_kind, db_type):
594 | """
595 | Deconverts values stored for EmbeddedModelFields.
596 |
597 | Embedded instances are stored as a (column, value) pairs in a
598 | dict, a single-flattened list or a serialized dict.
599 |
600 | Returns a tuple with model class and field.attname => value
601 | mapping.
602 | """
603 |
604 | # Separate keys from values and create a dict or unpickle one.
605 | if db_type == 'list':
606 | value = dict(zip(value[::2], value[1::2]))
607 | elif db_type == 'bytes' or db_type == 'string':
608 | value = pickle.loads(value)
609 |
610 | # Let untyped fields determine the embedded instance's model.
611 | embedded_model = field.stored_model(value)
612 |
613 | # Deconvert fields' values and prepare a dict that can be used
614 | # to initialize a model (by changing keys from columns to
615 | # attribute names).
616 | return embedded_model, dict(
617 | (subfield.attname, self._value_from_db(
618 | value[subfield.column], *self._convert_as(subfield)))
619 | for subfield in embedded_model._meta.fields
620 | if subfield.column in value)
621 |
622 | def _value_for_db_key(self, value, field_kind):
623 | """
624 | Converts value to be used as a key to an acceptable type.
625 | On default we do no encoding, only allowing key values directly
626 | acceptable by the database for its key type (if any).
627 |
628 | The conversion has to be reversible given the field type,
629 | encoding should preserve comparisons.
630 |
631 | Use this to expand the set of fields that can be used as
632 | primary keys, return value suitable for a key rather than
633 | a key itself.
634 | """
635 | raise DatabaseError(
636 | "%s may not be used as primary key field." % field_kind)
637 |
638 | def _value_from_db_key(self, value, field_kind):
639 | """
640 | Decodes a value previously encoded for a key.
641 | """
642 | return value
643 |
644 |
645 | class NonrelDatabaseClient(BaseDatabaseClient):
646 | pass
647 |
648 |
649 | class NonrelDatabaseValidation(BaseDatabaseValidation):
650 | pass
651 |
652 |
653 | class NonrelDatabaseIntrospection(BaseDatabaseIntrospection):
654 |
655 | def table_names(self, cursor=None):
656 | """
657 | Returns a list of names of all tables that exist in the
658 | database.
659 | """
660 | return self.django_table_names()
661 |
662 |
663 | class FakeCursor(object):
664 |
665 | def __getattribute__(self, name):
666 | raise Database.NotSupportedError("Cursors are not supported.")
667 |
668 | def __setattr__(self, name, value):
669 | raise Database.NotSupportedError("Cursors are not supported.")
670 |
671 |
672 | class FakeConnection(object):
673 |
674 | def commit(self):
675 | pass
676 |
677 | def rollback(self):
678 | pass
679 |
680 | def cursor(self):
681 | return FakeCursor()
682 |
683 | def close(self):
684 | pass
685 |
686 |
687 | class Database(object):
688 | class Error(Exception):
689 | pass
690 |
691 | class InterfaceError(Error):
692 | pass
693 |
694 | class DatabaseError(Error):
695 | pass
696 |
697 | class DataError(DatabaseError):
698 | pass
699 |
700 | class OperationalError(DatabaseError):
701 | pass
702 |
703 | class IntegrityError(DatabaseError):
704 | pass
705 |
706 | class InternalError(DatabaseError):
707 | pass
708 |
709 | class ProgrammingError(DatabaseError):
710 | pass
711 |
712 | class NotSupportedError(DatabaseError):
713 | pass
714 |
715 |
716 | class NonrelDatabaseWrapper(BaseDatabaseWrapper):
717 |
718 | Database = Database
719 |
720 | # These fake operators are required for SQLQuery.as_sql() support.
721 | operators = {
722 | 'exact': '= %s',
723 | 'iexact': '= UPPER(%s)',
724 | 'contains': 'LIKE %s',
725 | 'icontains': 'LIKE UPPER(%s)',
726 | 'regex': '~ %s',
727 | 'iregex': '~* %s',
728 | 'gt': '> %s',
729 | 'gte': '>= %s',
730 | 'lt': '< %s',
731 | 'lte': '<= %s',
732 | 'startswith': 'LIKE %s',
733 | 'endswith': 'LIKE %s',
734 | 'istartswith': 'LIKE UPPER(%s)',
735 | 'iendswith': 'LIKE UPPER(%s)',
736 | }
737 |
738 | def get_connection_params(self):
739 | return {}
740 |
741 | def get_new_connection(self, conn_params):
742 | return FakeConnection()
743 |
744 | def init_connection_state(self):
745 | pass
746 |
747 | def _set_autocommit(self, autocommit):
748 | pass
749 |
750 | def _cursor(self):
751 | return FakeCursor()
752 |
--------------------------------------------------------------------------------
/djangotoolbox/tests.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 | from decimal import Decimal, InvalidOperation
3 | import time
4 |
5 | from django.core import serializers
6 | from django.db import models
7 | from django.db.models import Q
8 | from django.db.models.signals import post_save
9 | from django.db.utils import DatabaseError
10 | from django.dispatch.dispatcher import receiver
11 | from django.test import TestCase
12 | from django.utils.unittest import expectedFailure, skip
13 |
14 | from .fields import ListField, SetField, DictField, EmbeddedModelField
15 |
16 |
17 | def count_calls(func):
18 |
19 | def wrapper(*args, **kwargs):
20 | wrapper.calls += 1
21 | return func(*args, **kwargs)
22 | wrapper.calls = 0
23 |
24 | return wrapper
25 |
26 |
27 | class Target(models.Model):
28 | index = models.IntegerField()
29 |
30 |
31 | class Source(models.Model):
32 | target = models.ForeignKey(Target)
33 | index = models.IntegerField()
34 |
35 |
36 | class DecimalModel(models.Model):
37 | decimal = models.DecimalField(max_digits=9, decimal_places=2)
38 |
39 |
40 | class DecimalKey(models.Model):
41 | decimal = models.DecimalField(max_digits=9, decimal_places=2, primary_key=True)
42 |
43 |
44 | class DecimalParent(models.Model):
45 | child = models.ForeignKey(DecimalKey)
46 |
47 |
48 | class DecimalsList(models.Model):
49 | decimals = ListField(models.ForeignKey(DecimalKey))
50 |
51 |
52 | class ListModel(models.Model):
53 | integer = models.IntegerField(primary_key=True)
54 | floating_point = models.FloatField()
55 | names = ListField(models.CharField)
56 | names_with_default = ListField(models.CharField(max_length=500),
57 | default=[])
58 | names_nullable = ListField(models.CharField(max_length=500), null=True)
59 |
60 |
61 | class OrderedListModel(models.Model):
62 | ordered_ints = ListField(models.IntegerField(max_length=500), default=[],
63 | ordering=count_calls(lambda x: x), null=True)
64 | ordered_nullable = ListField(ordering=lambda x: x, null=True)
65 |
66 |
67 | class SetModel(models.Model):
68 | setfield = SetField(models.IntegerField())
69 |
70 |
71 | class DictModel(models.Model):
72 | dictfield = DictField(models.IntegerField)
73 | dictfield_nullable = DictField(null=True)
74 | auto_now = DictField(models.DateTimeField(auto_now=True))
75 |
76 |
77 | class EmbeddedModelFieldModel(models.Model):
78 | simple = EmbeddedModelField('EmbeddedModel', null=True)
79 | simple_untyped = EmbeddedModelField(null=True)
80 | decimal_parent = EmbeddedModelField(DecimalParent, null=True)
81 | typed_list = ListField(EmbeddedModelField('SetModel'))
82 | typed_list2 = ListField(EmbeddedModelField('EmbeddedModel'))
83 | untyped_list = ListField(EmbeddedModelField())
84 | untyped_dict = DictField(EmbeddedModelField())
85 | ordered_list = ListField(EmbeddedModelField(),
86 | ordering=lambda obj: obj.index)
87 |
88 |
89 | class EmbeddedModel(models.Model):
90 | some_relation = models.ForeignKey(DictModel, null=True)
91 | someint = models.IntegerField(db_column='custom')
92 | auto_now = models.DateTimeField(auto_now=True)
93 | auto_now_add = models.DateTimeField(auto_now_add=True)
94 |
95 |
96 | class IterableFieldsTest(TestCase):
97 | floats = [5.3, 2.6, 9.1, 1.58]
98 | names = [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']
99 | unordered_ints = [4, 2, 6, 1]
100 |
101 | def setUp(self):
102 | for i, float in zip(range(1, 5), IterableFieldsTest.floats):
103 | ListModel(integer=i, floating_point=float,
104 | names=IterableFieldsTest.names[:i]).save()
105 |
106 | def test_startswith(self):
107 | self.assertEquals(
108 | dict([(entity.pk, entity.names) for entity in
109 | ListModel.objects.filter(names__startswith='Sa')]),
110 | dict([(3, ['Kakashi', 'Naruto', 'Sasuke']),
111 | (4, ['Kakashi', 'Naruto', 'Sasuke', 'Sakura']), ]))
112 |
113 | def test_options(self):
114 | self.assertEqual([entity.names_with_default for entity in
115 | ListModel.objects.filter(names__startswith='Sa')],
116 | [[], []])
117 |
118 | self.assertEqual([entity.names_nullable for entity in
119 | ListModel.objects.filter(names__startswith='Sa')],
120 | [None, None])
121 |
122 | def test_default_value(self):
123 | # Make sure default value is copied.
124 | ListModel().names_with_default.append(2)
125 | self.assertEqual(ListModel().names_with_default, [])
126 |
127 | def test_ordering(self):
128 | f = OrderedListModel._meta.fields[1]
129 | f.ordering.calls = 0
130 |
131 | # Ensure no ordering happens on assignment.
132 | obj = OrderedListModel()
133 | obj.ordered_ints = self.unordered_ints
134 | self.assertEqual(f.ordering.calls, 0)
135 |
136 | obj.save()
137 | self.assertEqual(OrderedListModel.objects.get().ordered_ints,
138 | sorted(self.unordered_ints))
139 | # Ordering should happen only once, i.e. the order function may
140 | # be called N times at most (N being the number of items in the
141 | # list).
142 | self.assertLessEqual(f.ordering.calls, len(self.unordered_ints))
143 |
144 | def test_gt(self):
145 | self.assertEquals(
146 | dict([(entity.pk, entity.names) for entity in
147 | ListModel.objects.filter(names__gt='Kakashi')]),
148 | dict([(2, [u'Kakashi', u'Naruto']),
149 | (3, [u'Kakashi', u'Naruto', u'Sasuke']),
150 | (4, [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']), ]))
151 |
152 | def test_lt(self):
153 | self.assertEquals(
154 | dict([(entity.pk, entity.names) for entity in
155 | ListModel.objects.filter(names__lt='Naruto')]),
156 | dict([(1, [u'Kakashi']),
157 | (2, [u'Kakashi', u'Naruto']),
158 | (3, [u'Kakashi', u'Naruto', u'Sasuke']),
159 | (4, [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']), ]))
160 |
161 | def test_gte(self):
162 | self.assertEquals(
163 | dict([(entity.pk, entity.names) for entity in
164 | ListModel.objects.filter(names__gte='Sakura')]),
165 | dict([(3, [u'Kakashi', u'Naruto', u'Sasuke']),
166 | (4, [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']), ]))
167 |
168 | def test_lte(self):
169 | self.assertEquals(
170 | dict([(entity.pk, entity.names) for entity in
171 | ListModel.objects.filter(names__lte='Kakashi')]),
172 | dict([(1, [u'Kakashi']),
173 | (2, [u'Kakashi', u'Naruto']),
174 | (3, [u'Kakashi', u'Naruto', u'Sasuke']),
175 | (4, [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']), ]))
176 |
177 | def test_equals(self):
178 | self.assertEquals([entity.names for entity in
179 | ListModel.objects.filter(names='Sakura')],
180 | [[u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']])
181 |
182 | # Test with additonal pk filter (for DBs that have special pk
183 | # queries).
184 | query = ListModel.objects.filter(names='Sakura')
185 | self.assertEquals(query.get(pk=query[0].pk).names,
186 | [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura'])
187 |
188 | def test_is_null(self):
189 | self.assertEquals(ListModel.objects.filter(
190 | names__isnull=True).count(), 0)
191 |
192 | def test_exclude(self):
193 | self.assertEquals(
194 | dict([(entity.pk, entity.names) for entity in
195 | ListModel.objects.all().exclude(names__lt='Sakura')]),
196 | dict([(3, [u'Kakashi', u'Naruto', u'Sasuke']),
197 | (4, [u'Kakashi', u'Naruto', u'Sasuke', u'Sakura']), ]))
198 |
199 | def test_chained_filter(self):
200 | self.assertEquals(
201 | [entity.names for entity in ListModel.objects
202 | .filter(names='Sasuke').filter(names='Sakura')],
203 | [['Kakashi', 'Naruto', 'Sasuke', 'Sakura'], ])
204 |
205 | self.assertEquals(
206 | [entity.names for entity in ListModel.objects
207 | .filter(names__startswith='Sa').filter(names='Sakura')],
208 | [['Kakashi', 'Naruto', 'Sasuke', 'Sakura']])
209 |
210 | # Test across multiple columns. On app engine only one filter
211 | # is allowed to be an inequality filter.
212 | self.assertEquals(
213 | [entity.names for entity in ListModel.objects
214 | .filter(floating_point=9.1).filter(names__startswith='Sa')],
215 | [['Kakashi', 'Naruto', 'Sasuke'], ])
216 |
217 | def test_setfield(self):
218 | setdata = [1, 2, 3, 2, 1]
219 | # At the same time test value conversion.
220 | SetModel(setfield=map(str, setdata)).save()
221 | item = SetModel.objects.filter(setfield=3)[0]
222 | self.assertEqual(item.setfield, set(setdata))
223 | # This shouldn't raise an error because the default value is
224 | # an empty list.
225 | SetModel().save()
226 |
227 | def test_dictfield(self):
228 | DictModel(dictfield=dict(a=1, b='55', foo=3.14),
229 | auto_now={'a': None}).save()
230 | item = DictModel.objects.get()
231 | self.assertEqual(item.dictfield, {u'a': 1, u'b': 55, u'foo': 3})
232 |
233 | dt = item.auto_now['a']
234 | self.assertNotEqual(dt, None)
235 | item.save()
236 | time.sleep(0.5) # Sleep to avoid false positive failure on the assertion below
237 | self.assertGreater(DictModel.objects.get().auto_now['a'], dt)
238 | item.delete()
239 |
240 | # Saving empty dicts shouldn't throw errors.
241 | DictModel().save()
242 | # Regression tests for djangoappengine issue #39.
243 | DictModel.add_to_class('new_dict_field', DictField())
244 | DictModel.objects.get()
245 |
246 | @skip("GAE specific?")
247 | def test_Q_objects(self):
248 | self.assertEquals(
249 | [entity.names for entity in ListModel.objects
250 | .exclude(Q(names__lt='Sakura') | Q(names__gte='Sasuke'))],
251 | [['Kakashi', 'Naruto', 'Sasuke', 'Sakura']])
252 |
253 | def test_list_with_foreignkeys(self):
254 |
255 | class ReferenceList(models.Model):
256 | keys = ListField(models.ForeignKey('Model'))
257 |
258 | class Model(models.Model):
259 | pass
260 |
261 | model1 = Model.objects.create()
262 | model2 = Model.objects.create()
263 | ReferenceList.objects.create(keys=[model1.pk, model2.pk])
264 |
265 | self.assertEqual(ReferenceList.objects.get().keys[0], model1.pk)
266 | self.assertEqual(ReferenceList.objects.filter(keys=model1.pk).count(), 1)
267 |
268 | def test_list_with_foreign_conversion(self):
269 | decimal = DecimalKey.objects.create(decimal=Decimal('1.5'))
270 | DecimalsList.objects.create(decimals=[decimal.pk])
271 |
272 | @expectedFailure
273 | def test_nested_list(self):
274 | """
275 | Some back-ends expect lists to be strongly typed or not contain
276 | other lists (e.g. GAE), this limits how the ListField can be
277 | used (unless the back-end were to serialize all lists).
278 | """
279 |
280 | class UntypedListModel(models.Model):
281 | untyped_list = ListField()
282 |
283 | UntypedListModel.objects.create(untyped_list=[1, [2, 3]])
284 |
285 |
286 | class Child(models.Model):
287 | pass
288 |
289 |
290 | class Parent(models.Model):
291 | id = models.IntegerField(primary_key=True)
292 | integer_list = ListField(models.IntegerField)
293 | integer_dict = DictField(models.IntegerField)
294 | embedded_list = ListField(EmbeddedModelField(Child))
295 | embedded_dict = DictField(EmbeddedModelField(Child))
296 |
297 |
298 | class EmbeddedModelFieldTest(TestCase):
299 |
300 | def assertEqualDatetime(self, d1, d2):
301 | """Compares d1 and d2, ignoring microseconds."""
302 | self.assertEqual(d1.replace(microsecond=0),
303 | d2.replace(microsecond=0))
304 |
305 | def assertNotEqualDatetime(self, d1, d2):
306 | self.assertNotEqual(d1.replace(microsecond=0),
307 | d2.replace(microsecond=0))
308 |
309 | def _simple_instance(self):
310 | EmbeddedModelFieldModel.objects.create(
311 | simple=EmbeddedModel(someint='5'))
312 | return EmbeddedModelFieldModel.objects.get()
313 |
314 | def test_simple(self):
315 | instance = self._simple_instance()
316 | self.assertIsInstance(instance.simple, EmbeddedModel)
317 | # Make sure get_prep_value is called.
318 | self.assertEqual(instance.simple.someint, 5)
319 | # Primary keys should not be populated...
320 | self.assertEqual(instance.simple.id, None)
321 | # ... unless set explicitly.
322 | instance.simple.id = instance.id
323 | instance.save()
324 | instance = EmbeddedModelFieldModel.objects.get()
325 | self.assertEqual(instance.simple.id, instance.id)
326 |
327 | def _test_pre_save(self, instance, get_field):
328 | # Make sure field.pre_save is called for embedded objects.
329 | from time import sleep
330 | instance.save()
331 | auto_now = get_field(instance).auto_now
332 | auto_now_add = get_field(instance).auto_now_add
333 | self.assertNotEqual(auto_now, None)
334 | self.assertNotEqual(auto_now_add, None)
335 |
336 | sleep(1) # FIXME
337 | instance.save()
338 | self.assertNotEqualDatetime(get_field(instance).auto_now,
339 | get_field(instance).auto_now_add)
340 |
341 | instance = EmbeddedModelFieldModel.objects.get()
342 | instance.save()
343 | # auto_now_add shouldn't have changed now, but auto_now should.
344 | self.assertEqualDatetime(get_field(instance).auto_now_add,
345 | auto_now_add)
346 | self.assertGreater(get_field(instance).auto_now, auto_now)
347 |
348 | def test_pre_save(self):
349 | obj = EmbeddedModelFieldModel(simple=EmbeddedModel())
350 | self._test_pre_save(obj, lambda instance: instance.simple)
351 |
352 | def test_pre_save_untyped(self):
353 | obj = EmbeddedModelFieldModel(simple_untyped=EmbeddedModel())
354 | self._test_pre_save(obj, lambda instance: instance.simple_untyped)
355 |
356 | def test_pre_save_in_list(self):
357 | obj = EmbeddedModelFieldModel(untyped_list=[EmbeddedModel()])
358 | self._test_pre_save(obj, lambda instance: instance.untyped_list[0])
359 |
360 | def test_pre_save_in_dict(self):
361 | obj = EmbeddedModelFieldModel(untyped_dict={'a': EmbeddedModel()})
362 | self._test_pre_save(obj, lambda instance: instance.untyped_dict['a'])
363 |
364 | def test_pre_save_list(self):
365 | # Also make sure auto_now{,add} works for embedded object *lists*.
366 | EmbeddedModelFieldModel.objects.create(typed_list2=[EmbeddedModel()])
367 | instance = EmbeddedModelFieldModel.objects.get()
368 |
369 | auto_now = instance.typed_list2[0].auto_now
370 | auto_now_add = instance.typed_list2[0].auto_now_add
371 | self.assertNotEqual(auto_now, None)
372 | self.assertNotEqual(auto_now_add, None)
373 |
374 | instance.typed_list2.append(EmbeddedModel())
375 | instance.save()
376 | instance = EmbeddedModelFieldModel.objects.get()
377 |
378 | self.assertEqualDatetime(instance.typed_list2[0].auto_now_add,
379 | auto_now_add)
380 | self.assertGreater(instance.typed_list2[0].auto_now, auto_now)
381 | self.assertNotEqual(instance.typed_list2[1].auto_now, None)
382 | self.assertNotEqual(instance.typed_list2[1].auto_now_add, None)
383 |
384 | def test_error_messages(self):
385 | for kwargs, expected in (
386 | ({'simple': 42}, EmbeddedModel),
387 | ({'simple_untyped': 42}, models.Model),
388 | ({'typed_list': [EmbeddedModel()]}, SetModel)):
389 | self.assertRaisesRegexp(
390 | TypeError, "Expected instance of type %r." % expected,
391 | EmbeddedModelFieldModel(**kwargs).save)
392 |
393 | def test_typed_listfield(self):
394 | EmbeddedModelFieldModel.objects.create(
395 | typed_list=[SetModel(setfield=range(3)),
396 | SetModel(setfield=range(9))],
397 | ordered_list=[Target(index=i) for i in xrange(5, 0, -1)])
398 | obj = EmbeddedModelFieldModel.objects.get()
399 | self.assertIn(5, obj.typed_list[1].setfield)
400 | self.assertEqual([target.index for target in obj.ordered_list],
401 | range(1, 6))
402 |
403 | def test_untyped_listfield(self):
404 | EmbeddedModelFieldModel.objects.create(untyped_list=[
405 | EmbeddedModel(someint=7),
406 | OrderedListModel(ordered_ints=range(5, 0, -1)),
407 | SetModel(setfield=[1, 2, 2, 3])])
408 | instances = EmbeddedModelFieldModel.objects.get().untyped_list
409 | for instance, cls in zip(instances,
410 | [EmbeddedModel, OrderedListModel, SetModel]):
411 | self.assertIsInstance(instance, cls)
412 | self.assertNotEqual(instances[0].auto_now, None)
413 | self.assertEqual(instances[1].ordered_ints, range(1, 6))
414 |
415 | def test_untyped_dict(self):
416 | EmbeddedModelFieldModel.objects.create(untyped_dict={
417 | 'a': SetModel(setfield=range(3)),
418 | 'b': DictModel(dictfield={'a': 1, 'b': 2}),
419 | 'c': DictModel(dictfield={}, auto_now={'y': 1})})
420 | data = EmbeddedModelFieldModel.objects.get().untyped_dict
421 | self.assertIsInstance(data['a'], SetModel)
422 | self.assertNotEqual(data['c'].auto_now['y'], None)
423 |
424 | def test_foreignkey_in_embedded_object(self):
425 | simple = EmbeddedModel(some_relation=DictModel.objects.create())
426 | obj = EmbeddedModelFieldModel.objects.create(simple=simple)
427 | simple = EmbeddedModelFieldModel.objects.get().simple
428 | self.assertNotIn('some_relation', simple.__dict__)
429 | self.assertIsInstance(simple.__dict__['some_relation_id'],
430 | type(obj.id))
431 | self.assertIsInstance(simple.some_relation, DictModel)
432 |
433 | def test_embedded_field_with_foreign_conversion(self):
434 | decimal = DecimalKey.objects.create(decimal=Decimal('1.5'))
435 | decimal_parent = DecimalParent.objects.create(child=decimal)
436 | EmbeddedModelFieldModel.objects.create(decimal_parent=decimal_parent)
437 |
438 | def test_update(self):
439 | """
440 | Test that update can be used on an a subset of objects
441 | containing collections of embedded instances; see issue #13.
442 | Also ensure that updated values are coerced according to
443 | collection field.
444 | """
445 | child1 = Child.objects.create()
446 | child2 = Child.objects.create()
447 | parent = Parent.objects.create(pk=1,
448 | integer_list=[1], integer_dict={'a': 2},
449 | embedded_list=[child1], embedded_dict={'a': child2})
450 | Parent.objects.filter(pk=1).update(
451 | integer_list=['3'], integer_dict={'b': '3'},
452 | embedded_list=[child2], embedded_dict={'b': child1})
453 | parent = Parent.objects.get()
454 | self.assertEqual(parent.integer_list, [3])
455 | self.assertEqual(parent.integer_dict, {'b': 3})
456 | self.assertEqual(parent.embedded_list, [child2])
457 | self.assertEqual(parent.embedded_dict, {'b': child1})
458 |
459 |
460 | class BaseModel(models.Model):
461 | pass
462 |
463 |
464 | class ExtendedModel(BaseModel):
465 | name = models.CharField(max_length=20)
466 |
467 |
468 | class BaseModelProxy(BaseModel):
469 |
470 | class Meta:
471 | proxy = True
472 |
473 |
474 | class ExtendedModelProxy(ExtendedModel):
475 |
476 | class Meta:
477 | proxy = True
478 |
479 |
480 | class ProxyTest(TestCase):
481 |
482 | def test_proxy(self):
483 | list(BaseModelProxy.objects.all())
484 |
485 | def test_proxy_with_inheritance(self):
486 | self.assertRaises(DatabaseError,
487 | lambda: list(ExtendedModelProxy.objects.all()))
488 |
489 |
490 | class SignalTest(TestCase):
491 |
492 | def test_post_save(self):
493 | created = []
494 |
495 | @receiver(post_save, sender=SetModel)
496 | def handle(**kwargs):
497 | created.append(kwargs['created'])
498 |
499 | SetModel().save()
500 | self.assertEqual(created, [True])
501 | SetModel.objects.get().save()
502 | self.assertEqual(created, [True, False])
503 | qs = SetModel.objects.all()
504 | list(qs)[0].save()
505 | self.assertEqual(created, [True, False, False])
506 | list(qs)[0].save()
507 | self.assertEqual(created, [True, False, False, False])
508 | list(qs.select_related())[0].save()
509 | self.assertEqual(created, [True, False, False, False, False])
510 |
511 |
512 | class SelectRelatedTest(TestCase):
513 |
514 | def test_select_related(self):
515 | target = Target(index=5)
516 | target.save()
517 | Source(target=target, index=8).save()
518 | source = Source.objects.all().select_related()[0]
519 | self.assertEqual(source.target.pk, target.pk)
520 | self.assertEqual(source.target.index, target.index)
521 | source = Source.objects.all().select_related('target')[0]
522 | self.assertEqual(source.target.pk, target.pk)
523 | self.assertEqual(source.target.index, target.index)
524 |
525 |
526 | class DBColumn(models.Model):
527 | a = models.IntegerField(db_column='b')
528 |
529 |
530 | class OrderByTest(TestCase):
531 |
532 | def test_foreign_keys(self):
533 | target1 = Target.objects.create(index=1)
534 | target2 = Target.objects.create(index=2)
535 | source1 = Source.objects.create(target=target1, index=3)
536 | source2 = Source.objects.create(target=target2, index=4)
537 | self.assertEqual(list(Source.objects.all().order_by('target')),
538 | [source1, source2])
539 | self.assertEqual(list(Source.objects.all().order_by('-target')),
540 | [source2, source1])
541 |
542 | def test_db_column(self):
543 | model1 = DBColumn.objects.create(a=1)
544 | model2 = DBColumn.objects.create(a=2)
545 | self.assertEqual(list(DBColumn.objects.all().order_by('a')),
546 | [model1, model2])
547 | self.assertEqual(list(DBColumn.objects.all().order_by('-a')),
548 | [model2, model1])
549 |
550 | def test_reverse(self):
551 | model1 = DBColumn.objects.create(a=1)
552 | model2 = DBColumn.objects.create(a=2)
553 | self.assertEqual(list(DBColumn.objects.all().order_by('a').reverse()),
554 | [model2, model1])
555 | self.assertEqual(list(DBColumn.objects.all().order_by('-a').reverse()),
556 | [model1, model2])
557 |
558 | def test_chain(self):
559 | model1 = Target.objects.create(index=1)
560 | model2 = Target.objects.create(index=2)
561 | self.assertEqual(
562 | list(Target.objects.all().order_by('index').order_by('-index')),
563 | [model2, model1])
564 |
565 |
566 | class SerializableSetModel(models.Model):
567 | setfield = SetField(models.IntegerField())
568 | setcharfield = SetField(models.CharField(), null=True)
569 |
570 |
571 | class SerializationTest(TestCase):
572 | """
573 | JSON doesn't support sets, so they need to be converted to lists
574 | for serialization; see issue #12.
575 |
576 | TODO: Check if the fix works with embedded models / nested sets.
577 | """
578 | names = ['foo', 'bar', 'baz', 'monkey']
579 |
580 | def test_json_listfield(self):
581 | for i in range(1, 5):
582 | ListModel(integer=i, floating_point=0,
583 | names=SerializationTest.names[:i]).save()
584 | objects = ListModel.objects.all()
585 | serialized = serializers.serialize('json', objects)
586 | deserialized = serializers.deserialize('json', serialized)
587 | for m in deserialized:
588 | integer = m.object.integer
589 | names = m.object.names
590 | self.assertEqual(names, SerializationTest.names[:integer])
591 |
592 | def test_json_setfield(self):
593 | for i in range(1, 5):
594 | SerializableSetModel(
595 | setfield=set([i - 1]),
596 | setcharfield=set(SerializationTest.names[:i])).save()
597 | objects = SerializableSetModel.objects.all()
598 | serialized = serializers.serialize('json', objects)
599 | deserialized = serializers.deserialize('json', serialized)
600 | for m in deserialized:
601 | integer = m.object.setfield.pop()
602 | names = m.object.setcharfield
603 | self.assertEqual(names, set(SerializationTest.names[:integer + 1]))
604 |
605 |
606 | class String(models.Model):
607 | s = models.CharField(max_length=20)
608 |
609 |
610 | class LazyObjectsTest(TestCase):
611 |
612 | def test_translation(self):
613 | """
614 | Using a lazy translation call should work just the same as
615 | a non-lazy one (or a plain string).
616 | """
617 | from django.utils.translation import ugettext_lazy
618 |
619 | a = String.objects.create(s='a')
620 | b = String.objects.create(s=ugettext_lazy('b'))
621 |
622 | self.assertEqual(String.objects.get(s='a'), a)
623 | self.assertEqual(list(String.objects.filter(s='a')), [a])
624 | self.assertEqual(list(String.objects.filter(s__lte='a')), [a])
625 | self.assertEqual(String.objects.get(s=ugettext_lazy('a')), a)
626 | self.assertEqual(
627 | list(String.objects.filter(s__lte=ugettext_lazy('a'))), [a])
628 |
629 | self.assertEqual(String.objects.get(s='b'), b)
630 | self.assertEqual(list(String.objects.filter(s='b')), [b])
631 | self.assertEqual(list(String.objects.filter(s__gte='b')), [b])
632 | self.assertEqual(String.objects.get(s=ugettext_lazy('b')), b)
633 | self.assertEqual(
634 | list(String.objects.filter(s__gte=ugettext_lazy('b'))), [b])
635 |
636 | def test_marked_strings(self):
637 | """
638 | Check that strings marked as safe or needing escaping do not
639 | confuse the back-end.
640 | """
641 | from django.utils.safestring import mark_safe, mark_for_escaping
642 |
643 | a = String.objects.create(s='a')
644 | b = String.objects.create(s=mark_safe('b'))
645 | c = String.objects.create(s=mark_for_escaping('c'))
646 |
647 | self.assertEqual(String.objects.get(s='a'), a)
648 | self.assertEqual(list(String.objects.filter(s__startswith='a')), [a])
649 | self.assertEqual(String.objects.get(s=mark_safe('a')), a)
650 | self.assertEqual(
651 | list(String.objects.filter(s__startswith=mark_safe('a'))), [a])
652 | self.assertEqual(String.objects.get(s=mark_for_escaping('a')), a)
653 | self.assertEqual(
654 | list(String.objects.filter(s__startswith=mark_for_escaping('a'))),
655 | [a])
656 |
657 | self.assertEqual(String.objects.get(s='b'), b)
658 | self.assertEqual(list(String.objects.filter(s__startswith='b')), [b])
659 | self.assertEqual(String.objects.get(s=mark_safe('b')), b)
660 | self.assertEqual(
661 | list(String.objects.filter(s__startswith=mark_safe('b'))), [b])
662 | self.assertEqual(String.objects.get(s=mark_for_escaping('b')), b)
663 | self.assertEqual(
664 | list(String.objects.filter(s__startswith=mark_for_escaping('b'))),
665 | [b])
666 |
667 | self.assertEqual(String.objects.get(s='c'), c)
668 | self.assertEqual(list(String.objects.filter(s__startswith='c')), [c])
669 | self.assertEqual(String.objects.get(s=mark_safe('c')), c)
670 | self.assertEqual(
671 | list(String.objects.filter(s__startswith=mark_safe('c'))), [c])
672 | self.assertEqual(String.objects.get(s=mark_for_escaping('c')), c)
673 | self.assertEqual(
674 | list(String.objects.filter(s__startswith=mark_for_escaping('c'))),
675 | [c])
676 |
677 |
678 | class FeaturesTest(TestCase):
679 | """
680 | Some things are unlikely to cause problems for SQL back-ends, but
681 | require special handling in nonrel.
682 | """
683 |
684 | def test_subqueries(self):
685 | """
686 | Django includes SQL statements as WHERE tree values when
687 | filtering using a QuerySet -- this won't "just work" with
688 | nonrel back-ends.
689 |
690 | TODO: Subqueries handling may require a bit of Django
691 | changing, but should be easy to support.
692 | """
693 | target = Target.objects.create(index=1)
694 | source = Source.objects.create(index=2, target=target)
695 | targets = Target.objects.all()
696 | with self.assertRaises(DatabaseError):
697 | Source.objects.get(target__in=targets)
698 | self.assertEqual(
699 | Source.objects.get(target__in=list(targets)),
700 | source)
701 |
702 |
703 | class DecimalFieldTest(TestCase):
704 | """
705 | Some NoSQL databases can't handle Decimals, so respective back-ends
706 | convert them to strings or floats. This can cause some precision
707 | and sorting problems.
708 | """
709 |
710 | def setUp(self):
711 | for d in (Decimal('12345.6789'), Decimal('5'), Decimal('345.67'),
712 | Decimal('45.6'), Decimal('2345.678'),):
713 | DecimalModel(decimal=d).save()
714 |
715 | def test_filter(self):
716 | d = DecimalModel.objects.get(decimal=Decimal('5.0'))
717 |
718 | self.assertTrue(isinstance(d.decimal, Decimal))
719 | self.assertEquals(str(d.decimal), '5.00')
720 |
721 | d = DecimalModel.objects.get(decimal=Decimal('45.60'))
722 | self.assertEquals(str(d.decimal), '45.60')
723 |
724 | # Filter argument should be converted to Decimal with 2 decimal
725 | #_places.
726 | d = DecimalModel.objects.get(decimal='0000345.67333333333333333')
727 | self.assertEquals(str(d.decimal), '345.67')
728 |
729 | def test_order(self):
730 | """
731 | Standard Django decimal-to-string conversion isn't monotonic
732 | (see `django.db.backends.util.format_number`).
733 | """
734 | rows = DecimalModel.objects.all().order_by('decimal')
735 | values = list(d.decimal for d in rows)
736 | self.assertEquals(values, sorted(values))
737 |
738 | def test_sign_extend(self):
739 | DecimalModel(decimal=Decimal('-0.0')).save()
740 |
741 | try:
742 | # If we've written a valid string we should be able to
743 | # retrieve the DecimalModel object without error.
744 | DecimalModel.objects.filter(decimal__lt=1)[0]
745 | except InvalidOperation:
746 | self.assertTrue(False)
747 |
748 |
749 | class DeleteModel(models.Model):
750 | key = models.IntegerField(primary_key=True)
751 | deletable = models.BooleanField()
752 |
753 | class BasicDeleteTest(TestCase):
754 |
755 | def setUp(self):
756 | for i in range(1, 10):
757 | DeleteModel(key=i, deletable=i % 2 == 0).save()
758 |
759 | def test_model_delete(self):
760 | d = DeleteModel.objects.get(pk=1)
761 | d.delete()
762 |
763 | with self.assertRaises(DeleteModel.DoesNotExist):
764 | DeleteModel.objects.get(pk=1)
765 |
766 | def test_delete_all(self):
767 | DeleteModel.objects.all().delete()
768 |
769 | self.assertEquals(0, DeleteModel.objects.all().count())
770 |
771 | def test_delete_filtered(self):
772 | DeleteModel.objects.filter(deletable=True).delete()
773 |
774 | self.assertEquals(5, DeleteModel.objects.all().count())
775 |
776 |
777 | class M2MDeleteChildModel(models.Model):
778 | key = models.IntegerField(primary_key=True)
779 |
780 | class M2MDeleteModel(models.Model):
781 | key = models.IntegerField(primary_key=True)
782 | deletable = models.BooleanField()
783 | children = models.ManyToManyField(M2MDeleteChildModel, blank=True)
784 |
785 | class ManyToManyDeleteTest(TestCase):
786 | """
787 | Django-nonrel doesn't support many-to-many, but there may be
788 | models that are used which contain them, even if they're not
789 | accessed. This test ensures they can be deleted.
790 | """
791 |
792 | def setUp(self):
793 | for i in range(1, 10):
794 | M2MDeleteModel(key=i, deletable=i % 2 == 0).save()
795 |
796 | def test_model_delete(self):
797 | d = M2MDeleteModel.objects.get(pk=1)
798 | d.delete()
799 |
800 | with self.assertRaises(M2MDeleteModel.DoesNotExist):
801 | M2MDeleteModel.objects.get(pk=1)
802 |
803 | @expectedFailure
804 | def test_delete_all(self):
805 | M2MDeleteModel.objects.all().delete()
806 |
807 | self.assertEquals(0, M2MDeleteModel.objects.all().count())
808 |
809 | @expectedFailure
810 | def test_delete_filtered(self):
811 | M2MDeleteModel.objects.filter(deletable=True).delete()
812 |
813 | self.assertEquals(5, M2MDeleteModel.objects.all().count())
814 |
815 |
816 | class QuerysetModel(models.Model):
817 | key = models.IntegerField(primary_key=True)
818 |
819 | class QuerysetTest(TestCase):
820 | """
821 | Django 1.6 changes how
822 | """
823 |
824 | def setUp(self):
825 | for i in range(10):
826 | QuerysetModel.objects.create(key=i + 1)
827 |
828 | def test_all(self):
829 | self.assertEqual(10, len(QuerysetModel.objects.all()))
830 |
831 | def test_none(self):
832 | self.assertEqual(0, len(QuerysetModel.objects.none()))
833 |
--------------------------------------------------------------------------------