#34985 closed Bug (fixed)
Migrations raise AppRegistryNotReady when GeneratedField references incorrect fields.
Reported by: | Paolo Melchiorre | Owned by: | Mariusz Felisiak |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.0 |
Severity: | Release blocker | Keywords: | field, database, generated, output_field |
Cc: | Lily Foote | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Note: In my experiments, I am sure I have encountered similar errors even in cases that are simpler to reproduce, without models with multi-table inheritance to clarify, but in this case, I cannot find the example code of the field generated to reproduce it, so I report below the example that certainly allows you to reproduce the error.
I tried to test the behavior of the generated fields in the case of multi-table inheritance.
I was expecting an error since the documentation specifies that:
The expressions should be deterministic and only reference fields within the model (in the same database table).
The generation of migrations of two inheritance models occurs successfully (without generated fields)
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=150) last_name = models.CharField(max_length=150) class Profile(Person): email = models.EmailField()
$ python3 -m manage makemigrations Migrations for 'samples': samples/migrations/0010_person_profile.py - Create model Person - Create model Profile $ python3 -m manage sqlmigrate samples 0010
BEGIN; -- -- Create model Person -- CREATE TABLE "samples_person" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(150) NOT NULL, "last_name" varchar(150) NOT NULL); -- -- Create model Profile -- CREATE TABLE "samples_profile" ("person_ptr_id" bigint NOT NULL PRIMARY KEY REFERENCES "samples_person" ("id") DEFERRABLE INITIALLY DEFERRED, "email" varchar(254) NOT NULL); COMMIT;
Instead, if in the second model, I create a generated field that references the fields in the first model I get a very unclear error that perhaps we could make more clear.
from django.db import models from django.db.models import Value from django.db.models.functions import ( Concat, ) class Person(models.Model): first_name = models.CharField(max_length=150) last_name = models.CharField(max_length=150) class Profile(Person): email = models.EmailField() display_name = models.GeneratedField( expression=Concat("first_name", Value(" "), "last_name", Value(" "), "email",), db_persist=True, output_field=models.TextField(), )
$ python3 -m manage makemigrations
Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "/home/paulox/Projects/generatedfields/manage.py", line 22, in <module> main() File "/home/paulox/Projects/generatedfields/manage.py", line 18, in main execute_from_command_line(sys.argv) File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line utility.execute() File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/core/management/__init__.py", line 416, in execute django.setup() File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/__init__.py", line 24, in setup apps.populate(settings.INSTALLED_APPS) File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/apps/registry.py", line 116, in populate app_config.import_models() File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/apps/config.py", line 269, in import_models self.models_module = import_module(models_module_name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module return _bootstrap._gcd_import(name[level:], package, level) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "<frozen importlib._bootstrap>", line 1381, in _gcd_import File "<frozen importlib._bootstrap>", line 1354, in _find_and_load File "<frozen importlib._bootstrap>", line 1325, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 929, in _load_unlocked File "<frozen importlib._bootstrap_external>", line 994, in exec_module File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed File "/home/paulox/Projects/generatedfields/samples/models.py", line 233, in <module> class Profile(User): File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/base.py", line 194, in __new__ new_class.add_to_class(obj_name, obj) File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/base.py", line 371, in add_to_class value.contribute_to_class(cls, name) File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/fields/generated.py", line 51, in contribute_to_class self._resolved_expression = self.expression.resolve_expression( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/expressions.py", line 975, in resolve_expression c.source_expressions[pos] = arg.resolve_expression( ^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/expressions.py", line 975, in resolve_expression c.source_expressions[pos] = arg.resolve_expression( ^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/expressions.py", line 854, in resolve_expression return query.resolve_ref(self.name, allow_joins, reuse, summarize) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/sql/query.py", line 2001, in resolve_ref join_info = self.setup_joins( ^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/sql/query.py", line 1854, in setup_joins path, final_field, targets, rest = self.names_to_path( ^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/sql/query.py", line 1754, in names_to_path *get_field_names_from_opts(opts), ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/sql/query.py", line 63, in get_field_names_from_opts (f.name, f.attname) if f.concrete else (f.name,) for f in opts.get_fields() ^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/options.py", line 858, in get_fields return self._get_fields( ^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/options.py", line 931, in _get_fields all_fields = self._relation_tree ^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/utils/functional.py", line 47, in __get__ res = instance.__dict__[self.name] = self.func(instance) ^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/options.py", line 831, in _relation_tree return self._populate_directed_relation_graph() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/db/models/options.py", line 798, in _populate_directed_relation_graph all_models = self.apps.get_models(include_auto_created=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/apps/registry.py", line 181, in get_models self.check_models_ready() File "/home/paulox/.virtualenvs/generatedfields/lib/python3.12/site-packages/django/apps/registry.py", line 143, in check_models_ready raise AppRegistryNotReady("Models aren't loaded yet.") django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Change History (7)
follow-up: 2 comment:1 by , 13 months ago
Cc: | added |
---|---|
Severity: | Normal → Release blocker |
Summary: | Confusing migration failures in GeneratedField → Migrations raise AppRegistryNotReady when GeneratedField references incorrect fields. |
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 13 months ago
Unfortunately, this would still required catching exceptions when resolving
GeneratedField
expressions.
Or we can move resolving an expression to the generated_sql()
which is not the end of the world:
-
django/db/models/fields/generated.py
diff --git a/django/db/models/fields/generated.py b/django/db/models/fields/generated.py index 9a73b7fe37..95d19582de 100644
a b class GeneratedField(Field): 13 13 db_returning = True 14 14 15 15 _query = None 16 _resolved_expression = None17 16 output_field = None 18 17 19 18 def __init__(self, *, expression, output_field, db_persist=None, **kwargs): … … class GeneratedField(Field): 48 47 super().contribute_to_class(*args, **kwargs) 49 48 50 49 self._query = Query(model=self.model, alias_cols=False) 51 self._resolved_expression = self.expression.resolve_expression(52 self._query, allow_joins=False53 )54 50 # Register lookups from the output_field class. 55 51 for lookup_name, lookup in self.output_field.get_class_lookups().items(): 56 52 self.register_lookup(lookup, lookup_name=lookup_name) … … class GeneratedField(Field): 59 55 compiler = connection.ops.compiler("SQLCompiler")( 60 56 self._query, connection=connection, using=None 61 57 ) 62 return compiler.compile(self._resolved_expression) 58 resolved_expression = self.expression.resolve_expression( 59 self._query, allow_joins=False 60 ) 61 return compiler.compile(resolved_expression) 63 62 64 63 def check(self, **kwargs): 65 64 databases = kwargs.get("databases") or []
comment:3 by , 13 months ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:4 by , 13 months ago
On reconsideration, system check is not necessary. makemigrations
doesn't crash with the proposed diff, and migrate
raises:
django.core.exceptions.FieldError: Joined field references are not permitted in this query
which is fine, IMO.
Thanks for the report. We could add a small check for
GeneratedField
references, e.g.django/db/models/base.py
Unfortunately, this would still required catching exceptions when resolving
GeneratedField
expressions.