summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorDaniel Pyrathon <pirosb3@gmail.com>2015-01-06 19:16:35 -0500
committerTim Graham <timograham@gmail.com>2015-01-06 19:25:12 -0500
commitfb48eb05816b1ac87d58696cdfe48be18c901f16 (patch)
tree3d2e981b6f3fafdeb7310d0734fb148ebb7f6aef /django
parent749d23251bbd6564341405e6f8c1da129b8307e7 (diff)
Fixed #12663 -- Formalized the Model._meta API for retrieving fields.
Thanks to Russell Keith-Magee for mentoring this Google Summer of Code 2014 project and everyone else who helped with the patch!
Diffstat (limited to 'django')
-rw-r--r--django/apps/registry.py5
-rw-r--r--django/contrib/admin/checks.py2
-rw-r--r--django/contrib/admin/options.py13
-rw-r--r--django/contrib/admin/templatetags/admin_list.py2
-rw-r--r--django/contrib/admin/utils.py30
-rw-r--r--django/contrib/admin/validation.py2
-rw-r--r--django/contrib/admindocs/views.py2
-rw-r--r--django/contrib/contenttypes/fields.py27
-rw-r--r--django/contrib/gis/db/models/query.py2
-rw-r--r--django/contrib/gis/db/models/sql/compiler.py5
-rw-r--r--django/contrib/gis/sitemaps/views.py2
-rw-r--r--django/contrib/gis/utils/layermapping.py5
-rw-r--r--django/contrib/gis/utils/srs.py2
-rw-r--r--django/core/serializers/python.py6
-rw-r--r--django/core/serializers/xml_serializer.py4
-rw-r--r--django/db/backends/creation.py4
-rw-r--r--django/db/backends/schema.py44
-rw-r--r--django/db/backends/sqlite3/schema.py4
-rw-r--r--django/db/migrations/autodetector.py38
-rw-r--r--django/db/migrations/operations/fields.py20
-rw-r--r--django/db/migrations/operations/models.py34
-rw-r--r--django/db/migrations/state.py6
-rw-r--r--django/db/models/base.py69
-rw-r--r--django/db/models/deletion.py28
-rw-r--r--django/db/models/fields/__init__.py19
-rw-r--r--django/db/models/fields/related.py149
-rw-r--r--django/db/models/manager.py8
-rw-r--r--django/db/models/options.py722
-rw-r--r--django/db/models/query.py26
-rw-r--r--django/db/models/query_utils.py4
-rw-r--r--django/db/models/sql/compiler.py28
-rw-r--r--django/db/models/sql/query.py52
-rw-r--r--django/db/models/sql/subqueries.py8
-rw-r--r--django/forms/models.py7
34 files changed, 889 insertions, 490 deletions
diff --git a/django/apps/registry.py b/django/apps/registry.py
index fe53d965de..68aa411d91 100644
--- a/django/apps/registry.py
+++ b/django/apps/registry.py
@@ -337,7 +337,12 @@ class Apps(object):
This is mostly used in tests.
"""
+ # Call expire cache on each model. This will purge
+ # the relation tree and the fields cache.
self.get_models.cache_clear()
+ if self.ready:
+ for model in self.get_models(include_auto_created=True):
+ model._meta._expire_cache()
### DEPRECATED METHODS GO BELOW THIS LINE ###
diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py
index ee47f911b1..896d59155c 100644
--- a/django/contrib/admin/checks.py
+++ b/django/contrib/admin/checks.py
@@ -762,7 +762,7 @@ class ModelAdminChecks(BaseModelAdminChecks):
def _check_list_editable_item(self, cls, model, field_name, label):
try:
- field = model._meta.get_field_by_name(field_name)[0]
+ field = model._meta.get_field(field_name)
except FieldDoesNotExist:
return refer_to_missing_field(field=field_name, option=label,
model=model, obj=cls, id='admin.E121')
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 35758cfdaf..3e682f9b10 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -406,7 +406,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
rel_name = None
for part in parts[:-1]:
try:
- field, _, _, _ = model._meta.get_field_by_name(part)
+ field = model._meta.get_field(part)
except FieldDoesNotExist:
# Lookups on non-existent fields are ok, since they're ignored
# later.
@@ -422,7 +422,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
else:
rel_name = None
elif isinstance(field, ForeignObjectRel):
- model = field.model
+ model = field.related_model
rel_name = model._meta.pk.name
else:
rel_name = None
@@ -473,9 +473,12 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
for inline in admin.inlines:
registered_models.add(inline.model)
- for related_object in (opts.get_all_related_objects(include_hidden=True) +
- opts.get_all_related_many_to_many_objects()):
- related_model = related_object.model
+ related_objects = (
+ f for f in opts.get_fields(include_hidden=True)
+ if (f.auto_created and not f.concrete)
+ )
+ for related_object in related_objects:
+ related_model = related_object.related_model
if (any(issubclass(model, related_model) for model in registered_models) and
related_object.field.rel.get_related_field() == field):
return True
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 544c5503ec..266b880d99 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -326,7 +326,7 @@ def date_hierarchy(cl):
"""
if cl.date_hierarchy:
field_name = cl.date_hierarchy
- field = cl.opts.get_field_by_name(field_name)[0]
+ field = cl.opts.get_field(field_name)
dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates'
year_field = '%s__year' % field_name
month_field = '%s__month' % field_name
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index 5da461351a..0f617b3fb9 100644
--- a/django/contrib/admin/utils.py
+++ b/django/contrib/admin/utils.py
@@ -25,7 +25,7 @@ def lookup_needs_distinct(opts, lookup_path):
Returns True if 'distinct()' should be used to query the given lookup path.
"""
field_name = lookup_path.split('__', 1)[0]
- field = opts.get_field_by_name(field_name)[0]
+ field = opts.get_field(field_name)
if hasattr(field, 'get_path_info') and any(path.m2m for path in field.get_path_info()):
return True
return False
@@ -265,7 +265,7 @@ def model_ngettext(obj, n=None):
def lookup_field(name, obj, model_admin=None):
opts = obj._meta
try:
- f = opts.get_field(name)
+ f = _get_non_gfk_field(opts, name)
except FieldDoesNotExist:
# For non-field values, the value is either a method, property or
# returned via a callable.
@@ -291,6 +291,17 @@ def lookup_field(name, obj, model_admin=None):
return f, attr, value
+def _get_non_gfk_field(opts, name):
+ """
+ For historical reasons, the admin app relies on GenericForeignKeys as being
+ "not found" by get_field(). This could likely be cleaned up.
+ """
+ field = opts.get_field(name)
+ if field.is_relation and field.one_to_many and not field.related_model:
+ raise FieldDoesNotExist()
+ return field
+
+
def label_for_field(name, model, model_admin=None, return_attr=False):
"""
Returns a sensible label for a field name. The name can be a callable,
@@ -301,7 +312,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
"""
attr = None
try:
- field = model._meta.get_field_by_name(name)[0]
+ field = _get_non_gfk_field(model._meta, name)
try:
label = field.verbose_name
except AttributeError:
@@ -349,11 +360,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
def help_text_for_field(name, model):
help_text = ""
try:
- field_data = model._meta.get_field_by_name(name)
+ field = _get_non_gfk_field(model._meta, name)
except FieldDoesNotExist:
pass
else:
- field = field_data[0]
if hasattr(field, 'help_text'):
help_text = field.help_text
return smart_text(help_text)
@@ -425,19 +435,21 @@ def reverse_field_path(model, path):
parent = model
pieces = path.split(LOOKUP_SEP)
for piece in pieces:
- field, model, direct, m2m = parent._meta.get_field_by_name(piece)
+ field = parent._meta.get_field(piece)
# skip trailing data field if extant:
if len(reversed_path) == len(pieces) - 1: # final iteration
try:
get_model_from_relation(field)
except NotRelationField:
break
- if direct:
+
+ # Field should point to another model
+ if field.is_relation and not (field.auto_created and not field.concrete):
related_name = field.related_query_name()
parent = field.rel.to
else:
related_name = field.field.name
- parent = field.model
+ parent = field.related_model
reversed_path.insert(0, related_name)
return (parent, LOOKUP_SEP.join(reversed_path))
@@ -458,7 +470,7 @@ def get_fields_from_path(model, path):
parent = get_model_from_relation(fields[-1])
else:
parent = model
- fields.append(parent._meta.get_field_by_name(piece)[0])
+ fields.append(parent._meta.get_field(piece))
return fields
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 92ede613ec..67b97f776e 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -346,7 +346,7 @@ class ModelAdminValidator(BaseValidator):
check_isseq(cls, 'list_editable', cls.list_editable)
for idx, field_name in enumerate(cls.list_editable):
try:
- field = model._meta.get_field_by_name(field_name)[0]
+ field = model._meta.get_field(field_name)
except FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', not defined on %s.%s."
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index 2b45301f3a..2ffd402c95 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -262,7 +262,7 @@ class ModelDetailView(BaseAdminDocsView):
})
# Gather related objects
- for rel in opts.get_all_related_objects() + opts.get_all_related_many_to_many_objects():
+ for rel in opts.related_objects:
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {
'app_label': rel.opts.app_label,
'object_name': rel.opts.object_name,
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index 2297ad63a9..07d47becf8 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -21,6 +21,18 @@ class GenericForeignKey(object):
Provides a generic relation to any object through content-type/object-id
fields.
"""
+ # Field flags
+ auto_created = False
+ concrete = False
+ editable = False
+ hidden = False
+
+ is_relation = True
+ many_to_many = False
+ many_to_one = False
+ one_to_many = True
+ one_to_one = False
+ related_model = None
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
self.ct_field = ct_field
@@ -28,12 +40,13 @@ class GenericForeignKey(object):
self.for_concrete_model = for_concrete_model
self.editable = False
self.rel = None
+ self.column = None
def contribute_to_class(self, cls, name, **kwargs):
self.name = name
self.model = cls
self.cache_attr = "_%s_cache" % name
- cls._meta.add_virtual_field(self)
+ cls._meta.add_field(self, virtual=True)
# Only run pre-initialization field assignment on non-abstract models
if not cls._meta.abstract:
@@ -243,6 +256,13 @@ class GenericForeignKey(object):
class GenericRelation(ForeignObject):
"""Provides an accessor to generic related objects (e.g. comments)"""
+ # Field flags
+ auto_created = False
+
+ many_to_many = False
+ many_to_one = True
+ one_to_many = False
+ one_to_one = False
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@@ -303,8 +323,7 @@ class GenericRelation(ForeignObject):
def resolve_related_fields(self):
self.to_fields = [self.model._meta.pk.name]
- return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
- self.model._meta.pk)]
+ return [(self.rel.to._meta.get_field(self.object_id_field_name), self.model._meta.pk)]
def get_path_info(self):
opts = self.rel.to._meta
@@ -345,7 +364,7 @@ class GenericRelation(ForeignObject):
for_concrete_model=self.for_concrete_model)
def get_extra_restriction(self, where_class, alias, remote_alias):
- field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
+ field = self.rel.to._meta.get_field(self.content_type_field_name)
contenttype_pk = self.get_content_type().pk
cond = where_class()
lookup = field.get_lookup('exact')(Col(remote_alias, field, field), contenttype_pk)
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index f2e7657850..c05688dec9 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -758,7 +758,7 @@ class GeoQuerySet(QuerySet):
elif geo_field not in opts.local_fields:
# This geographic field is inherited from another model, so we have to
# use the db table for the _parent_ model instead.
- tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
+ parent_model = geo_field.model._meta.concrete_model
return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
else:
return self.query.get_compiler(self.db)._field_column(geo_field)
diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py
index dd156ea4b6..1501c98136 100644
--- a/django/contrib/gis/db/models/sql/compiler.py
+++ b/django/contrib/gis/db/models/sql/compiler.py
@@ -118,7 +118,10 @@ class GeoSQLCompiler(compiler.SQLCompiler):
seen = self.query.included_inherited_models.copy()
if start_alias:
seen[None] = start_alias
- for field, model in opts.get_concrete_fields_with_model():
+ for field in opts.concrete_fields:
+ model = field.model._meta.concrete_model
+ if model is opts.model:
+ model = None
if from_parent and model is not None and issubclass(from_parent, model):
# Avoid loading data for already loaded parents.
continue
diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py
index c0c2f83598..d12ed53298 100644
--- a/django/contrib/gis/sitemaps/views.py
+++ b/django/contrib/gis/sitemaps/views.py
@@ -23,7 +23,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB
if field_name:
try:
- field, _, _, _ = klass._meta.get_field_by_name(field_name)
+ field = klass._meta.get_field(field_name)
if not isinstance(field, GeometryField):
raise FieldDoesNotExist
except FieldDoesNotExist:
diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py
index 1c848f105e..2ff23fc38b 100644
--- a/django/contrib/gis/utils/layermapping.py
+++ b/django/contrib/gis/utils/layermapping.py
@@ -457,11 +457,10 @@ class LayerMapping(object):
def geometry_field(self):
"Returns the GeometryField instance associated with the geographic column."
- # Use the `get_field_by_name` on the model's options so that we
+ # Use `get_field()` on the model's options so that we
# get the correct field instance if there's model inheritance.
opts = self.model._meta
- fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
- return fld
+ return opts.get_field(self.geom_field)
def make_multi(self, geom_type, model_field):
"""
diff --git a/django/contrib/gis/utils/srs.py b/django/contrib/gis/utils/srs.py
index e5aa5a7039..1460be2de9 100644
--- a/django/contrib/gis/utils/srs.py
+++ b/django/contrib/gis/utils/srs.py
@@ -61,7 +61,7 @@ def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None,
}
# Backend-specific fields for the SpatialRefSys model.
- srs_field_names = SpatialRefSys._meta.get_all_field_names()
+ srs_field_names = {f.name for f in SpatialRefSys._meta.get_fields()}
if 'srtext' in srs_field_names:
kwargs['srtext'] = srs.wkt
if 'ref_sys_name' in srs_field_names:
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index b4712c76be..f8dd7aebac 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -8,7 +8,7 @@ from __future__ import unicode_literals
from django.apps import apps
from django.conf import settings
from django.core.serializers import base
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import DEFAULT_DB_ALIAS, models
from django.utils.encoding import force_text, is_protected_type
from django.utils import six
@@ -101,12 +101,12 @@ def Deserializer(object_list, **options):
if 'pk' in d:
data[Model._meta.pk.attname] = Model._meta.pk.to_python(d.get("pk", None))
m2m_data = {}
- model_fields = Model._meta.get_all_field_names()
+ field_names = {f.name for f in Model._meta.get_fields()}
# Handle each field
for (field_name, field_value) in six.iteritems(d["fields"]):
- if ignore and field_name not in model_fields:
+ if ignore and field_name not in field_names:
# skip fields no longer on model
continue
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index de82a969c1..0b759799d2 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -186,7 +186,7 @@ class Deserializer(base.Deserializer):
# {m2m_accessor_attribute : [list_of_related_objects]})
m2m_data = {}
- model_fields = Model._meta.get_all_field_names()
+ field_names = {f.name for f in Model._meta.get_fields()}
# Deserialize each field.
for field_node in node.getElementsByTagName("field"):
# If the field is missing the name attribute, bail (are you
@@ -198,7 +198,7 @@ class Deserializer(base.Deserializer):
# Get the field from the Model. This will raise a
# FieldDoesNotExist if, well, the field doesn't exist, which will
# be propagated correctly unless ignorenonexistent=True is used.
- if self.ignore and field_name not in model_fields:
+ if self.ignore and field_name not in field_names:
continue
field = Model._meta.get_field(field_name)
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index 5e0248d4df..42a58c03e8 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -199,7 +199,7 @@ class BaseDatabaseCreation(object):
for f in model._meta.local_fields:
output.extend(self.sql_indexes_for_field(model, f, style))
for fs in model._meta.index_together:
- fields = [model._meta.get_field_by_name(f)[0] for f in fs]
+ fields = [model._meta.get_field(f) for f in fs]
output.extend(self.sql_indexes_for_fields(model, fields, style))
return output
@@ -290,7 +290,7 @@ class BaseDatabaseCreation(object):
for f in model._meta.local_fields:
output.extend(self.sql_destroy_indexes_for_field(model, f, style))
for fs in model._meta.index_together:
- fields = [model._meta.get_field_by_name(f)[0] for f in fs]
+ fields = [model._meta.get_field(f) for f in fs]
output.extend(self.sql_destroy_indexes_for_fields(model, fields, style))
return output
diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py
index 40db888e50..dfda05bc19 100644
--- a/django/db/backends/schema.py
+++ b/django/db/backends/schema.py
@@ -10,6 +10,11 @@ from django.utils import six
logger = getLogger('django.db.backends.schema')
+def _related_non_m2m_objects(opts):
+ # filters out m2m objects from reverse relations.
+ return (obj for obj in opts.related_objects if not obj.field.many_to_many)
+
+
class BaseDatabaseSchemaEditor(object):
"""
This class (and its subclasses) are responsible for emitting schema-changing
@@ -261,7 +266,7 @@ class BaseDatabaseSchemaEditor(object):
# Add any unique_togethers
for fields in model._meta.unique_together:
- columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
+ columns = [model._meta.get_field(field).column for field in fields]
column_sqls.append(self.sql_create_table_unique % {
"columns": ", ".join(self.quote_name(column) for column in columns),
})
@@ -309,7 +314,7 @@ class BaseDatabaseSchemaEditor(object):
news = set(tuple(fields) for fields in new_unique_together)
# Deleted uniques
for fields in olds.difference(news):
- columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
+ columns = [model._meta.get_field(field).column for field in fields]
constraint_names = self._constraint_names(model, columns, unique=True)
if len(constraint_names) != 1:
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
@@ -320,7 +325,7 @@ class BaseDatabaseSchemaEditor(object):
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_names[0]))
# Created uniques
for fields in news.difference(olds):
- columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
+ columns = [model._meta.get_field(field).column for field in fields]
self.execute(self._create_unique_sql(model, columns))
def alter_index_together(self, model, old_index_together, new_index_together):
@@ -333,7 +338,7 @@ class BaseDatabaseSchemaEditor(object):
news = set(tuple(fields) for fields in new_index_together)
# Deleted indexes
for fields in olds.difference(news):
- columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
+ columns = [model._meta.get_field(field).column for field in fields]
constraint_names = self._constraint_names(model, list(columns), index=True)
if len(constraint_names) != 1:
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
@@ -344,7 +349,7 @@ class BaseDatabaseSchemaEditor(object):
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, constraint_names[0]))
# Created indexes
for field_names in news.difference(olds):
- fields = [model._meta.get_field_by_name(field)[0] for field in field_names]
+ fields = [model._meta.get_field(field) for field in field_names]
self.execute(self._create_index_sql(model, fields, suffix="_idx"))
def alter_db_table(self, model, old_db_table, new_db_table):
@@ -511,10 +516,12 @@ class BaseDatabaseSchemaEditor(object):
# Drop incoming FK constraints if we're a primary key and things are going
# to change.
if old_field.primary_key and new_field.primary_key and old_type != new_type:
- for rel in new_field.model._meta.get_all_related_objects():
- rel_fk_names = self._constraint_names(rel.model, [rel.field.column], foreign_key=True)
+ # '_meta.related_field' also contains M2M reverse fields, these
+ # will be filtered out
+ for rel in _related_non_m2m_objects(new_field.model._meta):
+ rel_fk_names = self._constraint_names(rel.related_model, [rel.field.column], foreign_key=True)
for fk_name in rel_fk_names:
- self.execute(self._delete_constraint_sql(self.sql_delete_fk, rel.model, fk_name))
+ self.execute(self._delete_constraint_sql(self.sql_delete_fk, rel.related_model, fk_name))
# Removed an index? (no strict check, as multiple indexes are possible)
if (old_field.db_index and not new_field.db_index and
not old_field.unique and not
@@ -661,7 +668,7 @@ class BaseDatabaseSchemaEditor(object):
# referring to us.
rels_to_update = []
if old_field.primary_key and new_field.primary_key and old_type != new_type:
- rels_to_update.extend(new_field.model._meta.get_all_related_objects())
+ rels_to_update.extend(_related_non_m2m_objects(new_field.model._meta))
# Changed to become primary key?
# Note that we don't detect unsetting of a PK, as we assume another field
# will always come along and replace it.
@@ -684,14 +691,14 @@ class BaseDatabaseSchemaEditor(object):
}
)
# Update all referencing columns
- rels_to_update.extend(new_field.model._meta.get_all_related_objects())
+ rels_to_update.extend(_related_non_m2m_objects(new_field.model._meta))
# Handle our type alters on the other end of rels from the PK stuff above
for rel in rels_to_update:
rel_db_params = rel.field.db_parameters(connection=self.connection)
rel_type = rel_db_params['type']
self.execute(
self.sql_alter_column % {
- "table": self.quote_name(rel.model._meta.db_table),
+ "table": self.quote_name(rel.related_model._meta.db_table),
"changes": self.sql_alter_column_type % {
"column": self.quote_name(rel.field.column),
"type": rel_type,
@@ -705,8 +712,9 @@ class BaseDatabaseSchemaEditor(object):
self.execute(self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s"))
# Rebuild FKs that pointed to us if we previously had to drop them
if old_field.primary_key and new_field.primary_key and old_type != new_type:
- for rel in new_field.model._meta.get_all_related_objects():
- self.execute(self._create_fk_sql(rel.model, rel.field, "_fk"))
+ for rel in new_field.model._meta.related_objects:
+ if not rel.many_to_many:
+ self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk"))
# Does it have check constraints we need to add?
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
self.execute(
@@ -765,14 +773,14 @@ class BaseDatabaseSchemaEditor(object):
new_field.rel.through,
# We need the field that points to the target model, so we can tell alter_field to change it -
# this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model)
- old_field.rel.through._meta.get_field_by_name(old_field.m2m_reverse_field_name())[0],
- new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0],
+ old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()),
+ new_field.rel.through._meta.get_field(new_field.m2m_reverse_field_name()),
)
self.alter_field(
new_field.rel.through,
# for self-referential models we need to alter field from the other end too
- old_field.rel.through._meta.get_field_by_name(old_field.m2m_field_name())[0],
- new_field.rel.through._meta.get_field_by_name(new_field.m2m_field_name())[0],
+ old_field.rel.through._meta.get_field(old_field.m2m_field_name()),
+ new_field.rel.through._meta.get_field(new_field.m2m_field_name()),
)
def _create_index_name(self, model, column_names, suffix=""):
@@ -844,7 +852,7 @@ class BaseDatabaseSchemaEditor(object):
output.append(self._create_index_sql(model, [field], suffix=""))
for field_names in model._meta.index_together:
- fields = [model._meta.get_field_by_name(field)[0] for field in field_names]
+ fields = [model._meta.get_field(field) for field in field_names]
output.append(self._create_index_sql(model, fields, suffix="_idx"))
return output
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
index 9aeca56bf7..e0433b0c13 100644
--- a/django/db/backends/sqlite3/schema.py
+++ b/django/db/backends/sqlite3/schema.py
@@ -227,8 +227,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
alter_fields=[(
# We need the field that points to the target model, so we can tell alter_field to change it -
# this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model)
- old_field.rel.through._meta.get_field_by_name(old_field.m2m_reverse_field_name())[0],
- new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0],
+ old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()),
+ new_field.rel.through._meta.get_field(new_field.m2m_reverse_field_name()),
)],
override_uniques=(new_field.m2m_field_name(), new_field.m2m_reverse_field_name()),
)
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index e3dd87e3bd..8535a98e73 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -163,7 +163,7 @@ class MigrationAutodetector(object):
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
old_model_state = self.from_state.models[app_label, old_model_name]
for field_name, field in old_model_state.fields:
- old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field_by_name(field_name)[0]
+ old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(field_name)
if (hasattr(old_field, "rel") and getattr(old_field.rel, "through", None)
and not old_field.rel.through._meta.auto_created):
through_key = (
@@ -685,26 +685,14 @@ class MigrationAutodetector(object):
# and the removal of all its own related fields, and if it's
# a through model the field that references it.
dependencies = []
- for related_object in model._meta.get_all_related_objects():
- dependencies.append((
- related_object.model._meta.app_label,
- related_object.model._meta.object_name,
- related_object.field.name,
- False,
- ))
- dependencies.append((
- related_object.model._meta.app_label,
- related_object.model._meta.object_name,
- related_object.field.name,
- "alter",
- ))
- for related_object in model._meta.get_all_related_many_to_many_objects():
- dependencies.append((
- related_object.model._meta.app_label,
- related_object.model._meta.object_name,
- related_object.field.name,
- False,
- ))
+ for related_object in model._meta.related_objects:
+ related_object_app_label = related_object.related_model._meta.app_label
+ object_name = related_object.related_model._meta.object_name
+ field_name = related_object.field.name
+ dependencies.append((related_object_app_label, object_name, field_name, False))
+ if not related_object.many_to_many:
+ dependencies.append((related_object_app_label, object_name, field_name, "alter"))
+
for name, field in sorted(related_fields.items()):
dependencies.append((app_label, model_name, name, False))
# We're referenced in another field's through=
@@ -743,7 +731,7 @@ class MigrationAutodetector(object):
for app_label, model_name, field_name in sorted(self.new_field_keys - self.old_field_keys):
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
old_model_state = self.from_state.models[app_label, old_model_name]
- field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name)
# Scan to see if this is actually a rename!
field_dec = self.deep_deconstruct(field)
for rem_app_label, rem_model_name, rem_field_name in sorted(self.old_field_keys - self.new_field_keys):
@@ -776,7 +764,7 @@ class MigrationAutodetector(object):
self._generate_added_field(app_label, model_name, field_name)
def _generate_added_field(self, app_label, model_name, field_name):
- field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name)
# Fields that are foreignkeys/m2ms depend on stuff
dependencies = []
if field.rel and field.rel.to:
@@ -847,8 +835,8 @@ class MigrationAutodetector(object):
# Did the field change?
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
old_field_name = self.renamed_fields.get((app_label, model_name, field_name), field_name)
- old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field_by_name(old_field_name)[0]
- new_field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(old_field_name)
+ new_field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name)
# Implement any model renames on relations; these are handled by RenameModel
# so we need to exclude them from the comparison
if hasattr(new_field, "rel") and getattr(new_field.rel, "to", None):
diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py
index 54251cf6ed..6ce5e37296 100644
--- a/django/db/migrations/operations/fields.py
+++ b/django/db/migrations/operations/fields.py
@@ -44,7 +44,7 @@ class AddField(Operation):
to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
from_model = from_state.apps.get_model(app_label, self.model_name)
- field = to_model._meta.get_field_by_name(self.name)[0]
+ field = to_model._meta.get_field(self.name)
if not self.preserve_default:
field.default = self.field.default
schema_editor.add_field(
@@ -57,7 +57,7 @@ class AddField(Operation):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
- schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
+ schema_editor.remove_field(from_model, from_model._meta.get_field(self.name))
def describe(self):
return "Add field %s to %s" % (self.name, self.model_name)
@@ -100,13 +100,13 @@ class RemoveField(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
- schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
+ schema_editor.remove_field(from_model, from_model._meta.get_field(self.name))
def database_backwards(self, app_label, schema_editor, from_state, to_state):
to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
from_model = from_state.apps.get_model(app_label, self.model_name)
- schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
+ schema_editor.add_field(from_model, to_model._meta.get_field(self.name))
def describe(self):
return "Remove field %s from %s" % (self.name, self.model_name)
@@ -158,8 +158,8 @@ class AlterField(Operation):
to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
from_model = from_state.apps.get_model(app_label, self.model_name)
- from_field = from_model._meta.get_field_by_name(self.name)[0]
- to_field = to_model._meta.get_field_by_name(self.name)[0]
+ from_field = from_model._meta.get_field(self.name)
+ to_field = to_model._meta.get_field(self.name)
# If the field is a relatedfield with an unresolved rel.to, just
# set it equal to the other field side. Bandaid fix for AlterField
# migrations that are part of a RenameModel change.
@@ -231,8 +231,8 @@ class RenameField(Operation):
from_model = from_state.apps.get_model(app_label, self.model_name)
schema_editor.alter_field(
from_model,
- from_model._meta.get_field_by_name(self.old_name)[0],
- to_model._meta.get_field_by_name(self.new_name)[0],
+ from_model._meta.get_field(self.old_name),
+ to_model._meta.get_field(self.new_name),
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
@@ -241,8 +241,8 @@ class RenameField(Operation):
from_model = from_state.apps.get_model(app_label, self.model_name)
schema_editor.alter_field(
from_model,
- from_model._meta.get_field_by_name(self.new_name)[0],
- to_model._meta.get_field_by_name(self.old_name)[0],
+ from_model._meta.get_field(self.new_name),
+ to_model._meta.get_field(self.old_name),
)
def describe(self):
diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py
index f07f667c51..6dd66ae454 100644
--- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -138,25 +138,27 @@ class RenameModel(Operation):
)
def state_forwards(self, app_label, state):
- # Get all of the related objects we need to repoint
apps = state.apps
model = apps.get_model(app_label, self.old_name)
model._meta.apps = apps
- related_objects = model._meta.get_all_related_objects()
- related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
+ # Get all of the related objects we need to repoint
+ all_related_objects = (
+ f for f in model._meta.get_fields(include_hidden=True)
+ if f.auto_created and not f.concrete and not (f.hidden or f.many_to_many)
+ )
# Rename the model
state.models[app_label, self.new_name.lower()] = state.models[app_label, self.old_name.lower()]
state.models[app_label, self.new_name.lower()].name = self.new_name
state.remove_model(app_label, self.old_name)
# Repoint the FKs and M2Ms pointing to us
- for related_object in (related_objects + related_m2m_objects):
+ for related_object in all_related_objects:
# Use the new related key for self referential related objects.
- if related_object.model == model:
+ if related_object.related_model == model:
related_key = (app_label, self.new_name.lower())
else:
related_key = (
- related_object.model._meta.app_label,
- related_object.model._meta.object_name.lower(),
+ related_object.related_model._meta.app_label,
+ related_object.related_model._meta.object_name.lower(),
)
new_fields = []
for name, field in state.models[related_key].fields:
@@ -179,21 +181,19 @@ class RenameModel(Operation):
new_model._meta.db_table,
)
# Alter the fields pointing to us
- related_objects = old_model._meta.get_all_related_objects()
- related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
- for related_object in (related_objects + related_m2m_objects):
- if related_object.model == old_model:
+ for related_object in old_model._meta.related_objects:
+ if related_object.related_model == old_model:
model = new_model
related_key = (app_label, self.new_name.lower())
else:
- model = related_object.model
+ model = related_object.related_model
related_key = (
- related_object.model._meta.app_label,
- related_object.model._meta.object_name.lower(),
+ related_object.related_model._meta.app_label,
+ related_object.related_model._meta.object_name.lower(),
)
to_field = to_state.apps.get_model(
*related_key
- )._meta.get_field_by_name(related_object.field.name)[0]
+ )._meta.get_field(related_object.field.name)
schema_editor.alter_field(
model,
related_object.field,
@@ -394,11 +394,11 @@ class AlterOrderWithRespectTo(Operation):
from_model = from_state.apps.get_model(app_label, self.name)
# Remove a field if we need to
if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
- schema_editor.remove_field(from_model, from_model._meta.get_field_by_name("_order")[0])
+ schema_editor.remove_field(from_model, from_model._meta.get_field("_order"))
# Add a field if we need to (altering the column is untouched as
# it's likely a rename)
elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
- field = to_model._meta.get_field_by_name("_order")[0]
+ field = to_model._meta.get_field("_order")
if not field.has_default():
field.default = 0
schema_editor.add_field(
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 8a45a0d2f2..6626e31442 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -50,15 +50,15 @@ class ProjectState(object):
model_name = model_name.lower()
try:
related_old = {
- f.model for f in
- self.apps.get_model(app_label, model_name)._meta.get_all_related_objects()
+ f.related_model for f in
+ self.apps.get_model(app_label, model_name)._meta.related_objects
}
except LookupError:
related_old = set()
self._reload_one_model(app_label, model_name)
# Reload models if there are relations
model = self.apps.get_model(app_label, model_name)
- related_m2m = {f.rel.to for f, _ in model._meta.get_m2m_with_model()}
+ related_m2m = {f.related_model for f in model._meta.many_to_many}
for rel_model in related_old.union(related_m2m):
self._reload_one_model(rel_model._meta.app_label, rel_model._meta.model_name)
if related_m2m:
diff --git a/django/db/models/base.py b/django/db/models/base.py
index ec5d4c7c1c..945cd0154b 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import copy
import inspect
+from itertools import chain
import sys
import warnings
@@ -175,12 +176,12 @@ class ModelBase(type):
new_class.add_to_class(obj_name, obj)
# All the fields of any type declared on this model
- new_fields = (
- new_class._meta.local_fields +
- new_class._meta.local_many_to_many +
+ new_fields = chain(
+ new_class._meta.local_fields,
+ new_class._meta.local_many_to_many,
new_class._meta.virtual_fields
)
- field_names = set(f.name for f in new_fields)
+ field_names = {f.name for f in new_fields}
# Basic setup for proxy models.
if is_proxy:
@@ -202,6 +203,7 @@ class ModelBase(type):
raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
new_class._meta.setup_proxy(base)
new_class._meta.concrete_model = base._meta.concrete_model
+ base._meta.concrete_model._meta.proxied_children.append(new_class._meta)
else:
new_class._meta.concrete_model = new_class
@@ -342,7 +344,7 @@ class ModelBase(type):
# Give the class a docstring -- its definition.
if cls.__doc__ is None:
- cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join(f.attname for f in opts.fields))
+ cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join(f.name for f in opts.fields))
get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get(
'%s.%s' % (opts.app_label, opts.model_name)
@@ -630,7 +632,7 @@ class Model(six.with_metaclass(ModelBase)):
and not use this method.
"""
try:
- field = self._meta.get_field_by_name(field_name)[0]
+ field = self._meta.get_field(field_name)
except FieldDoesNotExist:
return getattr(self, field_name)
return getattr(self, field.attname)
@@ -1438,12 +1440,17 @@ class Model(six.with_metaclass(ModelBase)):
def _check_local_fields(cls, fields, option):
from django.db import models
+ # In order to avoid hitting the relation tree prematurely, we use our
+ # own fields_map instead of using get_field()
+ forward_fields_map = {
+ field.name: field for field in cls._meta._get_fields(reverse=False)
+ }
+
errors = []
for field_name in fields:
try:
- field = cls._meta.get_field(field_name,
- many_to_many=True)
- except FieldDoesNotExist:
+ field = forward_fields_map[field_name]
+ except KeyError:
errors.append(
checks.Error(
"'%s' refers to the non-existent field '%s'." % (option, field_name),
@@ -1484,7 +1491,6 @@ class Model(six.with_metaclass(ModelBase)):
def _check_ordering(cls):
""" Check "ordering" option -- is it a list of strings and do all fields
exist? """
-
if not cls._meta.ordering:
return []
@@ -1500,7 +1506,6 @@ class Model(six.with_metaclass(ModelBase)):
]
errors = []
-
fields = cls._meta.ordering
# Skip '?' fields.
@@ -1518,28 +1523,30 @@ class Model(six.with_metaclass(ModelBase)):
# Skip ordering on pk. This is always a valid order_by field
# but is an alias and therefore won't be found by opts.get_field.
- fields = (f for f in fields if f != 'pk')
+ fields = {f for f in fields if f != 'pk'}
- for field_name in fields:
- try:
- cls._meta.get_field(field_name, many_to_many=False)
- except FieldDoesNotExist:
- if field_name.endswith('_id'):
- try:
- field = cls._meta.get_field(field_name[:-3], many_to_many=False)
- except FieldDoesNotExist:
- pass
- else:
- if field.attname == field_name:
- continue
- errors.append(
- checks.Error(
- "'ordering' refers to the non-existent field '%s'." % field_name,
- hint=None,
- obj=cls,
- id='models.E015',
- )
+ # Check for invalid or non-existent fields in ordering.
+ invalid_fields = []
+
+ # Any field name that is not present in field_names does not exist.
+ # Also, ordering by m2m fields is not allowed.
+ opts = cls._meta
+ valid_fields = set(chain.from_iterable(
+ (f.name, f.attname) if not (f.auto_created and not f.concrete) else (f.field.related_query_name(),)
+ for f in chain(opts.fields, opts.related_objects)
+ ))
+
+ invalid_fields.extend(fields - valid_fields)
+
+ for invalid_field in invalid_fields:
+ errors.append(
+ checks.Error(
+ "'ordering' refers to the non-existent field '%s'." % invalid_field,
+ hint=None,
+ obj=cls,
+ id='models.E015',
)
+ )
return errors
@classmethod
diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py
index c61f865be2..016fc5637e 100644
--- a/django/db/models/deletion.py
+++ b/django/db/models/deletion.py
@@ -1,4 +1,5 @@
from collections import OrderedDict
+from itertools import chain
from operator import attrgetter
from django.db import connections, transaction, IntegrityError
@@ -51,6 +52,23 @@ def DO_NOTHING(collector, field, sub_objs, using):
pass
+def get_candidate_relations_to_delete(opts):
+ # Collect models that contain candidate relations to delete. This may include
+ # relations coming from proxy models.
+ candidate_models = {opts}
+ candidate_models = candidate_models.union(opts.concrete_model._meta.proxied_children)
+ # For each model, get all candidate fields.
+ candidate_model_fields = chain.from_iterable(
+ opts.get_fields(include_hidden=True) for opts in candidate_models
+ )
+ # The candidate relations are the ones that come from N-1 and 1-1 relations.
+ # N-N (i.e., many-to-many) relations aren't candidates for deletion.
+ return (
+ f for f in candidate_model_fields
+ if f.auto_created and not f.concrete and (f.one_to_one or f.many_to_one)
+ )
+
+
class Collector(object):
def __init__(self, using):
self.using = using
@@ -134,8 +152,7 @@ class Collector(object):
return False
# Foreign keys pointing to this model, both from m2m and other
# models.
- for related in opts.get_all_related_objects(
- include_hidden=True, include_proxy_eq=True):
+ for related in get_candidate_relations_to_delete(opts):
if related.field.rel.on_delete is not DO_NOTHING:
return False
for field in model._meta.virtual_fields:
@@ -184,7 +201,7 @@ class Collector(object):
model = new_objs[0].__class__
# Recursively collect concrete model's parent models, but not their
- # related objects. These will be found by meta.get_all_related_objects()
+ # related objects. These will be found by meta.get_fields()
concrete_model = model._meta.concrete_model
for ptr in six.itervalues(concrete_model._meta.parents):
if ptr:
@@ -199,8 +216,7 @@ class Collector(object):
reverse_dependency=True)
if collect_related:
- for related in model._meta.get_all_related_objects(
- include_hidden=True, include_proxy_eq=True):
+ for related in get_candidate_relations_to_delete(model._meta):
field = related.field
if field.rel.on_delete == DO_NOTHING:
continue
@@ -225,7 +241,7 @@ class Collector(object):
Gets a QuerySet of objects related to ``objs`` via the relation ``related``.
"""
- return related.model._base_manager.using(self.using).filter(
+ return related.related_model._base_manager.using(self.using).filter(
**{"%s__in" % related.field.name: objs}
)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 8dc9c554dd..a1995452a3 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -31,7 +31,9 @@ from django.utils.ipv6 import clean_ipv6_address
from django.utils import six
from django.utils.itercompat import is_iterable
-# imported for backwards compatibility
+# When the _meta object was formalized, this exception was moved to
+# django.core.exceptions. It is retained here for backwards compatibility
+# purposes.
from django.core.exceptions import FieldDoesNotExist # NOQA
# Avoid "TypeError: Item in ``from list'' not a string" -- unicode_literals
@@ -61,7 +63,7 @@ BLANK_CHOICE_DASH = [("", "---------")]
def _load_field(app_label, model_name, field_name):
- return apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ return apps.get_model(app_label, model_name)._meta.get_field(field_name)
# A guide to Field parameters:
@@ -116,6 +118,15 @@ class Field(RegisterLookupMixin):
system_check_deprecated_details = None
system_check_removed_details = None
+ # Field flags
+ hidden = False
+
+ many_to_many = None
+ many_to_one = None
+ one_to_many = None
+ one_to_one = None
+ related_model = None
+
# Generic field type description, usually overridden by subclasses
def _description(self):
return _('Field of type: %(field_type)s') % {
@@ -137,6 +148,7 @@ class Field(RegisterLookupMixin):
self.max_length, self._unique = max_length, unique
self.blank, self.null = blank, null
self.rel = rel
+ self.is_relation = self.rel is not None
self.default = default
self.editable = editable
self.serialize = serialize
@@ -603,6 +615,7 @@ class Field(RegisterLookupMixin):
if not self.name:
self.name = name
self.attname, self.column = self.get_attname_column()
+ self.concrete = self.column is not None
if self.verbose_name is None and self.name:
self.verbose_name = self.name.replace('_', ' ')
@@ -610,7 +623,7 @@ class Field(RegisterLookupMixin):
self.set_attributes_from_name(name)
self.model = cls
if virtual_only:
- cls._meta.add_virtual_field(self)
+ cls._meta.add_field(self, virtual=True)
else:
cls._meta.add_field(self)
if self.choices:
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 2e05e57e51..9ef6c1350a 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -98,6 +98,18 @@ signals.class_prepared.connect(do_pending_lookups)
class RelatedField(Field):
+ # Field flags
+ one_to_many = False
+ one_to_one = False
+ many_to_many = False
+ many_to_one = False
+
+ @cached_property
+ def related_model(self):
+ # Can't cache this property until all the models are loaded.
+ apps.check_models_ready()
+ return self.rel.to
+
def check(self, **kwargs):
errors = super(RelatedField, self).check(**kwargs)
errors.extend(self._check_related_name_is_valid())
@@ -235,13 +247,10 @@ class RelatedField(Field):
# Check clashes between accessors/reverse query names of `field` and
# any other field accessor -- i. e. Model.foreign accessor clashes with
# Model.m2m accessor.
- potential_clashes = rel_opts.get_all_related_many_to_many_objects()
- potential_clashes += rel_opts.get_all_related_objects()
- potential_clashes = (r for r in potential_clashes
- if r.field is not self)
+ potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
for clash_field in potential_clashes:
clash_name = "%s.%s" % ( # i. e. "Model.m2m"
- clash_field.model._meta.object_name,
+ clash_field.related_model._meta.object_name,
clash_field.field.name)
if clash_field.get_accessor_name() == rel_name:
errors.append(
@@ -392,7 +401,7 @@ class SingleRelatedObjectDescriptor(object):
# consistency with `ReverseSingleRelatedObjectDescriptor`.
return type(
str('RelatedObjectDoesNotExist'),
- (self.related.model.DoesNotExist, AttributeError),
+ (self.related.related_model.DoesNotExist, AttributeError),
{}
)
@@ -400,11 +409,11 @@ class SingleRelatedObjectDescriptor(object):
return hasattr(instance, self.cache_name)
def get_queryset(self, **hints):
- manager = self.related.model._default_manager
+ manager = self.related.related_model._default_manager
# If the related manager indicates that it should be used for
# related fields, respect that.
if not getattr(manager, 'use_for_related_fields', False):
- manager = self.related.model._base_manager
+ manager = self.related.related_model._base_manager
return manager.db_manager(hints=hints).all()
def get_prefetch_queryset(self, instances, queryset=None):
@@ -441,7 +450,7 @@ class SingleRelatedObjectDescriptor(object):
params['%s__%s' % (self.related.field.name, rh_field.name)] = getattr(instance, rh_field.attname)
try:
rel_obj = self.get_queryset(instance=instance).get(**params)
- except self.related.model.DoesNotExist:
+ except self.related.related_model.DoesNotExist:
rel_obj = None
else:
setattr(rel_obj, self.related.field.get_cache_name(), instance)
@@ -470,7 +479,7 @@ class SingleRelatedObjectDescriptor(object):
self.related.get_accessor_name(),
)
)
- elif value is not None and not isinstance(value, self.related.model):
+ elif value is not None and not isinstance(value, self.related.related_model):
raise ValueError(
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
value,
@@ -825,9 +834,9 @@ class ForeignRelatedObjectsDescriptor(object):
# Dynamically create a class that subclasses the related model's default
# manager.
return create_foreign_related_manager(
- self.related.model._default_manager.__class__,
+ self.related.related_model._default_manager.__class__,
self.related.field,
- self.related.model,
+ self.related.related_model,
)
@@ -1148,7 +1157,7 @@ class ManyRelatedObjectsDescriptor(object):
# Dynamically create a class that subclasses the related
# model's default manager.
return create_many_related_manager(
- self.related.model._default_manager.__class__,
+ self.related.related_model._default_manager.__class__,
self.related.field.rel
)
@@ -1156,7 +1165,7 @@ class ManyRelatedObjectsDescriptor(object):
if instance is None:
return self
- rel_model = self.related.model
+ rel_model = self.related.related_model
manager = self.related_manager_cls(
model=rel_model,
@@ -1255,6 +1264,12 @@ class ReverseManyRelatedObjectsDescriptor(object):
class ForeignObjectRel(object):
+ # Field flags
+ auto_created = True
+ concrete = False
+ editable = False
+ is_relation = True
+
def __init__(self, field, to, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None, related_query_name=None):
self.field = field
@@ -1267,32 +1282,55 @@ class ForeignObjectRel(object):
self.on_delete = on_delete
self.symmetrical = False
- # This and the following cached_properties can't be initialized in
+ # Some of the following cached_properties can't be initialized in
# __init__ as the field doesn't have its model yet. Calling these methods
# before field.contribute_to_class() has been called will result in
# AttributeError
@cached_property
def model(self):
- if not self.field.model:
- raise AttributeError(
- "This property can't be accessed before self.field.contribute_to_class has been called.")
- return self.field.model
+ return self.to
@cached_property
def opts(self):
- return self.model._meta
+ return self.related_model._meta
@cached_property
def to_opts(self):
return self.to._meta
@cached_property
- def parent_model(self):
- return self.to
+ def hidden(self):
+ return self.is_hidden()
@cached_property
def name(self):
- return '%s.%s' % (self.opts.app_label, self.opts.model_name)
+ return self.field.related_query_name()
+
+ @cached_property
+ def related_model(self):
+ if not self.field.model:
+ raise AttributeError(
+ "This property can't be accessed before self.field.contribute_to_class has been called.")
+ return self.field.model
+
+ @cached_property
+ def many_to_many(self):
+ return self.field.many_to_many
+
+ @cached_property
+ def many_to_one(self):
+ return self.field.one_to_many
+
+ @cached_property
+ def one_to_many(self):
+ return self.field.many_to_one
+
+ @cached_property
+ def one_to_one(self):
+ return self.field.one_to_one
+
+ def __repr__(self):
+ return '<%s: %s.%s>' % (type(self).__name__, self.opts.app_label, self.opts.model_name)
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
limit_to_currently_related=False):
@@ -1304,10 +1342,10 @@ class ForeignObjectRel(object):
initially for utilization by RelatedFieldListFilter.
"""
first_choice = blank_choice if include_blank else []
- queryset = self.model._default_manager.all()
+ queryset = self.related_model._default_manager.all()
if limit_to_currently_related:
queryset = queryset.complex_filter(
- {'%s__isnull' % self.parent_model._meta.model_name: False}
+ {'%s__isnull' % self.related_model._meta.model_name: False}
)
lst = [(x._get_pk_val(), smart_text(x)) for x in queryset]
return first_choice + lst
@@ -1318,7 +1356,7 @@ class ForeignObjectRel(object):
def is_hidden(self):
"Should the related object be hidden?"
- return self.related_name and self.related_name[-1] == '+'
+ return self.related_name is not None and self.related_name[-1] == '+'
def get_joining_columns(self):
return self.field.get_reverse_joining_columns()
@@ -1349,7 +1387,7 @@ class ForeignObjectRel(object):
# Due to backwards compatibility ModelForms need to be able to provide
# an alternate model. See BaseInlineFormSet.get_default_prefix().
opts = model._meta if model else self.opts
- model = model or self.model
+ model = model or self.related_model
if self.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if self.symmetrical and model == self.to:
@@ -1383,11 +1421,11 @@ class ManyToOneRel(ForeignObjectRel):
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]:
+ field = self.to._meta.get_field(self.field_name)
+ if not field.concrete:
raise FieldDoesNotExist("No related field named '%s'" %
self.field_name)
- return data[0]
+ return field
def set_field_name(self):
self.field_name = self.field_name or self.to._meta.pk.name
@@ -1419,6 +1457,10 @@ class ManyToManyRel(ForeignObjectRel):
self.through_fields = through_fields
self.db_constraint = db_constraint
+ def is_hidden(self):
+ "Should the related object be hidden?"
+ return self.related_name is not None and self.related_name[-1] == '+'
+
def get_related_field(self):
"""
Returns the field in the 'to' object to which this relationship is tied.
@@ -1436,8 +1478,13 @@ class ManyToManyRel(ForeignObjectRel):
class ForeignObject(RelatedField):
+ # Field flags
+ many_to_many = False
+ many_to_one = False
+ one_to_many = True
+ one_to_one = False
+
requires_unique_target = True
- generate_reverse_relation = True
related_accessor_class = ForeignRelatedObjectsDescriptor
def __init__(self, to, from_fields, to_fields, swappable=True, **kwargs):
@@ -1556,9 +1603,9 @@ class ForeignObject(RelatedField):
from_field_name = self.from_fields[index]
to_field_name = self.to_fields[index]
from_field = (self if from_field_name == 'self'
- else self.opts.get_field_by_name(from_field_name)[0])
+ else self.opts.get_field(from_field_name))
to_field = (self.rel.to._meta.pk if to_field_name is None
- else self.rel.to._meta.get_field_by_name(to_field_name)[0])
+ else self.rel.to._meta.get_field(to_field_name))
related_fields.append((from_field, to_field))
return related_fields
@@ -1731,7 +1778,7 @@ class ForeignObject(RelatedField):
def contribute_to_related_class(self, cls, related):
# Internal FK's - i.e., those with a related name ending with '+' -
# and swapped models don't get a related descriptor.
- if not self.rel.is_hidden() and not related.model._meta.swapped:
+ if not self.rel.is_hidden() and not related.related_model._meta.swapped:
setattr(cls, related.get_accessor_name(), self.related_accessor_class(related))
# While 'limit_choices_to' might be a callable, simply pass
# it along for later - this is too early because it's still
@@ -1741,6 +1788,12 @@ class ForeignObject(RelatedField):
class ForeignKey(ForeignObject):
+ # Field flags
+ many_to_many = False
+ many_to_one = False
+ one_to_many = True
+ one_to_one = False
+
empty_strings_allowed = False
default_error_messages = {
'invalid': _('%(model)s instance with %(field)s %(value)r does not exist.')
@@ -1951,6 +2004,12 @@ class OneToOneField(ForeignKey):
always returns the object pointed to (since there will only ever be one),
rather than returning a list.
"""
+ # Field flags
+ many_to_many = False
+ many_to_one = False
+ one_to_many = False
+ one_to_one = True
+
related_accessor_class = SingleRelatedObjectDescriptor
description = _("One-to-one relationship")
@@ -2036,6 +2095,12 @@ def create_many_to_many_intermediary_model(field, klass):
class ManyToManyField(RelatedField):
+ # Field flags
+ many_to_many = True
+ many_to_one = False
+ one_to_many = False
+ one_to_one = False
+
description = _("Many-to-many relationship")
def __init__(self, to, db_constraint=True, swappable=True, **kwargs):
@@ -2050,7 +2115,6 @@ class ManyToManyField(RelatedField):
# Class names must be ASCII in Python 2.x, so we forcibly coerce it
# here to break early if there's a problem.
to = str(to)
-
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = ManyToManyRel(
self, to,
@@ -2357,8 +2421,8 @@ class ManyToManyField(RelatedField):
"""
pathinfos = []
int_model = self.rel.through
- linkfield1 = int_model._meta.get_field_by_name(self.m2m_field_name())[0]
- linkfield2 = int_model._meta.get_field_by_name(self.m2m_reverse_field_name())[0]
+ linkfield1 = int_model._meta.get_field(self.m2m_field_name())
+ linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name())
if direct:
join1infos = linkfield1.get_reverse_path_info()
join2infos = linkfield2.get_path_info()
@@ -2398,8 +2462,8 @@ class ManyToManyField(RelatedField):
else:
link_field_name = None
for f in self.rel.through._meta.fields:
- if hasattr(f, 'rel') and f.rel and f.rel.to == related.model and \
- (link_field_name is None or link_field_name == f.name):
+ if (f.is_relation and f.rel.to == related.related_model and
+ (link_field_name is None or link_field_name == f.name)):
setattr(self, cache_attr, getattr(f, attr))
return getattr(self, cache_attr)
@@ -2414,8 +2478,9 @@ class ManyToManyField(RelatedField):
else:
link_field_name = None
for f in self.rel.through._meta.fields:
- if hasattr(f, 'rel') and f.rel and f.rel.to == related.parent_model:
- if link_field_name is None and related.model == related.parent_model:
+ # NOTE f.rel.to != f.related_model
+ if f.is_relation and f.rel.to == related.model:
+ if link_field_name is None and related.related_model == related.model:
# If this is an m2m-intermediate to self,
# the first foreign key you find will be
# the source column. Keep searching for
@@ -2479,7 +2544,7 @@ class ManyToManyField(RelatedField):
def contribute_to_related_class(self, cls, related):
# Internal M2Ms (i.e., those with a related name ending with '+')
# and swapped models don't get a related descriptor.
- if not self.rel.is_hidden() and not related.model._meta.swapped:
+ if not self.rel.is_hidden() and not related.related_model._meta.swapped:
setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
# Set up the accessors for the column names on the m2m table
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
index 179ea7cee3..aa2df3f0e8 100644
--- a/django/db/models/manager.py
+++ b/django/db/models/manager.py
@@ -2,7 +2,6 @@ import copy
from importlib import import_module
import inspect
-from django.core.exceptions import FieldDoesNotExist
from django.db import router
from django.db.models.query import QuerySet
from django.utils import six
@@ -23,15 +22,12 @@ def ensure_default_manager(cls):
setattr(cls, 'objects', SwappedManagerDescriptor(cls))
return
if not getattr(cls, '_default_manager', None):
- # Create the default manager, if needed.
- try:
- cls._meta.get_field('objects')
+ if any(f.name == 'objects' for f in cls._meta.fields):
raise ValueError(
"Model %s must specify a custom Manager, because it has a "
"field named 'objects'" % cls.__name__
)
- except FieldDoesNotExist:
- pass
+ # Create the default manager, if needed.
cls.add_to_class('objects', Manager())
cls._base_manager = cls.objects
elif not getattr(cls, '_base_manager', None):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 79954a87e6..6b48d2733b 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -1,20 +1,31 @@
from __future__ import unicode_literals
from bisect import bisect
-from collections import OrderedDict
+from collections import OrderedDict, defaultdict
+from itertools import chain
+import warnings
from django.apps import apps
from django.conf import settings
from django.core.exceptions import FieldDoesNotExist
-from django.db.models.fields.related import ManyToManyRel
+from django.db.models.fields.related import ManyToManyField
from django.db.models.fields import AutoField
from django.db.models.fields.proxy import OrderWrt
from django.utils import six
+from django.utils.datastructures import ImmutableList
+from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
from django.utils.functional import cached_property
+from django.utils.lru_cache import lru_cache
from django.utils.text import camel_case_to_spaces
from django.utils.translation import activate, deactivate_all, get_language, string_concat
+EMPTY_RELATION_TREE = tuple()
+
+IMMUTABLE_WARNING = (
+ "The return type of '%s' should never be mutated. If you want to manipulate this list "
+ "for your own use, make a copy first."
+)
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by',
@@ -24,6 +35,24 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'select_on_save', 'default_related_name')
+class raise_deprecation(object):
+ def __init__(self, suggested_alternative):
+ self.suggested_alternative = suggested_alternative
+
+ def __call__(self, fn):
+ def wrapper(*args, **kwargs):
+ warnings.warn(
+ "'%s is an unofficial API that has been deprecated. "
+ "You may be able to replace it with '%s'" % (
+ fn.__name__,
+ self.suggested_alternative,
+ ),
+ RemovedInDjango20Warning, stacklevel=2
+ )
+ return fn(*args, **kwargs)
+ return wrapper
+
+
def normalize_together(option_together):
"""
option_together can be either a tuple of tuples, or a single
@@ -46,9 +75,19 @@ def normalize_together(option_together):
return option_together
+def make_immutable_fields_list(name, data):
+ return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
+
+
@python_2_unicode_compatible
class Options(object):
+ FORWARD_PROPERTIES = ('fields', 'many_to_many', 'concrete_fields',
+ 'local_concrete_fields', '_forward_fields_map')
+ REVERSE_PROPERTIES = ('related_objects', 'fields_map', '_relation_tree')
+
def __init__(self, meta, app_label=None):
+ self._get_fields_cache = {}
+ self.proxied_children = []
self.local_fields = []
self.local_many_to_many = []
self.virtual_fields = []
@@ -103,6 +142,31 @@ class Options(object):
self.default_related_name = None
+ @lru_cache(maxsize=None)
+ def _map_model(self, link):
+ # This helper function is used to allow backwards compatibility with
+ # the previous API. No future methods should use this function.
+ # It maps a field to (field, model or related_model,) depending on the
+ # field type.
+ model = link.model._meta.concrete_model
+ if model is self.model:
+ model = None
+ return link, model
+
+ @lru_cache(maxsize=None)
+ def _map_model_details(self, link):
+ # This helper function is used to allow backwards compatibility with
+ # the previous API. No future methods should use this function.
+ # This function maps a field to a tuple of:
+ # (field, model or related_model, direct, is_m2m) depending on the
+ # field type.
+ direct = not link.auto_created or link.concrete
+ model = link.model._meta.concrete_model
+ if model is self.model:
+ model = None
+ m2m = link.is_relation and link.many_to_many
+ return link, model, direct, m2m
+
@property
def app_config(self):
# Don't go through get_app_config to avoid triggering imports.
@@ -183,7 +247,17 @@ class Options(object):
def _prepare(self, model):
if self.order_with_respect_to:
- self.order_with_respect_to = self.get_field(self.order_with_respect_to)
+ # The app registry will not be ready at this point, so we cannot
+ # use get_field().
+ query = self.order_with_respect_to
+ try:
+ self.order_with_respect_to = next(
+ f for f in self._get_fields(reverse=False)
+ if f.name == query or f.attname == query
+ )
+ except StopIteration:
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, query))
+
self.ordering = ('_order',)
if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields):
model.add_to_class('_order', OrderWrt())
@@ -208,56 +282,41 @@ class Options(object):
auto_created=True)
model.add_to_class('id', auto)
- def add_field(self, field):
+ def add_field(self, field, virtual=False):
# Insert the given field in the order in which it was created, using
# the "creation_counter" attribute of the field.
# Move many-to-many related fields from self.fields into
# self.many_to_many.
- if field.rel and isinstance(field.rel, ManyToManyRel):
+ if virtual:
+ self.virtual_fields.append(field)
+ elif field.is_relation and field.many_to_many:
self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
- if hasattr(self, '_m2m_cache'):
- del self._m2m_cache
else:
self.local_fields.insert(bisect(self.local_fields, field), field)
self.setup_pk(field)
- if hasattr(self, '_field_cache'):
- del self._field_cache
- del self._field_name_cache
- # The fields, concrete_fields and local_concrete_fields are
- # implemented as cached properties for performance reasons.
- # The attrs will not exists if the cached property isn't
- # accessed yet, hence the try-excepts.
- try:
- del self.fields
- except AttributeError:
- pass
- try:
- del self.concrete_fields
- except AttributeError:
- pass
- try:
- del self.local_concrete_fields
- except AttributeError:
- pass
-
- if hasattr(self, '_name_map'):
- del self._name_map
- def add_virtual_field(self, field):
- self.virtual_fields.append(field)
+ # If the field being added is a relation to another known field,
+ # expire the cache on this field and the forward cache on the field
+ # being referenced, because there will be new relationships in the
+ # cache. Otherwise, expire the cache of references *to* this field.
+ # The mechanism for getting at the related model is slightly odd -
+ # ideally, we'd just ask for field.related_model. However, related_model
+ # is a cached property, and all the models haven't been loaded yet, so
+ # we need to make sure we don't cache a string reference.
+ if field.is_relation and hasattr(field.rel, 'to') and field.rel.to:
+ try:
+ field.rel.to._meta._expire_cache(forward=False)
+ except AttributeError:
+ pass
+ self._expire_cache()
+ else:
+ self._expire_cache(reverse=False)
def setup_pk(self, field):
if not self.pk and field.primary_key:
self.pk = field
field.serialize = False
- def pk_index(self):
- """
- Returns the index of the primary key field in the self.concrete_fields
- list.
- """
- return self.concrete_fields.index(self.pk)
-
def setup_proxy(self, target):
"""
Does the internal setup so that the current model is a proxy for
@@ -273,6 +332,7 @@ class Options(object):
def __str__(self):
return "%s.%s" % (smart_text(self.app_label), smart_text(self.model_name))
+ @property
def verbose_name_raw(self):
"""
There are a few places where the untranslated verbose name is needed
@@ -284,9 +344,9 @@ class Options(object):
raw = force_text(self.verbose_name)
activate(lang)
return raw
- verbose_name_raw = property(verbose_name_raw)
- def _swapped(self):
+ @property
+ def swapped(self):
"""
Has this model been swapped out for another? If so, return the model
name of the replacement; otherwise, return None.
@@ -310,253 +370,253 @@ class Options(object):
if '%s.%s' % (swapped_label, swapped_object.lower()) not in (None, model_label):
return swapped_for
return None
- swapped = property(_swapped)
@cached_property
def fields(self):
"""
- The getter for self.fields. This returns the list of field objects
- available to this model (including through parent models).
+ Returns a list of all forward fields on the model and its parents,
+ excluding ManyToManyFields.
- Callers are not permitted to modify this list, since it's a reference
- to this instance (not a copy).
+ Private API intended only to be used by Django itself; get_fields()
+ combined with filtering of field properties is the public API for
+ obtaining this field list.
"""
- try:
- self._field_name_cache
- except AttributeError:
- self._fill_fields_cache()
- return self._field_name_cache
+ # For legacy reasons, the fields property should only contain forward
+ # fields that are not virtual or with a m2m cardinality. Therefore we
+ # pass these three filters as filters to the generator.
+ # The third lambda is a longwinded way of checking f.related_model - we don't
+ # use that property directly because related_model is a cached property,
+ # and all the models may not have been loaded yet; we don't want to cache
+ # the string reference to the related_model.
+ is_not_an_m2m_field = lambda f: not (f.is_relation and f.many_to_many)
+ is_not_a_generic_relation = lambda f: not (f.is_relation and f.many_to_one)
+ is_not_a_generic_foreign_key = lambda f: not (
+ f.is_relation and f.one_to_many and not (hasattr(f.rel, 'to') and f.rel.to)
+ )
+ return make_immutable_fields_list(
+ "fields",
+ (f for f in self._get_fields(reverse=False) if
+ is_not_an_m2m_field(f) and is_not_a_generic_relation(f)
+ and is_not_a_generic_foreign_key(f))
+ )
@cached_property
def concrete_fields(self):
- return [f for f in self.fields if f.column is not None]
+ """
+ Returns a list of all concrete fields on the model and its parents.
+
+ Private API intended only to be used by Django itself; get_fields()
+ combined with filtering of field properties is the public API for
+ obtaining this field list.
+ """
+ return make_immutable_fields_list(
+ "concrete_fields", (f for f in self.fields if f.concrete)
+ )
@cached_property
def local_concrete_fields(self):
- return [f for f in self.local_fields if f.column is not None]
-
- def get_fields_with_model(self):
"""
- Returns a sequence of (field, model) pairs for all fields. The "model"
- element is None for fields on the current model. Mostly of use when
- constructing queries so that we know which model a field belongs to.
+ Returns a list of all concrete fields on the model.
+
+ Private API intended only to be used by Django itself; get_fields()
+ combined with filtering of field properties is the public API for
+ obtaining this field list.
"""
- try:
- self._field_cache
- except AttributeError:
- self._fill_fields_cache()
- return self._field_cache
+ return make_immutable_fields_list(
+ "local_concrete_fields", (f for f in self.local_fields if f.concrete)
+ )
+
+ @raise_deprecation(suggested_alternative="get_fields()")
+ def get_fields_with_model(self):
+ return [self._map_model(f) for f in self.get_fields()]
+ @raise_deprecation(suggested_alternative="get_fields()")
def get_concrete_fields_with_model(self):
- return [(field, model) for field, model in self.get_fields_with_model() if
- field.column is not None]
+ return [self._map_model(f) for f in self.concrete_fields]
- def _fill_fields_cache(self):
- cache = []
- for parent in self.parents:
- for field, model in parent._meta.get_fields_with_model():
- if model:
- cache.append((field, model))
- else:
- cache.append((field, parent))
- cache.extend((f, None) for f in self.local_fields)
- self._field_cache = tuple(cache)
- self._field_name_cache = [x for x, _ in cache]
+ @cached_property
+ def many_to_many(self):
+ """
+ Returns a list of all many to many fields on the model and its parents.
- def _many_to_many(self):
- try:
- self._m2m_cache
- except AttributeError:
- self._fill_m2m_cache()
- return list(self._m2m_cache)
- many_to_many = property(_many_to_many)
+ Private API intended only to be used by Django itself; get_fields()
+ combined with filtering of field properties is the public API for
+ obtaining this list.
+ """
+ return make_immutable_fields_list(
+ "many_to_many",
+ (f for f in self._get_fields(reverse=False)
+ if f.is_relation and f.many_to_many)
+ )
- def get_m2m_with_model(self):
+ @cached_property
+ def related_objects(self):
"""
- The many-to-many version of get_fields_with_model().
+ Returns all related objects pointing to the current model. The related
+ objects can come from a one-to-one, one-to-many, or many-to-many field
+ relation type.
+
+ Private API intended only to be used by Django itself; get_fields()
+ combined with filtering of field properties is the public API for
+ obtaining this field list.
"""
- try:
- self._m2m_cache
- except AttributeError:
- self._fill_m2m_cache()
- return list(six.iteritems(self._m2m_cache))
+ all_related_fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
+ return make_immutable_fields_list(
+ "related_objects",
+ (obj for obj in all_related_fields
+ if not obj.hidden or obj.field.many_to_many)
+ )
- def _fill_m2m_cache(self):
- cache = OrderedDict()
- for parent in self.parents:
- for field, model in parent._meta.get_m2m_with_model():
- if model:
- cache[field] = model
- else:
- cache[field] = parent
- for field in self.local_many_to_many:
- cache[field] = None
- self._m2m_cache = cache
+ @raise_deprecation(suggested_alternative="get_fields()")
+ def get_m2m_with_model(self):
+ return [self._map_model(f) for f in self.many_to_many]
- def get_field(self, name, many_to_many=True):
- """
- Returns the requested field by name. Raises FieldDoesNotExist on error.
- """
- to_search = (self.fields + self.many_to_many) if many_to_many else self.fields
- for f in to_search:
- if f.name == name:
- return f
- raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, name))
+ @cached_property
+ def _forward_fields_map(self):
+ res = {}
+ # call get_fields() with export_ordered_set=True in order to have a
+ # field_instance -> names map
+ fields = self._get_fields(reverse=False)
+ for field in fields:
+ res[field.name] = field
+ # Due to the way Django's internals work, get_field() should also
+ # be able to fetch a field by attname. In the case of a concrete
+ # field with relation, includes the *_id name too
+ try:
+ res[field.attname] = field
+ except AttributeError:
+ pass
+ return res
- def get_field_by_name(self, name):
+ @cached_property
+ def fields_map(self):
+ res = {}
+ fields = self._get_fields(forward=False, include_hidden=True)
+ for field in fields:
+ res[field.name] = field
+ # Due to the way Django's internals work, get_field() should also
+ # be able to fetch a field by attname. In the case of a concrete
+ # field with relation, includes the *_id name too
+ try:
+ res[field.attname] = field
+ except AttributeError:
+ pass
+ return res
+
+ def get_field(self, field_name, many_to_many=None):
"""
- Returns the (field_object, model, direct, m2m), where field_object is
- the Field instance for the given name, model is the model containing
- this field (None for local fields), direct is True if the field exists
- on this model, and m2m is True for many-to-many relations. When
- 'direct' is False, 'field_object' is the corresponding ForeignObjectRel
- for this field (since the field doesn't have an instance associated
- with it).
+ Returns a field instance given a field name. The field can be either a
+ forward or reverse field, unless many_to_many is specified; if it is,
+ only forward fields will be returned.
- Uses a cache internally, so after the first access, this is very fast.
+ The many_to_many argument exists for backwards compatibility reasons;
+ it has been deprecated and will be removed in Django 2.0.
"""
+ m2m_in_kwargs = many_to_many is not None
+ if m2m_in_kwargs:
+ # Always throw a warning if many_to_many is used regardless of
+ # whether it alters the return type or not.
+ warnings.warn(
+ "The 'many_to_many' argument on get_field() is deprecated; "
+ "use a filter on field.many_to_many instead.",
+ RemovedInDjango20Warning
+ )
+
try:
- try:
- return self._name_map[name]
- except AttributeError:
- cache = self.init_name_map()
- return cache[name]
+ # In order to avoid premature loading of the relation tree
+ # (expensive) we prefer checking if the field is a forward field.
+ field = self._forward_fields_map[field_name]
+
+ if many_to_many is False and field.many_to_many:
+ raise FieldDoesNotExist(
+ '%s has no field named %r' % (self.object_name, field_name)
+ )
+
+ return field
except KeyError:
- raise FieldDoesNotExist('%s has no field named %r'
- % (self.object_name, name))
+ # If the app registry is not ready, reverse fields are
+ # unavailable, therefore we throw a FieldDoesNotExist exception.
+ if not self.apps.ready:
+ raise FieldDoesNotExist(
+ "%s has no field named %r. The app cache isn't "
+ "ready yet, so if this is a forward field, it won't "
+ "be available yet." % (self.object_name, field_name)
+ )
- def get_all_field_names(self):
- """
- Returns a list of all field names that are possible for this model
- (including reverse relation names). This is used for pretty printing
- debugging output (a list of choices), so any internal-only field names
- are not included.
- """
try:
- cache = self._name_map
- except AttributeError:
- cache = self.init_name_map()
- names = sorted(cache.keys())
- # Internal-only names end with "+" (symmetrical m2m related names being
- # the main example). Trim them.
- return [val for val in names if not val.endswith('+')]
+ if m2m_in_kwargs:
+ # Previous API does not allow searching reverse fields.
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
- def init_name_map(self):
- """
- Initialises the field name -> field object mapping.
- """
- cache = {}
- # We intentionally handle related m2m objects first so that symmetrical
- # m2m accessor names can be overridden, if necessary.
- for f, model in self.get_all_related_m2m_objects_with_model():
- cache[f.field.related_query_name()] = (f, model, False, True)
- for f, model in self.get_all_related_objects_with_model():
- cache[f.field.related_query_name()] = (f, model, False, False)
- for f, model in self.get_m2m_with_model():
- cache[f.name] = cache[f.attname] = (f, model, True, True)
- for f, model in self.get_fields_with_model():
- cache[f.name] = cache[f.attname] = (f, model, True, False)
- for f in self.virtual_fields:
- if f.rel:
- cache[f.name] = cache[f.attname] = (
- f, None if f.model == self.model else f.model, True, False)
- if apps.ready:
- self._name_map = cache
- return cache
+ # Retrieve field instance by name from cached or just-computed
+ # field map.
+ return self.fields_map[field_name]
+ except KeyError:
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
+ @raise_deprecation(suggested_alternative="get_field()")
+ def get_field_by_name(self, name):
+ return self._map_model_details(self.get_field(name))
+
+ @raise_deprecation(suggested_alternative="get_fields()")
+ def get_all_field_names(self):
+ names = set()
+ fields = self.get_fields()
+ for field in fields:
+ # For backwards compatibility GenericForeignKey should not be
+ # included in the results.
+ if field.is_relation and field.one_to_many and field.related_model is None:
+ continue
+
+ names.add(field.name)
+ if hasattr(field, 'attname'):
+ names.add(field.attname)
+ return list(names)
+
+ @raise_deprecation(suggested_alternative="get_fields()")
def get_all_related_objects(self, local_only=False, include_hidden=False,
include_proxy_eq=False):
- return [k for k, v in self.get_all_related_objects_with_model(
- local_only=local_only, include_hidden=include_hidden,
- include_proxy_eq=include_proxy_eq)]
- def get_all_related_objects_with_model(self, local_only=False,
- include_hidden=False,
- include_proxy_eq=False):
- """
- Returns a list of (related-object, model) pairs. Similar to
- get_fields_with_model().
- """
- try:
- self._related_objects_cache
- except AttributeError:
- self._fill_related_objects_cache()
- predicates = []
- if local_only:
- predicates.append(lambda k, v: not v)
- if not include_hidden:
- predicates.append(lambda k, v: not k.field.rel.is_hidden())
- cache = (self._related_objects_proxy_cache if include_proxy_eq
- else self._related_objects_cache)
- return [t for t in cache.items() if all(p(*t) for p in predicates)]
+ include_parents = local_only is False
+ fields = self._get_fields(
+ forward=False, reverse=True,
+ include_parents=include_parents,
+ include_hidden=include_hidden,
+ )
+ fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField))
- def _fill_related_objects_cache(self):
- cache = OrderedDict()
- parent_list = self.get_parent_list()
- for parent in self.parents:
- for obj, model in parent._meta.get_all_related_objects_with_model(include_hidden=True):
- if (obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.model not in parent_list:
- continue
- if not model:
- cache[obj] = parent
- else:
- cache[obj] = model
- # Collect also objects which are in relation to some proxy child/parent of self.
- proxy_cache = cache.copy()
- for klass in self.apps.get_models(include_auto_created=True):
- if not klass._meta.swapped:
- for f in klass._meta.local_fields + klass._meta.virtual_fields:
- if (hasattr(f, 'rel') and f.rel and not isinstance(f.rel.to, six.string_types)
- and f.generate_reverse_relation):
- if self == f.rel.to._meta:
- cache[f.rel] = None
- proxy_cache[f.rel] = None
- elif self.concrete_model == f.rel.to._meta.concrete_model:
- proxy_cache[f.rel] = None
- self._related_objects_cache = cache
- self._related_objects_proxy_cache = proxy_cache
+ if include_proxy_eq:
+ children = chain.from_iterable(c._relation_tree
+ for c in self.concrete_model._meta.proxied_children
+ if c is not self)
+ relations = (f.rel for f in children
+ if include_hidden or not f.rel.field.rel.is_hidden())
+ fields = chain(fields, relations)
+ return list(fields)
+ @raise_deprecation(suggested_alternative="get_fields()")
+ def get_all_related_objects_with_model(self, local_only=False, include_hidden=False,
+ include_proxy_eq=False):
+ return [
+ self._map_model(f) for f in self.get_all_related_objects(
+ local_only=local_only,
+ include_hidden=include_hidden,
+ include_proxy_eq=include_proxy_eq,
+ )
+ ]
+
+ @raise_deprecation(suggested_alternative="get_fields()")
def get_all_related_many_to_many_objects(self, local_only=False):
- try:
- cache = self._related_many_to_many_cache
- except AttributeError:
- cache = self._fill_related_many_to_many_cache()
- if local_only:
- return [k for k, v in cache.items() if not v]
- return list(cache)
+ fields = self._get_fields(
+ forward=False, reverse=True,
+ include_parents=local_only is not True, include_hidden=True
+ )
+ return [obj for obj in fields if isinstance(obj.field, ManyToManyField)]
+ @raise_deprecation(suggested_alternative="get_fields()")
def get_all_related_m2m_objects_with_model(self):
- """
- Returns a list of (related-m2m-object, model) pairs. Similar to
- get_fields_with_model().
- """
- try:
- cache = self._related_many_to_many_cache
- except AttributeError:
- cache = self._fill_related_many_to_many_cache()
- return list(six.iteritems(cache))
-
- def _fill_related_many_to_many_cache(self):
- cache = OrderedDict()
- parent_list = self.get_parent_list()
- for parent in self.parents:
- for obj, model in parent._meta.get_all_related_m2m_objects_with_model():
- if obj.field.creation_counter < 0 and obj.model not in parent_list:
- continue
- if not model:
- cache[obj] = parent
- else:
- cache[obj] = model
- for klass in self.apps.get_models():
- if not klass._meta.swapped:
- for f in klass._meta.local_many_to_many:
- if (f.rel
- and not isinstance(f.rel.to, six.string_types)
- and self == f.rel.to._meta):
- cache[f.rel] = None
- if apps.ready:
- self._related_many_to_many_cache = cache
- return cache
+ fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
+ return [self._map_model(obj) for obj in fields if isinstance(obj.field, ManyToManyField)]
def get_base_chain(self, model):
"""
@@ -605,3 +665,173 @@ class Options(object):
# of the chain to the ancestor is that parent
# links
return self.parents[parent] or parent_link
+
+ def _populate_directed_relation_graph(self):
+ """
+ This method is used by each model to find its reverse objects. As this
+ method is very expensive and is accessed frequently (it looks up every
+ field in a model, in every app), it is computed on first access and then
+ is set as a property on every model.
+ """
+ related_objects_graph = defaultdict(list)
+
+ all_models = self.apps.get_models(include_auto_created=True)
+ for model in all_models:
+ fields_with_relations = (
+ f for f in model._meta._get_fields(reverse=False)
+ if f.is_relation and f.related_model is not None
+ )
+ if model._meta.auto_created:
+ fields_with_relations = (
+ f for f in fields_with_relations
+ if not f.many_to_many
+ )
+
+ for f in fields_with_relations:
+ if not isinstance(f.rel.to, six.string_types):
+ # Set options_instance -> field
+ related_objects_graph[f.rel.to._meta].append(f)
+
+ for model in all_models:
+ # Set the relation_tree using the internal __dict__. In this way
+ # we avoid calling the cached property. In attribute lookup,
+ # __dict__ takes precedence over a data descriptor (such as
+ # @cached_property). This means that the _meta._relation_tree is
+ # only called if related_objects is not in __dict__.
+ related_objects = related_objects_graph[model._meta]
+
+ # If related_objects are empty, it makes sense to set
+ # EMPTY_RELATION_TREE. This will avoid allocating multiple empty
+ # relation trees.
+ relation_tree = EMPTY_RELATION_TREE
+ if related_objects:
+ relation_tree = related_objects
+ model._meta.__dict__['_relation_tree'] = relation_tree
+
+ @cached_property
+ def _relation_tree(self):
+ # If cache is not present, populate the cache
+ self._populate_directed_relation_graph()
+ # It may happen, often when the registry is not ready, that a not yet
+ # registered model is queried. In this very rare case we simply return
+ # an EMPTY_RELATION_TREE. When the registry will be ready, cache will
+ # be flushed and this model will be computed properly.
+ return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE)
+
+ def _expire_cache(self, forward=True, reverse=True):
+ # This method is usually called by apps.cache_clear(), when the
+ # registry is finalized, or when a new field is added.
+ properties_to_expire = []
+ if forward:
+ properties_to_expire.extend(self.FORWARD_PROPERTIES)
+ if reverse and not self.abstract:
+ properties_to_expire.extend(self.REVERSE_PROPERTIES)
+
+ for cache_key in properties_to_expire:
+ try:
+ delattr(self, cache_key)
+ except AttributeError:
+ pass
+
+ self._get_fields_cache = {}
+
+ def get_fields(self, include_parents=True, include_hidden=False):
+ """
+ Returns a list of fields associated to the model. By default will only
+ return forward fields. This can be changed by enabling or disabling
+ field types using the parameters:
+
+ - include_parents: include fields derived from inheritance
+ - include_hidden: include fields that have a related_name that
+ starts with a "+"
+ """
+ return self._get_fields(include_parents=include_parents, include_hidden=include_hidden)
+
+ def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False,
+ export_ordered_set=False):
+ # This helper function is used to allow recursion in ``get_fields()``
+ # implementation and to provide a fast way for Django's internals to
+ # access specific subsets of fields.
+
+ # Creates a cache key composed of all arguments
+ cache_key = (forward, reverse, include_parents, include_hidden, export_ordered_set)
+ try:
+ # In order to avoid list manipulation. Always return a shallow copy
+ # of the results.
+ return self._get_fields_cache[cache_key]
+ except KeyError:
+ pass
+
+ # Using an OrderedDict preserves the order of insertion. This is
+ # important when displaying a ModelForm or the contrib.admin panel
+ # and no specific ordering is provided.
+ fields = OrderedDict()
+ options = {
+ 'include_parents': include_parents,
+ 'include_hidden': include_hidden,
+ 'export_ordered_set': True,
+ }
+
+ # Abstract models cannot hold reverse fields.
+ if reverse and not self.abstract:
+ if include_parents:
+ parent_list = self.get_parent_list()
+ # Recursively call _get_fields() on each parent, with the same
+ # options provided in this call.
+ for parent in self.parents:
+ for obj, _ in six.iteritems(parent._meta._get_fields(forward=False, **options)):
+ if obj.many_to_many:
+ # In order for a reverse ManyToManyRel object to be
+ # valid, its creation counter must be > 0 and must
+ # be in the parent list.
+ if not (obj.field.creation_counter < 0 and obj.related_model not in parent_list):
+ fields[obj] = True
+
+ elif not ((obj.field.creation_counter < 0 or obj.field.rel.parent_link)
+ and obj.related_model not in parent_list):
+ fields[obj] = True
+
+ # Tree is computed once and cached until the app cache is expired.
+ # It is composed of a list of fields pointing to the current model
+ # from other models. If the model is a proxy model, then we also
+ # add the concrete model.
+ all_fields = (
+ self._relation_tree if not self.proxy else
+ chain(self._relation_tree, self.concrete_model._meta._relation_tree)
+ )
+
+ # Pull out all related objects from forward fields
+ for field in (f.rel for f in all_fields):
+ # If hidden fields should be included or the relation is not
+ # intentionally hidden, add to the fields dict.
+ if include_hidden or not field.hidden:
+ fields[field] = True
+ if forward:
+ if include_parents:
+ for parent in self.parents:
+ # Add the forward fields of each parent.
+ fields.update(parent._meta._get_fields(reverse=False, **options))
+ fields.update(
+ (field, True,)
+ for field in chain(self.local_fields, self.local_many_to_many)
+ )
+
+ if not export_ordered_set:
+ # By default, fields contains field instances as keys and all
+ # possible names if the field instance as values. When
+ # _get_fields() is called, we only want to return field instances,
+ # so we just preserve the keys.
+ fields = list(fields.keys())
+
+ # Virtual fields are not inheritable, therefore they are inserted
+ # only when the recursive _get_fields() call comes to an end.
+ if forward:
+ fields.extend(self.virtual_fields)
+ fields = make_immutable_fields_list("get_fields()", fields)
+
+ # Store result into cache for later access
+ self._get_fields_cache[cache_key] = fields
+
+ # In order to avoid list manipulation. Always
+ # return a shallow copy of the results
+ return fields
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 318e3ecf84..7a447f0313 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -252,9 +252,8 @@ class QuerySet(object):
# If only/defer clauses have been specified,
# build the list of fields that are to be loaded.
if only_load:
- for field, model in self.model._meta.get_concrete_fields_with_model():
- if model is None:
- model = self.model
+ for field in self.model._meta.concrete_fields:
+ model = field.model._meta.model
try:
if field.name in only_load[model]:
# Add a field that has been explicitly included
@@ -818,7 +817,7 @@ class QuerySet(object):
obj = self._clone()
names = getattr(self, '_fields', None)
if names is None:
- names = set(self.model._meta.get_all_field_names())
+ names = {f.name for f in self.model._meta.get_fields()}
# Add the annotations to the query
for alias, annotation in annotations.items():
@@ -1329,7 +1328,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
skip = set()
init_list = []
# Build the list of fields that *haven't* been requested
- for field, model in klass._meta.get_concrete_fields_with_model():
+ for field in klass._meta.concrete_fields:
+ model = field.model._meta.concrete_model
if from_parent and model and issubclass(from_parent, model):
# Avoid loading fields already loaded for parent model for
# child models.
@@ -1381,18 +1381,19 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
reverse_related_fields = []
if restricted:
- for o in klass._meta.get_all_related_objects():
+ for o in klass._meta.related_objects:
if o.field.unique and select_related_descend(o.field, restricted, requested,
- only_load.get(o.model), reverse=True):
+ only_load.get(o.related_model), reverse=True):
next = requested[o.field.related_query_name()]
- parent = klass if issubclass(o.model, klass) else None
- klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth + 1,
+ parent = klass if issubclass(o.related_model, klass) else None
+ klass_info = get_klass_info(o.related_model, max_depth=max_depth, cur_depth=cur_depth + 1,
requested=next, only_load=only_load, from_parent=parent)
reverse_related_fields.append((o.field, klass_info))
if field_names:
pk_idx = field_names.index(klass._meta.pk.attname)
else:
- pk_idx = klass._meta.pk_index()
+ meta = klass._meta
+ pk_idx = meta.concrete_fields.index(meta.pk)
return klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx
@@ -1485,7 +1486,10 @@ def get_cached_row(row, index_start, using, klass_info, offset=0,
for f, klass_info in reverse_related_fields:
# Transfer data from this object to childs.
parent_data = []
- for rel_field, rel_model in klass_info[0]._meta.get_fields_with_model():
+ for rel_field in klass_info[0]._meta.fields:
+ rel_model = rel_field.model._meta.concrete_model
+ if rel_model == klass_info[0]._meta.model:
+ rel_model = None
if rel_model is not None and isinstance(obj, rel_model):
parent_data.append((rel_field, getattr(obj, rel_field.attname)))
# Recursively retrieve the data for the related object
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 69bc878caa..e8b6cfb8c1 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -109,7 +109,7 @@ class DeferredAttribute(object):
# self.field_name is the attname of the field, but only() takes the
# actual name, so we need to translate it here.
try:
- f = opts.get_field_by_name(self.field_name)[0]
+ f = opts.get_field(self.field_name)
except FieldDoesNotExist:
f = [f for f in opts.fields if f.attname == self.field_name][0]
name = f.name
@@ -136,7 +136,7 @@ class DeferredAttribute(object):
field is a primary key field.
"""
opts = instance._meta
- f = opts.get_field_by_name(name)[0]
+ f = opts.get_field(name)
link_field = opts.get_ancestor_link(f.model)
if f.primary_key and f != link_field:
return getattr(instance, link_field.attname)
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index e8948cb9e1..1c0b99e897 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -298,7 +298,12 @@ class SQLCompiler(object):
# be used by local fields.
seen_models = {None: start_alias}
- for field, model in opts.get_concrete_fields_with_model():
+ for field in opts.concrete_fields:
+ model = field.model._meta.concrete_model
+ # A proxy model will have a different model and concrete_model. We
+ # will assign None if the field belongs to this model.
+ if model == opts.model:
+ model = None
if from_parent and model is not None and issubclass(from_parent, model):
# Avoid loading data for already loaded parents.
continue
@@ -601,10 +606,10 @@ class SQLCompiler(object):
connections to the root model).
"""
def _get_field_choices():
- direct_choices = (f.name for (f, _) in opts.get_fields_with_model() if f.rel)
+ direct_choices = (f.name for f in opts.fields if f.is_relation)
reverse_choices = (
f.field.related_query_name()
- for f in opts.get_all_related_objects() if f.field.unique
+ for f in opts.related_objects if f.field.unique
)
return chain(direct_choices, reverse_choices)
@@ -628,12 +633,13 @@ class SQLCompiler(object):
else:
restricted = False
- for f, model in opts.get_fields_with_model():
+ for f in opts.fields:
+ field_model = f.model._meta.concrete_model
fields_found.add(f.name)
if restricted:
next = requested.get(f.name, {})
- if not f.rel:
+ if not f.is_relation:
# If a non-related field is used like a relation,
# or if a single non-relational field is given.
if next or (cur_depth == 1 and f.name in requested):
@@ -647,10 +653,6 @@ class SQLCompiler(object):
else:
next = False
- # The get_fields_with_model() returns None for fields that live
- # in the field's local model. So, for those fields we want to use
- # the f.model - that is the field's local model.
- field_model = model or f.model
if not select_related_descend(f, restricted, requested,
only_load.get(field_model)):
continue
@@ -666,9 +668,9 @@ class SQLCompiler(object):
if restricted:
related_fields = [
- (o.field, o.model)
- for o in opts.get_all_related_objects()
- if o.field.unique
+ (o.field, o.related_model)
+ for o in opts.related_objects
+ if o.field.unique and not o.many_to_many
]
for f, model in related_fields:
if not select_related_descend(f, restricted, requested,
@@ -760,7 +762,7 @@ class SQLCompiler(object):
if self.query.select:
fields = [f.field for f in self.query.select]
elif self.query.default_cols:
- fields = self.query.get_meta().concrete_fields
+ fields = list(self.query.get_meta().concrete_fields)
else:
fields = []
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index eb16419091..1d1dbd8162 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -11,6 +11,7 @@ from itertools import count, product
from collections import Mapping, OrderedDict
import copy
+from itertools import chain
import warnings
from django.core.exceptions import FieldDoesNotExist, FieldError
@@ -33,6 +34,13 @@ from django.utils.tree import Node
__all__ = ['Query', 'RawQuery']
+def get_field_names_from_opts(opts):
+ return set(chain.from_iterable(
+ (f.name, f.attname) if f.concrete else (f.name,)
+ for f in opts.get_fields()
+ ))
+
+
class RawQuery(object):
"""
A single raw SQL query
@@ -593,9 +601,9 @@ class Query(object):
opts = orig_opts
for name in parts[:-1]:
old_model = cur_model
- source = opts.get_field_by_name(name)[0]
+ source = opts.get_field(name)
if is_reverse_o2o(source):
- cur_model = source.model
+ cur_model = source.related_model
else:
cur_model = source.rel.to
opts = cur_model._meta
@@ -605,8 +613,11 @@ class Query(object):
if not is_reverse_o2o(source):
must_include[old_model].add(source)
add_to_dict(must_include, cur_model, opts.pk)
- field, model, _, _ = opts.get_field_by_name(parts[-1])
- if model is None:
+ field = opts.get_field(parts[-1])
+ is_reverse_object = field.auto_created and not field.concrete
+ model = field.related_model if is_reverse_object else field.model
+ model = model._meta.concrete_model
+ if model == opts.model:
model = cur_model
if not is_reverse_o2o(field):
add_to_dict(seen, model, field)
@@ -618,10 +629,11 @@ class Query(object):
# models.
workset = {}
for model, values in six.iteritems(seen):
- for field, m in model._meta.get_fields_with_model():
+ for field in model._meta.fields:
if field in values:
continue
- add_to_dict(workset, m or model, field)
+ m = field.model._meta.concrete_model
+ add_to_dict(workset, m, field)
for model, values in six.iteritems(must_include):
# If we haven't included a model in workset, we don't add the
# corresponding must_include fields for that model, since an
@@ -934,8 +946,9 @@ class Query(object):
root_alias = self.tables[0]
seen = {None: root_alias}
- for field, model in opts.get_fields_with_model():
- if model not in seen:
+ for field in opts.fields:
+ model = field.model._meta.concrete_model
+ if model is not opts.model and model not in seen:
self.join_parent_model(opts, model, root_alias, seen)
self.included_inherited_models = seen
@@ -1368,7 +1381,19 @@ class Query(object):
if name == 'pk':
name = opts.pk.name
try:
- field, model, _, _ = opts.get_field_by_name(name)
+ field = opts.get_field(name)
+
+ # Fields that contain one-to-many relations with a generic
+ # model (like a GenericForeignKey) cannot generate reverse
+ # relations and therefore cannot be used for reverse querying.
+ if field.is_relation and not field.related_model:
+ raise FieldError(
+ "Field %r does not generate an automatic reverse "
+ "relation and therefore cannot be used for reverse "
+ "querying. If it is a GenericForeignKey, consider "
+ "adding a GenericRelation." % name
+ )
+ model = field.model._meta.concrete_model
except FieldDoesNotExist:
# is it an annotation?
if self._annotations and name in self._annotations:
@@ -1382,14 +1407,15 @@ class Query(object):
# one step.
pos -= 1
if pos == -1 or fail_on_missing:
- available = opts.get_all_field_names() + list(self.annotation_select)
+ field_names = list(get_field_names_from_opts(opts))
+ available = sorted(field_names + list(self.annotation_select))
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (name, ", ".join(available)))
break
# Check if we need any joins for concrete inheritance cases (the
# field lives in parent, but we are currently in one of its
# children)
- if model:
+ if model is not opts.model:
# The field lives on a base class of the current model.
# Skip the chain of proxy to the concrete proxied model
proxied_model = opts.concrete_model
@@ -1432,7 +1458,7 @@ class Query(object):
return path, final_field, targets, names[pos + 1:]
def raise_field_error(self, opts, name):
- available = opts.get_all_field_names() + list(self.annotation_select)
+ available = list(get_field_names_from_opts(opts)) + list(self.annotation_select)
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (name, ", ".join(available)))
@@ -1693,7 +1719,7 @@ class Query(object):
# from the model on which the lookup failed.
raise
else:
- names = sorted(opts.get_all_field_names() + list(self.extra)
+ names = sorted(list(get_field_names_from_opts(opts)) + list(self.extra)
+ list(self.annotation_select))
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (name, ", ".join(names)))
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 12bde13bf3..bae9f11c23 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -120,13 +120,15 @@ class UpdateQuery(Query):
"""
values_seq = []
for name, val in six.iteritems(values):
- field, model, direct, m2m = self.get_meta().get_field_by_name(name)
- if not direct or m2m:
+ field = self.get_meta().get_field(name)
+ direct = not (field.auto_created and not field.concrete) or not field.concrete
+ model = field.model._meta.concrete_model
+ if not direct or (field.is_relation and field.many_to_many):
raise FieldError(
'Cannot update model field %r (only non-relations and '
'foreign keys permitted).' % field
)
- if model:
+ if model is not self.get_meta().model:
self.add_related_update(model, field, val)
continue
values_seq.append((field, model, val))
diff --git a/django/forms/models.py b/django/forms/models.py
index c57c8af0b9..443d0559a6 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -6,6 +6,7 @@ and database field objects.
from __future__ import unicode_literals
from collections import OrderedDict
+from itertools import chain
import warnings
from django.core.exceptions import (
@@ -89,7 +90,7 @@ def save_instance(form, instance, fields=None, fail_message='saved',
# Note that for historical reasons we want to include also
# virtual_fields here. (GenericRelation was previously a fake
# m2m field).
- for f in opts.many_to_many + opts.virtual_fields:
+ for f in chain(opts.many_to_many, opts.virtual_fields):
if not hasattr(f, 'save_form_data'):
continue
if fields and f.name not in fields:
@@ -127,7 +128,7 @@ def model_to_dict(instance, fields=None, exclude=None):
from django.db.models.fields.related import ManyToManyField
opts = instance._meta
data = {}
- for f in opts.concrete_fields + opts.virtual_fields + opts.many_to_many:
+ for f in chain(opts.concrete_fields, opts.virtual_fields, opts.many_to_many):
if not getattr(f, 'editable', False):
continue
if fields and f.name not in fields:
@@ -186,7 +187,7 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
from django.db.models.fields import Field as ModelField
sortable_virtual_fields = [f for f in opts.virtual_fields
if isinstance(f, ModelField)]
- for f in sorted(opts.concrete_fields + sortable_virtual_fields + opts.many_to_many):
+ for f in sorted(chain(opts.concrete_fields, sortable_virtual_fields, opts.many_to_many)):
if not getattr(f, 'editable', False):
continue
if fields is not None and f.name not in fields: