#33313: Inheriting from multiple abstract models with same field causes name
collision when overriding field is direct parent
-------------------------------------+-------------------------------------
               Reporter:  Ben Nace   |          Owner:  nobody
                   Type:  Bug        |         Status:  new
              Component:  Database   |        Version:  3.2
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 Given the following example models:

 {{{
 class ModelActivation(models.Model):
     start_date = models.DateField(null=True, blank=True)
     end_date = models.DateField(null=True, blank=True)
     active = models.BooleanField()

     class Meta:
         abstract = True

 class BaseData(ModelActivation):
     entity_state = models.CharField(max_length=100)

     class Meta:
         abstract = True

 class RequiredStart(models.Model):
     start_date = models.DateField()

     class Meta:
         abstract = True

 class RequiredEnd(models.Model):
     end_date = models.DateField()

     class Meta:
         abstract = True

 class RequiredStartEnd(RequiredStart, RequiredEnd):
     class Meta:
         abstract = True
 }}}
 \\

 Any of the following when the override for start_date is defined on a
 direct parent model, results in the error "(models.E006) The field
 'start_date' clashes with the field 'start_date' from model
 'app.testmodel' (or 'app.testmodel2')"
 {{{
 class TestModel(RequiredStart, BaseData):
     pass

 class TestModel2(RequiredStart, ModelActivation):
     pass
 }}}
 However, if the overriding field is pushed up to a grandparent model,
 rather than a direct parent, it works fine.
 {{{
 class TestModel3(RequiredStartEnd, BaseData):
     pass

 class TestModel4(RequiredStartEnd, ModelActivation):
     pass
 }}}

 In my limited debugging, it appears to me that this is because of the way
 inherited_attributes is tracked in the __new__ method of the ModelBase
 model metaclass (in django.db.models.base.py). For a grandparent model,
 not being a direct parent, all items in the __dict__ will be added to
 inherited_attributes, which includes the fields:

 {{{
             if base not in parents or not hasattr(base, '_meta'):
                 # Things without _meta aren't functional models, so
 they're
                 # uninteresting parents.
                 inherited_attributes.update(base.__dict__)
                 continue
 }}}

 However, when a field is inherited from a direct parent, it is not added
 to inherited_attributes, it is not added to field_names, and it does not
 appear in new_class.__dict__, so the field from the ancestor higher up in
 the mro is also added via

 {{{
                 for field in parent_fields:
                     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)
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33313>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/056.7afd8fd4f992460198a083ec430dc520%40djangoproject.com.

Reply via email to