mirror of https://github.com/django/django.git
Refs #16508 -- Renamed the current "virtual" fields to "private".
The only reason why GenericForeignKey and GenericRelation are stored separately inside _meta is that they need to be cloned for every model subclass, but that's not true for any other virtual field. Actually, it's only true for GenericRelation.
This commit is contained in:
parent
47fbbc33de
commit
c339a5a6f7
|
@ -24,7 +24,7 @@ class GenericInlineModelAdminChecks(InlineModelAdminChecks):
|
||||||
# and that they are part of a GenericForeignKey.
|
# and that they are part of a GenericForeignKey.
|
||||||
|
|
||||||
gfks = [
|
gfks = [
|
||||||
f for f in obj.model._meta.virtual_fields
|
f for f in obj.model._meta.private_fields
|
||||||
if isinstance(f, GenericForeignKey)
|
if isinstance(f, GenericForeignKey)
|
||||||
]
|
]
|
||||||
if len(gfks) == 0:
|
if len(gfks) == 0:
|
||||||
|
|
|
@ -53,7 +53,7 @@ class GenericForeignKey(object):
|
||||||
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_field(self, virtual=True)
|
cls._meta.add_field(self, private=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:
|
||||||
|
@ -331,7 +331,7 @@ class GenericRelation(ForeignObject):
|
||||||
def _check_generic_foreign_key_existence(self):
|
def _check_generic_foreign_key_existence(self):
|
||||||
target = self.remote_field.model
|
target = self.remote_field.model
|
||||||
if isinstance(target, ModelBase):
|
if isinstance(target, ModelBase):
|
||||||
fields = target._meta.virtual_fields
|
fields = target._meta.private_fields
|
||||||
if any(isinstance(field, GenericForeignKey) and
|
if any(isinstance(field, GenericForeignKey) and
|
||||||
field.ct_field == self.content_type_field_name and
|
field.ct_field == self.content_type_field_name and
|
||||||
field.fk_field == self.object_id_field_name
|
field.fk_field == self.object_id_field_name
|
||||||
|
@ -409,7 +409,7 @@ class GenericRelation(ForeignObject):
|
||||||
return smart_text([instance._get_pk_val() for instance in qs])
|
return smart_text([instance._get_pk_val() for instance in qs])
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, **kwargs):
|
def contribute_to_class(self, cls, name, **kwargs):
|
||||||
kwargs['virtual_only'] = True
|
kwargs['private_only'] = True
|
||||||
super(GenericRelation, self).contribute_to_class(cls, name, **kwargs)
|
super(GenericRelation, self).contribute_to_class(cls, name, **kwargs)
|
||||||
self.model = cls
|
self.model = cls
|
||||||
setattr(cls, self.name, ReverseGenericManyToOneDescriptor(self.remote_field))
|
setattr(cls, self.name, ReverseGenericManyToOneDescriptor(self.remote_field))
|
||||||
|
|
|
@ -161,7 +161,7 @@ class ModelBase(type):
|
||||||
new_fields = chain(
|
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.private_fields
|
||||||
)
|
)
|
||||||
field_names = {f.name for f in new_fields}
|
field_names = {f.name for f in new_fields}
|
||||||
|
|
||||||
|
@ -268,9 +268,9 @@ class ModelBase(type):
|
||||||
if is_proxy:
|
if is_proxy:
|
||||||
new_class.copy_managers(original_base._meta.concrete_managers)
|
new_class.copy_managers(original_base._meta.concrete_managers)
|
||||||
|
|
||||||
# Inherit virtual fields (like GenericForeignKey) from the parent
|
# Inherit private fields (like GenericForeignKey) from the parent
|
||||||
# class
|
# class
|
||||||
for field in base._meta.virtual_fields:
|
for field in base._meta.private_fields:
|
||||||
if base._meta.abstract and field.name in field_names:
|
if base._meta.abstract and field.name in field_names:
|
||||||
raise FieldError(
|
raise FieldError(
|
||||||
'Local field %r in class %r clashes '
|
'Local field %r in class %r clashes '
|
||||||
|
|
|
@ -147,7 +147,7 @@ class Collector(object):
|
||||||
for related in get_candidate_relations_to_delete(opts):
|
for related in get_candidate_relations_to_delete(opts):
|
||||||
if related.field.remote_field.on_delete is not DO_NOTHING:
|
if related.field.remote_field.on_delete is not DO_NOTHING:
|
||||||
return False
|
return False
|
||||||
for field in model._meta.virtual_fields:
|
for field in model._meta.private_fields:
|
||||||
if hasattr(field, 'bulk_related_objects'):
|
if hasattr(field, 'bulk_related_objects'):
|
||||||
# It's something like generic foreign key.
|
# It's something like generic foreign key.
|
||||||
return False
|
return False
|
||||||
|
@ -221,7 +221,7 @@ class Collector(object):
|
||||||
self.fast_deletes.append(sub_objs)
|
self.fast_deletes.append(sub_objs)
|
||||||
elif sub_objs:
|
elif sub_objs:
|
||||||
field.remote_field.on_delete(self, field, sub_objs, self.using)
|
field.remote_field.on_delete(self, field, sub_objs, self.using)
|
||||||
for field in model._meta.virtual_fields:
|
for field in model._meta.private_fields:
|
||||||
if hasattr(field, 'bulk_related_objects'):
|
if hasattr(field, 'bulk_related_objects'):
|
||||||
# It's something like generic foreign key.
|
# It's something like generic foreign key.
|
||||||
sub_objs = field.bulk_related_objects(new_objs, self.using)
|
sub_objs = field.bulk_related_objects(new_objs, self.using)
|
||||||
|
|
|
@ -672,11 +672,25 @@ class Field(RegisterLookupMixin):
|
||||||
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('_', ' ')
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, virtual_only=False):
|
def contribute_to_class(self, cls, name, private_only=False, virtual_only=NOT_PROVIDED):
|
||||||
|
"""
|
||||||
|
Register the field with the model class it belongs to.
|
||||||
|
|
||||||
|
If private_only is True, a separate instance of this field will be
|
||||||
|
created for every subclass of cls, even if cls is not an abstract
|
||||||
|
model.
|
||||||
|
"""
|
||||||
|
if virtual_only is not NOT_PROVIDED:
|
||||||
|
warnings.warn(
|
||||||
|
"The `virtual_only` argument of Field.contribute_to_class() "
|
||||||
|
"has been renamed to `private_only`.",
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
private_only = virtual_only
|
||||||
self.set_attributes_from_name(name)
|
self.set_attributes_from_name(name)
|
||||||
self.model = cls
|
self.model = cls
|
||||||
if virtual_only:
|
if private_only:
|
||||||
cls._meta.add_field(self, virtual=True)
|
cls._meta.add_field(self, private=True)
|
||||||
else:
|
else:
|
||||||
cls._meta.add_field(self)
|
cls._meta.add_field(self)
|
||||||
if self.choices:
|
if self.choices:
|
||||||
|
|
|
@ -282,9 +282,9 @@ class RelatedField(Field):
|
||||||
# columns from another table.
|
# columns from another table.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, virtual_only=False):
|
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
|
||||||
|
|
||||||
super(RelatedField, self).contribute_to_class(cls, name, virtual_only=virtual_only)
|
super(RelatedField, self).contribute_to_class(cls, name, private_only=private_only, **kwargs)
|
||||||
|
|
||||||
self.opts = cls._meta
|
self.opts = cls._meta
|
||||||
|
|
||||||
|
@ -703,8 +703,8 @@ class ForeignObject(RelatedField):
|
||||||
def get_defaults(self):
|
def get_defaults(self):
|
||||||
return tuple(field.get_default() for field in self.local_related_fields)
|
return tuple(field.get_default() for field in self.local_related_fields)
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, virtual_only=False):
|
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
|
||||||
super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
|
super(ForeignObject, self).contribute_to_class(cls, name, private_only=private_only, **kwargs)
|
||||||
setattr(cls, self.name, ForwardManyToOneDescriptor(self))
|
setattr(cls, self.name, ForwardManyToOneDescriptor(self))
|
||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import warnings
|
||||||
from bisect import bisect
|
from bisect import bisect
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
@ -12,6 +13,9 @@ 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, OrderedSet
|
from django.utils.datastructures import ImmutableList, OrderedSet
|
||||||
|
from django.utils.deprecation import (
|
||||||
|
RemovedInDjango20Warning, warn_about_renamed_method,
|
||||||
|
)
|
||||||
from django.utils.encoding import (
|
from django.utils.encoding import (
|
||||||
force_text, python_2_unicode_compatible, smart_text,
|
force_text, python_2_unicode_compatible, smart_text,
|
||||||
)
|
)
|
||||||
|
@ -19,6 +23,8 @@ from django.utils.functional import cached_property
|
||||||
from django.utils.text import camel_case_to_spaces
|
from django.utils.text import camel_case_to_spaces
|
||||||
from django.utils.translation import override, string_concat
|
from django.utils.translation import override, string_concat
|
||||||
|
|
||||||
|
NOT_PROVIDED = object()
|
||||||
|
|
||||||
PROXY_PARENTS = object()
|
PROXY_PARENTS = object()
|
||||||
|
|
||||||
EMPTY_RELATION_TREE = tuple()
|
EMPTY_RELATION_TREE = tuple()
|
||||||
|
@ -76,7 +82,7 @@ class Options(object):
|
||||||
self._get_fields_cache = {}
|
self._get_fields_cache = {}
|
||||||
self.local_fields = []
|
self.local_fields = []
|
||||||
self.local_many_to_many = []
|
self.local_many_to_many = []
|
||||||
self.virtual_fields = []
|
self.private_fields = []
|
||||||
self.model_name = None
|
self.model_name = None
|
||||||
self.verbose_name = None
|
self.verbose_name = None
|
||||||
self.verbose_name_plural = None
|
self.verbose_name_plural = None
|
||||||
|
@ -253,13 +259,19 @@ class Options(object):
|
||||||
auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True)
|
auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True)
|
||||||
model.add_to_class('id', auto)
|
model.add_to_class('id', auto)
|
||||||
|
|
||||||
def add_field(self, field, virtual=False):
|
def add_field(self, field, private=False, virtual=NOT_PROVIDED):
|
||||||
|
if virtual is not NOT_PROVIDED:
|
||||||
|
warnings.warn(
|
||||||
|
"The `virtual` argument of Options.add_field() has been renamed to `private`.",
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
private = virtual
|
||||||
# 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 virtual:
|
if private:
|
||||||
self.virtual_fields.append(field)
|
self.private_fields.append(field)
|
||||||
elif field.is_relation and field.many_to_many:
|
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)
|
||||||
else:
|
else:
|
||||||
|
@ -365,7 +377,7 @@ class Options(object):
|
||||||
obtaining this field list.
|
obtaining this field list.
|
||||||
"""
|
"""
|
||||||
# For legacy reasons, the fields property should only contain forward
|
# For legacy reasons, the fields property should only contain forward
|
||||||
# fields that are not virtual or with a m2m cardinality. Therefore we
|
# fields that are not private or with a m2m cardinality. Therefore we
|
||||||
# pass these three filters as filters to the generator.
|
# pass these three filters as filters to the generator.
|
||||||
# The third lambda is a longwinded way of checking f.related_model - we don't
|
# 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,
|
# use that property directly because related_model is a cached property,
|
||||||
|
@ -401,6 +413,14 @@ class Options(object):
|
||||||
"concrete_fields", (f for f in self.fields if f.concrete)
|
"concrete_fields", (f for f in self.fields if f.concrete)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@warn_about_renamed_method(
|
||||||
|
'Options', 'virtual_fields', 'private_fields',
|
||||||
|
RemovedInDjango20Warning
|
||||||
|
)
|
||||||
|
def virtual_fields(self):
|
||||||
|
return self.private_fields
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def local_concrete_fields(self):
|
def local_concrete_fields(self):
|
||||||
"""
|
"""
|
||||||
|
@ -686,14 +706,14 @@ class Options(object):
|
||||||
fields.extend(
|
fields.extend(
|
||||||
field for field in chain(self.local_fields, self.local_many_to_many)
|
field for field in chain(self.local_fields, self.local_many_to_many)
|
||||||
)
|
)
|
||||||
# Virtual fields are recopied to each child model, and they get a
|
# Private fields are recopied to each child model, and they get a
|
||||||
# different model as field.model in each child. Hence we have to
|
# different model as field.model in each child. Hence we have to
|
||||||
# add the virtual fields separately from the topmost call. If we
|
# add the private fields separately from the topmost call. If we
|
||||||
# did this recursively similar to local_fields, we would get field
|
# did this recursively similar to local_fields, we would get field
|
||||||
# instances with field.model != self.model.
|
# instances with field.model != self.model.
|
||||||
if topmost_call:
|
if topmost_call:
|
||||||
fields.extend(
|
fields.extend(
|
||||||
f for f in self.virtual_fields
|
f for f in self.private_fields
|
||||||
)
|
)
|
||||||
|
|
||||||
# In order to avoid list manipulation. Always
|
# In order to avoid list manipulation. Always
|
||||||
|
|
|
@ -81,7 +81,7 @@ def model_to_dict(instance, fields=None, exclude=None):
|
||||||
"""
|
"""
|
||||||
opts = instance._meta
|
opts = instance._meta
|
||||||
data = {}
|
data = {}
|
||||||
for f in chain(opts.concrete_fields, opts.virtual_fields, opts.many_to_many):
|
for f in chain(opts.concrete_fields, opts.private_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:
|
||||||
|
@ -142,9 +142,8 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
# Avoid circular import
|
# Avoid circular import
|
||||||
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_private_fields = [f for f in opts.private_fields if isinstance(f, ModelField)]
|
||||||
if isinstance(f, ModelField)]
|
for f in sorted(chain(opts.concrete_fields, sortable_private_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):
|
||||||
if (fields is not None and f.name in fields and
|
if (fields is not None and f.name in fields and
|
||||||
(exclude is None or f.name not in exclude)):
|
(exclude is None or f.name not in exclude)):
|
||||||
|
@ -431,9 +430,9 @@ class BaseModelForm(BaseForm):
|
||||||
fields = self._meta.fields
|
fields = self._meta.fields
|
||||||
opts = self.instance._meta
|
opts = self.instance._meta
|
||||||
# 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
|
# private_fields here. (GenericRelation was previously a fake
|
||||||
# m2m field).
|
# m2m field).
|
||||||
for f in chain(opts.many_to_many, opts.virtual_fields):
|
for f in chain(opts.many_to_many, opts.private_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:
|
||||||
|
|
|
@ -150,6 +150,12 @@ details on these changes.
|
||||||
* Using ``User.is_authenticated()`` and ``User.is_anonymous()`` as methods
|
* Using ``User.is_authenticated()`` and ``User.is_anonymous()`` as methods
|
||||||
will no longer be supported.
|
will no longer be supported.
|
||||||
|
|
||||||
|
* The private attribute ``virtual_fields`` of ``Model._meta`` will be removed.
|
||||||
|
|
||||||
|
* The private keyword arguments ``virtual_only`` in
|
||||||
|
``Field.contribute_to_class()`` and ``virtual`` in
|
||||||
|
``Model._meta.add_field()`` will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.10:
|
.. _deprecation-removed-in-1.10:
|
||||||
|
|
||||||
1.10
|
1.10
|
||||||
|
|
|
@ -921,6 +921,14 @@ Miscellaneous
|
||||||
|
|
||||||
* The template ``Context.has_key()`` method is deprecated in favor of ``in``.
|
* The template ``Context.has_key()`` method is deprecated in favor of ``in``.
|
||||||
|
|
||||||
|
* The private attribute ``virtual_fields`` of ``Model._meta`` is
|
||||||
|
deprecated in favor of ``private_fields``.
|
||||||
|
|
||||||
|
* The private keyword arguments ``virtual_only`` in
|
||||||
|
``Field.contribute_to_class()`` and ``virtual`` in
|
||||||
|
``Model._meta.add_field()`` are deprecated in favor of ``private_only``
|
||||||
|
and ``private``, respectively.
|
||||||
|
|
||||||
.. _removed-features-1.10:
|
.. _removed-features-1.10:
|
||||||
|
|
||||||
Features removed in 1.10
|
Features removed in 1.10
|
||||||
|
|
|
@ -64,8 +64,8 @@ class StartsWithRelation(models.ForeignObject):
|
||||||
from_opts = self.remote_field.model._meta
|
from_opts = self.remote_field.model._meta
|
||||||
return [PathInfo(from_opts, to_opts, (to_opts.pk,), self.remote_field, False, False)]
|
return [PathInfo(from_opts, to_opts, (to_opts.pk,), self.remote_field, False, False)]
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, virtual_only=False):
|
def contribute_to_class(self, cls, name, private_only=False):
|
||||||
super(StartsWithRelation, self).contribute_to_class(cls, name, virtual_only)
|
super(StartsWithRelation, self).contribute_to_class(cls, name, private_only)
|
||||||
setattr(cls, self.name, ReverseManyToOneDescriptor(self))
|
setattr(cls, self.name, ReverseManyToOneDescriptor(self))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -78,13 +78,13 @@ class FieldFlagsTests(test.SimpleTestCase):
|
||||||
super(FieldFlagsTests, cls).setUpClass()
|
super(FieldFlagsTests, cls).setUpClass()
|
||||||
cls.fields = (
|
cls.fields = (
|
||||||
list(AllFieldsModel._meta.fields) +
|
list(AllFieldsModel._meta.fields) +
|
||||||
list(AllFieldsModel._meta.virtual_fields)
|
list(AllFieldsModel._meta.private_fields)
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.all_fields = (
|
cls.all_fields = (
|
||||||
cls.fields +
|
cls.fields +
|
||||||
list(AllFieldsModel._meta.many_to_many) +
|
list(AllFieldsModel._meta.many_to_many) +
|
||||||
list(AllFieldsModel._meta.virtual_fields)
|
list(AllFieldsModel._meta.private_fields)
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.fields_and_reverse_objects = (
|
cls.fields_and_reverse_objects = (
|
||||||
|
|
|
@ -863,7 +863,7 @@ TEST_RESULTS = {
|
||||||
'm2m_concrete_rel',
|
'm2m_concrete_rel',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'virtual_fields': {
|
'private_fields': {
|
||||||
AbstractPerson: [
|
AbstractPerson: [
|
||||||
'generic_relation_abstract',
|
'generic_relation_abstract',
|
||||||
'content_object_abstract',
|
'content_object_abstract',
|
||||||
|
|
|
@ -157,11 +157,11 @@ class RelatedObjectsTests(OptionsBaseTests):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VirtualFieldsTests(OptionsBaseTests):
|
class PrivateFieldsTests(OptionsBaseTests):
|
||||||
|
|
||||||
def test_virtual_fields(self):
|
def test_private_fields(self):
|
||||||
for model, expected_names in TEST_RESULTS['virtual_fields'].items():
|
for model, expected_names in TEST_RESULTS['private_fields'].items():
|
||||||
objects = model._meta.virtual_fields
|
objects = model._meta.private_fields
|
||||||
self.assertEqual(sorted([f.name for f in objects]), sorted(expected_names))
|
self.assertEqual(sorted([f.name for f in objects]), sorted(expected_names))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue