diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2013-06-28 17:32:57 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2013-06-28 17:32:57 +0100 |
| commit | 7a47ba6f6aeca36b8b092dbafd36abb342d34d4b (patch) | |
| tree | e959922f7d4d08057225459e31697f410e26df26 /django/db | |
| parent | e2d7e83256234251a81ad3388428f6579795a672 (diff) | |
| parent | 48dd1e63bbb93479666208535a56f8c7c4aeab3a (diff) | |
Merge remote-tracking branch 'core/master' into schema-alteration
Conflicts:
django/db/backends/__init__.py
django/db/models/fields/related.py
tests/field_deconstruction/tests.py
Diffstat (limited to 'django/db')
| -rw-r--r-- | django/db/backends/__init__.py | 14 | ||||
| -rw-r--r-- | django/db/backends/oracle/base.py | 66 | ||||
| -rw-r--r-- | django/db/backends/postgresql_psycopg2/base.py | 2 | ||||
| -rw-r--r-- | django/db/backends/postgresql_psycopg2/creation.py | 2 | ||||
| -rw-r--r-- | django/db/backends/postgresql_psycopg2/operations.py | 2 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/base.py | 7 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 22 | ||||
| -rw-r--r-- | django/db/models/options.py | 24 | ||||
| -rw-r--r-- | django/db/models/query.py | 5 | ||||
| -rw-r--r-- | django/db/transaction.py | 20 |
10 files changed, 125 insertions, 39 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 2fcf6b9604..26ca9a7fa1 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -330,6 +330,15 @@ class BaseDatabaseWrapper(object): self._set_autocommit(autocommit) self.autocommit = autocommit + def set_rollback(self, rollback): + """ + Set or unset the "needs rollback" flag -- for *advanced use* only. + """ + if not self.in_atomic_block: + raise TransactionManagementError( + "needs_rollback doesn't work outside of an 'atomic' block.") + self.needs_rollback = rollback + def validate_no_atomic_block(self): """ Raise an error if an atomic block is active. @@ -623,6 +632,11 @@ class BaseDatabaseFeatures(object): # Does it support CHECK constraints? supports_check_constraints = True + # Does the backend support 'pyformat' style ("... %(name)s ...", {'name': value}) + # parameter passing? Note this can be provided by the backend even if not + # supported by the Python driver + supports_paramstyle_pyformat = True + def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 3105ad17e3..5fb806807d 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -762,20 +762,37 @@ class FormatStylePlaceholderCursor(object): self.cursor.arraysize = 100 def _format_params(self, params): - return tuple([OracleParam(p, self, True) for p in params]) + try: + return dict((k,OracleParam(v, self, True)) for k,v in params.items()) + except AttributeError: + return tuple([OracleParam(p, self, True) for p in params]) def _guess_input_sizes(self, params_list): - sizes = [None] * len(params_list[0]) - for params in params_list: - for i, value in enumerate(params): - if value.input_size: - sizes[i] = value.input_size - self.setinputsizes(*sizes) + # Try dict handling; if that fails, treat as sequence + if hasattr(params_list[0], 'keys'): + sizes = {} + for params in params_list: + for k, value in params.items(): + if value.input_size: + sizes[k] = value.input_size + self.setinputsizes(**sizes) + else: + # It's not a list of dicts; it's a list of sequences + sizes = [None] * len(params_list[0]) + for params in params_list: + for i, value in enumerate(params): + if value.input_size: + sizes[i] = value.input_size + self.setinputsizes(*sizes) def _param_generator(self, params): - return [p.force_bytes for p in params] + # Try dict handling; if that fails, treat as sequence + if hasattr(params, 'items'): + return dict((k, v.force_bytes) for k,v in params.items()) + else: + return [p.force_bytes for p in params] - def execute(self, query, params=None): + def _fix_for_params(self, query, params): # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it # it does want a trailing ';' but not a trailing '/'. However, these # characters must be included in the original query in case the query @@ -785,10 +802,18 @@ class FormatStylePlaceholderCursor(object): if params is None: params = [] query = convert_unicode(query, self.charset) + elif hasattr(params, 'keys'): + # Handle params as dict + args = dict((k, ":%s"%k) for k in params.keys()) + query = convert_unicode(query % args, self.charset) else: - params = self._format_params(params) + # Handle params as sequence args = [(':arg%d' % i) for i in range(len(params))] query = convert_unicode(query % tuple(args), self.charset) + return query, self._format_params(params) + + def execute(self, query, params=None): + query, params = self._fix_for_params(query, params) self._guess_input_sizes([params]) try: return self.cursor.execute(query, self._param_generator(params)) @@ -799,22 +824,15 @@ class FormatStylePlaceholderCursor(object): raise def executemany(self, query, params=None): - # cx_Oracle doesn't support iterators, convert them to lists - if params is not None and not isinstance(params, (list, tuple)): - params = list(params) - try: - args = [(':arg%d' % i) for i in range(len(params[0]))] - except (IndexError, TypeError): + if not params: # No params given, nothing to do return None - # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it - # it does want a trailing ';' but not a trailing '/'. However, these - # characters must be included in the original query in case the query - # is being passed to SQL*Plus. - if query.endswith(';') or query.endswith('/'): - query = query[:-1] - query = convert_unicode(query % tuple(args), self.charset) - formatted = [self._format_params(i) for i in params] + # uniform treatment for sequences and iterables + params_iter = iter(params) + query, firstparams = self._fix_for_params(query, next(params_iter)) + # we build a list of formatted params; as we're going to traverse it + # more than once, we can't make it lazy by using a generator + formatted = [firstparams]+[self._format_params(p) for p in params_iter] self._guess_input_sizes(formatted) try: return self.cursor.executemany(query, diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index de86758653..bf75459b4a 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -6,7 +6,6 @@ Requires psycopg 2: http://initd.org/projects/psycopg2 import logging import sys -from django.db import utils from django.db.backends import * from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations from django.db.backends.postgresql_psycopg2.client import DatabaseClient @@ -17,7 +16,6 @@ from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor from django.utils.encoding import force_str from django.utils.functional import cached_property from django.utils.safestring import SafeText, SafeBytes -from django.utils import six from django.utils.timezone import utc try: diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py index 819dd7a29e..1bfda6354d 100644 --- a/django/db/backends/postgresql_psycopg2/creation.py +++ b/django/db/backends/postgresql_psycopg2/creation.py @@ -1,5 +1,3 @@ -import psycopg2.extensions - from django.db.backends.creation import BaseDatabaseCreation from django.db.backends.util import truncate_name diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index f96757da8b..c5aab84693 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -69,7 +69,7 @@ class DatabaseOperations(BaseDatabaseOperations): # Cast text lookups to text to allow things like filter(x__contains=4) if lookup_type in ('iexact', 'contains', 'icontains', 'startswith', - 'istartswith', 'endswith', 'iendswith'): + 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'): lookup = "%s::text" # Use UPPER(x) for case-insensitive lookups; it's faster. diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index a53d3ee53e..64da0a9f25 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -20,6 +20,7 @@ from django.db.backends.sqlite3.schema import DatabaseSchemaEditor from django.db.models import fields from django.db.models.sql import aggregates from django.utils.dateparse import parse_date, parse_datetime, parse_time +from django.utils.encoding import force_text from django.utils.functional import cached_property from django.utils.safestring import SafeBytes from django.utils import six @@ -103,6 +104,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_foreign_keys = False supports_check_constraints = False autocommits_when_autocommit_is_off = True + supports_paramstyle_pyformat = False @cached_property def uses_savepoints(self): @@ -253,6 +255,9 @@ class DatabaseOperations(BaseDatabaseOperations): and gets dates and datetimes wrong. For consistency with other backends, coerce when required. """ + if value is None: + return None + internal_type = field.get_internal_type() if internal_type == 'DecimalField': return util.typecast_decimal(field.format_number(value)) @@ -526,4 +531,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs): return str(dt) def _sqlite_regexp(re_pattern, re_string): - return bool(re.search(re_pattern, re_string)) + return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 64890fff47..9f2e01e781 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -137,7 +137,7 @@ class RelatedField(Field): # related object in a table-spanning query. It uses the lower-cased # object_name by default, but this can be overridden with the # "related_name" option. - return self.rel.related_name or self.opts.model_name + return self.rel.related_query_name or self.rel.related_name or self.opts.model_name class RenameRelatedObjectDescriptorMethods(RenameMethodsBase): @@ -824,7 +824,7 @@ class ReverseManyRelatedObjectsDescriptor(object): class ForeignObjectRel(object): def __init__(self, field, to, related_name=None, limit_choices_to=None, - parent_link=False, on_delete=None): + parent_link=False, on_delete=None, related_query_name=None): try: to._meta except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT @@ -833,6 +833,7 @@ class ForeignObjectRel(object): self.field = field self.to = to self.related_name = related_name + self.related_query_name = related_query_name self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to self.multiple = True self.parent_link = parent_link @@ -860,10 +861,10 @@ class ForeignObjectRel(object): class ManyToOneRel(ForeignObjectRel): def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, - parent_link=False, on_delete=None): + parent_link=False, on_delete=None, related_query_name=None): super(ManyToOneRel, self).__init__( field, to, related_name=related_name, limit_choices_to=limit_choices_to, - parent_link=parent_link, on_delete=on_delete) + parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name) self.field_name = field_name def get_related_field(self): @@ -883,21 +884,22 @@ class ManyToOneRel(ForeignObjectRel): class OneToOneRel(ManyToOneRel): def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, - parent_link=False, on_delete=None): + parent_link=False, on_delete=None, related_query_name=None): super(OneToOneRel, self).__init__(field, to, field_name, related_name=related_name, limit_choices_to=limit_choices_to, - parent_link=parent_link, on_delete=on_delete + parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name, ) self.multiple = False class ManyToManyRel(object): def __init__(self, to, related_name=None, limit_choices_to=None, - symmetrical=True, through=None, db_constraint=True): + symmetrical=True, through=None, db_constraint=True, related_query_name=None): if through and not db_constraint: raise ValueError("Can't supply a through model and db_constraint=False") self.to = to self.related_name = related_name + self.related_query_name = related_query_name if limit_choices_to is None: limit_choices_to = {} self.limit_choices_to = limit_choices_to @@ -931,6 +933,7 @@ class ForeignObject(RelatedField): kwargs['rel'] = ForeignObjectRel( self, to, related_name=kwargs.pop('related_name', None), + related_query_name=kwargs.pop('related_query_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), parent_link=kwargs.pop('parent_link', False), on_delete=kwargs.pop('on_delete', CASCADE), @@ -1141,6 +1144,7 @@ class ForeignKey(ForeignObject): kwargs['rel'] = rel_class( self, to, to_field, related_name=kwargs.pop('related_name', None), + related_query_name=kwargs.pop('related_query_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), parent_link=kwargs.pop('parent_link', False), on_delete=kwargs.pop('on_delete', CASCADE), @@ -1371,6 +1375,7 @@ class ManyToManyField(RelatedField): kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['rel'] = ManyToManyRel(to, related_name=kwargs.pop('related_name', None), + related_query_name=kwargs.pop('related_query_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), through=kwargs.pop('through', None), @@ -1388,7 +1393,8 @@ class ManyToManyField(RelatedField): # Handle the simpler arguments if self.rel.db_constraint is not True: kwargs['db_constraint'] = self.db_constraint - del kwargs['help_text'] + if "help_text" in kwargs: + del kwargs['help_text'] # Rel needs more work. rel = self.rel if isinstance(self.rel.to, basestring): diff --git a/django/db/models/options.py b/django/db/models/options.py index 1a9421c0fa..b173da9538 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -422,12 +422,36 @@ class Options(object): return cache def get_add_permission(self): + """ + This method has been deprecated in favor of + `django.contrib.auth.get_permission_codename`. refs #20642 + """ + warnings.warn( + "`Options.get_add_permission` has been deprecated in favor " + "of `django.contrib.auth.get_permission_codename`.", + PendingDeprecationWarning, stacklevel=2) return 'add_%s' % self.model_name def get_change_permission(self): + """ + This method has been deprecated in favor of + `django.contrib.auth.get_permission_codename`. refs #20642 + """ + warnings.warn( + "`Options.get_change_permission` has been deprecated in favor " + "of `django.contrib.auth.get_permission_codename`.", + PendingDeprecationWarning, stacklevel=2) return 'change_%s' % self.model_name def get_delete_permission(self): + """ + This method has been deprecated in favor of + `django.contrib.auth.get_permission_codename`. refs #20642 + """ + warnings.warn( + "`Options.get_delete_permission` has been deprecated in favor " + "of `django.contrib.auth.get_permission_codename`.", + PendingDeprecationWarning, stacklevel=2) return 'delete_%s' % self.model_name def get_all_related_objects(self, local_only=False, include_hidden=False, diff --git a/django/db/models/query.py b/django/db/models/query.py index b0ce25f5b5..27a87a3f65 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1445,7 +1445,10 @@ class RawQuerySet(object): yield instance def __repr__(self): - return "<RawQuerySet: %r>" % (self.raw_query % tuple(self.params)) + text = self.raw_query + if self.params: + text = text % (self.params if hasattr(self.params, 'keys') else tuple(self.params)) + return "<RawQuerySet: %r>" % text def __getitem__(self, k): return list(self)[k] diff --git a/django/db/transaction.py b/django/db/transaction.py index f770f2efa7..95b9ae165e 100644 --- a/django/db/transaction.py +++ b/django/db/transaction.py @@ -171,6 +171,26 @@ def clean_savepoints(using=None): """ get_connection(using).clean_savepoints() +def get_rollback(using=None): + """ + Gets the "needs rollback" flag -- for *advanced use* only. + """ + return get_connection(using).needs_rollback + +def set_rollback(rollback, using=None): + """ + Sets or unsets the "needs rollback" flag -- for *advanced use* only. + + When `rollback` is `True`, it triggers a rollback when exiting the + innermost enclosing atomic block that has `savepoint=True` (that's the + default). Use this to force a rollback without raising an exception. + + When `rollback` is `False`, it prevents such a rollback. Use this only + after rolling back to a known-good state! Otherwise, you break the atomic + block and data corruption may occur. + """ + return get_connection(using).set_rollback(rollback) + ################################# # Decorators / context managers # ################################# |
