diff --git a/typedmodels/models.py b/typedmodels/models.py index e418d5d..516eaa1 100644 --- a/typedmodels/models.py +++ b/typedmodels/models.py @@ -111,7 +111,19 @@ def do_related_class(self, other, cls): remote_field = field.remote_field if isinstance(remote_field.model, TypedModel) and remote_field.model.base_class: remote_field.limit_choices_to['type__in'] = remote_field.model._typedmodels_subtypes - field.contribute_to_class(base_class, field_name) + + # Check if a field with this name has already been added to class + try: + duplicate_field = base_class._meta.get_field(field_name) + # Check if the field being added is _exactly_ the same as the field + # that already exists. + if duplicate_field.deconstruct()[1:] != field.deconstruct()[1:]: + raise ValueError("Can't add field '%s' from '%s' to '%s', field already exists.", + field_name, classname, base_class.__name__) + # Otherwise, the field already exists on the base class. Don't add it. + except FieldDoesNotExist: + field.contribute_to_class(base_class, field_name) + classdict.pop(field_name) base_class._meta.fields_from_subclasses.update(declared_fields) diff --git a/typedmodels/test_models.py b/typedmodels/test_models.py index 8b25f3b..8973ed7 100644 --- a/typedmodels/test_models.py +++ b/typedmodels/test_models.py @@ -109,3 +109,16 @@ class Child1(Parent): class Child2(Parent): pass + + +class Employee(TypedModel): + pass + + +class Developer(Employee): + name = models.CharField(max_length=255, null=True) + + +class Manager(Employee): + # Adds the _exact_ same field as Developer. Shouldn't error. + name = models.CharField(max_length=255, null=True) diff --git a/typedmodels/tests.py b/typedmodels/tests.py index 44a14c0..e1b9b98 100644 --- a/typedmodels/tests.py +++ b/typedmodels/tests.py @@ -14,7 +14,7 @@ from .models import TypedModelManager from .test_models import AngryBigCat, Animal, BigCat, Canine, Feline, Parrot, AbstractVegetable, Vegetable, \ - Fruit, UniqueIdentifier, Child2 + Fruit, UniqueIdentifier, Child2, Employee @pytest.fixture @@ -308,3 +308,15 @@ def test_explicit_recast_with_string_on_untyped_instance(): animal.recast('typedmodels.feline') assert animal.type == 'typedmodels.feline' assert type(animal) is Feline + + +def test_same_field_name_in_two_subclasses(): + with pytest.raises(ValueError): + class Tester1(Employee): + name = models.CharField(max_length=255, blank=True, null=True) + with pytest.raises(ValueError): + class Tester2(Employee): + name = models.CharField(max_length=254, null=True) + with pytest.raises(ValueError): + class Tester3(Employee): + name = models.IntegerField(null=True)