mirror of
https://github.com/django/django.git
synced 2025-10-26 15:16:09 +00:00
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing code. Also adds a couple of new features. Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658 git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -1,25 +1,32 @@
|
||||
import re
|
||||
from bisect import bisect
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set # Python 2.3 fallback
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields.related import ManyToManyRel
|
||||
from django.db.models.fields import AutoField, FieldDoesNotExist
|
||||
from django.db.models.fields.proxy import OrderWrt
|
||||
from django.db.models.loading import get_models, app_cache_ready
|
||||
from django.db.models.query import orderlist2sql
|
||||
from django.db.models import Manager
|
||||
from django.utils.translation import activate, deactivate_all, get_language, string_concat
|
||||
from django.utils.encoding import force_unicode, smart_str
|
||||
from bisect import bisect
|
||||
import re
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
|
||||
get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
|
||||
|
||||
DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
|
||||
'unique_together', 'permissions', 'get_latest_by',
|
||||
'order_with_respect_to', 'app_label', 'db_tablespace')
|
||||
'order_with_respect_to', 'app_label', 'db_tablespace',
|
||||
'abstract')
|
||||
|
||||
class Options(object):
|
||||
def __init__(self, meta):
|
||||
self.fields, self.many_to_many = [], []
|
||||
self.local_fields, self.local_many_to_many = [], []
|
||||
self.module_name, self.verbose_name = None, None
|
||||
self.verbose_name_plural = None
|
||||
self.db_table = ''
|
||||
@@ -35,7 +42,8 @@ class Options(object):
|
||||
self.pk = None
|
||||
self.has_auto_field, self.auto_field = False, None
|
||||
self.one_to_one_field = None
|
||||
self.parents = []
|
||||
self.abstract = False
|
||||
self.parents = SortedDict()
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
cls._meta = self
|
||||
@@ -47,11 +55,14 @@ class Options(object):
|
||||
|
||||
# Next, apply any overridden values from 'class Meta'.
|
||||
if self.meta:
|
||||
meta_attrs = self.meta.__dict__
|
||||
meta_attrs = self.meta.__dict__.copy()
|
||||
del meta_attrs['__module__']
|
||||
del meta_attrs['__doc__']
|
||||
for attr_name in DEFAULT_NAMES:
|
||||
setattr(self, attr_name, meta_attrs.pop(attr_name, getattr(self, attr_name)))
|
||||
if attr_name in meta_attrs:
|
||||
setattr(self, attr_name, meta_attrs.pop(attr_name))
|
||||
elif hasattr(self.meta, attr_name):
|
||||
setattr(self, attr_name, getattr(self.meta, attr_name))
|
||||
|
||||
# unique_together can be either a tuple of tuples, or a single
|
||||
# tuple of two strings. Normalize it to a tuple of tuples, so that
|
||||
@@ -82,9 +93,16 @@ class Options(object):
|
||||
self.order_with_respect_to = None
|
||||
|
||||
if self.pk is None:
|
||||
auto = AutoField(verbose_name='ID', primary_key=True)
|
||||
auto.creation_counter = -1
|
||||
model.add_to_class('id', auto)
|
||||
if self.parents:
|
||||
# Promote the first parent link in lieu of adding yet another
|
||||
# field.
|
||||
field = self.parents.value_for_index(0)
|
||||
field.primary_key = True
|
||||
self.pk = field
|
||||
else:
|
||||
auto = AutoField(verbose_name='ID', primary_key=True,
|
||||
auto_created=True)
|
||||
model.add_to_class('id', auto)
|
||||
|
||||
# If the db_table wasn't provided, use the app_label + module_name.
|
||||
if not self.db_table:
|
||||
@@ -94,14 +112,26 @@ class Options(object):
|
||||
def add_field(self, field):
|
||||
# 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.
|
||||
# Move many-to-many related fields from self.fields into
|
||||
# self.many_to_many.
|
||||
if field.rel and isinstance(field.rel, ManyToManyRel):
|
||||
self.many_to_many.insert(bisect(self.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:
|
||||
self.fields.insert(bisect(self.fields, field), field)
|
||||
if not self.pk and field.primary_key:
|
||||
self.pk = field
|
||||
field.serialize = False
|
||||
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
|
||||
|
||||
if hasattr(self, '_name_map'):
|
||||
del self._name_map
|
||||
|
||||
def setup_pk(self, field):
|
||||
if not self.pk and field.primary_key:
|
||||
self.pk = field
|
||||
field.serialize = False
|
||||
|
||||
def __repr__(self):
|
||||
return '<Options for %s>' % self.object_name
|
||||
@@ -122,19 +152,137 @@ class Options(object):
|
||||
return raw
|
||||
verbose_name_raw = property(verbose_name_raw)
|
||||
|
||||
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
|
||||
fields = property(_fields)
|
||||
|
||||
def get_fields_with_model(self):
|
||||
"""
|
||||
Returns a sequence of (field, model) pairs for all fields. The "model"
|
||||
element is None for fields on the current model. Mostly of use when
|
||||
constructing queries so that we know which model a field belongs to.
|
||||
"""
|
||||
try:
|
||||
self._field_cache
|
||||
except AttributeError:
|
||||
self._fill_fields_cache()
|
||||
return self._field_cache
|
||||
|
||||
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 self._m2m_cache.keys()
|
||||
many_to_many = property(_many_to_many)
|
||||
|
||||
def get_m2m_with_model(self):
|
||||
"""
|
||||
The many-to-many version of get_fields_with_model().
|
||||
"""
|
||||
try:
|
||||
self._m2m_cache
|
||||
except AttributeError:
|
||||
self._fill_m2m_cache()
|
||||
return self._m2m_cache.items()
|
||||
|
||||
def _fill_m2m_cache(self):
|
||||
cache = SortedDict()
|
||||
for parent in self.parents:
|
||||
for field, model in parent._meta.get_m2m_with_model():
|
||||
if model:
|
||||
cache[field] = model
|
||||
else:
|
||||
cache[field] = parent
|
||||
for field in self.local_many_to_many:
|
||||
cache[field] = None
|
||||
self._m2m_cache = cache
|
||||
|
||||
def get_field(self, name, many_to_many=True):
|
||||
"Returns the requested field by name. Raises FieldDoesNotExist on error."
|
||||
"""
|
||||
Returns the requested field by name. Raises FieldDoesNotExist on error.
|
||||
"""
|
||||
to_search = many_to_many and (self.fields + self.many_to_many) or 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_order_sql(self, table_prefix=''):
|
||||
"Returns the full 'ORDER BY' clause for this object, according to self.ordering."
|
||||
if not self.ordering: return ''
|
||||
pre = table_prefix and (table_prefix + '.') or ''
|
||||
return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
|
||||
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 RelatedObject
|
||||
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:
|
||||
return self._name_map[name]
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
return self._name_map[name]
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist('%s has no field named %r'
|
||||
% (self.object_name, name))
|
||||
|
||||
def get_all_field_names(self):
|
||||
"""
|
||||
Returns a list of all field names that are possible for this model
|
||||
(including reverse relation names).
|
||||
"""
|
||||
try:
|
||||
cache = self._name_map
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
names = cache.keys()
|
||||
names.sort()
|
||||
return names
|
||||
|
||||
def init_name_map(self):
|
||||
"""
|
||||
Initialises the field name -> field object mapping.
|
||||
"""
|
||||
cache = dict([(f.name, (f, m, True, False)) for f, m in
|
||||
self.get_fields_with_model()])
|
||||
for f, model in self.get_m2m_with_model():
|
||||
cache[f.name] = (f, model, True, True)
|
||||
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)
|
||||
if self.order_with_respect_to:
|
||||
cache['_order'] = OrderWrt(), None, True, False
|
||||
if app_cache_ready():
|
||||
self._name_map = cache
|
||||
return cache
|
||||
|
||||
def get_add_permission(self):
|
||||
return 'add_%s' % self.object_name.lower()
|
||||
@@ -145,17 +293,81 @@ class Options(object):
|
||||
def get_delete_permission(self):
|
||||
return 'delete_%s' % self.object_name.lower()
|
||||
|
||||
def get_all_related_objects(self):
|
||||
try: # Try the cache first.
|
||||
return self._all_related_objects
|
||||
def get_all_related_objects(self, local_only=False):
|
||||
try:
|
||||
self._related_objects_cache
|
||||
except AttributeError:
|
||||
rel_objs = []
|
||||
for klass in get_models():
|
||||
for f in klass._meta.fields:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
rel_objs.append(RelatedObject(f.rel.to, klass, f))
|
||||
self._all_related_objects = rel_objs
|
||||
return rel_objs
|
||||
self._fill_related_objects_cache()
|
||||
if local_only:
|
||||
return [k for k, v in self._related_objects_cache.items() if not v]
|
||||
return self._related_objects_cache.keys()
|
||||
|
||||
def get_all_related_objects_with_model(self):
|
||||
"""
|
||||
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()
|
||||
return self._related_objects_cache.items()
|
||||
|
||||
def _fill_related_objects_cache(self):
|
||||
cache = SortedDict()
|
||||
parent_list = self.get_parent_list()
|
||||
for parent in self.parents:
|
||||
for obj, model in parent._meta.get_all_related_objects_with_model():
|
||||
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
|
||||
for klass in get_models():
|
||||
for f in klass._meta.local_fields:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
cache[RelatedObject(f.rel.to, klass, f)] = None
|
||||
self._related_objects_cache = cache
|
||||
|
||||
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 cache.keys()
|
||||
|
||||
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 cache.items()
|
||||
|
||||
def _fill_related_many_to_many_cache(self):
|
||||
cache = SortedDict()
|
||||
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 get_models():
|
||||
for f in klass._meta.local_many_to_many:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
cache[RelatedObject(f.rel.to, klass, f)] = None
|
||||
if app_cache_ready():
|
||||
self._related_many_to_many_cache = cache
|
||||
return cache
|
||||
|
||||
def get_followed_related_objects(self, follow=None):
|
||||
if follow == None:
|
||||
@@ -179,18 +391,34 @@ class Options(object):
|
||||
follow[f.name] = fol
|
||||
return follow
|
||||
|
||||
def get_all_related_many_to_many_objects(self):
|
||||
try: # Try the cache first.
|
||||
return self._all_related_many_to_many_objects
|
||||
except AttributeError:
|
||||
rel_objs = []
|
||||
for klass in get_models():
|
||||
for f in klass._meta.many_to_many:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
rel_objs.append(RelatedObject(f.rel.to, klass, f))
|
||||
if app_cache_ready():
|
||||
self._all_related_many_to_many_objects = rel_objs
|
||||
return rel_objs
|
||||
def get_base_chain(self, model):
|
||||
"""
|
||||
Returns a list of parent classes leading to 'model' (order from closet
|
||||
to most distant ancestor). This has to handle the case were 'model' is
|
||||
a granparent or even more distant relation.
|
||||
"""
|
||||
if not self.parents:
|
||||
return
|
||||
if model in self.parents:
|
||||
return [model]
|
||||
for parent in self.parents:
|
||||
res = parent._meta.get_base_chain(model)
|
||||
if res:
|
||||
res.insert(0, parent)
|
||||
return res
|
||||
raise TypeError('%r is not an ancestor of this model'
|
||||
% model._meta.module_name)
|
||||
|
||||
def get_parent_list(self):
|
||||
"""
|
||||
Returns a list of all the ancestor of this model as a list. Useful for
|
||||
determining if something is an ancestor, regardless of lineage.
|
||||
"""
|
||||
result = set()
|
||||
for parent in self.parents:
|
||||
result.add(parent)
|
||||
result.update(parent._meta.get_parent_list())
|
||||
return result
|
||||
|
||||
def get_ordered_objects(self):
|
||||
"Returns a list of Options objects that are ordered with respect to this object."
|
||||
|
||||
Reference in New Issue
Block a user