[Django]-Getting model ContentType in migration – Django 1.7

18👍

The answer is:

apps.get_model('contenttypes', 'ContentType') 

🙂 Needed it myself today.

👤gabn88

16👍

For Django 2.1 I had to import apps from global registry because passed apps into migration were instances of django.db.migrations.state.AppConfigStub without populated models_module attribute. And create_contenttypes is checking this attribute.

from django.apps.registry import Apps, apps as global_apps
from django.contrib.contenttypes.management import create_contenttypes
from django.db import migrations


def add_permision(apps: Apps, schema_editor):
    my_app_config = global_apps.get_app_config('my_app')
    create_contenttypes(my_app_config)

    ...

8👍

Since, I ended up spending 3-4 hours on this I am adding my solution.

The problem was ContentType and Permission objects were not getting created when I ran multiple migrations together. Since I was referencing these content type and migration in next migration, this was causing problem.)

However they work fine if I run them one by one using migration number. (which were referenced in future migrations)

To solve it I added a extra migration in between to create ContentType and Permission objects.

# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-03-11 05:59
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations


def update_all_contenttypes(**kwargs):
    from django.apps import apps
    from django.contrib.contenttypes.management import update_contenttypes

    for app_config in apps.get_app_configs():
        update_contenttypes(app_config, **kwargs)


def create_all_permissions(**kwargs):
    from django.contrib.auth.management import create_permissions
    from django.apps import apps

    for app_config in apps.get_app_configs():
        create_permissions(app_config, **kwargs)


def forward(apps, schema_editor):
    update_all_contenttypes()
    create_all_permissions()


def backward(apps, schema_editor):
    pass


class Migration(migrations.Migration):
    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('contenttypes', '0002_remove_content_type_name'),
        ('MY_APP', '0123_LAST_MIGRATION'),
    ]

    operations = [
        migrations.RunPython(forward, backward)
    ]

7👍

Having a similar issue when writing a data migration that spans several apps. Turns out Django only loads those models into the app registry that are affected by what the “dependencies” member of the migration states: https://code.djangoproject.com/ticket/24303

Had to basically add an entry to the migration dependencies that I use that is not directly related by e.g. a ForeignKey to the app that is currently being migrated.

2👍

I faced the same issue today (Django 3.2.12). ContentTypes were present while running the migrate command, however, they were missing on test, though.
I managed to solve it by calling ContentType.objects.get_for_model (which is an alternative solution to those given above). E.g.:

ContentType = apps.get_model("contenttypes", "ContentType")
YourModel = apps.get_model("app_name", "model_name")
content_type = ContentType.objects.get_for_model(YourModel)

It works because get_for_model creates a ContentType if it doesn’t exist. See the Django implementation below:

    def get_for_model(self, model, for_concrete_model=True):
        """
        Return the ContentType object for a given model, creating the
        ContentType if necessary. Lookups are cached so that subsequent lookups
        for the same model don't hit the database.
        """
        opts = self._get_opts(model, for_concrete_model)
        try:
            return self._get_from_cache(opts)
        except KeyError:
            pass

        # The ContentType entry was not found in the cache, therefore we
        # proceed to load or create it.
        try:
            # Start with get() and not get_or_create() in order to use
            # the db_for_read (see #20401).
            ct = self.get(app_label=opts.app_label, model=opts.model_name)
        except self.model.DoesNotExist:
            # Not found in the database; we proceed to create it. This time
            # use get_or_create to take care of any race conditions.
            ct, created = self.get_or_create(
                app_label=opts.app_label,
                model=opts.model_name,
            )
        self._add_to_cache(self.db, ct)
        return ct

1👍

update_contenttypes(apps.app_configs['contenttypes'])

will update the contenttypes app’s content types.

I believe you would want to do this…

update_contenttypes(apps.app_configs['app_label']) 

where app_label is the app label for the app where the Invitation model lives.This will update your single app’s content types so it will be available to query as per your original code.

0👍

from django.db import migrations
from django.db.migrations import RunPython
from django.apps.registry import Apps, apps as global_apps
from django.contrib.contenttypes.management import create_contenttypes


def add_content_type_records(apps: Apps, schema_editor):
    my_app_config = global_apps.get_app_config('my_1_app')
    my_app_config.models_module = True
    create_contenttypes(my_app_config)
    my_app_config.models_module = None
    my_app_config = global_apps.get_app_config('my_2_app')
    my_app_config.models_module = True
    create_contenttypes(my_app_config)
    my_app_config.models_module = None


def create_setup_data(apps, schema_editor):
    ...


def delete_setup_data(apps, schema_editor):
    ...


class Migration(migrations.Migration):

    dependencies = [
        ('my_1_app', '....'),
        ('my_2_app', '....'),
        ('contenttypes', '__latest__'),
    ]

    operations = [
        RunPython(add_content_type_records, RunPython.noop),
        RunPython(create_setup_data, delete_setup_data),
    ]

0👍

I’m unwilling to depend on an undocumented private interface like create_contenttypes, so I chose a different solution.

Context: I have a migration that adds a field and then populates the field. The field is a GenericForeignKey, so I need access to ContentType data to populate it. That data is absent in the test database, which is created automatically during the execution of the test suite.

Therefore, resting on the assumption of "test DB = empty", I implemented the following check at the top of my forward function, which is passed to RunPython:

def forward(apps, schema_editor):
    MyModel = apps.get_model("myapp", "MyModel")
    if MyModel.objects.count() == 0:
        return
    # code that depends on ContentType here...

It still runs properly in a regular context, and no longer fails in a test context.

PS – The Django Project really ought to implement a proper solution to this problem in the core.

Leave a comment