|
@@ -1,20 +1,31 @@
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
from bisect import bisect
|
|
|
-from collections import OrderedDict
|
|
|
+from collections import OrderedDict, defaultdict
|
|
|
+from itertools import chain
|
|
|
+import warnings
|
|
|
|
|
|
from django.apps import apps
|
|
|
from django.conf import settings
|
|
|
from django.core.exceptions import FieldDoesNotExist
|
|
|
-from django.db.models.fields.related import ManyToManyRel
|
|
|
+from django.db.models.fields.related import ManyToManyField
|
|
|
from django.db.models.fields import AutoField
|
|
|
from django.db.models.fields.proxy import OrderWrt
|
|
|
from django.utils import six
|
|
|
+from django.utils.datastructures import ImmutableList
|
|
|
+from django.utils.deprecation import RemovedInDjango20Warning
|
|
|
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
|
|
|
from django.utils.functional import cached_property
|
|
|
+from django.utils.lru_cache import lru_cache
|
|
|
from django.utils.text import camel_case_to_spaces
|
|
|
from django.utils.translation import activate, deactivate_all, get_language, string_concat
|
|
|
|
|
|
+EMPTY_RELATION_TREE = tuple()
|
|
|
+
|
|
|
+IMMUTABLE_WARNING = (
|
|
|
+ "The return type of '%s' should never be mutated. If you want to manipulate this list "
|
|
|
+ "for your own use, make a copy first."
|
|
|
+)
|
|
|
|
|
|
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
|
|
'unique_together', 'permissions', 'get_latest_by',
|
|
@@ -24,6 +35,24 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
|
|
'select_on_save', 'default_related_name')
|
|
|
|
|
|
|
|
|
+class raise_deprecation(object):
|
|
|
+ def __init__(self, suggested_alternative):
|
|
|
+ self.suggested_alternative = suggested_alternative
|
|
|
+
|
|
|
+ def __call__(self, fn):
|
|
|
+ def wrapper(*args, **kwargs):
|
|
|
+ warnings.warn(
|
|
|
+ "'%s is an unofficial API that has been deprecated. "
|
|
|
+ "You may be able to replace it with '%s'" % (
|
|
|
+ fn.__name__,
|
|
|
+ self.suggested_alternative,
|
|
|
+ ),
|
|
|
+ RemovedInDjango20Warning, stacklevel=2
|
|
|
+ )
|
|
|
+ return fn(*args, **kwargs)
|
|
|
+ return wrapper
|
|
|
+
|
|
|
+
|
|
|
def normalize_together(option_together):
|
|
|
"""
|
|
|
option_together can be either a tuple of tuples, or a single
|
|
@@ -46,9 +75,19 @@ def normalize_together(option_together):
|
|
|
return option_together
|
|
|
|
|
|
|
|
|
+def make_immutable_fields_list(name, data):
|
|
|
+ return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
|
|
|
+
|
|
|
+
|
|
|
@python_2_unicode_compatible
|
|
|
class Options(object):
|
|
|
+ FORWARD_PROPERTIES = ('fields', 'many_to_many', 'concrete_fields',
|
|
|
+ 'local_concrete_fields', '_forward_fields_map')
|
|
|
+ REVERSE_PROPERTIES = ('related_objects', 'fields_map', '_relation_tree')
|
|
|
+
|
|
|
def __init__(self, meta, app_label=None):
|
|
|
+ self._get_fields_cache = {}
|
|
|
+ self.proxied_children = []
|
|
|
self.local_fields = []
|
|
|
self.local_many_to_many = []
|
|
|
self.virtual_fields = []
|
|
@@ -103,6 +142,31 @@ class Options(object):
|
|
|
|
|
|
self.default_related_name = None
|
|
|
|
|
|
+ @lru_cache(maxsize=None)
|
|
|
+ def _map_model(self, link):
|
|
|
+ # This helper function is used to allow backwards compatibility with
|
|
|
+ # the previous API. No future methods should use this function.
|
|
|
+ # It maps a field to (field, model or related_model,) depending on the
|
|
|
+ # field type.
|
|
|
+ model = link.model._meta.concrete_model
|
|
|
+ if model is self.model:
|
|
|
+ model = None
|
|
|
+ return link, model
|
|
|
+
|
|
|
+ @lru_cache(maxsize=None)
|
|
|
+ def _map_model_details(self, link):
|
|
|
+ # This helper function is used to allow backwards compatibility with
|
|
|
+ # the previous API. No future methods should use this function.
|
|
|
+ # This function maps a field to a tuple of:
|
|
|
+ # (field, model or related_model, direct, is_m2m) depending on the
|
|
|
+ # field type.
|
|
|
+ direct = not link.auto_created or link.concrete
|
|
|
+ model = link.model._meta.concrete_model
|
|
|
+ if model is self.model:
|
|
|
+ model = None
|
|
|
+ m2m = link.is_relation and link.many_to_many
|
|
|
+ return link, model, direct, m2m
|
|
|
+
|
|
|
@property
|
|
|
def app_config(self):
|
|
|
# Don't go through get_app_config to avoid triggering imports.
|
|
@@ -183,7 +247,17 @@ class Options(object):
|
|
|
|
|
|
def _prepare(self, model):
|
|
|
if self.order_with_respect_to:
|
|
|
- self.order_with_respect_to = self.get_field(self.order_with_respect_to)
|
|
|
+ # The app registry will not be ready at this point, so we cannot
|
|
|
+ # use get_field().
|
|
|
+ query = self.order_with_respect_to
|
|
|
+ try:
|
|
|
+ self.order_with_respect_to = next(
|
|
|
+ f for f in self._get_fields(reverse=False)
|
|
|
+ if f.name == query or f.attname == query
|
|
|
+ )
|
|
|
+ except StopIteration:
|
|
|
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, query))
|
|
|
+
|
|
|
self.ordering = ('_order',)
|
|
|
if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields):
|
|
|
model.add_to_class('_order', OrderWrt())
|
|
@@ -208,56 +282,41 @@ class Options(object):
|
|
|
auto_created=True)
|
|
|
model.add_to_class('id', auto)
|
|
|
|
|
|
- def add_field(self, field):
|
|
|
+ def add_field(self, field, virtual=False):
|
|
|
# Insert the given field in the order in which it was created, using
|
|
|
# the "creation_counter" attribute of the field.
|
|
|
# Move many-to-many related fields from self.fields into
|
|
|
# self.many_to_many.
|
|
|
- if field.rel and isinstance(field.rel, ManyToManyRel):
|
|
|
+ if virtual:
|
|
|
+ self.virtual_fields.append(field)
|
|
|
+ elif field.is_relation and field.many_to_many:
|
|
|
self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
|
|
|
- if hasattr(self, '_m2m_cache'):
|
|
|
- del self._m2m_cache
|
|
|
else:
|
|
|
self.local_fields.insert(bisect(self.local_fields, field), field)
|
|
|
self.setup_pk(field)
|
|
|
- if hasattr(self, '_field_cache'):
|
|
|
- del self._field_cache
|
|
|
- del self._field_name_cache
|
|
|
- # The fields, concrete_fields and local_concrete_fields are
|
|
|
- # implemented as cached properties for performance reasons.
|
|
|
- # The attrs will not exists if the cached property isn't
|
|
|
- # accessed yet, hence the try-excepts.
|
|
|
- try:
|
|
|
- del self.fields
|
|
|
- except AttributeError:
|
|
|
- pass
|
|
|
- try:
|
|
|
- del self.concrete_fields
|
|
|
- except AttributeError:
|
|
|
- pass
|
|
|
- try:
|
|
|
- del self.local_concrete_fields
|
|
|
- except AttributeError:
|
|
|
- pass
|
|
|
-
|
|
|
- if hasattr(self, '_name_map'):
|
|
|
- del self._name_map
|
|
|
|
|
|
- def add_virtual_field(self, field):
|
|
|
- self.virtual_fields.append(field)
|
|
|
+ # If the field being added is a relation to another known field,
|
|
|
+ # expire the cache on this field and the forward cache on the field
|
|
|
+ # being referenced, because there will be new relationships in the
|
|
|
+ # cache. Otherwise, expire the cache of references *to* this field.
|
|
|
+ # The mechanism for getting at the related model is slightly odd -
|
|
|
+ # ideally, we'd just ask for field.related_model. However, related_model
|
|
|
+ # is a cached property, and all the models haven't been loaded yet, so
|
|
|
+ # we need to make sure we don't cache a string reference.
|
|
|
+ if field.is_relation and hasattr(field.rel, 'to') and field.rel.to:
|
|
|
+ try:
|
|
|
+ field.rel.to._meta._expire_cache(forward=False)
|
|
|
+ except AttributeError:
|
|
|
+ pass
|
|
|
+ self._expire_cache()
|
|
|
+ else:
|
|
|
+ self._expire_cache(reverse=False)
|
|
|
|
|
|
def setup_pk(self, field):
|
|
|
if not self.pk and field.primary_key:
|
|
|
self.pk = field
|
|
|
field.serialize = False
|
|
|
|
|
|
- def pk_index(self):
|
|
|
- """
|
|
|
- Returns the index of the primary key field in the self.concrete_fields
|
|
|
- list.
|
|
|
- """
|
|
|
- return self.concrete_fields.index(self.pk)
|
|
|
-
|
|
|
def setup_proxy(self, target):
|
|
|
"""
|
|
|
Does the internal setup so that the current model is a proxy for
|
|
@@ -273,6 +332,7 @@ class Options(object):
|
|
|
def __str__(self):
|
|
|
return "%s.%s" % (smart_text(self.app_label), smart_text(self.model_name))
|
|
|
|
|
|
+ @property
|
|
|
def verbose_name_raw(self):
|
|
|
"""
|
|
|
There are a few places where the untranslated verbose name is needed
|
|
@@ -284,9 +344,9 @@ class Options(object):
|
|
|
raw = force_text(self.verbose_name)
|
|
|
activate(lang)
|
|
|
return raw
|
|
|
- verbose_name_raw = property(verbose_name_raw)
|
|
|
|
|
|
- def _swapped(self):
|
|
|
+ @property
|
|
|
+ def swapped(self):
|
|
|
"""
|
|
|
Has this model been swapped out for another? If so, return the model
|
|
|
name of the replacement; otherwise, return None.
|
|
@@ -310,253 +370,253 @@ class Options(object):
|
|
|
if '%s.%s' % (swapped_label, swapped_object.lower()) not in (None, model_label):
|
|
|
return swapped_for
|
|
|
return None
|
|
|
- swapped = property(_swapped)
|
|
|
|
|
|
@cached_property
|
|
|
def fields(self):
|
|
|
"""
|
|
|
- The getter for self.fields. This returns the list of field objects
|
|
|
- available to this model (including through parent models).
|
|
|
-
|
|
|
- Callers are not permitted to modify this list, since it's a reference
|
|
|
- to this instance (not a copy).
|
|
|
- """
|
|
|
- try:
|
|
|
- self._field_name_cache
|
|
|
- except AttributeError:
|
|
|
- self._fill_fields_cache()
|
|
|
- return self._field_name_cache
|
|
|
+ Returns a list of all forward fields on the model and its parents,
|
|
|
+ excluding ManyToManyFields.
|
|
|
+
|
|
|
+ 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.
|
|
|
+ """
|
|
|
+ # For legacy reasons, the fields property should only contain forward
|
|
|
+ # fields that are not virtual or with a m2m cardinality. Therefore we
|
|
|
+ # pass these three filters as filters to the generator.
|
|
|
+ # The third lambda is a longwinded way of checking f.related_model - we don't
|
|
|
+ # use that property directly because related_model is a cached property,
|
|
|
+ # and all the models may not have been loaded yet; we don't want to cache
|
|
|
+ # the string reference to the related_model.
|
|
|
+ is_not_an_m2m_field = lambda f: not (f.is_relation and f.many_to_many)
|
|
|
+ is_not_a_generic_relation = lambda f: not (f.is_relation and f.many_to_one)
|
|
|
+ is_not_a_generic_foreign_key = lambda f: not (
|
|
|
+ f.is_relation and f.one_to_many and not (hasattr(f.rel, 'to') and f.rel.to)
|
|
|
+ )
|
|
|
+ return make_immutable_fields_list(
|
|
|
+ "fields",
|
|
|
+ (f for f in self._get_fields(reverse=False) if
|
|
|
+ is_not_an_m2m_field(f) and is_not_a_generic_relation(f)
|
|
|
+ and is_not_a_generic_foreign_key(f))
|
|
|
+ )
|
|
|
|
|
|
@cached_property
|
|
|
def concrete_fields(self):
|
|
|
- return [f for f in self.fields if f.column is not None]
|
|
|
+ """
|
|
|
+ Returns a list of all concrete fields on the model and its parents.
|
|
|
+
|
|
|
+ Private API intended only to be used by Django itself; get_fields()
|
|
|
+ combined with filtering of field properties is the public API for
|
|
|
+ obtaining this field list.
|
|
|
+ """
|
|
|
+ return make_immutable_fields_list(
|
|
|
+ "concrete_fields", (f for f in self.fields if f.concrete)
|
|
|
+ )
|
|
|
|
|
|
@cached_property
|
|
|
def local_concrete_fields(self):
|
|
|
- return [f for f in self.local_fields if f.column is not None]
|
|
|
-
|
|
|
- def get_fields_with_model(self):
|
|
|
"""
|
|
|
- Returns a sequence of (field, model) pairs for all fields. The "model"
|
|
|
- element is None for fields on the current model. Mostly of use when
|
|
|
- constructing queries so that we know which model a field belongs to.
|
|
|
+ Returns a list of all concrete fields on the model.
|
|
|
+
|
|
|
+ Private API intended only to be used by Django itself; get_fields()
|
|
|
+ combined with filtering of field properties is the public API for
|
|
|
+ obtaining this field list.
|
|
|
"""
|
|
|
- try:
|
|
|
- self._field_cache
|
|
|
- except AttributeError:
|
|
|
- self._fill_fields_cache()
|
|
|
- return self._field_cache
|
|
|
+ return make_immutable_fields_list(
|
|
|
+ "local_concrete_fields", (f for f in self.local_fields if f.concrete)
|
|
|
+ )
|
|
|
|
|
|
- def get_concrete_fields_with_model(self):
|
|
|
- return [(field, model) for field, model in self.get_fields_with_model() if
|
|
|
- field.column is not None]
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
+ def get_fields_with_model(self):
|
|
|
+ return [self._map_model(f) for f in self.get_fields()]
|
|
|
|
|
|
- def _fill_fields_cache(self):
|
|
|
- cache = []
|
|
|
- for parent in self.parents:
|
|
|
- for field, model in parent._meta.get_fields_with_model():
|
|
|
- if model:
|
|
|
- cache.append((field, model))
|
|
|
- else:
|
|
|
- cache.append((field, parent))
|
|
|
- cache.extend((f, None) for f in self.local_fields)
|
|
|
- self._field_cache = tuple(cache)
|
|
|
- self._field_name_cache = [x for x, _ in cache]
|
|
|
-
|
|
|
- def _many_to_many(self):
|
|
|
- try:
|
|
|
- self._m2m_cache
|
|
|
- except AttributeError:
|
|
|
- self._fill_m2m_cache()
|
|
|
- return list(self._m2m_cache)
|
|
|
- many_to_many = property(_many_to_many)
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
+ def get_concrete_fields_with_model(self):
|
|
|
+ return [self._map_model(f) for f in self.concrete_fields]
|
|
|
|
|
|
- def get_m2m_with_model(self):
|
|
|
- """
|
|
|
- The many-to-many version of get_fields_with_model().
|
|
|
+ @cached_property
|
|
|
+ def many_to_many(self):
|
|
|
"""
|
|
|
- try:
|
|
|
- self._m2m_cache
|
|
|
- except AttributeError:
|
|
|
- self._fill_m2m_cache()
|
|
|
- return list(six.iteritems(self._m2m_cache))
|
|
|
+ Returns a list of all many to many fields on the model and its parents.
|
|
|
|
|
|
- def _fill_m2m_cache(self):
|
|
|
- cache = OrderedDict()
|
|
|
- for parent in self.parents:
|
|
|
- for field, model in parent._meta.get_m2m_with_model():
|
|
|
- if model:
|
|
|
- cache[field] = model
|
|
|
- else:
|
|
|
- cache[field] = parent
|
|
|
- for field in self.local_many_to_many:
|
|
|
- cache[field] = None
|
|
|
- self._m2m_cache = cache
|
|
|
-
|
|
|
- def get_field(self, name, many_to_many=True):
|
|
|
+ Private API intended only to be used by Django itself; get_fields()
|
|
|
+ combined with filtering of field properties is the public API for
|
|
|
+ obtaining this list.
|
|
|
"""
|
|
|
- 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))
|
|
|
+ return make_immutable_fields_list(
|
|
|
+ "many_to_many",
|
|
|
+ (f for f in self._get_fields(reverse=False)
|
|
|
+ if f.is_relation and f.many_to_many)
|
|
|
+ )
|
|
|
|
|
|
- def get_field_by_name(self, name):
|
|
|
+ @cached_property
|
|
|
+ def related_objects(self):
|
|
|
"""
|
|
|
- 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.
|
|
|
+ Returns all related objects pointing to the current model. The related
|
|
|
+ objects can come from a one-to-one, one-to-many, or many-to-many field
|
|
|
+ relation type.
|
|
|
+
|
|
|
+ Private API intended only to be used by Django itself; get_fields()
|
|
|
+ combined with filtering of field properties is the public API for
|
|
|
+ obtaining this field list.
|
|
|
"""
|
|
|
- try:
|
|
|
+ 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):
|
|
|
+ return [self._map_model(f) for f in self.many_to_many]
|
|
|
+
|
|
|
+ @cached_property
|
|
|
+ def _forward_fields_map(self):
|
|
|
+ res = {}
|
|
|
+ # call get_fields() with export_ordered_set=True in order to have a
|
|
|
+ # field_instance -> names map
|
|
|
+ fields = self._get_fields(reverse=False)
|
|
|
+ for field in fields:
|
|
|
+ res[field.name] = field
|
|
|
+ # Due to the way Django's internals work, get_field() should also
|
|
|
+ # be able to fetch a field by attname. In the case of a concrete
|
|
|
+ # field with relation, includes the *_id name too
|
|
|
try:
|
|
|
- return self._name_map[name]
|
|
|
+ res[field.attname] = field
|
|
|
except AttributeError:
|
|
|
- cache = self.init_name_map()
|
|
|
- return cache[name]
|
|
|
- except KeyError:
|
|
|
- raise FieldDoesNotExist('%s has no field named %r'
|
|
|
- % (self.object_name, name))
|
|
|
+ pass
|
|
|
+ return res
|
|
|
|
|
|
- def get_all_field_names(self):
|
|
|
+ @cached_property
|
|
|
+ def fields_map(self):
|
|
|
+ res = {}
|
|
|
+ fields = self._get_fields(forward=False, include_hidden=True)
|
|
|
+ for field in fields:
|
|
|
+ res[field.name] = field
|
|
|
+ # Due to the way Django's internals work, get_field() should also
|
|
|
+ # be able to fetch a field by attname. In the case of a concrete
|
|
|
+ # field with relation, includes the *_id name too
|
|
|
+ try:
|
|
|
+ res[field.attname] = field
|
|
|
+ except AttributeError:
|
|
|
+ pass
|
|
|
+ return res
|
|
|
+
|
|
|
+ def get_field(self, field_name, many_to_many=None):
|
|
|
"""
|
|
|
- Returns a list of all field names that are possible for this model
|
|
|
- (including reverse relation names). This is used for pretty printing
|
|
|
- debugging output (a list of choices), so any internal-only field names
|
|
|
- are not included.
|
|
|
+ Returns a field instance given a field name. The field can be either a
|
|
|
+ forward or reverse field, unless many_to_many is specified; if it is,
|
|
|
+ only forward fields will be returned.
|
|
|
+
|
|
|
+ The many_to_many argument exists for backwards compatibility reasons;
|
|
|
+ it has been deprecated and will be removed in Django 2.0.
|
|
|
"""
|
|
|
+ m2m_in_kwargs = many_to_many is not None
|
|
|
+ if m2m_in_kwargs:
|
|
|
+ # Always throw a warning if many_to_many is used regardless of
|
|
|
+ # whether it alters the return type or not.
|
|
|
+ warnings.warn(
|
|
|
+ "The 'many_to_many' argument on get_field() is deprecated; "
|
|
|
+ "use a filter on field.many_to_many instead.",
|
|
|
+ RemovedInDjango20Warning
|
|
|
+ )
|
|
|
+
|
|
|
try:
|
|
|
- cache = self._name_map
|
|
|
- except AttributeError:
|
|
|
- cache = self.init_name_map()
|
|
|
- names = sorted(cache.keys())
|
|
|
- # Internal-only names end with "+" (symmetrical m2m related names being
|
|
|
- # the main example). Trim them.
|
|
|
- return [val for val in names if not val.endswith('+')]
|
|
|
-
|
|
|
- def init_name_map(self):
|
|
|
- """
|
|
|
- Initialises the field name -> field object mapping.
|
|
|
- """
|
|
|
- cache = {}
|
|
|
- # We intentionally handle related m2m objects first so that symmetrical
|
|
|
- # m2m accessor names can be overridden, if necessary.
|
|
|
- for f, model in self.get_all_related_m2m_objects_with_model():
|
|
|
- cache[f.field.related_query_name()] = (f, model, False, True)
|
|
|
- for f, model in self.get_all_related_objects_with_model():
|
|
|
- cache[f.field.related_query_name()] = (f, model, False, False)
|
|
|
- for f, model in self.get_m2m_with_model():
|
|
|
- cache[f.name] = cache[f.attname] = (f, model, True, True)
|
|
|
- for f, model in self.get_fields_with_model():
|
|
|
- cache[f.name] = cache[f.attname] = (f, model, True, False)
|
|
|
- for f in self.virtual_fields:
|
|
|
- if f.rel:
|
|
|
- cache[f.name] = cache[f.attname] = (
|
|
|
- f, None if f.model == self.model else f.model, True, False)
|
|
|
- if apps.ready:
|
|
|
- self._name_map = cache
|
|
|
- return cache
|
|
|
+ # In order to avoid premature loading of the relation tree
|
|
|
+ # (expensive) we prefer checking if the field is a forward field.
|
|
|
+ field = self._forward_fields_map[field_name]
|
|
|
+
|
|
|
+ if many_to_many is False and field.many_to_many:
|
|
|
+ raise FieldDoesNotExist(
|
|
|
+ '%s has no field named %r' % (self.object_name, field_name)
|
|
|
+ )
|
|
|
+
|
|
|
+ return field
|
|
|
+ except KeyError:
|
|
|
+ # If the app registry is not ready, reverse fields are
|
|
|
+ # unavailable, therefore we throw a FieldDoesNotExist exception.
|
|
|
+ if not self.apps.ready:
|
|
|
+ raise FieldDoesNotExist(
|
|
|
+ "%s has no field named %r. The app cache isn't "
|
|
|
+ "ready yet, so if this is a forward field, it won't "
|
|
|
+ "be available yet." % (self.object_name, field_name)
|
|
|
+ )
|
|
|
+
|
|
|
+ try:
|
|
|
+ if m2m_in_kwargs:
|
|
|
+ # Previous API does not allow searching reverse fields.
|
|
|
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
|
|
|
|
|
+ # Retrieve field instance by name from cached or just-computed
|
|
|
+ # field map.
|
|
|
+ return self.fields_map[field_name]
|
|
|
+ except KeyError:
|
|
|
+ raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
|
|
+
|
|
|
+ @raise_deprecation(suggested_alternative="get_field()")
|
|
|
+ def get_field_by_name(self, name):
|
|
|
+ return self._map_model_details(self.get_field(name))
|
|
|
+
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
+ def get_all_field_names(self):
|
|
|
+ names = set()
|
|
|
+ fields = self.get_fields()
|
|
|
+ for field in fields:
|
|
|
+ # For backwards compatibility GenericForeignKey should not be
|
|
|
+ # included in the results.
|
|
|
+ if field.is_relation and field.one_to_many and field.related_model is None:
|
|
|
+ continue
|
|
|
+
|
|
|
+ names.add(field.name)
|
|
|
+ if hasattr(field, 'attname'):
|
|
|
+ names.add(field.attname)
|
|
|
+ return list(names)
|
|
|
+
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
|
|
include_proxy_eq=False):
|
|
|
- return [k for k, v in self.get_all_related_objects_with_model(
|
|
|
- local_only=local_only, include_hidden=include_hidden,
|
|
|
- include_proxy_eq=include_proxy_eq)]
|
|
|
|
|
|
- def get_all_related_objects_with_model(self, local_only=False,
|
|
|
- include_hidden=False,
|
|
|
+ include_parents = local_only is False
|
|
|
+ fields = self._get_fields(
|
|
|
+ forward=False, reverse=True,
|
|
|
+ include_parents=include_parents,
|
|
|
+ include_hidden=include_hidden,
|
|
|
+ )
|
|
|
+ fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField))
|
|
|
+
|
|
|
+ if include_proxy_eq:
|
|
|
+ children = chain.from_iterable(c._relation_tree
|
|
|
+ for c in self.concrete_model._meta.proxied_children
|
|
|
+ if c is not self)
|
|
|
+ relations = (f.rel for f in children
|
|
|
+ if include_hidden or not f.rel.field.rel.is_hidden())
|
|
|
+ fields = chain(fields, relations)
|
|
|
+ return list(fields)
|
|
|
+
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
+ def get_all_related_objects_with_model(self, local_only=False, include_hidden=False,
|
|
|
include_proxy_eq=False):
|
|
|
- """
|
|
|
- Returns a list of (related-object, model) pairs. Similar to
|
|
|
- get_fields_with_model().
|
|
|
- """
|
|
|
- try:
|
|
|
- self._related_objects_cache
|
|
|
- except AttributeError:
|
|
|
- self._fill_related_objects_cache()
|
|
|
- predicates = []
|
|
|
- if local_only:
|
|
|
- predicates.append(lambda k, v: not v)
|
|
|
- if not include_hidden:
|
|
|
- predicates.append(lambda k, v: not k.field.rel.is_hidden())
|
|
|
- cache = (self._related_objects_proxy_cache if include_proxy_eq
|
|
|
- else self._related_objects_cache)
|
|
|
- return [t for t in cache.items() if all(p(*t) for p in predicates)]
|
|
|
-
|
|
|
- def _fill_related_objects_cache(self):
|
|
|
- cache = OrderedDict()
|
|
|
- parent_list = self.get_parent_list()
|
|
|
- for parent in self.parents:
|
|
|
- for obj, model in parent._meta.get_all_related_objects_with_model(include_hidden=True):
|
|
|
- if (obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.model not in parent_list:
|
|
|
- continue
|
|
|
- if not model:
|
|
|
- cache[obj] = parent
|
|
|
- else:
|
|
|
- cache[obj] = model
|
|
|
- # Collect also objects which are in relation to some proxy child/parent of self.
|
|
|
- proxy_cache = cache.copy()
|
|
|
- for klass in self.apps.get_models(include_auto_created=True):
|
|
|
- if not klass._meta.swapped:
|
|
|
- for f in klass._meta.local_fields + klass._meta.virtual_fields:
|
|
|
- if (hasattr(f, 'rel') and f.rel and not isinstance(f.rel.to, six.string_types)
|
|
|
- and f.generate_reverse_relation):
|
|
|
- if self == f.rel.to._meta:
|
|
|
- cache[f.rel] = None
|
|
|
- proxy_cache[f.rel] = None
|
|
|
- elif self.concrete_model == f.rel.to._meta.concrete_model:
|
|
|
- proxy_cache[f.rel] = None
|
|
|
- self._related_objects_cache = cache
|
|
|
- self._related_objects_proxy_cache = proxy_cache
|
|
|
+ return [
|
|
|
+ self._map_model(f) for f in self.get_all_related_objects(
|
|
|
+ local_only=local_only,
|
|
|
+ include_hidden=include_hidden,
|
|
|
+ include_proxy_eq=include_proxy_eq,
|
|
|
+ )
|
|
|
+ ]
|
|
|
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
def get_all_related_many_to_many_objects(self, local_only=False):
|
|
|
- try:
|
|
|
- cache = self._related_many_to_many_cache
|
|
|
- except AttributeError:
|
|
|
- cache = self._fill_related_many_to_many_cache()
|
|
|
- if local_only:
|
|
|
- return [k for k, v in cache.items() if not v]
|
|
|
- return list(cache)
|
|
|
+ fields = self._get_fields(
|
|
|
+ forward=False, reverse=True,
|
|
|
+ include_parents=local_only is not True, include_hidden=True
|
|
|
+ )
|
|
|
+ return [obj for obj in fields if isinstance(obj.field, ManyToManyField)]
|
|
|
|
|
|
+ @raise_deprecation(suggested_alternative="get_fields()")
|
|
|
def get_all_related_m2m_objects_with_model(self):
|
|
|
- """
|
|
|
- Returns a list of (related-m2m-object, model) pairs. Similar to
|
|
|
- get_fields_with_model().
|
|
|
- """
|
|
|
- try:
|
|
|
- cache = self._related_many_to_many_cache
|
|
|
- except AttributeError:
|
|
|
- cache = self._fill_related_many_to_many_cache()
|
|
|
- return list(six.iteritems(cache))
|
|
|
-
|
|
|
- def _fill_related_many_to_many_cache(self):
|
|
|
- cache = OrderedDict()
|
|
|
- parent_list = self.get_parent_list()
|
|
|
- for parent in self.parents:
|
|
|
- for obj, model in parent._meta.get_all_related_m2m_objects_with_model():
|
|
|
- if obj.field.creation_counter < 0 and obj.model not in parent_list:
|
|
|
- continue
|
|
|
- if not model:
|
|
|
- cache[obj] = parent
|
|
|
- else:
|
|
|
- cache[obj] = model
|
|
|
- for klass in self.apps.get_models():
|
|
|
- if not klass._meta.swapped:
|
|
|
- for f in klass._meta.local_many_to_many:
|
|
|
- if (f.rel
|
|
|
- and not isinstance(f.rel.to, six.string_types)
|
|
|
- and self == f.rel.to._meta):
|
|
|
- cache[f.rel] = None
|
|
|
- if apps.ready:
|
|
|
- self._related_many_to_many_cache = cache
|
|
|
- return cache
|
|
|
+ fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
|
|
|
+ return [self._map_model(obj) for obj in fields if isinstance(obj.field, ManyToManyField)]
|
|
|
|
|
|
def get_base_chain(self, model):
|
|
|
"""
|
|
@@ -605,3 +665,173 @@ class Options(object):
|
|
|
# of the chain to the ancestor is that parent
|
|
|
# links
|
|
|
return self.parents[parent] or parent_link
|
|
|
+
|
|
|
+ def _populate_directed_relation_graph(self):
|
|
|
+ """
|
|
|
+ This method is used by each model to find its reverse objects. As this
|
|
|
+ method is very expensive and is accessed frequently (it looks up every
|
|
|
+ field in a model, in every app), it is computed on first access and then
|
|
|
+ is set as a property on every model.
|
|
|
+ """
|
|
|
+ related_objects_graph = defaultdict(list)
|
|
|
+
|
|
|
+ all_models = self.apps.get_models(include_auto_created=True)
|
|
|
+ for model in all_models:
|
|
|
+ fields_with_relations = (
|
|
|
+ f for f in model._meta._get_fields(reverse=False)
|
|
|
+ if f.is_relation and f.related_model is not None
|
|
|
+ )
|
|
|
+ if model._meta.auto_created:
|
|
|
+ fields_with_relations = (
|
|
|
+ f for f in fields_with_relations
|
|
|
+ if not f.many_to_many
|
|
|
+ )
|
|
|
+
|
|
|
+ for f in fields_with_relations:
|
|
|
+ if not isinstance(f.rel.to, six.string_types):
|
|
|
+ # Set options_instance -> field
|
|
|
+ related_objects_graph[f.rel.to._meta].append(f)
|
|
|
+
|
|
|
+ for model in all_models:
|
|
|
+ # Set the relation_tree using the internal __dict__. In this way
|
|
|
+ # we avoid calling the cached property. In attribute lookup,
|
|
|
+ # __dict__ takes precedence over a data descriptor (such as
|
|
|
+ # @cached_property). This means that the _meta._relation_tree is
|
|
|
+ # only called if related_objects is not in __dict__.
|
|
|
+ related_objects = related_objects_graph[model._meta]
|
|
|
+
|
|
|
+ # If related_objects are empty, it makes sense to set
|
|
|
+ # EMPTY_RELATION_TREE. This will avoid allocating multiple empty
|
|
|
+ # relation trees.
|
|
|
+ relation_tree = EMPTY_RELATION_TREE
|
|
|
+ if related_objects:
|
|
|
+ relation_tree = related_objects
|
|
|
+ model._meta.__dict__['_relation_tree'] = relation_tree
|
|
|
+
|
|
|
+ @cached_property
|
|
|
+ def _relation_tree(self):
|
|
|
+ # If cache is not present, populate the cache
|
|
|
+ self._populate_directed_relation_graph()
|
|
|
+ # It may happen, often when the registry is not ready, that a not yet
|
|
|
+ # registered model is queried. In this very rare case we simply return
|
|
|
+ # an EMPTY_RELATION_TREE. When the registry will be ready, cache will
|
|
|
+ # be flushed and this model will be computed properly.
|
|
|
+ return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE)
|
|
|
+
|
|
|
+ def _expire_cache(self, forward=True, reverse=True):
|
|
|
+ # This method is usually called by apps.cache_clear(), when the
|
|
|
+ # registry is finalized, or when a new field is added.
|
|
|
+ properties_to_expire = []
|
|
|
+ if forward:
|
|
|
+ properties_to_expire.extend(self.FORWARD_PROPERTIES)
|
|
|
+ if reverse and not self.abstract:
|
|
|
+ properties_to_expire.extend(self.REVERSE_PROPERTIES)
|
|
|
+
|
|
|
+ for cache_key in properties_to_expire:
|
|
|
+ try:
|
|
|
+ delattr(self, cache_key)
|
|
|
+ except AttributeError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ self._get_fields_cache = {}
|
|
|
+
|
|
|
+ def get_fields(self, include_parents=True, include_hidden=False):
|
|
|
+ """
|
|
|
+ Returns a list of fields associated to the model. By default will only
|
|
|
+ return forward fields. This can be changed by enabling or disabling
|
|
|
+ field types using the parameters:
|
|
|
+
|
|
|
+ - include_parents: include fields derived from inheritance
|
|
|
+ - include_hidden: include fields that have a related_name that
|
|
|
+ starts with a "+"
|
|
|
+ """
|
|
|
+ return self._get_fields(include_parents=include_parents, include_hidden=include_hidden)
|
|
|
+
|
|
|
+ def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False,
|
|
|
+ export_ordered_set=False):
|
|
|
+ # This helper function is used to allow recursion in ``get_fields()``
|
|
|
+ # implementation and to provide a fast way for Django's internals to
|
|
|
+ # access specific subsets of fields.
|
|
|
+
|
|
|
+ # Creates a cache key composed of all arguments
|
|
|
+ cache_key = (forward, reverse, include_parents, include_hidden, export_ordered_set)
|
|
|
+ try:
|
|
|
+ # In order to avoid list manipulation. Always return a shallow copy
|
|
|
+ # of the results.
|
|
|
+ return self._get_fields_cache[cache_key]
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ # Using an OrderedDict preserves the order of insertion. This is
|
|
|
+ # important when displaying a ModelForm or the contrib.admin panel
|
|
|
+ # and no specific ordering is provided.
|
|
|
+ fields = OrderedDict()
|
|
|
+ options = {
|
|
|
+ 'include_parents': include_parents,
|
|
|
+ 'include_hidden': include_hidden,
|
|
|
+ 'export_ordered_set': True,
|
|
|
+ }
|
|
|
+
|
|
|
+ # Abstract models cannot hold reverse fields.
|
|
|
+ if reverse and not self.abstract:
|
|
|
+ if include_parents:
|
|
|
+ parent_list = self.get_parent_list()
|
|
|
+ # Recursively call _get_fields() on each parent, with the same
|
|
|
+ # options provided in this call.
|
|
|
+ for parent in self.parents:
|
|
|
+ for obj, _ in six.iteritems(parent._meta._get_fields(forward=False, **options)):
|
|
|
+ if obj.many_to_many:
|
|
|
+ # In order for a reverse ManyToManyRel object to be
|
|
|
+ # valid, its creation counter must be > 0 and must
|
|
|
+ # be in the parent list.
|
|
|
+ if not (obj.field.creation_counter < 0 and obj.related_model not in parent_list):
|
|
|
+ fields[obj] = True
|
|
|
+
|
|
|
+ elif not ((obj.field.creation_counter < 0 or obj.field.rel.parent_link)
|
|
|
+ and obj.related_model not in parent_list):
|
|
|
+ fields[obj] = True
|
|
|
+
|
|
|
+ # Tree is computed once and cached until the app cache is expired.
|
|
|
+ # It is composed of a list of fields pointing to the current model
|
|
|
+ # from other models. If the model is a proxy model, then we also
|
|
|
+ # add the concrete model.
|
|
|
+ all_fields = (
|
|
|
+ self._relation_tree if not self.proxy else
|
|
|
+ chain(self._relation_tree, self.concrete_model._meta._relation_tree)
|
|
|
+ )
|
|
|
+
|
|
|
+ # Pull out all related objects from forward fields
|
|
|
+ for field in (f.rel for f in all_fields):
|
|
|
+ # If hidden fields should be included or the relation is not
|
|
|
+ # intentionally hidden, add to the fields dict.
|
|
|
+ if include_hidden or not field.hidden:
|
|
|
+ fields[field] = True
|
|
|
+ if forward:
|
|
|
+ if include_parents:
|
|
|
+ for parent in self.parents:
|
|
|
+ # Add the forward fields of each parent.
|
|
|
+ fields.update(parent._meta._get_fields(reverse=False, **options))
|
|
|
+ fields.update(
|
|
|
+ (field, True,)
|
|
|
+ for field in chain(self.local_fields, self.local_many_to_many)
|
|
|
+ )
|
|
|
+
|
|
|
+ if not export_ordered_set:
|
|
|
+ # By default, fields contains field instances as keys and all
|
|
|
+ # possible names if the field instance as values. When
|
|
|
+ # _get_fields() is called, we only want to return field instances,
|
|
|
+ # so we just preserve the keys.
|
|
|
+ fields = list(fields.keys())
|
|
|
+
|
|
|
+ # Virtual fields are not inheritable, therefore they are inserted
|
|
|
+ # only when the recursive _get_fields() call comes to an end.
|
|
|
+ if forward:
|
|
|
+ fields.extend(self.virtual_fields)
|
|
|
+ fields = make_immutable_fields_list("get_fields()", fields)
|
|
|
+
|
|
|
+ # Store result into cache for later access
|
|
|
+ self._get_fields_cache[cache_key] = fields
|
|
|
+
|
|
|
+ # In order to avoid list manipulation. Always
|
|
|
+ # return a shallow copy of the results
|
|
|
+ return fields
|