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?
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.
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.
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.
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.)
Related
I should limit choices of manytomanyfield to logged in admin user profile, in django admin.
class News(models.Model):
title=models.CharField(max_length=200)
users=models.ManyToManyField(User, blank=True, limit_choices_to={"profile__school": "request.user.profile.school"})
I have tried to implement it in admin.ModelAdmin, where I can access request.user, but couldn't find a way to do it.
You don't do this in the model layer. Django's user layer is request unaware. Some code paths don't even have a request, for example if these are done through a Python script, or a Django management command.
You can limit the choices in the ModelAdmin by overriding get_form:
class NewsAdmin(ModelAdmin):
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj=obj, change=change, **kwargs)
form.base_fields['users'].queryset = User.objects.filter(
profile__school__profile__user=request.user
)
return form
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I have two models, User and Book. Users own books and can only be seen by their owners.
NOTE: the book model is handled in a separate database, so I can't use a foreign key on Book pointing to User. Not sure if this matters.
If I'm authenticated, and send a GET /books request, I want only the books owned by the user to be shown. If I'm not authenticated, I should get a 403 error.
Where should I implement this logic?
I could do it in the View, with something like this:
class BookView(APIView):
"""
Get books
"""
permission_classes = (IsAuthenticated, IsBookOwner,)
queryset = Book.objects.all()
serializer_class = BookSerializer
def post(self, request):
# create a book
def get(self, request):
books = Book.objects.filter(owner_id=request.user.owner_id)
serializer = self.serializer_class(books, many=True)
return Response(serializer.data)
class IsBookOwner(permissions.BasePermission):
"""
Object-level permission to only allow seeing his own books
"""
def has_object_permission(self, request, view, obj):
# obj here is a Book instance
return obj.owner_id == request.user.owner_id
Is this the correct way to do it? Also, is the IsBookOwner permission doing anything here?
User model dont have owner_id field. You should change request.user.owner_id to request.user.id
For get request you dont need IsBookOwner Permission. You already check owner in your queryset. if you need to check book owner entire view, it is okay.
books = Book.objects.filter(owner_id=request.user.owner_id)
I have this page :8000/edit/6/ that show a form to update an exciting model and i logged in as X, if i looged in as Y and try to open that page i can see it and update. So this is with no doubt a big bug and dangerous.
Here is my view code
class VideoUpdate(UpdateView):
form_class = VideoForm
model = Video
template_name = 'videos/video_update.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(VideoUpdate, self).dispatch(*args, **kwargs)
def form_valid(self, form):
messages.info(self.request, _('Event is updated successfully'))
return super(VideoUpdate, self).form_valid(form)
Is there a way to check the model object id with the user id. A simple question from a newbie
Solution:
Actually there are two solutions that works for me in views.py, one is using the get_queryset method
def get_queryset(self):
base_qs = super(VideoUpdate, self).get_queryset()
return base_qs.filter(user=self.request.user.get_profile)
or using get_object method
def get_object(self):
video = get_object_or_404(Video, pk=self.kwargs['pk'])
if video.user != self.request.user.get_profile():
raise Http404
return video
Your question is not entirely clear to me but I think you want to restrict a view from registered but unauthorized users. Usually, this can be better achieved in your views instead of your models:
# views.py
def edit_form(request, parameter_indicating_user):
user = request.user
if #some logic:
# if user is equal to the user indicated by some parameter
# (id, username, etc) then allow that view to be rendered
else:
raise Http404 # or redirect the unauthorized user
I am interpreting your question as meaning the following.
When using class-based views - is there a way to control whether the specific instance of a form is editable by a given user ie. we have record #1 of a model. When you are logged in as user X you can edit record #1, but user Y is not allowed to edit record #1.
If this is what you are talking about you are going to require row/object level permissions, which I have found is best when using django-guardian.
Specifically, when using class-based views you can use the PermissionRequiredMixin, which can be found here: http://packages.python.org/django-guardian/api/guardian.mixins.html#permissionrequiredmixin
If you are looking for just controlling whether User X vs. User Y can edit any instance of that form. ie. User X can edit Form A values. Then you will just need to manage the permissions appropriately and then check if the user has that permission in the view.
JD
I'm using django's admin to let users manage model instances of a specific model.
Each user should be able to manage only his model instances. (except for administrators which should manage all).
How do I filter the objects in the admin's changelist view?
Thoughts:
I guess the most elegant approach would be to use Object-level permissions. Anyone aware of an implementation of this?
Is it possible to do by overriding the admin's view using ModelAdmin.changelist_view?
Does list_select_related have anything to do with it?
You can override the admin's queryset-method to just display an user's items:
def queryset(self, request):
user = getattr(request, 'user', None)
qs = super(MyAdmin, self).queryset(request)
if user.is_superuser:
return qs
return qs.filter(user=user)
Besides that you should also take care about the has_change_permission and has_delete_permission-methods, eg like:
def has_delete_permission(self, request, obj=None):
if not request.user == obj.user and not request.user.is_superuser:
return False
return super(MyAdmin, self).has_delete_permission(request, obj)
Same for has_change_permission!
list_select_related is only used when getting the admin's queryset to get also related data out of relations immediately, not when it is need!
If your main goal is only to restrict a user to be not able to work with other's objects the above approch will work, if it's getting more complicated and you can't tell a permissions simply from ONE attribute, like user, have a look into django's proposal's for row level permissions!
Let's say I have a site where Users can add Entries through admin panel. Each User has his own Category he is responsible for (each Category has an Editor assigned through ForeingKey/ManyToManyField).
When User adds Entry, I limit the choices by using EntryAdmin like this:
class EntryAdmin(admin.ModelAdmin):
(...)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'category':
if request.user.is_superuser:
kwargs['queryset'] = Category.objects.all()
else:
kwargs['queryset'] = Category.objects.filter(editors=request.user)
return db_field.formfield(**kwargs)
return super(EntryAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This way I can limit the categories to which a User can add Entry and it works perfect.
Now the tricky part: On the Entry changelist/action page I want to show only those Entries which belong to current User's Category. I tried to do this using this method:
def changelist_view(self, request, extra_context=None):
if not request.user.is_superuser:
self.queryset = self.queryset.filter(editors=request.user)
But I get this error:
AttributeError: 'function' object has no attribute 'filter'
This is strange, because I thought it should be a typical QuerySet. Basically such methods are not well documented and digging through tons of Django code is not my favourite sport.
Any ideas how can I achieve my goal?
Warning: This answer is from 2010, and is not useful for Django >= 1.8.
queryset is a method on ModelAdmin which returns a queryset. You need to override it on your EntryAdmin class.
def queryset(self, request):
qs = super(EntryAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(editors=request.user)
Changing the queryset will limit the Entries shown in the list view. You also need to override has_change_permission to ensure that the user has permission to edit the object on the individual object editing page. See the following blog post by James Bennett for further details:
http://www.b-list.org/weblog/2008/dec/24/admin/