diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index f0b5b1c..a91ef81 100644
a
|
b
|
def get_validation_errors(outfile, app=None):
|
32 | 32 | |
33 | 33 | for cls in models.get_models(app): |
34 | 34 | opts = cls._meta |
| 35 | # Keep record of used field names. Shadowing field names in inheritance |
| 36 | # cases is not allowed as this will lead to buggy behavior. See ticket |
| 37 | # #17673 for details. |
| 38 | # It is also possible to define clashing names inside a single model: |
| 39 | # f = models.ForeignKey() |
| 40 | # f_id = models.IntegerField |
| 41 | # => f.attname clashes with f_id.name. |
| 42 | used_fields = {} # name or attname -> field |
| 43 | for parent in opts.parents: |
| 44 | for f in parent._meta.local_fields: |
| 45 | # Check that multi-inheritance doesn't cause field name |
| 46 | # shadowing. |
| 47 | clash = used_fields.get(f.name) or used_fields.get(f.attname) or None |
| 48 | if clash: |
| 49 | e.add(opts, 'The field "%s" from parent model "%s" clashes with the field ' |
| 50 | '"%s" from another parent model "%s"' % ( |
| 51 | f.name, f.model._meta, clash.name, clash.model._meta |
| 52 | )) |
| 53 | used_fields[f.name] = f |
| 54 | used_fields[f.attname] = f |
35 | 55 | |
36 | 56 | # Do field-specific validation. |
37 | 57 | for f in opts.local_fields: |
| 58 | clash = used_fields.get(f.name) or used_fields.get(f.attname) or None |
| 59 | if clash: |
| 60 | e.add(opts, '"%s": This field clashes with field "%s" from "%s"' % |
| 61 | (f.name, clash.name, clash.model._meta)) |
| 62 | used_fields[f.name] = f |
| 63 | used_fields[f.attname] = f |
| 64 | if f.name == 'pk': |
| 65 | e.add(opts, '"%s": You can\'t use "pk" as a field name. It is a reserved name.' % |
| 66 | f.name) |
| 67 | if '__' in f.name: |
| 68 | e.add(opts, '"%s": Field\'s name must not contain "__".' % f.name) |
38 | 69 | if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': |
39 | 70 | e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name) |
40 | 71 | if f.name.endswith('_'): |
diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py
index ed69fb6..a870417 100644
a
|
b
|
class OrderByPKModel(models.Model):
|
243 | 243 | class Meta: |
244 | 244 | ordering = ('pk',) |
245 | 245 | |
| 246 | class InvalidFieldNames(models.Model): |
| 247 | pk = models.IntegerField() |
| 248 | some__field = models.IntegerField() |
| 249 | |
| 250 | class Parent1(models.Model): |
| 251 | somef_id = models.IntegerField() |
| 252 | someotherf = models.IntegerField() |
| 253 | |
| 254 | class Parent2(models.Model): |
| 255 | somef_id = models.IntegerField() |
| 256 | |
| 257 | class Child1(Parent1): |
| 258 | somef = models.ForeignKey(Parent2) |
| 259 | |
| 260 | class MultiInheritanceClash(Parent1, Parent2): |
| 261 | # Here we have two clashed: id (automatic field) and somef, because |
| 262 | # both parents define these fields. |
| 263 | pass |
| 264 | |
| 265 | class InternalClashingNames(models.Model): |
| 266 | # fk.attname must not clash with fk_id.name |
| 267 | fk = models.ForeignKey(Parent1) |
| 268 | fk_id = models.IntegerField() |
| 269 | |
246 | 270 | model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. |
247 | 271 | invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. |
248 | 272 | invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. |
… |
… |
invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have
|
351 | 375 | invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. |
352 | 376 | invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null. |
353 | 377 | invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value. |
| 378 | invalid_models.invalidfieldnames: "pk": You can't use "pk" as a field name. It is a reserved name. |
| 379 | invalid_models.invalidfieldnames: "some__field": Field's name must not contain "__". |
| 380 | invalid_models.child1: "somef": This field clashes with field "somef_id" from "invalid_models.parent1" |
| 381 | invalid_models.multiinheritanceclash: The field "id" from parent model "invalid_models.parent2" clashes with the field "id" from another parent model "invalid_models.parent1" |
| 382 | invalid_models.multiinheritanceclash: The field "somef_id" from parent model "invalid_models.parent2" clashes with the field "somef_id" from another parent model "invalid_models.parent1" |
| 383 | invalid_models.internalclashingnames: "fk_id": This field clashes with field "fk" from "invalid_models.internalclashingnames" |
354 | 384 | """ |
355 | 385 | |
356 | 386 | if not connection.features.interprets_empty_strings_as_nulls: |
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index a0fed8a..d2afce7 100644
a
|
b
|
class Student(CommonInfo):
|
38 | 38 | class Meta: |
39 | 39 | pass |
40 | 40 | |
41 | | class StudentWorker(Student, Worker): |
42 | | pass |
| 41 | # This is invalid. The parent student and worker can have different names, |
| 42 | # ages and most of all different id. However, the child model can have just |
| 43 | # single name, age and id. So, what you get in Python doesn't match what you |
| 44 | # have in the DB. |
| 45 | #class StudentWorker(Student, Worker): |
| 46 | # pass |
43 | 47 | |
44 | 48 | # |
45 | 49 | # Abstract base classes with related models |
diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
index 2e1a7a5..19cda4c 100644
a
|
b
|
from django.core.exceptions import FieldError
|
6 | 6 | from django.test import TestCase |
7 | 7 | |
8 | 8 | from .models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, |
9 | | Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel) |
| 9 | Post, Restaurant, Student, Supplier, Worker, MixinModel) |
10 | 10 | |
11 | 11 | |
12 | 12 | class ModelInheritanceTests(TestCase): |
… |
… |
class ModelInheritanceTests(TestCase):
|
45 | 45 | |
46 | 46 | # A StudentWorker which does not exist is both a Student and Worker |
47 | 47 | # which does not exist. |
48 | | self.assertRaises(Student.DoesNotExist, |
49 | | StudentWorker.objects.get, pk=12321321 |
50 | | ) |
51 | | self.assertRaises(Worker.DoesNotExist, |
52 | | StudentWorker.objects.get, pk=12321321 |
53 | | ) |
| 48 | #self.assertRaises(Student.DoesNotExist, |
| 49 | # StudentWorker.objects.get, pk=12321321 |
| 50 | #) |
| 51 | #self.assertRaises(Worker.DoesNotExist, |
| 52 | # StudentWorker.objects.get, pk=12321321 |
| 53 | #) |
54 | 54 | |
55 | 55 | # MultipleObjectsReturned is also inherited. |
56 | 56 | # This is written out "long form", rather than using __init__/create() |
57 | 57 | # because of a bug with diamond inheritance (#10808) |
58 | | sw1 = StudentWorker() |
59 | | sw1.name = "Wilma" |
60 | | sw1.age = 35 |
61 | | sw1.save() |
62 | | sw2 = StudentWorker() |
63 | | sw2.name = "Betty" |
64 | | sw2.age = 24 |
65 | | sw2.save() |
66 | | |
67 | | self.assertRaises(Student.MultipleObjectsReturned, |
68 | | StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
69 | | ) |
70 | | self.assertRaises(Worker.MultipleObjectsReturned, |
71 | | StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
72 | | ) |
| 58 | #sw1 = StudentWorker() |
| 59 | #sw1.name = "Wilma" |
| 60 | #sw1.age = 35 |
| 61 | #sw1.save() |
| 62 | #sw2 = StudentWorker() |
| 63 | #sw2.name = "Betty" |
| 64 | #sw2.age = 24 |
| 65 | #sw2.save() |
| 66 | |
| 67 | #self.assertRaises(Student.MultipleObjectsReturned, |
| 68 | # StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
| 69 | #) |
| 70 | #self.assertRaises(Worker.MultipleObjectsReturned, |
| 71 | # StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
| 72 | #) |
73 | 73 | |
74 | 74 | def test_multiple_table(self): |
75 | 75 | post = Post.objects.create(title="Lorem Ipsum") |