Consider that I have a simple view as,
# serializers.py
class EmptyPayloadResponseSerializer(serializers.Serializer):
detail = serializers.CharField()
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from .serializers import EmptyPayloadResponseSerializer
class EmptyPayloadAPI(APIView):
#extend_schema(responses=EmptyPayloadResponseSerializer)
def post(self, request, *args, **kwargs):
# some actions
return Response(data={"detail": "Success"})
When I render the schema, I have got the following error response,
Error #0: EmptyPayloadAPI: unable to guess serializer. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. Ignoring view for now.
So, how can I tell to #extend_schema decorator that I'm intended to pass nothing as payload?
Settings request=None in the #extend_schema(...) decorator will do the trick!!!
class EmptyPayloadAPI(APIView):
#extend_schema(request=None, responses=EmptyPayloadResponseSerializer)
def post(self, request, *args, **kwargs):
# some actions
return Response(data={"detail": "Success"})
Related
I have a generic class based view:
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
# Rest of definition
And in my urls.py, I have:
urlpatterns = [
url(r'^(?P<pk>[0-9]+)/$', views.ProjectDetails.as_view())
]
When the API is called with a non-existent id, it returns HTTP 404 response with the content:
{
"detail": "Not found."
}
Is it possible to modify this response?
I need to customize error message for this view only.
This solution affect all views:
Surely you can supply your custom exception handler: Custom exception handling
from rest_framework.views import exception_handler
from rest_framework import status
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response.status_code == status.HTTP_404_NOT_FOUND:
response.data['custom_field'] = 'some_custom_value'
return response
Sure you can skip default rest_framework.views.exception_handler and make it completely raw.
Note: remember to mention your handler in django.conf.settings.REST_FRAMEWORK['EXCEPTION_HANDLER']
Solution for specific view:
from rest_framework.response import Response
# rest of the imports
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def handle_exception(self, exc):
if isinstance(exc, Http404):
return Response({'data': 'your custom response'},
status=status.HTTP_404_NOT_FOUND)
return super(ProjectDetails, self).handle_exception(exc)
It's possible by overriding specific methods like update, retrieve as:
from django.http import Http404
from rest_framework.response import Response
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def retrieve(self, request, *args, **kwargs):
try:
return super().retrieve(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
def update(self, request, *args, **kwargs):
try:
return super().update(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
I am trying to set csrf_exempt on class-based view in Django I've tried this one:
Now, my class is look like this:
class UserView(View):
# Check csrf here
def get(self, request, pk):
#code here
# I want to exempt csrf checking only on this method
def post(self, request):
#code here
Meanwhile, if I use #method_decorator(csrf_exempt, name='dispatch') it will be applied to every method in the class. What's the best approach to exempt only for a specific method in a class-based view in Django?
You can't dacorate only the post()/put() because as_view function in base.py doesn't carry __dict__ methods from other methods than dispatch(). Source.
You can only decorate a class or override the dispatch() method as documentation says.
You can try this -
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
class UserView(View):
# Check csrf here
def get(self, request, pk):
#code here
# exempt csrf will affect only this method
#method_decorator(csrf_exempt)
def post(self, request):
#code here
You can also write it this way -
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
#method_decorator(csrf_exempt, name='post')
class UserView(View):
# Check csrf here
def get(self, request, pk):
#code here
# exempt csrf will affect only post method
def post(self, request):
#code here
Read the documentation here.
Working on an Api I wanted to give class based views in Django a go.
This is what I got so far:
urls.py
from django.conf.urls import url
from .api import Api
urlpatterns = [
url(r'^', Api.as_view())
]
api.py
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
class Api(View):
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
super(Api, self).dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return HttpResponse("result")
def get(self, request):
return HttpResponse("result")
When calling this code from Postman I keep getting 2 issues:
Post data
In postman I have set a value in the Post headers but when I use the debugger to check the POST data it just isn't there.
Return HttpResponse
I keep getting the error The view api.api.Api didn't return an HttpResponse object. It returned None instead.
When I change the post method to a get method and send a get request with Postman I get the same result.
You need to do:
return super(Api, self).dispatch(request, *args, **kwargs)
You're not returning anything, and a function returns None by default.
Also, you probably want your url as r'^$'
I am using the token based Authentication in Django and need to add User object in addition to token being returned.
How do I override this class view ? Where do I need add this class and make the changes ? Currently this is found in the rest_framework package and I don't want to modify the library .
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.response import Response
from rest_framework.views import APIView
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
print "dasdsa"
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
From docs.
First that you need is to extend the ObtainAuthToken class.
# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
And after this add the CustomAuthToken class to your urls.py like view
# urls.py
from django.urls import path
from . import views
urlpatterns += [
path(r'api-token-auth/', views.CustomAuthToken.as_view())
]
You should extend your CustomClass from AuthToken, the route default url to your CustomClass:
from rest_framework_jwt.views import ObtainJSONWebToken
class JSONWebTokenAPIOverride(ObtainJSONWebToken):
"""
Override JWT
"""
def post(self, request):
# Do whatever you want
Then in your urls.py:
url(
r'^api-auth$',
cache_page(0)(views.JSONWebTokenAPIOverride.as_view())
)
I hope it helps
I wanted to override some default CRSF functionality and used the following approach:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
# Do not perform a csrf check
return False
Then in my settings file I referenced it in the following way:
'DEFAULT_AUTHENTICATION_CLASSES': (
'myapp.utils.authenticate.SessionCsrfExemptAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
This allowed me to import the existing functionality, override it and reference it in the settings file. I think you can use a similar approach here.
I use the option JWT_RESPONSE_PAYLOAD_HANDLER.
In the response I include the token, expiration timestamp and the user.
In settings.py add:
JWT_AUTH = {
...
'JWT_RESPONSE_PAYLOAD_HANDLER':'<app_name>.functions.custom_jwt_response',
}
Then in functions.py add the following
def custom_jwt_response(token, user=None, request=None):
import jwt
jwt = jwt.decode(token, verify=False)
return {
'token': token,
'token_exp': jwt['exp'],
'user': UserSerializer(user, context={'request': request}).data
}
The answers here are good but in my opinion they don't make full use of inheritance. When we inherit a class, we shouldn't just try to reinvent the wheel and instead make use of the super() keyword. Here is my code example, where I want to turn the username argument into lowercase before performing the authentication request:
class GetAuthToken(ObtainAuthToken):
"""
Override Django's ObtainAuthToken to provide custom way of authenticating user for token
"""
def post(self, request, *args, **kwargs):
#-- turn username to lowercase
if ('username' in request.data):
request.data['username'] = request.data['username'].lower()
#-- perform normal function
return super().post(request, *args, **kwargs)
I think I'm experiencing a threadding issue with the Django class-based views I have written.
After launching the application, the UpdateView functions fine until CreateView is called/visited. Then subsequent UpdateViews populate the 'code' field with the value generated in the get_initial method of CreateView.
The problem only shows itself on the web server, and not when using the development runserver command.
E.g. if an instance of MyObject has a code of '123', then visiting the UpdateView shows the code in the form as '123'. After visiting a page which calls CreateView, a new code is generated by get_initial(), say '456'. From then on, visiting any url which calls UpdateView shows '456' in the form instead of the instances actual code.
Sample myproject.app.views.myobject view classes:
from django.contrib.auth.decorators import permission_required
from django.utils.decorators import method_decorator
from django.views import generic
from myproject.app.forms import MyObjectForm
from myproject.app.models import MyObject
class EditMixin(generic.base.View):
form_class = MyObjectForm
def get_success_url(self):
return self.object.get_absolute_url()
def form_valid(self, form):
self.object = form.save(commit=False)
if not self.object.pk:
self.object.created_by = self.request.user
self.object.updated_by = self.request.user
self.object.save()
messages.success(self.request, 'Object saved.')
return HttpResponseRedirect(self.get_success_url())
class CreateView(EditMixin, generic.edit.CreateView):
model = MyObject
#method_decorator(permission_required('app.add_myobject'))
def dispatch(self, *args, **kwargs):
return super(CreateView, self).dispatch(*args, **kwargs)
def get_initial(self):
initial = super(CreateView, self).get_initial()
#TODO: proper auto-generation of code
myobject = MyObject.objects.order_by('-code')[0]
code = int(myobject.code) + 1
initial.update({'code': str(code)})
return initial
class UpdateView(EditMixin, generic.edit.UpdateView):
#method_decorator(permission_required('app.change_myobject'))
def dispatch(self, *args, **kwargs):
return super(UpdateView, self).dispatch(*args, **kwargs)
def get_queryset(self):
return MyObject.objects.filter(created_by=self.request.user)
Url Patterns:
from myproject.app.views import myobjects
urlpatterns = patterns('',
url(r'^$', myobjects.ListView.as_view(), name='myobject_list'),
url(r'^(?P<pk>[\d]+)/$', myobjects.DetailView.as_view(),
name='myobject_detail'),
url(r'^(?P<pk>[\d]+)/edit$', myobjects.UpdateView.as_view(),
name='myobject_edit'),
url(r'^new$', myobjects.CreateView.as_view(),
name='myobject_new'),
)
Can anyone help explain where I might be causing the threadding issue, and the best practice to avoid this?
Try removing the call to super's get_initial. It's seems to use a class property instead of an instance property, causing you trouble. Try this:
def get_initial(self):
myobject = MyObject.objects.order_by('-code')[0]
code = int(myobject.code) + 1
initial={'code': str(code)}
return initial