Django-rest-framework #detail_route for specific url - python

I'm using Django-rest-framework==3.3.2 and Django==1.8.8. I have a simple GenericView
from rest_framework import generics
from rest_framework.decorators import detail_route
class MyApiView(generics.RetrieveAPIView):
serializer = MySerializer
def get(self, *args, **kwargs):
super(MyApiView, self).get(*args, **kwargs)
#detail_route(methods=['post'])
def custom_action(self, request)
# do something important
return Response()
This works fine if I use the router that django-rest-framework offers, however I'm creating all my urls manually and would like to do the same with the detail_route.
I wonder if it's possible for me to do something like this:
from django.conf.urls import patterns, url
from myapi import views
urlpatterns = patterns(
'',
url(r'^my-api/$', views.MyApiView.as_view()),
url(r'^my-api/action$', views.MyApiView.custom_action.as_view()),
)
Of course this second url doesn't work. It's just an example of what I would like to do.
Thanks in advance.

As per the example from the Viewsets docs, you can extract individual methods into views:
custom_action_view = views.MyApiView.as_view({"post": "custom_action"})
You're then free to route this as normal:
urlpatterns = [
url(r'^my-api/action$', custom_action_view),
]
I hope that helps.

Related

how to reverse the URL of a ViewSet's custom action in django restframework

I have defined a custom action for a ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
And the viewset is registered to url in the conventional way
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='myuser')
urlpatterns = [
url(r'^', include(router.urls)),
]
The URL /api/users/gender/ works. But I don't know how to get it using reverse in unit test. (I can surely hard code this URL, but it'll be nice to get it from code)
According to the django documentation, the following code should work
reverse('admin:app_list', kwargs={'app_label': 'auth'})
# '/admin/auth/'
But I tried the following and they don't work
reverse('myuser-list', kwargs={'app_label':'gender'})
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'
In the django-restframework documentation, there is a function called reverse_action. However, my attempts didn't work
from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req) # still error out
What is the proper way to reverse the URL of that action?
You can use reverse just add to viewset's basename action:
reverse('myuser-gender')
See related part of docs.
You can print all reversible url names of a given router at startup with the get_urls() method
In urls.py after the last item is registered to the router = DefaultRouter() variable, you can add:
#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())
The next time your app is initialized it will print to stdout something like
[<RegexURLPattern user-list ^users/$>,
<RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
#...
]
where 'user-list' and 'user-detail' are the names that can be given to reverse(...)
Based on DRF Doc.
from rest_framework import routers
router = routers.DefaultRouter()
view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None
or you can set request if you want.
view.request = req
In the end, you can get a reverse action URL and use it.
url = view.reverse_action('gender', args=[])
If you want to use UserViewset().reverse_action() specifically, you can do this by assigning a basename and request = None to your ViewSet:
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
basename = 'user'
request = None
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
and in urls.py:
router.register('user', UserViewSet, basename=UserViewSet.basename)
Then calling url = UserViewset().reverse_action('gender') or url = UserViewset().reverse_action(UserViewSet().gender.url_name) will return the correct url.
Edit:
Above method only works when calling reverse_action() only once because the ViewSetMixin.as_view() method overrides basename on instantiation. This can be solved by adding a custom subclass of GenericViewSet (or ModelViewSet if preferred) like so:
from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet
class ReversibleViewSet(GenericViewSet):
basename = None
request = None
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
basename = cls.basename
view = super().as_view(actions, **initkwargs)
cls.basename = basename
return view
and subclassing this class for the specific ViewSets, setting basename as desired.
The answer is reverse('myuser-gender').
Note! But remember that DRF will replace _ in the action name with -. That means if the action name is my_pretty_action in the reverse you should use reverse(app-my-pretty-action).
According to your code you would like to get the list of users you use
reverse("myuser-list")
details
reverse("myuser-detail",kwargs={"id":1})
In here you might notice i use list or detail by adding it to the reverse so if you add appname you can just input it thats because we are using viewsets

Import a class into the urls.py in Django

In the last few weeks I've switched from developing an application that processes a simple XML file, then writes its contents to an Oracle DB (cx_Oracle) and outputs in HTML, to using a Django framework. The Django framework switch wasn't necessary, but since I have an opportunity to develop something by using Django, I thought why not as it is a new area for me and can't damage my CV.
Anyway, I'm having issues with knowing what to write in my urls.py file when importing a Class from the views.py file. Here are the current contents:
urls.py
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.abc),
)
views.py
class Pymat_program:
def abc(self, request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
I've tried various permutations of using request, not using request, and also how the class is called in the url tuple, all to no avail. When I use a definition outside of the class (i.e. not in any class), then this is correctly displayed in HTML.
You don't want to wrap your program in a class. (In general, in Python you should treat modules like, say, Java might treat classes with static members only.)
There are two approaches, really:
Function-based views
urls.py
from myproj.views import abc_view
urlpatterns = (
url(r'^pymat/$', abc_view),
)
views.py
def abc_view(request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Class-based views
urls.py
from myproj.views import AbcView
urlpatterns = (
url(r'^pymat/$', AbcView.as_view()),
)
views.py
from django.views.generic import View
class AbcView(View):
def get(self, request, *args, **kwargs):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Since you are using class based views in Django, so you have to call this class in URL like:
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.as_view()),
)
and then use get or post methods name in class like:
class Pymat_program:
model = ModelName
def post(self, request, *args, **kwargs):
....
2nd way is to use function based views:
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.view_name),
]
views.py
from django.http import HttpResponse
import datetime
def view_name(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
for more details look into class based views docs

How to Make Different Urls For same viewset in Django Rest Framework

I have a Serializer in my code like this
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = Model
and Viewset like this
class SampleViewSet(GenericAPIView):
serializer_class = SampleSerializer
def get(self, request, *args, **kwargs):
pass
def post(self, request, *args, **kwargs):
pass
def put(self, request, *args, **kwargs):
pass
I have url like this for this viewset
Url #1:
url(r'^sample/$', SampleViewSet.as_view())
This makes url for all methods I have in my viewset like get, post and put etc. I want to make separate url for my get method but using same serializer. This url will look like this
Url #2:
url(r'^sample/(?P<model_id>\d+)/$', SampleViewSet.as_view())
How can I do this using same Viewset and Serializer? When I write Url #2 in urls.py, Swagger shows me all three type (get, post and put) of methods for that Url.
You could use require_GET decorator from django.views.decorators.http for this, and use it in your URL config:
urlpatterns = [
url(r'^sample/$', SampleViewSet.as_view()),
url(r'^sample/(?P<model_id>\d+)/$', require_GET(SampleViewSet.as_view())),
]
for more fine tuning there is also a require_http_method decorator which receives allowed methods in its parameters, e.g.:
url(r'^sample/(?P<model_id>\d+)/$', require_http_method(['GET', 'DELETE'])(SampleViewSet.as_view()))
See https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-in-urlconf for details.
Why don't you inherit ViewSet from viewsets.ViewSet and map your urls view DefaultRouter?
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'sample', SampleViewSet)
urlpatterns = router.urls
It will handle all urls for your. /sample/:id now will be available for GET, PUT and DELETE methods.
Also if it plain CRUD for your Sample model, there is a better solution to use a viewsets.ModelViewset.

How to route an url to an specific method of a class Django and DRF

I am very new in the python world and now I building an application with Django 1.8 with the Rest Framework and I want to create a class view to DRY my code.
For example I want to have a class view for the students in my system
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
def getWorstStudents(self, request, format=None):
# Logic here
How can I assign a specified URL in urls.py to hit this method?
And also I have implemented REST framework JWT Auth
http://getblimp.github.io/django-rest-framework-jwt/ for token auth.
How can I restrict the access to allow only authenticated users can access to this url?
Thank you in advance!
Use routers with viewsets.
First, subclass your view from ModelViewSet instead of from APIView. Second, use the #list_route decorator in your getWorstStudents method. Third, tie everything up in your urls.py with a router.
It should look like (I have not tested the code):
views.py
class StudentsViewSet(viewsets.ViewSet):
#list_route(methods=['get'], permission_classes=(IsAuthenticated,)) # you can define who can access this view here
def getWorstStudents(self, request, format=None):
# Logic here
# routers.py
router = routers.DefaultRouter()
router.register(r'students', views.StudentsViewSet, base_name='student')
# urls.py
import .routers import router
urlpatterns = [
url(r'^', include(router.urls)),
]
The router will generate a view with the name student-getWorstStudents accessible from the students/getWorstStudents url.
You can set urls like any other Django app, documented here
# urls.py
from django.conf.urls import url
from somewhere import SnippetList
urlpatterns = [
url(r'^your/url/$', SnippetList.as_view()),
]
About DRY with your method, you can define the method you want to response to, and call the getWorstStudents (btw by convention I would call it get_worst_students). Let's say you want to response the post method:
# views.py
from rest_framework.response import Response
def getWorstStudents(params)
class SnippetList(APIView):
def post(self, request, *args, **kwargs):
# call getWorstStudents method here and response a Response Object
You can define getWorstStudents inside SnippetList class or in other file to import wherever you need it.
Finally, about authentication, DRF provides classes for this, documented here.
From docs, you need define this in your settings.py file:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
And use it in your views:
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)
You can also define your own authentication class and set it in authentication_classes tuple. Custom authentication classes documented here.

Login restriction in django

This is a very basic question cause I´m new using django. I made a login form that works just fine with the users i have in the database. The problem is that if i enter, for example, "localhost:8000/Exi/index" it does go to the main 'Home' page, the problem, obviously, is that i want users to see this page only if they are logged in. I tried with the
class LoginRequiredMixin(object):
#classmethod
def as_view(cls):
return login_required(super(LoginRequiredMixin, cls).as_view())
class Index (LoginRequiredMixin,TemplateView):
template_name = 'index.html'
But that doesn´t work for me. Maybe im not seeing something cause i looked around a couple of similar questions here and everyone seemed to have fix this in their projects.
This is my urls for this page:
url(r'^$', views.LoginView.as_view(), name='login'),
url(r'^index$', views.Index.as_view(), name='index')
Thank you in advance.
For class-based views I encourage you to use django-braces's LoginRequiredMixin
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
class YourSecuredView(LoginRequiredMixin, TemplateView):
template_name = "yourtemplate.html"
Notice that LoginRequiredMixin has to be the left-most mixin.
Please refer to the documentation of Django: The login-required decorator
Example from the docs for simple views:
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
For class-based views, you can use the decorator in two different ways, as described in the docs: Decorating class-based views
First way, in the url routing definition:
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = patterns('',
(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
)
Second way, in the view itself:
class ProtectedView(TemplateView):
template_name = 'secret.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
If it's site-wide login restrictions you need give https://github.com/mgrouchy/django-stronghold#stronghold a try.

Categories

Resources