瀏覽代碼

Fixed #34697 -- Fixed non-deterministic order of dependencies and sets/frozensets in migrations.

Co-authored-by: Dakota Hawkins <dakotahawkins@gmail.com>
Yury V. Zaytsev 1 年之前
父節點
當前提交
02966a30dd
共有 4 個文件被更改,包括 43 次插入3 次删除
  1. 1 0
      AUTHORS
  2. 7 2
      django/db/migrations/serializer.py
  3. 3 1
      django/db/migrations/writer.py
  4. 32 0
      tests/migrations/test_writer.py

+ 1 - 0
AUTHORS

@@ -1043,6 +1043,7 @@ answer newbie questions, and generally made Django that much better:
     ye7cakf02@sneakemail.com
     ymasuda@ethercube.com
     Yoong Kang Lim <yoongkang.lim@gmail.com>
+    Yury V. Zaytsev <yury@shurup.com>
     Yusuke Miyazaki <miyazaki.dev@gmail.com>
     yyyyyyyan <contact@yyyyyyyan.tech>
     Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>

+ 7 - 2
django/db/migrations/serializer.py

@@ -46,6 +46,11 @@ class BaseSequenceSerializer(BaseSerializer):
         return value % (", ".join(strings)), imports
 
 
+class BaseUnorderedSequenceSerializer(BaseSequenceSerializer):
+    def __init__(self, value):
+        super().__init__(sorted(value, key=repr))
+
+
 class BaseSimpleSerializer(BaseSerializer):
     def serialize(self):
         return repr(self.value), set()
@@ -151,7 +156,7 @@ class FloatSerializer(BaseSimpleSerializer):
         return super().serialize()
 
 
-class FrozensetSerializer(BaseSequenceSerializer):
+class FrozensetSerializer(BaseUnorderedSequenceSerializer):
     def _format(self):
         return "frozenset([%s])"
 
@@ -279,7 +284,7 @@ class SequenceSerializer(BaseSequenceSerializer):
         return "[%s]"
 
 
-class SetSerializer(BaseSequenceSerializer):
+class SetSerializer(BaseUnorderedSequenceSerializer):
     def _format(self):
         # Serialize as a set literal except when value is empty because {}
         # is an empty dict.

+ 3 - 1
django/db/migrations/writer.py

@@ -154,7 +154,9 @@ class MigrationWriter:
                 imports.add("from django.conf import settings")
             else:
                 dependencies.append("        %s," % self.serialize(dependency)[0])
-        items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else ""
+        items["dependencies"] = (
+            "\n".join(sorted(dependencies)) + "\n" if dependencies else ""
+        )
 
         # Format imports nicely, swapping imports of functions from migration files
         # for comments

+ 32 - 0
tests/migrations/test_writer.py

@@ -768,12 +768,17 @@ class WriterTests(SimpleTestCase):
     def test_serialize_frozensets(self):
         self.assertSerializedEqual(frozenset())
         self.assertSerializedEqual(frozenset("let it go"))
+        self.assertSerializedResultEqual(
+            frozenset("cba"), ("frozenset(['a', 'b', 'c'])", set())
+        )
 
     def test_serialize_set(self):
         self.assertSerializedEqual(set())
         self.assertSerializedResultEqual(set(), ("set()", set()))
         self.assertSerializedEqual({"a"})
         self.assertSerializedResultEqual({"a"}, ("{'a'}", set()))
+        self.assertSerializedEqual({"c", "b", "a"})
+        self.assertSerializedResultEqual({"c", "b", "a"}, ("{'a', 'b', 'c'}", set()))
 
     def test_serialize_timedelta(self):
         self.assertSerializedEqual(datetime.timedelta())
@@ -891,6 +896,33 @@ class WriterTests(SimpleTestCase):
             result["custom_migration_operations"].more_operations.TestOperation,
         )
 
+    def test_sorted_dependencies(self):
+        migration = type(
+            "Migration",
+            (migrations.Migration,),
+            {
+                "operations": [
+                    migrations.AddField("mymodel", "myfield", models.IntegerField()),
+                ],
+                "dependencies": [
+                    ("testapp10", "0005_fifth"),
+                    ("testapp02", "0005_third"),
+                    ("testapp02", "0004_sixth"),
+                    ("testapp01", "0001_initial"),
+                ],
+            },
+        )
+        output = MigrationWriter(migration, include_header=False).as_string()
+        self.assertIn(
+            "    dependencies = [\n"
+            "        ('testapp01', '0001_initial'),\n"
+            "        ('testapp02', '0004_sixth'),\n"
+            "        ('testapp02', '0005_third'),\n"
+            "        ('testapp10', '0005_fifth'),\n"
+            "    ]",
+            output,
+        )
+
     def test_sorted_imports(self):
         """
         #24155 - Tests ordering of imports.