summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2008-08-05 17:15:33 +0000
committerJustin Bronn <jbronn@gmail.com>2008-08-05 17:15:33 +0000
commitaa239e3e5405933af6a29dac3cf587b59a099927 (patch)
treeea2cbd139c9a8cf84c09e0b2008bff70e05927ef /django/db
parent45b73c9a4685809236f84046cc7ffd32a50db958 (diff)
downloaddjango-attic/gis.tar.gz
gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.archive/attic/gisattic/gis
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@8215 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db')
-rw-r--r--django/db/backends/__init__.py70
-rw-r--r--django/db/backends/mysql/base.py19
-rw-r--r--django/db/backends/oracle/base.py30
-rw-r--r--django/db/backends/oracle/query.py9
-rw-r--r--django/db/backends/sqlite3/base.py8
-rw-r--r--django/db/backends/util.py12
-rw-r--r--django/db/models/__init__.py2
-rw-r--r--django/db/models/base.py38
-rw-r--r--django/db/models/fields/__init__.py244
-rw-r--r--django/db/models/fields/related.py173
-rw-r--r--django/db/models/fields/subclassing.py5
-rw-r--r--django/db/models/query.py35
-rw-r--r--django/db/models/sql/query.py34
-rw-r--r--django/db/models/sql/where.py39
14 files changed, 471 insertions, 247 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 899a779673..d65eacd042 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -5,6 +5,9 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
+from django.db.backends import util
+from django.utils import datetime_safe
+
class BaseDatabaseWrapper(local):
"""
Represents a database connection.
@@ -36,12 +39,13 @@ class BaseDatabaseWrapper(local):
return cursor
def make_debug_cursor(self, cursor):
- from django.db.backends import util
return util.CursorDebugWrapper(cursor, self)
class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
inline_fk_references = True
+ # True if django.db.backend.utils.typecast_timestamp is used on values
+ # returned from dates() calls.
needs_datetime_string_cast = True
supports_constraints = True
supports_tablespaces = False
@@ -49,10 +53,7 @@ class BaseDatabaseFeatures(object):
uses_custom_query_class = False
empty_fetchmany_value = []
update_can_self_select = True
- supports_usecs = True
- time_field_needs_date = False
interprets_empty_strings_as_nulls = False
- date_field_supports_time_value = True
can_use_chunked_reads = True
class BaseDatabaseOperations(object):
@@ -263,3 +264,64 @@ class BaseDatabaseOperations(object):
"""Prepares a value for use in a LIKE query."""
from django.utils.encoding import smart_unicode
return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+
+ def value_to_db_date(self, value):
+ """
+ Transform a date value to an object compatible with what is expected
+ by the backend driver for date columns.
+ """
+ if value is None:
+ return None
+ return datetime_safe.new_date(value).strftime('%Y-%m-%d')
+
+ def value_to_db_datetime(self, value):
+ """
+ Transform a datetime value to an object compatible with what is expected
+ by the backend driver for datetime columns.
+ """
+ if value is None:
+ return None
+ return unicode(value)
+
+ def value_to_db_time(self, value):
+ """
+ Transform a datetime value to an object compatible with what is expected
+ by the backend driver for time columns.
+ """
+ if value is None:
+ return None
+ return unicode(value)
+
+ def value_to_db_decimal(self, value, max_digits, decimal_places):
+ """
+ Transform a decimal.Decimal value to an object compatible with what is
+ expected by the backend driver for decimal (numeric) columns.
+ """
+ if value is None:
+ return None
+ return util.format_number(value, max_digits, decimal_places)
+
+ def year_lookup_bounds(self, value):
+ """
+ Returns a two-elements list with the lower and upper bound to be used
+ with a BETWEEN operator to query a field value using a year lookup
+
+ `value` is an int, containing the looked-up year.
+ """
+ first = '%s-01-01 00:00:00'
+ second = '%s-12-31 23:59:59.999999'
+ return [first % value, second % value]
+
+ def year_lookup_bounds_for_date_field(self, value):
+ """
+ Returns a two-elements list with the lower and upper bound to be used
+ with a BETWEEN operator to query a DateField value using a year lookup
+
+ `value` is an int, containing the looked-up year.
+
+ By default, it just calls `self.year_lookup_bounds`. Some backends need
+ this hook because on their DB date fields can't be compared to values
+ which include a time part.
+ """
+ return self.year_lookup_bounds(value)
+
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 74138a7b11..3b8d897925 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -63,7 +63,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
- supports_usecs = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
@@ -124,6 +123,24 @@ class DatabaseOperations(BaseDatabaseOperations):
else:
return []
+ def value_to_db_datetime(self, value):
+ # MySQL doesn't support microseconds
+ if value is None:
+ return None
+ return unicode(value.replace(microsecond=0))
+
+ def value_to_db_time(self, value):
+ # MySQL doesn't support microseconds
+ if value is None:
+ return None
+ return unicode(value.replace(microsecond=0))
+
+ def year_lookup_bounds(self, value):
+ # Again, no microseconds
+ first = '%s-01-01 00:00:00'
+ second = '%s-12-31 23:59:59.99'
+ return [first % value, second % value]
+
class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index a41084bca3..bdb73b1864 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -5,10 +5,11 @@ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
"""
import os
+import datetime
+import time
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
from django.db.backends.oracle import query
-from django.utils.datastructures import SortedDict
from django.utils.encoding import smart_str, force_unicode
# Oracle takes client-side character set encoding from the environment.
@@ -29,9 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_tablespaces = True
uses_case_insensitive_names = True
uses_custom_query_class = True
- time_field_needs_date = True
interprets_empty_strings_as_nulls = True
- date_field_supports_time_value = False
class DatabaseOperations(BaseDatabaseOperations):
def autoinc_sql(self, table, column):
@@ -181,6 +180,21 @@ class DatabaseOperations(BaseDatabaseOperations):
def tablespace_sql(self, tablespace, inline=False):
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace))
+ def value_to_db_time(self, value):
+ if value is None:
+ return None
+ if isinstance(value, basestring):
+ return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
+ return datetime.datetime(1900, 1, 1, value.hour, value.minute,
+ value.second, value.microsecond)
+
+ def year_lookup_bounds_for_date_field(self, value):
+ first = '%s-01-01'
+ second = '%s-12-31'
+ return [first % value, second % value]
+
+
+
class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
@@ -245,10 +259,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
class OracleParam(object):
"""
- Wrapper object for formatting parameters for Oracle. If the string
- representation of the value is large enough (greater than 4000 characters)
+ Wrapper object for formatting parameters for Oracle. If the string
+ representation of the value is large enough (greater than 4000 characters)
the input size needs to be set as NCLOB. Alternatively, if the parameter has
- an `input_size` attribute, then the value of the `input_size` attribute will
+ an `input_size` attribute, then the value of the `input_size` attribute will
be used instead. Otherwise, no input size will be set for the parameter when
executing the query.
"""
@@ -282,7 +296,7 @@ class FormatStylePlaceholderCursor(Database.Cursor):
return result
else:
return tuple([OracleParam(p, self.charset, True) for p in params])
-
+
def _guess_input_sizes(self, params_list):
if isinstance(params_list[0], dict):
sizes = {}
@@ -303,7 +317,7 @@ class FormatStylePlaceholderCursor(Database.Cursor):
return dict([(k, p.smart_str) for k, p in params.iteritems()])
else:
return [p.smart_str for p in params]
-
+
def execute(self, query, params=None):
if params is None:
params = []
diff --git a/django/db/backends/oracle/query.py b/django/db/backends/oracle/query.py
index 7e50c7b5db..49e1c4131c 100644
--- a/django/db/backends/oracle/query.py
+++ b/django/db/backends/oracle/query.py
@@ -87,9 +87,11 @@ def query_class(QueryClass, Database):
If 'with_limits' is False, any limit/offset information is not
included in the query.
"""
+
# The `do_offset` flag indicates whether we need to construct
# the SQL needed to use limit/offset w/Oracle.
- do_offset = with_limits and (self.high_mark or self.low_mark)
+ do_offset = with_limits and (self.high_mark is not None
+ or self.low_mark)
# If no offsets, just return the result of the base class
# `as_sql`.
@@ -117,7 +119,7 @@ def query_class(QueryClass, Database):
# Getting the selection SQL and the params, which has the `rn`
# extra selection SQL.
self.extra_select['rn'] = 'ROW_NUMBER() OVER (ORDER BY %s )' % rn_orderby
- sql, params= super(OracleQuery, self).as_sql(with_limits=False,
+ sql, params = super(OracleQuery, self).as_sql(with_limits=False,
with_col_aliases=True)
# Constructing the result SQL, using the initial select SQL
@@ -126,7 +128,7 @@ def query_class(QueryClass, Database):
# Place WHERE condition on `rn` for the desired range.
result.append('WHERE rn > %d' % self.low_mark)
- if self.high_mark:
+ if self.high_mark is not None:
result.append('AND rn <= %d' % self.high_mark)
# Returning the SQL w/params.
@@ -148,4 +150,3 @@ def query_class(QueryClass, Database):
_classes[QueryClass] = OracleQuery
return OracleQuery
-
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 48d9ad4c4b..71be86f00b 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -84,6 +84,12 @@ class DatabaseOperations(BaseDatabaseOperations):
# sql_flush() implementations). Just return SQL at this point
return sql
+ def year_lookup_bounds(self, value):
+ first = '%s-01-01'
+ second = '%s-12-31 23:59:59.999999'
+ return [first % value, second % value]
+
+
class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
@@ -159,7 +165,7 @@ def _sqlite_extract(lookup_type, dt):
dt = util.typecast_timestamp(dt)
except (ValueError, TypeError):
return None
- return str(getattr(dt, lookup_type))
+ return getattr(dt, lookup_type)
def _sqlite_date_trunc(lookup_type, dt):
try:
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index 367072879e..7228b4046b 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -1,7 +1,8 @@
import datetime
-import md5
from time import time
+from django.utils.hashcompat import md5_constructor
+
try:
import decimal
except ImportError:
@@ -114,6 +115,13 @@ def truncate_name(name, length=None):
if length is None or len(name) <= length:
return name
- hash = md5.md5(name).hexdigest()[:4]
+ hash = md5_constructor(name).hexdigest()[:4]
return '%s%s' % (name[:length-4], hash)
+
+def format_number(value, max_digits, decimal_places):
+ """
+ Formats a number into a string with the requisite number of digits and
+ decimal places.
+ """
+ return u"%.*f" % (decimal_places, value)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index bd6cc3542d..18c47e86f3 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -10,8 +10,6 @@ from django.db.models.fields import *
from django.db.models.fields.subclassing import SubfieldBase
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
from django.db.models import signals
-from django.utils.functional import curry
-from django.utils.text import capfirst
# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 080e0af588..7d7def3bad 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -12,7 +12,7 @@ import django.db.models.manipulators # Imported to register signal handler.
import django.db.models.manager # Ditto.
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
-from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
+from django.db.models.fields import AutoField, ImageField
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
from django.db.models.query import delete_objects, Q, CollectedObjects
from django.db.models.options import Options
@@ -20,7 +20,6 @@ from django.db import connection, transaction
from django.db.models import signals
from django.db.models.loading import register_models, get_model
from django.dispatch import dispatcher
-from django.utils.datastructures import SortedDict
from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode, smart_unicode
from django.core.files.move import file_move_safe
@@ -201,6 +200,7 @@ class Model(object):
# keywords, or default.
for field in fields_iter:
+ rel_obj = None
if kwargs:
if isinstance(field.rel, ManyToOneRel):
try:
@@ -217,17 +217,18 @@ class Model(object):
# pass in "None" for related objects if it's allowed.
if rel_obj is None and field.null:
val = None
- else:
- try:
- val = getattr(rel_obj, field.rel.get_related_field().attname)
- except AttributeError:
- raise TypeError("Invalid value: %r should be a %s instance, not a %s" %
- (field.name, field.rel.to, type(rel_obj)))
else:
val = kwargs.pop(field.attname, field.get_default())
else:
val = field.get_default()
- setattr(self, field.attname, val)
+ # If we got passed a related instance, set it using the field.name
+ # instead of field.attname (e.g. "user" instead of "user_id") so
+ # that the object gets properly cached (and type checked) by the
+ # RelatedObjectDescriptor.
+ if rel_obj:
+ setattr(self, field.name, rel_obj)
+ else:
+ setattr(self, field.attname, val)
if kwargs:
for prop in kwargs.keys():
@@ -299,6 +300,12 @@ class Model(object):
# attributes we have been given to the class we have been given.
if not raw:
for parent, field in meta.parents.items():
+ # At this point, parent's primary key field may be unknown
+ # (for example, from administration form which doesn't fill
+ # this field). If so, fill it.
+ if getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
+ setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
+
self.save_base(raw, parent)
setattr(self, field.attname, self._get_pk_val(parent._meta))
@@ -472,11 +479,12 @@ class Model(object):
return os.path.getsize(self._get_FIELD_filename(field))
def _save_FIELD_file(self, field, filename, raw_field, save=True):
- directory = field.get_directory_name()
- try: # Create the date-based directory if it doesn't exist.
- os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
- except OSError: # Directory probably already exists.
- pass
+ # Create the upload directory if it doesn't already exist
+ directory = os.path.join(settings.MEDIA_ROOT, field.get_directory_name())
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ elif not os.path.isdir(directory):
+ raise IOError('%s exists and is not a directory' % directory)
# Check for old-style usage (files-as-dictionaries). Warn here first
# since there are multiple locations where we need to support both new
@@ -494,7 +502,7 @@ class Model(object):
elif isinstance(raw_field, basestring):
import warnings
warnings.warn(
- message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.",
+ message = "Representing uploaded files as strings is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.",
category = DeprecationWarning,
stacklevel = 2
)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 78f75aea35..494c42cc54 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -22,7 +22,6 @@ from django.utils.itercompat import tee
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.encoding import smart_unicode, force_unicode, smart_str
-from django.utils.maxlength import LegacyMaxlength
from django.utils import datetime_safe
class NOT_PROVIDED:
@@ -62,10 +61,6 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data):
# getattr(obj, opts.pk.attname)
class Field(object):
- # Provide backwards compatibility for the maxlength attribute and
- # argument for this class and all subclasses.
- __metaclass__ = LegacyMaxlength
-
# Designates whether empty strings fundamentally are allowed at the
# database level.
empty_strings_allowed = True
@@ -191,7 +186,8 @@ class Field(object):
def set_attributes_from_name(self, name):
self.name = name
self.attname, self.column = self.get_attname_column()
- self.verbose_name = self.verbose_name or (name and name.replace('_', ' '))
+ if self.verbose_name is None and name:
+ self.verbose_name = name.replace('_', ' ')
def contribute_to_class(self, cls, name):
self.set_attributes_from_name(name)
@@ -217,19 +213,30 @@ class Field(object):
"Returns field's value just before saving."
return getattr(model_instance, self.attname)
+ def get_db_prep_value(self, value):
+ """Returns field's value prepared for interacting with the database
+ backend.
+
+ Used by the default implementations of ``get_db_prep_save``and
+ `get_db_prep_lookup```
+ """
+ return value
+
def get_db_prep_save(self, value):
"Returns field's value prepared for saving into a database."
- return value
+ return self.get_db_prep_value(value)
def get_db_prep_lookup(self, lookup_type, value):
"Returns field's value prepared for database lookup."
if hasattr(value, 'as_sql'):
sql, params = value.as_sql()
return QueryWrapper(('(%s)' % sql), params)
- if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
+ if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'):
return [value]
+ elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
+ return [self.get_db_prep_value(value)]
elif lookup_type in ('range', 'in'):
- return value
+ return [self.get_db_prep_value(v) for v in value]
elif lookup_type in ('contains', 'icontains'):
return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
elif lookup_type == 'iexact':
@@ -245,19 +252,12 @@ class Field(object):
value = int(value)
except ValueError:
raise ValueError("The __year lookup type requires an integer argument")
- if settings.DATABASE_ENGINE == 'sqlite3':
- first = '%s-01-01'
- second = '%s-12-31 23:59:59.999999'
- elif not connection.features.date_field_supports_time_value and self.get_internal_type() == 'DateField':
- first = '%s-01-01'
- second = '%s-12-31'
- elif not connection.features.supports_usecs:
- first = '%s-01-01 00:00:00'
- second = '%s-12-31 23:59:59.99'
+
+ if self.get_internal_type() == 'DateField':
+ return connection.ops.year_lookup_bounds_for_date_field(value)
else:
- first = '%s-01-01 00:00:00'
- second = '%s-12-31 23:59:59.999999'
- return [first % value, second % value]
+ return connection.ops.year_lookup_bounds(value)
+
raise TypeError("Field has invalid lookup: %s" % lookup_type)
def has_default(self):
@@ -288,7 +288,7 @@ class Field(object):
if self.choices:
field_objs = [oldforms.SelectField]
- params['choices'] = self.flatchoices
+ params['choices'] = self.get_flatchoices()
else:
field_objs = self.get_manipulator_field_objs()
return (field_objs, params)
@@ -362,7 +362,8 @@ class Field(object):
return val
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
- "Returns a list of tuples used as SelectField choices for this field."
+ """Returns choices with a default blank choices included, for use
+ as SelectField choices for this field."""
first_choice = include_blank and blank_choice or []
if self.choices:
return first_choice + list(self.choices)
@@ -376,6 +377,11 @@ class Field(object):
def get_choices_default(self):
return self.get_choices()
+ def get_flatchoices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
+ "Returns flattened choices with a default blank choice included."
+ first_choice = include_blank and blank_choice or []
+ return first_choice + list(self.flatchoices)
+
def _get_val_from_obj(self, obj):
if obj:
return getattr(obj, self.attname)
@@ -408,15 +414,16 @@ class Field(object):
choices = property(_get_choices)
def _get_flatchoices(self):
+ """Flattened version of choices tuple."""
flat = []
- for choice, value in self.get_choices_default():
+ for choice, value in self.choices:
if type(value) in (list, tuple):
flat.extend(value)
else:
flat.append((choice,value))
return flat
flatchoices = property(_get_flatchoices)
-
+
def save_form_data(self, instance, data):
setattr(instance, self.name, data)
@@ -449,6 +456,11 @@ class AutoField(Field):
except (TypeError, ValueError):
raise validators.ValidationError, _("This value must be an integer.")
+ def get_db_prep_value(self, value):
+ if value is None:
+ return None
+ return int(value)
+
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
if not rel:
return [] # Don't add a FormField unless it's in a related context.
@@ -477,6 +489,8 @@ class AutoField(Field):
class BooleanField(Field):
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
+ if 'default' not in kwargs and not kwargs.get('null'):
+ kwargs['default'] = False
Field.__init__(self, *args, **kwargs)
def get_internal_type(self):
@@ -488,6 +502,11 @@ class BooleanField(Field):
if value in ('f', 'False', '0'): return False
raise validators.ValidationError, _("This value must be either True or False.")
+ def get_db_prep_value(self, value):
+ if value is None:
+ return None
+ return bool(value)
+
def get_manipulator_field_objs(self):
return [oldforms.CheckboxField]
@@ -549,15 +568,6 @@ class DateField(Field):
except ValueError:
raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
- def get_db_prep_lookup(self, lookup_type, value):
- if lookup_type in ('range', 'in'):
- value = [smart_unicode(v) for v in value]
- elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
- value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
- else:
- value = smart_unicode(value)
- return Field.get_db_prep_lookup(self, lookup_type, value)
-
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now()
@@ -581,16 +591,9 @@ class DateField(Field):
else:
return self.editable or self.auto_now or self.auto_now_add
- def get_db_prep_save(self, value):
- # Casts dates into string format for entry into database.
- if value is not None:
- try:
- value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
- except AttributeError:
- # If value is already a string it won't have a strftime method,
- # so we'll just let it pass through.
- pass
- return Field.get_db_prep_save(self, value)
+ def get_db_prep_value(self, value):
+ # Casts dates into the format expected by the backend
+ return connection.ops.value_to_db_date(self.to_python(value))
def get_manipulator_field_objs(self):
return [oldforms.DateField]
@@ -619,33 +622,37 @@ class DateTimeField(DateField):
return value
if isinstance(value, datetime.date):
return datetime.datetime(value.year, value.month, value.day)
+
+ # Attempt to parse a datetime:
+ value = smart_str(value)
+ # split usecs, because they are not recognized by strptime.
+ if '.' in value:
+ try:
+ value, usecs = value.split('.')
+ usecs = int(usecs)
+ except ValueError:
+ raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
+ else:
+ usecs = 0
+ kwargs = {'microsecond': usecs}
try: # Seconds are optional, so try converting seconds first.
- return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6],
+ **kwargs)
+
except ValueError:
try: # Try without seconds.
- return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5],
+ **kwargs)
except ValueError: # Try without hour/minutes/seconds.
try:
- return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
+ **kwargs)
except ValueError:
- raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+ raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
- def get_db_prep_save(self, value):
- # Casts dates into string format for entry into database.
- if value is not None:
- # MySQL will throw a warning if microseconds are given, because it
- # doesn't support microseconds.
- if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
- value = value.replace(microsecond=0)
- value = smart_unicode(value)
- return Field.get_db_prep_save(self, value)
-
- def get_db_prep_lookup(self, lookup_type, value):
- if lookup_type in ('range', 'in'):
- value = [smart_unicode(v) for v in value]
- else:
- value = smart_unicode(value)
- return Field.get_db_prep_lookup(self, lookup_type, value)
+ def get_db_prep_value(self, value):
+ # Casts dates into the format expected by the backend
+ return connection.ops.value_to_db_datetime(self.to_python(value))
def get_manipulator_field_objs(self):
return [oldforms.DateField, oldforms.TimeField]
@@ -710,26 +717,18 @@ class DecimalField(Field):
Formats a number into a string with the requisite number of digits and
decimal places.
"""
- num_chars = self.max_digits
- # Allow for a decimal point
- if self.decimal_places > 0:
- num_chars += 1
- # Allow for a minus sign
- if value < 0:
- num_chars += 1
-
- return u"%.*f" % (self.decimal_places, value)
-
- def get_db_prep_save(self, value):
- value = self._format(value)
- return super(DecimalField, self).get_db_prep_save(value)
+ # Method moved to django.db.backends.util.
+ #
+ # It is preserved because it is used by the oracle backend
+ # (django.db.backends.oracle.query), and also for
+ # backwards-compatibility with any external code which may have used
+ # this method.
+ from django.db.backends import util
+ return util.format_number(value, self.max_digits, self.decimal_places)
- def get_db_prep_lookup(self, lookup_type, value):
- if lookup_type in ('range', 'in'):
- value = [self._format(v) for v in value]
- else:
- value = self._format(value)
- return super(DecimalField, self).get_db_prep_lookup(lookup_type, value)
+ def get_db_prep_value(self, value):
+ return connection.ops.value_to_db_decimal(self.to_python(value),
+ self.max_digits, self.decimal_places)
def get_manipulator_field_objs(self):
return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
@@ -768,7 +767,7 @@ class FileField(Field):
def get_internal_type(self):
return "FileField"
- def get_db_prep_save(self, value):
+ def get_db_prep_value(self, value):
"Returns field's value prepared for saving into a database."
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
if hasattr(value, 'name'):
@@ -909,6 +908,11 @@ class FilePathField(Field):
class FloatField(Field):
empty_strings_allowed = False
+ def get_db_prep_value(self, value):
+ if value is None:
+ return None
+ return float(value)
+
def get_manipulator_field_objs(self):
return [oldforms.FloatField]
@@ -956,6 +960,11 @@ class ImageField(FileField):
class IntegerField(Field):
empty_strings_allowed = False
+ def get_db_prep_value(self, value):
+ if value is None:
+ return None
+ return int(value)
+
def get_manipulator_field_objs(self):
return [oldforms.IntegerField]
@@ -1003,6 +1012,11 @@ class NullBooleanField(Field):
if value in ('f', 'False', '0'): return False
raise validators.ValidationError, _("This value must be either None, True or False.")
+ def get_db_prep_value(self, value):
+ if value is None:
+ return None
+ return bool(value)
+
def get_manipulator_field_objs(self):
return [oldforms.NullBooleanField]
@@ -1015,7 +1029,7 @@ class NullBooleanField(Field):
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)
-class PhoneNumberField(IntegerField):
+class PhoneNumberField(Field):
def get_manipulator_field_objs(self):
return [oldforms.PhoneNumberField]
@@ -1097,20 +1111,34 @@ class TimeField(Field):
def get_internal_type(self):
return "TimeField"
- def get_db_prep_lookup(self, lookup_type, value):
- if connection.features.time_field_needs_date:
- # Oracle requires a date in order to parse.
- def prep(value):
- if isinstance(value, datetime.time):
- value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
- return smart_unicode(value)
- else:
- prep = smart_unicode
- if lookup_type in ('range', 'in'):
- value = [prep(v) for v in value]
+ def to_python(self, value):
+ if value is None:
+ return None
+ if isinstance(value, datetime.time):
+ return value
+
+ # Attempt to parse a datetime:
+ value = smart_str(value)
+ # split usecs, because they are not recognized by strptime.
+ if '.' in value:
+ try:
+ value, usecs = value.split('.')
+ usecs = int(usecs)
+ except ValueError:
+ raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
else:
- value = prep(value)
- return Field.get_db_prep_lookup(self, lookup_type, value)
+ usecs = 0
+ kwargs = {'microsecond': usecs}
+
+ try: # Seconds are optional, so try converting seconds first.
+ return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
+ **kwargs)
+ except ValueError:
+ try: # Try without seconds.
+ return datetime.time(*time.strptime(value, '%H:%M')[3:5],
+ **kwargs)
+ except ValueError:
+ raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
@@ -1120,23 +1148,9 @@ class TimeField(Field):
else:
return super(TimeField, self).pre_save(model_instance, add)
- def get_db_prep_save(self, value):
- # Casts dates into string format for entry into database.
- if value is not None:
- # MySQL will throw a warning if microseconds are given, because it
- # doesn't support microseconds.
- if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
- value = value.replace(microsecond=0)
- if connection.features.time_field_needs_date:
- # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
- if isinstance(value, datetime.time):
- value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
- value.second, value.microsecond)
- elif isinstance(value, basestring):
- value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
- else:
- value = smart_unicode(value)
- return Field.get_db_prep_save(self, value)
+ def get_db_prep_value(self, value):
+ # Casts times into the format expected by the backend
+ return connection.ops.value_to_db_time(self.to_python(value))
def get_manipulator_field_objs(self):
return [oldforms.TimeField]
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 735dda4969..ab2b9a6c5e 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -2,11 +2,10 @@ from django.db import connection, transaction
from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
from django.db.models.related import RelatedObject
+from django.db.models.query import QuerySet
from django.db.models.query_utils import QueryWrapper
-from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
from django.utils.functional import curry
-from django.utils.encoding import smart_unicode
from django.core import validators
from django import oldforms
from django import forms
@@ -24,7 +23,7 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
pending_lookups = {}
-def add_lazy_relation(cls, field, relation):
+def add_lazy_relation(cls, field, relation, operation):
"""
Adds a lookup on ``cls`` when a related field is defined using a string,
i.e.::
@@ -46,6 +45,8 @@ def add_lazy_relation(cls, field, relation):
If the other model hasn't yet been loaded -- almost a given if you're using
lazy relationships -- then the relation won't be set up until the
class_prepared signal fires at the end of model initialization.
+
+ operation is the work that must be performed once the relation can be resolved.
"""
# Check for recursive relations
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
@@ -67,11 +68,10 @@ def add_lazy_relation(cls, field, relation):
# is prepared.
model = get_model(app_label, model_name, False)
if model:
- field.rel.to = model
- field.do_related_class(model, cls)
+ operation(field, model, cls)
else:
key = (app_label, model_name)
- value = (cls, field)
+ value = (cls, field, operation)
pending_lookups.setdefault(key, []).append(value)
def do_pending_lookups(sender):
@@ -79,9 +79,8 @@ def do_pending_lookups(sender):
Handle any pending relations to the sending model. Sent from class_prepared.
"""
key = (sender._meta.app_label, sender.__name__)
- for cls, field in pending_lookups.pop(key, []):
- field.rel.to = sender
- field.do_related_class(sender, cls)
+ for cls, field, operation in pending_lookups.pop(key, []):
+ operation(field, sender, cls)
dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
@@ -109,13 +108,17 @@ class RelatedField(object):
other = self.rel.to
if isinstance(other, basestring):
- add_lazy_relation(cls, self, other)
+ def resolve_related_class(field, model, cls):
+ field.rel.to = model
+ field.do_related_class(model, cls)
+ add_lazy_relation(cls, self, other, resolve_related_class)
else:
self.do_related_class(other, cls)
def set_attributes_from_rel(self):
self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
- self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name
+ if self.verbose_name is None:
+ self.verbose_name = self.rel.to._meta.verbose_name
self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
def do_related_class(self, other, cls):
@@ -236,7 +239,14 @@ class ReverseSingleRelatedObjectDescriptor(object):
params = {'%s__pk' % self.field.rel.field_name: val}
else:
params = {'%s__exact' % self.field.rel.field_name: val}
- rel_obj = self.field.rel.to._default_manager.get(**params)
+
+ # If the related manager indicates that it should be used for
+ # related fields, respect that.
+ rel_mgr = self.field.rel.to._default_manager
+ if getattr(rel_mgr, 'use_for_related_fields', False):
+ rel_obj = rel_mgr.get(**params)
+ else:
+ rel_obj = QuerySet(self.field.rel.to).get(**params)
setattr(instance, cache_name, rel_obj)
return rel_obj
@@ -340,7 +350,7 @@ class ForeignRelatedObjectsDescriptor(object):
manager.clear()
manager.add(*value)
-def create_many_related_manager(superclass):
+def create_many_related_manager(superclass, through=False):
"""Creates a manager that subclasses 'superclass' (which is a Manager)
and adds behavior for many-to-many related objects."""
class ManyRelatedManager(superclass):
@@ -354,6 +364,7 @@ def create_many_related_manager(superclass):
self.join_table = join_table
self.source_col_name = source_col_name
self.target_col_name = target_col_name
+ self.through = through
self._pk_val = self.instance._get_pk_val()
if self._pk_val is None:
raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
@@ -361,21 +372,24 @@ def create_many_related_manager(superclass):
def get_query_set(self):
return superclass.get_query_set(self).filter(**(self.core_filters))
- def add(self, *objs):
- self._add_items(self.source_col_name, self.target_col_name, *objs)
+ # If the ManyToMany relation has an intermediary model,
+ # the add and remove methods do not exist.
+ if through is None:
+ def add(self, *objs):
+ self._add_items(self.source_col_name, self.target_col_name, *objs)
- # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
- if self.symmetrical:
- self._add_items(self.target_col_name, self.source_col_name, *objs)
- add.alters_data = True
+ # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
+ if self.symmetrical:
+ self._add_items(self.target_col_name, self.source_col_name, *objs)
+ add.alters_data = True
- def remove(self, *objs):
- self._remove_items(self.source_col_name, self.target_col_name, *objs)
+ def remove(self, *objs):
+ self._remove_items(self.source_col_name, self.target_col_name, *objs)
- # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
- if self.symmetrical:
- self._remove_items(self.target_col_name, self.source_col_name, *objs)
- remove.alters_data = True
+ # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
+ if self.symmetrical:
+ self._remove_items(self.target_col_name, self.source_col_name, *objs)
+ remove.alters_data = True
def clear(self):
self._clear_items(self.source_col_name)
@@ -386,6 +400,10 @@ def create_many_related_manager(superclass):
clear.alters_data = True
def create(self, **kwargs):
+ # This check needs to be done here, since we can't later remove this
+ # from the method lookup table, as we do with add and remove.
+ if through is not None:
+ raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
new_obj = self.model(**kwargs)
new_obj.save()
self.add(new_obj)
@@ -473,7 +491,7 @@ class ManyRelatedObjectsDescriptor(object):
# model's default manager.
rel_model = self.related.model
superclass = rel_model._default_manager.__class__
- RelatedManager = create_many_related_manager(superclass)
+ RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
qn = connection.ops.quote_name
manager = RelatedManager(
@@ -492,6 +510,10 @@ class ManyRelatedObjectsDescriptor(object):
if instance is None:
raise AttributeError, "Manager must be accessed via instance"
+ through = getattr(self.related.field.rel, 'through', None)
+ if through is not None:
+ raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+
manager = self.__get__(instance)
manager.clear()
manager.add(*value)
@@ -514,7 +536,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
# model's default manager.
rel_model=self.field.rel.to
superclass = rel_model._default_manager.__class__
- RelatedManager = create_many_related_manager(superclass)
+ RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
qn = connection.ops.quote_name
manager = RelatedManager(
@@ -533,6 +555,10 @@ class ReverseManyRelatedObjectsDescriptor(object):
if instance is None:
raise AttributeError, "Manager must be accessed via instance"
+ through = getattr(self.field.rel, 'through', None)
+ if through is not None:
+ raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+
manager = self.__get__(instance)
manager.clear()
manager.add(*value)
@@ -584,7 +610,7 @@ class OneToOneRel(ManyToOneRel):
class ManyToManyRel(object):
def __init__(self, to, num_in_admin=0, related_name=None,
- limit_choices_to=None, symmetrical=True):
+ limit_choices_to=None, symmetrical=True, through=None):
self.to = to
self.num_in_admin = num_in_admin
self.related_name = related_name
@@ -594,6 +620,7 @@ class ManyToManyRel(object):
self.edit_inline = False
self.symmetrical = symmetrical
self.multiple = True
+ self.through = through
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
@@ -604,12 +631,7 @@ class ForeignKey(RelatedField, Field):
assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else:
to_field = to_field or to._meta.pk.name
- kwargs['verbose_name'] = kwargs.get('verbose_name', '')
-
- if 'edit_inline_type' in kwargs:
- import warnings
- warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.", DeprecationWarning)
- kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
+ kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = rel_class(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 3),
@@ -705,6 +727,7 @@ class OneToOneField(ForeignKey):
"""
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
+ kwargs['editable'] = False
if 'num_in_admin' not in kwargs:
kwargs['num_in_admin'] = 0
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -722,8 +745,16 @@ class ManyToManyField(RelatedField, Field):
num_in_admin=kwargs.pop('num_in_admin', 0),
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
- symmetrical=kwargs.pop('symmetrical', True))
+ symmetrical=kwargs.pop('symmetrical', True),
+ through=kwargs.pop('through', None))
+
self.db_table = kwargs.pop('db_table', None)
+ if kwargs['rel'].through is not None:
+ self.creates_table = False
+ assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
+ else:
+ self.creates_table = True
+
Field.__init__(self, **kwargs)
msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
@@ -738,26 +769,62 @@ class ManyToManyField(RelatedField, Field):
def _get_m2m_db_table(self, opts):
"Function that can be curried to provide the m2m table name for this relation"
- if self.db_table:
+ if self.rel.through is not None:
+ return self.rel.through_model._meta.db_table
+ elif self.db_table:
return self.db_table
else:
return '%s_%s' % (opts.db_table, self.name)
def _get_m2m_column_name(self, related):
"Function that can be curried to provide the source column name for the m2m table"
- # If this is an m2m relation to self, avoid the inevitable name clash
- if related.model == related.parent_model:
- return 'from_' + related.model._meta.object_name.lower() + '_id'
- else:
- return related.model._meta.object_name.lower() + '_id'
+ try:
+ return self._m2m_column_name_cache
+ except:
+ if self.rel.through is not None:
+ for f in self.rel.through_model._meta.fields:
+ if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
+ self._m2m_column_name_cache = f.column
+ break
+ # If this is an m2m relation to self, avoid the inevitable name clash
+ elif related.model == related.parent_model:
+ self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id'
+ else:
+ self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id'
+
+ # Return the newly cached value
+ return self._m2m_column_name_cache
def _get_m2m_reverse_name(self, related):
"Function that can be curried to provide the related column name for the m2m table"
- # If this is an m2m relation to self, avoid the inevitable name clash
- if related.model == related.parent_model:
- return 'to_' + related.parent_model._meta.object_name.lower() + '_id'
- else:
- return related.parent_model._meta.object_name.lower() + '_id'
+ try:
+ return self._m2m_reverse_name_cache
+ except:
+ if self.rel.through is not None:
+ found = False
+ for f in self.rel.through_model._meta.fields:
+ if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
+ if related.model == related.parent_model:
+ # If this is an m2m-intermediate to self,
+ # the first foreign key you find will be
+ # the source column. Keep searching for
+ # the second foreign key.
+ if found:
+ self._m2m_reverse_name_cache = f.column
+ break
+ else:
+ found = True
+ else:
+ self._m2m_reverse_name_cache = f.column
+ break
+ # If this is an m2m relation to self, avoid the inevitable name clash
+ elif related.model == related.parent_model:
+ self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id'
+ else:
+ self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id'
+
+ # Return the newly cached value
+ return self._m2m_reverse_name_cache
def isValidIDList(self, field_data, all_data):
"Validates that the value is a valid list of foreign keys"
@@ -791,13 +858,23 @@ class ManyToManyField(RelatedField, Field):
return new_data
def contribute_to_class(self, cls, name):
- super(ManyToManyField, self).contribute_to_class(cls, name)
+ super(ManyToManyField, self).contribute_to_class(cls, name)
# Add the descriptor for the m2m relation
setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
# Set up the accessor for the m2m table name for the relation
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
-
+
+ # Populate some necessary rel arguments so that cross-app relations
+ # work correctly.
+ if isinstance(self.rel.through, basestring):
+ def resolve_through_model(field, model, cls):
+ field.rel.through_model = model
+ add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
+ elif self.rel.through:
+ self.rel.through_model = self.rel.through
+ self.rel.through = self.rel.through._meta.object_name
+
if isinstance(self.rel.to, basestring):
target = self.rel.to
else:
diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py
index 36f7e4d934..10add10739 100644
--- a/django/db/models/fields/subclassing.py
+++ b/django/db/models/fields/subclassing.py
@@ -5,9 +5,7 @@ Add SubfieldBase as the __metaclass__ for your Field subclass, implement
to_python() and the other necessary methods and everything will work seamlessly.
"""
-from django.utils.maxlength import LegacyMaxlength
-
-class SubfieldBase(LegacyMaxlength):
+class SubfieldBase(type):
"""
A metaclass for custom Field subclasses. This ensures the model's attribute
has the descriptor protocol attached to it.
@@ -50,4 +48,3 @@ def make_contrib(func=None):
setattr(cls, self.name, Creator(self))
return contribute_to_class
-
diff --git a/django/db/models/query.py b/django/db/models/query.py
index f0a0cf8218..5b24195a7d 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1,4 +1,3 @@
-import warnings
try:
set
except NameError:
@@ -757,22 +756,6 @@ class EmptyQuerySet(QuerySet):
yield iter([]).next()
-# QOperator, QNot, QAnd and QOr are temporarily retained for backwards
-# compatibility. All the old functionality is now part of the 'Q' class.
-class QOperator(Q):
- def __init__(self, *args, **kwargs):
- warnings.warn('Use Q instead of QOr, QAnd or QOperation.',
- DeprecationWarning, stacklevel=2)
- super(QOperator, self).__init__(*args, **kwargs)
-
-QOr = QAnd = QOperator
-
-
-def QNot(q):
- warnings.warn('Use ~q instead of QNot(q)', DeprecationWarning, stacklevel=2)
- return ~q
-
-
def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
requested=None):
"""
@@ -785,7 +768,11 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
restricted = requested is not None
index_end = index_start + len(klass._meta.fields)
- obj = klass(*row[index_start:index_end])
+ fields = row[index_start:index_end]
+ if not [x for x in fields if x is not None]:
+ # If we only have a list of Nones, there was not related object.
+ return None, index_end
+ obj = klass(*fields)
for f in klass._meta.fields:
if not select_related_descend(f, restricted, requested):
continue
@@ -831,9 +818,15 @@ def delete_objects(seen_objs):
del_query.delete_batch_related(pk_list)
update_query = sql.UpdateQuery(cls, connection)
- for field in cls._meta.fields:
- if field.rel and field.null and field.rel.to in seen_objs:
- update_query.clear_related(field, pk_list)
+ for field, model in cls._meta.get_fields_with_model():
+ if (field.rel and field.null and field.rel.to in seen_objs and
+ filter(lambda f: f.column == field.column,
+ field.rel.to._meta.fields)):
+ if model:
+ sql.UpdateQuery(model, connection).clear_related(field,
+ pk_list)
+ else:
+ update_query.clear_related(field, pk_list)
# Now delete the actual data.
for cls in ordered_classes:
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index ef69d7657f..18dd8cc3f2 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -7,7 +7,6 @@ databases). The abstraction barrier only works one way: this module has to know
all about the internals of models in order to get the information it needs.
"""
-import datetime
from copy import deepcopy
from django.utils.tree import Node
@@ -107,6 +106,8 @@ class Query(object):
Pickling support.
"""
obj_dict = self.__dict__.copy()
+ obj_dict['related_select_fields'] = []
+ obj_dict['related_select_cols'] = []
del obj_dict['connection']
return obj_dict
@@ -196,14 +197,18 @@ class Query(object):
Returns an iterator over the results from executing this query.
"""
resolve_columns = hasattr(self, 'resolve_columns')
- if resolve_columns:
- if self.select_fields:
- fields = self.select_fields + self.related_select_fields
- else:
- fields = self.model._meta.fields
+ fields = None
for rows in self.execute_sql(MULTI):
for row in rows:
if resolve_columns:
+ if fields is None:
+ # We only set this up here because
+ # related_select_fields isn't populated until
+ # execute_sql() has been called.
+ if self.select_fields:
+ fields = self.select_fields + self.related_select_fields
+ else:
+ fields = self.model._meta.fields
row = self.resolve_columns(row, fields)
yield row
@@ -1088,20 +1093,23 @@ class Query(object):
join_it = iter(join_list)
table_it = iter(self.tables)
join_it.next(), table_it.next()
+ table_promote = False
for join in join_it:
table = table_it.next()
if join == table and self.alias_refcount[join] > 1:
continue
- self.promote_alias(join)
+ join_promote = self.promote_alias(join)
if table != join:
- self.promote_alias(table)
+ table_promote = self.promote_alias(table)
break
for join in join_it:
- self.promote_alias(join)
+ if self.promote_alias(join, join_promote):
+ join_promote = True
for table in table_it:
# Some of these will have been promoted from the join_list, but
# that's harmless.
- self.promote_alias(table)
+ if self.promote_alias(table, table_promote):
+ table_promote = True
self.where.add((alias, col, field, lookup_type, value), connector)
@@ -1214,7 +1222,6 @@ class Query(object):
raise MultiJoin(pos + 1)
if model:
# The field lives on a base class of the current model.
- alias_list = []
for int_model in opts.get_base_chain(model):
lhs_col = opts.parents[int_model].column
dedupe = lhs_col in opts.duplicate_targets
@@ -1618,8 +1625,9 @@ class Query(object):
if self.ordering_aliases:
result = order_modified_iter(cursor, len(self.ordering_aliases),
self.connection.features.empty_fetchmany_value)
- result = iter((lambda: cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)),
- self.connection.features.empty_fetchmany_value)
+ else:
+ result = iter((lambda: cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)),
+ self.connection.features.empty_fetchmany_value)
if not self.connection.features.can_use_chunked_reads:
# If we are using non-chunked reads, we return the same data
# structure as normally, but ensure it is all read into memory
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 18e4bf2f7e..662d99a4a2 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -35,20 +35,30 @@ class WhereNode(tree.Node):
storing any reference to field objects). Otherwise, the 'data' is
stored unchanged and can be anything with an 'as_sql()' method.
"""
+ # Because of circular imports, we need to import this here.
+ from django.db.models.base import ObjectDoesNotExist
+
if not isinstance(data, (list, tuple)):
super(WhereNode, self).add(data, connector)
return
alias, col, field, lookup_type, value = data
- if field:
- params = field.get_db_prep_lookup(lookup_type, value)
- db_type = field.db_type()
- else:
- # This is possible when we add a comparison to NULL sometimes (we
- # don't really need to waste time looking up the associated field
- # object).
- params = Field().get_db_prep_lookup(lookup_type, value)
- db_type = None
+ try:
+ if field:
+ params = field.get_db_prep_lookup(lookup_type, value)
+ db_type = field.db_type()
+ else:
+ # This is possible when we add a comparison to NULL sometimes
+ # (we don't really need to waste time looking up the associated
+ # field object).
+ params = Field().get_db_prep_lookup(lookup_type, value)
+ db_type = None
+ except ObjectDoesNotExist:
+ # This can happen when trying to insert a reference to a null pk.
+ # We break out of the normal path and indicate there's nothing to
+ # match.
+ super(WhereNode, self).add(NothingNode(), connector)
+ return
if isinstance(value, datetime.datetime):
annotation = datetime.datetime
else:
@@ -190,3 +200,14 @@ class EverythingNode(object):
def relabel_aliases(self, change_map, node=None):
return
+
+class NothingNode(object):
+ """
+ A node that matches nothing.
+ """
+ def as_sql(self, qn=None):
+ raise EmptyResultSet
+
+ def relabel_aliases(self, change_map, node=None):
+ return
+