405 Method Post Not Allowed - python

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.

Related

Django ViewSet serializer_class is being ignored

I have two models: ModelA and ModelB, with their corresponding serializers ModelASerializer and ModelBSerializer
In a specific viewset, called MyViewSet i have the follwing structure:
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
# The returned values are of type "ModelA", so I need it to use that serializer
serializer_class = ModelASerializer
queryset = ""
Finally, in my actual view, I do something like this:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
return Response(
data=ModelA_queryset,
status=status.HTTP_200_OK,
)
I would expect in that case for the queryset to be serialized using the ModelASerializer that I specified in the serializer_class field. However, I get the error
Object of type ModelA is not JSON serializable
If I do this instead:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
serialized_queryset = ModelASerializer(ModelA_queryset, many=True)
return Response(
data=serialized_queryset.data,
status=status.HTTP_200_OK,
)
It works just fine, but I want to avoid serializing explicitly in the view.
Any ideas on what could be actually going on? Am I forced to serialize explicitly in this case?
I think you don't need to customize the get function. In ModelViewSet, the function for the GET API, is list or retrieve. But you don't need to redefine it.
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
serializer_class = ModelASerializer
queryset = ModelA.objects.all()
class MyViewSet(MyViewSetRoot):
pass

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

In the Django REST framework, how are the default permission classes combined with per-view(set) ones?

I'm reading http://www.django-rest-framework.org/api-guide/permissions/ and trying to relate it to the OAuth2 toolkit documentation, http://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html. The latter has an example in which in settings.py one specifies
REST_FRAMEWORK = {
# ...
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
and in addition, IsAuthenticated is also specified added to the permission_classes list of a ModelViewSet:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
Do I infer correctly from this example that the DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a ModelViewSet's permission classes, but are instead replaced by it?
In the Django REST framework, how are the default permission classes combined with per-view(set) ones?
They are not combined.
... the DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a ModelViewSet's permission classes, but are instead replaced by it?
Correct.
Do I infer correctly from this example that the
DEFAULT_PERMISSION_CLASSES are not prepended / postpended to a
ModelViewSet's permission classes, but are instead replaced by it?
The DEFAULT_PERMISSION_CLASSES are used for views/viewsets where permission_classes is not defined. In the cases they are defined, those are used instead, not the default ones.
If you do want to extend the default permissions, this seems to work.
Disclaimer: I found it by looking into DRF's code, not sure it is documented.
from rest_framework.settings import api_settings
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [*api_settings.DEFAULT_PERMISSION_CLASSES, TokenHasReadWriteScope]
Add code in your custom Permission class like this
class ObjectWritePermission(BasePermission):
# you will see this function in IsAuthenticated Permission class
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
def has_object_permission(self, request, view, obj):
return obj.user == request.user

django rest framework same route, different

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

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