Viewsets are convenient because we can do stuff like this and get a fully working serializer:
class StoreObjectViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes = [IsAuthenticated]
queryset = StoreObject.active_objects.all()
serializer_class = serializers.StoreObjectSerializer
Unfortunately, as far as I know– to pass the context into the serializer we need to do things like this:
PostSerializer(data=request.data, context={'request': request})
Which means we need to manually override every convenient method provided by ViewSets (as far as I know). Is there a way to inject the context into every serializer while still keeping Viewsets convenient?
By default, request is being sent to any Generic View and ViewSet. You can check the source code in GitHub as well. So you do not have to inject them in every view. If you want to pass extra context, then override get_serializer_context(...) method:
class StoreObjectViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
...
def get_serializer_context(self):
context = super().get_serializer_context()
context['custom_context'] = 'Your custom context'
return context
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 wrote the following code:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsCreation|permissions.IsAuthenticated]
def change_password(self, request):
print(request.user)
With the corresponding route in urls.py:
path('api/users/password', views.UserViewSet.as_view({'post': 'change_password'}))
But when I make a request to 'api/users/password/', I get this error:
{
"detail": "Method \"POST\" not allowed."
}
What am I doing wrong here?
ReadOnlyModelViewSet only provides the 'read-only' actions and that's why you are getting error POST not allowed. You can read more about it Here
The ReadOnlyModelViewSet only support the HTTP GET method, as the name indicates.
In order to support other methods, you need to use ModelViewSet class (or other suitable classes)
For example:
class UserViewSet(viewsets.ModelViewSet): # change the super class
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsCreation | permissions.IsAuthenticated]
Side Note: the method change_password(...) does seem like a custom action, but you are missing #action decorator.
i'm using django rest framework to build an api, here is my problem
url(r'^profiles/(?P<pk>[0-9]*)', ProfileRetrieveView.as_view(), name='profiles-detail'),
url(r'^profiles/(?P<pk>[0-9]*)', ProfileUpdateView.as_view(), name='profiles-update'),
class ProfileRetrieveView(RetrieveAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileUpdateView(UpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
When i query the api with the link /profile/2 and the method patch, i receive 405, method not allowed, only the get method is allowed, how can i solve that without haven to transform my two view classes into on class with a GenericView Base class and Retrive + update Mixins.
urls.py
url(r'^profiles/(?P<pk>[0-9]*)', ProfileRetrieveUpdateView.as_view(), name='profiles-detail-update'),
views.py
from rest_framework.generics import RetrieveUpdateAPIView
class ProfileRetrieveUpdateView(RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def get_permissions(self):
if self.request.method == "GET":
return []
else:
return [IsAuthenticated()]
You should condense this into a single endpoint. You can have a single class handle all the listing, update, get, etc for this. Try...something like this:
from rest_framework import mixins, viewsets
class ProfileUpdateView(viewset.ModelViewSet,
mixins.ListModelMixin,
mixins.UpdateModelMixin):
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
get_queryset(self):
return Profile.objects.all()
If you're using pure models, use the built in model stuff, and check out the mixins. It will save you TONS of generic code writing. It has some magic that knows how to route the request to the matching http method.
http://www.django-rest-framework.org/api-guide/generic-views/#mixins
Django rest framework provides generic view you don't require Mixins.
You can directly use RetrieveUpdateAPIView. Provides request methods get to retrieve data, put update data and patch for partial update.
from rest_framework.generics import RetrieveUpdateAPIView
class ProfileUpdateView(RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
Reference: http://www.django-rest-framework.org/api-guide/generic-views/#retrieveupdateapiview
I have a setup similar to this - a Cookbook class, which has multiple Recipes.
I have a
class CookbookListCreateView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Cookbook.objects.all()
serializer_class = CookbookSerializer
and this handles creating / listing the cookbooks.
I need a ListCreateView for the Recipe model but the list must belong to a specific cookbook, in such a way that this url:
/cookbook/2/recipes
would return only recipes found in a cookbook with pk of 2.
How can I modify ListCreateAPIView to follow this behavior?
You can create a new route/url:
/cookbook/<cookbook_pk>/recipes
And an api view as you want:
class RecipeListCreateView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
def get_cookbook(self):
queryset = Cookbook.objects.all()
return get_object_or_404(queryset, pk=self.kwargs['cookbook_pk'])
def get_queryset(self):
cookbook = self.get_cookbook()
return super().get_queryset().filter(cookbook=cookbook)
def perform_create(self, serializer):
cookbook = self.get_cookbook()
serializer.save(cookbook=cookbook)
Use get_cookbook whenever you need the cookbook (eg. in perform_create method as above)
That's what called a "Detail Route" in DRF.
class CookbookListCreateView(ListCreateAPIView):
....
#detail_route(methods=['get'])
def recipes(self, request, **kwargs):
# Do what you would do in a function-based view here
It will suffice for simple cases but in more complex views using nested route functionality of DRF-extensions is a better solution.
I want to access the request object in my Views.py and Serializers.py in DRF.
My Views.py:
class ProductViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = Product.objects.all()
serializer_class = ProductSerializer(context={'request': request})
My Serializers.py:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.request.user ??
class Meta:
model = Product
fields = (
'title', 'slug', 'product_stores', 'get_sr_price')
In Serializers.py I get ProductSerializer' object has no attribute 'request'. Also In views.py I get NameError: name 'request' is not defined
How do I access request object? Do I have to pass it from views to serializers? Also what's the difference between views.py and serializers.py? Generally I write all the business logic in Views.py ; here also should I do all the queries/filters in the views or should I do them in serializers or it doesn't make a difference. New to DRF please help.
You don't need to include request object in the context as the generic views passes request object to the serializer context.
DRF Source code snippet:
# rest_framework/generics.py
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request, # request object is passed here
'format': self.format_kwarg,
'view': self
}
In your serializer, you can access the request object using .context attribute.
The context dictionary can be used within any serializer field logic,
such as a custom .to_representation() method, by accessing the
self.context attribute.
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.context['request'].user # access the request object
Serializers are the way external data is mapped from / to models (Django or simple Python classes).
Views are dealing with how the data will be shown. Throttling, pagination, authentication are managed by the view. They also handle the data set.
DRF provides a context to pass request specific data to the serializer without having to redefine the init. This is likely what you're looking for.