Why does Django not have a view permission? - python

I have an active Django project where the admin panel is used by the customer support team. Django lacks a view permission because of which I have to assign the change permission to the customer support team which is slightly dangerous. I have some models for which the customer support team needs just the view access and not the change access because of security issues. Why is the view permision missing in Django? Any workaround to this?

Here's a workaround.
Models
Simply create models with view permission by inheriting them from mixin:
class ViewPermissionsMixin(models.Model):
"""
Mixin adds view permission to model.
"""
class Meta:
abstract=True
default_permissions = ('add', 'change', 'delete', 'view')
Example model:
class ExampleModel(ViewPermissionsMixin):
name = models.CharField(max_length=255)
class Meta(ViewPermissionsMixin.Meta):
abstract = False
This will add view permission that can be assigned to certain user/group. But such permission is useless without proper admin modification.
Admins
Here is mixin for your admins:
class AdminViewMixin(admin.ModelAdmin):
def has_perm(self,user,permission):
"""
Usefull shortcut for `user.has_perm()`
"""
if user.has_perm("%s.%s_%s" % (self.model._meta.app_label,permission,self.model.__name__.lower(),)):
return True
return False
def has_module_permission(self, request): # Django 1.8
pass
def has_change_permission(self, request, obj=None):
"""
Necessary permission check to let Django show change_form for `view` permissions
"""
if request.user.is_superuser:
return True
elif self.has_perm(request.user,'change'):
return True
elif self.has_perm(request.user,'view'):
return True
return super(AdminMixin, self).has_change_permission(request, obj)
def get_readonly_fields(self, request, obj=None):
"""
Turn each model field into read-only for `viewers`
"""
all_model_fields = []
for field in self.model._meta.fields:
# TODO in Django 1.8 use ModelAdmin.get_fields()
if not field.auto_created \
and (not hasattr(field,'auto_now_add') or not field.auto_now_add) \
and (not hasattr(field,'auto_now') or not field.auto_now) \
:
all_model_fields.append(field.name)
if request.user.is_superuser:
return self.readonly_fields
elif self.has_perm(request.user,'change'):
return self.readonly_fields
elif self.has_perm(request.user,'view'):
return all_model_fields
return self.readonly_fields
def change_view(self, request, object_id, extra_context=None):
"""
Disable buttons for `viewers` in `change_view`
"""
if request.user.is_superuser:
pass
elif self.has_perm(request.user,'change'):
pass
elif self.has_perm(request.user,'view'):
extra_context = extra_context or {}
extra_context['hide_save_buttons'] = True
return super(AdminViewMixin, self).change_view(request, object_id, extra_context=extra_context)
Example admin:
#admin.register(models.ExampleModel)
class ExampleAdmin(AdminViewMixin):
list_display = ('name',)
pass
Finally just assign view permission for specific models to any user or group in your Django Admin.

I think this should work:
1.Add "view" permission, see https://stackoverflow.com/a/23411901/1266258
2.Customize "change" permission:
class FooAdmin(ModelAdmin):
def has_change_permission(self, request, obj=None):
# user can view the change list
if not obj and request.user.has_perm('myapp.view_foo'):
return True
# user can view the change form and change the obj
return request.user.has_perm('myapp.change_foo')

Related

Django : class based view with multiple permissions

I am trying to create a class based view with different permissions per function. I have already created my permissions in a file that I import :
utils.py
from rest_framework.permissions import BasePermission
from rest_framework.authentication import TokenAuthentication
from rest_framework.views import APIView
class IsOwner(BasePermission):
"""
Check if the user who made the request is owner.
Use like that : permission_classes = [IsOwner]
"""
def has_object_permission(self, request, view, obj):
# if request.method in permissions.SAFE_METHODS:
# return True
return obj.user == request.user
class IsAdmin(BasePermission):
"""
Check if the user who made the request is admin.
Use like that : permission_classes = [IsAdmin]
"""
def has_permission(self, request, view):
return request.user.is_admin
class BaseView(APIView):
"""
Check if a user is authenticated
"""
authentication_classes = [
TokenAuthentication,
]
class AdminOrOwnerView(APIView):
"""
Check if a user is admin or owner
"""
authentication_classes = ( IsOwner | IsAdmin,)
I want to create a class with different method. The GET method would allow admins to have a view of all users. The POST method would allow any user to connect.
I have already created the serializer for each of these methods but I cannot assign different permissions per method.
This is my class :
class Users(APIView):
def get(self, request):
"""Only for admin"""
try:
user = Users.objects.all()
except User.DoesNotExist():
return HttpResponse(status=404)
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
"""For everyone"""
serializer = RegistrationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.error)
How I can apply my permissions on each of the methods separately ?
Thank you in advance for your help
You just need to create a permission class like this:
class CustomPermissionClass(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET':
# logic for GET method
elif request.method == 'POST'
# logic for POST metod
# default logic
And add it to your view:
class Users(APIView):
permission_classes = [CustomPermissionClass]

Django admin inline - show only when changing object

I have a UserProfile which has many required (not null but blank) fields and I'm trying not to show it when adding a user.
I tried multiple things, for example:
def get_formsets_with_inlines(self, request, obj=None):
if not obj:
return []
return super().get_formsets_with_inlines(request, obj)
But after saving User, django raises error which says that fields from UserProfile can't be null.
Do you know how to make it work?
As of Django 3.0, there is a ModelAdmin.get_inlines() method, which you can override like this:
def get_inlines(self, request, obj=None):
if obj:
return [FirstInline, SecondInline]
else:
return []
See Django Documentation.
ModelAdmin provides a method get_inline_instances for conditional inlines.
from the docs:
The get_inline_instances method is given the HttpRequest and the obj
being edited (or None on an add form) and is expected to return a list
or tuple of InlineModelAdmin objects.
for example:
class MyModelAdmin(admin.ModelAdmin):
inlines = (MyInline,)
def get_inline_instances(self, request, obj=None):
return [inline(self.model, self.admin_site) for inline in self.inlines]
here you can check if obj is present or not.
One important point from the docs:
If you override this method, make sure that the returned inlines are
instances of the classes defined in inlines or you might encounter a
“Bad Request” error when adding related objects.
I use "get_inlines()" added to Django since v3.0 instead of "inlines = ()" as shown below so that "AddressInline" is not displayed when adding a user but it's displayed when changing a user:
# "admin.py"
class AddressInline(admin.TabularInline):
model = Address
#admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
# inlines = (AddressInline,)
# Here
def get_inlines(self, request, obj=None):
if obj:
return (AddressInline,)
else:
return ()
fieldsets = (
# ...
)
add_fieldsets = (
# ...
)

Permission class in Django

I use Django group permissions, I can assign some permission for users.
Here is my test scenario:
I have a Company Model,
User1.hasPerm -> view_company, change_company, add_company, delete_company
Permissions.py:
class HasPermissions(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.has_perm('MYAPP.view_company'):
return True
else:
return False
if request.user.has_perm('MYAPP.change_company'):
return True
else:
return False
if request.user.has_perm('MYAPP.add_company'):
return True
else:
return False
if request.user.has_perm('MYAPP.delete_company'):
return True
else:
return False
return True
CompanyView.py:
class CompanyViewSet(ModelViewSet):
queryset = Company.objects.all()
filter_class = CompanyFilter
serializer_class = CompanySerializer
permission_classes = [IsAuthenticated, HasPermissions]
def get_queryset(self):
if self.request.user.is_authenticated and self.request.user.is_active:
company = Company.objects.filter(companyUser__exact=self.request.user)
return company
else:
return Company.objects.all()
I wrote HasPermissions function to control user permissions, and this function works only CompanyView. I want to make global this function for I can control all view.
HasPermissions function is like hard coding, I want to change the more usable version to control all views.
How can ı do this?
You do not need to write this yourself. Django already has a DjangoModelPermissions [drf-doc] for that:
This permission class ties into Django's standard django.contrib.auth
model permissions. This permission must only be applied to views that
have a .queryset property set. Authorization will only be granted if
the user is authenticated and has the relevant model permissions
assigned.
POST requests require the user to have the add permission on the
model.
PUT and PATCH requests require the user to have the change permission on the model.
DELETE requests require the user to have the delete permission on the model.
So you can use:
class CompanyViewSet(ModelViewSet):
queryset = Company.objects.all()
filter_class = CompanyFilter
serializer_class = CompanySerializer
permission_classes = [IsAuthenticated, DjangoModelPermissions]

Django Rest Framework permissions not being called

I'm currently converting all my views to generics, as I like how cleaner the code gets. I am trying to make my User detail view, like so:
# User.views
from Common import view_mixins, view_filters, view_permissions
class UserDetail(view_mixins.IntOrStrLookupMixin, generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
lookup_fields = ('user__pk', 'user__username')
lookup_url_kwarg = 'userid'
filter_backends = (view_filters.ResourceVisibilityFilter, )
permission_classes = (view_permissions.IsOwnerOrReadOnly, )
serializer_class = ProfileSerializer
def update(self, request, *args, **kwargs):
user = self.get_object()
return Response('whatever')
# Common.view_permissions
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class IsOwnerOrReadOnly(permissions.BasePermission):
'''
Owner of object can GET, PUT, DELETE. Everyone else can GET.
'''
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
print('did you call me?')
return (
request.method in SAFE_METHODS
or
obj.user == request.user
)
# Common.view_mixins
class IntOrStrLookupMixin(object):
"""
Apply to views that can be looked up by slug or pk
"""
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.kwargs:
argument = self.kwargs[field]
if is_int(argument):
filter[self.lookup_fields[0]] = argument
else:
filter[self.lookup_fields[1]] = argument
return get_object_or_404(queryset, **filter)
So my issue is, the permission is never called. I can get the user just fine, but anyone can do PUT or DELETE on which I am trying to prevent.
In my mixin, I had to call the check_object_permissions method.
class IntOrStrLookupMixin(object):
"""
Apply to views that can be looked up by slug or pk
"""
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.kwargs:
argument = self.kwargs[field]
if is_int(argument):
filter[self.lookup_fields[0]] = argument
else:
filter[self.lookup_fields[1]] = argument
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
return obj
I think I encountered this problem. The object level permissions are only called when you use get_object() method to get the object being operated on. Update your update() with this line and you should see the permissions being called. And update your custom get_object() to either call the super method or call the permissions directly
class UserDetail(view_mixins.IntOrStrLookupMixin, generics.RetrieveUpdateDestroyAPIView):
... blah blah blah
def update(self, request):
user = self.get_object()
return Response('whatever')
class IntOrStrLookupMixin(object):
def get_object(self):
... retrieve the object ...
self.check_object_permissions(self.request, obj)
return obj
Edit: I filed a bug report about this with the DRF team and they updated the docs. On the permission page in the docs it says "Object level permissions are run by REST framework's generic views when .get_object() is called". I agree that this is a rather subtle thing and is easy to miss.
Edit #2: Looks like the problem is not only in the update() not calling get_object(), but also in the IntOrStrLookupMixin mixin redefining get_object() method. Updated the code to reflect

Filter django admin by logged in user (Foreign Key showing up)

I want the admin users to see only the model instances they created. I followed these instructions Filter django admin by logged in user
class FilterUserAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'user', None) is None: #Assign user only the first time, superusers can edit without changing user
obj.user = request.user
obj.save()
def 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:
# the changelist itself
print('query change')
return True # So they can see the change list page
return obj.user == request.user or request.user.is_superuser
class CampaignAdmin(FilterUserAdmin):
...
This is how my code looks like. Everything works fine. I need one more model with campaign as foreign key.
class ScreenAdmin(FilterUserAdmin):
...
admin.site.register(Campaign,CampaignAdmin)
admin.site.register(Screen,ScreenAdmin)
now when I go to screens, and I see campaigns created by other users to select from
I don't want campaigns from other users to be able to be selected
Update: This is my current get_form
class ScreenAdmin(FilterUserAdmin):
list_display = ('screen_name', 'id','screen_URL')
def get_form(self, request, obj=None, **kwargs):
self.exclude = ["beacon"]
if not request.user.is_superuser:
self.exclude.append('user') #here!
form = super(ScreenAdmin, self).get_form(request, obj, **kwargs)
#print(vars(form))
form.base_fields['campaign'].queryset = Campaign.objects.filter(user=request.user)
return form
def changelist_view(self, request, extra_context=None):
if request.user.is_superuser:
self.list_display = ('screen_name','user', 'id','screen_URL')
#print('Change List######')
return super(ScreenAdmin, self).changelist_view(request, extra_context)
this solution is perfectly sane and working
def queryset(self, request):
qs = super(FilterUserAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(user=request.user)
unless you are marked as superuser, an this is probably what you do.
to have access to the admin interface, you must check user as "staff" (is_staff)
if you check "superuser" you will see all of the data, so create other user (for the tests) add him proper rights, but do not mark mim as superuser, only is_staff and test it.

Categories

Resources