#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 , 4 years ago
| Description: | modified (diff) |
|---|
comment:2 by , 4 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:3 by , 4 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
OK I will implement 2nd solution.
follow-up: 6 comment:4 by , 4 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 , 4 years ago
| Has patch: | set |
|---|---|
| Patch needs improvement: | set |
comment:6 by , 4 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
GroupandUserand both are already in its internal cache, thenneeded_optsis 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._cachealready, you'd fill the missing items intoneeded_optsand hit the line which has caused you problems.
comment:7 by , 4 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.
PR made.
comment:8 by , 4 years ago
| Patch needs improvement: | unset |
|---|
comment:9 by , 4 years ago
| Patch needs improvement: | set |
|---|
comment:10 by , 3 years ago
| Owner: | removed |
|---|---|
| Status: | assigned → new |
comment:11 by , 3 years ago
| Owner: | set to |
|---|---|
| Patch needs improvement: | unset |
| Status: | new → assigned |
comment:12 by , 3 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Since
ContentTypeManageris marked asuse_in_migrations = Trueit should be able to handle this case.There's two possible solutions here:
AbstractContentType(models.Model)abstract model class that include most of the logicContentTypeand have the latter subclass the former. From thereCreateModel(bases)should be adjusted to pass this base instead.ContentTypeManager.get_for_modelsto 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?