diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index f4ada55ac6..219686789f 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -5,9 +5,13 @@ from django.core import management # types, as strings. Column-type strings can contain format strings; they'll # be interpolated against the values of Field.__dict__ before being output. # If a column type is set to None, it won't be included in the output. +# +# Any format strings starting with "qn_" are quoted before being used in the +# output (the "qn_" prefix is stripped before the lookup is performed. + DATA_TYPES = { 'AutoField': 'NUMBER(11)', - 'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))', + 'BooleanField': 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))', 'CharField': 'NVARCHAR2(%(max_length)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', 'DateField': 'DATE', @@ -19,11 +23,11 @@ DATA_TYPES = { 'ImageField': 'NVARCHAR2(%(max_length)s)', 'IntegerField': 'NUMBER(11)', 'IPAddressField': 'VARCHAR2(15)', - 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', + 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))', 'OneToOneField': 'NUMBER(11)', 'PhoneNumberField': 'VARCHAR2(20)', - 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', - 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', + 'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', + 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', 'SlugField': 'NVARCHAR2(50)', 'SmallIntegerField': 'NUMBER(11)', 'TextField': 'NCLOB', diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 96bb466c7b..a893c25e63 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -16,6 +16,7 @@ from django.core import validators from django import oldforms from django import newforms as forms from django.core.exceptions import ObjectDoesNotExist +from django.utils.datastructures import DictWrapper from django.utils.functional import curry from django.utils.itercompat import tee from django.utils.text import capfirst @@ -161,8 +162,9 @@ class Field(object): # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. + data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") try: - return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__ + return get_creation_module().DATA_TYPES[self.get_internal_type()] % data except KeyError: return None diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4c278c0d8e..21a72f2d1e 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -343,3 +343,34 @@ class FileDict(dict): d = dict(self, content='') return dict.__repr__(d) return dict.__repr__(self) + +class DictWrapper(dict): + """ + Wraps accesses to a dictionary so that certain values (those starting with + the specified prefix) are passed through a function before being returned. + The prefix is removed before looking up the real value. + + Used by the SQL construction code to ensure that values are correctly + quoted before being used. + """ + def __init__(self, data, func, prefix): + super(DictWrapper, self).__init__(data) + self.func = func + self.prefix = prefix + + def __getitem__(self, key): + """ + Retrieves the real value after stripping the prefix string (if + present). If the prefix is present, pass the value through self.func + before returning, otherwise return the raw value. + """ + if key.startswith(self.prefix): + use_func = True + key = key[len(self.prefix):] + else: + use_func = False + value = super(DictWrapper, self).__getitem__(key) + if use_func: + return self.func(value) + return value + diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 2e84d89ea8..8059798f86 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -122,12 +122,12 @@ class LoopZ(models.Model): class CustomManager(models.Manager): def get_query_set(self): qs = super(CustomManager, self).get_query_set() - return qs.filter(is_public=True, tag__name='t1') + return qs.filter(public=True, tag__name='t1') class ManagedModel(models.Model): data = models.CharField(max_length=10) tag = models.ForeignKey(Tag) - is_public = models.BooleanField(default=True) + public = models.BooleanField(default=True) objects = CustomManager() normal_manager = models.Manager() @@ -730,7 +730,7 @@ More twisted cases, involving nested negations. Bug #7095 Updates that are filtered on the model being updated are somewhat tricky to get in MySQL. This exercises that case. ->>> mm = ManagedModel.objects.create(data='mm1', tag=t1, is_public=True) +>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True) >>> ManagedModel.objects.update(data='mm') A values() or values_list() query across joined models must use outer joins