Trying to manage django-guardian object-level permissions with django-rest-framework the most cleanly and canon possible.
I want to assign a read permission (module.view_object) of an object to to the user making the request when performing a POST.
My class-based view:
class ObjectList(ListCreateAPIView):
queryset = Object.objects.all()
serializer_class = ObjectSerializer
filter_backends = (DjangoObjectPermissionsFilter,)
# MyObjectPermissions inherit from DjangoObjectPermissions and just
# add the 'view_model' permission
permission_classes = (MyObjectPermissions,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
As described in the django-rest-framework documentation, I've overloaded perform_create in order to pass the user to the serializer.
But how do I assign the permission (using guardian.shortcuts.assign_perm) in the serializer. Is there no other way but to override the save method to assign the permission manually? Isn't there some kind of standard mechanism to manage a common behavior as this one?
You can assign the permission in the perform_create() method of the ViewSet.
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)
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.
In django rest framework i am trying to add an object in a ModelSerializer with some custom functionality.
I want one of the fields to be set to self.request.user and i have the following view:
class GigSubmitView(generics.CreateAPIView):
permission_classes = (ProviderRW,)
serializer_class = serializers.GigSubmitSerializer
queryset = models.Gig.objects
def perform_create(self, serializer):
serializer.save(provider=self.request.user)
However that doesn't seem to be the correct answer. What should i do instead?
You dont need to override the perform create method to access request object in the serializer.
You have 2 options:
use serializer.context['request'] to access request object in serializer
request object is inserted by default in all generic views.
http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context
you can use the current user default method to set the current user
http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
I'm reading http://www.django-rest-framework.org/api-guide/permissions/ and trying to relate it to the OAuth2 toolkit documentation, http://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html. The latter has an example in which in settings.py one specifies
REST_FRAMEWORK = {
# ...
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
and in addition, IsAuthenticated is also specified added to the permission_classes list of a ModelViewSet:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
Do I infer correctly from this example that the DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a ModelViewSet's permission classes, but are instead replaced by it?
In the Django REST framework, how are the default permission classes combined with per-view(set) ones?
They are not combined.
... the DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a ModelViewSet's permission classes, but are instead replaced by it?
Correct.
Do I infer correctly from this example that the
DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a
ModelViewSet's permission classes, but are instead replaced by it?
The DEFAULT_PERMISSION_CLASSES are used for views/viewsets where permission_classes is not defined. In the cases they are defined, those are used instead, not the default ones.
If you do want to extend the default permissions, this seems to work.
Disclaimer: I found it by looking into DRF's code, not sure it is documented.
from rest_framework.settings import api_settings
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [*api_settings.DEFAULT_PERMISSION_CLASSES, TokenHasReadWriteScope]
Add code in your custom Permission class like this
class ObjectWritePermission(BasePermission):
# you will see this function in IsAuthenticated Permission class
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
def has_object_permission(self, request, view, obj):
return obj.user == request.user
Using DRF and DRF-nested-routers
Here's my code:
class MemberViewSet(viewsets.ViewSet):
queryset = GroupMember.objects.all()
serializer_class = GroupMembersSerializer
def create(self, request, group_pk=None):
queryset = self.queryset.all()
serializer = GroupMembersSerializer(queryset)
return Response(serializer.data)
But once a new Member is posted the error "QuerySet' object has no attribute 'user' comes up
Any help?
To serialize a queryset (or list of objects) you need to pass many=True
serializer = GroupMembersSerializer(queryset, many=True)
Otherwise it thinks you want to serialize a single GroupMember instance, which is why it tried to access the user attribute on it
If isn't too late in your development and you have the choice, you may want to check out https://github.com/chibisov/drf-extensions. It does routers nesting in a non-intrusive manner - you won't be required to overwrite the viewsets basic methods.
I've learned form the past that drf-nested-routers will interfere with the underlying viewset methods which enable pagination and filtering on your class:
get_queryset
get_serializer_class
get_serializer
get_object
In my opinion affects too much from the Viewset design and functionality for what it offers.