Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merged the queryset-refactor branch into trunk.

This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.

Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658


git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9c52d56f6f8a9cdafb231adf9f4110473099c9b5 1 parent c91a30f
@malcolmt malcolmt authored
Showing with 5,722 additions and 1,744 deletions.
  1. +7 −11 django/contrib/admin/views/main.py
  2. +5 −0 django/contrib/contenttypes/generic.py
  3. +5 −0 django/core/exceptions.py
  4. +9 −15 django/core/management/sql.py
  5. +18 −12 django/core/management/validation.py
  6. +1 −1  django/core/serializers/base.py
  7. +9 −5 django/db/__init__.py
  8. +33 −6 django/db/backends/__init__.py
  9. +5 −0 django/db/backends/mysql/base.py
  10. +5 −0 django/db/backends/mysql_old/base.py
  11. +37 −237 django/db/backends/oracle/base.py
  12. +151 −0 django/db/backends/oracle/query.py
  13. +3 −0  django/db/backends/postgresql/operations.py
  14. +3 −0  django/db/backends/sqlite3/base.py
  15. +238 −143 django/db/models/base.py
  16. +30 −13 django/db/models/fields/__init__.py
  17. +16 −0 django/db/models/fields/proxy.py
  18. +111 −144 django/db/models/fields/related.py
  19. +34 −6 django/db/models/manager.py
  20. +273 −45 django/db/models/options.py
  21. +471 −940 django/db/models/query.py
  22. +50 −0 django/db/models/query_utils.py
  23. +7 −0 django/db/models/sql/__init__.py
  24. +36 −0 django/db/models/sql/constants.py
  25. +103 −0 django/db/models/sql/datastructures.py
  26. +1,504 −0 django/db/models/sql/query.py
  27. +367 −0 django/db/models/sql/subqueries.py
  28. +171 −0 django/db/models/sql/where.py
  29. +134 −0 django/utils/tree.py
  30. +373 −65 docs/db-api.txt
  31. +270 −6 docs/model-api.txt
  32. +3 −5 tests/modeltests/basic/models.py
  33. +3 −3 tests/modeltests/custom_columns/models.py
  34. +3 −2 tests/modeltests/field_subclassing/models.py
  35. +29 −3 tests/modeltests/lookup/models.py
  36. +5 −0 tests/modeltests/many_to_many/models.py
  37. +11 −6 tests/modeltests/many_to_one/models.py
  38. +5 −0 tests/modeltests/many_to_one_null/models.py
  39. +226 −11 tests/modeltests/model_inheritance/models.py
  40. +34 −9 tests/modeltests/one_to_one/models.py
  41. +16 −5 tests/modeltests/or_lookups/models.py
  42. 0  tests/modeltests/order_with_respect_to/__init__.py
  43. +78 −0 tests/modeltests/order_with_respect_to/models.py
  44. +13 −0 tests/modeltests/ordering/models.py
  45. +0 −2  tests/modeltests/reserved_names/models.py
  46. +1 −1  tests/modeltests/reverse_lookup/models.py
  47. +42 −5 tests/modeltests/select_related/models.py
  48. +15 −14 tests/modeltests/serializers/models.py
  49. +2 −1  tests/modeltests/signals/models.py
  50. +1 −1  tests/modeltests/transactions/models.py
  51. 0  tests/modeltests/update/__init__.py
  52. +67 −0 tests/modeltests/update/models.py
  53. +10 −5 tests/regressiontests/null_queries/models.py
  54. 0  tests/regressiontests/queries/__init__.py
  55. +658 −0 tests/regressiontests/queries/models.py
  56. +13 −14 tests/regressiontests/serializers_regress/models.py
  57. +8 −8 tests/regressiontests/serializers_regress/tests.py
View
18 django/contrib/admin/views/main.py
@@ -8,7 +8,7 @@
from django.core.paginator import QuerySetPaginator, InvalidPage
from django.shortcuts import get_object_or_404, render_to_response
from django.db import models
-from django.db.models.query import handle_legacy_orderlist, QuerySet
+from django.db.models.query import QuerySet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.utils.html import escape
from django.utils.text import capfirst, get_text_list
@@ -627,7 +627,7 @@ def get_results(self, request):
# Perform a slight optimization: Check to see whether any filters were
# given. If not, use paginator.hits to calculate the number of objects,
# because we've already done paginator.hits and the value is cached.
- if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs:
+ if not self.query_set.query.where:
full_result_count = result_count
else:
full_result_count = self.manager.count()
@@ -653,15 +653,12 @@ def get_results(self, request):
def get_ordering(self):
lookup_opts, params = self.lookup_opts, self.params
- # For ordering, first check the "ordering" parameter in the admin options,
- # then check the object's default ordering. If neither of those exist,
- # order descending by ID by default. Finally, look for manually-specified
- # ordering from the query string.
+ # For ordering, first check the "ordering" parameter in the admin
+ # options, then check the object's default ordering. If neither of
+ # those exist, order descending by ID by default. Finally, look for
+ # manually-specified ordering from the query string.
ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
- # Normalize it to new-style ordering.
- ordering = handle_legacy_orderlist(ordering)
-
if ordering[0].startswith('-'):
order_field, order_type = ordering[0][1:], 'desc'
else:
@@ -753,8 +750,7 @@ def construct_search(field_name):
for bit in self.query.split():
or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields]
other_qs = QuerySet(self.model)
- if qs._select_related:
- other_qs = other_qs.select_related()
+ other_qs.dup_select_related(qs)
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
qs = qs & other_qs
View
5 django/contrib/contenttypes/generic.py
@@ -154,6 +154,11 @@ def set_attributes_from_rel(self):
def get_internal_type(self):
return "ManyToManyField"
+ def db_type(self):
+ # Since we're simulating a ManyToManyField, in effect, best return the
+ # same db_type as well.
+ return None
+
class ReverseGenericRelatedObjectsDescriptor(object):
"""
This class provides the functionality that makes the related-object
View
5 django/core/exceptions.py
@@ -27,3 +27,8 @@ class MiddlewareNotUsed(Exception):
class ImproperlyConfigured(Exception):
"Django is somehow improperly configured"
pass
+
+class FieldError(Exception):
+ """Some kind of problem with a model field."""
+ pass
+
View
24 django/core/management/sql.py
@@ -26,7 +26,7 @@ def django_table_list(only_existing=False):
for app in models.get_apps():
for model in models.get_models(app):
tables.append(model._meta.db_table)
- tables.extend([f.m2m_db_table() for f in model._meta.many_to_many])
+ tables.extend([f.m2m_db_table() for f in model._meta.local_many_to_many])
if only_existing:
existing = table_list()
tables = [t for t in tables if t in existing]
@@ -54,12 +54,12 @@ def sequence_list():
for app in apps:
for model in models.get_models(app):
- for f in model._meta.fields:
+ for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
sequence_list.append({'table': model._meta.db_table, 'column': f.column})
break # Only one AutoField is allowed per model, so don't bother continuing.
- for f in model._meta.many_to_many:
+ for f in model._meta.local_many_to_many:
sequence_list.append({'table': f.m2m_db_table(), 'column': None})
return sequence_list
@@ -149,7 +149,7 @@ def sql_delete(app, style):
if cursor and table_name_converter(model._meta.db_table) in table_names:
# The table exists, so it needs to be dropped
opts = model._meta
- for f in opts.fields:
+ for f in opts.local_fields:
if f.rel and f.rel.to not in to_delete:
references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
@@ -181,7 +181,7 @@ def sql_delete(app, style):
# Output DROP TABLE statements for many-to-many tables.
for model in app_models:
opts = model._meta
- for f in opts.many_to_many:
+ for f in opts.local_many_to_many:
if isinstance(f.rel, generic.GenericRel):
continue
if cursor and table_name_converter(f.m2m_db_table()) in table_names:
@@ -258,7 +258,7 @@ def sql_model_create(model, style, known_models=set()):
pending_references = {}
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
- for f in opts.fields:
+ for f in opts.local_fields:
col_type = f.db_type()
tablespace = f.db_tablespace or opts.db_tablespace
if col_type is None:
@@ -294,14 +294,8 @@ def sql_model_create(model, style, known_models=set()):
style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
style.SQL_KEYWORD('NULL'))
for field_constraints in opts.unique_together:
- constraint_output = [style.SQL_KEYWORD('UNIQUE')]
- constraint_output.append('(%s)' % \
+ table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
- if opts.db_tablespace and connection.features.supports_tablespaces \
- and connection.features.autoindexes_primary_keys:
- constraint_output.append(connection.ops.tablespace_sql(
- opts.db_tablespace, inline=True))
- table_output.append(' '.join(constraint_output))
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas.
@@ -359,7 +353,7 @@ def many_to_many_sql_for_model(model, style):
final_output = []
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
- for f in opts.many_to_many:
+ for f in opts.local_many_to_many:
if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys:
@@ -466,7 +460,7 @@ def sql_indexes_for_model(model, style):
output = []
qn = connection.ops.quote_name
- for f in model._meta.fields:
+ for f in model._meta.local_fields:
if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
unique = f.unique and 'UNIQUE ' or ''
tablespace = f.db_tablespace or model._meta.db_tablespace
View
30 django/core/management/validation.py
@@ -32,7 +32,7 @@ def get_validation_errors(outfile, app=None):
opts = cls._meta
# Do field-specific validation.
- for f in opts.fields:
+ for f in opts.local_fields:
if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
if f.name.endswith('_'):
@@ -69,8 +69,8 @@ def get_validation_errors(outfile, app=None):
if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.max_length > 255:
e.add(opts, '"%s": %s cannot have a "max_length" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]])))
- # Check to see if the related field will clash with any
- # existing fields, m2m fields, m2m related objects or related objects
+ # Check to see if the related field will clash with any existing
+ # fields, m2m fields, m2m related objects or related objects
if f.rel:
if f.rel.to not in models.get_models():
e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, f.rel.to))
@@ -87,7 +87,7 @@ def get_validation_errors(outfile, app=None):
e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- for r in rel_opts.many_to_many:
+ for r in rel_opts.local_many_to_many:
if r.name == rel_name:
e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
@@ -104,9 +104,10 @@ def get_validation_errors(outfile, app=None):
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
- for i, f in enumerate(opts.many_to_many):
+ for i, f in enumerate(opts.local_many_to_many):
# Check to see if the related m2m field will clash with any
- # existing fields, m2m fields, m2m related objects or related objects
+ # existing fields, m2m fields, m2m related objects or related
+ # objects
if f.rel.to not in models.get_models():
e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, f.rel.to))
# it is a string and we could not find the model it refers to
@@ -117,17 +118,17 @@ def get_validation_errors(outfile, app=None):
rel_opts = f.rel.to._meta
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
- # If rel_name is none, there is no reverse accessor.
- # (This only occurs for symmetrical m2m relations to self).
- # If this is the case, there are no clashes to check for this field, as
- # there are no reverse descriptors for this field.
+ # If rel_name is none, there is no reverse accessor (this only
+ # occurs for symmetrical m2m relations to self). If this is the
+ # case, there are no clashes to check for this field, as there are
+ # no reverse descriptors for this field.
if rel_name is not None:
for r in rel_opts.fields:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- for r in rel_opts.many_to_many:
+ for r in rel_opts.local_many_to_many:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
@@ -200,7 +201,10 @@ def get_validation_errors(outfile, app=None):
field_name = field_name[1:]
if opts.order_with_respect_to and field_name == '_order':
continue
- if '.' in field_name: continue # Skip ordering in the format 'table.field'.
+ # Skip ordering in the format field1__field2 (FIXME: checking
+ # this format would be nice, but it's a little fiddly).
+ if '_' in field_name:
+ continue
try:
opts.get_field(field_name, many_to_many=False)
except models.FieldDoesNotExist:
@@ -228,5 +232,7 @@ def get_validation_errors(outfile, app=None):
else:
if isinstance(f.rel, models.ManyToManyRel):
e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
+ if f not in opts.local_fields:
+ e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
return len(e.errors)
View
2  django/core/serializers/base.py
@@ -165,7 +165,7 @@ def save(self, save_m2m=True):
# This ensures that the data that is deserialized is literally
# what came from the file, not post-processed by pre_save/save
# methods.
- models.Model.save(self.object, raw=True)
+ models.Model.save_base(self.object, raw=True)
if self.m2m_data and save_m2m:
for accessor_name, object_list in self.m2m_data.items():
setattr(self.object, accessor_name, object_list)
View
14 django/db/__init__.py
@@ -11,16 +11,18 @@
settings.DATABASE_ENGINE = 'dummy'
try:
- # Most of the time, the database backend will be one of the official
+ # Most of the time, the database backend will be one of the official
# backends that ships with Django, so look there first.
_import_path = 'django.db.backends.'
backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
+ creation = __import__('%s%s.creation' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
except ImportError, e:
- # If the import failed, we might be looking for a database backend
+ # If the import failed, we might be looking for a database backend
# distributed external to Django. So we'll try that next.
try:
_import_path = ''
backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
+ creation = __import__('%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
except ImportError, e_user:
# The database backend wasn't found. Display a helpful error message
# listing all possible (built-in) database backends.
@@ -37,10 +39,12 @@ def _import_database_module(import_path='', module_name=''):
"""Lazily import a database module when requested."""
return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
-# We don't want to import the introspect/creation modules unless
-# someone asks for 'em, so lazily load them on demmand.
+# We don't want to import the introspect module unless someone asks for it, so
+# lazily load it on demmand.
get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
-get_creation_module = curry(_import_database_module, _import_path, 'creation')
+
+def get_creation_module():
+ return creation
# We want runshell() to work the same way, but we have to treat it a
# little differently (since it just runs instead of returning a module like
View
39 django/db/backends/__init__.py
@@ -49,7 +49,8 @@ class BaseDatabaseFeatures(object):
supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
- uses_custom_queryset = False
+ uses_custom_query_class = False
+ empty_fetchmany_value = []
class BaseDatabaseOperations(object):
"""
@@ -86,10 +87,9 @@ def datetime_cast_sql(self):
Returns the SQL necessary to cast a datetime value so that it will be
retrieved as a Python datetime object instead of a string.
- This SQL should include a '%s' in place of the field's name. This
- method should return None if no casting is necessary.
+ This SQL should include a '%s' in place of the field's name.
"""
- return None
+ return "%s"
def deferrable_sql(self):
"""
@@ -169,6 +169,14 @@ def limit_offset_sql(self, limit, offset=None):
sql += " OFFSET %s" % offset
return sql
+ def lookup_cast(self, lookup_type):
+ """
+ Returns the string to use in a query when performing lookups
+ ("contains", "like", etc). The resulting string should contain a '%s'
+ placeholder for the column being searched against.
+ """
+ return "%s"
+
def max_name_length(self):
"""
Returns the maximum length of table and column names, or None if there
@@ -176,6 +184,14 @@ def max_name_length(self):
"""
return None
+ def no_limit_value(self):
+ """
+ Returns the value to use for the LIMIT when we are wanting "LIMIT
+ infinity". Returns None if the limit clause can be omitted in this case.
+ """
+ # FIXME: API may need to change once Oracle backend is repaired.
+ raise NotImplementedError()
+
def pk_default_value(self):
"""
Returns the value to use during an INSERT statement to specify that
@@ -183,11 +199,11 @@ def pk_default_value(self):
"""
return 'DEFAULT'
- def query_set_class(self, DefaultQuerySet):
+ def query_class(self, DefaultQueryClass):
"""
Given the default QuerySet class, returns a custom QuerySet class
to use for this backend. Returns None if a custom QuerySet isn't used.
- See also BaseDatabaseFeatures.uses_custom_queryset, which regulates
+ See also BaseDatabaseFeatures.uses_custom_query_class, which regulates
whether this method is called at all.
"""
return None
@@ -205,6 +221,17 @@ def random_function_sql(self):
"""
return 'RANDOM()'
+ def regex_lookup(self, lookup_type):
+ """
+ Returns the string to use in a query when performing regular expression
+ lookups (using "regex" or "iregex"). The resulting string should
+ contain a '%s' placeholder for the column being searched against.
+
+ If the feature is not supported (or part of it is not supported), a
+ NotImplementedError exception can be raised.
+ """
+ raise NotImplementedError
+
def sql_flush(self, style, tables, sequences):
"""
Returns a list of SQL statements required to remove all data from
View
5 django/db/backends/mysql/base.py
@@ -62,6 +62,7 @@
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
+ empty_fetchmany_value = ()
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
@@ -94,6 +95,10 @@ def limit_offset_sql(self, limit, offset=None):
sql += "%s," % offset
return sql + str(limit)
+ def no_limit_value(self):
+ # 2**64 - 1, as recommended by the MySQL documentation
+ return 18446744073709551615L
+
def quote_name(self, name):
if name.startswith("`") and name.endswith("`"):
return name # Quoting once is enough.
View
5 django/db/backends/mysql_old/base.py
@@ -66,6 +66,7 @@ def __getattr__(self, attr):
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
+ empty_fetchmany_value = ()
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
@@ -98,6 +99,10 @@ def limit_offset_sql(self, limit, offset=None):
sql += "%s," % offset
return sql + str(limit)
+ def no_limit_value(self):
+ # 2**64 - 1, as recommended by the MySQL documentation
+ return 18446744073709551615L
+
def quote_name(self, name):
if name.startswith("`") and name.endswith("`"):
return name # Quoting once is enough.
View
274 django/db/backends/oracle/base.py
@@ -4,11 +4,12 @@
Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
"""
+import os
+
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
-import datetime
-import os
# Oracle takes client-side character set encoding from the environment.
os.environ['NLS_LANG'] = '.UTF8'
@@ -24,11 +25,12 @@
class DatabaseFeatures(BaseDatabaseFeatures):
allows_group_by_ordinal = False
allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259)
+ empty_fetchmany_value = ()
needs_datetime_string_cast = False
needs_upper_for_iops = True
supports_tablespaces = True
uses_case_insensitive_names = True
- uses_custom_queryset = True
+ uses_custom_query_class = True
class DatabaseOperations(BaseDatabaseOperations):
def autoinc_sql(self, table, column):
@@ -89,243 +91,16 @@ def limit_offset_sql(self, limit, offset=None):
# Instead, they are handled in django/db/backends/oracle/query.py.
return ""
+ def lookup_cast(self, lookup_type):
+ if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
+ return "UPPER(%s)"
+ return "%s"
+
def max_name_length(self):
return 30
- def query_set_class(self, DefaultQuerySet):
- from django.db import connection
- from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
-
- class OracleQuerySet(DefaultQuerySet):
-
- def iterator(self):
- "Performs the SELECT database lookup of this QuerySet."
-
- from django.db.models.query import get_cached_row
-
- # self._select is a dictionary, and dictionaries' key order is
- # undefined, so we convert it to a list of tuples.
- extra_select = self._select.items()
-
- full_query = None
-
- try:
- try:
- select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
- except TypeError:
- select, sql, params = self._get_sql_clause()
- except EmptyResultSet:
- raise StopIteration
- if not full_query:
- full_query = "SELECT %s%s\n%s" % ((self._distinct and "DISTINCT " or ""), ', '.join(select), sql)
-
- cursor = connection.cursor()
- cursor.execute(full_query, params)
-
- fill_cache = self._select_related
- fields = self.model._meta.fields
- index_end = len(fields)
-
- # so here's the logic;
- # 1. retrieve each row in turn
- # 2. convert NCLOBs
-
- while 1:
- rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
- if not rows:
- raise StopIteration
- for row in rows:
- row = self.resolve_columns(row, fields)
- if fill_cache:
- obj, index_end = get_cached_row(klass=self.model, row=row,
- index_start=0, max_depth=self._max_related_depth)
- else:
- obj = self.model(*row[:index_end])
- for i, k in enumerate(extra_select):
- setattr(obj, k[0], row[index_end+i])
- yield obj
-
-
- def _get_sql_clause(self, get_full_query=False):
- from django.db.models.query import fill_table_cache, \
- handle_legacy_orderlist, orderfield2column
-
- opts = self.model._meta
- qn = connection.ops.quote_name
-
- # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
- select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
- tables = [quote_only_if_word(t) for t in self._tables]
- joins = SortedDict()
- where = self._where[:]
- params = self._params[:]
-
- # Convert self._filters into SQL.
- joins2, where2, params2 = self._filters.get_sql(opts)
- joins.update(joins2)
- where.extend(where2)
- params.extend(params2)
-
- # Add additional tables and WHERE clauses based on select_related.
- if self._select_related:
- fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
-
- # Add any additional SELECTs.
- if self._select:
- select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])
-
- # Start composing the body of the SQL statement.
- sql = [" FROM", qn(opts.db_table)]
-
- # Compose the join dictionary into SQL describing the joins.
- if joins:
- sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
- for (alias, (table, join_type, condition)) in joins.items()]))
-
- # Compose the tables clause into SQL.
- if tables:
- sql.append(", " + ", ".join(tables))
-
- # Compose the where clause into SQL.
- if where:
- sql.append(where and "WHERE " + " AND ".join(where))
-
- # ORDER BY clause
- order_by = []
- if self._order_by is not None:
- ordering_to_use = self._order_by
- else:
- ordering_to_use = opts.ordering
- for f in handle_legacy_orderlist(ordering_to_use):
- if f == '?': # Special case.
- order_by.append(DatabaseOperations().random_function_sql())
- else:
- if f.startswith('-'):
- col_name = f[1:]
- order = "DESC"
- else:
- col_name = f
- order = "ASC"
- if "." in col_name:
- table_prefix, col_name = col_name.split('.', 1)
- table_prefix = qn(table_prefix) + '.'
- else:
- # Use the database table as a column prefix if it wasn't given,
- # and if the requested column isn't a custom SELECT.
- if "." not in col_name and col_name not in (self._select or ()):
- table_prefix = qn(opts.db_table) + '.'
- else:
- table_prefix = ''
- order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))
- if order_by:
- sql.append("ORDER BY " + ", ".join(order_by))
-
- # Look for column name collisions in the select elements
- # and fix them with an AS alias. This allows us to do a
- # SELECT * later in the paging query.
- cols = [clause.split('.')[-1] for clause in select]
- for index, col in enumerate(cols):
- if cols.count(col) > 1:
- col = '%s%d' % (col.replace('"', ''), index)
- cols[index] = col
- select[index] = '%s AS %s' % (select[index], col)
-
- # LIMIT and OFFSET clauses
- # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
- select_clause = ",".join(select)
- distinct = (self._distinct and "DISTINCT " or "")
-
- if order_by:
- order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
- else:
- #Oracle's row_number() function always requires an order-by clause.
- #So we need to define a default order-by, since none was provided.
- order_by_clause = " OVER (ORDER BY %s.%s)" % \
- (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
- # limit_and_offset_clause
- if self._limit is None:
- assert self._offset is None, "'offset' is not allowed without 'limit'"
-
- if self._offset is not None:
- offset = int(self._offset)
- else:
- offset = 0
- if self._limit is not None:
- limit = int(self._limit)
- else:
- limit = None
-
- limit_and_offset_clause = ''
- if limit is not None:
- limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
- elif offset:
- limit_and_offset_clause = "WHERE rn > %s" % (offset)
-
- if len(limit_and_offset_clause) > 0:
- fmt = \
- """SELECT * FROM
- (SELECT %s%s,
- ROW_NUMBER()%s AS rn
- %s)
- %s"""
- full_query = fmt % (distinct, select_clause,
- order_by_clause, ' '.join(sql).strip(),
- limit_and_offset_clause)
- else:
- full_query = None
-
- if get_full_query:
- return select, " ".join(sql), params, full_query
- else:
- return select, " ".join(sql), params
-
- def resolve_columns(self, row, fields=()):
- from django.db.models.fields import DateField, DateTimeField, \
- TimeField, BooleanField, NullBooleanField, DecimalField, Field
- values = []
- for value, field in map(None, row, fields):
- if isinstance(value, Database.LOB):
- value = value.read()
- # Oracle stores empty strings as null. We need to undo this in
- # order to adhere to the Django convention of using the empty
- # string instead of null, but only if the field accepts the
- # empty string.
- if value is None and isinstance(field, Field) and field.empty_strings_allowed:
- value = u''
- # Convert 1 or 0 to True or False
- elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
- value = bool(value)
- # Convert floats to decimals
- elif value is not None and isinstance(field, DecimalField):
- value = util.typecast_decimal(field.format_number(value))
- # cx_Oracle always returns datetime.datetime objects for
- # DATE and TIMESTAMP columns, but Django wants to see a
- # python datetime.date, .time, or .datetime. We use the type
- # of the Field to determine which to cast to, but it's not
- # always available.
- # As a workaround, we cast to date if all the time-related
- # values are 0, or to time if the date is 1/1/1900.
- # This could be cleaned a bit by adding a method to the Field
- # classes to normalize values from the database (the to_python
- # method is used for validation and isn't what we want here).
- elif isinstance(value, Database.Timestamp):
- # In Python 2.3, the cx_Oracle driver returns its own
- # Timestamp object that we must convert to a datetime class.
- if not isinstance(value, datetime.datetime):
- value = datetime.datetime(value.year, value.month, value.day, value.hour,
- value.minute, value.second, value.fsecond)
- if isinstance(field, DateTimeField):
- pass # DateTimeField subclasses DateField so must be checked first.
- elif isinstance(field, DateField):
- value = value.date()
- elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
- value = value.time()
- elif value.hour == value.minute == value.second == value.microsecond == 0:
- value = value.date()
- values.append(value)
- return values
-
- return OracleQuerySet
+ def query_class(self, DefaultQueryClass):
+ return query.query_class(DefaultQueryClass, Database)
def quote_name(self, name):
# SQL92 requires delimited (quoted) names to be case-sensitive. When
@@ -339,6 +114,23 @@ def quote_name(self, name):
def random_function_sql(self):
return "DBMS_RANDOM.RANDOM"
+ def regex_lookup_9(self, lookup_type):
+ raise NotImplementedError("Regexes are not supported in Oracle before version 10g.")
+
+ def regex_lookup_10(self, lookup_type):
+ if lookup_type == 'regex':
+ match_option = "'c'"
+ else:
+ match_option = "'i'"
+ return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
+
+ def regex_lookup(self, lookup_type):
+ # If regex_lookup is called before it's been initialized, then create
+ # a cursor to initialize it and recur.
+ from django.db import connection
+ connection.cursor()
+ return connection.ops.regex_lookup(lookup_type)
+
def sql_flush(self, style, tables, sequences):
# Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
# 'TRUNCATE z;'... style SQL statements
@@ -430,6 +222,14 @@ def _cursor(self, settings):
"NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
try:
self.oracle_version = int(self.connection.version.split('.')[0])
+ # There's no way for the DatabaseOperations class to know the
+ # currently active Oracle version, so we do some setups here.
+ # TODO: Multi-db support will need a better solution (a way to
+ # communicate the current version).
+ if self.oracle_version <= 9:
+ self.ops.regex_lookup = self.ops.regex_lookup_9
+ else:
+ self.ops.regex_lookup = self.ops.regex_lookup_10
except ValueError:
pass
try:
View
151 django/db/backends/oracle/query.py
@@ -0,0 +1,151 @@
+"""
+Custom Query class for this backend (a derivative of
+django.db.models.sql.query.Query).
+"""
+
+import datetime
+
+from django.db.backends import util
+
+# Cache. Maps default query class to new Oracle query class.
+_classes = {}
+
+def query_class(QueryClass, Database):
+ """
+ Returns a custom djang.db.models.sql.query.Query subclass that is
+ appropraite for Oracle.
+
+ The 'Database' module (cx_Oracle) is passed in here so that all the setup
+ required to import it only needs to be done by the calling module.
+ """
+ global _classes
+ try:
+ return _classes[QueryClass]
+ except KeyError:
+ pass
+
+ class OracleQuery(QueryClass):
+ def resolve_columns(self, row, fields=()):
+ index_start = len(self.extra_select.keys())
+ values = [self.convert_values(v, None) for v in row[:index_start]]
+ for value, field in map(None, row[index_start:], fields):
+ values.append(self.convert_values(value, field))
+ return values
+
+ def convert_values(self, value, field):
+ from django.db.models.fields import DateField, DateTimeField, \
+ TimeField, BooleanField, NullBooleanField, DecimalField, Field
+ if isinstance(value, Database.LOB):
+ value = value.read()
+ # Oracle stores empty strings as null. We need to undo this in
+ # order to adhere to the Django convention of using the empty
+ # string instead of null, but only if the field accepts the
+ # empty string.
+ if value is None and isinstance(field, Field) and field.empty_strings_allowed:
+ value = u''
+ # Convert 1 or 0 to True or False
+ elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
+ value = bool(value)
+ # Convert floats to decimals
+ elif value is not None and isinstance(field, DecimalField):
+ value = util.typecast_decimal(field.format_number(value))
+ # cx_Oracle always returns datetime.datetime objects for
+ # DATE and TIMESTAMP columns, but Django wants to see a
+ # python datetime.date, .time, or .datetime. We use the type
+ # of the Field to determine which to cast to, but it's not
+ # always available.
+ # As a workaround, we cast to date if all the time-related
+ # values are 0, or to time if the date is 1/1/1900.
+ # This could be cleaned a bit by adding a method to the Field
+ # classes to normalize values from the database (the to_python
+ # method is used for validation and isn't what we want here).
+ elif isinstance(value, Database.Timestamp):
+ # In Python 2.3, the cx_Oracle driver returns its own
+ # Timestamp object that we must convert to a datetime class.
+ if not isinstance(value, datetime.datetime):
+ value = datetime.datetime(value.year, value.month,
+ value.day, value.hour, value.minute, value.second,
+ value.fsecond)
+ if isinstance(field, DateTimeField):
+ # DateTimeField subclasses DateField so must be checked
+ # first.
+ pass
+ elif isinstance(field, DateField):
+ value = value.date()
+ elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
+ value = value.time()
+ elif value.hour == value.minute == value.second == value.microsecond == 0:
+ value = value.date()
+ return value
+
+ def as_sql(self, with_limits=True, with_col_aliases=False):
+ """
+ Creates the SQL for this query. Returns the SQL string and list
+ of parameters. This is overriden from the original Query class
+ to accommodate Oracle's limit/offset SQL.
+
+ 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)
+
+ # If no offsets, just return the result of the base class
+ # `as_sql`.
+ if not do_offset:
+ return super(OracleQuery, self).as_sql(with_limits=False,
+ with_col_aliases=with_col_aliases)
+
+ # `get_columns` needs to be called before `get_ordering` to
+ # populate `_select_alias`.
+ self.pre_sql_setup()
+ out_cols = self.get_columns()
+ ordering = self.get_ordering()
+
+ # Getting the "ORDER BY" SQL for the ROW_NUMBER() result.
+ if ordering:
+ rn_orderby = ', '.join(ordering)
+ else:
+ # Oracle's ROW_NUMBER() function always requires an
+ # order-by clause. So we need to define a default
+ # order-by, since none was provided.
+ qn = self.quote_name_unless_alias
+ opts = self.model._meta
+ rn_orderby = '%s.%s' % (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
+
+ # 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,
+ with_col_aliases=True)
+
+ # Constructing the result SQL, using the initial select SQL
+ # obtained above.
+ result = ['SELECT * FROM (%s)' % sql]
+
+ # Place WHERE condition on `rn` for the desired range.
+ result.append('WHERE rn > %d' % self.low_mark)
+ if self.high_mark:
+ result.append('AND rn <= %d' % self.high_mark)
+
+ # Returning the SQL w/params.
+ return ' '.join(result), params
+
+ def set_limits(self, low=None, high=None):
+ super(OracleQuery, self).set_limits(low, high)
+
+ # We need to select the row number for the LIMIT/OFFSET sql.
+ # A placeholder is added to extra_select now, because as_sql is
+ # too late to be modifying extra_select. However, the actual sql
+ # depends on the ordering, so that is generated in as_sql.
+ self.extra_select['rn'] = '1'
+
+ def clear_limits(self):
+ super(OracleQuery, self).clear_limits()
+ if 'rn' in self.extra_select:
+ del self.extra_select['rn']
+
+ _classes[QueryClass] = OracleQuery
+ return OracleQuery
+
View
3  django/db/backends/postgresql/operations.py
@@ -44,6 +44,9 @@ def last_insert_id(self, cursor, table_name, pk_name):
cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
return cursor.fetchone()[0]
+ def no_limit_value(self):
+ return None
+
def quote_name(self, name):
if name.startswith('"') and name.endswith('"'):
return name # Quoting once is enough.
View
3  django/db/backends/sqlite3/base.py
@@ -63,6 +63,9 @@ def quote_name(self, name):
return name # Quoting once is enough.
return '"%s"' % name
+ def no_limit_value(self):
+ return -1
+
def sql_flush(self, style, tables, sequences):
# NB: The generated SQL below is specific to SQLite
# Note: The DELETE FROM... SQL generated below works for SQLite databases
View
381 django/db/models/base.py
@@ -1,10 +1,16 @@
-import django.db.models.manipulators
-import django.db.models.manager
+import copy
+import types
+import sys
+import os
+from itertools import izip
+
+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
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
-from django.db.models.fields.related import OneToOneRel, ManyToOneRel
-from django.db.models.query import delete_objects
+from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
+from django.db.models.query import delete_objects, Q
from django.db.models.options import Options, AdminOptions
from django.db import connection, transaction
from django.db.models import signals
@@ -14,10 +20,11 @@
from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode, smart_unicode
from django.conf import settings
-from itertools import izip
-import types
-import sys
-import os
+
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
class ModelBase(type):
"Metaclass for all models"
@@ -25,29 +32,45 @@ def __new__(cls, name, bases, attrs):
# If this isn't a subclass of Model, don't do anything special.
try:
parents = [b for b in bases if issubclass(b, Model)]
- if not parents:
- return super(ModelBase, cls).__new__(cls, name, bases, attrs)
except NameError:
# 'Model' isn't defined yet, meaning we're looking at Django's own
# Model class, defined below.
+ parents = []
+ if not parents:
return super(ModelBase, cls).__new__(cls, name, bases, attrs)
# Create the class.
- new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')})
- new_class.add_to_class('_meta', Options(attrs.pop('Meta', None)))
- new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}))
- new_class.add_to_class('MultipleObjectsReturned',
- types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {}))
-
- # Build complete list of parents
- for base in parents:
- # Things without _meta aren't functional models, so they're
- # uninteresting parents.
- if hasattr(base, '_meta'):
- new_class._meta.parents.append(base)
- new_class._meta.parents.extend(base._meta.parents)
-
-
+ module = attrs.pop('__module__')
+ new_class = type.__new__(cls, name, bases, {'__module__': module})
+ attr_meta = attrs.pop('Meta', None)
+ abstract = getattr(attr_meta, 'abstract', False)
+ if not attr_meta:
+ meta = getattr(new_class, 'Meta', None)
+ else:
+ meta = attr_meta
+ base_meta = getattr(new_class, '_meta', None)
+
+ new_class.add_to_class('_meta', Options(meta))
+ if not abstract:
+ new_class.add_to_class('DoesNotExist',
+ subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
+ new_class.add_to_class('MultipleObjectsReturned',
+ subclass_exception('MultipleObjectsReturned', MultipleObjectsReturned, module))
+ if base_meta and not base_meta.abstract:
+ # Non-abstract child classes inherit some attributes from their
+ # non-abstract parent (unless an ABC comes before it in the
+ # method resolution order).
+ if not hasattr(meta, 'ordering'):
+ new_class._meta.ordering = base_meta.ordering
+ if not hasattr(meta, 'get_latest_by'):
+ new_class._meta.get_latest_by = base_meta.get_latest_by
+
+ old_default_mgr = None
+ if getattr(new_class, '_default_manager', None):
+ # We have a parent who set the default manager.
+ if new_class._default_manager.model._meta.abstract:
+ old_default_mgr = new_class._default_manager
+ new_class._default_manager = None
if getattr(new_class._meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
@@ -63,21 +86,50 @@ def __new__(cls, name, bases, attrs):
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
- # Add Fields inherited from parents
- for parent in new_class._meta.parents:
- for field in parent._meta.fields:
- # Only add parent fields if they aren't defined for this class.
- try:
- new_class._meta.get_field(field.name)
- except FieldDoesNotExist:
- field.contribute_to_class(new_class, field.name)
-
+ # Do the appropriate setup for any model parents.
+ o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
+ if isinstance(f, OneToOneField)])
+ for base in parents:
+ if not hasattr(base, '_meta'):
+ # Things without _meta aren't functional models, so they're
+ # uninteresting parents.
+ continue
+ if not base._meta.abstract:
+ if base in o2o_map:
+ field = o2o_map[base]
+ field.primary_key = True
+ new_class._meta.setup_pk(field)
+ else:
+ attr_name = '%s_ptr' % base._meta.module_name
+ field = OneToOneField(base, name=attr_name,
+ auto_created=True, parent_link=True)
+ new_class.add_to_class(attr_name, field)
+ new_class._meta.parents[base] = field
+ else:
+ # The abstract base class case.
+ names = set([f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many])
+ for field in base._meta.local_fields + base._meta.local_many_to_many:
+ if field.name in names:
+ raise FieldError('Local field %r in class %r clashes with field of similar name from abstract base class %r'
+ % (field.name, name, base.__name__))
+ new_class.add_to_class(field.name, copy.deepcopy(field))
+
+ if abstract:
+ # Abstract base models can't be instantiated and don't appear in
+ # the list of models for an app. We do the final setup for them a
+ # little differently from normal models.
+ attr_meta.abstract = False
+ new_class.Meta = attr_meta
+ return new_class
+
+ if old_default_mgr and not new_class._default_manager:
+ new_class._default_manager = old_default_mgr._copy_to_model(new_class)
new_class._prepare()
-
register_models(new_class._meta.app_label, new_class)
+
# Because of the way imports happen (recursively), we may or may not be
- # the first class for this model to register with the framework. There
- # should only be one class for each model, so we must always return the
+ # the first time this model tries to register with the framework. There
+ # should only be one class for each model, so we always return the
# registered version.
return get_model(new_class._meta.app_label, name, False)
@@ -113,31 +165,6 @@ def _prepare(cls):
class Model(object):
__metaclass__ = ModelBase
- def _get_pk_val(self):
- return getattr(self, self._meta.pk.attname)
-
- def _set_pk_val(self, value):
- return setattr(self, self._meta.pk.attname, value)
-
- pk = property(_get_pk_val, _set_pk_val)
-
- def __repr__(self):
- return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
-
- def __str__(self):
- if hasattr(self, '__unicode__'):
- return force_unicode(self).encode('utf-8')
- return '%s object' % self.__class__.__name__
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __hash__(self):
- return hash(self._get_pk_val())
-
def __init__(self, *args, **kwargs):
dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
@@ -210,72 +237,133 @@ def __init__(self, *args, **kwargs):
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self)
- def save(self, raw=False):
- dispatcher.send(signal=signals.pre_save, sender=self.__class__,
- instance=self, raw=raw)
+ def from_sequence(cls, values):
+ """
+ An alternate class constructor, primarily for internal use.
+
+ Creates a model instance from a sequence of values (which corresponds
+ to all the non-many-to-many fields in creation order. If there are more
+ fields than values, the remaining (final) fields are given their
+ default values.
+
+ ForeignKey fields can only be initialised using id values, not
+ instances, in this method.
+ """
+ dispatcher.send(signal=signals.pre_init, sender=cls, args=values,
+ kwargs={})
+ obj = Empty()
+ obj.__class__ = cls
+ field_iter = iter(obj._meta.fields)
+ for val, field in izip(values, field_iter):
+ setattr(obj, field.attname, val)
+ for field in field_iter:
+ setattr(obj, field.attname, field.get_default())
+ dispatcher.send(signal=signals.post_init, sender=cls, instance=obj)
+ return obj
+
+ from_sequence = classmethod(from_sequence)
+
+ def __repr__(self):
+ return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
+
+ def __str__(self):
+ if hasattr(self, '__unicode__'):
+ return force_unicode(self).encode('utf-8')
+ return '%s object' % self.__class__.__name__
+
+ def __eq__(self, other):
+ return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return hash(self._get_pk_val())
+
+ def _get_pk_val(self, meta=None):
+ if not meta:
+ meta = self._meta
+ return getattr(self, meta.pk.attname)
- non_pks = [f for f in self._meta.fields if not f.primary_key]
- cursor = connection.cursor()
+ def _set_pk_val(self, value):
+ return setattr(self, self._meta.pk.attname, value)
- qn = connection.ops.quote_name
+ pk = property(_get_pk_val, _set_pk_val)
+
+ def save(self):
+ """
+ Save the current instance. Override this in a subclass if you want to
+ control the saving process.
+ """
+ self.save_base()
+
+ save.alters_data = True
+
+ def save_base(self, raw=False, cls=None):
+ """
+ Does the heavy-lifting involved in saving. Subclasses shouldn't need to
+ override this method. It's separate from save() in order to hide the
+ need for overrides of save() to pass around internal-only parameters
+ ('raw' and 'cls').
+ """
+ if not cls:
+ cls = self.__class__
+ meta = self._meta
+ signal = True
+ dispatcher.send(signal=signals.pre_save, sender=self.__class__,
+ instance=self, raw=raw)
+ else:
+ meta = cls._meta
+ signal = False
+
+ for parent, field in meta.parents.items():
+ self.save_base(raw, parent)
+ setattr(self, field.attname, self._get_pk_val(parent._meta))
+
+ non_pks = [f for f in meta.local_fields if not f.primary_key]
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
- pk_val = self._get_pk_val()
+ pk_val = self._get_pk_val(meta)
# Note: the comparison with '' is required for compatibility with
# oldforms-style model creation.
pk_set = pk_val is not None and smart_unicode(pk_val) != u''
record_exists = True
+ manager = cls._default_manager
if pk_set:
# Determine whether a record with the primary key already exists.
- cursor.execute("SELECT 1 FROM %s WHERE %s=%%s" % \
- (qn(self._meta.db_table), qn(self._meta.pk.column)),
- self._meta.pk.get_db_prep_lookup('exact', pk_val))
- # If it does already exist, do an UPDATE.
- if cursor.fetchone():
- db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks]
- if db_values:
- cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
- (qn(self._meta.db_table),
- ','.join(['%s=%%s' % qn(f.column) for f in non_pks]),
- qn(self._meta.pk.column)),
- db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val))
+ if manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by():
+ # It does already exist, so do an UPDATE.
+ if non_pks:
+ values = [(f, None, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
+ manager.filter(pk=pk_val)._update(values)
else:
record_exists = False
if not pk_set or not record_exists:
- field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
- db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
- # If the PK has been manually set, respect that.
- if pk_set:
- field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
- db_values += [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
- placeholders = ['%s'] * len(field_names)
- if self._meta.order_with_respect_to:
- field_names.append(qn('_order'))
- placeholders.append('%s')
- subsel = 'SELECT COUNT(*) FROM %s WHERE %s = %%s' % (
- qn(self._meta.db_table),
- qn(self._meta.order_with_respect_to.column))
- cursor.execute(subsel, (getattr(self, self._meta.order_with_respect_to.attname),))
- db_values.append(cursor.fetchone()[0])
+ if not pk_set:
+ values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)]
+ else:
+ values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
+
+ if meta.order_with_respect_to:
+ field = meta.order_with_respect_to
+ values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
record_exists = False
- if db_values:
- cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
- (qn(self._meta.db_table), ','.join(field_names),
- ','.join(placeholders)), db_values)
+
+ update_pk = bool(meta.has_auto_field and not pk_set)
+ if values:
+ # Create a new record.
+ result = manager._insert(values, return_id=update_pk)
else:
# Create a new record with defaults for everything.
- cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
- (qn(self._meta.db_table), qn(self._meta.pk.column),
- connection.ops.pk_default_value()))
- if self._meta.has_auto_field and not pk_set:
- setattr(self, self._meta.pk.attname, connection.ops.last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
- transaction.commit_unless_managed()
+ result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
- # Run any post-save hooks.
- dispatcher.send(signal=signals.post_save, sender=self.__class__,
- instance=self, created=(not record_exists), raw=raw)
+ if update_pk:
+ setattr(self, meta.pk.attname, result)
+ transaction.commit_unless_managed()
- save.alters_data = True
+ if signal:
+ dispatcher.send(signal=signals.post_save, sender=self.__class__,
+ instance=self, created=(not record_exists), raw=raw)
def validate(self):
"""
@@ -341,32 +429,31 @@ def _get_FIELD_display(self, field):
return force_unicode(dict(field.choices).get(value, value), strings_only=True)
def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
- qn = connection.ops.quote_name
- op = is_next and '>' or '<'
- where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
- (qn(field.column), op, qn(field.column),
- qn(self._meta.db_table), qn(self._meta.pk.column), op)
+ op = is_next and 'gt' or 'lt'
+ order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
- q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
- q._where.append(where)
- q._params.extend([param, param, getattr(self, self._meta.pk.attname)])
+ q = Q(**{'%s__%s' % (field.name, op): param})
+ q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
+ qs = self.__class__._default_manager.filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
- return q[0]
+ return qs[0]
except IndexError:
raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name
def _get_next_or_previous_in_order(self, is_next):
- qn = connection.ops.quote_name
cachename = "__%s_order_cache" % is_next
if not hasattr(self, cachename):
+ qn = connection.ops.quote_name
op = is_next and '>' or '<'
+ order = not is_next and '-_order' or '_order'
order_field = self._meta.order_with_respect_to
+ # FIXME: When querysets support nested queries, this can be turned
+ # into a pure queryset operation.
where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
(qn('_order'), op, qn('_order'),
- qn(self._meta.db_table), qn(self._meta.pk.column)),
- '%s=%%s' % qn(order_field.column)]
- params = [self._get_pk_val(), getattr(self, order_field.attname)]
- obj = self._default_manager.order_by('_order').extra(where=where, params=params)[:1].get()
+ qn(self._meta.db_table), qn(self._meta.pk.column))]
+ params = [self.pk]
+ obj = self._default_manager.filter(**{order_field.name: getattr(self, order_field.attname)}).extra(where=where, params=params).order_by(order)[:1].get()
setattr(self, cachename, obj)
return getattr(self, cachename)
@@ -446,29 +533,20 @@ def _get_image_dimensions(self, field):
# ORDERING METHODS #########################
def method_set_order(ordered_obj, self, id_list):
- qn = connection.ops.quote_name
- cursor = connection.cursor()
- # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
- sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
- (qn(ordered_obj._meta.db_table), qn('_order'),
- qn(ordered_obj._meta.order_with_respect_to.column),
- qn(ordered_obj._meta.pk.column))
rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
- cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
+ order_name = ordered_obj._meta.order_with_respect_to.name
+ # FIXME: It would be nice if there was an "update many" version of update
+ # for situations like this.
+ for i, j in enumerate(id_list):
+ ordered_obj.objects.filter(**{'pk': j, order_name: rel_val}).update(_order=i)
transaction.commit_unless_managed()
def method_get_order(ordered_obj, self):
- qn = connection.ops.quote_name
- cursor = connection.cursor()
- # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
- sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
- (qn(ordered_obj._meta.pk.column),
- qn(ordered_obj._meta.db_table),
- qn(ordered_obj._meta.order_with_respect_to.column),
- qn('_order'))
rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
- cursor.execute(sql, [rel_val])
- return [r[0] for r in cursor.fetchall()]
+ order_name = ordered_obj._meta.order_with_respect_to.name
+ pk_name = ordered_obj._meta.pk.name
+ return [r[pk_name] for r in
+ ordered_obj.objects.filter(**{order_name: rel_val}).values(pk_name)]
##############################################
# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
@@ -476,3 +554,20 @@ def method_get_order(ordered_obj, self):
def get_absolute_url(opts, func, self, *args, **kwargs):
return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self, *args, **kwargs)
+
+########
+# MISC #
+########
+
+class Empty(object):
+ pass
+
+if sys.version_info < (2, 5):
+ # Prior to Python 2.5, Exception was an old-style class
+ def subclass_exception(name, parent, unused):
+ return types.ClassType(name, (parent,), {})
+
+else:
+ def subclass_exception(name, parent, module):
+ return type(name, (parent,), {'__module__': module})
+
View
43 django/db/models/fields/__init__.py
@@ -1,3 +1,4 @@
+import copy
import datetime
import os
import time
@@ -75,15 +76,19 @@ class Field(object):
# database level.
empty_strings_allowed = True
- # Tracks each time a Field instance is created. Used to retain order.
+ # These track each time a Field instance is created. Used to retain order.
+ # The auto_creation_counter is used for fields that Django implicitly
+ # creates, creation_counter is used for all user-specified fields.
creation_counter = 0
+ auto_creation_counter = -1
def __init__(self, verbose_name=None, name=None, primary_key=False,
- max_length=None, unique=False, blank=False, null=False, db_index=False,
- core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
- prepopulate_from=None, unique_for_date=None, unique_for_month=None,
- unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
- help_text='', db_column=None, db_tablespace=None):
+ max_length=None, unique=False, blank=False, null=False,
+ db_index=False, core=False, rel=None, default=NOT_PROVIDED,
+ editable=True, serialize=True, prepopulate_from=None,
+ unique_for_date=None, unique_for_month=None, unique_for_year=None,
+ validator_list=None, choices=None, radio_admin=None, help_text='',
+ db_column=None, db_tablespace=None, auto_created=False):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
@@ -109,14 +114,27 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
self.db_index = db_index
- # Increase the creation counter, and save our local copy.
- self.creation_counter = Field.creation_counter
- Field.creation_counter += 1
+ # Adjust the appropriate creation counter, and save our local copy.
+ if auto_created:
+ self.creation_counter = Field.auto_creation_counter
+ Field.auto_creation_counter -= 1
+ else:
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
def __cmp__(self, other):
# This is needed because bisect does not take a comparison function.
return cmp(self.creation_counter, other.creation_counter)
+ def __deepcopy__(self, memodict):
+ # We don't have to deepcopy very much here, since most things are not
+ # intended to be altered after initial creation.
+ obj = copy.copy(self)
+ if self.rel:
+ obj.rel = copy.copy(self.rel)
+ memodict[id(self)] = obj
+ return obj
+
def to_python(self, value):
"""
Converts the input value into the expected Python data type, raising
@@ -145,11 +163,10 @@ def db_type(self):
# 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_types = get_creation_module().DATA_TYPES
- internal_type = self.get_internal_type()
- if internal_type not in data_types:
+ try:
+ return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__
+ except KeyError:
return None
- return data_types[internal_type] % self.__dict__
def validate_full(self, field_data, all_data):
"""
View
16 django/db/models/fields/proxy.py
@@ -0,0 +1,16 @@
+"""
+Field-like classes that aren't really fields. It's easier to use objects that
+have the same attributes as fields sometimes (avoids a lot of special casing).
+"""
+
+from django.db.models import fields
+
+class OrderWrt(fields.IntegerField):
+ """
+ A proxy for the _order database field that is used when
+ Meta.order_with_respect_to is specified.
+ """
+ name = '_order'
+ attname = '_order'
+ column = '_order'
+
View
255 django/db/models/fields/related.py
@@ -2,6 +2,7 @@
from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class
from django.db.models.related import RelatedObject
+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
@@ -27,21 +28,21 @@ def add_lazy_relation(cls, field, relation):
"""
Adds a lookup on ``cls`` when a related field is defined using a string,
i.e.::
-
+
class MyModel(Model):
fk = ForeignKey("AnotherModel")
-
+
This string can be:
-
+
* RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive
relation.
-
+
* The name of a model (i.e "AnotherModel") to indicate another model in
the same app.
-
+
* An app-label and model name (i.e. "someapp.AnotherModel") to indicate
another model in a different app.
-
+
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.
@@ -50,7 +51,7 @@ class MyModel(Model):
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
app_label = cls._meta.app_label
model_name = cls.__name__
-
+
else:
# Look for an "app.Model" relation
try:
@@ -59,10 +60,10 @@ class MyModel(Model):
# If we can't split, assume a model in current app
app_label = cls._meta.app_label
model_name = relation
-
+
# Try to look up the related model, and if it's already loaded resolve the
# string right away. If get_model returns None, it means that the related
- # model isn't loaded yet, so we need to pend the relation until the class
+ # model isn't loaded yet, so we need to pend the relation until the class
# is prepared.
model = get_model(app_label, model_name, False)
if model:
@@ -72,7 +73,7 @@ class MyModel(Model):
key = (app_label, model_name)
value = (cls, field)
pending_lookups.setdefault(key, []).append(value)
-
+
def do_pending_lookups(sender):
"""
Handle any pending relations to the sending model. Sent from class_prepared.
@@ -107,6 +108,8 @@ def contribute_to_class(self, cls, name):
add_lazy_relation(cls, self, other)
else:
self.do_related_class(other, cls)
+ if not cls._meta.abstract and self.rel.related_name:
+ self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
def set_attributes_from_rel(self):
self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
@@ -136,6 +139,9 @@ def pk_trace(value):
pass
return v
+ if hasattr(value, 'as_sql'):
+ sql, params = value.as_sql()
+ return QueryWrapper(('(%s)' % sql), params)
if lookup_type == 'exact':
return [pk_trace(value)]
if lookup_type == 'in':
@@ -145,9 +151,10 @@ def pk_trace(value):
raise TypeError, "Related Field has invalid lookup: %s" % lookup_type
def _get_related_query_name(self, opts):
- # This method defines the name that can be used to identify this 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.
+ # This method defines the name that can be used to identify this
+ # 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 opts.object_name.lower()
class SingleRelatedObjectDescriptor(object):
@@ -158,14 +165,19 @@ class SingleRelatedObjectDescriptor(object):
# SingleRelatedObjectDescriptor instance.
def __init__(self, related):
self.related = related
+ self.cache_name = '_%s_cache' % related.field.name
def __get__(self, instance, instance_type=None):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
- params = {'%s__pk' % self.related.field.name: instance._get_pk_val()}
- rel_obj = self.related.model._default_manager.get(**params)
- return rel_obj
+ try:
+ return getattr(instance, self.cache_name)
+ except AttributeError:
+ params = {'%s__pk' % self.related.field.name: instance._get_pk_val()}
+ rel_obj = self.related.model._default_manager.get(**params)
+ setattr(instance, self.cache_name, rel_obj)
+ return rel_obj
def __set__(self, instance, value):
if instance is None:
@@ -495,13 +507,77 @@ def __set__(self, instance, value):
manager.clear()
manager.add(*value)
+class ManyToOneRel(object):
+ def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
+ max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
+ related_name=None, limit_choices_to=None, lookup_overrides=None,
+ raw_id_admin=False, parent_link=False):
+ try:
+ to._meta
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
+ self.to, self.field_name = to, field_name
+ self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
+ self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
+ self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+ self.lookup_overrides = lookup_overrides or {}
+ self.raw_id_admin = raw_id_admin
+ self.multiple = True
+ self.parent_link = parent_link
+
+ def get_related_field(self):
+ """
+ Returns the Field in the 'to' object to which this relationship is
+ tied.
+ """
+ data = self.to._meta.get_field_by_name(self.field_name)
+ if not data[2]:
+ raise FieldDoesNotExist("No related field named '%s'" %
+ self.field_name)
+ return data[0]
+
+class OneToOneRel(ManyToOneRel):
+ def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
+ max_num_in_admin=None, num_extra_on_change=None, edit_inline=False,
+ related_name=None, limit_choices_to=None, lookup_overrides=None,
+ raw_id_admin=False, parent_link=False):
+ # NOTE: *_num_in_admin and num_extra_on_change are intentionally
+ # ignored here. We accept them as parameters only to match the calling
+ # signature of ManyToOneRel.__init__().
+ super(OneToOneRel, self).__init__(to, field_name, num_in_admin,
+ edit_inline=edit_inline, related_name=related_name,
+ limit_choices_to=limit_choices_to,
+ lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin,
+ parent_link=parent_link)
+ self.multiple = False
+
+class ManyToManyRel(object):
+ def __init__(self, to, num_in_admin=0, related_name=None,
+ filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
+ self.to = to
+ self.num_in_admin = num_in_admin
+ self.related_name = related_name
+ self.filter_interface = filter_interface
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+ self.edit_inline = False
+ self.raw_id_admin = raw_id_admin
+ self.symmetrical = symmetrical
+ self.multiple = True
+
+ assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
+
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
- def __init__(self, to, to_field=None, **kwargs):
+ def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
to_name = to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
- assert isinstance(to, basestring), "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
+ 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', '')
@@ -511,7 +587,7 @@ def __init__(self, to, to_field=None, **kwargs):
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.", DeprecationWarning)
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
- kwargs['rel'] = ManyToOneRel(to, to_field,
+ kwargs['rel'] = rel_class(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 3),
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
@@ -520,7 +596,8 @@ def __init__(self, to, to_field=None, **kwargs):
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
- raw_id_admin=kwargs.pop('raw_id_admin', False))
+ raw_id_admin=kwargs.pop('raw_id_admin', False),
+ parent_link=kwargs.pop('parent_link', False))
Field.__init__(self, **kwargs)
self.db_index = True
@@ -606,82 +683,25 @@ def db_type(self):
return IntegerField().db_type()
return rel_field.db_type()
-class OneToOneField(RelatedField, IntegerField):
+class OneToOneField(ForeignKey):
+ """
+ A OneToOneField is essentially the same as a ForeignKey, with the exception
+ that always carries a "unique" constraint with it and the reverse relation
+ always returns the object pointed to (since there will only ever be one),
+ rather than returning a list.
+ """
def __init__(self, to, to_field=None, **kwargs):
- try:
- to_name = to._meta.object_name.lower()
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
- assert isinstance(to, basestring), "OneToOneField(%r) is invalid. First parameter to OneToOneField must be either a model, a model name, or the string %r" % (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['rel'] = OneToOneRel(to, to_field,
- num_in_admin=kwargs.pop('num_in_admin', 0),
- edit_inline=kwargs.pop('edit_inline', False),
- related_name=kwargs.pop('related_name', None),
- limit_choices_to=kwargs.pop('limit_choices_to', None),
- lookup_overrides=kwargs.pop('lookup_overrides', None),
- raw_id_admin=kwargs.pop('raw_id_admin', False))
- kwargs['primary_key'] = True
- IntegerField.__init__(self, **kwargs)
-
- self.db_index = True
-
- def get_attname(self):
- return '%s_id' % self.name
-
- def get_validator_unique_lookup_type(self):
- return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
-
- # TODO: Copied from ForeignKey... putting this in RelatedField adversely affects
- # ManyToManyField. This works for now.
- def prepare_field_objs_and_params(self, manipulator, name_prefix):
- params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
- if self.rel.raw_id_admin:
- field_objs = self.get_manipulator_field_objs()
- params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
- else:
- if self.radio_admin:
- field_objs = [oldforms.RadioSelectField]
- params['ul_class'] = get_ul_class(self.radio_admin)
- else:
- if self.null:
- field_objs = [oldforms.NullSelectField]
- else:
- field_objs = [oldforms.SelectField]
- params['choices'] = self.get_choices_default()
- return field_objs, params
-
- def contribute_to_class(self, cls, name):
- super(OneToOneField, self).contribute_to_class(cls, name)
- setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
+ kwargs['unique'] = True
+ if 'num_in_admin' not in kwargs:
+ kwargs['num_in_admin'] = 0
+ super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
def contribute_to_related_class(self, cls, related):
- setattr(cls, related.get_accessor_name(), SingleRelatedObjectDescriptor(related))
+ setattr(cls, related.get_accessor_name(),
+ SingleRelatedObjectDescriptor(related))
if not cls._meta.one_to_one_field:
cls._meta.one_to_one_field = self
- def formfield(self, **kwargs):
- defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()}
- defaults.update(kwargs)
- return super(OneToOneField, self).formfield(**defaults)
-
- def db_type(self):
- # The database column type of a OneToOneField is the column type
- # of the field to which it points. An exception is if the OneToOneField
- # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
- # in which case the column type is simply that of an IntegerField.
- rel_field = self.rel.get_related_field()
- if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)):
- return IntegerField().db_type()
- return rel_field.db_type()
-
class ManyToManyField(RelatedField, Field):
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@@ -798,7 +818,7 @@ def value_from_object(self, obj):
def save_form_data(self, instance, data):
setattr(instance, self.attname, data)
-
+
def formfield(self, **kwargs):
defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
defaults.update(kwargs)
@@ -813,56 +833,3 @@ def db_type(self):
# so return None.
return None
-class ManyToOneRel(object):
- def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
- max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
- related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
- try:
- to._meta
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
- assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
- self.to, self.field_name = to, field_name
- self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
- self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
- self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
- if limit_choices_to is None:
- limit_choices_to = {}
- self.limit_choices_to = limit_choices_to
- self.lookup_overrides = lookup_overrides or {}
- self.raw_id_admin = raw_id_admin
- self.multiple = True
-
- def get_related_field(self):
- "Returns the Field in the 'to' object to which this relationship is tied."
- return self.to._meta.get_field(self.field_name)
-
-class OneToOneRel(ManyToOneRel):
- def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
- related_name=None, limit_choices_to=None, lookup_overrides=None,
- raw_id_admin=False):
- self.to, self.field_name = to, field_name
- self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
- self.related_name = related_name
- if limit_choices_to is None:
- limit_choices_to = {}
- self.limit_choices_to = limit_choices_to
- self.lookup_overrides = lookup_overrides or {}
- self.raw_id_admin = raw_id_admin
- self.multiple = False
-
-class ManyToManyRel(object):
- def __init__(self, to, num_in_admin=0, related_name=None,
- filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
- self.to = to
- self.num_in_admin = num_in_admin
- self.related_name = related_name
- self.filter_interface = filter_interface
- if limit_choices_to is None:
- limit_choices_to = {}
- self.limit_choices_to = limit_choices_to
- self.edit_inline = False
- self.raw_id_admin = raw_id_admin
- self.symmetrical = symmetrical
- self.multiple = True
-
- assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
View
40 django/db/models/manager.py
@@ -1,11 +1,13 @@
-from django.db.models.query import QuerySet, EmptyQuerySet
+import copy
+
+from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
from django.dispatch import dispatcher
from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist
def ensure_default_manager(sender):
cls = sender
- if not hasattr(cls, '_default_manager'):
+ if not getattr(cls, '_default_manager', None) and not cls._meta.abstract:
# Create the default manager, if needed.
try:
cls._meta.get_field('objects')
@@ -31,13 +33,24 @@ def contribute_to_class(self, model, name):
# TODO: Use weakref because of possible memory leak / circular reference.
self.model = model
setattr(model, name, ManagerDescriptor(self))
- if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter:
+ if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter:
model._default_manager = self
+ def _copy_to_model(self, model):
+ """
+ Makes a copy of the manager and assigns it to 'model', which should be
+ a child of the existing model (used when inheriting a manager from an
+ abstract base class).
+ """
+ assert issubclass(model, self.model)
+ mgr = copy.copy(self)
+ mgr.model = model
+ return mgr
+
#######################
# PROXIES TO QUERYSET #
#######################
-
+
def get_empty_query_set(self):
return EmptyQuerySet(self.model)
@@ -46,7 +59,7 @@ def get_query_set(self):
to easily customize the behavior of the Manager.
"""
return QuerySet(self.model)
-
+
def none(self):
return self.get_empty_query_set()
@@ -70,7 +83,7 @@ def get(self, *args, **kwargs):
def get_or_create(self, **kwargs):
return self.get_query_set().get_or_create(**kwargs)
-
+
def create(self, **kwargs):
return self.get_query_set().create(**kwargs)
@@ -101,6 +114,21 @@ def select_related(self, *args, **kwargs):
def values(self, *args, **kwargs):
return self.get_query_set().values(*args, **kwargs)