#32833 closed Bug (fixed)
ContentType.objects.get_for_models() in migrations does not works for multiple models
Reported by: | HMaker | Owned by: | Sarah Boyce |
---|---|---|---|
Component: | contrib.contenttypes | Version: | 3.1 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Ready for checkin | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I am trying to use migrations to create default groups, I tried to run the following procedure with migrations.RunPython()
def create_normal_users_group(apps, *args): auth = SimpleNamespace(**apps.all_models['auth']) myapp = SimpleNamespace(**apps.all_models['myapp']) ContentType = apps.get_model('contenttypes', 'ContentType') group = auth.group.objects.create(name='Normal Users') contenttypes = ContentType.objects.get_for_models(myapp.user, myapp.proxy) # there are more models... # ...
but it raises AttributeError
File "/home/user/projects/myapp/.venv/lib/python3.8/site-packages/django/contrib/contenttypes/models.py", line 89, in get_for_models opts_models = needed_opts.pop(ct.model_class()._meta, []) AttributeError: 'ContentType' object has no attribute 'model_class'
it works when I pass a single model to ContentType.objects.get_for_models()
, but get_for_models()
is supposed to work with multiple models.
EDIT:
Adding model_class()
to ContentType
makes it work
def create_normal_users_group(apps, *args): auth = SimpleNamespace(**apps.all_models['auth']) myapp = SimpleNamespace(**apps.all_models['myapp']) ContentType = apps.get_model('contenttypes', 'ContentType') # this makes it work ========================= def model_class(self): return apps.get_model(self.app_label, self.model) ContentType.model_class = model_class # ======================================== group = auth.group.objects.create(name='Normal Users') contenttypes = ContentType.objects.get_for_models(myapp.user, myapp.proxy) # there are more models... # ...
Change History (15)
comment:1 by , 3 years ago
Description: | modified (diff) |
---|
comment:2 by , 3 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:3 by , 3 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
OK I will implement 2nd solution.
follow-up: 6 comment:4 by , 3 years ago
I tried to reproduce this problem but I could not, check my partial patch, it's based on 3.1.4 branch.
comment:5 by , 3 years ago
Has patch: | set |
---|---|
Patch needs improvement: | set |
comment:6 by , 3 years ago
Replying to Heraldo Lucena:
I tried to reproduce this problem but I could not [...]
My guess would be that the ContentTypeManager instance already had a partially filled (or empty) cache when you encountered the error, such that self._get_from_cache(modelinstance._meta)
worked (didn't throw a KeyError
looking in the self._cache
).
- If you pass in 2 model classes
Group
andUser
and both are already in its internal cache, thenneeded_opts
is empty andneeded_opts.pop(ct.model_class()._meta, [])
doesn't occur. - If you pass the same 2 classes and only 1 or 0 of them are in the
self._cache
already, you'd fill the missing items intoneeded_opts
and hit the line which has caused you problems.
comment:7 by , 3 years ago
I thought migrations would be automatically rolled back after each test case, but that did not happen and last test case could not run properly since all migrations were already applied. Now the regression test can reproduce reported problem.
comment:8 by , 3 years ago
Patch needs improvement: | unset |
---|
comment:9 by , 3 years ago
Patch needs improvement: | set |
---|
comment:10 by , 2 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:11 by , 2 years ago
Owner: | set to |
---|---|
Patch needs improvement: | unset |
Status: | new → assigned |
comment:12 by , 2 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
Since
ContentTypeManager
is marked asuse_in_migrations = True
it should be able to handle this case.There's two possible solutions here:
AbstractContentType(models.Model)
abstract model class that include most of the logicContentType
and have the latter subclass the former. From thereCreateModel(bases)
should be adjusted to pass this base instead.ContentTypeManager.get_for_models
to avoid calling.model_class()
and inline its logic in the manager method instead.Feels like 2. is the less disruptive approach and easier to test. Would you be interested in writing a patch PR for it?