Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Django admin command to "fix permissions" (create them properly for proxy models). This is needed because of the following bug in Django (not fixed as of 1.6): https://code.djangoproject.com/ticket/11154
# -*- coding: utf-8 -*-
"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""
from __future__ import unicode_literals, absolute_import, division
import sys
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.db.models import get_models
from django.utils.encoding import smart_unicode
class Command(BaseCommand):
help = "Fix permissions for proxy models."
def handle(self, *args, **options):
for model in get_models():
opts = model._meta
ctype, created = ContentType.objects.get_or_create(
app_label=opts.app_label,
model=opts.object_name.lower(),
defaults={'name': smart_unicode(opts.verbose_name_raw)})
for codename, name in _get_all_permissions(opts, ctype):
p, created = Permission.objects.get_or_create(
codename=codename,
content_type=ctype,
defaults={'name': name})
if created:
sys.stdout.write('Adding permission {}\n'.format(p))
@chogarcia

This comment has been minimized.

Copy link

commented May 18, 2015

thank you so much for this!

@aaronhelton

This comment has been minimized.

Copy link

commented Aug 27, 2015

Agreed, thank you for this. As of 1.8, this is still an issue. This script works.

In case you have no idea how to use it, try the following (I had to search around for this since I'm still learning Django):

  • create a directory /myproject/myapp/management/commands
  • touch /myproject/myapp/management/__init__.py
  • touch /myproject/myapp/management/commands/__init__.py
  • and then save the code above into /myproject/myapp/management/commands/fix_permissions.py

Then you'll be able to run /manage.py fix_permissions

@nando1

This comment has been minimized.

Copy link

commented Oct 12, 2015

Just a heads up, if you're using Python 3, you'll have to swap out smart_unicode for smart_text.

https://docs.djangoproject.com/en/1.7/ref/utils/#django.utils.encoding.smart_unicode

@arulmr

This comment has been minimized.

Copy link

commented Nov 23, 2015

@magopian Thank you so much for this script. This is perfect. @nando1 Thanks for the fix.

@dckemble

This comment has been minimized.

Copy link

commented Feb 17, 2016

I think I have this working hooked generally into post_migrate (Django 1.9 specifically, havent tested other versions, note the differences in get_models import location etc for 1.9). I have an app, label "support" which is nothing but proxy models and custom admins. In that app's models.py I added:

import sys

from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.apps import apps
from django.utils.encoding import smart_unicode
from django.db import models

def proxy_perm_create(**kwargs):
    for model in apps.get_models():
        opts = model._meta
        ctype, created = ContentType.objects.get_or_create(
            app_label=opts.app_label,
            model=opts.object_name.lower(),
            defaults={'name': smart_unicode(opts.verbose_name_raw)})

        for codename, name in _get_all_permissions(opts, ctype):
            p, created = Permission.objects.get_or_create(
                codename=codename,
                content_type=ctype,
                defaults={'name': name})
            if created:
                sys.stdout.write('Adding permission {}\n'.format(p))


models.signals.post_migrate.connect(
    proxy_perm_create)

Worth noting I explicitly made sure 'django.contrib.contenttypes' is first in the INSTALLED_APPS list to get this working well.

I was using this method https://djangosnippets.org/snippets/2677/ in Django 1.6 prior to beginning the upgrade process to go to latest stable 1.9, this is how I have worked around the proxy permissions issue in current versions.

This tentatively seems to be working for me, including permission creation during test database setup etc. I may try limiting the signal to my "support" app sender explicitly but wasn't sure if other app migrations could somehow mess up content-types so, since it was already using get_or_create I simply hooked it in globally to run for all apps.

Hope this helps someone!

@k1000

This comment has been minimized.

Copy link

commented May 24, 2016

updated for django1.9

`

-- coding: utf-8 --

"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""

from future import unicode_literals, absolute_import, division

import sys

from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_unicode

class Command(BaseCommand):
help = "Fix permissions for proxy models."

def handle(self, *args, **options):
    for model in apps.get_models():
        opts = model._meta
        ctype, created = ContentType.objects.get_or_create(
            app_label=opts.app_label,
            model=opts.object_name.lower(),
            defaults={'name': smart_unicode(opts.verbose_name_raw)})

        for codename, name in _get_all_permissions(opts, ctype):
            p, created = Permission.objects.get_or_create(
                codename=codename,
                content_type=ctype,
                defaults={'name': name})
            if created:
                sys.stdout.write('Adding permission {}\n'.format(p))

`

@escsun

This comment has been minimized.

Copy link

commented Mar 13, 2017

For python 3 and django 1.10.x

# -*- coding: utf-8 -*-

"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""

import sys

from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_text


class Command(BaseCommand):
    help = "Fix permissions for proxy models."

    def handle(self, *args, **options):
        for model in apps.get_models():
            opts = model._meta
            ctype, created = ContentType.objects.get_or_create(
                app_label=opts.app_label,
                model=opts.object_name.lower(),
                defaults={'name': smart_text(opts.verbose_name_raw)})

            for codename, name in _get_all_permissions(opts):
                p, created = Permission.objects.get_or_create(
                    codename=codename,
                    content_type=ctype,
                    defaults={'name': name})
                if created:
                    sys.stdout.write('Adding permission {}\n'.format(p))
@ppython

This comment has been minimized.

Copy link

commented Mar 15, 2017

For those of you with django-extensions installed, just call the management command update_permissions

https://github.com/django-extensions/django-extensions/blob/master/django_extensions/management/commands/update_permissions.py

Manually, or at the end of your migration script with a RunPython / call_command.

@pablocesar87

This comment has been minimized.

Copy link

commented Mar 24, 2017

@escsun, in for codename, name in _get_all_permissions(opts): it is also needed ctype: for codename, name in _get_all_permissions(opts, ctype):. Apart from that, it works like a charm.

@rebkwok

This comment has been minimized.

Copy link

commented Mar 31, 2017

Thank you for this! Just upgraded to django 1.10.6, and name is now deprecated on ContentType, so the line defaults={'name': smart_text(opts.verbose_name_raw)} needs to be removed.

@pablocesar87 _get_all_permissions(opts) is correct for Django 1.10, _get_all_permissions(opts, ctype) for Django <= 1.9

@xleon

This comment has been minimized.

Copy link

commented Aug 25, 2017

Here´s a working sample for django 1.11:

import sys

from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps


class Command(BaseCommand):
    help = "Fix permissions for proxy models."

    def handle(self, *args, **options):
        for model in apps.get_models():
            opts = model._meta
            sys.stdout.write('{}-{}\n'.format(opts.app_label, opts.object_name.lower()))
            ctype, created = ContentType.objects.get_or_create(
                app_label=opts.app_label,
                model=opts.object_name.lower())

            for codename, name in _get_all_permissions(opts):
                sys.stdout.write('  --{}\n'.format(codename))
                p, created = Permission.objects.get_or_create(
                    codename=codename,
                    content_type=ctype,
                    defaults={'name': name})
                if created:
                    sys.stdout.write('Adding permission {}\n'.format(p))
@wbworks

This comment has been minimized.

Copy link

commented Apr 19, 2018

As this bug is still present in version 2.0 -- here is a working version for django 2.0

import sys

from django.apps import apps
from django.utils.encoding import smart_text
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

# proxy model missing permission workaround - modified for Django 2.0
def proxy_perm_create(**kwargs):
    for model in apps.get_models():
        opts = model._meta
        print(opts.verbose_name_raw)
        ctype, created = ContentType.objects.get_or_create(
            app_label=opts.app_label,
            model=opts.object_name.lower())

        for code_tupel in _get_all_permissions(opts):
            codename = code_tupel[0]
            name = code_tupel[1]
            p, created = Permission.objects.get_or_create(
                codename=codename,
                content_type=ctype,
                defaults={'name': name})
            if created:
                sys.stdout.write('Adding permission {}\n'.format(p))


models.signals.post_migrate.connect(proxy_perm_create)

As @dckemble noticed you should adjust your INSTALLED_APPS:

Worth noting I explicitly made sure 'django.contrib.contenttypes' is first in the INSTALLED_APPS list to get this working well.

@arthurio

This comment has been minimized.

Copy link

commented Jul 5, 2018

The permissions are created for proxy models, just with the wrong content_type. I updated the code and tested in Django 1.11:

from django.apps import apps
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


def fix_proxy_permissions(**kwargs):
    for model in apps.get_models():
        opts = model._meta

        if not opts.proxy:
            continue

        # The content_type creation is needed for the tests
        proxy_content_type, __ = ContentType.objects.get_or_create(
            app_label=opts.app_label,
            model=opts.model_name,
        )

        concrete_content_type = ContentType.objects.get_for_model(
            model,
            for_concrete_model=True,
        )

        for code_tuple in _get_all_permissions(opts):
            codename = code_tuple[0]
            name = code_tuple[1]
            # Delete the automatically generated permission from Django
            Permission.objects.filter(
                codename=codename,
                content_type=concrete_content_type,
            ).delete()

            # Create the correct permission for the proxy model
            Permission.objects.get_or_create(
                codename=codename,
                content_type=proxy_content_type,
                defaults={
                    'name': name,
                })

signals.post_migrate.connect(fix_proxy_permissions)
@arthurio

This comment has been minimized.

Copy link

commented Jan 16, 2019

If anyone is interested, https://code.djangoproject.com/ticket/11154 has been fixed and will be part of the 2.2 release.

@FrankSalad

This comment has been minimized.

Copy link

commented Mar 28, 2019

FYI, for Django <= 1.9, modify the above inner loop from @arthurio's answer

for code_tuple in _get_all_permissions(opts):

to

for code_tuple in _get_all_permissions(opts, concrete_content_type):

_get_all_permissions took two parameters before Django 1.10.

@MalikRumi

This comment has been minimized.

Copy link

commented Apr 30, 2019

@arthurio I am on 2.2. While running migrations, I got:

auth.0011_update_proxy_permissions
Raw Python operation -> Update the content_type of prox…

I don't know where or how to make the rest of this truncated string show up, but it seems this 'fix' is manual, not automatic. Is that what was intended?

@rebkwok I remember struggling with this once a while back. So why are all the code samples posted after your post still using name? I find this confusing. Clarification welcome.

https://docs.djangoproject.com/en/dev/internals/deprecation/
...
1.10
...
Ability to specify ContentType.name when creating a content type instance will be removed.

Finally, to all and any reading this: I find the reference to "raw python" confusing. Do they mean runPython?
https://docs.djangoproject.com/en/dev/ref/migration-operations/#django.db.migrations.operations.RunPython

@arthurio

This comment has been minimized.

Copy link

commented Apr 30, 2019

@MalikRumi Checkout the 2.2.1 release notes and the related ticket. The fix should be automatic now.

@MalikRumi

This comment has been minimized.

Copy link

commented May 1, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.