Kaydet (Commit) 85ef98dc authored tarafından Aron Podrigal's avatar Aron Podrigal Kaydeden (comit) Tim Graham

Fixed #24305 -- Allowed overriding fields on abstract models.

Fields inherited from abstract base classes may be overridden like
any other Python attribute. Inheriting from multiple models/classes
with the same attribute name will follow the MRO.
üst 61a16e02
......@@ -69,6 +69,7 @@ answer newbie questions, and generally made Django that much better:
Aram Dulyan
arien <regexbot@gmail.com>
Armin Ronacher
Aron Podrigal <aronp@guaranteedplus.com>
Artem Gnilov <boobsd@gmail.com>
Arthur <avandorp@gmail.com>
Arthur Koziel <http://arthurkoziel.com>
......
......@@ -212,26 +212,35 @@ class ModelBase(type):
if isinstance(field, OneToOneField):
related = resolve_relation(new_class, field.remote_field.model)
parent_links[make_model_tuple(related)] = field
# Track fields inherited from base models.
inherited_attributes = set()
# Do the appropriate setup for any model parents.
for base in parents:
for base in new_class.mro():
original_base = base
if not hasattr(base, '_meta'):
if base not in parents or not hasattr(base, '_meta'):
# Things without _meta aren't functional models, so they're
# uninteresting parents.
inherited_attributes |= set(base.__dict__.keys())
continue
parent_fields = base._meta.local_fields + base._meta.local_many_to_many
# Check for clashes between locally declared fields and those
# on the base classes (we cannot handle shadowed fields at the
# moment).
for field in parent_fields:
if field.name in field_names:
raise FieldError(
'Local field %r in class %r clashes '
'with field of similar name from '
'base class %r' % (field.name, name, base.__name__)
)
if not base._meta.abstract:
# Check for clashes between locally declared fields and those
# on the base classes.
for field in parent_fields:
if field.name in field_names:
raise FieldError(
'Local field %r in class %r clashes with field of '
'the same name from base class %r.' % (
field.name,
name,
base.__name__,
)
)
else:
inherited_attributes.add(field.name)
# Concrete classes...
base = base._meta.concrete_model
base_key = make_model_tuple(base)
......@@ -246,6 +255,18 @@ class ModelBase(type):
auto_created=True,
parent_link=True,
)
if attr_name in field_names:
raise FieldError(
"Auto-generated field '%s' in class %r for "
"parent_link to base class %r clashes with "
"declared field of the same name." % (
attr_name,
name,
base.__name__,
)
)
# Only add the ptr field if it's not already present;
# e.g. migrations will already have it specified
if not hasattr(new_class, attr_name):
......@@ -256,16 +277,19 @@ class ModelBase(type):
else:
base_parents = base._meta.parents.copy()
# .. and abstract ones.
# Add fields from abstract base class if it wasn't overridden.
for field in parent_fields:
new_field = copy.deepcopy(field)
new_class.add_to_class(field.name, new_field)
# Replace parent links defined on this base by the new
# field as it will be appropriately resolved if required.
if field.one_to_one:
for parent, parent_link in base_parents.items():
if field == parent_link:
base_parents[parent] = new_field
if (field.name not in field_names and
field.name not in new_class.__dict__ and
field.name not in inherited_attributes):
new_field = copy.deepcopy(field)
new_class.add_to_class(field.name, new_field)
# Replace parent links defined on this base by the new
# field. It will be appropriately resolved if required.
if field.one_to_one:
for parent, parent_link in base_parents.items():
if field == parent_link:
base_parents[parent] = new_field
# Pass any non-abstract parent classes onto child.
new_class._meta.parents.update(base_parents)
......@@ -281,13 +305,18 @@ class ModelBase(type):
# Inherit private fields (like GenericForeignKey) from the parent
# class
for field in base._meta.private_fields:
if base._meta.abstract and field.name in field_names:
raise FieldError(
'Local field %r in class %r clashes '
'with field of similar name from '
'abstract base class %r' % (field.name, name, base.__name__)
)
new_class.add_to_class(field.name, copy.deepcopy(field))
if field.name in field_names:
if not base._meta.abstract:
raise FieldError(
'Local field %r in class %r clashes with field of '
'the same name from base class %r.' % (
field.name,
name,
base.__name__,
)
)
else:
new_class.add_to_class(field.name, copy.deepcopy(field))
if abstract:
# Abstract base models can't be instantiated and don't appear in
......
......@@ -400,6 +400,8 @@ Models
app label and class interpolation using the ``'%(app_label)s'`` and
``'%(class)s'`` strings.
* Allowed overriding model fields inherited from abstract base classes.
* The :func:`~django.db.models.prefetch_related_objects` function is now a
public API.
......
......@@ -1376,11 +1376,35 @@ Field name "hiding" is not permitted
-------------------------------------
In normal Python class inheritance, it is permissible for a child class to
override any attribute from the parent class. In Django, this is not permitted
for attributes that are :class:`~django.db.models.Field` instances (at
least, not at the moment). If a base class has a field called ``author``, you
cannot create another model field called ``author`` in any class that inherits
from that base class.
override any attribute from the parent class. In Django, this isn't usually
permitted for model fields. If a non-abstract model base class has a field
called ``author``, you can't create another model field or define
an attribute called ``author`` in any class that inherits from that base class.
This restriction doesn't apply to model fields inherited from an abstract
model. Such fields may be overridden with another field or value, or be removed
by setting ``field_name = None``.
.. versionchanged:: 1.10
The ability to override abstract fields was added.
.. warning::
Model managers are inherited from abstract base classes. Overriding an
inherited field which is referenced by an inherited
:class:`~django.db.models.Manager` may cause subtle bugs. See :ref:`custom
managers and model inheritance <custom-managers-and-inheritance>`.
.. note::
Some fields define extra attributes on the model, e.g. a
:class:`~django.db.models.ForeignKey` defines an extra attribute with
``_id`` appended to the field name, as well as ``related_name`` and
``related_query_name`` on the foreign model.
These extra attributes cannot be overridden unless the field that defines
it is changed or removed so that it no longer defines the extra attribute.
Overriding fields in a parent model leads to difficulties in areas such as
initializing new instances (specifying which field is being initialized in
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment