Django migration fails with "__fake__.DoesNotExist: Permission matching query does not exist."
Asked Answered
G

1

21

In a Django 1.8 project, I have a migration that worked fine, when it had the following code:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

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


def update_site_forward(apps, schema_editor):
    """Add group osmaxx."""
    Group = apps.get_model("auth", "Group")
    Group.objects.create(name=settings.OSMAXX_FRONTEND_USER_GROUP)


def update_site_backward(apps, schema_editor):
    """Revert add group osmaxx."""
    Group = apps.get_model("auth", "Group")
    Group.objects.get(name=settings.OSMAXX_FRONTEND_USER_GROUP).delete()


class Migration(migrations.Migration):

    dependencies = [
        ('auth', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(update_site_forward, update_site_backward),
    ]

This group is created in a migration, because it shall be available in all installations of the web app. To make it more useful, I wanted to also give it a default permission, so I changed update_site_forward to:

def update_site_forward(apps, schema_editor):
    """Add group osmaxx."""
    Group = apps.get_model("auth", "Group")
    Permission = apps.get_model("auth", "Permission")
    ContentType = apps.get_model("contenttypes", "ContentType")
    ExtractionOrder = apps.get_model("excerptexport", "ExtractionOrder")
    group = Group.objects.create(name=settings.OSMAXX_FRONTEND_USER_GROUP)
    content_type = ContentType.objects.get_for_model(ExtractionOrder)
    permission = Permission.objects.get(codename='add_extractionorder',
                                        content_type=content_type) # line 16
    group.permissions.add(permission)

and Migration.dependencies to:

    dependencies = [
        ('contenttypes', '0002_remove_content_type_name'),
        ('excerptexport', '0001_initial'),
        ('auth', '0001_initial'),
    ]

While applying the migration (after first reverting it) (python3 manage.py migrate auth 0001 && python3 managy.py migrate) worked, migrating a newly created PostgreSQL database with this and all other migrations (python3 manage.py migrate) fails:

Operations to perform:
  Synchronize unmigrated apps: debug_toolbar, django_extensions, messages, humanize, social_auth, kombu_transport_django, staticfiles
  Apply all migrations: excerptexport, admin, sites, contenttypes, sessions, default, stored_messages, auth
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying auth.0002_add_default_usergroup_osmaxx...Traceback (most recent call last):
  File "manage.py", line 17, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/base.py", line 393, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/base.py", line 444, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/commands/migrate.py", line 221, in handle
    executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.4/dist-packages/django/db/migrations/executor.py", line 110, in migrate
    self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.4/dist-packages/django/db/migrations/executor.py", line 148, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/usr/local/lib/python3.4/dist-packages/django/db/migrations/migration.py", line 115, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/usr/local/lib/python3.4/dist-packages/django/db/migrations/operations/special.py", line 183, in database_forwards
    self.code(from_state.apps, schema_editor)
  File "/home/osmaxx/source/osmaxx/contrib/auth/migrations/0002_add_default_usergroup_osmaxx.py", line 16, in update_site_forward
    permission = Permission.objects.get(codename='add_extractionorder', content_type=content_type)
  File "/usr/local/lib/python3.4/dist-packages/django/db/models/manager.py", line 127, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.4/dist-packages/django/db/models/query.py", line 334, in get
    self.model._meta.object_name
__fake__.DoesNotExist: Permission matching query does not exist.

What am I doing wrong?

Goutweed answered 21/7, 2015 at 12:58 Comment(1)
Note: The full code of the project (without the mentioned change) is available on GitHub. The mentioned migration is osmaxx-py/osmaxx/contrib/auth/migrations/0002_add_default_usergroup_osmaxx.py But please comment, if anything there but not mentioned in the question is relevant for answering the question, so that I can include it in the question.Goutweed
A
32

The default permissions are created in a post_migrate signal handler, after the migrations have run. This won't be a problem if your updated code runs as part of the second manage.py migrate run, but it is a problem in the test suite and any new deployment.

The easy fix is to change this line:

permission = Permission.objects.get(codename='add_extractionorder',
                                    content_type=content_type) # line 16

to this:

permission, created = Permission.objects.get_or_create(codename='add_extractionorder',
                                              content_type=content_type)

The signal handler that creates the default permissions will never create a duplicate permission, so it is safe to create it if it doesn't exist already.

Adalie answered 21/7, 2015 at 15:13 Comment(5)
Ah, so permissions are created after all migrations, not after every individual migration?Goutweed
The post_migrate signal fires every time the manage.py migrate command completes -- so yes, if you apply all migrations at once, it fires after all migrations have run.Adalie
This answer has a workaround for this: #31735542Hendrix
it's also possible to migrate to the migration just before it. then continue the migration as normal. this solved it for me.Thirst
Saved my day, thanks! Strangely, this is not mentioned in the documentation on migrations.Hiram

© 2022 - 2024 — McMap. All rights reserved.