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.
|
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()
|
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 ###
|
### DEPRECATED METHODS GO BELOW THIS LINE ###
|
||||||
|
|
||||||
|
|
|
@ -762,7 +762,7 @@ class ModelAdminChecks(BaseModelAdminChecks):
|
||||||
|
|
||||||
def _check_list_editable_item(self, cls, model, field_name, label):
|
def _check_list_editable_item(self, cls, model, field_name, label):
|
||||||
try:
|
try:
|
||||||
field = model._meta.get_field_by_name(field_name)[0]
|
field = model._meta.get_field(field_name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
return refer_to_missing_field(field=field_name, option=label,
|
return refer_to_missing_field(field=field_name, option=label,
|
||||||
model=model, obj=cls, id='admin.E121')
|
model=model, obj=cls, id='admin.E121')
|
||||||
|
|
|
@ -406,7 +406,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
||||||
rel_name = None
|
rel_name = None
|
||||||
for part in parts[:-1]:
|
for part in parts[:-1]:
|
||||||
try:
|
try:
|
||||||
field, _, _, _ = model._meta.get_field_by_name(part)
|
field = model._meta.get_field(part)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
# Lookups on non-existent fields are ok, since they're ignored
|
# Lookups on non-existent fields are ok, since they're ignored
|
||||||
# later.
|
# later.
|
||||||
|
@ -422,7 +422,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
||||||
else:
|
else:
|
||||||
rel_name = None
|
rel_name = None
|
||||||
elif isinstance(field, ForeignObjectRel):
|
elif isinstance(field, ForeignObjectRel):
|
||||||
model = field.model
|
model = field.related_model
|
||||||
rel_name = model._meta.pk.name
|
rel_name = model._meta.pk.name
|
||||||
else:
|
else:
|
||||||
rel_name = None
|
rel_name = None
|
||||||
|
@ -473,9 +473,12 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
||||||
for inline in admin.inlines:
|
for inline in admin.inlines:
|
||||||
registered_models.add(inline.model)
|
registered_models.add(inline.model)
|
||||||
|
|
||||||
for related_object in (opts.get_all_related_objects(include_hidden=True) +
|
related_objects = (
|
||||||
opts.get_all_related_many_to_many_objects()):
|
f for f in opts.get_fields(include_hidden=True)
|
||||||
related_model = related_object.model
|
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
|
if (any(issubclass(model, related_model) for model in registered_models) and
|
||||||
related_object.field.rel.get_related_field() == field):
|
related_object.field.rel.get_related_field() == field):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -326,7 +326,7 @@ def date_hierarchy(cl):
|
||||||
"""
|
"""
|
||||||
if cl.date_hierarchy:
|
if cl.date_hierarchy:
|
||||||
field_name = 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'
|
dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates'
|
||||||
year_field = '%s__year' % field_name
|
year_field = '%s__year' % field_name
|
||||||
month_field = '%s__month' % 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.
|
Returns True if 'distinct()' should be used to query the given lookup path.
|
||||||
"""
|
"""
|
||||||
field_name = lookup_path.split('__', 1)[0]
|
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()):
|
if hasattr(field, 'get_path_info') and any(path.m2m for path in field.get_path_info()):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -265,7 +265,7 @@ def model_ngettext(obj, n=None):
|
||||||
def lookup_field(name, obj, model_admin=None):
|
def lookup_field(name, obj, model_admin=None):
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
try:
|
try:
|
||||||
f = opts.get_field(name)
|
f = _get_non_gfk_field(opts, name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
# For non-field values, the value is either a method, property or
|
# For non-field values, the value is either a method, property or
|
||||||
# returned via a callable.
|
# returned via a callable.
|
||||||
|
@ -291,6 +291,17 @@ def lookup_field(name, obj, model_admin=None):
|
||||||
return f, attr, value
|
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):
|
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,
|
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
|
attr = None
|
||||||
try:
|
try:
|
||||||
field = model._meta.get_field_by_name(name)[0]
|
field = _get_non_gfk_field(model._meta, name)
|
||||||
try:
|
try:
|
||||||
label = field.verbose_name
|
label = field.verbose_name
|
||||||
except AttributeError:
|
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):
|
def help_text_for_field(name, model):
|
||||||
help_text = ""
|
help_text = ""
|
||||||
try:
|
try:
|
||||||
field_data = model._meta.get_field_by_name(name)
|
field = _get_non_gfk_field(model._meta, name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
field = field_data[0]
|
|
||||||
if hasattr(field, 'help_text'):
|
if hasattr(field, 'help_text'):
|
||||||
help_text = field.help_text
|
help_text = field.help_text
|
||||||
return smart_text(help_text)
|
return smart_text(help_text)
|
||||||
|
@ -425,19 +435,21 @@ def reverse_field_path(model, path):
|
||||||
parent = model
|
parent = model
|
||||||
pieces = path.split(LOOKUP_SEP)
|
pieces = path.split(LOOKUP_SEP)
|
||||||
for piece in pieces:
|
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:
|
# skip trailing data field if extant:
|
||||||
if len(reversed_path) == len(pieces) - 1: # final iteration
|
if len(reversed_path) == len(pieces) - 1: # final iteration
|
||||||
try:
|
try:
|
||||||
get_model_from_relation(field)
|
get_model_from_relation(field)
|
||||||
except NotRelationField:
|
except NotRelationField:
|
||||||
break
|
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()
|
related_name = field.related_query_name()
|
||||||
parent = field.rel.to
|
parent = field.rel.to
|
||||||
else:
|
else:
|
||||||
related_name = field.field.name
|
related_name = field.field.name
|
||||||
parent = field.model
|
parent = field.related_model
|
||||||
reversed_path.insert(0, related_name)
|
reversed_path.insert(0, related_name)
|
||||||
return (parent, LOOKUP_SEP.join(reversed_path))
|
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])
|
parent = get_model_from_relation(fields[-1])
|
||||||
else:
|
else:
|
||||||
parent = model
|
parent = model
|
||||||
fields.append(parent._meta.get_field_by_name(piece)[0])
|
fields.append(parent._meta.get_field(piece))
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -346,7 +346,7 @@ class ModelAdminValidator(BaseValidator):
|
||||||
check_isseq(cls, 'list_editable', cls.list_editable)
|
check_isseq(cls, 'list_editable', cls.list_editable)
|
||||||
for idx, field_name in enumerate(cls.list_editable):
|
for idx, field_name in enumerate(cls.list_editable):
|
||||||
try:
|
try:
|
||||||
field = model._meta.get_field_by_name(field_name)[0]
|
field = model._meta.get_field(field_name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
|
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
|
||||||
"field, '%s', not defined on %s.%s."
|
"field, '%s', not defined on %s.%s."
|
||||||
|
|
|
@ -262,7 +262,7 @@ class ModelDetailView(BaseAdminDocsView):
|
||||||
})
|
})
|
||||||
|
|
||||||
# Gather related objects
|
# 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") % {
|
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {
|
||||||
'app_label': rel.opts.app_label,
|
'app_label': rel.opts.app_label,
|
||||||
'object_name': rel.opts.object_name,
|
'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
|
Provides a generic relation to any object through content-type/object-id
|
||||||
fields.
|
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):
|
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
|
||||||
self.ct_field = ct_field
|
self.ct_field = ct_field
|
||||||
|
@ -28,12 +40,13 @@ class GenericForeignKey(object):
|
||||||
self.for_concrete_model = for_concrete_model
|
self.for_concrete_model = for_concrete_model
|
||||||
self.editable = False
|
self.editable = False
|
||||||
self.rel = None
|
self.rel = None
|
||||||
|
self.column = None
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, **kwargs):
|
def contribute_to_class(self, cls, name, **kwargs):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.model = cls
|
self.model = cls
|
||||||
self.cache_attr = "_%s_cache" % name
|
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
|
# Only run pre-initialization field assignment on non-abstract models
|
||||||
if not cls._meta.abstract:
|
if not cls._meta.abstract:
|
||||||
|
@ -243,6 +256,13 @@ class GenericForeignKey(object):
|
||||||
|
|
||||||
class GenericRelation(ForeignObject):
|
class GenericRelation(ForeignObject):
|
||||||
"""Provides an accessor to generic related objects (e.g. comments)"""
|
"""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):
|
def __init__(self, to, **kwargs):
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
|
@ -303,8 +323,7 @@ class GenericRelation(ForeignObject):
|
||||||
|
|
||||||
def resolve_related_fields(self):
|
def resolve_related_fields(self):
|
||||||
self.to_fields = [self.model._meta.pk.name]
|
self.to_fields = [self.model._meta.pk.name]
|
||||||
return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
|
return [(self.rel.to._meta.get_field(self.object_id_field_name), self.model._meta.pk)]
|
||||||
self.model._meta.pk)]
|
|
||||||
|
|
||||||
def get_path_info(self):
|
def get_path_info(self):
|
||||||
opts = self.rel.to._meta
|
opts = self.rel.to._meta
|
||||||
|
@ -345,7 +364,7 @@ class GenericRelation(ForeignObject):
|
||||||
for_concrete_model=self.for_concrete_model)
|
for_concrete_model=self.for_concrete_model)
|
||||||
|
|
||||||
def get_extra_restriction(self, where_class, alias, remote_alias):
|
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
|
contenttype_pk = self.get_content_type().pk
|
||||||
cond = where_class()
|
cond = where_class()
|
||||||
lookup = field.get_lookup('exact')(Col(remote_alias, field, field), contenttype_pk)
|
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:
|
elif geo_field not in opts.local_fields:
|
||||||
# This geographic field is inherited from another model, so we have to
|
# This geographic field is inherited from another model, so we have to
|
||||||
# use the db table for the _parent_ model instead.
|
# 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)
|
return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
|
||||||
else:
|
else:
|
||||||
return self.query.get_compiler(self.db)._field_column(geo_field)
|
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()
|
seen = self.query.included_inherited_models.copy()
|
||||||
if start_alias:
|
if start_alias:
|
||||||
seen[None] = 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):
|
if from_parent and model is not None and issubclass(from_parent, model):
|
||||||
# Avoid loading data for already loaded parents.
|
# Avoid loading data for already loaded parents.
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -23,7 +23,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB
|
||||||
|
|
||||||
if field_name:
|
if field_name:
|
||||||
try:
|
try:
|
||||||
field, _, _, _ = klass._meta.get_field_by_name(field_name)
|
field = klass._meta.get_field(field_name)
|
||||||
if not isinstance(field, GeometryField):
|
if not isinstance(field, GeometryField):
|
||||||
raise FieldDoesNotExist
|
raise FieldDoesNotExist
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
|
|
|
@ -457,11 +457,10 @@ class LayerMapping(object):
|
||||||
|
|
||||||
def geometry_field(self):
|
def geometry_field(self):
|
||||||
"Returns the GeometryField instance associated with the geographic column."
|
"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.
|
# get the correct field instance if there's model inheritance.
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
|
return opts.get_field(self.geom_field)
|
||||||
return fld
|
|
||||||
|
|
||||||
def make_multi(self, geom_type, model_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.
|
# 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:
|
if 'srtext' in srs_field_names:
|
||||||
kwargs['srtext'] = srs.wkt
|
kwargs['srtext'] = srs.wkt
|
||||||
if 'ref_sys_name' in srs_field_names:
|
if 'ref_sys_name' in srs_field_names:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from __future__ import unicode_literals
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.serializers import base
|
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.encoding import force_text, is_protected_type
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
@ -101,12 +101,12 @@ def Deserializer(object_list, **options):
|
||||||
if 'pk' in d:
|
if 'pk' in d:
|
||||||
data[Model._meta.pk.attname] = Model._meta.pk.to_python(d.get("pk", None))
|
data[Model._meta.pk.attname] = Model._meta.pk.to_python(d.get("pk", None))
|
||||||
m2m_data = {}
|
m2m_data = {}
|
||||||
model_fields = Model._meta.get_all_field_names()
|
field_names = {f.name for f in Model._meta.get_fields()}
|
||||||
|
|
||||||
# Handle each field
|
# Handle each field
|
||||||
for (field_name, field_value) in six.iteritems(d["fields"]):
|
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
|
# skip fields no longer on model
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,7 @@ class Deserializer(base.Deserializer):
|
||||||
# {m2m_accessor_attribute : [list_of_related_objects]})
|
# {m2m_accessor_attribute : [list_of_related_objects]})
|
||||||
m2m_data = {}
|
m2m_data = {}
|
||||||
|
|
||||||
model_fields = Model._meta.get_all_field_names()
|
field_names = {f.name for f in Model._meta.get_fields()}
|
||||||
# Deserialize each field.
|
# Deserialize each field.
|
||||||
for field_node in node.getElementsByTagName("field"):
|
for field_node in node.getElementsByTagName("field"):
|
||||||
# If the field is missing the name attribute, bail (are you
|
# 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
|
# Get the field from the Model. This will raise a
|
||||||
# FieldDoesNotExist if, well, the field doesn't exist, which will
|
# FieldDoesNotExist if, well, the field doesn't exist, which will
|
||||||
# be propagated correctly unless ignorenonexistent=True is used.
|
# 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
|
continue
|
||||||
field = Model._meta.get_field(field_name)
|
field = Model._meta.get_field(field_name)
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ class BaseDatabaseCreation(object):
|
||||||
for f in model._meta.local_fields:
|
for f in model._meta.local_fields:
|
||||||
output.extend(self.sql_indexes_for_field(model, f, style))
|
output.extend(self.sql_indexes_for_field(model, f, style))
|
||||||
for fs in model._meta.index_together:
|
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))
|
output.extend(self.sql_indexes_for_fields(model, fields, style))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ class BaseDatabaseCreation(object):
|
||||||
for f in model._meta.local_fields:
|
for f in model._meta.local_fields:
|
||||||
output.extend(self.sql_destroy_indexes_for_field(model, f, style))
|
output.extend(self.sql_destroy_indexes_for_field(model, f, style))
|
||||||
for fs in model._meta.index_together:
|
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))
|
output.extend(self.sql_destroy_indexes_for_fields(model, fields, style))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,11 @@ from django.utils import six
|
||||||
logger = getLogger('django.db.backends.schema')
|
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):
|
class BaseDatabaseSchemaEditor(object):
|
||||||
"""
|
"""
|
||||||
This class (and its subclasses) are responsible for emitting schema-changing
|
This class (and its subclasses) are responsible for emitting schema-changing
|
||||||
|
@ -261,7 +266,7 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
|
|
||||||
# Add any unique_togethers
|
# Add any unique_togethers
|
||||||
for fields in model._meta.unique_together:
|
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 % {
|
column_sqls.append(self.sql_create_table_unique % {
|
||||||
"columns": ", ".join(self.quote_name(column) for column in columns),
|
"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)
|
news = set(tuple(fields) for fields in new_unique_together)
|
||||||
# Deleted uniques
|
# Deleted uniques
|
||||||
for fields in olds.difference(news):
|
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)
|
constraint_names = self._constraint_names(model, columns, unique=True)
|
||||||
if len(constraint_names) != 1:
|
if len(constraint_names) != 1:
|
||||||
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
|
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]))
|
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_names[0]))
|
||||||
# Created uniques
|
# Created uniques
|
||||||
for fields in news.difference(olds):
|
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))
|
self.execute(self._create_unique_sql(model, columns))
|
||||||
|
|
||||||
def alter_index_together(self, model, old_index_together, new_index_together):
|
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)
|
news = set(tuple(fields) for fields in new_index_together)
|
||||||
# Deleted indexes
|
# Deleted indexes
|
||||||
for fields in olds.difference(news):
|
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)
|
constraint_names = self._constraint_names(model, list(columns), index=True)
|
||||||
if len(constraint_names) != 1:
|
if len(constraint_names) != 1:
|
||||||
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
|
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]))
|
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, constraint_names[0]))
|
||||||
# Created indexes
|
# Created indexes
|
||||||
for field_names in news.difference(olds):
|
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"))
|
self.execute(self._create_index_sql(model, fields, suffix="_idx"))
|
||||||
|
|
||||||
def alter_db_table(self, model, old_db_table, new_db_table):
|
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
|
# Drop incoming FK constraints if we're a primary key and things are going
|
||||||
# to change.
|
# to change.
|
||||||
if old_field.primary_key and new_field.primary_key and old_type != new_type:
|
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():
|
# '_meta.related_field' also contains M2M reverse fields, these
|
||||||
rel_fk_names = self._constraint_names(rel.model, [rel.field.column], foreign_key=True)
|
# 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:
|
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)
|
# Removed an index? (no strict check, as multiple indexes are possible)
|
||||||
if (old_field.db_index and not new_field.db_index and
|
if (old_field.db_index and not new_field.db_index and
|
||||||
not old_field.unique and not
|
not old_field.unique and not
|
||||||
|
@ -661,7 +668,7 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
# referring to us.
|
# referring to us.
|
||||||
rels_to_update = []
|
rels_to_update = []
|
||||||
if old_field.primary_key and new_field.primary_key and old_type != new_type:
|
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?
|
# Changed to become primary key?
|
||||||
# Note that we don't detect unsetting of a PK, as we assume another field
|
# Note that we don't detect unsetting of a PK, as we assume another field
|
||||||
# will always come along and replace it.
|
# will always come along and replace it.
|
||||||
|
@ -684,14 +691,14 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Update all referencing columns
|
# 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
|
# Handle our type alters on the other end of rels from the PK stuff above
|
||||||
for rel in rels_to_update:
|
for rel in rels_to_update:
|
||||||
rel_db_params = rel.field.db_parameters(connection=self.connection)
|
rel_db_params = rel.field.db_parameters(connection=self.connection)
|
||||||
rel_type = rel_db_params['type']
|
rel_type = rel_db_params['type']
|
||||||
self.execute(
|
self.execute(
|
||||||
self.sql_alter_column % {
|
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 % {
|
"changes": self.sql_alter_column_type % {
|
||||||
"column": self.quote_name(rel.field.column),
|
"column": self.quote_name(rel.field.column),
|
||||||
"type": rel_type,
|
"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"))
|
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
|
# 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:
|
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():
|
for rel in new_field.model._meta.related_objects:
|
||||||
self.execute(self._create_fk_sql(rel.model, rel.field, "_fk"))
|
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?
|
# Does it have check constraints we need to add?
|
||||||
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
|
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
|
||||||
self.execute(
|
self.execute(
|
||||||
|
@ -765,14 +773,14 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
new_field.rel.through,
|
new_field.rel.through,
|
||||||
# We need the field that points to the target model, so we can tell alter_field to change it -
|
# 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)
|
# 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],
|
old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()),
|
||||||
new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0],
|
new_field.rel.through._meta.get_field(new_field.m2m_reverse_field_name()),
|
||||||
)
|
)
|
||||||
self.alter_field(
|
self.alter_field(
|
||||||
new_field.rel.through,
|
new_field.rel.through,
|
||||||
# for self-referential models we need to alter field from the other end too
|
# 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],
|
old_field.rel.through._meta.get_field(old_field.m2m_field_name()),
|
||||||
new_field.rel.through._meta.get_field_by_name(new_field.m2m_field_name())[0],
|
new_field.rel.through._meta.get_field(new_field.m2m_field_name()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_index_name(self, model, column_names, suffix=""):
|
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=""))
|
output.append(self._create_index_sql(model, [field], suffix=""))
|
||||||
|
|
||||||
for field_names in model._meta.index_together:
|
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"))
|
output.append(self._create_index_sql(model, fields, suffix="_idx"))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
|
@ -227,8 +227,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
alter_fields=[(
|
alter_fields=[(
|
||||||
# We need the field that points to the target model, so we can tell alter_field to change it -
|
# 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)
|
# 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],
|
old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()),
|
||||||
new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0],
|
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()),
|
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_name = self.renamed_models.get((app_label, model_name), model_name)
|
||||||
old_model_state = self.from_state.models[app_label, old_model_name]
|
old_model_state = self.from_state.models[app_label, old_model_name]
|
||||||
for field_name, field in old_model_state.fields:
|
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)
|
if (hasattr(old_field, "rel") and getattr(old_field.rel, "through", None)
|
||||||
and not old_field.rel.through._meta.auto_created):
|
and not old_field.rel.through._meta.auto_created):
|
||||||
through_key = (
|
through_key = (
|
||||||
|
@ -685,26 +685,14 @@ class MigrationAutodetector(object):
|
||||||
# and the removal of all its own related fields, and if it's
|
# and the removal of all its own related fields, and if it's
|
||||||
# a through model the field that references it.
|
# a through model the field that references it.
|
||||||
dependencies = []
|
dependencies = []
|
||||||
for related_object in model._meta.get_all_related_objects():
|
for related_object in model._meta.related_objects:
|
||||||
dependencies.append((
|
related_object_app_label = related_object.related_model._meta.app_label
|
||||||
related_object.model._meta.app_label,
|
object_name = related_object.related_model._meta.object_name
|
||||||
related_object.model._meta.object_name,
|
field_name = related_object.field.name
|
||||||
related_object.field.name,
|
dependencies.append((related_object_app_label, object_name, field_name, False))
|
||||||
False,
|
if not related_object.many_to_many:
|
||||||
))
|
dependencies.append((related_object_app_label, object_name, field_name, "alter"))
|
||||||
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 name, field in sorted(related_fields.items()):
|
for name, field in sorted(related_fields.items()):
|
||||||
dependencies.append((app_label, model_name, name, False))
|
dependencies.append((app_label, model_name, name, False))
|
||||||
# We're referenced in another field's through=
|
# 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):
|
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_name = self.renamed_models.get((app_label, model_name), model_name)
|
||||||
old_model_state = self.from_state.models[app_label, old_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!
|
# Scan to see if this is actually a rename!
|
||||||
field_dec = self.deep_deconstruct(field)
|
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):
|
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)
|
self._generate_added_field(app_label, model_name, field_name)
|
||||||
|
|
||||||
def _generate_added_field(self, 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
|
# Fields that are foreignkeys/m2ms depend on stuff
|
||||||
dependencies = []
|
dependencies = []
|
||||||
if field.rel and field.rel.to:
|
if field.rel and field.rel.to:
|
||||||
|
@ -847,8 +835,8 @@ class MigrationAutodetector(object):
|
||||||
# Did the field change?
|
# Did the field change?
|
||||||
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
|
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_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]
|
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_by_name(field_name)[0]
|
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
|
# Implement any model renames on relations; these are handled by RenameModel
|
||||||
# so we need to exclude them from the comparison
|
# so we need to exclude them from the comparison
|
||||||
if hasattr(new_field, "rel") and getattr(new_field.rel, "to", None):
|
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)
|
to_model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model = from_state.apps.get_model(app_label, self.model_name)
|
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:
|
if not self.preserve_default:
|
||||||
field.default = self.field.default
|
field.default = self.field.default
|
||||||
schema_editor.add_field(
|
schema_editor.add_field(
|
||||||
|
@ -57,7 +57,7 @@ class AddField(Operation):
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.apps.get_model(app_label, self.model_name)
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
|
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):
|
def describe(self):
|
||||||
return "Add field %s to %s" % (self.name, self.model_name)
|
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):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.apps.get_model(app_label, self.model_name)
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
|
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):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
to_model = to_state.apps.get_model(app_label, self.model_name)
|
to_model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model = from_state.apps.get_model(app_label, self.model_name)
|
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):
|
def describe(self):
|
||||||
return "Remove field %s from %s" % (self.name, self.model_name)
|
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)
|
to_model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model = from_state.apps.get_model(app_label, self.model_name)
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
from_field = from_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_by_name(self.name)[0]
|
to_field = to_model._meta.get_field(self.name)
|
||||||
# If the field is a relatedfield with an unresolved rel.to, just
|
# If the field is a relatedfield with an unresolved rel.to, just
|
||||||
# set it equal to the other field side. Bandaid fix for AlterField
|
# set it equal to the other field side. Bandaid fix for AlterField
|
||||||
# migrations that are part of a RenameModel change.
|
# 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)
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
schema_editor.alter_field(
|
schema_editor.alter_field(
|
||||||
from_model,
|
from_model,
|
||||||
from_model._meta.get_field_by_name(self.old_name)[0],
|
from_model._meta.get_field(self.old_name),
|
||||||
to_model._meta.get_field_by_name(self.new_name)[0],
|
to_model._meta.get_field(self.new_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
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)
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
schema_editor.alter_field(
|
schema_editor.alter_field(
|
||||||
from_model,
|
from_model,
|
||||||
from_model._meta.get_field_by_name(self.new_name)[0],
|
from_model._meta.get_field(self.new_name),
|
||||||
to_model._meta.get_field_by_name(self.old_name)[0],
|
to_model._meta.get_field(self.old_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
|
|
|
@ -138,25 +138,27 @@ class RenameModel(Operation):
|
||||||
)
|
)
|
||||||
|
|
||||||
def state_forwards(self, app_label, state):
|
def state_forwards(self, app_label, state):
|
||||||
# Get all of the related objects we need to repoint
|
|
||||||
apps = state.apps
|
apps = state.apps
|
||||||
model = apps.get_model(app_label, self.old_name)
|
model = apps.get_model(app_label, self.old_name)
|
||||||
model._meta.apps = apps
|
model._meta.apps = apps
|
||||||
related_objects = model._meta.get_all_related_objects()
|
# Get all of the related objects we need to repoint
|
||||||
related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
|
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
|
# 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()] = state.models[app_label, self.old_name.lower()]
|
||||||
state.models[app_label, self.new_name.lower()].name = self.new_name
|
state.models[app_label, self.new_name.lower()].name = self.new_name
|
||||||
state.remove_model(app_label, self.old_name)
|
state.remove_model(app_label, self.old_name)
|
||||||
# Repoint the FKs and M2Ms pointing to us
|
# 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.
|
# 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())
|
related_key = (app_label, self.new_name.lower())
|
||||||
else:
|
else:
|
||||||
related_key = (
|
related_key = (
|
||||||
related_object.model._meta.app_label,
|
related_object.related_model._meta.app_label,
|
||||||
related_object.model._meta.object_name.lower(),
|
related_object.related_model._meta.object_name.lower(),
|
||||||
)
|
)
|
||||||
new_fields = []
|
new_fields = []
|
||||||
for name, field in state.models[related_key].fields:
|
for name, field in state.models[related_key].fields:
|
||||||
|
@ -179,21 +181,19 @@ class RenameModel(Operation):
|
||||||
new_model._meta.db_table,
|
new_model._meta.db_table,
|
||||||
)
|
)
|
||||||
# Alter the fields pointing to us
|
# Alter the fields pointing to us
|
||||||
related_objects = old_model._meta.get_all_related_objects()
|
for related_object in old_model._meta.related_objects:
|
||||||
related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
|
if related_object.related_model == old_model:
|
||||||
for related_object in (related_objects + related_m2m_objects):
|
|
||||||
if related_object.model == old_model:
|
|
||||||
model = new_model
|
model = new_model
|
||||||
related_key = (app_label, self.new_name.lower())
|
related_key = (app_label, self.new_name.lower())
|
||||||
else:
|
else:
|
||||||
model = related_object.model
|
model = related_object.related_model
|
||||||
related_key = (
|
related_key = (
|
||||||
related_object.model._meta.app_label,
|
related_object.related_model._meta.app_label,
|
||||||
related_object.model._meta.object_name.lower(),
|
related_object.related_model._meta.object_name.lower(),
|
||||||
)
|
)
|
||||||
to_field = to_state.apps.get_model(
|
to_field = to_state.apps.get_model(
|
||||||
*related_key
|
*related_key
|
||||||
)._meta.get_field_by_name(related_object.field.name)[0]
|
)._meta.get_field(related_object.field.name)
|
||||||
schema_editor.alter_field(
|
schema_editor.alter_field(
|
||||||
model,
|
model,
|
||||||
related_object.field,
|
related_object.field,
|
||||||
|
@ -394,11 +394,11 @@ class AlterOrderWithRespectTo(Operation):
|
||||||
from_model = from_state.apps.get_model(app_label, self.name)
|
from_model = from_state.apps.get_model(app_label, self.name)
|
||||||
# Remove a field if we need to
|
# Remove a field if we need to
|
||||||
if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_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
|
# Add a field if we need to (altering the column is untouched as
|
||||||
# it's likely a rename)
|
# it's likely a rename)
|
||||||
elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
|
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():
|
if not field.has_default():
|
||||||
field.default = 0
|
field.default = 0
|
||||||
schema_editor.add_field(
|
schema_editor.add_field(
|
||||||
|
|
|
@ -50,15 +50,15 @@ class ProjectState(object):
|
||||||
model_name = model_name.lower()
|
model_name = model_name.lower()
|
||||||
try:
|
try:
|
||||||
related_old = {
|
related_old = {
|
||||||
f.model for f in
|
f.related_model for f in
|
||||||
self.apps.get_model(app_label, model_name)._meta.get_all_related_objects()
|
self.apps.get_model(app_label, model_name)._meta.related_objects
|
||||||
}
|
}
|
||||||
except LookupError:
|
except LookupError:
|
||||||
related_old = set()
|
related_old = set()
|
||||||
self._reload_one_model(app_label, model_name)
|
self._reload_one_model(app_label, model_name)
|
||||||
# Reload models if there are relations
|
# Reload models if there are relations
|
||||||
model = self.apps.get_model(app_label, model_name)
|
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):
|
for rel_model in related_old.union(related_m2m):
|
||||||
self._reload_one_model(rel_model._meta.app_label, rel_model._meta.model_name)
|
self._reload_one_model(rel_model._meta.app_label, rel_model._meta.model_name)
|
||||||
if related_m2m:
|
if related_m2m:
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
|
from itertools import chain
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
@ -175,12 +176,12 @@ class ModelBase(type):
|
||||||
new_class.add_to_class(obj_name, obj)
|
new_class.add_to_class(obj_name, obj)
|
||||||
|
|
||||||
# All the fields of any type declared on this model
|
# All the fields of any type declared on this model
|
||||||
new_fields = (
|
new_fields = chain(
|
||||||
new_class._meta.local_fields +
|
new_class._meta.local_fields,
|
||||||
new_class._meta.local_many_to_many +
|
new_class._meta.local_many_to_many,
|
||||||
new_class._meta.virtual_fields
|
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.
|
# Basic setup for proxy models.
|
||||||
if is_proxy:
|
if is_proxy:
|
||||||
|
@ -202,6 +203,7 @@ class ModelBase(type):
|
||||||
raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
|
raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
|
||||||
new_class._meta.setup_proxy(base)
|
new_class._meta.setup_proxy(base)
|
||||||
new_class._meta.concrete_model = base._meta.concrete_model
|
new_class._meta.concrete_model = base._meta.concrete_model
|
||||||
|
base._meta.concrete_model._meta.proxied_children.append(new_class._meta)
|
||||||
else:
|
else:
|
||||||
new_class._meta.concrete_model = new_class
|
new_class._meta.concrete_model = new_class
|
||||||
|
|
||||||
|
@ -342,7 +344,7 @@ class ModelBase(type):
|
||||||
|
|
||||||
# Give the class a docstring -- its definition.
|
# Give the class a docstring -- its definition.
|
||||||
if cls.__doc__ is None:
|
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(
|
get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get(
|
||||||
'%s.%s' % (opts.app_label, opts.model_name)
|
'%s.%s' % (opts.app_label, opts.model_name)
|
||||||
|
@ -630,7 +632,7 @@ class Model(six.with_metaclass(ModelBase)):
|
||||||
and not use this method.
|
and not use this method.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
field = self._meta.get_field_by_name(field_name)[0]
|
field = self._meta.get_field(field_name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
return getattr(self, field_name)
|
return getattr(self, field_name)
|
||||||
return getattr(self, field.attname)
|
return getattr(self, field.attname)
|
||||||
|
@ -1438,12 +1440,17 @@ class Model(six.with_metaclass(ModelBase)):
|
||||||
def _check_local_fields(cls, fields, option):
|
def _check_local_fields(cls, fields, option):
|
||||||
from django.db import models
|
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 = []
|
errors = []
|
||||||
for field_name in fields:
|
for field_name in fields:
|
||||||
try:
|
try:
|
||||||
field = cls._meta.get_field(field_name,
|
field = forward_fields_map[field_name]
|
||||||
many_to_many=True)
|
except KeyError:
|
||||||
except FieldDoesNotExist:
|
|
||||||
errors.append(
|
errors.append(
|
||||||
checks.Error(
|
checks.Error(
|
||||||
"'%s' refers to the non-existent field '%s'." % (option, field_name),
|
"'%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):
|
def _check_ordering(cls):
|
||||||
""" Check "ordering" option -- is it a list of strings and do all fields
|
""" Check "ordering" option -- is it a list of strings and do all fields
|
||||||
exist? """
|
exist? """
|
||||||
|
|
||||||
if not cls._meta.ordering:
|
if not cls._meta.ordering:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -1500,7 +1506,6 @@ class Model(six.with_metaclass(ModelBase)):
|
||||||
]
|
]
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
fields = cls._meta.ordering
|
fields = cls._meta.ordering
|
||||||
|
|
||||||
# Skip '?' fields.
|
# Skip '?' fields.
|
||||||
|
@ -1518,28 +1523,30 @@ class Model(six.with_metaclass(ModelBase)):
|
||||||
|
|
||||||
# Skip ordering on pk. This is always a valid order_by field
|
# 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.
|
# 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:
|
# Check for invalid or non-existent fields in ordering.
|
||||||
try:
|
invalid_fields = []
|
||||||
cls._meta.get_field(field_name, many_to_many=False)
|
|
||||||
except FieldDoesNotExist:
|
# Any field name that is not present in field_names does not exist.
|
||||||
if field_name.endswith('_id'):
|
# Also, ordering by m2m fields is not allowed.
|
||||||
try:
|
opts = cls._meta
|
||||||
field = cls._meta.get_field(field_name[:-3], many_to_many=False)
|
valid_fields = set(chain.from_iterable(
|
||||||
except FieldDoesNotExist:
|
(f.name, f.attname) if not (f.auto_created and not f.concrete) else (f.field.related_query_name(),)
|
||||||
pass
|
for f in chain(opts.fields, opts.related_objects)
|
||||||
else:
|
))
|
||||||
if field.attname == field_name:
|
|
||||||
continue
|
invalid_fields.extend(fields - valid_fields)
|
||||||
errors.append(
|
|
||||||
checks.Error(
|
for invalid_field in invalid_fields:
|
||||||
"'ordering' refers to the non-existent field '%s'." % field_name,
|
errors.append(
|
||||||
hint=None,
|
checks.Error(
|
||||||
obj=cls,
|
"'ordering' refers to the non-existent field '%s'." % invalid_field,
|
||||||
id='models.E015',
|
hint=None,
|
||||||
)
|
obj=cls,
|
||||||
|
id='models.E015',
|
||||||
)
|
)
|
||||||
|
)
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.db import connections, transaction, IntegrityError
|
from django.db import connections, transaction, IntegrityError
|
||||||
|
@ -51,6 +52,23 @@ def DO_NOTHING(collector, field, sub_objs, using):
|
||||||
pass
|
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):
|
class Collector(object):
|
||||||
def __init__(self, using):
|
def __init__(self, using):
|
||||||
self.using = using
|
self.using = using
|
||||||
|
@ -134,8 +152,7 @@ class Collector(object):
|
||||||
return False
|
return False
|
||||||
# Foreign keys pointing to this model, both from m2m and other
|
# Foreign keys pointing to this model, both from m2m and other
|
||||||
# models.
|
# models.
|
||||||
for related in opts.get_all_related_objects(
|
for related in get_candidate_relations_to_delete(opts):
|
||||||
include_hidden=True, include_proxy_eq=True):
|
|
||||||
if related.field.rel.on_delete is not DO_NOTHING:
|
if related.field.rel.on_delete is not DO_NOTHING:
|
||||||
return False
|
return False
|
||||||
for field in model._meta.virtual_fields:
|
for field in model._meta.virtual_fields:
|
||||||
|
@ -184,7 +201,7 @@ class Collector(object):
|
||||||
model = new_objs[0].__class__
|
model = new_objs[0].__class__
|
||||||
|
|
||||||
# Recursively collect concrete model's parent models, but not their
|
# 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
|
concrete_model = model._meta.concrete_model
|
||||||
for ptr in six.itervalues(concrete_model._meta.parents):
|
for ptr in six.itervalues(concrete_model._meta.parents):
|
||||||
if ptr:
|
if ptr:
|
||||||
|
@ -199,8 +216,7 @@ class Collector(object):
|
||||||
reverse_dependency=True)
|
reverse_dependency=True)
|
||||||
|
|
||||||
if collect_related:
|
if collect_related:
|
||||||
for related in model._meta.get_all_related_objects(
|
for related in get_candidate_relations_to_delete(model._meta):
|
||||||
include_hidden=True, include_proxy_eq=True):
|
|
||||||
field = related.field
|
field = related.field
|
||||||
if field.rel.on_delete == DO_NOTHING:
|
if field.rel.on_delete == DO_NOTHING:
|
||||||
continue
|
continue
|
||||||
|
@ -225,7 +241,7 @@ class Collector(object):
|
||||||
Gets a QuerySet of objects related to ``objs`` via the relation ``related``.
|
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}
|
**{"%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 import six
|
||||||
from django.utils.itercompat import is_iterable
|
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
|
from django.core.exceptions import FieldDoesNotExist # NOQA
|
||||||
|
|
||||||
# Avoid "TypeError: Item in ``from list'' not a string" -- unicode_literals
|
# 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):
|
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:
|
# A guide to Field parameters:
|
||||||
|
@ -116,6 +118,15 @@ class Field(RegisterLookupMixin):
|
||||||
system_check_deprecated_details = None
|
system_check_deprecated_details = None
|
||||||
system_check_removed_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
|
# Generic field type description, usually overridden by subclasses
|
||||||
def _description(self):
|
def _description(self):
|
||||||
return _('Field of type: %(field_type)s') % {
|
return _('Field of type: %(field_type)s') % {
|
||||||
|
@ -137,6 +148,7 @@ class Field(RegisterLookupMixin):
|
||||||
self.max_length, self._unique = max_length, unique
|
self.max_length, self._unique = max_length, unique
|
||||||
self.blank, self.null = blank, null
|
self.blank, self.null = blank, null
|
||||||
self.rel = rel
|
self.rel = rel
|
||||||
|
self.is_relation = self.rel is not None
|
||||||
self.default = default
|
self.default = default
|
||||||
self.editable = editable
|
self.editable = editable
|
||||||
self.serialize = serialize
|
self.serialize = serialize
|
||||||
|
@ -603,6 +615,7 @@ class Field(RegisterLookupMixin):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.attname, self.column = self.get_attname_column()
|
self.attname, self.column = self.get_attname_column()
|
||||||
|
self.concrete = self.column is not None
|
||||||
if self.verbose_name is None and self.name:
|
if self.verbose_name is None and self.name:
|
||||||
self.verbose_name = self.name.replace('_', ' ')
|
self.verbose_name = self.name.replace('_', ' ')
|
||||||
|
|
||||||
|
@ -610,7 +623,7 @@ class Field(RegisterLookupMixin):
|
||||||
self.set_attributes_from_name(name)
|
self.set_attributes_from_name(name)
|
||||||
self.model = cls
|
self.model = cls
|
||||||
if virtual_only:
|
if virtual_only:
|
||||||
cls._meta.add_virtual_field(self)
|
cls._meta.add_field(self, virtual=True)
|
||||||
else:
|
else:
|
||||||
cls._meta.add_field(self)
|
cls._meta.add_field(self)
|
||||||
if self.choices:
|
if self.choices:
|
||||||
|
|
|
@ -98,6 +98,18 @@ signals.class_prepared.connect(do_pending_lookups)
|
||||||
|
|
||||||
|
|
||||||
class RelatedField(Field):
|
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):
|
def check(self, **kwargs):
|
||||||
errors = super(RelatedField, self).check(**kwargs)
|
errors = super(RelatedField, self).check(**kwargs)
|
||||||
errors.extend(self._check_related_name_is_valid())
|
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
|
# Check clashes between accessors/reverse query names of `field` and
|
||||||
# any other field accessor -- i. e. Model.foreign accessor clashes with
|
# any other field accessor -- i. e. Model.foreign accessor clashes with
|
||||||
# Model.m2m accessor.
|
# Model.m2m accessor.
|
||||||
potential_clashes = rel_opts.get_all_related_many_to_many_objects()
|
potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
|
||||||
potential_clashes += rel_opts.get_all_related_objects()
|
|
||||||
potential_clashes = (r for r in potential_clashes
|
|
||||||
if r.field is not self)
|
|
||||||
for clash_field in potential_clashes:
|
for clash_field in potential_clashes:
|
||||||
clash_name = "%s.%s" % ( # i. e. "Model.m2m"
|
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)
|
clash_field.field.name)
|
||||||
if clash_field.get_accessor_name() == rel_name:
|
if clash_field.get_accessor_name() == rel_name:
|
||||||
errors.append(
|
errors.append(
|
||||||
|
@ -392,7 +401,7 @@ class SingleRelatedObjectDescriptor(object):
|
||||||
# consistency with `ReverseSingleRelatedObjectDescriptor`.
|
# consistency with `ReverseSingleRelatedObjectDescriptor`.
|
||||||
return type(
|
return type(
|
||||||
str('RelatedObjectDoesNotExist'),
|
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)
|
return hasattr(instance, self.cache_name)
|
||||||
|
|
||||||
def get_queryset(self, **hints):
|
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
|
# If the related manager indicates that it should be used for
|
||||||
# related fields, respect that.
|
# related fields, respect that.
|
||||||
if not getattr(manager, 'use_for_related_fields', False):
|
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()
|
return manager.db_manager(hints=hints).all()
|
||||||
|
|
||||||
def get_prefetch_queryset(self, instances, queryset=None):
|
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)
|
params['%s__%s' % (self.related.field.name, rh_field.name)] = getattr(instance, rh_field.attname)
|
||||||
try:
|
try:
|
||||||
rel_obj = self.get_queryset(instance=instance).get(**params)
|
rel_obj = self.get_queryset(instance=instance).get(**params)
|
||||||
except self.related.model.DoesNotExist:
|
except self.related.related_model.DoesNotExist:
|
||||||
rel_obj = None
|
rel_obj = None
|
||||||
else:
|
else:
|
||||||
setattr(rel_obj, self.related.field.get_cache_name(), instance)
|
setattr(rel_obj, self.related.field.get_cache_name(), instance)
|
||||||
|
@ -470,7 +479,7 @@ class SingleRelatedObjectDescriptor(object):
|
||||||
self.related.get_accessor_name(),
|
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(
|
raise ValueError(
|
||||||
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
|
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
|
||||||
value,
|
value,
|
||||||
|
@ -825,9 +834,9 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
# Dynamically create a class that subclasses the related model's default
|
# Dynamically create a class that subclasses the related model's default
|
||||||
# manager.
|
# manager.
|
||||||
return create_foreign_related_manager(
|
return create_foreign_related_manager(
|
||||||
self.related.model._default_manager.__class__,
|
self.related.related_model._default_manager.__class__,
|
||||||
self.related.field,
|
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
|
# Dynamically create a class that subclasses the related
|
||||||
# model's default manager.
|
# model's default manager.
|
||||||
return create_many_related_manager(
|
return create_many_related_manager(
|
||||||
self.related.model._default_manager.__class__,
|
self.related.related_model._default_manager.__class__,
|
||||||
self.related.field.rel
|
self.related.field.rel
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1156,7 +1165,7 @@ class ManyRelatedObjectsDescriptor(object):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
rel_model = self.related.model
|
rel_model = self.related.related_model
|
||||||
|
|
||||||
manager = self.related_manager_cls(
|
manager = self.related_manager_cls(
|
||||||
model=rel_model,
|
model=rel_model,
|
||||||
|
@ -1255,6 +1264,12 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
||||||
|
|
||||||
|
|
||||||
class ForeignObjectRel(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,
|
def __init__(self, field, to, related_name=None, limit_choices_to=None,
|
||||||
parent_link=False, on_delete=None, related_query_name=None):
|
parent_link=False, on_delete=None, related_query_name=None):
|
||||||
self.field = field
|
self.field = field
|
||||||
|
@ -1267,32 +1282,55 @@ class ForeignObjectRel(object):
|
||||||
self.on_delete = on_delete
|
self.on_delete = on_delete
|
||||||
self.symmetrical = False
|
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
|
# __init__ as the field doesn't have its model yet. Calling these methods
|
||||||
# before field.contribute_to_class() has been called will result in
|
# before field.contribute_to_class() has been called will result in
|
||||||
# AttributeError
|
# AttributeError
|
||||||
@cached_property
|
@cached_property
|
||||||
def model(self):
|
def model(self):
|
||||||
if not self.field.model:
|
return self.to
|
||||||
raise AttributeError(
|
|
||||||
"This property can't be accessed before self.field.contribute_to_class has been called.")
|
|
||||||
return self.field.model
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def opts(self):
|
def opts(self):
|
||||||
return self.model._meta
|
return self.related_model._meta
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def to_opts(self):
|
def to_opts(self):
|
||||||
return self.to._meta
|
return self.to._meta
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def parent_model(self):
|
def hidden(self):
|
||||||
return self.to
|
return self.is_hidden()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def name(self):
|
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,
|
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
|
||||||
limit_to_currently_related=False):
|
limit_to_currently_related=False):
|
||||||
|
@ -1304,10 +1342,10 @@ class ForeignObjectRel(object):
|
||||||
initially for utilization by RelatedFieldListFilter.
|
initially for utilization by RelatedFieldListFilter.
|
||||||
"""
|
"""
|
||||||
first_choice = blank_choice if include_blank else []
|
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:
|
if limit_to_currently_related:
|
||||||
queryset = queryset.complex_filter(
|
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]
|
lst = [(x._get_pk_val(), smart_text(x)) for x in queryset]
|
||||||
return first_choice + lst
|
return first_choice + lst
|
||||||
|
@ -1318,7 +1356,7 @@ class ForeignObjectRel(object):
|
||||||
|
|
||||||
def is_hidden(self):
|
def is_hidden(self):
|
||||||
"Should the related object be hidden?"
|
"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):
|
def get_joining_columns(self):
|
||||||
return self.field.get_reverse_joining_columns()
|
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
|
# Due to backwards compatibility ModelForms need to be able to provide
|
||||||
# an alternate model. See BaseInlineFormSet.get_default_prefix().
|
# an alternate model. See BaseInlineFormSet.get_default_prefix().
|
||||||
opts = model._meta if model else self.opts
|
opts = model._meta if model else self.opts
|
||||||
model = model or self.model
|
model = model or self.related_model
|
||||||
if self.multiple:
|
if self.multiple:
|
||||||
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
|
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
|
||||||
if self.symmetrical and model == self.to:
|
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
|
Returns the Field in the 'to' object to which this relationship is
|
||||||
tied.
|
tied.
|
||||||
"""
|
"""
|
||||||
data = self.to._meta.get_field_by_name(self.field_name)
|
field = self.to._meta.get_field(self.field_name)
|
||||||
if not data[2]:
|
if not field.concrete:
|
||||||
raise FieldDoesNotExist("No related field named '%s'" %
|
raise FieldDoesNotExist("No related field named '%s'" %
|
||||||
self.field_name)
|
self.field_name)
|
||||||
return data[0]
|
return field
|
||||||
|
|
||||||
def set_field_name(self):
|
def set_field_name(self):
|
||||||
self.field_name = self.field_name or self.to._meta.pk.name
|
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.through_fields = through_fields
|
||||||
self.db_constraint = db_constraint
|
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):
|
def get_related_field(self):
|
||||||
"""
|
"""
|
||||||
Returns the field in the 'to' object to which this relationship is tied.
|
Returns the field in the 'to' object to which this relationship is tied.
|
||||||
|
@ -1436,8 +1478,13 @@ class ManyToManyRel(ForeignObjectRel):
|
||||||
|
|
||||||
|
|
||||||
class ForeignObject(RelatedField):
|
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
|
requires_unique_target = True
|
||||||
generate_reverse_relation = True
|
|
||||||
related_accessor_class = ForeignRelatedObjectsDescriptor
|
related_accessor_class = ForeignRelatedObjectsDescriptor
|
||||||
|
|
||||||
def __init__(self, to, from_fields, to_fields, swappable=True, **kwargs):
|
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]
|
from_field_name = self.from_fields[index]
|
||||||
to_field_name = self.to_fields[index]
|
to_field_name = self.to_fields[index]
|
||||||
from_field = (self if from_field_name == 'self'
|
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
|
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))
|
related_fields.append((from_field, to_field))
|
||||||
return related_fields
|
return related_fields
|
||||||
|
|
||||||
|
@ -1731,7 +1778,7 @@ class ForeignObject(RelatedField):
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
# Internal FK's - i.e., those with a related name ending with '+' -
|
# Internal FK's - i.e., those with a related name ending with '+' -
|
||||||
# and swapped models don't get a related descriptor.
|
# 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))
|
setattr(cls, related.get_accessor_name(), self.related_accessor_class(related))
|
||||||
# While 'limit_choices_to' might be a callable, simply pass
|
# While 'limit_choices_to' might be a callable, simply pass
|
||||||
# it along for later - this is too early because it's still
|
# it along for later - this is too early because it's still
|
||||||
|
@ -1741,6 +1788,12 @@ class ForeignObject(RelatedField):
|
||||||
|
|
||||||
|
|
||||||
class ForeignKey(ForeignObject):
|
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
|
empty_strings_allowed = False
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('%(model)s instance with %(field)s %(value)r does not exist.')
|
'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),
|
always returns the object pointed to (since there will only ever be one),
|
||||||
rather than returning a list.
|
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
|
related_accessor_class = SingleRelatedObjectDescriptor
|
||||||
description = _("One-to-one relationship")
|
description = _("One-to-one relationship")
|
||||||
|
|
||||||
|
@ -2036,6 +2095,12 @@ def create_many_to_many_intermediary_model(field, klass):
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyField(RelatedField):
|
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")
|
description = _("Many-to-many relationship")
|
||||||
|
|
||||||
def __init__(self, to, db_constraint=True, swappable=True, **kwargs):
|
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
|
# Class names must be ASCII in Python 2.x, so we forcibly coerce it
|
||||||
# here to break early if there's a problem.
|
# here to break early if there's a problem.
|
||||||
to = str(to)
|
to = str(to)
|
||||||
|
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
kwargs['rel'] = ManyToManyRel(
|
kwargs['rel'] = ManyToManyRel(
|
||||||
self, to,
|
self, to,
|
||||||
|
@ -2357,8 +2421,8 @@ class ManyToManyField(RelatedField):
|
||||||
"""
|
"""
|
||||||
pathinfos = []
|
pathinfos = []
|
||||||
int_model = self.rel.through
|
int_model = self.rel.through
|
||||||
linkfield1 = int_model._meta.get_field_by_name(self.m2m_field_name())[0]
|
linkfield1 = int_model._meta.get_field(self.m2m_field_name())
|
||||||
linkfield2 = int_model._meta.get_field_by_name(self.m2m_reverse_field_name())[0]
|
linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name())
|
||||||
if direct:
|
if direct:
|
||||||
join1infos = linkfield1.get_reverse_path_info()
|
join1infos = linkfield1.get_reverse_path_info()
|
||||||
join2infos = linkfield2.get_path_info()
|
join2infos = linkfield2.get_path_info()
|
||||||
|
@ -2398,8 +2462,8 @@ class ManyToManyField(RelatedField):
|
||||||
else:
|
else:
|
||||||
link_field_name = None
|
link_field_name = None
|
||||||
for f in self.rel.through._meta.fields:
|
for f in self.rel.through._meta.fields:
|
||||||
if hasattr(f, 'rel') and f.rel and f.rel.to == related.model and \
|
if (f.is_relation and f.rel.to == related.related_model and
|
||||||
(link_field_name is None or link_field_name == f.name):
|
(link_field_name is None or link_field_name == f.name)):
|
||||||
setattr(self, cache_attr, getattr(f, attr))
|
setattr(self, cache_attr, getattr(f, attr))
|
||||||
return getattr(self, cache_attr)
|
return getattr(self, cache_attr)
|
||||||
|
|
||||||
|
@ -2414,8 +2478,9 @@ class ManyToManyField(RelatedField):
|
||||||
else:
|
else:
|
||||||
link_field_name = None
|
link_field_name = None
|
||||||
for f in self.rel.through._meta.fields:
|
for f in self.rel.through._meta.fields:
|
||||||
if hasattr(f, 'rel') and f.rel and f.rel.to == related.parent_model:
|
# NOTE f.rel.to != f.related_model
|
||||||
if link_field_name is None and related.model == related.parent_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,
|
# If this is an m2m-intermediate to self,
|
||||||
# the first foreign key you find will be
|
# the first foreign key you find will be
|
||||||
# the source column. Keep searching for
|
# the source column. Keep searching for
|
||||||
|
@ -2479,7 +2544,7 @@ class ManyToManyField(RelatedField):
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
# Internal M2Ms (i.e., those with a related name ending with '+')
|
# Internal M2Ms (i.e., those with a related name ending with '+')
|
||||||
# and swapped models don't get a related descriptor.
|
# 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))
|
setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
|
||||||
|
|
||||||
# Set up the accessors for the column names on the m2m table
|
# Set up the accessors for the column names on the m2m table
|
||||||
|
|
|
@ -2,7 +2,6 @@ import copy
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
|
||||||
from django.db import router
|
from django.db import router
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -23,15 +22,12 @@ def ensure_default_manager(cls):
|
||||||
setattr(cls, 'objects', SwappedManagerDescriptor(cls))
|
setattr(cls, 'objects', SwappedManagerDescriptor(cls))
|
||||||
return
|
return
|
||||||
if not getattr(cls, '_default_manager', None):
|
if not getattr(cls, '_default_manager', None):
|
||||||
# Create the default manager, if needed.
|
if any(f.name == 'objects' for f in cls._meta.fields):
|
||||||
try:
|
|
||||||
cls._meta.get_field('objects')
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Model %s must specify a custom Manager, because it has a "
|
"Model %s must specify a custom Manager, because it has a "
|
||||||
"field named 'objects'" % cls.__name__
|
"field named 'objects'" % cls.__name__
|
||||||
)
|
)
|
||||||
except FieldDoesNotExist:
|
# Create the default manager, if needed.
|
||||||
pass
|
|
||||||
cls.add_to_class('objects', Manager())
|
cls.add_to_class('objects', Manager())
|
||||||
cls._base_manager = cls.objects
|
cls._base_manager = cls.objects
|
||||||
elif not getattr(cls, '_base_manager', None):
|
elif not getattr(cls, '_base_manager', None):
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from bisect import bisect
|
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.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
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 import AutoField
|
||||||
from django.db.models.fields.proxy import OrderWrt
|
from django.db.models.fields.proxy import OrderWrt
|
||||||
from django.utils import six
|
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.encoding import force_text, smart_text, python_2_unicode_compatible
|
||||||
from django.utils.functional import cached_property
|
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.text import camel_case_to_spaces
|
||||||
from django.utils.translation import activate, deactivate_all, get_language, string_concat
|
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',
|
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
||||||
'unique_together', 'permissions', 'get_latest_by',
|
'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')
|
'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):
|
def normalize_together(option_together):
|
||||||
"""
|
"""
|
||||||
option_together can be either a tuple of tuples, or a single
|
option_together can be either a tuple of tuples, or a single
|
||||||
|
@ -46,9 +75,19 @@ def normalize_together(option_together):
|
||||||
return option_together
|
return option_together
|
||||||
|
|
||||||
|
|
||||||
|
def make_immutable_fields_list(name, data):
|
||||||
|
return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Options(object):
|
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):
|
def __init__(self, meta, app_label=None):
|
||||||
|
self._get_fields_cache = {}
|
||||||
|
self.proxied_children = []
|
||||||
self.local_fields = []
|
self.local_fields = []
|
||||||
self.local_many_to_many = []
|
self.local_many_to_many = []
|
||||||
self.virtual_fields = []
|
self.virtual_fields = []
|
||||||
|
@ -103,6 +142,31 @@ class Options(object):
|
||||||
|
|
||||||
self.default_related_name = None
|
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
|
@property
|
||||||
def app_config(self):
|
def app_config(self):
|
||||||
# Don't go through get_app_config to avoid triggering imports.
|
# Don't go through get_app_config to avoid triggering imports.
|
||||||
|
@ -183,7 +247,17 @@ class Options(object):
|
||||||
|
|
||||||
def _prepare(self, model):
|
def _prepare(self, model):
|
||||||
if self.order_with_respect_to:
|
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',)
|
self.ordering = ('_order',)
|
||||||
if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields):
|
if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields):
|
||||||
model.add_to_class('_order', OrderWrt())
|
model.add_to_class('_order', OrderWrt())
|
||||||
|
@ -208,56 +282,41 @@ class Options(object):
|
||||||
auto_created=True)
|
auto_created=True)
|
||||||
model.add_to_class('id', auto)
|
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
|
# Insert the given field in the order in which it was created, using
|
||||||
# the "creation_counter" attribute of the field.
|
# the "creation_counter" attribute of the field.
|
||||||
# Move many-to-many related fields from self.fields into
|
# Move many-to-many related fields from self.fields into
|
||||||
# self.many_to_many.
|
# 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)
|
self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
|
||||||
if hasattr(self, '_m2m_cache'):
|
|
||||||
del self._m2m_cache
|
|
||||||
else:
|
else:
|
||||||
self.local_fields.insert(bisect(self.local_fields, field), field)
|
self.local_fields.insert(bisect(self.local_fields, field), field)
|
||||||
self.setup_pk(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'):
|
# If the field being added is a relation to another known field,
|
||||||
del self._name_map
|
# expire the cache on this field and the forward cache on the field
|
||||||
|
# being referenced, because there will be new relationships in the
|
||||||
def add_virtual_field(self, field):
|
# cache. Otherwise, expire the cache of references *to* this field.
|
||||||
self.virtual_fields.append(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):
|
def setup_pk(self, field):
|
||||||
if not self.pk and field.primary_key:
|
if not self.pk and field.primary_key:
|
||||||
self.pk = field
|
self.pk = field
|
||||||
field.serialize = False
|
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):
|
def setup_proxy(self, target):
|
||||||
"""
|
"""
|
||||||
Does the internal setup so that the current model is a proxy for
|
Does the internal setup so that the current model is a proxy for
|
||||||
|
@ -273,6 +332,7 @@ class Options(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s.%s" % (smart_text(self.app_label), smart_text(self.model_name))
|
return "%s.%s" % (smart_text(self.app_label), smart_text(self.model_name))
|
||||||
|
|
||||||
|
@property
|
||||||
def verbose_name_raw(self):
|
def verbose_name_raw(self):
|
||||||
"""
|
"""
|
||||||
There are a few places where the untranslated verbose name is needed
|
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)
|
raw = force_text(self.verbose_name)
|
||||||
activate(lang)
|
activate(lang)
|
||||||
return raw
|
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
|
Has this model been swapped out for another? If so, return the model
|
||||||
name of the replacement; otherwise, return None.
|
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):
|
if '%s.%s' % (swapped_label, swapped_object.lower()) not in (None, model_label):
|
||||||
return swapped_for
|
return swapped_for
|
||||||
return None
|
return None
|
||||||
swapped = property(_swapped)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def fields(self):
|
def fields(self):
|
||||||
"""
|
"""
|
||||||
The getter for self.fields. This returns the list of field objects
|
Returns a list of all forward fields on the model and its parents,
|
||||||
available to this model (including through parent models).
|
excluding ManyToManyFields.
|
||||||
|
|
||||||
Callers are not permitted to modify this list, since it's a reference
|
Private API intended only to be used by Django itself; get_fields()
|
||||||
to this instance (not a copy).
|
combined with filtering of field properties is the public API for
|
||||||
|
obtaining this field list.
|
||||||
"""
|
"""
|
||||||
try:
|
# For legacy reasons, the fields property should only contain forward
|
||||||
self._field_name_cache
|
# fields that are not virtual or with a m2m cardinality. Therefore we
|
||||||
except AttributeError:
|
# pass these three filters as filters to the generator.
|
||||||
self._fill_fields_cache()
|
# The third lambda is a longwinded way of checking f.related_model - we don't
|
||||||
return self._field_name_cache
|
# 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
|
@cached_property
|
||||||
def concrete_fields(self):
|
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
|
@cached_property
|
||||||
def local_concrete_fields(self):
|
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):
|
def get_fields_with_model(self):
|
||||||
"""
|
return [self._map_model(f) for f in self.get_fields()]
|
||||||
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
|
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_fields()")
|
||||||
def get_concrete_fields_with_model(self):
|
def get_concrete_fields_with_model(self):
|
||||||
return [(field, model) for field, model in self.get_fields_with_model() if
|
return [self._map_model(f) for f in self.concrete_fields]
|
||||||
field.column is not None]
|
|
||||||
|
|
||||||
def _fill_fields_cache(self):
|
@cached_property
|
||||||
cache = []
|
def many_to_many(self):
|
||||||
for parent in self.parents:
|
"""
|
||||||
for field, model in parent._meta.get_fields_with_model():
|
Returns a list of all many to many fields on the model and its parents.
|
||||||
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]
|
|
||||||
|
|
||||||
def _many_to_many(self):
|
Private API intended only to be used by Django itself; get_fields()
|
||||||
try:
|
combined with filtering of field properties is the public API for
|
||||||
self._m2m_cache
|
obtaining this list.
|
||||||
except AttributeError:
|
"""
|
||||||
self._fill_m2m_cache()
|
return make_immutable_fields_list(
|
||||||
return list(self._m2m_cache)
|
"many_to_many",
|
||||||
many_to_many = property(_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):
|
def get_m2m_with_model(self):
|
||||||
"""
|
return [self._map_model(f) for f in self.many_to_many]
|
||||||
The many-to-many version of get_fields_with_model().
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self._m2m_cache
|
|
||||||
except AttributeError:
|
|
||||||
self._fill_m2m_cache()
|
|
||||||
return list(six.iteritems(self._m2m_cache))
|
|
||||||
|
|
||||||
def _fill_m2m_cache(self):
|
@cached_property
|
||||||
cache = OrderedDict()
|
def _forward_fields_map(self):
|
||||||
for parent in self.parents:
|
res = {}
|
||||||
for field, model in parent._meta.get_m2m_with_model():
|
# call get_fields() with export_ordered_set=True in order to have a
|
||||||
if model:
|
# field_instance -> names map
|
||||||
cache[field] = model
|
fields = self._get_fields(reverse=False)
|
||||||
else:
|
for field in fields:
|
||||||
cache[field] = parent
|
res[field.name] = field
|
||||||
for field in self.local_many_to_many:
|
# Due to the way Django's internals work, get_field() should also
|
||||||
cache[field] = None
|
# be able to fetch a field by attname. In the case of a concrete
|
||||||
self._m2m_cache = cache
|
# field with relation, includes the *_id name too
|
||||||
|
|
||||||
def get_field(self, name, many_to_many=True):
|
|
||||||
"""
|
|
||||||
Returns the requested field by name. Raises FieldDoesNotExist on error.
|
|
||||||
"""
|
|
||||||
to_search = (self.fields + self.many_to_many) if many_to_many else self.fields
|
|
||||||
for f in to_search:
|
|
||||||
if f.name == name:
|
|
||||||
return f
|
|
||||||
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, name))
|
|
||||||
|
|
||||||
def get_field_by_name(self, name):
|
|
||||||
"""
|
|
||||||
Returns the (field_object, model, direct, m2m), where field_object is
|
|
||||||
the Field instance for the given name, model is the model containing
|
|
||||||
this field (None for local fields), direct is True if the field exists
|
|
||||||
on this model, and m2m is True for many-to-many relations. When
|
|
||||||
'direct' is False, 'field_object' is the corresponding ForeignObjectRel
|
|
||||||
for this field (since the field doesn't have an instance associated
|
|
||||||
with it).
|
|
||||||
|
|
||||||
Uses a cache internally, so after the first access, this is very fast.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
try:
|
try:
|
||||||
return self._name_map[name]
|
res[field.attname] = field
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
cache = self.init_name_map()
|
pass
|
||||||
return cache[name]
|
return res
|
||||||
except KeyError:
|
|
||||||
raise FieldDoesNotExist('%s has no field named %r'
|
|
||||||
% (self.object_name, name))
|
|
||||||
|
|
||||||
def get_all_field_names(self):
|
@cached_property
|
||||||
|
def fields_map(self):
|
||||||
|
res = {}
|
||||||
|
fields = self._get_fields(forward=False, include_hidden=True)
|
||||||
|
for field in fields:
|
||||||
|
res[field.name] = field
|
||||||
|
# Due to the way Django's internals work, get_field() should also
|
||||||
|
# be able to fetch a field by attname. In the case of a concrete
|
||||||
|
# field with relation, includes the *_id name too
|
||||||
|
try:
|
||||||
|
res[field.attname] = field
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_field(self, field_name, many_to_many=None):
|
||||||
"""
|
"""
|
||||||
Returns a list of all field names that are possible for this model
|
Returns a field instance given a field name. The field can be either a
|
||||||
(including reverse relation names). This is used for pretty printing
|
forward or reverse field, unless many_to_many is specified; if it is,
|
||||||
debugging output (a list of choices), so any internal-only field names
|
only forward fields will be returned.
|
||||||
are not included.
|
|
||||||
|
The many_to_many argument exists for backwards compatibility reasons;
|
||||||
|
it has been deprecated and will be removed in Django 2.0.
|
||||||
"""
|
"""
|
||||||
|
m2m_in_kwargs = many_to_many is not None
|
||||||
|
if m2m_in_kwargs:
|
||||||
|
# Always throw a warning if many_to_many is used regardless of
|
||||||
|
# whether it alters the return type or not.
|
||||||
|
warnings.warn(
|
||||||
|
"The 'many_to_many' argument on get_field() is deprecated; "
|
||||||
|
"use a filter on field.many_to_many instead.",
|
||||||
|
RemovedInDjango20Warning
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cache = self._name_map
|
# In order to avoid premature loading of the relation tree
|
||||||
except AttributeError:
|
# (expensive) we prefer checking if the field is a forward field.
|
||||||
cache = self.init_name_map()
|
field = self._forward_fields_map[field_name]
|
||||||
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('+')]
|
|
||||||
|
|
||||||
def init_name_map(self):
|
if many_to_many is False and field.many_to_many:
|
||||||
"""
|
raise FieldDoesNotExist(
|
||||||
Initialises the field name -> field object mapping.
|
'%s has no field named %r' % (self.object_name, field_name)
|
||||||
"""
|
)
|
||||||
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
|
|
||||||
|
|
||||||
|
return field
|
||||||
|
except KeyError:
|
||||||
|
# If the app registry is not ready, reverse fields are
|
||||||
|
# unavailable, therefore we throw a FieldDoesNotExist exception.
|
||||||
|
if not self.apps.ready:
|
||||||
|
raise FieldDoesNotExist(
|
||||||
|
"%s has no field named %r. The app cache isn't "
|
||||||
|
"ready yet, so if this is a forward field, it won't "
|
||||||
|
"be available yet." % (self.object_name, field_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if m2m_in_kwargs:
|
||||||
|
# Previous API does not allow searching reverse fields.
|
||||||
|
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
||||||
|
|
||||||
|
# Retrieve field instance by name from cached or just-computed
|
||||||
|
# field map.
|
||||||
|
return self.fields_map[field_name]
|
||||||
|
except KeyError:
|
||||||
|
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_field()")
|
||||||
|
def get_field_by_name(self, name):
|
||||||
|
return self._map_model_details(self.get_field(name))
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_fields()")
|
||||||
|
def get_all_field_names(self):
|
||||||
|
names = set()
|
||||||
|
fields = self.get_fields()
|
||||||
|
for field in fields:
|
||||||
|
# For backwards compatibility GenericForeignKey should not be
|
||||||
|
# included in the results.
|
||||||
|
if field.is_relation and field.one_to_many and field.related_model is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
names.add(field.name)
|
||||||
|
if hasattr(field, 'attname'):
|
||||||
|
names.add(field.attname)
|
||||||
|
return list(names)
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_fields()")
|
||||||
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
||||||
include_proxy_eq=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_parents = local_only is False
|
||||||
include_hidden=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):
|
include_proxy_eq=False):
|
||||||
"""
|
return [
|
||||||
Returns a list of (related-object, model) pairs. Similar to
|
self._map_model(f) for f in self.get_all_related_objects(
|
||||||
get_fields_with_model().
|
local_only=local_only,
|
||||||
"""
|
include_hidden=include_hidden,
|
||||||
try:
|
include_proxy_eq=include_proxy_eq,
|
||||||
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
|
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_fields()")
|
||||||
def get_all_related_many_to_many_objects(self, local_only=False):
|
def get_all_related_many_to_many_objects(self, local_only=False):
|
||||||
try:
|
fields = self._get_fields(
|
||||||
cache = self._related_many_to_many_cache
|
forward=False, reverse=True,
|
||||||
except AttributeError:
|
include_parents=local_only is not True, include_hidden=True
|
||||||
cache = self._fill_related_many_to_many_cache()
|
)
|
||||||
if local_only:
|
return [obj for obj in fields if isinstance(obj.field, ManyToManyField)]
|
||||||
return [k for k, v in cache.items() if not v]
|
|
||||||
return list(cache)
|
|
||||||
|
|
||||||
|
@raise_deprecation(suggested_alternative="get_fields()")
|
||||||
def get_all_related_m2m_objects_with_model(self):
|
def get_all_related_m2m_objects_with_model(self):
|
||||||
"""
|
fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
|
||||||
Returns a list of (related-m2m-object, model) pairs. Similar to
|
return [self._map_model(obj) for obj in fields if isinstance(obj.field, ManyToManyField)]
|
||||||
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
|
|
||||||
|
|
||||||
def get_base_chain(self, model):
|
def get_base_chain(self, model):
|
||||||
"""
|
"""
|
||||||
|
@ -605,3 +665,173 @@ class Options(object):
|
||||||
# of the chain to the ancestor is that parent
|
# of the chain to the ancestor is that parent
|
||||||
# links
|
# links
|
||||||
return self.parents[parent] or parent_link
|
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,
|
# If only/defer clauses have been specified,
|
||||||
# build the list of fields that are to be loaded.
|
# build the list of fields that are to be loaded.
|
||||||
if only_load:
|
if only_load:
|
||||||
for field, model in self.model._meta.get_concrete_fields_with_model():
|
for field in self.model._meta.concrete_fields:
|
||||||
if model is None:
|
model = field.model._meta.model
|
||||||
model = self.model
|
|
||||||
try:
|
try:
|
||||||
if field.name in only_load[model]:
|
if field.name in only_load[model]:
|
||||||
# Add a field that has been explicitly included
|
# Add a field that has been explicitly included
|
||||||
|
@ -818,7 +817,7 @@ class QuerySet(object):
|
||||||
obj = self._clone()
|
obj = self._clone()
|
||||||
names = getattr(self, '_fields', None)
|
names = getattr(self, '_fields', None)
|
||||||
if names is 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
|
# Add the annotations to the query
|
||||||
for alias, annotation in annotations.items():
|
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()
|
skip = set()
|
||||||
init_list = []
|
init_list = []
|
||||||
# Build the list of fields that *haven't* been requested
|
# 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):
|
if from_parent and model and issubclass(from_parent, model):
|
||||||
# Avoid loading fields already loaded for parent model for
|
# Avoid loading fields already loaded for parent model for
|
||||||
# child models.
|
# child models.
|
||||||
|
@ -1381,18 +1381,19 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
|
||||||
|
|
||||||
reverse_related_fields = []
|
reverse_related_fields = []
|
||||||
if restricted:
|
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,
|
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()]
|
next = requested[o.field.related_query_name()]
|
||||||
parent = klass if issubclass(o.model, klass) else None
|
parent = klass if issubclass(o.related_model, klass) else None
|
||||||
klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth + 1,
|
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)
|
requested=next, only_load=only_load, from_parent=parent)
|
||||||
reverse_related_fields.append((o.field, klass_info))
|
reverse_related_fields.append((o.field, klass_info))
|
||||||
if field_names:
|
if field_names:
|
||||||
pk_idx = field_names.index(klass._meta.pk.attname)
|
pk_idx = field_names.index(klass._meta.pk.attname)
|
||||||
else:
|
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
|
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:
|
for f, klass_info in reverse_related_fields:
|
||||||
# Transfer data from this object to childs.
|
# Transfer data from this object to childs.
|
||||||
parent_data = []
|
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):
|
if rel_model is not None and isinstance(obj, rel_model):
|
||||||
parent_data.append((rel_field, getattr(obj, rel_field.attname)))
|
parent_data.append((rel_field, getattr(obj, rel_field.attname)))
|
||||||
# Recursively retrieve the data for the related object
|
# 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
|
# self.field_name is the attname of the field, but only() takes the
|
||||||
# actual name, so we need to translate it here.
|
# actual name, so we need to translate it here.
|
||||||
try:
|
try:
|
||||||
f = opts.get_field_by_name(self.field_name)[0]
|
f = opts.get_field(self.field_name)
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
f = [f for f in opts.fields if f.attname == self.field_name][0]
|
f = [f for f in opts.fields if f.attname == self.field_name][0]
|
||||||
name = f.name
|
name = f.name
|
||||||
|
@ -136,7 +136,7 @@ class DeferredAttribute(object):
|
||||||
field is a primary key field.
|
field is a primary key field.
|
||||||
"""
|
"""
|
||||||
opts = instance._meta
|
opts = instance._meta
|
||||||
f = opts.get_field_by_name(name)[0]
|
f = opts.get_field(name)
|
||||||
link_field = opts.get_ancestor_link(f.model)
|
link_field = opts.get_ancestor_link(f.model)
|
||||||
if f.primary_key and f != link_field:
|
if f.primary_key and f != link_field:
|
||||||
return getattr(instance, link_field.attname)
|
return getattr(instance, link_field.attname)
|
||||||
|
|
|
@ -298,7 +298,12 @@ class SQLCompiler(object):
|
||||||
# be used by local fields.
|
# be used by local fields.
|
||||||
seen_models = {None: start_alias}
|
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):
|
if from_parent and model is not None and issubclass(from_parent, model):
|
||||||
# Avoid loading data for already loaded parents.
|
# Avoid loading data for already loaded parents.
|
||||||
continue
|
continue
|
||||||
|
@ -601,10 +606,10 @@ class SQLCompiler(object):
|
||||||
connections to the root model).
|
connections to the root model).
|
||||||
"""
|
"""
|
||||||
def _get_field_choices():
|
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 = (
|
reverse_choices = (
|
||||||
f.field.related_query_name()
|
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)
|
return chain(direct_choices, reverse_choices)
|
||||||
|
|
||||||
|
@ -628,12 +633,13 @@ class SQLCompiler(object):
|
||||||
else:
|
else:
|
||||||
restricted = False
|
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)
|
fields_found.add(f.name)
|
||||||
|
|
||||||
if restricted:
|
if restricted:
|
||||||
next = requested.get(f.name, {})
|
next = requested.get(f.name, {})
|
||||||
if not f.rel:
|
if not f.is_relation:
|
||||||
# If a non-related field is used like a relation,
|
# If a non-related field is used like a relation,
|
||||||
# or if a single non-relational field is given.
|
# or if a single non-relational field is given.
|
||||||
if next or (cur_depth == 1 and f.name in requested):
|
if next or (cur_depth == 1 and f.name in requested):
|
||||||
|
@ -647,10 +653,6 @@ class SQLCompiler(object):
|
||||||
else:
|
else:
|
||||||
next = False
|
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,
|
if not select_related_descend(f, restricted, requested,
|
||||||
only_load.get(field_model)):
|
only_load.get(field_model)):
|
||||||
continue
|
continue
|
||||||
|
@ -666,9 +668,9 @@ class SQLCompiler(object):
|
||||||
|
|
||||||
if restricted:
|
if restricted:
|
||||||
related_fields = [
|
related_fields = [
|
||||||
(o.field, o.model)
|
(o.field, o.related_model)
|
||||||
for o in opts.get_all_related_objects()
|
for o in opts.related_objects
|
||||||
if o.field.unique
|
if o.field.unique and not o.many_to_many
|
||||||
]
|
]
|
||||||
for f, model in related_fields:
|
for f, model in related_fields:
|
||||||
if not select_related_descend(f, restricted, requested,
|
if not select_related_descend(f, restricted, requested,
|
||||||
|
@ -760,7 +762,7 @@ class SQLCompiler(object):
|
||||||
if self.query.select:
|
if self.query.select:
|
||||||
fields = [f.field for f in self.query.select]
|
fields = [f.field for f in self.query.select]
|
||||||
elif self.query.default_cols:
|
elif self.query.default_cols:
|
||||||
fields = self.query.get_meta().concrete_fields
|
fields = list(self.query.get_meta().concrete_fields)
|
||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from itertools import count, product
|
||||||
|
|
||||||
from collections import Mapping, OrderedDict
|
from collections import Mapping, OrderedDict
|
||||||
import copy
|
import copy
|
||||||
|
from itertools import chain
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import FieldDoesNotExist, FieldError
|
from django.core.exceptions import FieldDoesNotExist, FieldError
|
||||||
|
@ -33,6 +34,13 @@ from django.utils.tree import Node
|
||||||
__all__ = ['Query', 'RawQuery']
|
__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):
|
class RawQuery(object):
|
||||||
"""
|
"""
|
||||||
A single raw SQL query
|
A single raw SQL query
|
||||||
|
@ -593,9 +601,9 @@ class Query(object):
|
||||||
opts = orig_opts
|
opts = orig_opts
|
||||||
for name in parts[:-1]:
|
for name in parts[:-1]:
|
||||||
old_model = cur_model
|
old_model = cur_model
|
||||||
source = opts.get_field_by_name(name)[0]
|
source = opts.get_field(name)
|
||||||
if is_reverse_o2o(source):
|
if is_reverse_o2o(source):
|
||||||
cur_model = source.model
|
cur_model = source.related_model
|
||||||
else:
|
else:
|
||||||
cur_model = source.rel.to
|
cur_model = source.rel.to
|
||||||
opts = cur_model._meta
|
opts = cur_model._meta
|
||||||
|
@ -605,8 +613,11 @@ class Query(object):
|
||||||
if not is_reverse_o2o(source):
|
if not is_reverse_o2o(source):
|
||||||
must_include[old_model].add(source)
|
must_include[old_model].add(source)
|
||||||
add_to_dict(must_include, cur_model, opts.pk)
|
add_to_dict(must_include, cur_model, opts.pk)
|
||||||
field, model, _, _ = opts.get_field_by_name(parts[-1])
|
field = opts.get_field(parts[-1])
|
||||||
if model is None:
|
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
|
model = cur_model
|
||||||
if not is_reverse_o2o(field):
|
if not is_reverse_o2o(field):
|
||||||
add_to_dict(seen, model, field)
|
add_to_dict(seen, model, field)
|
||||||
|
@ -618,10 +629,11 @@ class Query(object):
|
||||||
# models.
|
# models.
|
||||||
workset = {}
|
workset = {}
|
||||||
for model, values in six.iteritems(seen):
|
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:
|
if field in values:
|
||||||
continue
|
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):
|
for model, values in six.iteritems(must_include):
|
||||||
# If we haven't included a model in workset, we don't add the
|
# If we haven't included a model in workset, we don't add the
|
||||||
# corresponding must_include fields for that model, since an
|
# corresponding must_include fields for that model, since an
|
||||||
|
@ -934,8 +946,9 @@ class Query(object):
|
||||||
root_alias = self.tables[0]
|
root_alias = self.tables[0]
|
||||||
seen = {None: root_alias}
|
seen = {None: root_alias}
|
||||||
|
|
||||||
for field, model in opts.get_fields_with_model():
|
for field in opts.fields:
|
||||||
if model not in seen:
|
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.join_parent_model(opts, model, root_alias, seen)
|
||||||
self.included_inherited_models = seen
|
self.included_inherited_models = seen
|
||||||
|
|
||||||
|
@ -1368,7 +1381,19 @@ class Query(object):
|
||||||
if name == 'pk':
|
if name == 'pk':
|
||||||
name = opts.pk.name
|
name = opts.pk.name
|
||||||
try:
|
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:
|
except FieldDoesNotExist:
|
||||||
# is it an annotation?
|
# is it an annotation?
|
||||||
if self._annotations and name in self._annotations:
|
if self._annotations and name in self._annotations:
|
||||||
|
@ -1382,14 +1407,15 @@ class Query(object):
|
||||||
# one step.
|
# one step.
|
||||||
pos -= 1
|
pos -= 1
|
||||||
if pos == -1 or fail_on_missing:
|
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. "
|
raise FieldError("Cannot resolve keyword %r into field. "
|
||||||
"Choices are: %s" % (name, ", ".join(available)))
|
"Choices are: %s" % (name, ", ".join(available)))
|
||||||
break
|
break
|
||||||
# Check if we need any joins for concrete inheritance cases (the
|
# Check if we need any joins for concrete inheritance cases (the
|
||||||
# field lives in parent, but we are currently in one of its
|
# field lives in parent, but we are currently in one of its
|
||||||
# children)
|
# children)
|
||||||
if model:
|
if model is not opts.model:
|
||||||
# The field lives on a base class of the current model.
|
# The field lives on a base class of the current model.
|
||||||
# Skip the chain of proxy to the concrete proxied model
|
# Skip the chain of proxy to the concrete proxied model
|
||||||
proxied_model = opts.concrete_model
|
proxied_model = opts.concrete_model
|
||||||
|
@ -1432,7 +1458,7 @@ class Query(object):
|
||||||
return path, final_field, targets, names[pos + 1:]
|
return path, final_field, targets, names[pos + 1:]
|
||||||
|
|
||||||
def raise_field_error(self, opts, name):
|
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. "
|
raise FieldError("Cannot resolve keyword %r into field. "
|
||||||
"Choices are: %s" % (name, ", ".join(available)))
|
"Choices are: %s" % (name, ", ".join(available)))
|
||||||
|
|
||||||
|
@ -1693,7 +1719,7 @@ class Query(object):
|
||||||
# from the model on which the lookup failed.
|
# from the model on which the lookup failed.
|
||||||
raise
|
raise
|
||||||
else:
|
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))
|
+ list(self.annotation_select))
|
||||||
raise FieldError("Cannot resolve keyword %r into field. "
|
raise FieldError("Cannot resolve keyword %r into field. "
|
||||||
"Choices are: %s" % (name, ", ".join(names)))
|
"Choices are: %s" % (name, ", ".join(names)))
|
||||||
|
|
|
@ -120,13 +120,15 @@ class UpdateQuery(Query):
|
||||||
"""
|
"""
|
||||||
values_seq = []
|
values_seq = []
|
||||||
for name, val in six.iteritems(values):
|
for name, val in six.iteritems(values):
|
||||||
field, model, direct, m2m = self.get_meta().get_field_by_name(name)
|
field = self.get_meta().get_field(name)
|
||||||
if not direct or m2m:
|
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(
|
raise FieldError(
|
||||||
'Cannot update model field %r (only non-relations and '
|
'Cannot update model field %r (only non-relations and '
|
||||||
'foreign keys permitted).' % field
|
'foreign keys permitted).' % field
|
||||||
)
|
)
|
||||||
if model:
|
if model is not self.get_meta().model:
|
||||||
self.add_related_update(model, field, val)
|
self.add_related_update(model, field, val)
|
||||||
continue
|
continue
|
||||||
values_seq.append((field, model, val))
|
values_seq.append((field, model, val))
|
||||||
|
|
|
@ -6,6 +6,7 @@ and database field objects.
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from itertools import chain
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import (
|
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
|
# Note that for historical reasons we want to include also
|
||||||
# virtual_fields here. (GenericRelation was previously a fake
|
# virtual_fields here. (GenericRelation was previously a fake
|
||||||
# m2m field).
|
# 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'):
|
if not hasattr(f, 'save_form_data'):
|
||||||
continue
|
continue
|
||||||
if fields and f.name not in fields:
|
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
|
from django.db.models.fields.related import ManyToManyField
|
||||||
opts = instance._meta
|
opts = instance._meta
|
||||||
data = {}
|
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):
|
if not getattr(f, 'editable', False):
|
||||||
continue
|
continue
|
||||||
if fields and f.name not in fields:
|
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
|
from django.db.models.fields import Field as ModelField
|
||||||
sortable_virtual_fields = [f for f in opts.virtual_fields
|
sortable_virtual_fields = [f for f in opts.virtual_fields
|
||||||
if isinstance(f, ModelField)]
|
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):
|
if not getattr(f, 'editable', False):
|
||||||
continue
|
continue
|
||||||
if fields is not None and f.name not in fields:
|
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
|
* :attr:`~django.db.models.Field.db_tablespace`: Only for index creation, if the
|
||||||
backend supports :doc:`tablespaces </topics/db/tablespaces>`. You can usually
|
backend supports :doc:`tablespaces </topics/db/tablespaces>`. You can usually
|
||||||
ignore this option.
|
ignore this option.
|
||||||
* ``auto_created``: ``True`` if the field was automatically created, as for the
|
* :attr:`~django.db.models.Field.auto_created`: ``True`` if the field was
|
||||||
:class:`~django.db.models.OneToOneField` used by model inheritance. For
|
automatically created, as for the :class:`~django.db.models.OneToOneField`
|
||||||
advanced use only.
|
used by model inheritance. For advanced use only.
|
||||||
|
|
||||||
All of the options without an explanation in the above list have the same
|
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
|
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.
|
* ``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 ``error_message`` argument of ``django.forms.RegexField`` will be removed.
|
||||||
|
|
||||||
* The ``unordered_list`` filter will no longer support old style lists.
|
* 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
|
This method must be added to fields prior to 1.7 to migrate its data
|
||||||
using :doc:`/topics/migrations`.
|
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
|
:maxdepth: 1
|
||||||
|
|
||||||
fields
|
fields
|
||||||
|
meta
|
||||||
relations
|
relations
|
||||||
class
|
class
|
||||||
options
|
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
|
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
|
Security enhancements
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -998,6 +1014,26 @@ Miscellaneous
|
||||||
Features deprecated in 1.8
|
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
|
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(models.Object.objects.count(), 2)
|
||||||
self.assertEqual(obj.related_objects.count(), 1)
|
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)
|
intermediary_model.objects.create(from_object_id=obj.id, to_object_id=12345)
|
||||||
self.assertEqual(obj.related_objects.count(), 1)
|
self.assertEqual(obj.related_objects.count(), 1)
|
||||||
self.assertEqual(intermediary_model.objects.count(), 2)
|
self.assertEqual(intermediary_model.objects.count(), 2)
|
||||||
|
|
|
@ -776,7 +776,7 @@ class ModelRefreshTests(TestCase):
|
||||||
|
|
||||||
class TestRelatedObjectDeprecation(TestCase):
|
class TestRelatedObjectDeprecation(TestCase):
|
||||||
def test_field_related_deprecation(self):
|
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:
|
with warnings.catch_warnings(record=True) as warns:
|
||||||
warnings.simplefilter('always')
|
warnings.simplefilter('always')
|
||||||
self.assertIsInstance(field.related, ForeignObjectRel)
|
self.assertIsInstance(field.related, ForeignObjectRel)
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.db import connection, IntegrityError
|
from django.db import connection, IntegrityError
|
||||||
|
@ -76,6 +77,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_loading_and_dumping(self):
|
def test_loading_and_dumping(self):
|
||||||
|
apps.clear_cache()
|
||||||
Site.objects.all().delete()
|
Site.objects.all().delete()
|
||||||
# Load fixture 1. Single JSON file, with two objects.
|
# Load fixture 1. Single JSON file, with two objects.
|
||||||
management.call_command('loaddata', 'fixture1.json', verbosity=0)
|
management.call_command('loaddata', 'fixture1.json', verbosity=0)
|
||||||
|
|
|
@ -403,7 +403,8 @@ class GenericRelationsTests(TestCase):
|
||||||
self.assertEqual(tag.content_object.id, diamond.id)
|
self.assertEqual(tag.content_object.id, diamond.id)
|
||||||
|
|
||||||
def test_query_content_type(self):
|
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='')
|
TaggedItem.objects.get(content_object='')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -260,7 +260,7 @@ class GenericRelationTests(TestCase):
|
||||||
form = GenericRelationForm({'links': None})
|
form = GenericRelationForm({'links': None})
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
form.save()
|
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)
|
self.assertEqual(links.save_form_data_calls, 1)
|
||||||
|
|
||||||
def test_ticket_22998(self):
|
def test_ticket_22998(self):
|
||||||
|
|
|
@ -5,6 +5,12 @@ from .models import Issue, User, UnicodeReferenceModel
|
||||||
|
|
||||||
|
|
||||||
class RelatedObjectTests(TestCase):
|
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):
|
def test_m2m_and_m2o(self):
|
||||||
r = User.objects.create(username="russell")
|
r = User.objects.create(username="russell")
|
||||||
g = User.objects.create(username="gustav")
|
g = User.objects.create(username="gustav")
|
||||||
|
|
|
@ -437,11 +437,11 @@ class ManyToOneTests(TestCase):
|
||||||
expected_message = "Cannot resolve keyword 'notafield' into field. Choices are: %s"
|
expected_message = "Cannot resolve keyword 'notafield' into field. Choices are: %s"
|
||||||
|
|
||||||
self.assertRaisesMessage(FieldError,
|
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,
|
Article.objects.values_list,
|
||||||
'reporter__notafield')
|
'reporter__notafield')
|
||||||
self.assertRaisesMessage(FieldError,
|
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,
|
Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list,
|
||||||
'notafield')
|
'notafield')
|
||||||
|
|
||||||
|
|
|
@ -202,8 +202,8 @@ class StateTests(TestCase):
|
||||||
))
|
))
|
||||||
|
|
||||||
new_apps = project_state.apps
|
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("name").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("hidden").null, False)
|
||||||
|
|
||||||
self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)
|
self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ except ImportError:
|
||||||
Image = None
|
Image = None
|
||||||
|
|
||||||
from django.core.files.storage import FileSystemStorage
|
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 import models
|
||||||
from django.db.models.fields.files import ImageFieldFile, ImageField
|
from django.db.models.fields.files import ImageFieldFile, ImageField
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -295,6 +300,52 @@ if Image:
|
||||||
height_field='headshot_height',
|
height_field='headshot_height',
|
||||||
width_field='headshot_width')
|
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)
|
self.assertEqual(warnings, expected_warnings)
|
||||||
|
|
||||||
def test_related_name_converted_to_text(self):
|
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)
|
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
|
testing is impossible, this is a sanity check against invalid use of
|
||||||
deepcopy. refs #16759.
|
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)
|
note_deepcopy = getattr(opts_class, "__deepcopy__", None)
|
||||||
opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned")
|
opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -79,7 +79,7 @@ class PickleabilityTestCase(TestCase):
|
||||||
m1 = M2MModel.objects.create()
|
m1 = M2MModel.objects.create()
|
||||||
g1 = Group.objects.create(name='foof')
|
g1 = Group.objects.create(name='foof')
|
||||||
m1.groups.add(g1)
|
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()
|
original = m2m_through.objects.get()
|
||||||
dumped = pickle.dumps(original)
|
dumped = pickle.dumps(original)
|
||||||
reloaded = pickle.loads(dumped)
|
reloaded = pickle.loads(dumped)
|
||||||
|
|
|
@ -138,7 +138,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Book,
|
Book,
|
||||||
Book._meta.get_field_by_name("author")[0],
|
Book._meta.get_field("author"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -393,7 +393,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
Author._meta.get_field_by_name("name")[0],
|
Author._meta.get_field("name"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -424,7 +424,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Note,
|
Note,
|
||||||
Note._meta.get_field_by_name("info")[0],
|
Note._meta.get_field("info"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -451,7 +451,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
Author._meta.get_field_by_name("height")[0],
|
Author._meta.get_field("height"),
|
||||||
new_field
|
new_field
|
||||||
)
|
)
|
||||||
# Ensure the field is right afterwards
|
# Ensure the field is right afterwards
|
||||||
|
@ -479,7 +479,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
AuthorWithDefaultHeight,
|
AuthorWithDefaultHeight,
|
||||||
AuthorWithDefaultHeight._meta.get_field_by_name("height")[0],
|
AuthorWithDefaultHeight._meta.get_field("height"),
|
||||||
new_field,
|
new_field,
|
||||||
)
|
)
|
||||||
# Ensure the field is right afterwards
|
# Ensure the field is right afterwards
|
||||||
|
@ -512,7 +512,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Book,
|
Book,
|
||||||
Book._meta.get_field_by_name("author")[0],
|
Book._meta.get_field("author"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -542,7 +542,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
Author._meta.get_field_by_name("id")[0],
|
Author._meta.get_field("id"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -568,7 +568,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
Author._meta.get_field_by_name("name")[0],
|
Author._meta.get_field("name"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -587,7 +587,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
editor.create_model(TagM2MTest)
|
editor.create_model(TagM2MTest)
|
||||||
editor.create_model(BookWithM2M)
|
editor.create_model(BookWithM2M)
|
||||||
# Ensure there is now an m2m table there
|
# 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")
|
self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
|
||||||
|
|
||||||
def test_m2m_create_through(self):
|
def test_m2m_create_through(self):
|
||||||
|
@ -661,7 +661,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
self.assertEqual(len(self.column_classes(AuthorTag)), 3)
|
self.assertEqual(len(self.column_classes(AuthorTag)), 3)
|
||||||
# "Alter" the field's blankness. This should not actually do anything.
|
# "Alter" the field's blankness. This should not actually do anything.
|
||||||
with connection.schema_editor() as editor:
|
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 = ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag")
|
||||||
new_field.contribute_to_class(AuthorWithM2MThrough, "tags")
|
new_field.contribute_to_class(AuthorWithM2MThrough, "tags")
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
|
@ -683,7 +683,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
editor.create_model(TagM2MTest)
|
editor.create_model(TagM2MTest)
|
||||||
editor.create_model(UniqueTest)
|
editor.create_model(UniqueTest)
|
||||||
# Ensure the M2M exists and points to TagM2MTest
|
# 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:
|
if connection.features.supports_foreign_keys:
|
||||||
for name, details in constraints.items():
|
for name, details in constraints.items():
|
||||||
if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
|
if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
|
||||||
|
@ -698,11 +698,11 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
BookWithM2M._meta.get_field_by_name("tags")[0],
|
BookWithM2M._meta.get_field("tags"),
|
||||||
new_field,
|
new_field,
|
||||||
)
|
)
|
||||||
# Ensure old M2M is gone
|
# 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
|
# Ensure the new M2M exists and points to UniqueTest
|
||||||
constraints = self.get_constraints(new_field.rel.through._meta.db_table)
|
constraints = self.get_constraints(new_field.rel.through._meta.db_table)
|
||||||
if connection.features.supports_foreign_keys:
|
if connection.features.supports_foreign_keys:
|
||||||
|
@ -715,10 +715,10 @@ class SchemaTests(TransactionTestCase):
|
||||||
finally:
|
finally:
|
||||||
# Cleanup through table separately
|
# Cleanup through table separately
|
||||||
with connection.schema_editor() as editor:
|
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
|
# Cleanup model states
|
||||||
BookWithM2M._meta.local_many_to_many.remove(new_field)
|
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")
|
@unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints")
|
||||||
def test_check_constraints(self):
|
def test_check_constraints(self):
|
||||||
|
@ -741,7 +741,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
Author._meta.get_field_by_name("height")[0],
|
Author._meta.get_field("height"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -754,7 +754,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Author,
|
Author,
|
||||||
new_field,
|
new_field,
|
||||||
Author._meta.get_field_by_name("height")[0],
|
Author._meta.get_field("height"),
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
constraints = self.get_constraints(Author._meta.db_table)
|
constraints = self.get_constraints(Author._meta.db_table)
|
||||||
|
@ -781,7 +781,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Tag,
|
Tag,
|
||||||
Tag._meta.get_field_by_name("slug")[0],
|
Tag._meta.get_field("slug"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -809,8 +809,8 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Tag,
|
Tag,
|
||||||
Tag._meta.get_field_by_name("slug")[0],
|
Tag._meta.get_field("slug"),
|
||||||
TagUniqueRename._meta.get_field_by_name("slug2")[0],
|
TagUniqueRename._meta.get_field("slug2"),
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
# Ensure the field is still unique
|
# Ensure the field is still unique
|
||||||
|
@ -976,7 +976,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Book,
|
Book,
|
||||||
Book._meta.get_field_by_name("title")[0],
|
Book._meta.get_field("title"),
|
||||||
new_field,
|
new_field,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -990,7 +990,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
Book,
|
Book,
|
||||||
new_field,
|
new_field,
|
||||||
Book._meta.get_field_by_name("title")[0],
|
Book._meta.get_field("title"),
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
# Ensure the table is there and has the index again
|
# Ensure the table is there and has the index again
|
||||||
|
@ -1002,7 +1002,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.add_field(
|
editor.add_field(
|
||||||
Book,
|
Book,
|
||||||
BookWithSlug._meta.get_field_by_name("slug")[0],
|
BookWithSlug._meta.get_field("slug"),
|
||||||
)
|
)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"slug",
|
"slug",
|
||||||
|
@ -1014,7 +1014,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(
|
editor.alter_field(
|
||||||
BookWithSlug,
|
BookWithSlug,
|
||||||
BookWithSlug._meta.get_field_by_name("slug")[0],
|
BookWithSlug._meta.get_field("slug"),
|
||||||
new_field2,
|
new_field2,
|
||||||
strict=True,
|
strict=True,
|
||||||
)
|
)
|
||||||
|
@ -1039,10 +1039,10 @@ class SchemaTests(TransactionTestCase):
|
||||||
new_field.set_attributes_from_name("slug")
|
new_field.set_attributes_from_name("slug")
|
||||||
new_field.model = Tag
|
new_field.model = Tag
|
||||||
with connection.schema_editor() as editor:
|
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(
|
editor.alter_field(
|
||||||
Tag,
|
Tag,
|
||||||
Tag._meta.get_field_by_name("slug")[0],
|
Tag._meta.get_field("slug"),
|
||||||
new_field,
|
new_field,
|
||||||
)
|
)
|
||||||
# Ensure the PK changed
|
# Ensure the PK changed
|
||||||
|
|
Loading…
Reference in New Issue