Ticket #17673: 17673_v2.diff

File 17673_v2.diff, 7.9 KB (added by Christopher Medrela, 12 years ago)

akaariai's patch with my small improvements

  • django/core/management/validation.py

    diff --git a/django/core/management/validation.py b/django/core/management/validation.py
    index f0b5b1c..56708e1 100644
    a b  
    11import sys
    22
    33from django.core.management.color import color_style
     4from django.db.models.sql.constants import LOOKUP_SEP
    45from django.utils.itercompat import is_iterable
    56
     7
    68class ModelErrorCollection:
    79    def __init__(self, outfile=sys.stdout):
    810        self.errors = []
    def get_validation_errors(outfile, app=None):  
    3234
    3335    for cls in models.get_models(app):
    3436        opts = cls._meta
     37        # Keep record of used field names. Clashing names inside a single model
     38        # (example: `book` and `book_id`) and shadowing field names in inheritance
     39        # cases is not allowed as this will lead to buggy behavior. See ticket
     40        # #17673 for details.
     41        used_fields = {} # name or attname -> field
     42
     43        # Check that multi-inheritance doesn't cause field name
     44        # shadowing.
     45        for parent in opts.parents:
     46            for f in parent._meta.local_fields:
     47                clashing_field = (used_fields.get(f.name) or
     48                                  used_fields.get(f.attname) or
     49                                  None)
     50                if clashing_field:
     51                    e.add(opts, 'The field "%s" from parent model "%s" clashes with the field '
     52                                '"%s" from another parent model "%s"' % (
     53                                    f.name, f.model._meta, clashing_field.name,
     54                                    clashing_field.model._meta))
     55                used_fields[f.name] = f
     56                used_fields[f.attname] = f
    3557
    3658        # Do field-specific validation.
    3759        for f in opts.local_fields:
     60            clashing_field = (used_fields.get(f.name) or
     61                              used_fields.get(f.attname) or
     62                              None)
     63            if clashing_field:
     64                e.add(opts, '"%s": This field clashes with field "%s" from "%s"' % (
     65                                f.name, clashing_field.name, clashing_field.model._meta))
     66            used_fields[f.name] = f
     67            used_fields[f.attname] = f
     68            if f.name == 'pk':
     69                e.add(opts, '"%s": You can\'t use "pk" as a field name. '
     70                            'It is a reserved name.' % f.name)
     71            if LOOKUP_SEP in f.name:
     72                e.add(opts, '"%s": Field\'s name must not contain "%s".' % (
     73                                f.name, LOOKUP_SEP))
    3874            if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
    3975                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)
    4076            if f.name.endswith('_'):
  • tests/modeltests/invalid_models/invalid_models/models.py

    diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py
    index ed69fb6..9f9ae40 100644
    a b class OrderByPKModel(models.Model):  
    243243    class Meta:
    244244        ordering = ('pk',)
    245245
     246class InvalidFieldNames(models.Model):
     247    pk = models.IntegerField()
     248    some__field = models.IntegerField()
     249
     250class FirstParent(models.Model):
     251    somef_id = models.IntegerField()
     252    someotherf = models.IntegerField()
     253
     254class SecondParent(models.Model):
     255    somef_id = models.IntegerField()
     256
     257class ChildShadowingField(FirstParent):
     258    somef = models.ForeignKey(SecondParent)
     259
     260class MultiInheritanceClash(FirstParent, SecondParent):
     261    # Here we have two clashed: id (automatic field) and somef, because
     262    # both parents define these fields.
     263    pass
     264
     265class InternalClashingNames(models.Model):
     266    # fk.attname must not clash with fk_id.name
     267    fk = models.ForeignKey(FirstParent)
     268    fk_id = models.IntegerField()
     269
    246270model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer.
    247271invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer.
    248272invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer.
    invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have  
    351375invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist.
    352376invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null.
    353377invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value.
     378invalid_models.invalidfieldnames: "pk": You can't use "pk" as a field name. It is a reserved name.
     379invalid_models.invalidfieldnames: "some__field": Field's name must not contain "__".
     380invalid_models.childshadowingfield: "somef": This field clashes with field "somef_id" from "invalid_models.firstparent"
     381invalid_models.multiinheritanceclash: The field "id" from parent model "invalid_models.secondparent" clashes with the field "id" from another parent model "invalid_models.firstparent"
     382invalid_models.multiinheritanceclash: The field "somef_id" from parent model "invalid_models.secondparent" clashes with the field "somef_id" from another parent model "invalid_models.firstparent"
     383invalid_models.internalclashingnames: "fk_id": This field clashes with field "fk" from "invalid_models.internalclashingnames"
    354384"""
    355385
    356386if not connection.features.interprets_empty_strings_as_nulls:
  • tests/modeltests/model_inheritance/models.py

    diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
    index a0fed8a..3435d6a 100644
    a b class Student(CommonInfo):  
    3838    class Meta:
    3939        pass
    4040
    41 class StudentWorker(Student, Worker):
    42     pass
    43 
    4441#
    4542# Abstract base classes with related models
    4643#
  • tests/modeltests/model_inheritance/tests.py

    diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
    index 2e1a7a5..0123a6b 100644
    a b from django.core.exceptions import FieldError  
    66from django.test import TestCase
    77
    88from .models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place,
    9     Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel)
     9    Post, Restaurant, Student, Supplier, Worker, MixinModel)
    1010
    1111
    1212class ModelInheritanceTests(TestCase):
    class ModelInheritanceTests(TestCase):  
    4343        # doesn't exist as a model).
    4444        self.assertRaises(AttributeError, lambda: CommonInfo.objects.all())
    4545
    46         # A StudentWorker which does not exist is both a Student and Worker
    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         )
    54 
    55         # MultipleObjectsReturned is also inherited.
    56         # This is written out "long form", rather than using __init__/create()
    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         )
    73 
    7446    def test_multiple_table(self):
    7547        post = Post.objects.create(title="Lorem Ipsum")
    7648        # The Post model has distinct accessors for the Comment and Link models.
Back to Top