View permissions in Django [duplicate]

2019-01-13 16:40发布

问题:

This question already has an answer here:

  • Permission to view, but not to change! - Django 10 answers

As django admin has three permissions in it's auth : add, change, delete! I want to add view permission in this auth in admin panel.I know i have to customize permissions to add view permission in 'auth|permission|can view permission' to view all entries!

THE WAY:

[X] 1. Added 'view' to default permission list

#./contrib/auth/management/init.py
def _get_all_permissions(opts):

    "Returns (codename, name) for all permissions in the given opts."
    perms = []
    for action in ('add', 'change', 'delete', 'view'):

        perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))

    return perms + list(opts.permissions)

[X] 2. Test the 'view' permission is added to all models

run manage.py syncdb

I confirmed that view permission is now added for all tables in the auth_permissions table

[X] 3. Add "get_view_permission" to default model class.

Added get_view_permission to the model class. You can find this in the file ./db/models/options.py This is used by the admin class in the next step.

def get_view_permission(self):

    return 'view_%s' % self.object_name.lower()

[X] 4. Add "has_view_permission" to default admin class

Just to be consistent I'm going to add "has_view_permission" to the system. Looks like it should be somewhere in contrib/admin/options.py. Made sure if the user has has change permission, then view permissions are automatically implied.

# /contrib/admin/options.py
# Added has_view_permissions
def has_view_permission(self, request, obj=None):

    """
    Returns True if the given request has permission to change or view
    the given Django model instance.


    If obj is None, this should return True if the given request has
    permission to change *any* object of the given type.
    """
    opts = self.opts
    return self.has_change_permission(request, obj) or \

        request.user.has_perm(opts.app_label + '.' + opts.get_view_permission())


# modified get_model_perms to include 'view' too.
# No idea where this may be used, but trying to stay consistent
def get_model_perms(self, request):

    """
    Returns a dict of all perms for this model. This dict has the keys
    add, change, and delete and view mapping to the True/False
    for each of those actions.
    """
    return {

        'add': self.has_add_permission(request),
        'change': self.has_change_permission(request),
        'delete': self.has_delete_permission(request),
        'view': self.has_view_permission(request),

    }


# modified response_add function to return the user to the mode list
# if they added a unit and have view rights
...

    else:

        self.message_user(request, msg)

        # Figure out where to redirect. If the user has change permission,
        # redirect to the change-list page for this object. Otherwise,
        # redirect to the admin index.
        #if self.has_change_permission(request, None):
        if self.has_change_permission(request, None) or self.has_view_permission(request, None):

            post_url = '../'

        else:

            post_url = '../../../'

        return HttpResponseRedirect(post_url)

    # modified the change_view function so it becomes the details
    # for users with view permission

        #if not self.has_change_permission(request, obj):
        if not (self.has_change_permission(request, obj) or (self.has_view_permission(request, obj) and not request.POST)):

            raise PermissionDenied

        # modified the changelist_view function so it shows the list of items
        # if you have view permissions

def changelist_view(self, request, extra_context=None):

            "The 'change list' admin view for this model."
            from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
            opts = self.model._meta
            app_label = opts.app_label
            #if not self.has_change_permission(request, None):
            if not (self.has_change_permission(request, None) or self.has_view_permission(request, None)):

                raise PermissionDenied

[X] 5. Update default template to list models if user has view permission

I modified the default template in contrib/admin/templates/admin/index.html. This could also be handled by copying the file to the local templates directory instead. I made changes in both so I have a copy if a later upgrade overwrites my changes.

{% for model in app.models %}

    <tr>
    {% if model.perms.change %}

        <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>

    {% else %}

        {% if model.perms.view %}

            <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>

        {% else %}

            <th scope="row">{{ model.name }}</th>

        {% endif %}

    {% endif %}

[X] 6. Confirm user can "view" but not "change" the model

Found contrib/admin/templatetags/admin_modify.py appears to control save / save and continue buttons appearing or not. Changed "save" field from default of always True, to check for context and permissions. User should be able to save if they have change or add permissions.

'show_save': (change and context['has_change_permission']) or (context['add'] and context['has_add_permission'])

[X] 7. Remove "Save and Add another" button if user is viewing an item

Modified contrib/admin/templatetags/admin_modify.py again. I don't know what 'save_as' means so maybe I broke something, but it seems to work.

#'show_save_and_add_another': context['has_add_permission'] and
# not is_popup and (not save_as or context['add']) ,
'show_save_and_add_another': not is_popup and
    (( change and context['has_change_permission']) or (context['add'] and context['has_add_permission']))
    and
    (not save_as or context['add']),

[X] 8. Modify "view" permission to make form read only

If the user has "view" permission and "change" permission, then do nothing. Change overrides view.

If the user has "view" permission without "change" then change the default forms and add DISABLED or READONLY attributes to the form elements. Not all browsers support this, but for my purposes I can require that users use the right one. [Disabled / Readonly example][1]

Found that not all browsers honor "readonly" so it sets some controls to readonly, others to disabled. This allows users to copy data from the text controls if needed.

#/django/contrib/admin/templates/admin/change_form.html


{# JavaScript for prepopulated fields #}
{% prepopulated_fields_js %}


</div>
</form></div>
{% if has_view_permission and not has_change_permission %}

    <script type="text/javascript">
    jQuery('input:text').attr('readonly', 'readonly');
    jQuery('textarea').attr('readonly', 'readonly');
    jQuery('input:checkbox').attr('disabled', true);
    jQuery('select').attr('disabled', true);
    jQuery('.add-another').hide();
    </script>

{% endif %}

ANSWER SOURCE : ​How can I MODIFY django to create "view" permission?

Question: After following above answer i have done and can see this 127.0.0.1:8000/en-us/admin/ page as read-only **but the users in users is not visible 127.0.0.1:8000/en-us/admin/user . Need help!**

回答1:

Django 2.1 added a view permission to the default permissions. The solution below may work in earlier versions of Django. https://docs.djangoproject.com/en/2.1/releases/2.1/


This is a working solution tested in Django 1.6.2

[X] 1. Added 'view' to default permission list: OK
[X] 2. Test the 'view' permission is added to all models: OK

[X] 3. Add "get_view_permission" to default model class. is useless any more:

def get_add_permission(self):
    """
    This method has been deprecated in favor of
    `django.contrib.auth.get_permission_codename`. refs #20642
    """
    warnings.warn(
        "`Options.get_add_permission` has been deprecated in favor "
        "of `django.contrib.auth.get_permission_codename`.",
        PendingDeprecationWarning, stacklevel=2)
    return 'add_%s' % self.model_name

And that the case for all of those methods get_foo_permission

[X] 4. Add "has_view_permission" to default admin class should be:

def has_view_permission(self, request, obj=None):
    """
    Returns True if the given request has permission to change or view
    the given Django model instance.


    If obj is None, this should return True if the given request has
    permission to change *any* object of the given type.
    """
    opts = self.opts
    codename = get_permission_codename('view', opts)
    return self.has_change_permission(request, obj) or \
        request.user.has_perm("%s.%s" % (opts.app_label, codename))

if the model is an inline one check its right, so need to be aware of the right view

def get_inline_instances(self, request, obj=None):

    ...

    if not (inline.has_add_permission(request) or
            inline.has_change_permission(request, obj) or
            inline.has_delete_permission(request, obj) or
            inline.has_view_permission(request, obj)):  # add the view right
        continue

    ...

Do the modification on get_model_perms to include 'view', in the same idea do this one:

def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):

    ...

    context.update({

        ...

        'has_view_permission': self.has_view_permission(request, obj), # add the view right

        ...

    })

    ....

Allow the 'right view' to render the page (of one object) and disable the 'right view' to save the modification done on a page avoid [X] 8. Modify "view" permission to make form read only

@csrf_protect_m
@transaction.atomic
def change_view(self, request, object_id, form_url='', extra_context=None):
    "The 'change' admin view for this model."
    model = self.model
    opts = model._meta

    obj = self.get_object(request, unquote(object_id))

    # addthe view right
    if not (self.has_view_permission(request, obj) or
            self.has_change_permission(request, obj)):
        raise PermissionDenied

    ...

    inline_instances = self.get_inline_instances(request, obj)
    # do not save the change if I'm not allowed to:
    if request.method == 'POST' and self.has_change_permission(request, obj):
        form = ModelForm(request.POST, request.FILES, instance=obj)

    ...

Allow the 'right view' to render the page (the list of all objects)

@csrf_protect_m
def changelist_view(self, request, extra_context=None):
    """
    The 'change list' admin view for this model.
    """
    from django.contrib.admin.views.main import ERROR_FLAG
    opts = self.model._meta
    app_label = opts.app_label
    # allow user with the view right to see the page
    if not (self.has_view_permission(request, None) or
            self.has_change_permission(request, None)):
        raise PermissionDenied

    ....

[X] 5. Update default template to list models if user has view permission: OK but to avoid to modify the html template edit this file: contrib/admin/site.py

class AdminSite(object):

    @never_cache
    def index(self, request, extra_context=None):

        ...

        # add the view right
        if perms.get('view', False) or perms.get('change', False):
        try:
            model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
        except NoReverseMatch:
            pass

        ...

    def app_index(self, request, app_label, extra_context=None):

        ...

        # add the view right
        if perms.get('view', False) or perms.get('change', False):
            try:
                model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
            except NoReverseMatch:
                pass

        ...

[X] 6. Confirm user can "view" but not "change" the model and [X] 7. Remove "Save and Add another" button if user is viewing an item: should be ok but i did that:

'show_save_as_new': context['has_add_permission'] and not is_popup and change and save_as,
'show_save': context['has_change_permission'],

[X] 8. Modify "view" permission to make form read only: Ok but i have an other solution see above



回答2:

Adding 'view' permission to default permissions list

Your solution works, but you should really avoid editing source code if possible. There's a few ways to accomplish this within the framework:

1. Add the permission during post_syncdb():

In a file under your_app/management/

from django.db.models.signals import post_syncdb
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission

def add_view_permissions(sender, **kwargs):
    """
    This syncdb hooks takes care of adding a view permission too all our 
    content types.
    """
    # for each of our content types
    for content_type in ContentType.objects.all():
        # build our permission slug
        codename = "view_%s" % content_type.model

        # if it doesn't exist..
        if not Permission.objects.filter(content_type=content_type, codename=codename):
            # add it
            Permission.objects.create(content_type=content_type,
                                      codename=codename,
                                      name="Can view %s" % content_type.name)
            print "Added view permission for %s" % content_type.name

# check for all our view permissions after a syncdb
post_syncdb.connect(add_view_permissions)

Whenever you issue a 'syncdb' command, all content types can be checked to see if they have a 'view' permission, and if not, create one.

  • SOURCE: The Nyaruka Blog

2. Add the permission to the Meta permissions option:

Under every model you would add something like this to its Meta options:

class Pizza(models.Model):
    cheesiness = models.IntegerField()

    class Meta:
        permissions = (
            ('view_pizza', 'Can view pizza'),
        )

This will accomplish the same as 1 except you have to manually add it to each class.

3. NEW in Django 1.7, Add the permission to the Meta default_permissions option:

In Django 1.7 they added the default_permissions Meta option. Under every model you would add 'view' to the default_permissions option:

class Pizza(models.Model):
    cheesiness = models.IntegerField()

    class Meta:
        default_permissions = ('add', 'change', 'delete', 'view')