I am new to DRF, and I had came across the following problem when I trying to customize the Permission in DRF.
Suppose I had the following code in my permissions.py file:
class GetPermission(BasePermission):
obj_attr = 'POITS'
def has_permission(self, request, view):
user = request.user
employee = Employee.objects.get(user=user)
# Return a dict which indicates whether the request user has the corresponding permissions
permissions = get_permission(employee.id)
return permissions[GetPermission.obj_attr]
And in my view, I want to override the static variable in the GetPermission class:
class AssignmentList(generics.ListCreateAPIView):
GetPermission.obj_attr = 'ASSIGNMENT'
permission_classes = (IsAuthenticated, IsStaff, GetPermission)
queryset = Assignment.objects.all()
serializer_class = AssignmentSerializer
pagination_class = LargeResultsSetPagination
def perform_create(self, serializer):
employee = Employee.objects.get(user=self.request.user)
serializer.save(sender=employee, status=0, operatable=0)
However, as the documentation of DRF points out:
Permission checks are always run at the very start of the view, before any other code is allowed to proceed.
So how am I supposed to do, thanks in advance, any ideas will be welcomed, since I am a new fish to DRF.
You need to create subclasses of your permission for each attribute, and use self.obj_attr inside the has_permission method.
class DefaultPermission(GetPermission):
obj_attr = 'POITS'
class AssignmentPermission(GetPermission):
obj_attr = 'ASSIGNMENT'
Related
I am trying to create a custom permission for my view that allow read and write permissions to the owner of the model in the QuerySet but do not allow any permission/request to other users or un-authenticated ones.
Source: https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
View:
class My_classListCreateAPIView(generics.ListCreateAPIView):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = [IsModelOwner]
Permission:
class IsModelOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Permissions are only allowed to the owner of the model and admins.
if request.user.is_staff == True:
return True
return obj.owner == request.user
unfortunately it seems that my view is not even calling my custom permission class. (I imported it etc.) If instead of my custom permission class, I use a default one like permissions.isAuthenticatedOrReadOnly that works instead. What am I missing here?
Thanks.
The has_object_permission method is only called on objects, not on querysets, what this means is that on a list request it won't be called.
Your view only has list and create endpoints, neither of those use the object_permissions, use has_permission instead.
However i believe what you want to do is actually use the isAuthenticated permission combined with a modified get_queryset in your view
class My_classListCreateAPIView(generics.ListCreateAPIView):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = [isAuthenticated]
def get_queryset(self):
return Model.objects.filter(owner=self.request.user)
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]
In my API, I have a create view tied that references another record OneToOne. However, occasionally it seems that users send through two requests at once and the second fails due to a duplicate record clash:
class CreateProfileLink(generics.CreateAPIView):
def perform_create(self, serializer):
ins = serializer.save(user=self.request.user)
serializer_class = ProfileLinkSerializer
Is there a way I could override the create method to return the record if it already exists rather than creating it?
You could use get_or_create in your serializer class, by overriding its create() method:
class ProfileLinkSerializer(serializers.ModelSerializer):
...
class Meta:
model = Profile
fields = (...)
def create(self, validated_data):
profile, _ = Profile.objects.get_or_create(**validated_data)
return profile
Since you haven't provided your models.py, I am using Profile as a model name here. Make sure to replace it if it is different in your project.
I'm in the process of debugging my custom permissions class and returning a value of False for my has_object_permission() function, but my I'm still able to access my API (GET request), via Restframework's API browser without authenticating and I can't understand why. Any help would be greatly appreciated. Please see code below. for whatever reasons, it appears that my has_object_permission function is not executing. Please Help
urls.py
router = BulkRouter()
router.register(r'api1', SimpleViewSet1)
urlpatterns = [
url(r'^test/', include(router.urls, namespace='api1')),
]
views.py
class SimpleViewSet1(generics.BulkModelViewSet):
queryset = Barcode.objects.all()
permission_classes = (MyUserPermission,)
serializer_class = SimpleSerializer1
def get_queryset(self):
user = User.objects.get(pk=2)
return Barcode.objects.filter(owner = user)
def get_object(self):
obj = get_object_or_404(self.get_queryset())
self.check_object_permissions(self.request, obj)
return obj
permissions.py
class MyUserPermission(BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return False
serializer.py
class SimpleSerializer1(BulkSerializerMixin, # only required in DRF3
ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta(object):
model = Barcode
# only required in DRF3
list_serializer_class = BulkListSerializer
fields = ('barcode_number', 'barcode_type', 'owner')
models.py
#python_2_unicode_compatible
class Barcode(models.Model):
owner = models.ForeignKey('auth.User', related_name = 'barcodes')
barcode_number = models.CharField(max_length=200)
barcode_type = models.CharField(max_length=200)
def __str__(self):
return self.barcode_number
Django Rest API Guide says:
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.
rest_framework.generics.BulkModelViewSet, as it's name suggests,does bulk operations. It means that you have to use object-level filtering as proposed in the docs.
You should be looking especially under this section. Pay close attention to the example and make use of the code. You should also read about the DjangoModelPermissions to understand how does the example in the link above works.
Suppose I have the following model -
class Person(models.Model):
name = models.CharField(max_length=200)
clubs = models.ManyToManyField(Club,related_name = 'people')
date = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.name
used to create a rest api.
views.py
class PersonDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PersonSerializer
def get_object(self):
person_id = self.kwargs.get('pk',None)
return Person.objects.get(pk=person_id)
How do I add permissions so that only authenticated user can add,update delete or retrieve objects from the person list in the api. And read-only permissions for non authorized users. I tried going through the docs but it is all very confusing. Can someone explain?
You need to add IsAuthenticatedOrReadOnly permission class to PersonDetail view.
From the DRF Docs:
The IsAuthenticatedOrReadOnly will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request
method is one of the "safe" methods; GET, HEAD or OPTIONS.
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class PersonDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PersonSerializer
permission_classes = (IsAuthenticatedOrReadOnly,) # specify the permission class in your view
def get_object(self):
person_id = self.kwargs.get('pk',None)
return Person.objects.get(pk=person_id)