-
-
Save magopian/7543724 to your computer and use it in GitHub Desktop.
# -*- 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)) |
@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.
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
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))
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.
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)
If anyone is interested, https://code.djangoproject.com/ticket/11154 has been fixed and will be part of the 2.2 release.
@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
@MalikRumi Checkout the 2.2.1 release notes and the related ticket. The fix should be automatic now.
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)
How to Use @wbworks
Does anyone have a nice solution for moving from 1.11 to 2.2 if we have done this? When my migrations were run on deployment the first time I got IntegrityError (1062, "Duplicate entry...
because of django/contrib/auth/migrations/0011_update_proxy_permissions.py
We didn't need those permissions anymore so I just deleted them and re ran the migration then it worked fine, but what if someone had complex groups/permissions set up...?
@mdaizovi See my comment above, this has been fixed in thee 2.2.1 release.
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.