How can I MODIFY django to create "view" permission?
Asked Answered
D

6

31

I've recently started using django to administer a large existing application that was grown organically over the years using twisted.web. I started experimenting with django and it's automatic admin interface and I've been very pleased with the results.

One thing that seems to be missing for my purposes is the ability to give users read only access to data. For instance we have a role where people are allowed to login and create purchase orders. They also need to be able to view, but not edit other client or product data.

How would I create "view" permissions in the django admin so users can modify data for some tables, while having read only access to others?

Update: Django Admin appears to give me the CUD of a CRUD interface. How do I get the Read Only part with associated permissions and groups?

Update 2010-Feb-12: Django 1.2 will now include read-only. Details below.


I answered my own question I guess. Moving the content down to a real answer below.

Dosage answered 26/8, 2009 at 17:39 Comment(1)
An actual has_view_permission was introduced in Django 2.1 (not 1.2). Also see docs here.Justiciary
D
2

The ability to add read-only fields to the admin view is now included in Django version 1.2.

See ticket number 342 http://code.djangoproject.com/ticket/342

See changeset number 11965 http://code.djangoproject.com/changeset/11965

See documentation http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields

Dosage answered 26/8, 2009 at 17:39 Comment(2)
readonly_fields != readonly permission. It's not easily configurable per user.Pinard
The ticket for read-only model permissions is this one: #820 and while it has a patch attached, it was closed as wontfix, on the grounds that the admin should only be an interface for editing.Outlier
D
35

This is how I changed Django 1.0.2 to add 'view' permissions. Sorry there is no diff available.

[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

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 %}
Dosage answered 26/8, 2009 at 17:39 Comment(9)
Django 1.2 now includes this feature.Dosage
Could you provide an url for this? Django including view permission.Provencher
Juanjo Conti: I've already included a link directly to the documentation, as well as the tickets involved in getting the feature into the system in my other answer to this question. I've flagged it as the correct answer now since it is now the correct answer as of Django 1.2.Dosage
It didn't work as expected. A user who has only view permission, don't have any link to 'view'. Ridiculous!Appurtenance
@GreatTurtle thank's for great answer! hmmm i am using djano 1.6 but there is no view permission in it. i followed your answer but nothing works. any alternative!Symon
@TameenMalik - This is now called "read only fields" I think. docs.djangoproject.com/en/dev/ref/contrib/admin/… I haven't used Django in over a year. If this doesn't help just as it as a question and someone more up to date should direct you to the answers you seek.Dosage
Thank's . @GreatTurtle here we have model user,groups in admin . i want to make them all readonly by assigning view permission. how to do this :(Symon
Hello @Tameen Malik,Ask the question directly in Stack Exchange as a normal question. I'm out of the loop on Django. I don't know how it "should" be done. I know python well enough I would just dig into the source and make it work however I wanted. I can't explain how to do that in a 600 character response on a 4 year old question.Dosage
@GreatTurtle I posted this question here https://mcmap.net/q/471037/-view-permissions-in-django-duplicate on 16th April and no one replies! so that's why i thought you can as my problem is similar to this one mentioned above! Note: "I also followed these patches but this one is for old djano code.djangoproject.com/attachment/ticket/820/… ... ans this one is for data-browse code.djangoproject.com/ticket/8936"! and Model.Admin readonly fields too --Symon
S
11

This snippet will make superuser the only one with write access.

class AdminOwn(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return self.readonly_fields
        #get all fields as readonly
        fields = [f.name for f in self.model._meta.fields]
        return fields
Strong answered 26/8, 2009 at 17:39 Comment(1)
I find this answer very useful, but want to add a bit. You probably want to make many-to-many fields readonly as well fields = [f.name for f in self.model._meta.fields + self.model._meta.many_to_many]Upholsterer
L
6

It's right there in the admin. You can set permissions for Users and Groups in the admin to add, change and delete specific models.

Update: Sorry, I misunderstood the question because I misinterpreted the word view to give it the Django meaning rather than "read-only". If you want read-only using the admin, I think you'll need to do a bit of work. See this thread, where James Bennett (Django release manager) says:

As you'll find by searching the archives of this list, this is not something the Django admin interface is designed to support, and so any solution will need to come entirely from you writing your own code.

and

The Django admin operates on three permissions: "add", "change" and "delete". There is no "view but make no modifications" permission, hence there is no way to apply such a restriction without doing significant custom coding.

The additional work will involve you adding a "readonly" permission for certain models, and changing the basic admin templates to check if the user has that permission - and if so, disabling certain controls (such as save buttons) and making others read-only. That will prevent casual tinkering, but you may also need to modify server-side logic to check the same permission, to avoid any POSTs made in a sneaky way to circumvent permissions.

Loner answered 26/8, 2009 at 17:57 Comment(2)
I don't think you understood the question. How would I give someone permission to VIEW the data WITHOUT the ability to add, change or delete the data for a specific model.Dosage
Hello Vinay, this is now included in django 1.2. Thanks for your previous help.Dosage
C
3

You can create a "readonly" permission in your model and use the code of jasuca with a modification:

models.py:

class MyModel(models.Model):
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=256, null=True, blank=True)

    class Meta:
        permissions = (
            ('readonly_mymodel','Readonly MyModel'),
        )

admin.py:

class MyModelAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if not request.user.is_superuser and request.user.has_perm('mymodel.readonly_mymodel'):
            return [f.name for f in self.model._meta.fields]
        return self.readonly_fields

In the admin of the aplication you have to give permission of "change" and "readonly" to the user.

Chenee answered 26/8, 2009 at 17:39 Comment(0)
D
2

The ability to add read-only fields to the admin view is now included in Django version 1.2.

See ticket number 342 http://code.djangoproject.com/ticket/342

See changeset number 11965 http://code.djangoproject.com/changeset/11965

See documentation http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields

Dosage answered 26/8, 2009 at 17:39 Comment(2)
readonly_fields != readonly permission. It's not easily configurable per user.Pinard
The ticket for read-only model permissions is this one: #820 and while it has a patch attached, it was closed as wontfix, on the grounds that the admin should only be an interface for editing.Outlier
D
0

You can create groups in the auth module. Then in admin.py based on user group login, set the modeladmin's readonly_fields attribute. Add the method def has_add_permission(self, request) to return false for the group with readonly permission. Give the add, modify permissions to the group. They will be able to only read the model attributes.

Diligent answered 26/8, 2009 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.