Code

Ticket #14705: 14705_model_fields_order.diff

File 14705_model_fields_order.diff, 5.4 KB (added by julien, 3 years ago)
Line 
1diff --git a/django/db/models/base.py b/django/db/models/base.py
2index 4000379..3c4b65d 100644
3--- a/django/db/models/base.py
4+++ b/django/db/models/base.py
5@@ -101,10 +101,15 @@ class ModelBase(type):
6                      new_class._meta.virtual_fields
7         field_names = set([f.name for f in new_fields])
8 
9+        # Things without _meta aren't functional models, so they're
10+        # uninteresting parents.
11+        parents = [p for p in parents if hasattr(p, '_meta')]
12+        new_class._meta.model_bases = parents
13+
14         # Basic setup for proxy models.
15         if is_proxy:
16             base = None
17-            for parent in [cls for cls in parents if hasattr(cls, '_meta')]:
18+            for parent in parents:
19                 if parent._meta.abstract:
20                     if parent._meta.fields:
21                         raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name)
22@@ -129,11 +134,6 @@ class ModelBase(type):
23 
24         for base in parents:
25             original_base = base
26-            if not hasattr(base, '_meta'):
27-                # Things without _meta aren't functional models, so they're
28-                # uninteresting parents.
29-                continue
30-
31             parent_fields = base._meta.local_fields + base._meta.local_many_to_many
32             # Check for clashes between locally declared fields and those
33             # on the base classes (we cannot handle shadowed fields at the
34diff --git a/django/db/models/options.py b/django/db/models/options.py
35index 2f64c56..052d158 100644
36--- a/django/db/models/options.py
37+++ b/django/db/models/options.py
38@@ -47,6 +47,7 @@ class Options(object):
39         self.proxy = False
40         self.proxy_for_model = None
41         self.parents = SortedDict()
42+        self.model_bases = []
43         self.duplicate_targets = {}
44         self.auto_created = False
45 
46@@ -232,13 +233,28 @@ class Options(object):
47 
48     def _fill_fields_cache(self):
49         cache = []
50-        for parent in self.parents:
51-            for field, model in parent._meta.get_fields_with_model():
52-                if model:
53-                    cache.append((field, model))
54+        local_fields = list(self.local_fields)
55+        for field in self.local_fields:
56+            if isinstance(field, AutoField):
57+                cache.append((field, None))
58+                local_fields.remove(field)
59+                break
60+        for base in self.model_bases:
61+            for field, model in base._meta.get_fields_with_model():
62+                if not model:
63+                    model = base
64+                if model._meta.abstract:
65+                    field_index = local_fields.index(field)
66+                    cache.append((local_fields[field_index], None))
67+                    del local_fields[field_index]
68                 else:
69-                    cache.append((field, parent))
70-        cache.extend([(f, None) for f in self.local_fields])
71+                    cache.append((field, model))
72+            if base in self.parents:
73+                field = self.parents[base]
74+                if field:
75+                    cache.append((field, None))
76+                    local_fields.remove(field)
77+        cache.extend([(f, None) for f in local_fields])
78         self._field_cache = tuple(cache)
79         self._field_name_cache = [x for x, _ in cache]
80 
81diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
82index a0fed8a..1537b1e 100644
83--- a/tests/modeltests/model_inheritance/models.py
84+++ b/tests/modeltests/model_inheritance/models.py
85@@ -119,6 +119,26 @@ class ParkingLot(Place):
86     def __unicode__(self):
87         return u"%s the parking lot" % self.name
88 
89+class AbstractModelOne(models.Model):
90+    field_one = models.BooleanField()
91+    field_two = models.TextField()
92+
93+    class Meta:
94+        abstract = True
95+
96+class AbstractModelTwo(models.Model):
97+    field_three = models.BooleanField()
98+    field_four = models.TextField()
99+
100+    class Meta:
101+        abstract = True
102+
103+class OneTwo(AbstractModelOne, AbstractModelTwo):
104+    pass
105+
106+class TwoOne(AbstractModelTwo, AbstractModelOne):
107+    pass
108+
109 #
110 # Abstract base classes with related models where the sub-class has the
111 # same name in a different app and inherits from the same abstract base
112diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
113index 334297a..1078b1e 100644
114--- a/tests/modeltests/model_inheritance/tests.py
115+++ b/tests/modeltests/model_inheritance/tests.py
116@@ -4,7 +4,8 @@ from django.core.exceptions import FieldError
117 from django.test import TestCase
118 
119 from models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place,
120-    Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel)
121+    Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel,
122+    OneTwo, TwoOne)
123 
124 
125 class ModelInheritanceTests(TestCase):
126@@ -269,6 +270,13 @@ class ModelInheritanceTests(TestCase):
127         self.assertNumQueries(1,
128             lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
129         )
130+       
131+        names = [field.name for field in OneTwo._meta.fields]
132+        self.assertEqual(["id", "field_one", "field_two", "field_three",
133+            "field_four"], names)
134+        names = [field.name for field in TwoOne._meta.fields]
135+        self.assertEqual(["id", "field_three", "field_four", "field_one",
136+               "field_two"], names)
137 
138     def test_mixin_init(self):
139         m = MixinModel()