Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Admin - Specific user (admin) content

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?

like image 797
Thiago Belem Avatar asked Jun 10 '11 19:06

Thiago Belem


People also ask

How do I restrict access to admin pages in Django?

Django admin allows access to users marked as is_staff=True . To disable a user from being able to access the admin, you should set is_staff=False . This holds true even if the user is a superuser. is_superuser=True .

What is Is_active in Django?

is_active. Boolean. Designates whether this user account should be considered active. We recommend that you set this flag to False instead of deleting accounts; that way, if your applications have any foreign keys to users, the foreign keys won't break.


2 Answers

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.

like image 66
Chris Pratt Avatar answered Sep 17 '22 14:09

Chris Pratt


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.

like image 40
chatuur Avatar answered Sep 16 '22 14:09

chatuur