Django Admin - Specific user (admin) content

2019-01-21 00:03发布

问题:

I'm starting to organize a new project and let's say i'll have a few models like products and catalogs.

I will allow my clients (not visitors, only specific clients) to login on Django Admin site to create, edit and delete their own catalogs.

Lets say I create a model called "Shop", create every shop (name, address, logo, contact info and etc.) and create an admin user binded to that shop.

Now I want this new admin (who's not a site admin, but a shop admin -- probably an user group) to see and edit only the catalogs linked with his shop.

Is that possible?

Should I do this inside the Django Admin or should I create a new "shop admin" app?

回答1:

First, the cautionary warning: The Django admin design philosophy is that any user with access to the admin (is_staff==True) is a trusted user, e.g. an employee, hence the "staff" designation to even gain access to the admin. While you can customize the admin to restrict areas, allowing anyone not within your organization access to your admin is considered risky, and Django makes no guarantees about any sort of security at that point.

Now, if you still want to proceed, you can restrict most everything but the shops right off the bat by simply not assigning those privileges to the user. You'll have to give all the shop owners rights to edit any of the shop models they'll need access to, but everything else should be left off their permissions list.

Then, for each model that needs to be limited to the owner's eyes only, you'll need to add a field to store the "owner", or user allowed access to it. You can do this with the save_model method on ModelAdmin, which has access to the request object:

class MyModelAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.user = request.user
        super(MyModelAdmin, self).save_model(request, obj, form, change)

Then you'll also need to limit the ModelAdmin's queryset to only those items own by the current user:

class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyModelAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(owner=request.user)

However, that will only limit what gets listed, the user could still play with the URL to access other objects they don't have access to, so you'll need to override each of the ModelAdmin's vulnerable views to redirect if the user is not the owner:

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse

class MyModelAdmin(admin.ModelAdmin):
    def change_view(self, request, object_id, form_url='', extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).change_view(request, object_id, form_url, extra_context)

    def delete_view(self, request, object_id, extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).delete_view(request, object_id, extra_context)

    def history_view(self, request, object_id, extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).history_view(request, object_id, extra_context)

UPDATE 06/05/12

Thanks @christophe31 for pointing out that since the ModelAdmin's queryset is already limited by user, you can just use self.queryset() in the change, delete and history views. This nicely abstracts away the model classname making the code less fragile. I've also changed to using filter and exists instead of a try...except block with get. It's more streamlined that way, and actually results in a simpler query, as well.



回答2:

I'm just posting this here since the top comment is no longer the most up to date answer. I'm using Django 1.9, I'm not sure when this is change took place.

For example, you have different Venues and different users associated with each Venue, the model will look something like this:

class Venue(models.Model):
    user = models.ForeignKey(User)
    venue_name = models.CharField(max_length=255)
    area = models.CharField(max_length=255)

Now, staff status for the user must be true if he allowed to log in through the django admin panel.

The admin.py looks something like:

class FilterUserAdmin(admin.ModelAdmin): 
    def save_model(self, request, obj, form, change):
        if getattr(obj, 'user', None) is None:  
            obj.user = request.user
        obj.save()
    def get_queryset(self, request):
        qs = super(FilterUserAdmin, self).queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(user=request.user)
    def has_change_permission(self, request, obj=None):
        if not obj:
            return True 
        return obj.user == request.user or request.user.is_superuser


@admin.register(Venue)
class VenueAdmin(admin.ModelAdmin):
    pass

function name has changed from queryset to get_queryset.

EDIT: I wanted to extend my answer. There's another way to return filtered objects without using the queryset function. I do want to emphasise that I don't know if this method is more efficient or less efficient.

An alternative implementation for the get_queryset method is as follows:

def get_queryset(self, request):
    if request.user.is_superuser:
        return Venue.objects.all()
    else:
        return Venue.objects.filter(user=request.user)

Furthermore, we can also filter content is the relationships are more deeper.

class VenueDetails(models.Model):
    venue = models.ForeignKey(Venue)
    details = models.TextField()

Now, if I want to filter this model which has Venue as foreignkey but does not have User, my query changes as follows:

def get_queryset(self, request):
    if request.user.is_superuser:
        return VenueDetails.objects.all()
    else:
        return VenueDetails.objects.filter(venue__user=request.user)

Django ORM allows us to access different kinds of relationships which can be as deep as we want via '__'

Here's a link to the offical docs for the above.



回答3:

I am sorry, I know it is late ,but maybe it would help anyone else. I guess that the django-permission app could help to perform the purpose.



回答4:

I think RelatedOnlyFieldListFilter should help you. Here the link to Django Doc : RelatedOnlyFieldListFilter

list_filter can be : a tuple, where the first element is a field name and the second element is a class inheriting from django.contrib.admin.FieldListFilter, for example:

class PersonAdmin(admin.ModelAdmin):
    list_filter = (
        ('is_staff', admin.BooleanFieldListFilter),
    )

You can limit the choices of a related model to the objects involved in that relation using RelatedOnlyFieldListFilter: (Vous pouvez limiter les choix d’un modèle lié aux objets concernés par la relation en utilisant RelatedOnlyFieldListFilter:)

  class BookAdmin(admin.ModelAdmin):
      list_filter = (
           ('author', admin.RelatedOnlyFieldListFilter),
      ) 

Assuming author is a ForeignKey to a User model, this will limit the list_filter choices to the users who have written a book instead of listing all users. (En supposant que author est une clé ForeignKey vers un modèle User, cela va limiter les choix de list_filter aux utilisateurs qui ont écrit un livre au lieu d’énumérer tous les utilisateurs.)