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!
This commit is contained in:
Daniel Pyrathon 2015-01-06 19:16:35 -05:00 committed by Tim Graham
parent 749d23251b
commit fb48eb0581
58 changed files with 2851 additions and 1195 deletions

View File

@ -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 ###

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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):
"""

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()),
)

View File

@ -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):

View File

@ -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):

View File

@ -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(

View File

@ -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:

View File

@ -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

View File

@ -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}
)

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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]
"""
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.
"""
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):
"""
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.
"""
try:
self._field_cache
except AttributeError:
self._fill_fields_cache()
return self._field_cache
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)
)
@cached_property
def related_objects(self):
"""
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.
"""
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)
)
@raise_deprecation(suggested_alternative="get_fields()")
def get_m2m_with_model(self):
"""
The many-to-many version of get_fields_with_model().
"""
try:
self._m2m_cache
except AttributeError:
self._fill_m2m_cache()
return list(six.iteritems(self._m2m_cache))
return [self._map_model(f) for f in self.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
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))
def get_field_by_name(self, name):
"""
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).
Uses a cache internally, so after the first access, this is very fast.
"""
try:
@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:
return self._name_map[name]
res[field.attname] = field
except AttributeError:
cache = self.init_name_map()
return cache[name]
except KeyError:
raise FieldDoesNotExist('%s has no field named %r'
% (self.object_name, name))
pass
return res
def get_all_field_names(self):
@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 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.
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.
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:
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('+')]
# 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]
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
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:
# 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)
)
try:
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))
# 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_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))
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):
"""
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)]
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
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

View File

@ -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

View File

@ -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)

View File

@ -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 = []

View File

@ -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)))

View File

@ -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))

View File

@ -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:

View File

@ -217,9 +217,9 @@ The ``Field.__init__()`` method takes the following parameters:
* :attr:`~django.db.models.Field.db_tablespace`: Only for index creation, if the
backend supports :doc:`tablespaces </topics/db/tablespaces>`. You can usually
ignore this option.
* ``auto_created``: ``True`` if the field was automatically created, as for the
:class:`~django.db.models.OneToOneField` used by model inheritance. For
advanced use only.
* :attr:`~django.db.models.Field.auto_created`: ``True`` if the field was
automatically created, as for the :class:`~django.db.models.OneToOneField`
used by model inheritance. For advanced use only.
All of the options without an explanation in the above list have the same
meaning they do for normal Django fields. See the :doc:`field documentation

View File

@ -56,6 +56,19 @@ details on these changes.
* ``django.template.resolve_variable`` will be removed.
* The following private APIs will be removed from
:class:`django.db.models.options.Options` (``Model._meta``):
* ``get_field_by_name()``
* ``get_all_field_names()``
* ``get_fields_with_model()``
* ``get_concrete_fields_with_model()``
* ``get_m2m_with_model()``
* ``get_all_related_objects()``
* ``get_all_related_objects_with_model()``
* ``get_all_related_many_to_many_objects()``
* ``get_all_related_m2m_objects_with_model()``
* The ``error_message`` argument of ``django.forms.RegexField`` will be removed.
* The ``unordered_list`` filter will no longer support old style lists.

View File

@ -1790,3 +1790,95 @@ Field API reference
This method must be added to fields prior to 1.7 to migrate its data
using :doc:`/topics/migrations`.
.. _model-field-attributes:
=========================
Field attribute reference
=========================
.. versionadded:: 1.8
Every ``Field`` instance contains several attributes that allow
introspecting its behavior. Use these attributes instead of ``isinstance``
checks when you need to write code that depends on a field's functionality.
These attributes can be used together with the :ref:`Model._meta API
<model-meta-field-api>` to narrow down a search for specific field types.
Custom model fields should implement these flags.
Attributes for fields
=====================
.. attribute:: Field.auto_created
Boolean flag that indicates if the field was automatically created, such
as the ``OneToOneField`` used by model inheritance.
.. attribute:: Field.concrete
Boolean flag that indicates if the field has a database column associated
with it.
.. attribute:: Field.hidden
Boolean flag that indicates if a field is used to back another non-hidden
field's functionality (e.g. the ``content_type`` and ``object_id`` fields
that make up a ``GenericForeignKey``). The ``hidden`` flag is used to
distinguish what constitutes the public subset of fields on the model from
all the fields on the model.
.. note::
:meth:`Options.get_fields()
<django.db.models.options.Options.get_fields()>`
excludes hidden fields by default. Pass in ``include_hidden=True`` to
return hidden fields in the results.
.. attribute:: Field.is_relation
Boolean flag that indicates if a field contains references to one or
more other models for its functionality (e.g. ``ForeignKey``,
``ManyToManyField``, ``OneToOneField``, etc.).
.. attribute:: Field.model
Returns the model on which the field is defined. If a field is defined on
a superclass of a model, ``model`` will refer to the superclass, not the
class of the instance.
Attributes for fields with relations
====================================
These attributes are used to query for the cardinality and other details of a
relation. These attribute are present on all fields; however, they will only
have meaningful values if the field is a relation type
(:attr:`Field.is_relation=True <Field.is_relation>`).
.. attribute:: Field.one_to_many
Boolean flag that is ``True`` if the field has a one-to-many relation, such
as a ``ForeignKey``; ``False`` otherwise.
.. attribute:: Field.one_to_one
Boolean flag that is ``True`` if the field has a one-to-one relation, such
as a ``OneToOneField``; ``False`` otherwise.
.. attribute:: Field.many_to_many
Boolean flag that is ``True`` if the field has a many-to-many relation;
``False`` otherwise. The only field included with Django where this is
``True`` is ``ManyToManyField``.
.. attribute:: Field.many_to_one
Boolean flag that is ``True`` if the field has a many-to-one relation, such
as a ``GenericRelation`` or the reverse of a ``ForeignKey``; ``False``
otherwise.
.. attribute:: Field.related_model
Points to the model the field relates to. For example, ``Author`` in
``ForeignKey(Author)``. If a field has a generic relation (such as a
``GenericForeignKey`` or a ``GenericRelation``) then ``related_model``
will be ``None``.

View File

@ -8,6 +8,7 @@ Model API reference. For introductory material, see :doc:`/topics/db/models`.
:maxdepth: 1
fields
meta
relations
class
options

287
docs/ref/models/meta.txt Normal file
View File

@ -0,0 +1,287 @@
===================
Model ``_meta`` API
===================
.. module:: django.db.models.options
:synopsis: Model meta-class layer
.. class:: Options
The model ``_meta`` API is at the core of the Django ORM. It enables other
parts of the system such as lookups, queries, forms, and the admin to
understand the capabilities of each model. The API is accessible through
the ``_meta`` attribute of each model class, which is an instance of an
``django.db.models.options.Options`` object.
Methods that it provides can be used to:
* Retrieve all field instances of a model
* Retrieve a single field instance of a model by name
.. versionchanged:: 1.8
The Model ``_meta`` API has always existed as a Django internal, but
wasn't formally documented and supported. As part of the effort to
make this API public, some of the already existing API entry points
have changed slightly. A :ref:`migration guide <migrating-old-meta-api>`
has been provided to assist in converting your code to use the new,
official API.
.. _model-meta-field-api:
Field access API
================
Retrieving a single field instance of a model by name
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. method:: Options.get_field(field_name)
Returns the field instance given a name of a field.
``field_name`` can be the name of a field on the model, a field
on an abstract or inherited model, or a field defined on another
model that points to the model. In the latter case, the ``field_name``
will be the ``related_name`` defined by the user or the name automatically
generated by Django itself.
:attr:`Hidden fields <django.db.models.Field.hidden>` cannot be retrieved
by name.
If a field with the given name is not found a
:class:`~django.core.exceptions.FieldDoesNotExist` exception will be
raised.
.. code-block:: python
>>> from django.contrib.auth.models import User
# A field on the model
>>> User._meta.get_field('username')
<django.db.models.fields.CharField: username>
# A field from another model that has a relation with the current model
>>> User._meta.get_field('logentry')
<ManyToOneRel: admin.logentry>
# A non existent field
>>> User._meta.get_field('does_not_exist')
Traceback (most recent call last):
...
FieldDoesNotExist: User has no field named 'does_not_exist'
.. deprecated:: 1.8
:meth:`Options.get_field()` previously accepted a ``many_to_many``
parameter which could be set to ``False`` to avoid searching
``ManyToManyField``\s. The old behavior has been preserved for
backwards compatibility; however, the parameter and this behavior
has been deprecated.
If you wish to filter out ``ManyToManyField``\s, you can inspect the
:attr:`Field.many_to_many <django.db.models.Field.many_to_many>`
attribute after calling ``get_field()``.
Retrieving all field instances of a model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. method:: Options.get_fields(include_parents=True, include_hidden=False)
.. versionadded:: 1.8
Returns a tuple of fields associated with a model. ``get_fields()`` accepts
two parameters that can be used to control which fields are returned:
``include_parents``
``True`` by default. Recursively includes fields defined on parent
classes. If set to ``False``, ``get_fields()`` will only search for
fields declared directly on the current model. Fields from models that
directly inherit from abstract models or proxy classes are considered
to be local, not on the parent.
``include_hidden``
``False`` by default. If set to ``True``, ``get_fields()`` will include
fields that are used to back other field's functionality. This will
also include any fields that have a ``related_name`` (such
as :class:`~django.db.models.ManyToManyField`, or
:class:`~django.db.models.ForeignKey`) that start with a "+".
.. code-block:: python
>>> from django.contrib.auth.models import User
>>> User._meta.get_fields()
(<ManyToOneRel: admin.logentry>,
<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: password>,
<django.db.models.fields.DateTimeField: last_login>,
<django.db.models.fields.BooleanField: is_superuser>,
<django.db.models.fields.CharField: username>,
<django.db.models.fields.CharField: first_name>,
<django.db.models.fields.CharField: last_name>,
<django.db.models.fields.EmailField: email>,
<django.db.models.fields.BooleanField: is_staff>,
<django.db.models.fields.BooleanField: is_active>,
<django.db.models.fields.DateTimeField: date_joined>,
<django.db.models.fields.related.ManyToManyField: groups>,
<django.db.models.fields.related.ManyToManyField: user_permissions>)
# Also include hidden fields.
>>> User._meta.get_fields(include_hidden=True)
(<ManyToOneRel: auth.user_groups>,
<ManyToOneRel: auth.user_user_permissions>,
<ManyToOneRel: admin.logentry>,
<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: password>,
<django.db.models.fields.DateTimeField: last_login>,
<django.db.models.fields.BooleanField: is_superuser>,
<django.db.models.fields.CharField: username>,
<django.db.models.fields.CharField: first_name>,
<django.db.models.fields.CharField: last_name>,
<django.db.models.fields.EmailField: email>,
<django.db.models.fields.BooleanField: is_staff>,
<django.db.models.fields.BooleanField: is_active>,
<django.db.models.fields.DateTimeField: date_joined>,
<django.db.models.fields.related.ManyToManyField: groups>,
<django.db.models.fields.related.ManyToManyField: user_permissions>)
.. _migrating-old-meta-api:
Migrating from the old API
==========================
As part of the formalization of the ``Model._meta`` API (from the
:class:`django.db.models.options.Options` class), a number of methods and
properties have been deprecated and will be removed in Django 2.0.
These old APIs can be replicated by either:
* invoking :meth:`Options.get_field()
<django.db.models.options.Options.get_field()>`, or;
* invoking :meth:`Options.get_fields()
<django.db.models.options.Options.get_fields()>` to retrieve a list of all
fields, and then filtering this list using the :ref:`field attributes
<model-field-attributes>` that describe (or retrieve, in the case of
``_with_model`` variants) the properties of the desired fields.
Although it's possible to make strictly equivalent replacements of the old
methods, that might not be the best approach. Taking the time to refactor any
field loops to make better use of the new API - and possibly include fields
that were previously excluded - will almost certainly result in better code.
Assuming you have a model named ``MyModel``, the following substitutions
can be made to convert your code to the new API:
* ``MyModel._meta.get_field(name)``::
f = MyModel._meta.get_field(name)
then check if:
- ``f.auto_created == False``, because the new ``get_field()``
API will find "reverse" relations), and:
- ``f.is_relation and f.related_model is None``, because the new
``get_field()`` API will find
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` relations;
* ``MyModel._meta.get_field_by_name(name)``:
``get_field_by_name()`` returned four values:
``(field, model, direct, m2m)``:
- ``field`` can be found by ``MyModel._meta.get_field(name)``
- ``model`` can be found through the
:attr:`~django.db.models.Field.model` attribute on the field.
- ``direct`` can be found by: ``not field.auto_created or field.concrete``
The :attr:`~django.db.models.Field.auto_created` check excludes
all "forward" and "reverse" relations that are created by Django, but
this also includes ``AutoField`` and ``OneToOneField`` on proxy models.
We avoid filtering out these attributes using the
:attr:`concrete <django.db.models.Field.concrete>` attribute.
- ``m2m`` can be found through the
:attr:`~django.db.models.Field.many_to_many` attribute on the field.
* ``MyModel._meta.get_fields_with_model()``::
[
(f, f.model if f.model != MyModel else None)
for f in MyModel._meta.get_fields()
if not f.is_relation
or f.one_to_one
or (f.one_to_many and f.related_model)
]
* ``MyModel._meta.get_concrete_fields_with_model()``::
[
(f, f.model if f.model != MyModel else None)
for f in MyModel._meta.get_fields()
if f.concrete and (
not f.is_relation
or f.one_to_one
or (f.one_to_many and f.related_model)
)
]
* ``MyModel._meta.get_m2m_with_model()``::
[
(f, f.model if f.model != MyModel else None)
for f in MyModel._meta.get_fields()
if f.many_to_many and not f.auto_created
]
* ``MyModel._meta.get_all_related_objects()``::
[
f for f in MyModel._meta.get_fields()
if f.many_to_one and f.auto_created
]
* ``MyModel._meta.get_all_related_objects_with_model()``::
[
(f, f.model if f.model != MyModel else None)
for f in MyModel._meta.get_fields()
if f.many_to_one and f.auto_created
]
* ``MyModel._meta.get_all_related_many_to_many_objects()``::
[
f for f in MyModel._meta.get_fields(include_hidden=True)
if f.many_to_many and f.auto_created
]
* ``MyModel._meta.get_all_related_m2m_objects_with_model()``::
[
(f, f.model if f.model != MyModel else None)
for f in MyModel._meta.get_fields(include_hidden=True)
if f.many_to_many and f.auto_created
]
* ``MyModel._meta.get_all_field_names()``::
from itertools import chain
list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in MyModel._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.one_to_many and field.related_model is None)
)))
This provides a 100% backwards compatible replacement, ensuring that both
field names and attribute names ``ForeignKey``\s are included, but fields
associated with ``GenericForeignKey``\s are not. A simpler version would be::
[f.name for f in MyModel._meta.get_fields()]
While this isn't 100% backwards compatible, it may be sufficient in many
situations.

View File

@ -29,6 +29,22 @@ Like Django 1.7, Django 1.8 requires Python 2.7 or above, though we
What's new in Django 1.8
========================
``Model._meta`` API
~~~~~~~~~~~~~~~~~~~
Django now has a formalized API for :doc:`Model._meta </ref/models/meta>`,
providing an officially supported way to :ref:`retrieve fields
<model-meta-field-api>` and filter fields based on their :ref:`attributes
<model-field-attributes>`.
The ``Model._meta`` object has been part of Django since the days of pre-0.96
"Magic Removal" -- it just wasn't an official, stable API. In recognition of
this, we've endeavored to maintain backwards-compatibility with the old
API endpoint where possible. However, API endpoints that aren't part of the
new official API have been deprecated and will eventually be removed. A
:ref:`guide to migrating from the old API to the new API
<migrating-old-meta-api>` has been provided.
Security enhancements
~~~~~~~~~~~~~~~~~~~~~
@ -998,6 +1014,26 @@ Miscellaneous
Features deprecated in 1.8
==========================
Selected methods in ``django.db.models.options.Options``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As part of the formalization of the ``Model._meta`` API (from the
:class:`django.db.models.options.Options` class), a number of methods have been
deprecated and will be removed in in Django 2.0:
* ``get_all_field_names()``
* ``get_all_related_objects()``
* ``get_all_related_objects_with_model()``
* ``get_all_related_many_to_many_objects()``
* ``get_all_related_m2m_objects_with_model()``
* ``get_concrete_fields_with_model()``
* ``get_field_by_name()``
* ``get_fields_with_model()``
* ``get_m2m_with_model()``
A :ref:`migration guide <migrating-old-meta-api>` has been provided to assist
in converting your code from the old API to the new, official API.
Loading ``cycle`` and ``firstof`` template tags from ``future`` library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1020,7 +1020,7 @@ class DBConstraintTestCase(TestCase):
self.assertEqual(models.Object.objects.count(), 2)
self.assertEqual(obj.related_objects.count(), 1)
intermediary_model = models.Object._meta.get_field_by_name("related_objects")[0].rel.through
intermediary_model = models.Object._meta.get_field("related_objects").rel.through
intermediary_model.objects.create(from_object_id=obj.id, to_object_id=12345)
self.assertEqual(obj.related_objects.count(), 1)
self.assertEqual(intermediary_model.objects.count(), 2)

View File

@ -776,7 +776,7 @@ class ModelRefreshTests(TestCase):
class TestRelatedObjectDeprecation(TestCase):
def test_field_related_deprecation(self):
field = SelfRef._meta.get_field_by_name('selfref')[0]
field = SelfRef._meta.get_field('selfref')
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
self.assertIsInstance(field.related, ForeignObjectRel)

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os
import warnings
from django.apps import apps
from django.contrib.sites.models import Site
from django.core import management
from django.db import connection, IntegrityError
@ -76,6 +77,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
])
def test_loading_and_dumping(self):
apps.clear_cache()
Site.objects.all().delete()
# Load fixture 1. Single JSON file, with two objects.
management.call_command('loaddata', 'fixture1.json', verbosity=0)

View File

@ -403,7 +403,8 @@ class GenericRelationsTests(TestCase):
self.assertEqual(tag.content_object.id, diamond.id)
def test_query_content_type(self):
with six.assertRaisesRegex(self, FieldError, "^Cannot resolve keyword 'content_object' into field."):
msg = "Field 'content_object' does not generate an automatic reverse relation"
with self.assertRaisesMessage(FieldError, msg):
TaggedItem.objects.get(content_object='')

View File

@ -260,7 +260,7 @@ class GenericRelationTests(TestCase):
form = GenericRelationForm({'links': None})
self.assertTrue(form.is_valid())
form.save()
links = HasLinkThing._meta.get_field_by_name('links')[0]
links = HasLinkThing._meta.get_field('links')
self.assertEqual(links.save_form_data_calls, 1)
def test_ticket_22998(self):

View File

@ -5,6 +5,12 @@ from .models import Issue, User, UnicodeReferenceModel
class RelatedObjectTests(TestCase):
def test_related_objects_have_name_attribute(self):
for field_name in ('test_issue_client', 'test_issue_cc'):
obj = User._meta.get_field(field_name)
self.assertEqual(field_name, obj.field.related_query_name())
def test_m2m_and_m2o(self):
r = User.objects.create(username="russell")
g = User.objects.create(username="gustav")

View File

@ -437,11 +437,11 @@ class ManyToOneTests(TestCase):
expected_message = "Cannot resolve keyword 'notafield' into field. Choices are: %s"
self.assertRaisesMessage(FieldError,
expected_message % ', '.join(Reporter._meta.get_all_field_names()),
expected_message % ', '.join(sorted(f.name for f in Reporter._meta.get_fields())),
Article.objects.values_list,
'reporter__notafield')
self.assertRaisesMessage(FieldError,
expected_message % ', '.join(['EXTRA'] + Article._meta.get_all_field_names()),
expected_message % ', '.join(['EXTRA'] + sorted(f.name for f in Article._meta.get_fields())),
Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list,
'notafield')

View File

@ -202,8 +202,8 @@ class StateTests(TestCase):
))
new_apps = project_state.apps
self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("name")[0].max_length, 100)
self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("hidden")[0].null, False)
self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field("name").max_length, 100)
self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field("hidden").null, False)
self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)

View File

@ -8,6 +8,11 @@ except ImportError:
Image = None
from django.core.files.storage import FileSystemStorage
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db.models.fields.related import (
ForeignObject, ForeignKey, ManyToManyField, OneToOneField,
)
from django.db import models
from django.db.models.fields.files import ImageFieldFile, ImageField
from django.utils import six
@ -295,6 +300,52 @@ if Image:
height_field='headshot_height',
width_field='headshot_width')
class AllFieldsModel(models.Model):
big_integer = models.BigIntegerField()
binary = models.BinaryField()
boolean = models.BooleanField(default=False)
char = models.CharField(max_length=10)
csv = models.CommaSeparatedIntegerField(max_length=10)
date = models.DateField()
datetime = models.DateTimeField()
decimal = models.DecimalField(decimal_places=2, max_digits=2)
duration = models.DurationField()
email = models.EmailField()
file_path = models.FilePathField()
floatf = models.FloatField()
integer = models.IntegerField()
ip_address = models.IPAddressField()
generic_ip = models.GenericIPAddressField()
null_boolean = models.NullBooleanField()
positive_integer = models.PositiveIntegerField()
positive_small_integer = models.PositiveSmallIntegerField()
slug = models.SlugField()
small_integer = models.SmallIntegerField()
text = models.TextField()
time = models.TimeField()
url = models.URLField()
uuid = models.UUIDField()
fo = ForeignObject(
'self',
from_fields=['abstract_non_concrete_id'],
to_fields=['id'],
related_name='reverse'
)
fk = ForeignKey(
'self',
related_name='reverse2'
)
m2m = ManyToManyField('self')
oto = OneToOneField('self')
object_id = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType)
gfk = GenericForeignKey()
gr = GenericRelation(DataModel)
###############################################################################

View File

@ -0,0 +1,220 @@
from django import test
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation,
)
from django.db import models
from django.db.models.fields.related import (
ForeignObject, ForeignKey, OneToOneField, ManyToManyField,
ManyToOneRel, ForeignObjectRel,
)
from .models import AllFieldsModel
NON_CONCRETE_FIELDS = (
ForeignObject,
GenericForeignKey,
GenericRelation,
)
NON_EDITABLE_FIELDS = (
models.BinaryField,
GenericForeignKey,
GenericRelation,
)
RELATION_FIELDS = (
ForeignKey,
ForeignObject,
ManyToManyField,
OneToOneField,
GenericForeignKey,
GenericRelation,
)
ONE_TO_MANY_CLASSES = {
ForeignObject,
ForeignKey,
GenericForeignKey,
}
MANY_TO_ONE_CLASSES = {
ForeignObjectRel,
ManyToOneRel,
GenericRelation,
}
MANY_TO_MANY_CLASSES = {
ManyToManyField,
}
ONE_TO_ONE_CLASSES = {
OneToOneField,
}
FLAG_PROPERTIES = (
'concrete',
'editable',
'is_relation',
'model',
'hidden',
'one_to_many',
'many_to_one',
'many_to_many',
'one_to_one',
'related_model',
)
FLAG_PROPERTIES_FOR_RELATIONS = (
'one_to_many',
'many_to_one',
'many_to_many',
'one_to_one',
)
class FieldFlagsTests(test.TestCase):
@classmethod
def setUpClass(cls):
super(FieldFlagsTests, cls).setUpClass()
cls.fields = (
list(AllFieldsModel._meta.fields) +
list(AllFieldsModel._meta.virtual_fields)
)
cls.all_fields = (
cls.fields +
list(AllFieldsModel._meta.many_to_many) +
list(AllFieldsModel._meta.virtual_fields)
)
cls.fields_and_reverse_objects = (
cls.all_fields +
list(AllFieldsModel._meta.related_objects)
)
def test_each_field_should_have_a_concrete_attribute(self):
self.assertTrue(all(f.concrete.__class__ == bool for f in self.fields))
def test_each_field_should_have_an_editable_attribute(self):
self.assertTrue(all(f.editable.__class__ == bool for f in self.all_fields))
def test_each_field_should_have_a_has_rel_attribute(self):
self.assertTrue(all(f.is_relation.__class__ == bool for f in self.all_fields))
def test_each_object_should_have_auto_created(self):
self.assertTrue(
all(f.auto_created.__class__ == bool
for f in self.fields_and_reverse_objects)
)
def test_non_concrete_fields(self):
for field in self.fields:
if type(field) in NON_CONCRETE_FIELDS:
self.assertFalse(field.concrete)
else:
self.assertTrue(field.concrete)
def test_non_editable_fields(self):
for field in self.all_fields:
if type(field) in NON_EDITABLE_FIELDS:
self.assertFalse(field.editable)
else:
self.assertTrue(field.editable)
def test_related_fields(self):
for field in self.all_fields:
if type(field) in RELATION_FIELDS:
self.assertTrue(field.is_relation)
else:
self.assertFalse(field.is_relation)
def test_field_names_should_always_be_available(self):
for field in self.fields_and_reverse_objects:
self.assertTrue(field.name)
def test_all_field_types_should_have_flags(self):
for field in self.fields_and_reverse_objects:
for flag in FLAG_PROPERTIES:
self.assertTrue(hasattr(field, flag), "Field %s does not have flag %s" % (field, flag))
if field.is_relation:
true_cardinality_flags = sum(
getattr(field, flag) is True
for flag in FLAG_PROPERTIES_FOR_RELATIONS
)
# If the field has a relation, there should be only one of the
# 4 cardinality flags available.
self.assertEqual(1, true_cardinality_flags)
def test_cardinality_m2m(self):
m2m_type_fields = (
f for f in self.all_fields
if f.is_relation and f.many_to_many
)
# Test classes are what we expect
self.assertEqual(MANY_TO_MANY_CLASSES, {f.__class__ for f in m2m_type_fields})
# Ensure all m2m reverses are m2m
for field in m2m_type_fields:
reverse_field = field.rel
self.assertTrue(reverse_field.is_relation)
self.assertTrue(reverse_field.many_to_many)
self.assertTrue(reverse_field.related_model)
def test_cardinality_o2m(self):
o2m_type_fields = [
f for f in self.fields_and_reverse_objects
if f.is_relation and f.one_to_many
]
# Test classes are what we expect
self.assertEqual(ONE_TO_MANY_CLASSES, {f.__class__ for f in o2m_type_fields})
# Ensure all o2m reverses are m2o
for field in o2m_type_fields:
if field.concrete:
reverse_field = field.rel
self.assertTrue(reverse_field.is_relation and reverse_field.many_to_one)
def test_cardinality_m2o(self):
m2o_type_fields = [
f for f in self.fields_and_reverse_objects
if f.is_relation and f.many_to_one
]
# Test classes are what we expect
self.assertEqual(MANY_TO_ONE_CLASSES, {f.__class__ for f in m2o_type_fields})
# Ensure all m2o reverses are o2m
for obj in m2o_type_fields:
if hasattr(obj, 'field'):
reverse_field = obj.field
self.assertTrue(reverse_field.is_relation and reverse_field.one_to_many)
def test_cardinality_o2o(self):
o2o_type_fields = [
f for f in self.all_fields
if f.is_relation and f.one_to_one
]
# Test classes are what we expect
self.assertEqual(ONE_TO_ONE_CLASSES, {f.__class__ for f in o2o_type_fields})
# Ensure all o2o reverses are o2o
for obj in o2o_type_fields:
if hasattr(obj, 'field'):
reverse_field = obj.field
self.assertTrue(reverse_field.is_relation and reverse_field.one_to_one)
def test_hidden_flag(self):
incl_hidden = set(AllFieldsModel._meta.get_fields(include_hidden=True))
no_hidden = set(AllFieldsModel._meta.get_fields())
fields_that_should_be_hidden = (incl_hidden - no_hidden)
for f in incl_hidden:
self.assertEqual(f in fields_that_should_be_hidden, f.hidden)
def test_model_and_reverse_model_should_equal_on_relations(self):
for field in AllFieldsModel._meta.get_fields():
is_concrete_forward_field = field.concrete and field.related_model
if is_concrete_forward_field:
reverse_field = field.rel
self.assertEqual(field.model, reverse_field.related_model)
self.assertEqual(field.related_model, reverse_field.model)

View File

@ -198,7 +198,7 @@ class ForeignKeyTests(test.TestCase):
self.assertEqual(warnings, expected_warnings)
def test_related_name_converted_to_text(self):
rel_name = Bar._meta.get_field_by_name('a')[0].rel.related_name
rel_name = Bar._meta.get_field('a').rel.related_name
self.assertIsInstance(rel_name, six.text_type)

796
tests/model_meta/results.py Normal file
View File

@ -0,0 +1,796 @@
from .models import (
AbstractPerson, BasePerson, Person, Relating, Relation,
)
TEST_RESULTS = {
'get_all_field_names': {
Person: [
'baseperson_ptr',
'baseperson_ptr_id',
'content_type_abstract',
'content_type_abstract_id',
'content_type_base',
'content_type_base_id',
'content_type_concrete',
'content_type_concrete_id',
'data_abstract',
'data_base',
'data_inherited',
'data_not_concrete_abstract',
'data_not_concrete_base',
'data_not_concrete_inherited',
'fk_abstract',
'fk_abstract_id',
'fk_base',
'fk_base_id',
'fk_inherited',
'fk_inherited_id',
'followers_abstract',
'followers_base',
'followers_concrete',
'following_abstract',
'following_base',
'following_inherited',
'friends_abstract',
'friends_base',
'friends_inherited',
'generic_relation_abstract',
'generic_relation_base',
'generic_relation_concrete',
'id',
'm2m_abstract',
'm2m_base',
'm2m_inherited',
'object_id_abstract',
'object_id_base',
'object_id_concrete',
'relating_basepeople',
'relating_baseperson',
'relating_people',
'relating_person',
],
BasePerson: [
'content_type_abstract',
'content_type_abstract_id',
'content_type_base',
'content_type_base_id',
'data_abstract',
'data_base',
'data_not_concrete_abstract',
'data_not_concrete_base',
'fk_abstract',
'fk_abstract_id',
'fk_base',
'fk_base_id',
'followers_abstract',
'followers_base',
'following_abstract',
'following_base',
'friends_abstract',
'friends_base',
'generic_relation_abstract',
'generic_relation_base',
'id',
'm2m_abstract',
'm2m_base',
'object_id_abstract',
'object_id_base',
'person',
'relating_basepeople',
'relating_baseperson'
],
AbstractPerson: [
'content_type_abstract',
'content_type_abstract_id',
'data_abstract',
'data_not_concrete_abstract',
'fk_abstract',
'fk_abstract_id',
'following_abstract',
'friends_abstract',
'generic_relation_abstract',
'm2m_abstract',
'object_id_abstract',
],
Relating: [
'basepeople',
'basepeople_hidden',
'baseperson',
'baseperson_hidden',
'baseperson_hidden_id',
'baseperson_id',
'id',
'people',
'people_hidden',
'person',
'person_hidden',
'person_hidden_id',
'person_id',
'proxyperson',
'proxyperson_hidden',
'proxyperson_hidden_id',
'proxyperson_id',
],
},
'fields': {
Person: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'data_not_concrete_inherited',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'local_fields': {
Person: [
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'data_not_concrete_inherited',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'local_concrete_fields': {
Person: [
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'many_to_many': {
Person: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
'm2m_base',
'friends_base',
'following_base',
'm2m_inherited',
'friends_inherited',
'following_inherited',
],
BasePerson: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
'm2m_base',
'friends_base',
'following_base',
],
AbstractPerson: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
],
Relating: [
'basepeople',
'basepeople_hidden',
'people',
'people_hidden',
],
},
'many_to_many_with_model': {
Person: [
BasePerson,
BasePerson,
BasePerson,
BasePerson,
BasePerson,
BasePerson,
None,
None,
None,
],
BasePerson: [
None,
None,
None,
None,
None,
None,
],
AbstractPerson: [
None,
None,
None,
],
Relating: [
None,
None,
None,
None,
],
},
'get_all_related_objects_with_model_legacy': {
Person: (
('relating_baseperson', BasePerson),
('relating_person', None),
),
BasePerson: (
('person', None),
('relating_baseperson', None),
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_hidden_local': {
Person: (
('+', None),
('+', None),
('Person_following_inherited+', None),
('Person_following_inherited+', None),
('Person_friends_inherited+', None),
('Person_friends_inherited+', None),
('Person_m2m_inherited+', None),
('Relating_people+', None),
('Relating_people_hidden+', None),
('followers_concrete', None),
('friends_inherited_rel_+', None),
('relating_people', None),
('relating_person', None),
),
BasePerson: (
('+', None),
('+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_base+', None),
('BasePerson_following_base+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_base+', None),
('BasePerson_friends_base+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Relating_basepeople+', None),
('Relating_basepeople_hidden+', None),
('followers_abstract', None),
('followers_base', None),
('friends_abstract_rel_+', None),
('friends_base_rel_+', None),
('person', None),
('relating_basepeople', None),
('relating_baseperson', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Person_m2m_inherited+', None),
('fk_abstract_rel', None),
('fk_base_rel', None),
('fk_concrete_rel', None),
('fo_abstract_rel', None),
('fo_base_rel', None),
('fo_concrete_rel', None),
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_objects_with_model_hidden': {
Person: (
('+', BasePerson),
('+', BasePerson),
('+', None),
('+', None),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_m2m_abstract+', BasePerson),
('BasePerson_m2m_base+', BasePerson),
('Person_following_inherited+', None),
('Person_following_inherited+', None),
('Person_friends_inherited+', None),
('Person_friends_inherited+', None),
('Person_m2m_inherited+', None),
('Relating_basepeople+', BasePerson),
('Relating_basepeople_hidden+', BasePerson),
('Relating_people+', None),
('Relating_people_hidden+', None),
('followers_abstract', BasePerson),
('followers_base', BasePerson),
('followers_concrete', None),
('friends_abstract_rel_+', BasePerson),
('friends_base_rel_+', BasePerson),
('friends_inherited_rel_+', None),
('relating_basepeople', BasePerson),
('relating_baseperson', BasePerson),
('relating_people', None),
('relating_person', None),
),
BasePerson: (
('+', None),
('+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_base+', None),
('BasePerson_following_base+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_base+', None),
('BasePerson_friends_base+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Relating_basepeople+', None),
('Relating_basepeople_hidden+', None),
('followers_abstract', None),
('followers_base', None),
('friends_abstract_rel_+', None),
('friends_base_rel_+', None),
('person', None),
('relating_basepeople', None),
('relating_baseperson', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Person_m2m_inherited+', None),
('fk_abstract_rel', None),
('fk_base_rel', None),
('fk_concrete_rel', None),
('fo_abstract_rel', None),
('fo_base_rel', None),
('fo_concrete_rel', None),
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_objects_with_model_local': {
Person: (
('followers_concrete', None),
('relating_person', None),
('relating_people', None),
),
BasePerson: (
('followers_abstract', None),
('followers_base', None),
('person', None),
('relating_baseperson', None),
('relating_basepeople', None),
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_objects_with_model': {
Person: (
('followers_abstract', BasePerson),
('followers_base', BasePerson),
('relating_baseperson', BasePerson),
('relating_basepeople', BasePerson),
('followers_concrete', None),
('relating_person', None),
('relating_people', None),
),
BasePerson: (
('followers_abstract', None),
('followers_base', None),
('person', None),
('relating_baseperson', None),
('relating_basepeople', None),
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_objects_with_model_local_legacy': {
Person: (
('relating_person', None),
),
BasePerson: (
('person', None),
('relating_baseperson', None)
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_hidden_legacy': {
BasePerson: (
('+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_base+', None),
('BasePerson_following_base+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_base+', None),
('BasePerson_friends_base+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Relating_basepeople+', None),
('Relating_basepeople_hidden+', None),
('person', None),
('relating_baseperson', None),
),
Person: (
('+', BasePerson),
('+', None),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_m2m_abstract+', BasePerson),
('BasePerson_m2m_base+', BasePerson),
('Person_following_inherited+', None),
('Person_following_inherited+', None),
('Person_friends_inherited+', None),
('Person_friends_inherited+', None),
('Person_m2m_inherited+', None),
('Relating_basepeople+', BasePerson),
('Relating_basepeople_hidden+', BasePerson),
('Relating_people+', None),
('Relating_people_hidden+', None),
('relating_baseperson', BasePerson),
('relating_person', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Person_m2m_inherited+', None),
('fk_abstract_rel', None),
('fk_base_rel', None),
('fk_concrete_rel', None),
('fo_abstract_rel', None),
('fo_base_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_hidden_local_legacy': {
BasePerson: (
('+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_base+', None),
('BasePerson_following_base+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_base+', None),
('BasePerson_friends_base+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Relating_basepeople+', None),
('Relating_basepeople_hidden+', None),
('person', None),
('relating_baseperson', None),
),
Person: (
('+', None),
('Person_following_inherited+', None),
('Person_following_inherited+', None),
('Person_friends_inherited+', None),
('Person_friends_inherited+', None),
('Person_m2m_inherited+', None),
('Relating_people+', None),
('Relating_people_hidden+', None),
('relating_person', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Person_m2m_inherited+', None),
('fk_abstract_rel', None),
('fk_base_rel', None),
('fk_concrete_rel', None),
('fo_abstract_rel', None),
('fo_base_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_proxy_legacy': {
BasePerson: (
('person', None),
('relating_baseperson', None),
),
Person: (
('relating_baseperson', BasePerson),
('relating_person', None), ('relating_proxyperson', None),
),
Relation: (
('fk_abstract_rel', None), ('fo_abstract_rel', None),
('fk_base_rel', None), ('fo_base_rel', None),
('fk_concrete_rel', None), ('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_proxy_hidden_legacy': {
BasePerson: (
('+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_abstract+', None),
('BasePerson_following_base+', None),
('BasePerson_following_base+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_abstract+', None),
('BasePerson_friends_base+', None),
('BasePerson_friends_base+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Relating_basepeople+', None),
('Relating_basepeople_hidden+', None),
('person', None),
('relating_baseperson', None),
),
Person: (
('+', BasePerson),
('+', None),
('+', None),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_abstract+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_following_base+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_abstract+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_friends_base+', BasePerson),
('BasePerson_m2m_abstract+', BasePerson),
('BasePerson_m2m_base+', BasePerson),
('Person_following_inherited+', None),
('Person_following_inherited+', None),
('Person_friends_inherited+', None),
('Person_friends_inherited+', None),
('Person_m2m_inherited+', None),
('Relating_basepeople+', BasePerson),
('Relating_basepeople_hidden+', BasePerson),
('Relating_people+', None),
('Relating_people_hidden+', None),
('relating_baseperson', BasePerson),
('relating_person', None),
('relating_proxyperson', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('BasePerson_m2m_abstract+', None),
('BasePerson_m2m_base+', None),
('Person_m2m_inherited+', None),
('fk_abstract_rel', None),
('fk_base_rel', None),
('fk_concrete_rel', None),
('fo_abstract_rel', None),
('fo_base_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_many_to_many_with_model_legacy': {
BasePerson: (
('friends_abstract_rel_+', None),
('followers_abstract', None),
('friends_base_rel_+', None),
('followers_base', None),
('relating_basepeople', None),
('+', None),
),
Person: (
('friends_abstract_rel_+', BasePerson),
('followers_abstract', BasePerson),
('friends_base_rel_+', BasePerson),
('followers_base', BasePerson),
('relating_basepeople', BasePerson),
('+', BasePerson),
('friends_inherited_rel_+', None),
('followers_concrete', None),
('relating_people', None),
('+', None),
),
Relation: (
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_many_to_many_local_legacy': {
BasePerson: [
'friends_abstract_rel_+',
'followers_abstract',
'friends_base_rel_+',
'followers_base',
'relating_basepeople',
'+',
],
Person: [
'friends_inherited_rel_+',
'followers_concrete',
'relating_people',
'+',
],
Relation: [
'm2m_abstract_rel',
'm2m_base_rel',
'm2m_concrete_rel',
],
},
'virtual_fields': {
AbstractPerson: [
'generic_relation_abstract',
'content_object_abstract',
],
BasePerson: [
'generic_relation_base',
'content_object_base',
'generic_relation_abstract',
'content_object_abstract',
],
Person: [
'content_object_concrete',
'generic_relation_concrete',
'generic_relation_base',
'content_object_base',
'generic_relation_abstract',
'content_object_abstract',
],
},
}

View File

@ -1,661 +0,0 @@
from django import test
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields import related, CharField, Field
from .models import (
AbstractPerson, BasePerson, Person, Relating, Relation
)
TEST_RESULTS = {
'fields': {
Person: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'data_not_concrete_inherited',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'local_fields': {
Person: [
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'data_not_concrete_inherited',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'data_not_concrete_base',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'data_not_concrete_abstract',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'local_concrete_fields': {
Person: [
'baseperson_ptr_id',
'data_inherited',
'fk_inherited_id',
'content_type_concrete_id',
'object_id_concrete',
],
BasePerson: [
'id',
'data_abstract',
'fk_abstract_id',
'content_type_abstract_id',
'object_id_abstract',
'data_base',
'fk_base_id',
'content_type_base_id',
'object_id_base',
],
AbstractPerson: [
'data_abstract',
'fk_abstract_id',
'content_type_abstract_id',
'object_id_abstract',
],
Relating: [
'id',
'baseperson_id',
'baseperson_hidden_id',
'person_id',
'person_hidden_id',
'proxyperson_id',
'proxyperson_hidden_id',
],
},
'many_to_many': {
Person: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
'm2m_base',
'friends_base',
'following_base',
'm2m_inherited',
'friends_inherited',
'following_inherited',
],
BasePerson: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
'm2m_base',
'friends_base',
'following_base',
],
AbstractPerson: [
'm2m_abstract',
'friends_abstract',
'following_abstract',
],
Relating: [
'basepeople',
'basepeople_hidden',
'people',
'people_hidden',
],
},
'many_to_many_with_model': {
Person: [
BasePerson,
BasePerson,
BasePerson,
BasePerson,
BasePerson,
BasePerson,
None,
None,
None,
],
BasePerson: [
None,
None,
None,
None,
None,
None,
],
AbstractPerson: [
None,
None,
None,
],
Relating: [
None,
None,
None,
None,
],
},
'get_all_related_objects_with_model': {
Person: (
('relating_baseperson', BasePerson),
('relating_person', None),
),
BasePerson: (
('person', None),
('relating_baseperson', None),
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_local': {
Person: (
('relating_person', None),
),
BasePerson: (
('person', None),
('relating_baseperson', None)
),
Relation: (
('fk_abstract_rel', None),
('fo_abstract_rel', None),
('fk_base_rel', None),
('fo_base_rel', None),
('fk_concrete_rel', None),
('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_hidden': {
BasePerson: (
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.person', None),
('model_meta.relating_basepeople', None),
('model_meta.relating_basepeople_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Person: (
('model_meta.baseperson_friends_base', BasePerson),
('model_meta.baseperson_friends_base', BasePerson),
('model_meta.baseperson_m2m_base', BasePerson),
('model_meta.baseperson_following_base', BasePerson),
('model_meta.baseperson_following_base', BasePerson),
('model_meta.baseperson_m2m_abstract', BasePerson),
('model_meta.baseperson_friends_abstract', BasePerson),
('model_meta.baseperson_friends_abstract', BasePerson),
('model_meta.baseperson_following_abstract', BasePerson),
('model_meta.baseperson_following_abstract', BasePerson),
('model_meta.relating_basepeople', BasePerson),
('model_meta.relating_basepeople_hidden', BasePerson),
('model_meta.relating', BasePerson),
('model_meta.relating', BasePerson),
('model_meta.person_m2m_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.relating_people', None),
('model_meta.relating_people_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Relation: (
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.person_m2m_inherited', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
),
},
'get_all_related_objects_with_model_hidden_local': {
BasePerson: (
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.person', None),
('model_meta.relating_basepeople', None),
('model_meta.relating_basepeople_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Person: (
('model_meta.person_m2m_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.relating_people', None),
('model_meta.relating_people_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Relation: (
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.person_m2m_inherited', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
),
},
'get_all_related_objects_with_model_proxy': {
BasePerson: (
('person', None),
('relating_baseperson', None),
),
Person: (
('relating_baseperson', BasePerson),
('relating_person', None), ('relating_proxyperson', None),
),
Relation: (
('fk_abstract_rel', None), ('fo_abstract_rel', None),
('fk_base_rel', None), ('fo_base_rel', None),
('fk_concrete_rel', None), ('fo_concrete_rel', None),
),
},
'get_all_related_objects_with_model_proxy_hidden': {
BasePerson: (
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_friends_base', None),
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_following_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_friends_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.baseperson_following_abstract', None),
('model_meta.person', None),
('model_meta.relating_basepeople', None),
('model_meta.relating_basepeople_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Person: (
('model_meta.baseperson_friends_base', BasePerson),
('model_meta.baseperson_friends_base', BasePerson),
('model_meta.baseperson_m2m_base', BasePerson),
('model_meta.baseperson_following_base', BasePerson),
('model_meta.baseperson_following_base', BasePerson),
('model_meta.baseperson_m2m_abstract', BasePerson),
('model_meta.baseperson_friends_abstract', BasePerson),
('model_meta.baseperson_friends_abstract', BasePerson),
('model_meta.baseperson_following_abstract', BasePerson),
('model_meta.baseperson_following_abstract', BasePerson),
('model_meta.relating_basepeople', BasePerson),
('model_meta.relating_basepeople_hidden', BasePerson),
('model_meta.relating', BasePerson),
('model_meta.relating', BasePerson),
('model_meta.person_m2m_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_friends_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.person_following_inherited', None),
('model_meta.relating_people', None),
('model_meta.relating_people_hidden', None),
('model_meta.relating', None),
('model_meta.relating', None),
('model_meta.relating', None),
('model_meta.relating', None),
),
Relation: (
('model_meta.baseperson_m2m_base', None),
('model_meta.baseperson_m2m_abstract', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.baseperson', None),
('model_meta.person_m2m_inherited', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.person', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
('model_meta.proxyperson', None),
),
},
'get_all_related_many_to_many_with_model': {
BasePerson: (
('friends_abstract_rel_+', None),
('followers_abstract', None),
('friends_base_rel_+', None),
('followers_base', None),
('relating_basepeople', None),
('+', None),
),
Person: (
('friends_abstract_rel_+', BasePerson),
('followers_abstract', BasePerson),
('friends_base_rel_+', BasePerson),
('followers_base', BasePerson),
('relating_basepeople', BasePerson),
('+', BasePerson),
('friends_inherited_rel_+', None),
('followers_concrete', None),
('relating_people', None),
('+', None),
),
Relation: (
('m2m_abstract_rel', None),
('m2m_base_rel', None),
('m2m_concrete_rel', None),
),
},
'get_all_related_many_to_many_local': {
BasePerson: [
'friends_abstract_rel_+',
'followers_abstract',
'friends_base_rel_+',
'followers_base',
'relating_basepeople',
'+',
],
Person: [
'friends_inherited_rel_+',
'followers_concrete',
'relating_people',
'+',
],
Relation: [
'm2m_abstract_rel',
'm2m_base_rel',
'm2m_concrete_rel',
],
},
'virtual_fields': {
AbstractPerson: [
'generic_relation_abstract',
'content_object_abstract',
],
BasePerson: [
'generic_relation_base',
'content_object_base',
'generic_relation_abstract',
'content_object_abstract',
],
Person: [
'content_object_concrete',
'generic_relation_concrete',
'generic_relation_base',
'content_object_base',
'generic_relation_abstract',
'content_object_abstract',
],
},
}
class OptionsBaseTests(test.TestCase):
def _map_rq_names(self, res):
return tuple((o.field.related_query_name(), m) for o, m in res)
def _map_names(self, res):
return tuple((f.name, m) for f, m in res)
class DataTests(OptionsBaseTests):
def test_fields(self):
for model, expected_result in TEST_RESULTS['fields'].items():
fields = model._meta.fields
self.assertEqual([f.attname for f in fields], expected_result)
def test_local_fields(self):
is_data_field = lambda f: isinstance(f, Field) and not isinstance(f, related.ManyToManyField)
for model, expected_result in TEST_RESULTS['local_fields'].items():
fields = model._meta.local_fields
self.assertEqual([f.attname for f in fields], expected_result)
self.assertTrue(all([f.model is model for f in fields]))
self.assertTrue(all([is_data_field(f) for f in fields]))
def test_local_concrete_fields(self):
for model, expected_result in TEST_RESULTS['local_concrete_fields'].items():
fields = model._meta.local_concrete_fields
self.assertEqual([f.attname for f in fields], expected_result)
self.assertTrue(all([f.column is not None for f in fields]))
class M2MTests(OptionsBaseTests):
def test_many_to_many(self):
for model, expected_result in TEST_RESULTS['many_to_many'].items():
fields = model._meta.many_to_many
self.assertEqual([f.attname for f in fields], expected_result)
self.assertTrue(all([isinstance(f.rel, related.ManyToManyRel) for f in fields]))
def test_many_to_many_with_model(self):
for model, expected_result in TEST_RESULTS['many_to_many_with_model'].items():
models = [model for field, model in model._meta.get_m2m_with_model()]
self.assertEqual(models, expected_result)
class RelatedObjectsTests(OptionsBaseTests):
def setUp(self):
self.key_name = lambda r: r[0]
def test_related_objects(self):
result_key = 'get_all_related_objects_with_model'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model()
self.assertEqual(self._map_rq_names(objects), expected)
def test_related_objects_local(self):
result_key = 'get_all_related_objects_with_model_local'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(local_only=True)
self.assertEqual(self._map_rq_names(objects), expected)
def test_related_objects_include_hidden(self):
result_key = 'get_all_related_objects_with_model_hidden'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(include_hidden=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
def test_related_objects_include_hidden_local_only(self):
result_key = 'get_all_related_objects_with_model_hidden_local'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_hidden=True, local_only=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
def test_related_objects_proxy(self):
result_key = 'get_all_related_objects_with_model_proxy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_proxy_eq=True)
self.assertEqual(self._map_rq_names(objects), expected)
def test_related_objects_proxy_hidden(self):
result_key = 'get_all_related_objects_with_model_proxy_hidden'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_proxy_eq=True, include_hidden=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
class RelatedM2MTests(OptionsBaseTests):
def test_related_m2m_with_model(self):
result_key = 'get_all_related_many_to_many_with_model'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_m2m_objects_with_model()
self.assertEqual(self._map_rq_names(objects), expected)
def test_related_m2m_local_only(self):
result_key = 'get_all_related_many_to_many_local'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_many_to_many_objects(local_only=True)
self.assertEqual([o.field.related_query_name() for o in objects], expected)
def test_related_m2m_asymmetrical(self):
m2m = Person._meta.many_to_many
self.assertIn('following_base', [f.attname for f in m2m])
related_m2m = Person._meta.get_all_related_many_to_many_objects()
self.assertIn('followers_base', [o.field.related_query_name() for o in related_m2m])
def test_related_m2m_symmetrical(self):
m2m = Person._meta.many_to_many
self.assertIn('friends_base', [f.attname for f in m2m])
related_m2m = Person._meta.get_all_related_many_to_many_objects()
self.assertIn('friends_inherited_rel_+', [o.field.related_query_name() for o in related_m2m])
class VirtualFieldsTests(OptionsBaseTests):
def test_virtual_fields(self):
for model, expected_names in TEST_RESULTS['virtual_fields'].items():
objects = model._meta.virtual_fields
self.assertEqual(sorted([f.name for f in objects]), sorted(expected_names))
class GetFieldByNameTests(OptionsBaseTests):
def test_get_data_field(self):
field_info = Person._meta.get_field_by_name('data_abstract')
self.assertEqual(field_info[1:], (BasePerson, True, False))
self.assertIsInstance(field_info[0], CharField)
def test_get_m2m_field(self):
field_info = Person._meta.get_field_by_name('m2m_base')
self.assertEqual(field_info[1:], (BasePerson, True, True))
self.assertIsInstance(field_info[0], related.ManyToManyField)
def test_get_related_object(self):
field_info = Person._meta.get_field_by_name('relating_baseperson')
self.assertEqual(field_info[1:], (BasePerson, False, False))
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
def test_get_related_m2m(self):
field_info = Person._meta.get_field_by_name('relating_people')
self.assertEqual(field_info[1:], (None, False, True))
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
def test_get_generic_foreign_key(self):
# For historic reasons generic foreign keys aren't available.
with self.assertRaises(FieldDoesNotExist):
Person._meta.get_field_by_name('content_object_base')
def test_get_generic_relation(self):
field_info = Person._meta.get_field_by_name('generic_relation_base')
self.assertEqual(field_info[1:], (None, True, False))
self.assertIsInstance(field_info[0], GenericRelation)

View File

@ -0,0 +1,166 @@
import warnings
from django import test
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields import related, CharField
from django.utils.deprecation import RemovedInDjango20Warning
from .models import BasePerson, Person
from .results import TEST_RESULTS
class OptionsBaseTests(test.TestCase):
def _map_related_query_names(self, res):
return tuple((o.field.related_query_name(), m) for o, m in res)
def _map_names(self, res):
return tuple((f.name, m) for f, m in res)
class M2MTests(OptionsBaseTests):
def test_many_to_many_with_model(self):
for model, expected_result in TEST_RESULTS['many_to_many_with_model'].items():
with warnings.catch_warnings(record=True) as warning:
warnings.simplefilter("always")
models = [model for field, model in model._meta.get_m2m_with_model()]
self.assertEqual([RemovedInDjango20Warning], [w.message.__class__ for w in warning])
self.assertEqual(models, expected_result)
@test.ignore_warnings(category=RemovedInDjango20Warning)
class RelatedObjectsTests(OptionsBaseTests):
key_name = lambda self, r: r[0]
def test_related_objects(self):
result_key = 'get_all_related_objects_with_model_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model()
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_objects_local(self):
result_key = 'get_all_related_objects_with_model_local_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(local_only=True)
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_objects_include_hidden(self):
result_key = 'get_all_related_objects_with_model_hidden_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(include_hidden=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
def test_related_objects_include_hidden_local_only(self):
result_key = 'get_all_related_objects_with_model_hidden_local_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_hidden=True, local_only=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
def test_related_objects_proxy(self):
result_key = 'get_all_related_objects_with_model_proxy_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_proxy_eq=True)
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_objects_proxy_hidden(self):
result_key = 'get_all_related_objects_with_model_proxy_hidden_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_objects_with_model(
include_proxy_eq=True, include_hidden=True)
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
@test.ignore_warnings(category=RemovedInDjango20Warning)
class RelatedM2MTests(OptionsBaseTests):
def test_related_m2m_with_model(self):
result_key = 'get_all_related_many_to_many_with_model_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_m2m_objects_with_model()
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_m2m_local_only(self):
result_key = 'get_all_related_many_to_many_local_legacy'
for model, expected in TEST_RESULTS[result_key].items():
objects = model._meta.get_all_related_many_to_many_objects(local_only=True)
self.assertEqual([o.field.related_query_name() for o in objects], expected)
def test_related_m2m_asymmetrical(self):
m2m = Person._meta.many_to_many
self.assertTrue('following_base' in [f.attname for f in m2m])
related_m2m = Person._meta.get_all_related_many_to_many_objects()
self.assertTrue('followers_base' in [o.field.related_query_name() for o in related_m2m])
def test_related_m2m_symmetrical(self):
m2m = Person._meta.many_to_many
self.assertTrue('friends_base' in [f.attname for f in m2m])
related_m2m = Person._meta.get_all_related_many_to_many_objects()
self.assertIn('friends_inherited_rel_+', [o.field.related_query_name() for o in related_m2m])
@test.ignore_warnings(category=RemovedInDjango20Warning)
class GetFieldByNameTests(OptionsBaseTests):
def test_get_data_field(self):
field_info = Person._meta.get_field_by_name('data_abstract')
self.assertEqual(field_info[1:], (BasePerson, True, False))
self.assertIsInstance(field_info[0], CharField)
def test_get_m2m_field(self):
field_info = Person._meta.get_field_by_name('m2m_base')
self.assertEqual(field_info[1:], (BasePerson, True, True))
self.assertIsInstance(field_info[0], related.ManyToManyField)
def test_get_related_object(self):
field_info = Person._meta.get_field_by_name('relating_baseperson')
self.assertEqual(field_info[1:], (BasePerson, False, False))
self.assertTrue(field_info[0].auto_created)
def test_get_related_m2m(self):
field_info = Person._meta.get_field_by_name('relating_people')
self.assertEqual(field_info[1:], (None, False, True))
self.assertTrue(field_info[0].auto_created)
def test_get_generic_relation(self):
field_info = Person._meta.get_field_by_name('generic_relation_base')
self.assertEqual(field_info[1:], (None, True, False))
self.assertIsInstance(field_info[0], GenericRelation)
def test_get_m2m_field_invalid(self):
with warnings.catch_warnings(record=True) as warning:
warnings.simplefilter("always")
self.assertRaises(
FieldDoesNotExist,
Person._meta.get_field,
**{'field_name': 'm2m_base', 'many_to_many': False}
)
self.assertEqual(Person._meta.get_field('m2m_base', many_to_many=True).name, 'm2m_base')
# 2 RemovedInDjango20Warning messages should be raised, one for each call of get_field()
# with the 'many_to_many' argument.
self.assertEqual(
[RemovedInDjango20Warning, RemovedInDjango20Warning],
[w.message.__class__ for w in warning]
)
@test.ignore_warnings(category=RemovedInDjango20Warning)
class GetAllFieldNamesTestCase(OptionsBaseTests):
def test_get_all_field_names(self):
for model, expected_names in TEST_RESULTS['get_all_field_names'].items():
objects = model._meta.get_all_field_names()
self.assertEqual(sorted(map(str, objects)), sorted(expected_names))

247
tests/model_meta/tests.py Normal file
View File

@ -0,0 +1,247 @@
from django.apps import apps
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields import related, CharField, Field
from django.db.models.options import IMMUTABLE_WARNING, EMPTY_RELATION_TREE
from django.test import TestCase
from .models import Relation, AbstractPerson, BasePerson, Person, ProxyPerson, Relating
from .results import TEST_RESULTS
class OptionsBaseTests(TestCase):
def _map_related_query_names(self, res):
return tuple((o.name, m) for o, m in res)
def _map_names(self, res):
return tuple((f.name, m) for f, m in res)
def _model(self, current_model, field):
model = field.model._meta.concrete_model
return None if model == current_model else model
def _details(self, current_model, relation):
direct = isinstance(relation, Field) or isinstance(relation, GenericForeignKey)
model = relation.model._meta.concrete_model
if model == current_model:
model = None
field = relation if direct else relation.field
m2m = isinstance(field, related.ManyToManyField)
return relation, model, direct, m2m
class GetFieldsTests(OptionsBaseTests):
def test_get_fields_is_immutable(self):
msg = IMMUTABLE_WARNING % "get_fields()"
for _ in range(2):
# Running unit test twice to ensure both non-cached and cached result
# are immutable.
fields = Person._meta.get_fields()
with self.assertRaisesMessage(AttributeError, msg):
fields += ["errors"]
class DataTests(OptionsBaseTests):
def test_fields(self):
for model, expected_result in TEST_RESULTS['fields'].items():
fields = model._meta.fields
self.assertEqual([f.attname for f in fields], expected_result)
def test_local_fields(self):
is_data_field = lambda f: isinstance(f, Field) and not isinstance(f, related.ManyToManyField)
for model, expected_result in TEST_RESULTS['local_fields'].items():
fields = model._meta.local_fields
self.assertEqual([f.attname for f in fields], expected_result)
for f in fields:
self.assertEqual(f.model, model)
self.assertTrue(is_data_field(f))
def test_local_concrete_fields(self):
for model, expected_result in TEST_RESULTS['local_concrete_fields'].items():
fields = model._meta.local_concrete_fields
self.assertEqual([f.attname for f in fields], expected_result)
for f in fields:
self.assertIsNotNone(f.column)
class M2MTests(OptionsBaseTests):
def test_many_to_many(self):
for model, expected_result in TEST_RESULTS['many_to_many'].items():
fields = model._meta.many_to_many
self.assertEqual([f.attname for f in fields], expected_result)
for f in fields:
self.assertTrue(f.many_to_many and f.is_relation)
def test_many_to_many_with_model(self):
for model, expected_result in TEST_RESULTS['many_to_many_with_model'].items():
models = [self._model(model, field) for field in model._meta.many_to_many]
self.assertEqual(models, expected_result)
class RelatedObjectsTests(OptionsBaseTests):
key_name = lambda self, r: r[0]
def test_related_objects(self):
result_key = 'get_all_related_objects_with_model'
for model, expected in TEST_RESULTS[result_key].items():
objects = [
(field, self._model(model, field))
for field in model._meta.get_fields()
if field.auto_created and not field.concrete
]
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_objects_local(self):
result_key = 'get_all_related_objects_with_model_local'
for model, expected in TEST_RESULTS[result_key].items():
objects = [
(field, self._model(model, field))
for field in model._meta.get_fields(include_parents=False)
if field.auto_created and not field.concrete
]
self.assertEqual(self._map_related_query_names(objects), expected)
def test_related_objects_include_hidden(self):
result_key = 'get_all_related_objects_with_model_hidden'
for model, expected in TEST_RESULTS[result_key].items():
objects = [
(field, self._model(model, field))
for field in model._meta.get_fields(include_hidden=True)
if field.auto_created and not field.concrete
]
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
def test_related_objects_include_hidden_local_only(self):
result_key = 'get_all_related_objects_with_model_hidden_local'
for model, expected in TEST_RESULTS[result_key].items():
objects = [
(field, self._model(model, field))
for field in model._meta.get_fields(include_hidden=True, include_parents=False)
if field.auto_created and not field.concrete
]
self.assertEqual(
sorted(self._map_names(objects), key=self.key_name),
sorted(expected, key=self.key_name)
)
class VirtualFieldsTests(OptionsBaseTests):
def test_virtual_fields(self):
for model, expected_names in TEST_RESULTS['virtual_fields'].items():
objects = model._meta.virtual_fields
self.assertEqual(sorted([f.name for f in objects]), sorted(expected_names))
class GetFieldByNameTests(OptionsBaseTests):
def test_get_data_field(self):
field_info = self._details(Person, Person._meta.get_field('data_abstract'))
self.assertEqual(field_info[1:], (BasePerson, True, False))
self.assertIsInstance(field_info[0], CharField)
def test_get_m2m_field(self):
field_info = self._details(Person, Person._meta.get_field('m2m_base'))
self.assertEqual(field_info[1:], (BasePerson, True, True))
self.assertIsInstance(field_info[0], related.ManyToManyField)
def test_get_related_object(self):
field_info = self._details(Person, Person._meta.get_field('relating_baseperson'))
self.assertEqual(field_info[1:], (BasePerson, False, False))
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
def test_get_related_m2m(self):
field_info = self._details(Person, Person._meta.get_field('relating_people'))
self.assertEqual(field_info[1:], (None, False, True))
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
def test_get_generic_relation(self):
field_info = self._details(Person, Person._meta.get_field('generic_relation_base'))
self.assertEqual(field_info[1:], (None, True, False))
self.assertIsInstance(field_info[0], GenericRelation)
def test_get_fields_only_searaches_forward_on_apps_not_ready(self):
opts = Person._meta
# If apps registry is not ready, get_field() searches over only
# forward fields.
opts.apps.ready = False
try:
# 'data_abstract' is a forward field, and therefore will be found
self.assertTrue(opts.get_field('data_abstract'))
msg = (
"Person has no field named 'relating_baseperson'. The app "
"cache isn't ready yet, so if this is a forward field, it "
"won't be available yet."
)
# 'data_abstract' is a reverse field, and will raise an exception
with self.assertRaisesMessage(FieldDoesNotExist, msg):
opts.get_field('relating_baseperson')
finally:
opts.apps.ready = True
class RelationTreeTests(TestCase):
all_models = (Relation, AbstractPerson, BasePerson, Person, ProxyPerson, Relating)
def setUp(self):
apps.clear_cache()
def test_clear_cache_clears_relation_tree(self):
# The apps.clear_cache is setUp() should have deleted all trees.
# Exclude abstract models that are not included in the Apps registry
# and have no cache.
all_models_with_cache = (m for m in self.all_models if not m._meta.abstract)
for m in all_models_with_cache:
self.assertNotIn('_relation_tree', m._meta.__dict__)
def test_first_relation_tree_access_populates_all(self):
# On first access, relation tree should have populated cache.
self.assertTrue(self.all_models[0]._meta._relation_tree)
# AbstractPerson does not have any relations, so relation_tree
# should just return an EMPTY_RELATION_TREE.
self.assertEqual(AbstractPerson._meta._relation_tree, EMPTY_RELATION_TREE)
# All the other models should already have their relation tree
# in the internal __dict__ .
all_models_but_abstractperson = (m for m in self.all_models if m is not AbstractPerson)
for m in all_models_but_abstractperson:
self.assertIn('_relation_tree', m._meta.__dict__)
def test_relations_related_objects(self):
# Testing non hidden related objects
self.assertEqual(
sorted([field.related_query_name() for field in Relation._meta._relation_tree
if not field.rel.field.rel.is_hidden()]),
sorted([
'fk_abstract_rel', 'fk_abstract_rel', 'fk_abstract_rel', 'fk_base_rel', 'fk_base_rel',
'fk_base_rel', 'fk_concrete_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_abstract_rel',
'fo_abstract_rel', 'fo_base_rel', 'fo_base_rel', 'fo_base_rel', 'fo_concrete_rel',
'fo_concrete_rel', 'm2m_abstract_rel', 'm2m_abstract_rel', 'm2m_abstract_rel',
'm2m_base_rel', 'm2m_base_rel', 'm2m_base_rel', 'm2m_concrete_rel', 'm2m_concrete_rel',
])
)
# Testing hidden related objects
self.assertEqual(
sorted([field.related_query_name() for field in BasePerson._meta._relation_tree]),
sorted([
'+', '+', 'BasePerson_following_abstract+', 'BasePerson_following_abstract+',
'BasePerson_following_base+', 'BasePerson_following_base+', 'BasePerson_friends_abstract+',
'BasePerson_friends_abstract+', 'BasePerson_friends_base+', 'BasePerson_friends_base+',
'BasePerson_m2m_abstract+', 'BasePerson_m2m_base+', 'Relating_basepeople+',
'Relating_basepeople_hidden+', 'followers_abstract', 'followers_abstract', 'followers_abstract',
'followers_base', 'followers_base', 'followers_base', 'friends_abstract_rel_+', 'friends_abstract_rel_+',
'friends_abstract_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'person',
'person', 'relating_basepeople', 'relating_baseperson',
])
)
self.assertEqual([field.related_query_name() for field in AbstractPerson._meta._relation_tree], [])

View File

@ -2093,7 +2093,7 @@ class CloneTests(TestCase):
testing is impossible, this is a sanity check against invalid use of
deepcopy. refs #16759.
"""
opts_class = type(Note._meta.get_field_by_name("misc")[0])
opts_class = type(Note._meta.get_field("misc"))
note_deepcopy = getattr(opts_class, "__deepcopy__", None)
opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned")
try:

View File

@ -79,7 +79,7 @@ class PickleabilityTestCase(TestCase):
m1 = M2MModel.objects.create()
g1 = Group.objects.create(name='foof')
m1.groups.add(g1)
m2m_through = M2MModel._meta.get_field_by_name('groups')[0].rel.through
m2m_through = M2MModel._meta.get_field('groups').rel.through
original = m2m_through.objects.get()
dumped = pickle.dumps(original)
reloaded = pickle.loads(dumped)

View File

@ -138,7 +138,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Book,
Book._meta.get_field_by_name("author")[0],
Book._meta.get_field("author"),
new_field,
strict=True,
)
@ -393,7 +393,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
Author._meta.get_field_by_name("name")[0],
Author._meta.get_field("name"),
new_field,
strict=True,
)
@ -424,7 +424,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Note,
Note._meta.get_field_by_name("info")[0],
Note._meta.get_field("info"),
new_field,
strict=True,
)
@ -451,7 +451,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
Author._meta.get_field_by_name("height")[0],
Author._meta.get_field("height"),
new_field
)
# Ensure the field is right afterwards
@ -479,7 +479,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
AuthorWithDefaultHeight,
AuthorWithDefaultHeight._meta.get_field_by_name("height")[0],
AuthorWithDefaultHeight._meta.get_field("height"),
new_field,
)
# Ensure the field is right afterwards
@ -512,7 +512,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Book,
Book._meta.get_field_by_name("author")[0],
Book._meta.get_field("author"),
new_field,
strict=True,
)
@ -542,7 +542,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
Author._meta.get_field_by_name("id")[0],
Author._meta.get_field("id"),
new_field,
strict=True,
)
@ -568,7 +568,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
Author._meta.get_field_by_name("name")[0],
Author._meta.get_field("name"),
new_field,
strict=True,
)
@ -587,7 +587,7 @@ class SchemaTests(TransactionTestCase):
editor.create_model(TagM2MTest)
editor.create_model(BookWithM2M)
# Ensure there is now an m2m table there
columns = self.column_classes(BookWithM2M._meta.get_field_by_name("tags")[0].rel.through)
columns = self.column_classes(BookWithM2M._meta.get_field("tags").rel.through)
self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
def test_m2m_create_through(self):
@ -661,7 +661,7 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(len(self.column_classes(AuthorTag)), 3)
# "Alter" the field's blankness. This should not actually do anything.
with connection.schema_editor() as editor:
old_field = AuthorWithM2MThrough._meta.get_field_by_name("tags")[0]
old_field = AuthorWithM2MThrough._meta.get_field("tags")
new_field = ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag")
new_field.contribute_to_class(AuthorWithM2MThrough, "tags")
editor.alter_field(
@ -683,7 +683,7 @@ class SchemaTests(TransactionTestCase):
editor.create_model(TagM2MTest)
editor.create_model(UniqueTest)
# Ensure the M2M exists and points to TagM2MTest
constraints = self.get_constraints(BookWithM2M._meta.get_field_by_name("tags")[0].rel.through._meta.db_table)
constraints = self.get_constraints(BookWithM2M._meta.get_field("tags").rel.through._meta.db_table)
if connection.features.supports_foreign_keys:
for name, details in constraints.items():
if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
@ -698,11 +698,11 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
BookWithM2M._meta.get_field_by_name("tags")[0],
BookWithM2M._meta.get_field("tags"),
new_field,
)
# Ensure old M2M is gone
self.assertRaises(DatabaseError, self.column_classes, BookWithM2M._meta.get_field_by_name("tags")[0].rel.through)
self.assertRaises(DatabaseError, self.column_classes, BookWithM2M._meta.get_field("tags").rel.through)
# Ensure the new M2M exists and points to UniqueTest
constraints = self.get_constraints(new_field.rel.through._meta.db_table)
if connection.features.supports_foreign_keys:
@ -715,10 +715,10 @@ class SchemaTests(TransactionTestCase):
finally:
# Cleanup through table separately
with connection.schema_editor() as editor:
editor.remove_field(BookWithM2M, BookWithM2M._meta.get_field_by_name("uniques")[0])
editor.remove_field(BookWithM2M, BookWithM2M._meta.get_field("uniques"))
# Cleanup model states
BookWithM2M._meta.local_many_to_many.remove(new_field)
del BookWithM2M._meta._m2m_cache
BookWithM2M._meta._expire_cache()
@unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints")
def test_check_constraints(self):
@ -741,7 +741,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Author,
Author._meta.get_field_by_name("height")[0],
Author._meta.get_field("height"),
new_field,
strict=True,
)
@ -754,7 +754,7 @@ class SchemaTests(TransactionTestCase):
editor.alter_field(
Author,
new_field,
Author._meta.get_field_by_name("height")[0],
Author._meta.get_field("height"),
strict=True,
)
constraints = self.get_constraints(Author._meta.db_table)
@ -781,7 +781,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Tag,
Tag._meta.get_field_by_name("slug")[0],
Tag._meta.get_field("slug"),
new_field,
strict=True,
)
@ -809,8 +809,8 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Tag,
Tag._meta.get_field_by_name("slug")[0],
TagUniqueRename._meta.get_field_by_name("slug2")[0],
Tag._meta.get_field("slug"),
TagUniqueRename._meta.get_field("slug2"),
strict=True,
)
# Ensure the field is still unique
@ -976,7 +976,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
Book,
Book._meta.get_field_by_name("title")[0],
Book._meta.get_field("title"),
new_field,
strict=True,
)
@ -990,7 +990,7 @@ class SchemaTests(TransactionTestCase):
editor.alter_field(
Book,
new_field,
Book._meta.get_field_by_name("title")[0],
Book._meta.get_field("title"),
strict=True,
)
# Ensure the table is there and has the index again
@ -1002,7 +1002,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.add_field(
Book,
BookWithSlug._meta.get_field_by_name("slug")[0],
BookWithSlug._meta.get_field("slug"),
)
self.assertIn(
"slug",
@ -1014,7 +1014,7 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(
BookWithSlug,
BookWithSlug._meta.get_field_by_name("slug")[0],
BookWithSlug._meta.get_field("slug"),
new_field2,
strict=True,
)
@ -1039,10 +1039,10 @@ class SchemaTests(TransactionTestCase):
new_field.set_attributes_from_name("slug")
new_field.model = Tag
with connection.schema_editor() as editor:
editor.remove_field(Tag, Tag._meta.get_field_by_name("id")[0])
editor.remove_field(Tag, Tag._meta.get_field("id"))
editor.alter_field(
Tag,
Tag._meta.get_field_by_name("slug")[0],
Tag._meta.get_field("slug"),
new_field,
)
# Ensure the PK changed