mirror of https://github.com/django/django.git
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:
parent
749d23251b
commit
fb48eb0581
|
@ -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 ###
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()),
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,23 +1523,25 @@ 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
|
||||
# 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'." % field_name,
|
||||
"'ordering' refers to the non-existent field '%s'." % invalid_field,
|
||||
hint=None,
|
||||
obj=cls,
|
||||
id='models.E015',
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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().
|
||||
"""
|
||||
return [self._map_model(f) for f in self.many_to_many]
|
||||
|
||||
@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:
|
||||
self._m2m_cache
|
||||
res[field.attname] = field
|
||||
except AttributeError:
|
||||
self._fill_m2m_cache()
|
||||
return list(six.iteritems(self._m2m_cache))
|
||||
pass
|
||||
return res
|
||||
|
||||
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.
|
||||
"""
|
||||
@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:
|
||||
try:
|
||||
return self._name_map[name]
|
||||
res[field.attname] = field
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
return cache[name]
|
||||
pass
|
||||
return res
|
||||
|
||||
def get_field(self, field_name, many_to_many=None):
|
||||
"""
|
||||
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:
|
||||
# In order to avoid premature loading of the relation tree
|
||||
# (expensive) we prefer checking if the field is a forward field.
|
||||
field = self._forward_fields_map[field_name]
|
||||
|
||||
if many_to_many is False and field.many_to_many:
|
||||
raise FieldDoesNotExist(
|
||||
'%s has no field named %r' % (self.object_name, field_name)
|
||||
)
|
||||
|
||||
return field
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist('%s has no field named %r'
|
||||
% (self.object_name, name))
|
||||
# If the app registry is not ready, reverse fields are
|
||||
# unavailable, therefore we throw a FieldDoesNotExist exception.
|
||||
if not self.apps.ready:
|
||||
raise FieldDoesNotExist(
|
||||
"%s has no field named %r. The app cache isn't "
|
||||
"ready yet, so if this is a forward field, it won't "
|
||||
"be available yet." % (self.object_name, field_name)
|
||||
)
|
||||
|
||||
def get_all_field_names(self):
|
||||
"""
|
||||
Returns a list of all field names that are possible for this model
|
||||
(including reverse relation names). This is used for pretty printing
|
||||
debugging output (a list of choices), so any internal-only field names
|
||||
are not included.
|
||||
"""
|
||||
try:
|
||||
cache = self._name_map
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
names = sorted(cache.keys())
|
||||
# Internal-only names end with "+" (symmetrical m2m related names being
|
||||
# the main example). Trim them.
|
||||
return [val for val in names if not val.endswith('+')]
|
||||
if m2m_in_kwargs:
|
||||
# Previous API does not allow searching reverse fields.
|
||||
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
||||
|
||||
def init_name_map(self):
|
||||
"""
|
||||
Initialises the field name -> field object mapping.
|
||||
"""
|
||||
cache = {}
|
||||
# We intentionally handle related m2m objects first so that symmetrical
|
||||
# m2m accessor names can be overridden, if necessary.
|
||||
for f, model in self.get_all_related_m2m_objects_with_model():
|
||||
cache[f.field.related_query_name()] = (f, model, False, True)
|
||||
for f, model in self.get_all_related_objects_with_model():
|
||||
cache[f.field.related_query_name()] = (f, model, False, False)
|
||||
for f, model in self.get_m2m_with_model():
|
||||
cache[f.name] = cache[f.attname] = (f, model, True, True)
|
||||
for f, model in self.get_fields_with_model():
|
||||
cache[f.name] = cache[f.attname] = (f, model, True, False)
|
||||
for f in self.virtual_fields:
|
||||
if f.rel:
|
||||
cache[f.name] = cache[f.attname] = (
|
||||
f, None if f.model == self.model else f.model, True, False)
|
||||
if apps.ready:
|
||||
self._name_map = cache
|
||||
return cache
|
||||
# Retrieve field instance by name from cached or just-computed
|
||||
# field map.
|
||||
return self.fields_map[field_name]
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
||||
|
||||
@raise_deprecation(suggested_alternative="get_field()")
|
||||
def get_field_by_name(self, name):
|
||||
return self._map_model_details(self.get_field(name))
|
||||
|
||||
@raise_deprecation(suggested_alternative="get_fields()")
|
||||
def get_all_field_names(self):
|
||||
names = set()
|
||||
fields = self.get_fields()
|
||||
for field in fields:
|
||||
# For backwards compatibility GenericForeignKey should not be
|
||||
# included in the results.
|
||||
if field.is_relation and field.one_to_many and field.related_model is None:
|
||||
continue
|
||||
|
||||
names.add(field.name)
|
||||
if hasattr(field, 'attname'):
|
||||
names.add(field.attname)
|
||||
return list(names)
|
||||
|
||||
@raise_deprecation(suggested_alternative="get_fields()")
|
||||
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
||||
include_proxy_eq=False):
|
||||
return [k for k, v in self.get_all_related_objects_with_model(
|
||||
local_only=local_only, include_hidden=include_hidden,
|
||||
include_proxy_eq=include_proxy_eq)]
|
||||
|
||||
def get_all_related_objects_with_model(self, local_only=False,
|
||||
include_hidden=False,
|
||||
include_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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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``.
|
||||
|
|
|
@ -8,6 +8,7 @@ Model API reference. For introductory material, see :doc:`/topics/db/models`.
|
|||
:maxdepth: 1
|
||||
|
||||
fields
|
||||
meta
|
||||
relations
|
||||
class
|
||||
options
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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='')
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
}
|
|
@ -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)
|
|
@ -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))
|
|
@ -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], [])
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue