I am using Django + GraphQL and it is extremely convenient to use DjangoListField. It allows me to override get_queryset at the ObjectType level and make sure all permissions verified there. In that manner I have a single place to have all my permission checks.
However, whenever I need to do something like:
contract = graphene.Field(ClientContractType,
pk=graphene.ID(required=True))
I have to also duplicate permissions validation in the resolve_contract method. I came up with the following solution to ensure permission and avoid duplication:
def resolve_contract(self, info, pk):
qs = ClientContract.objects.filter(pk=pk)
return ClientContractType.get_queryset(qs, info).get()
It works but I'd love to have some sort of DjangoObjectField which will encapsulate this for me and, potentially, pass arguments to the ClientContractType in some way. Have someone had this problem or knows a better solution?
The best I was able to come up with is to move this logic into the ObjectType class, defining a method (analogues to what DjangoObjectType already implemented)
#classmethod
def get_node(cls, info, id):
queryset = cls.get_queryset(cls._meta.model.objects, info)
try:
return queryset.get(pk=id)
except cls._meta.model.DoesNotExist:
return None
then, resolver will look like
def resolve_contract(self, info, pk):
return ClientContractType.get_node(pk)
Far from ideal, tho.
Related
What exactly I want to do
On rollback, I want to run a do_something function.
Is it possible to do so?
Right now, transaction.atomic does its job perfectly, but it only takes care of database. On rollback, I want to take care of a few other things using that do_something function I mentioned before.
My use cases for transaction.atomic are pretty simple and there's not much to say about, I just use it on typical views which are creating and updating objects.
*Updated
Example View
#transaction.atomic
def create(self, request, *args, **kwargs):
serializer = ConvertSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = ConvertModel.objects.create(
request.user, request.converter, serializer.validated_data
)
# I have some code here which create a json file on the disk based on that instance
create_json(request.user, request.converter)
return Response(ConvertSerializer.data, status=HTTP_201_CREATED)
Example on_rollback function
This function simply removes the JSON file which was created by the view.
def remove_json(file_path):
os.remove(file_path)
The example is not what exactly I do now, but it's a pretty similar case. After the rollback, the database is okay, but I have that invalid JSON file on the disk which can make huge problems for me.
I'm currently thinking of how to implement permissions in my Django application using DRF. My idea is to have several generic classes that inherit BasePermission and to define those classes in the view, ex:
class ExampleView(ModelViewSet):
permission_classes = (ExamplePermissionClass,)
However, this might not suffice for some views that I have i.e. I might declare a generic class on the whole view but I might also want one #action to use different permissions. For this, I though of making decorator permissions which would check permissions on a per method basis. However, I do think that permission_classes are executed first and this won't work. Is there a way arround this?
Example:
class ExampleView(ModelViewSet):
permission_classes = (ExamplePermissionClass,)
#permissions(PermissionName etc...)
#action(...)
def example_action
How can I circumvent the permission class and only check the decorator here?
You can use get_permissions method to configure different permissions for different actions:
def get_permissions(self):
if self.action == "example_action":
return (ExamplePermissionClass1, ExamplePermissionClass2)
else:
return self.permission_classes # Default value
Take a look a this section of the documentation for more info.
If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.
Of its available methods, which is the ideal place to place permission checking? If get(), should it also be in the post()?
The code lives in its own permissions.py and looks like this:
def has_perm_or_is_owner(user_object,
permission,
instance=None):
if instance is not None:
if user_object == instance.user:
return True
return user_object.has_perm(
permission
)
It checks whether the request.user is the rightful owner of the form instance. This particular form should not be viewable to anyone else.
The code I am trying to find a place to insert within the CBV, is here:
can_edit = has_perm_or_is_owner(
self.request.user,
'profile.fill_form',
instance=obj,
)
if not can_edit:
raise Http404
This is usually an easy choice with, say, an UpdateView, as I'll just stick it inside the get_object(). With FormViews, this is a bit more ambiguous. Thoughts?
edit: if you have comments pertaining to what the actual best practice would be in cases of CBV permissions, I would love to hear it.
Is there any feature available in django so that we can combine some filtering on all actions in the view of django, like before_filter: is available in rails.
I'm still learning Rails but from what I've observed so far python decorators also seem to be used in Django in a very similar way to the before_filter in Rails.
Here's one example of it's usage in authenticating users: https://docs.djangoproject.com/en/1.2/topics/auth/#the-login-required-decorator
No. before_, around_ and after_ filter concepts aren't present in Django, but it isn't hard to write your own functions to do the same thing. There are also signals and generic views that might accomplish what you're needing to do.
decorators can be used for this. your decorator can be the before_filter or after_filter depending on how whether it calls the decorated function first or last.
here is an example
#question_required
def show(request, question_id):
return HttpResponse(f'you are looking at {question_id}')
Here we have decorated the show function with question_required and want it to act as a before_filter. so we will define the decorator like this:
def question_required(func):
def containing_func(*args, **kwargs):
request = args[0]
question_id = kwargs['question_id']
try:
question = Question.objects.get(pk=question_id)
except Exception as e:
raise e
return func(*args, **kwargs)
return containing_func
As you can see above the decorator is first checking for the question to exist in the db. If it exists it calls the actual show function or it raises an exception.
In this way it acts like a before filter does in rails.