How to add allow response header to all django views? - django

How can I add the "Allow" header that specifies the allowed HTTP methods to the response headers of all Django views?
To be more clear, here is an example:
Allow: GET, HEAD, OPTIONS

For class-based views, you can add a mixin that will determine this. Indeed:
class AllowViewMixin:
def dispatch(self, *args, **kwargs):
response = super().dispatch(*args, **kwargs)
response.headers['Allow'] = ', '.join(self._allowed_methods())
return response
then you can mix this in the class-based views, for example a ListView:
class MyListView(AllowViewMixin, ListView):
# …

Related

Defining two api end points with same generic class view Django Rest Framework?

I am new to Django. I was trying to use django generic views, class based.
How do I implement following situation, I have two api end points login/ and logout/ and the same view class handles it ?
# Login and logout
class SignInActivity(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
request = None
def get_queryset(self):
return User.objects.filter(Q(username=self.request.data["username"])
& Q(userpassword=self.request.data["userpassword"]))
def update(self, request, *args, **kwargs):
self.request = request
query_set = self.get_queryset()
if bool(query_set):
query_set.update_or_create(username=self.request.data["username"],
userpassword=self.request.data["userpassword"],
defaults={
"lastlogin": timezone.now()
})
return Response(data={"message": "User logged in successfully.", "response_code": 222}, status=201)
else:
return Response(data={"message": "User not found.", "response_code": 444}, status=201)
And my urls.py is
path('login/', SignInActivity.as_view())
This implementation hadles login/ with this class, PUT method. Now can I use same class with another method to handle logout/ PUT method?
By default generics.RetrieveUpdateAPIView provides 3 HTTP methods, which are GET,PUT,PATCH. You could override any of the following methods to logout procedure.
class SignInActivity(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
request = None
def get_queryset(self):
return User.objects.filter(Q(username=self.request.data["username"])
& Q(userpassword=self.request.data["userpassword"]))
def update(self, request, *args, **kwargs):
return Response(data="HTTP PUT method")
def partial_update(self, request, *args, **kwargs):
return Response(data="HTTP PATCH method")
def retrieve(self, request, *args, **kwargs):
# your log-out logic
return Response(data="HTTP GET method")
I think you can use retrieve() method to do logout procedure, because, there is no need of sending additional payloads to the end-point, I assume
I think it's not possible to points two end points to same method function (POST or PUT) of the same view class.
One of the solution could be to use inheritance, where there could be a base class and login and logout endpoint classes inheriting from this class, and later implement respective method functions inside the child classes.
So basically, the two end points will still use functions of different class, with common properties inherited.
class SignInActivity(generics.RetrieveUpdateAPIView):
should be
from rest_framework.views import APIView
class SignInActivity(APIView):
Urls.py
path('login/', SignInActivity.as_view()),
path('logout/', SignInActivity.as_view())
This should be doing the fact as when the request is recieved our domain.com/logout/ it would forward it to the SignInActivity View as well as when request over domain.com/login/ is received!

Django rest framework- calling another class-based view

I have pored over several similar posts (and Calling a class-based view of an app from another app in same project seemed promising, but does not work), but some are older and none quite work for me. Here's my setup (using Django==2.0.6, djangorestframework==3.8.2)
I have a basic model (simplified here):
from django.db import models
class Resource(models.Model):
name = models.CharField(max_length=100, null=False)
I have a basic endpoint where I can list and create Resource instances:
from rest_framework import generics, permissions
from myapp.models import Resource
from myapp.serializers import ResourceSerializer
class ListAndCreateResource(generics.ListCreateAPIView):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
permission_classes = (permissions.IsAuthenticated,)
(afaik, the details of the serializer are not relevant, so that is left out).
Anyway, in addition to that basic endpoint, I have another API endpoint which performs some actions, but also creates some Resource objects in the process. Of course, I would like to make use of the functionality encapsulated in the ListAndCreateResource class so I only have to maintain one place where Resources are created.
I have tried:
Attempt 1:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource().post(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
Unfortunately, that does not work for me. In my trace, I get:
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 111, in get_serializer
kwargs['context'] = self.get_serializer_context()
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 137, in get_serializer_context
'request': self.request,
AttributeError: 'ListAndCreateResource' object has no attribute 'request'
Attempt 2:
This attempt tries to use the as_view method which is part of all Django class-based views:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
But that gives up with:
AssertionError: The `request` argument must be an instance of `django.http.HttpRequest`, not `rest_framework.request.Request`
So my question is...is there a straightforward way to do this? I can access the _request attribute of the rest_framework.request.Request object (which is of type django.http.HttpRequest, but then I do not have any of the authentication details that are contained in the DRF Request object (indeed, my ListAndCreateResource returns a 403 if I use response = ListAndCreateResource().as_view()(request._request, *args, **kwargs) in attempt #2 above).
Thanks in advance!
This seems a bit late, but in case anyone is wondering.
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
The as_view() is a function that when called, returns a function that takes a request, *args, **kwargs. So basically, a class view is an encapsulated function view.
I think you can use request._request. The DRF keeps a protected member _request, as is, received from the API call.
You can access the request with self.request in class based views.

django 405 method not allowed

The error occurs when I request via post method.
views.py
class ConceptForkView(ConceptBaseView, mixins.CreateModelMixin):
print 'oclapi ConceptForkView111'
def dispatch(self, request, *args, **kwargs):
print 'oclapi ConceptForkView dispatch'
return super(ConceptForkView, self).dispatch(request, *args, **kwargs)
def post(self, request):
print 'oclapi ConceptForkView post'
urls.py
url(r'^forking/$', ConceptForkView.as_view(), name='concept-forking'),
ConceptBaseView
class ConceptBaseView(ChildResourceMixin):
lookup_field = 'concept'
pk_field = 'mnemonic'
model = Concept
permission_classes = (CanViewParentDictionary,)
child_list_attribute = 'concepts'
The command print 'oclapi ConceptForkView111' can run, but the method dispatch and post don't run. What is the reason?
I have searched many solutions, but they don't work to me. How can I solve this problem? Thank you.
Note that mixin is not a view. You probably have to inherit from View also. Mixins are usually classes that extend functionality and are not standalone. Class inheriting only from mixins probably won't work properly unless one of these mixins is not mixin in fact.
See: rest framework documentation. There is CreateAPIView which inherits not only from CreateModelMixin but also from GenericAPIView (and you probably should inherit it too). As we can read about GenericAPIView:
This class extends REST framework's APIView class, adding commonly required behavior for standard list and detail views.
So this "commonly required behavior" is important for your class to behave like view.
try using
def create(request, *args, **kwargs)
...
method instead of
def post(self, request):
...
CreateModelMixin uses create method rather than post method

Add ListCreateAPIView-class to router

How can I add a ListCreateAPIView to the router urls?
Normally I do like:
router = routers.DefaultRouter()
router.register(r'busses', BusViewSet)
but now I have:
class CarList(generics.ListCreateAPIView): ...
I added it to the urlpatterns for now:
urlpatterns = patterns('',
url(r'^carts/', CarList.as_view(model=Car), name='cars'),
and I would like to add this Cars-view (which is working as intended if I call the url manually!) to the router, so it's in the overview page!
So: It works as it is, but I have to manually enter the url, it's not in the overview-page of the API.
The reason is why a ViewSet classes work with a router is a GenericViewSet which has a ViewSetMixin in a bases.
ViewSetMixin override as_view() method so that it takes an actions keyword that performs the binding of HTTP methods to actions on the Resource and router can build a map for action-method.
You can solve it by simple adding that mixin in a class bases:
from rest_framework.viewsets import ViewSetMixin
class CarList(ViewSetMixin, generics.ListCreateAPIView)
....
But it is not clear solution because ListCreateAPIView and ModelViewSet it is just an empty classes with a bunch of mixins in a bases. So you always can build you own ViewSet with a methods you need.
For example, here the code of ListCreateAPIView:
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
And here the ModelViewSet:
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
Notice a same mixins ListModelMixin and CreateModelMixin there is only difference in GenericViewSet and GenericAPIView.
GenericAPIView use method names and call an actions inside them. GenericViewSet instead use actions and map them to methods.
Here the ViewSet with methods you need:
class ListCreateViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericViewSet):
queryset_class = ..
serializer_class = ..
Now it will work with a router and you can override list and create methods if you need a special behavior.

cache_page with Class Based Views

I'm trying to do cache_page with class based views (TemplateView) and i'm not able to. I followed instructions here:
Django--URL Caching Failing for Class Based Views
as well as here:
https://github.com/msgre/hazard/blob/master/hazard/urls.py
But I get this error:
cache_page has a single mandatory positional argument: timeout
I read the code for cache_page and it has the following:
if len(args) != 1 or callable(args[0]):
raise TypeError("cache_page has a single mandatory positional argument: timeout")
cache_timeout = args[0]
which means it wont allow more than 1 argument. Is there any other way to get cache_page to work?? I have been digging into this for sometime...
It seems like the previous solutions wont work any longer
According to the caching docs, the correct way to cache a CBV in the URLs is:
from django.views.decorators.cache import cache_page
url(r'^my_url/?$', cache_page(60*60)(MyView.as_view())),
Note that the answer you linked to is out of date. The old way of using the decorator has been removed (changeset).
You can simply decorate the class itself instead of overriding the dispatch method or using a mixin.
For example
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
#method_decorator(cache_page(60 * 5), name='dispatch')
class ListView(ListView):
...
Django docs on decorating a method within a class based view.
yet another good example CacheMixin
from cyberdelia github
class CacheMixin(object):
cache_timeout = 60
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return cache_page(self.get_cache_timeout())(super(CacheMixin, self).dispatch)(*args, **kwargs)
usecase:
from django.views.generic.detail import DetailView
class ArticleView(CacheMixin, DetailView):
cache_timeout = 90
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"
You can add it as a class decorator and even add multiple using a list:
#method_decorator([vary_on_cookie, cache_page(900)], name='dispatch')
class SomeClass(View):
...
I created this little mixin generator to do the caching in the views file, instead of in the URL conf:
def CachedView(cache_time=60 * 60):
"""
Mixing generator for caching class-based views.
Example usage:
class MyView(CachedView(60), TemplateView):
....
:param cache_time: time to cache the page, in seconds
:return: a mixin for caching a view for a particular number of seconds
"""
class CacheMixin(object):
#classmethod
def as_view(cls, **initkwargs):
return cache_page(cache_time)(
super(CacheMixin, cls).as_view(**initkwargs)
)
return CacheMixin
Yet another answer, we found this to be simplest and is specific to template views.
class CachedTemplateView(TemplateView):
#classonlymethod
def as_view(cls, **initkwargs): ##NoSelf
return cache_page(15 * 60)(super(CachedTemplateView, cls).as_view(**initkwargs))
Would like to add this:
If you need to use multiple decorators for cache like vary_on_headers and cache_page together, here is one way I did:
class CacheHeaderMixin(object):
cache_timeout = int(config('CACHE_TIMEOUT', default=300))
# cache_timeout = 60 * 5
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return vary_on_headers('Authorization')(cache_page(self.get_cache_timeout())(super(CacheHeaderMixin, self).dispatch))(*args, **kwargs)
This way cache is stored and it varies for different Authorization header (JWT). You may use like this for a class based view.
class UserListAPIView(CacheHeaderMixin, ListAPIView):
serializer_class = UserSerializer
def get_queryset(self):
return CustomUser.objects.all()
I didn't found a good cache solution for class based views and created my own: https://gist.github.com/svetlyak40wt/11126018
It is a mixin for a class. Add it before the main base class and implement method get_cache_params like that:
def get_cache_params(self, *args, **kwargs):
return ('some-prefix-{username}'.format(
username=self.request.user.username),
3600)
Here's my variation of the CachedView() mixin - I don't want to cache the view if the user is authenticated, because their view of pages will be unique to them (e.g. include their username, log-out link, etc).
class CacheMixin(object):
"""
Add this mixin to a view to cache it.
Disables caching for logged-in users.
"""
cache_timeout = 60 * 5 # seconds
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
if hasattr(self.request, 'user') and self.request.user.is_authenticated:
# Logged-in, return the page without caching.
return super().dispatch(*args, **kwargs)
else:
# Unauthenticated user; use caching.
return cache_page(self.get_cache_timeout())(super().dispatch)(*args, **kwargs)