DJango REST Framework - how to pass parameter to serializer __init__ - python

I've been reading the django rest framework doc and I couldn't find how to pass parameter to the init method of a serializer. In a regular view I do like this:
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(ContentCreateView, self).get_form_kwargs(*args, **kwargs)
form_kwargs['delivery_id'] = self.kwargs['delivery_id']
return form_kwargs
Would you know how I can do that using a CreateAPIView/UpdateAPIView?
Thanks for any help

DRF has solutions around this.
Either you need delivery_id to save your instance, in which case you'll pass it directly to the serializer's save and get it in the create/update's validated_data
Either you need it for validation in which case you'll pass it to the serializer's context which you'll be able to use in the serializer/fields as self.context['request'] for example. You'll need to override the view's get_serializer_context for that.

Related

How to re-use GET call of another class-based api view

I have a hard time trying to re-use a get call from an existing APIView in another APIVIew.
I have a class-based DRF view:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SomethingList.as_view()), #legacy url
# in views.py
class SomethingList(generics.ListCreateAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
# override get, because of some required custom action
def get(self, request, *args, **kwargs):
# do some custom actions (scan folder on filesystem)
...
return super().get(request, *args, **kwargs)
The above view both provides a get (list) and post (create) API interface. As intended. I've augmented it with DRF-spectacular information (not shown here) to generate my swagger docs.
Now, I have another (legacy) URL defined that should do exactly the same as the get (list) call above. Currently, this legacy url also points to the SomethingList.
But ... the legacy URL should NOT provide the post (create) interface, and I want to mark it as 'deprecated' in swagger using drf-spectacular. So I figured I need a separate class to restrict to get() and add the #extend_schema decorator
So I though of re-using the existing SomethingList.get functionality as follows:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SimilarSomethingList.as_view()), # ! points to new class
# in views.py
class SomethingList(generics.ListCreateAPIView):
...
class SimilarSomethingList(generics.ListAPIView): #ListAPIView only!
#extend_schema(summary="Deprecated and other info..")
def get(self, request, *args, **kwargs):
view = SomethingList.as_view()
return view.get(request, *args, **kwargs)
However, this doesn't work. I get AttributeError: 'function' object has no attribute 'get'
I tried a couple of variations, but couldn't get that working either.
Question:
How can I reuse the get() call from another APIView? Should be simple, so I'm likely overlooking something obvious.
Set http_method_names to the class view.
class SomethingList(generics.ListCreateAPIView):
http_method_names = ['get', 'head']
reference: https://stackoverflow.com/a/31451101/13022138

How do I pass the request object to the method_decorator in django class views?

I have been working on this all day.
I am trying to write custom permission for class views to check if user is in a certain group of permissions.
def rights_needed(reguest):
if request.user.groups.filter(Q(name='Admin')).exists():
pass
else:
return HttpResponseRedirect('/account/log-in/')
#method_decorator(rights_needed, name='dispatch')
class AdminView(CreateView):
model = Admin
form_class = AdminForm
def get_template_names(self):
return 'clinic/visitform_list.html'
Could help me know how I can achieve this? Or an easier way around it?
I also tried this (code inside AdminView class):
def dispatch(self, request):
if request.user.groups.filter(Q(name='Admin')).exists():
return super().dispatch(*args, **kwargs)
else:
return HttpResponseRedirect('/account/log-in/')
A decorator is a function that takes a function (a view in this case), and returns another function (a view in this case). At the moment your rights_needed looks like a regular view - it’s returning a response not a function.
Django comes with a user_passes_test method that makes it easy to create decorators like this. Since you are using class based views, it would be even easier to use the UserPassesTest mixin.
Your test function for the mixin would be:
def test_func(self):
return self.request.user.groups.filter(Q(name='Admin')).exists()

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.

Django - how to use kwargs

I'm overriding the .save() method of a django model and I'm trying to pass an extra argument when saving:
View:
def form_valid(self, form):
response = super(DeliveryCreateView, self).form_valid(form)
self.object.save(owner=self.request.user)
return response
In the .save()
def save(self, *args, **kwargs):
owner = kwargs.pop('owner', None)
My problem is that owner always comes empty.
What am I doing wrong?
Assuming this is a standard create or update view, the superclass form_valid will already be calling the model save method, via the form. You'll either need to deal with that situation, or don't call super.

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.

Categories