浏览代码

[5.0.x] Fixed #34985 -- Fixed GeneratedFields.contribute_to_class() crash when apps are not populated.

Thanks Paolo Melchiorre for the report.

Regression in f333e3513e8bdf5ffeb6eeb63021c230082e6f95.
Backport of 101a85a5a06585ba16ecb25860146d034a8a55ec from main
Mariusz Felisiak 1 年之前
父节点
当前提交
48eebdc63c
共有 2 个文件被更改,包括 24 次插入9 次删除
  1. 4 5
      django/db/models/fields/generated.py
  2. 20 4
      tests/model_fields/test_generatedfield.py

+ 4 - 5
django/db/models/fields/generated.py

@@ -13,7 +13,6 @@ class GeneratedField(Field):
     db_returning = True
 
     _query = None
-    _resolved_expression = None
     output_field = None
 
     def __init__(self, *, expression, output_field, db_persist=None, **kwargs):
@@ -48,9 +47,6 @@ class GeneratedField(Field):
         super().contribute_to_class(*args, **kwargs)
 
         self._query = Query(model=self.model, alias_cols=False)
-        self._resolved_expression = self.expression.resolve_expression(
-            self._query, allow_joins=False
-        )
         # Register lookups from the output_field class.
         for lookup_name, lookup in self.output_field.get_class_lookups().items():
             self.register_lookup(lookup, lookup_name=lookup_name)
@@ -59,7 +55,10 @@ class GeneratedField(Field):
         compiler = connection.ops.compiler("SQLCompiler")(
             self._query, connection=connection, using=None
         )
-        return compiler.compile(self._resolved_expression)
+        resolved_expression = self.expression.resolve_expression(
+            self._query, allow_joins=False
+        )
+        return compiler.compile(resolved_expression)
 
     def check(self, **kwargs):
         databases = kwargs.get("databases") or []

+ 20 - 4
tests/model_fields/test_generatedfield.py

@@ -1,3 +1,4 @@
+from django.apps import apps
 from django.db import IntegrityError, connection
 from django.db.models import (
     CharField,
@@ -33,6 +34,25 @@ class BaseGeneratedFieldTests(SimpleTestCase):
                 db_persist=False,
             )
 
+    @isolate_apps("model_fields")
+    def test_contribute_to_class(self):
+        class BareModel(Model):
+            pass
+
+        new_field = GeneratedField(
+            expression=Lower("nonexistent"),
+            output_field=IntegerField(),
+            db_persist=True,
+        )
+        apps.models_ready = False
+        try:
+            # GeneratedField can be added to the model even when apps are not
+            # fully loaded.
+            new_field.contribute_to_class(BareModel, "name")
+            self.assertEqual(BareModel._meta.get_field("name"), new_field)
+        finally:
+            apps.models_ready = True
+
     def test_blank_unsupported(self):
         with self.assertRaisesMessage(ValueError, "GeneratedField must be blank."):
             GeneratedField(
@@ -217,10 +237,6 @@ class GeneratedFieldTestMixin:
         db_parameters = field.db_parameters(connection)
         self.assertEqual(db_parameters["collation"], collation)
         self.assertEqual(db_parameters["type"], field.output_field.db_type(connection))
-        self.assertNotEqual(
-            db_parameters["type"],
-            field._resolved_expression.output_field.db_type(connection),
-        )
 
     def test_db_type_parameters(self):
         db_type_parameters = self.output_field_db_collation_model._meta.get_field(