#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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/056.7afd8fd4f992460198a083ec430dc520%40djangoproject.com.

Reply via email to