Django: Filter users by user role - django

I am using the django admin site for my web app, but i am having a problem. I need that the staff users can change, create and delete another staff users, but i don't want that they change the informations of the superusers. I want to know if is possible filter the user list by role (the staff user don't see the superusers in the list).

Finally I found how to do this, I leave the code here just in case someone hav the same problem that I had
def get_queryset(self, request):
queryset = super(UserAdmin, self).get_queryset(request)
if request.user.is_superuser:
return queryset
return queryset.filter(is_superuser=False)

You will need to create a custom ModelAdmin for the User model. I recommend you to inherit from the original one and then you can override the get_queryset method.
You should end with:
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class MyUserAdmin(UserAdmin):
def get_queryset(self, request):
qs = super(MyUserAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(is_superuser=False)
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)

Related

Django admin : show records for respective user only

I have a simple model, say Resources. And I have fewer than 20 users and model admin serves the purpose to record requests.
Problem is that all users can see all records in model admin site.
Can this behaviour be changed to only show records created by same user only ?
Thank you in anticipation.
The django doc has an example that does almost exactly what you want:
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset
The idea is to override the get_queryset() method in the model admin view:
# admin.py
from django.contrib import admin
class YourModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyModelAdmin, self).get_queryset(request)
return qs.filter(author=request.user)
admin.site.register(YourModel, YourModelAdmin)
You can adapt the queryset filter to even more specific needs at will.
UPDATE 2020:
Anyone who is curious as to what is the author field, then that is established in the models.py file of your app. For the admin.py part, you can visit the docs.
Step 1:
Make sure in your permissions you give access to the apps you want your users to have CRUD functionality over.
Step 2:
Admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
Models.py
from django.contrib.auth import get_user_model
class Lecture(models.Model):
author = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)

How can I make a Django REST framework /me/ call?

Suppose I have a ViewSet:
class ProfileViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows a user's profile to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
...and a HyperlinkedModelSerializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
read_only_fields = ('user',)
I have my urls.py set up as:
router.register(r'profiles', api.ProfileViewSet, base_name='profile')
This lets me access e.g. /api/profile/1/ fine.
I want to set up a new endpoint on my API (similar to the Facebook API's /me/ call) at /api/profile/me/ to access the current user's profile - how can I do this with Django REST Framework?
Using the solution by #Gerard was giving me trouble:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly..
Taking a look at the source code for retrieve() it seems the user_id is not used (unused *args)
This solution is working:
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from rest_framework import filters
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.decorators import list_route
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
User = get_user_model()
queryset = User.objects.all()
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('username', 'email', 'usertype')
search_fields = ('username', 'email', 'usertype')
#list_route(permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
User = get_user_model()
self.object = get_object_or_404(User, pk=request.user.id)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
Accessing /api/users/me replies with the same data as /api/users/1 (when the logged-in user is user with pk=1)
You could create a new method in your view class using the list_route decorator, like:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route()
def me(self, request, *args, **kwargs):
# assumes the user is authenticated, handle this according your needs
user_id = request.user.id
return self.retrieve(request, user_id)
See the docs on this for more info on #list_route
I hope this helps!
You can override the get_queryset method by filtering the queryset by the logged in user, this will return the logged in user's profile in the list view (/api/profile/).
def get_queryset(self):
return Profile.objects.filter(user=self.request.user)
or
def get_queryset(self):
qs = super(ProfileViewSet, self).get_queryset()
return qs.filter(user=self.request.user)
or override the retrieve method like so, this will return the profile of the current user.
def retrieve(self, request, *args, **kwargs):
self.object = get_object_or_404(Profile, user=self.request.user)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
From Gerard's answer and looking at the error pointed out by delavnog, I developed the following solution:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route(methods=['GET'], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
self.kwargs.update(pk=request.user.id)
return self.retrieve(request,*args, **kwargs)
Notes:
ModelViewSet inherits GenericAPIView and the logic to get an object is implemented in there.
You need to check if the user is authenticated, otherwise request.user will not be available. Use at least permission_classes=[IsAuthenticated].
This solution is for GET but you may apply the same logic for other methods.
DRY assured!
Just override the get_object()
eg.
def get_object(self):
return self.request.user
Just providing a different way. I did it like this:
def get_object(self):
pk = self.kwargs['pk']
if pk == 'me':
return self.request.user
else:
return super().get_object()
This allows other detail_routes in the ViewSet to work like /api/users/me/activate
I've seen quite a few fragile solutions so I thought I'll respond with something more up-to-date and safer. More importantly you don't need a separate view, since me simply acts as a redirection.
#action(detail=False, methods=['get', 'patch'])
def me(self, request):
self.kwargs['pk'] = request.user.pk
if request.method == 'GET':
return self.retrieve(request)
elif request.method == 'PATCH':
return self.partial_update(request)
else:
raise Exception('Not implemented')
It's important to not duplicate the behaviour of retrieve like I've seen in some answers. What if the function retrieve ever changes? Then you end up with a different behaviour for /me and /<user pk>
If you only need to handle GET requests, you could also use Django's redirect. But that will not work with POST or PATCH.
Considering a OneToOneField relationship between the Profile and the User models with related_name='profile', I suggest the following as the #list_route has been deprecated since DRF 3.9
class ProfileViewSet(viewsets.GenericViewSet):
serializer_class = ProfileSerializer
#action(methods=('GET',), detail=False, url_path='me', url_name='me')
def me(self, request, *args, **kwargs):
serializer = self.get_serializer(self.request.user.profile)
return response.Response(serializer.data)

Django permissions separate users by country

I have a question regarding permissions. I would like to separate my Users who are marked as Staff ( and can enter the administration page) through countries. So for example an admin from Netherlands can only see and edit the users from netherlands. What is the best approach to do that? Should I make the querys in a way so users can only see users from their own country and than I can customize it with permissions?
my models.py defines a country
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', unique=True)
country = models.CharField("Country", max_length=150, blank=False)
and I thought about something like this in admin.py
def queryset(self, request):
qs = super(ProfileAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(country=request.user.country)
You are thinking in the right direction. It is the proper way to filter objects visible to admins.
Small corrections: the name of the method is get_queryset() and to access the user's country you should use the profile relation:
def get_queryset(self, request):
qs = super(ProfileAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(country=request.user.profile.country)
UPDATE: To filter users in the standard User admin you have to replace the UserAdmin with your own subclass:
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class MyUserAdmin(UserAdmin):
def get_queryset(self, request):
qs = super(MyUserAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(profile__country=request.user.profile.country)
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)

How to exclude instances from Admin site in Django

In Django, I have created another separate Admin site. This is what my SiteUserAdmin class looks like:
class SiteUserAdmin(UserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = ('username' ,'email',)
fieldsets = (
(None,{'fields':('username','email','password')}),('Permissions',{'fields':('is_active','is_admin_user','groups')})
)
Among all the users, there are superusers whose "is_superuser = True". Is there anyway to hide such superusers from the list of editable users of the admin site?
In another word, I am not excluding other fields from the Admin site, but rather hide some instances of User from being edited in the Admin site.
How about just overriding the get_queryset() method.
class SiteUserAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(SiteUserAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(is_superuser=False)
Docs: https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset
Override the get_queryset() method:
class SiteUserAdmin(UserAdmin):
...
def get_queryset(self, request):
qs = super(SiteUserAdmin, self).get_queryset(request)
return qs.exclude(is_superuser=True)

Django user hierarchy

I'm to make a web app that will implement something like user hierarchy. I would like to do it like this:
1. Superuser makes users (with some permissions), who can also add users (only with permissions which they own). This users can also add users, and so on. No one should be able to edit users who have more permissions.
The thing is that I want to make Django Admin Panel avaliable for all of these users. Is it even possible to make such thing? I've searched the web and didn't find solution. Thanks for advice.
You'll need to create your own views for adding users if you want to control the created users' permissions. On the Django admin site, any user that can create users can create superusers.
From the Django docs on creating users:
If you want your own user account to be able to create users using the Django admin site, you'll need to give yourself permission to add users and change users (i.e., the "Add user" and "Change user" permissions). If your account has permission to add users but not to change them, you won't be able to add users. Why? Because if you have permission to add users, you have the power to create superusers, which can then, in turn, change other users. So Django requires add and change permissions as a slight security measure.
Every user who'll need access to the admin must have the is_staff=True flag. It's never a good idea to allow users not associated with your organization access to the admin. Seriously, just don't do it. If that's your plan, find another.
That said, it can be done, but it's not for the faint of heart. There's a lot involved. First subclass the default UserCreationForm and UserChangeForm (Auth uses two separate forms for it's admin). Override the __init__ method of each to pull out the request from kwargs (Forms don't get the request by default, but it's necessary here, so you have to do a bit of a workaround.) Then, subclass the default UserAdmin, set form and add_form to the new forms and override get_form (to pass in request) and each of the has_foo_permission methods to limit access. The queryset method also needs to be overrode so users only see users they can modify in the admin.
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import Group, Permission
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
pass
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CustomUserCreationForm, self).__init__(*args, **kwargs)
# Limit groups and permissions to those that belong to current user
if self.request and not self.request.user.is_superuser:
self.fields['groups'].queryset = self.request.user.groups.all()
self.fields['user_permissions'].queryset = self.request.user.user_permissions.all()
class CustomUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
pass
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CustomUserChangeForm, self).__init__(*args, **kwargs)
# Limit groups and permissions to those that belong to current user
if self.request and not self.request.user.is_superuser:
self.fields['groups'].queryset = self.request.user.groups.all()
self.fields['user_permissions'].queryset = self.request.user.user_permissions.all()
class CustomUserAdmin(UserAdmin):
form = UserChangeForm
add_form = UserCreationForm
limited_fieldsets = ( # Copied from default `UserAdmin`, but removed `is_superuser`
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
elif not request.user.is_superuser:
return self.limited_fieldsets
else:
return super(CustomUserAdmin, self).get_fieldsets(request, obj)
def get_form(self, request, obj=None, **kwargs):
"""Return a metaclass that will automatically pass `request` kwarg into the form"""
ModelForm = super(LinkAdmin, self).get_form(request, obj, **kwargs)
class ModelFormMetaClass(ModelForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return ModelForm(*args, **kwargs)
return ModelFormMetaClass
def has_add_permission(self, request):
"""
If not superuser only allow add if the current user has at least some
groups or permissions. (they'll have to be able to at least have something
to assign the user they are creating).
"""
if not request.user.is_superuser:
if not request.user.groups.exists() or not request.user.user_permissions.exist():
return False
return True
def has_change_permission(self, request, obj=None):
"""
If not superuser, current user can only modify users who have a subset of the
groups and permissions they have.
"""
if obj and not request.user.is_superuser:
# Check that all of the object's groups are in the current user's groups
user_groups = list(request.user.groups.all())
for group in obj.groups.all():
try:
user_groups.index(group)
except ValueError:
return False
# Check that all of the object's permissions are in the current user's permissions
user_permissions = list(request.user.user_permissions.all())
for permission in obj.user_permissions.all():
try:
user_permissions.index(permission)
except ValueError:
return False
return True
def has_delete_permission(self, request, obj=None):
"""Same logic as `has_change_permission`"""
return self.has_change_permission(request, obj)
def queryset(self, request):
qs = super(CustomUserAdmin, self).queryset(self, request)
if request.user.is_superuser:
return qs
else:
"""
This part is a little counter-intuitive. We're going to first get a
list of all groups/permissions that don't belong to the user, and
then use that to exclude users that do have those from the queryset.
"""
user_group_pks = [g.pk for g request.user.groups.values('pk')]
exclude_groups = Group.objects.exclude(pk__in=user_group_pks)
user_permission_pks = [p.pk for p in request.user.user_permissions.values('pk')]
exclude_permissions = Permission.objects.exclude(pk__in=user_permission_pks)
return qs.exclude(groups__in=exclude_groups, user_permissions__in=exclude_permissions)
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
Django contains a inbuilt system for maintaining User Hierarchy - Django-RBAC.
RBAC stands for Role Based Access Control. Its a mechanism for creating and managing permission based on hierarchy. You just need to study this a bit.