16

I extended Django admin site for my app to allow non-staff/superusers access. This is working just fine.

I created a proxy model for an existing model and registered it to my admin site, however, it doesn't appear for non-staff users. From the documentation I read, my understanding is that proxy models get their own permissions. I checked and these don't appear in the list of available permissions.

Here's my code in case it helps:

Normal Model

class Engagement(models.Model):
    eng_type = models.CharField(max_length=5)
    environment = models.CharField(max_length=8)    
    is_scoped = models.BooleanField()    

    class Meta:
        ordering = ['eng_type', 'environment']
        app_label = 'myapp'

Proxy Model

class NewRequests(Engagement):
    class Meta:
        proxy = True
        app_label = 'myapp'
        verbose_name = 'New Request'
        verbose_name_plural = 'New Requests'

Model Admin

class NewRequestsAdmin(ModelAdmin):
pass

def queryset(self, request):
    return self.model.objects.filter(is_scoped=0)

Custom Admin Registration

myapps_admin_site.register(NewRequests, NewRequestsAdmin)

I've been managing my DB with South. According to this post, you have to tamper with it a bit by following the instructions it points users to. This was a failure. My DB doesn't have a whole lot of info in it, so I uncommented South and ran a regular syncdb to rule out South. Unfortunately, this is still not working and I'm at a loss. Any help is appreciated.

Edit

This was on Django 1.4

Community
  • 1
  • 1
chirinosky
  • 4,438
  • 1
  • 28
  • 39

7 Answers7

9

This is fixed in Django 2.2, quoting release notes:

Permissions for proxy models are now created using the content type of the proxy model rather than the content type of the concrete model. A migration will update existing permissions when you run migrate.

and docs:

Proxy models work exactly the same way as concrete models. Permissions are created using the own content type of the proxy model. Proxy models don’t inherit the permissions of the concrete model they subclass.

mrts
  • 16,697
  • 8
  • 89
  • 72
8

Turns out I didn't do anything wrong. I was looking for the permissions under

myapp | New Request | Can add new request

Permissions fall under the parent model.

myapp | engagement | Can add new request

chirinosky
  • 4,438
  • 1
  • 28
  • 39
6

There is a workaround, you can see it here: https://gist.github.com/magopian/7543724

It can vary based on your django version, but the priciple is the same.

Tested with Django 1.10.1

# -*- 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_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))

How to use

  • create a directory /myproject/myapp/management/commands
  • create the file /myproject/myapp/management/__init__.py
  • create the file /myproject/myapp/management/commands/__init__.py
  • save the code above into /myproject/myapp/management/commands/fix_permissions.py
  • run /manage.py fix_permissions
Bruno João
  • 5,105
  • 2
  • 21
  • 26
  • 1
    A link to a potential solution is always welcome, but please [add context around the link](http://meta.stackexchange.com/questions/8231/are-answers-that-just-contain-links-elsewhere-really-good-answers/8259#8259) so your fellow users will have some idea what it is and why it’s there. Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline. Take into account that being barely more than a link to an external site is a possible reason as to [Why and how are some answers deleted?](http://stackoverflow.com/help/deleted-answers). – FelixSFD Dec 15 '16 at 19:46
  • 1
    Sure. I'll improve my answer. Thanks! – Bruno João Dec 15 '16 at 19:49
  • 2
    @FelixSFD Thanks for your help. Now is much better and helpful. – Bruno João Dec 15 '16 at 19:58
  • Note that for Django 1.11 you shall remove the lines with "name=....." – ger.s.brett Jun 29 '20 at 13:55
3

This is a known bug in Django: https://code.djangoproject.com/ticket/11154 (check comments for some patches)

Danny W. Adair
  • 12,498
  • 4
  • 43
  • 49
2

As of 2021 and Django 3+, the solution for missing permissions for proxy model is simple, just generate migrations with makemigrations:

app@e31a3ffef22c:~/app$ python manage.py makemigrations my_app
Migrations for 'main':
  main/migrations/0193_myproxymodel.py
    - Create proxy model MyProxyModel

I came here and wasn't really sure, what is the correct cause/solution to this problem.

1

For Django 1.11 This issue is related due to the wrong content_type_id in auth_permission table. By default, it adds the content type of the base model instead of proxy model content type.

Naresh Walia
  • 2,012
  • 2
  • 16
  • 16
0

I realize this question was closed a while ago, but I'm sharing what worked for me in case it might help others.

It turns out that even though permissions for the proxy models I created were listed under the parent apps (as @chirinosky) has mentioned, and even though I granted my non-super user all permissions, it was still denied access to my proxy models through the admin.

What I had to do was workaround a known Django bug (https://code.djangoproject.com/ticket/11154) and connect to the post_syncdb signal to properly create permissions for the proxy models. The code below is modified from https://djangosnippets.org/snippets/2677/ per some of the comments on that thread.

I placed this in myapp/models.py that held my proxy models. Theoretically this can live in any of your INSTALLED_APPS after django.contrib.contenttypes because it needs to be loaded after the update_contenttypes handler is registered for the post_syncdb signal so we can disconnect it.

def create_proxy_permissions(app, created_models, verbosity, **kwargs):
    """
    Creates permissions for proxy models which are not created automatically
    by 'django.contrib.auth.management.create_permissions'.
    See https://code.djangoproject.com/ticket/11154
    Source: https://djangosnippets.org/snippets/2677/

    Since we can't rely on 'get_for_model' we must fallback to
    'get_by_natural_key'. However, this method doesn't automatically create
    missing 'ContentType' so we must ensure all the models' 'ContentType's are
    created before running this method. We do so by un-registering the
    'update_contenttypes' 'post_syncdb' signal and calling it in here just
    before doing everything.
    """
    update_contenttypes(app, created_models, verbosity, **kwargs)
    app_models = models.get_models(app)
    # The permissions we're looking for as (content_type, (codename, name))
    searched_perms = list()
    # The codenames and ctypes that should exist.
    ctypes = set()
    for model in app_models:
        opts = model._meta
        if opts.proxy:
            # Can't use 'get_for_model' here since it doesn't return
            # the correct 'ContentType' for proxy models.
            # See https://code.djangoproject.com/ticket/17648
            app_label, model = opts.app_label, opts.object_name.lower()
            ctype = ContentType.objects.get_by_natural_key(app_label, model)
            ctypes.add(ctype)
            for perm in _get_all_permissions(opts, ctype):
                searched_perms.append((ctype, perm))

    # Find all the Permissions that have a content_type for a model we're
    # looking for. We don't need to check for codenames since we already have
    # a list of the ones we're going to create.
    all_perms = set(Permission.objects.filter(
        content_type__in=ctypes,
    ).values_list(
        "content_type", "codename"
    ))

    objs = [
        Permission(codename=codename, name=name, content_type=ctype)
        for ctype, (codename, name) in searched_perms
        if (ctype.pk, codename) not in all_perms
    ]
    Permission.objects.bulk_create(objs)
    if verbosity >= 2:
        for obj in objs:
            sys.stdout.write("Adding permission '%s'" % obj)


models.signals.post_syncdb.connect(create_proxy_permissions)
# See 'create_proxy_permissions' docstring to understand why we un-register
# this signal handler.
models.signals.post_syncdb.disconnect(update_contenttypes)
Community
  • 1
  • 1
Steely
  • 720
  • 6
  • 13