Code

Ticket #10808: 10808b.diff

File 10808b.diff, 5.1 KB (added by lsaffre, 5 years ago)

contains 10808.diff + my suggestion

Line 
1Index: django/db/models/base.py
2===================================================================
3--- django/db/models/base.py    (revision 11231)
4+++ django/db/models/base.py    (working copy)
5@@ -273,7 +273,15 @@
6         # Now we're left with the unprocessed fields that *must* come from
7         # keywords, or default.
8 
9+        # In the case of diamond inheritance, where B and C inherit from A, and
10+        # D inherits from B and C, D will have "redundant" copies of each of
11+        # A's fields. As we iterate through all the fields, the second time we
12+        # see a field we run the risk of reassigning it the default value, so
13+        # if a field has already been seen in assigned_fields, we ignore it.
14+        assigned_fields = set()
15         for field in fields_iter:
16+            if field.attname in assigned_fields:
17+                continue
18             is_related_object = False
19             # This slightly odd construct is so that we can access any
20             # data-descriptor object (DeferredAttribute) without triggering its
21@@ -311,6 +319,7 @@
22                 setattr(self, field.name, rel_obj)
23             else:
24                 setattr(self, field.attname, val)
25+            assigned_fields.add(field.attname)
26 
27         if kwargs:
28             for prop in kwargs.keys():
29Index: django/forms/models.py
30===================================================================
31--- django/forms/models.py      (revision 11231)
32+++ django/forms/models.py      (working copy)
33@@ -793,15 +793,11 @@
34     from django.db.models import ForeignKey
35     opts = model._meta
36     if fk_name:
37-        fks_to_parent = [f for f in opts.fields if f.name == fk_name]
38-        if len(fks_to_parent) == 1:
39-            fk = fks_to_parent[0]
40-            if not isinstance(fk, ForeignKey) or \
41-                    (fk.rel.to != parent_model and
42-                     fk.rel.to not in parent_model._meta.get_parent_list()):
43-                raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
44-        elif len(fks_to_parent) == 0:
45-            raise Exception("%s has no field named '%s'" % (model, fk_name))
46+        fk = opts.get_field(fk_name,many_to_many=False)
47+        if not isinstance(fk, ForeignKey) or \
48+                (fk.rel.to != parent_model and
49+                 fk.rel.to not in parent_model._meta.get_parent_list()):
50+            raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
51     else:
52         # Try to discover what the ForeignKey from model to parent_model is
53         fks_to_parent = [
54Index: tests/modeltests/model_inheritance/models.py
55===================================================================
56--- tests/modeltests/model_inheritance/models.py        (revision 11231)
57+++ tests/modeltests/model_inheritance/models.py        (working copy)
58@@ -116,6 +116,26 @@
59     def __unicode__(self):
60         return u"%s the parking lot" % self.name
61 
62+#
63+# Diamond inheritance test
64+#
65+
66+class Owner(models.Model):
67+    name = models.CharField(max_length=255)
68+   
69+class FoodPlace(models.Model):
70+    name = models.CharField(max_length=255)
71+    owner = models.ForeignKey(Owner,blank=True,null=True)
72+
73+class Bar(FoodPlace):
74+    pass
75+
76+class Pizzeria(FoodPlace):
77+    pass
78+
79+class PizzeriaBar(Bar, Pizzeria):
80+    pizza_bar_specific_field = models.CharField(max_length=255)
81+
82 __test__ = {'API_TESTS':"""
83 # The Student and Worker models both have 'name' and 'age' fields on them and
84 # inherit the __unicode__() method, just as with normal Python subclassing.
85@@ -310,4 +330,37 @@
86 3
87 >>> settings.DEBUG = False
88 
89+# Test of diamond inheritance __init__. If B and C inherit from A, and D inherits from B and C, we should be able to use __init__ for D to properly set all the fields, regardless of the redundant copies of A's fields that D inherits from B and C.
90+
91+>>> p = PizzeriaBar(name="Mike's", pizza_bar_specific_field="Doodle")
92+>>> p.name == "Mike's"
93+True
94+>>> p.pizza_bar_specific_field == "Doodle"
95+True
96+
97+#Note that patch 10808.diff fixes only one symptom, not the real problem.
98+#The real problem is that in case of diamond inheritance there are duplicate field definitions:
99+
100+  >>> print ' '.join([f.name for f in p._meta.fields])
101+  id name owner foodplace_ptr id name owner foodplace_ptr pizzeria_ptr bar_ptr pizza_bar_specific_field
102
103+#The first 4 fields occur twice.
104+#My patch won't fix the real problem, but another symptom.
105+#When the top-level model of your diamond structure contains a ForeignKey, then you get problems when trying to create inline formsets:
106+
107+  >>> from django.forms.models import inlineformset_factory
108+  >>> f = inlineformset_factory(Owner,PizzeriaBar)
109+  Traceback (most recent call last):
110+    ...
111+  Exception: <class '...PizzeriaBar'> has more than 1 ForeignKey to <class '....Owner'>
112
113+#The workaround I suggest for this problem is to specify the fk_name explicitly:
114+
115+  >>> from django.forms.models import inlineformset_factory
116+  >>> f = inlineformset_factory(Owner,PizzeriaBar,fk_name='owner')
117
118+#Unfortunately this workaround needs another patch 10808b.diff because inlineformset_factory() just can't imagine that a model can have two fields with the same name.
119+
120+
121+
122 """}