Django Rest Update - python

I am trying to update data via Django Rest PUT method.
class TableView(generics.ListAPIView, generics.UpdateAPIView, generics.CreateAPIView):
serializer_class = TableSerializer
def update(self, request, *args, **kwargs):
if kwargs.__len__() != 0:
tableid = kwargs['id']
mycol = request.DATA['col']
Table.objects.filter(id=tableid).update(col=mycol)
So, this works but data is updated by Table.object which is model. Is there any generic way to update data? I mean, if I PUT col1 and col2 data, it will update them.. If I send only col1, it will update just it..

yes you can use something like this
def update(self, instance,validated_data):
# The instance is the already saved object
instance.col=validated_data['col']
instance.col2=validated_data['col2']
instance.save()
return instance
check this link for the .update() method http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations

This is what PATCH requests does for you. However, in some cases PATCH is not supported (some older versions of Lighttpd for instance) or you might need to use PUT for legacy reasons.
The good thing is that PATCH and PUT requests are almost the same in Django REST Framework. They share most of the code, and the main difference is that PATCH requests sets a parameter partial to True.
What you could try to do is, in your update() method by manually settings the partial flag and passing it along to your serializer like:
def update(self, request, *args, **kwargs):
partial = True
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
This should only update the parts that was passed as arguments in the request.
Normally a PATCH request will call the perform_update() function in your ModelViewSet, and just set the partial flag to true and then call the update() function after that. Source: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L76
Some info from the Django REST Framework page about the update mixin: http://www.django-rest-framework.org/api-guide/generic-views/#updatemodelmixin

Related

Permission class on #detail_route not working, Django Rest Framework

I'm new to DRF, but I'm trying to use a permission class on a #detail_route using the method in this stack thread: Using a permission class on a detail route
My code currently looks like this :
#detail_route(methods=['GET'], permission_classes=[IsStaffOrRestaurantUser])
def restaurant_dishes_ready_for_pickup(self, request, pk=None):
...stuff....
class IsStaffOrRestaurantUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(request)
print(view)
print(obj)
return False
The print statements never get executed... I'm probably missing something but I've looked through the documentation and can't really figure it out, is my approach right at all? Thanks!
EDIT:
I realize in our code already that we have this snippet in our Viewset, is it possible to override this in the decorator?
def get_permissions(self):
# Limit to listing and getting for non Admin user
if self.request.method in permissions.SAFE_METHODS:
return (permissions.AllowAny(),)
return (IsAdminUser(),)
Not sure if it's the most elegant solution, but you might be able to upgrade get_permissions() like so:
def get_permissions(self):
# check additional route specifics
path = self.request.path
if ("restaurant_dishes_ready_for_pickup" in path):
return (IsStaffOrRestaurantUser,)
# Limit to listing and getting for non Admin user
if (self.request.method in permissions.SAFE_METHODS):
return (permissions.AllowAny,)
return (IsAdminUser,)
PS: Also maybe return permission class objects instead of instances in get_permissions().
Quote from the documentation:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
So you'll need to call explicitly the permission check.
Note that you could have that for free if you were using a RetrieveAPIView instead of a function based view for example.

Using destroy() with some parameter other than pk

TLDR --> Is it possible to destroy() an item in DB using some parameter instead of pk?
I am using ViewSet in my DRF application. I am interested in destroy()-ing some queryset items. I would like to avoid using pk, can I do that? My approximate model/approach is as following.
class MyAwesomeMode(models.Model):
awesome_field = models.CharField(max_length = 256)
# some other fields
Now, my intention is to destroy() the Queryset element without using pk and using awesome_field. What do I need to do with my ViewSet?
Since you use ViewSet and that class extends from ViewSetMixin and views.APIView, the .destroy() method is not available.
You need extend from DestroyModelMixin to use the .destroy() method.
About your question, first take a look to the .destroy() source code:
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # Here is getting the object!
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
So, let's say you give a unique_name param in your request, you could override the .get_object() method:
class MyView(ViewSet, DestroyModelMixin):
def get_object(self):
return MyModel.objects.get(unique_name=request.data.get('unique_name'))
Then, when .destroy() calls self.get_object() will use your method, where you got the object based on the imaginary unique_name field istead of pk.

Use multiple ModelViewSet for a REST call

I'm using Django REST Framework's ModelViewSet. Inside "ModelViewSet-1", I need to break down a POST (create) request into 3 parts. The first part will be used by "ModelViewSet-1" and I need to delegate the other two parts to "ModelViewSet-2" and "ModelViewSet-3" respectively. Can someone suggest a workflow?
For now, I'm thinking of using python's 'requests' library, to send circular HTTP calls from 'ModelViewSet-1" to the other ModelViewSets. But that doesn't seem elegant.
Calling other ModelViewSets is possible, but not recommended (by me). But here's how you could do it if that's what you really want to do:
class ViewSet3(ModelViewSet):
def create(self, request):
# Preproccess
....
# Call view 1 & 2
response1 = ViewSet1.as_view({'post': 'create'})(request)
response2 = ViewSet2.as_view({'post': 'create'})(request)
However, it's more likely that you just need a custom create method on your serializer to handle creating all of the other objects, not multiple views (I assume you're using JSON).
class View3Serializer(serializers.Serializer):
# my nested fields
view_1_data = View2Serializer(required=False)
view_2_data = View3Serializer(required=False)
def create(self, validated_data):
view_1_data = validated_data.pop('view_1_data', None)
view_2_data = validated_data.pop('view_2_data', None)
# Create my View3 object normally
instance = View3Model.objects.create(**validated_data)
# Create other objects with other data here
....
return instance

django-rest-framework: add additional permission in ViewSet update method

I have the following code:
class UsersViewSet(viewsets.ModelViewSet):
model = Users
permission_classes = (IsAuthenticated,)
def update(self, request, *args, **kwargs):
return super(UsersViewSet, self).update(request, *args, **kwargs)
The question is:
how can I add additional Permission only for update method? (need to get isAuthenticated + Permission)
overwrite permissions only for update method? (need to get only Permission without isAuthenticated)
other methods in viewset should have IsAuthenticated permission
Can I make it with decorator?Or anything else?
Wanna get something like that:
#permission_classes((IsAuthenticated, AdditionalPermission ))
def update:
pass
But if i write this code the second permission is not checked through request
LATER EDIT
As it seems that DRF decorators don't really work (at least not for me), this is the best solution I could come up with:
def get_permissions(self):
# Your logic should be all here
if self.request.method == 'GET':
self.permission_classes = [DummyPermission, ]
else:
self.permission_classes = [IsAuthenticated, ]
return super(UsersViewSet, self).get_permissions()
This actually works for both cases that you asked, but requires a bit more work. However, I've tested it and it does the job.
ORIGINAL ANSWER BELOW
There is a small mistake in the docs, you should be sending a list to the decorator (not a tuple). So it should be like this:
#permission_classes([IsAuthenticated, AdditionalPermission, ])
def update:
pass
To answer your questions:
how can I add additional Permission only for update method?
First of all, you should know that DRF first checks for global permissions (those from the settings file), then for view permissions (declared in permission_classes -- if these exist, they will override global permissions) and only after that for method permissions (declared with the decorator #permission_classes). So another way to do the above is like this:
#permission_classes([AdditionalPermission, ])
def update:
pass
Since ISAuthenticated is already set on the entire view, it will always be checked BEFORE any other permission.
overwrite permissions only for update method?
Well, this is hard(er), but not impossible. You can:
set the permissions for each method and remove it from the class
modify your AdditionalPermission class so that it also checks for user authentication if the method is not update.
Good luck.
You can also specify permissions for specific methods in the get_permissions() method:
class MyViewSet(viewsets.ModelViewSet):
def get_permissions(self):
if self.action in ('update', 'other_viewset_method'):
self.permission_classes = [permissions.CustomPermissions,]
return super(self.__class__, self).get_permissions()
#permission_classes didn't work for class based view. And I tried #detail_route(permission_classes=(permissions.CustomPermissions,)) for update view function, still not work.
so, my solution is:
class MyViewSet(viewsets.ModelViewSet):
def update(self, request, *args, **kwargs):
self.methods=('put',)
self.permission_classes = (permissions.CustomPermissions,)
return super(self.__class__, self).update(request, *args, **kwargs)
Have a try. my drf is v3.1.1
For me, get_permissions worked but it did turn out that if you sending in Authorization in your header in your request rest framework will throw an error even if permission is set to AllowAny. If you are going to use both authorization and AllowAny you need to have to take this into consideration.
Yes you can by adding annotation
See this link for more information there are examples:
https://docs.djangoproject.com/en/1.6/topics/auth/default/#django.contrib.auth.decorators.permission_required

How can I use a Django F() expression in TastyPie's hydrate cycle?

I have a model with a version number in it. I want it to self-increment when new data is posted with an existing id via TastyPie. I'm currently doing this via the hydrate method, which works as long as two users don't try to update at once:
class MyResource(ModelResource):
...
def hydrate_version(self, bundle):
if 'id' in bundle.data:
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = target.version+1
return bundle
I'd like to do this more robustly by using Django's F() expressions, e.g.:
def hydrate_version(self, bundle):
if 'id' in bundle.data:
from django.db.models import F
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = F('version')+1
return bundle
However, this gives me an error:
TypeError: int() argument must be a string or a number, not 'ExpressionNode'
Is there a way to more robustly increment the version number with TastyPie?
thanks!
I would override the save() method for your Django Model instead, and perform the update there. That has the added advantage of ensuring the same behavior regardless of an update from tastypie or from the django/python shell.
def save(self, *args, **kwargs):
self.version = F('version') + 1
super(MyModel, self).save(*args, **kwargs)
This has been answered at github here, though I haven't tried this myself yet. To quote from that link:
You're setting bundle.data inside a hydrate method. Usually you modify bundle.obj in hydrate methods and bundle.data in dehydrate methods.
Also, those F objects are meant to be applied to Django model fields.
I think what you want is:
def hydrate_version(self, bundle):
if bundle.obj.id is not None:
from django.db.models import F
bundle.obj.version = F('version')+1
return bundle

Categories