summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorChristopher Medrela <chris.medrela@gmail.com>2014-02-07 22:34:56 +0100
committerTim Graham <timograham@gmail.com>2014-02-10 10:04:19 -0500
commitee9fcb1672ddf5910ed8c45c37a00f32ebbe2bb1 (patch)
tree5032ef2dc44c9c50b251adeb3506530e93e147e0 /django
parentf5123c7291e855713b59e739bad0e50a1d50d3ef (diff)
Fixed #17673 -- Forbid field shadowing.
Thanks Anssi Kääriäinen for the suggestion.
Diffstat (limited to 'django')
-rw-r--r--django/db/models/base.py63
-rw-r--r--django/db/models/fields/__init__.py23
2 files changed, 82 insertions, 4 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 4b8aededa9..6270e35b37 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1055,8 +1055,12 @@ class Model(six.with_metaclass(ModelBase)):
if not cls._meta.swapped:
errors.extend(cls._check_fields(**kwargs))
errors.extend(cls._check_m2m_through_same_relationship())
- errors.extend(cls._check_id_field())
- errors.extend(cls._check_column_name_clashes())
+ clash_errors = cls._check_id_field() + cls._check_field_name_clashes()
+ errors.extend(clash_errors)
+ # If there are field name clashes, hide consequent column name
+ # clashes.
+ if not clash_errors:
+ errors.extend(cls._check_column_name_clashes())
errors.extend(cls._check_index_together())
errors.extend(cls._check_unique_together())
errors.extend(cls._check_ordering())
@@ -1176,6 +1180,61 @@ class Model(six.with_metaclass(ModelBase)):
return []
@classmethod
+ def _check_field_name_clashes(cls):
+ """ Ref #17673. """
+
+ errors = []
+ used_fields = {} # name or attname -> field
+
+ # Check that multi-inheritance doesn't cause field name shadowing.
+ for parent in cls._meta.parents:
+ for f in parent._meta.local_fields:
+ clash = used_fields.get(f.name) or used_fields.get(f.attname) or None
+ if clash:
+ errors.append(
+ checks.Error(
+ ('The field "%s" from parent model '
+ '%s clashes with the field "%s" '
+ 'from parent model %s.') % (
+ clash.name, clash.model._meta,
+ f.name, f.model._meta
+ ),
+ hint=None,
+ obj=cls,
+ id='E053',
+ )
+ )
+ used_fields[f.name] = f
+ used_fields[f.attname] = f
+
+ # Check that fields defined in the model don't clash with fields from
+ # parents.
+ for f in cls._meta.local_fields:
+ clash = used_fields.get(f.name) or used_fields.get(f.attname) or None
+ # Note that we may detect clash between user-defined non-unique
+ # field "id" and automatically added unique field "id", both
+ # defined at the same model. This special case is considered in
+ # _check_id_field and here we ignore it.
+ id_conflict = (f.name == "id" and
+ clash and clash.name == "id" and clash.model == cls)
+ if clash and not id_conflict:
+ errors.append(
+ checks.Error(
+ ('The field clashes with the field "%s" '
+ 'from model %s.') % (
+ clash.name, clash.model._meta
+ ),
+ hint=None,
+ obj=f,
+ id='E054',
+ )
+ )
+ used_fields[f.name] = f
+ used_fields[f.attname] = f
+
+ return errors
+
+ @classmethod
def _check_column_name_clashes(cls):
# Store a list of column names which have already been used by other fields.
used_column_names = []
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 4454429997..77a48d0723 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -191,8 +191,9 @@ class Field(RegisterLookupMixin):
return errors
def _check_field_name(self):
- """ Check if field name is valid (i. e. not ending with an underscore).
- """
+ """ Check if field name is valid, i.e. 1) does not end with an
+ underscore, 2) does not contain "__" and 3) is not "pk". """
+
if self.name.endswith('_'):
return [
checks.Error(
@@ -202,6 +203,24 @@ class Field(RegisterLookupMixin):
id='E001',
)
]
+ elif '__' in self.name:
+ return [
+ checks.Error(
+ 'Field names must not contain "__".',
+ hint=None,
+ obj=self,
+ id='E052',
+ )
+ ]
+ elif self.name == 'pk':
+ return [
+ checks.Error(
+ 'Cannot use "pk" as a field name since it is a reserved name.',
+ hint=None,
+ obj=self,
+ id='E051',
+ )
+ ]
else:
return []