django rest framework same route, different - python

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

Related

Django Rest Framework get filtered queryset in custom get function

So I have an APIView like this:
from rest_framework.views import APIView
class MyAPIView(APIView):
queryset = MyObject.objects.all()
filter_backends = [MyCustomFilterBackend, DjangoFilterBackend]
filterset_fields = ["field1", "field2"]
def get(self, request):
s = StatManager(self.queryset)
return Response(s.dashboard())
where I filter bunch of stuff using MyCustomFilterBackend and DjangoFilterBackend. My goal is to give the filtered queryset to StatManager so it generates stats for the dashboard.
Currently, s = StatManager(self.queryset) does not take filters into account. How can I provide the filtered queryset in my get function?
I read the DRF documentation. I was expecting for APIView to have some function like get_filtered_queryset().
I read Filters of Django REST Framework inside GET function?
and DJango filter_queryset but they have no adequate answers.
Try to use self.queryset in filter_queryset() method inside get() method so:
from rest_framework.views import APIView
class MyAPIView(APIView):
queryset = MyObject.objects.all()
filter_backends = [MyCustomFilterBackend, DjangoFilterBackend]
filterset_fields = ["field1", "field2"]
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get(self, request):
queryset = self.filter_queryset(self.queryset)
s = StatManager(queryset)
return Response(s.dashboard())

405 Method Post Not Allowed

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.

Django Rest Framework: Passing Context in Viewsets

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

Django REST Generic View for related model

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.

Django Rest Framework viewsets queryset duplicate

Can I use same query set for two different ModelViewSet classes?
I try to use the same queryset, but django rest framework duplicate my API route.
views.py
class PlannerAgentViewSet(viewsets.ModelViewSet):
queryset = EstateStatus.objects.all()
serializer_class = PlannerAgentListingSerializer
permission_classes = (IsAuthenticated,)
http_method_names = ['get']
class PlannerClientViewSet(viewsets.ModelViewSet):
queryset = EstateStatus.objects.all()
serializer_class = PlannerClientListingSerializer
permission_classes = (IsAuthenticated,)
http_method_names = ['get']
urls.py
router = routers.DefaultRouter()
router.register(r'planner_agent', views.PlannerAgentViewSet)
router.register(r'planner_client', views.PlannerClientViewSet)
And I obtain:
"planner_agent": "http://127.0.0.1:8000/api/v1/planner_client/",
"planner_client": "http://127.0.0.1:8000/api/v1/planner_client/",
When I use different queryset works. So I can use same queryset?
Thanks for helping.
UPDATE:
Route "http:// 127.0.0.1:8000/api/v1/planner_agent/" exists but must accessed manually
Of course you can do it, but you have to manually set the base_name for your routes to change those uris.
In your case :
router.register(r'planner_agent', views.PlannerAgentViewSet, base_name='planner_agent')
router.register(r'planner_client', views.PlannerClientViewSet, base_name='planner_client')

Categories

Resources