How to use drf implement nested url route? [duplicate] - django

In the docs there is the example of methods with custom url:
http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers
class SnippetViewSet(viewsets.ModelViewSet):
...
#link(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
This example add following route:
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
It is possible to add an url without pk param, like this?
r'^snippets/highlight/$'

The ViewSets docs mention using action decorator:
from rest_framework.decorators import action
class SnippetViewSet(viewsets.ModelViewSet):
...
#action(detail=False, methods=['GET'], name='Get Highlight')
def highlight(self, request, *args, **kwargs):
queryset = models.Highlight.objects.all()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Then just update your queryset to do whatever it needs to do.
The advantage of doing it this way is that your serialisation is preserved.
If your urls.py looks like this:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from snippets import viewsets
router = routers.DefaultRouter()
router.register('snippets', viewsets.SnippetViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('snippets/', include(router.urls)),
]
Then it is reachable via http://localhost:8000/snippets/highlights
To see usage for a POST, or how to change routing, see docs for routers.

Yes, you can do that. Just add your method in the viewset with the list_route decorator.
from rest_framework.decorators import list_route
class SnippetViewSet(viewsets.ModelViewSet):
...
#list_route(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
...
It will add a url without the pk param like :
r'^snippets/highlight/$'
You can even specify the methods it supports using the methods argument in your decorator.
http://www.django-rest-framework.org/api-guide/routers/#usage

Since this question still turns up on first Google Page, here is up-to-date (for the late march of 2020) snippet (pun intended) to start working on your custom ModelViewSet route for single object:
from rest_framework.decorators import action
class SnippetViewSet(viewsets.ModelViewSet):
...
#action(detail=True, methods=['POST'], name='Attach meta items ids')
def custom_action(self, request, pk=None):
"""Does something on single item."""
queryset = Snippet.objects.get(pk=pk)
serializer = self.get_serializer(queryset, many=False)
return Response(serializer.data)
Having default routers from the DRF tutorial will allow you to access this route with: http://localhost:8000/snippets/<int:pk>/custom_action/

Related

DjangoREST APIView - different url params for different methods

I got a DjangoREST APIView that supports Read and Create operations. Something like this:
class FirebaseUser(APIView):
...
get(request):
...
post(request):
...
urls.py:
...
path('user/', views.FirebaseUser.as_view()),
...
I need an API that would accept a read request with user id as url param
GET .../api/user/<userId>
But for create operation there's no user ID yet and I need something like this
POST .../api/user/
What is the best way to make my APIView treat url params differently depending on method?
You can define a ModelViewSet like this in your views.py:
from rest_framework import viewsets
class FirebaseUserViewSet(viewsets.ModelViewSet):
queryset = FirebaseUser.objects.all() # or whatever should your queryset be
serializer_class = FirebaseUserSerializer
Then, in your urls.py you register the viewset:
from django.urls import path
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'user', FirebaseUserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
This will create a few new API endpoints and you'll be able to do all the CRUD operations.
I suggest reading a bit more about ModelViewSets in the official docs.
Also, if you require only certain operations, for example only read and create you may consider extending only certain mixins from rest_framework.mixins (read more here).
So, I came up with using ViewSet instead of APIView.
This is how it looks now:
urls.py
path('user/', views.FirebaseUser.as_view({'post': 'create'})),
path('user/<str:pk>', views.FirebaseUser.as_view({'patch': 'update', 'delete': 'destroy'})),
views.py
class FirebaseUser(ViewSet):
authentication_classes = [...]
permission_classes = [...]
#staticmethod
def create(request):
...
#staticmethod
def update(request: Request, pk=None):
uid = pk
...
#staticmethod
def destroy(request: Request, pk=None):
uid = pk
...

How to return custom json response with Django REST Framework?

Actually i return json from some media files depends on url <str:scene> varialbe is set. I just want to use rest framework interface and auth token so i think that could be easier way to present my data.
Here what i do:
views.py
from rest_framework import views
from django.http import JsonResponse
class Manager(views.APIView):
def get(self, request, scene):
return JsonResponse({'get': scene})
def post(self, request, scene):
return JsonResponse({'post': scene})
urls.py
router = routers.DefaultRouter()
router.register(r'', views.Manager)
urlpatterns = [
path('<str:scene>', include(router.urls)),
]
This code requires me to use basename argument in path but since i don't use models i haven't anything to pass in it.
So what is the way to return my custom json data using rest framework interface?
You can't add the APIView to the routers as the routers are meant for ViewSet classes. Also, in DRF, it is good to use the Response class instead of JsonResponse
from rest_framework import views
from rest_framework.response import Response
class Manager(views.APIView):
def get(self, request, scene):
return Response({'get': scene})
def post(self, request, scene):
return Response({'post': scene})
Now, change the URL patterns as,
urlpatterns = [
path('<str:scene>', Manager.as_view(), name='manager-view'),
]
Your code is already giving out json data
I don't know where this JsonResponse comes from (if it's a dependency) but the DRF has the Response that will return the json it needs.
from rest_framework.response import Response
class Manager(views.APIView):
def get(self, request, scene):
return Response({'get': scene})
def post(self, request, scene):
return Response({'post': scene})

Django rest framework- Single class based view bound to multiple urls

I'm creating a rest api that's going to be consumed by desktop based clients.
I want my urls to be like this with a view class named ProjectView:
api.myapp.com/project/ -> uses ProjectView get
api.myapp.com/project/create/ -> uses ProjectView post
api.myapp.com/project/edit/ -> uses ProjectView put
I couldn't manage to bind a single view class to multiple urls without exposing all other actions(get, post, put) to that url. Instead I created ProjectView, ProjectViewCreate, ProjectViewEdit classes which seems pretty pointless.
Is there anyway I can accomplish the url configuration that I outlined with a single view class?
Hmmm...perhaps a solution such as this may be sufficient (modify for your project models as required):
from rest_framework import viewsets
class ProjectViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving, creating and updating projects.
"""
def list(self, request):
....
def create(self, request, pk=None):
....
def update(self, request, pk=None):
....
Then in urls.py:
from myapp.views import ProjectViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'project', UserViewSet.as_view({'get': 'list'}), base_name='projects')
router.register(r'project/create', UserViewSet.as_view({'post': 'create'}), base_name='project_create')
router.register(r'project/edit', UserViewSet.as_view({'put': 'update'}), base_name='project_update')
urlpatterns = router.urls
Hopefully, with a little modification for your code and url structure - this will work!
Define views as,
from rest_framework.views import APIView
from rest_framework.response import Response
class ProjectView(APIView):
def get(self, request, *args, **kwargs):
# your code GET method code
return Response("This is GET method")
def post(self, request, *args, **kwargs):
# your code POST method code
return Response("This is POST method")
def put(self, request, *args, **kwargs):
# your code PUT method code
return Response("This is PUT method")
and change your urls.py as,
urlpatterns = [
url(r'project/', ProjectView.as_view(), name='project_list'),
url(r'project/create/', ProjectView.as_view(), name='project_create'),
url(r'project/edit/', ProjectView.as_view(), name='project_edit')
]

Django-REST-framework Concrete-View-Classes not returning json?

I'm working on a new app using React and DRF in which React must get the data from DRF in JSON and parse the data. But I think some of my Generic View Classes do not return JSON correctly.
For Example, a "ListCreateAPIView" class returns this:
[{"id":5,"name":"5 Storey I=1.3","Value":1399511075,"NSt":5},{"id":6,"name":"5 Storey I=0.7","Value":1344981250,"NSt":5},{"id":7,"name":"5 Storey I=1","Value":1363157800,"NSt":5}]
While a "RetrieveUpdateDestroyAPIView" class returns this:
{"id":6,"name":"5 Storey I=0.7","Value":1344981250,"NSt":5,"jRatio":"[0.2,0.4,0.4]","jEDR":"[0.02,0.1,0.5,1]","jStrDrSL":"[0.3826667,0.6046667,0.78,0.8666667]","StrDrB":0.8,"jNStrDrSL":"[0.4,0.8,2.5,5]","NStrDrB":0.75,"jNStrAccSL":"[0.25,0.5,1,2]","NStrAccB":0.5,"jPGA":"[0.141,0.374,0.550,0.822]","jRePr":"[75,475,975,2475]","NNTH":7,"jP_CL":"[ 0 , 5 , 6 , 7 ]","jMIDR":"[[[0.014488,0.021893,0.010635,0.029521,0.009106,0.013556,0.034016], [0.019524,0.022306,0.013733,0.041172,0.012122,0.019027,0.027467], \r\n[0.018057,0.019549,0.01485,0.034628,0.010172,0.022447,0.02065], \r\n[0.018057,0.015954,0.009193,0.024401,0.006838,0.022809,0.017592], \r\n[0.017192,0.012215,0.009257,0.016268,0.005854,0.019945,0.012777]], \r\n\r\n[[0.016604,0.025492], \r\n[0.026047,0.03113], \r\n[0.02794,0.02432], \r\n[0.024571,0.01748], \r\n[0.023962,0.014474]], \r\n\r\n[[0.040325], \r\n[0.044064], \r\n[0.035164], \r\n[0.024971], \r\n[0.020532]], \r\n\r\n[[1], \r\n[1], \r\n[1], \r\n[1], \r\n[1]]]","jMAcc":"[[[0.081014271,0.126595311,0.094557594,0.094250765,0.068444444,0.088719674,0.118411825], \r\n[0.06911213,0.091793068,0.097146789,0.11106422,0.056927625,0.118172273,0.103258919], \r\n[0.076614679,0.077261978,0.120961264,0.144769623,0.055780836,0.08214475,0.123833843], \r\n[0.082191641,0.08675739,0.129832824,0.13788685,0.04724159,0.071845056,0.091466871], \r\n[0.18904791,0.111108053,0.098691131,0.13933945,0.052319062,0.198410805,0.117994903]], \r\n\r\n[[0.184002039,0.156264016], \r\n[0.18011213,0.122554536], \r\n[0.24753211,0.136911315], \r\n[0.292653415,0.120941896], \r\n[0.255610601,0.127876656]], \r\n\r\n[[0.201146789], \r\n[0.173687054], \r\n[0.17719368], \r\n[0.172267074], \r\n[0.187479103]], \r\n\r\n[[1], \r\n[1], \r\n[1], \r\n[1], \r\n[1]]]"}
Notice that the data doesn't start with a bracket "[" and because of this I'm not able to parse it in React. I need to know where the problem is. Should I not use GenericViewClasses?
views.py:
class BuildingsList(ListCreateAPIView):
queryset=Building.objects.all()
serializer_class=BuildingSerializerList
class BuildingDetails(RetrieveUpdateDestroyAPIView):
queryset=Building.objects.all()
serializer_class=BuildingSerializerDetails
urls.py:
urlpatterns=[
path('', BuildingsList.as_view()),
path('<int:pk>/', BuildingDetails.as_view()),]
serializers.py:
class BuildingSerializerList(serializers.ModelSerializer):
class Meta:
model=Building
fields=['id','name','Value','NSt']
class BuildingSerializerDetails(serializers.ModelSerializer):
class Meta:
model=Building
fields='__all__'
The Detail-View means, it only returns One item/details of particular look-up. and moreover, the response behavior will purely depend on your Serializer classes (BuildingSerializerList and BuildingSerializerDetails).
The ListCreateAPIView is meant for listing all your Building instances.
In terms of react, the Detail-view returns a JSON object whereas in List-view it returns a JSON-Array
I would recommend you to use DRF's ModelViewset class for views, which is very handy
UPDATE-1
You can do simple CRUD operations on Sample model through REST-API by using following view class
from rest_framework.viewsets import ModelViewSet
class SampleViewset(ModelViewSet):
serializer_class = SampleSerializer
queryset = SampleModel.objects.all()
If you are trying to do CRUD operations by using GenericViewSet, you need to write views like this,
from rest_framework.viewsets import GenericViewSet
class SampleNew(GenericViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def list(self, request, *args, **kwargs):
# your list logic
return response
def create(self, request, *args, **kwargs):
# your list logic
return response
def destroy(self, request, *args, **kwargs):
# your list logic
return response
def retrieve(self, request, *args, **kwargs):
# your list logic
return response
Did you see the difference !!
Get ListCreateAPIView will return a list(jsonarray),get RetrieveUpdateDestroyAPIView will return a single object info(jsonobject) is right,that's ListCreateAPIView and RetrieveUpdateDestroyAPIView designed to be.You would better change the parse code in fontend in React.
In your case is GenericAPIView not suit your requirement,I think you want to CRUD the model Building by DRF,then the ModelViewSet is something you actually needed.
views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
class BuildingViewSet(ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializerList
permission_classes = (IsAuthenticated,)
def get_serializer_class(self):
if self.action in ['update', 'partial_update', 'retrieve']:
return BuildingSerializerDetails
else:
return self.serializer_class
urls.py:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'building', BuildingViewSet)
urlpatterns = [
path('', include(router.urls)),
path('admin/', admin.site.urls),
]
then
create with post http://localhost/building/
list with get http://localhost/building/ # return jsonarray
detail with get http://localhost/building/id/ # return jsonobject
update with put/patch http://localhost/building/id/
delete with delete http://localhost/building/id/
If you really want detail return a list,simple change to:
class BuildingViewSet(ModelViewSet):
....
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response([serializer.data])
but it's really meaningless.

django 403 forbidden - redirect to login

How can I redirect a user to the login page when confronted with a 403 Forbidden?
Consider the following code:
urls.py
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
renderer_classes = (renderers.JSONRenderer, renderers.TemplateHTMLRenderer)
template_name='rest_framework/users.html'
def list(self, request, *args, **kwargs):
response = super(viewSet, self).list(request, *args, **kwargs)
if request.accepted_renderer.format == 'html':
return Response({'request':request, 'queryset': response.data['results']})
return response
If I visit /domain/users while not logged in, I'll get a 403 Forbidden error. I would like to instead, redirect to the login page. In other words, never show 403 errors but instead redirect directly to the login page.
I have tried putting:
#login_required
def list(...):
but that does not work. I assume because its inside a viewset.
I have also tried:
def list(...):
if not request.user.is_authenticated():
return render_to_response('domain/login.html')
but again, doesn't work.
I tried various 403middlewares but to no avail.
I tried redirecting via the handler403 handler in urls.py
handler403 = 'site.views.login'
No success.
What to do?
Django version: 1.5.4
You can't use the standard login_required decorator on a class method. You have to either wrap the as_view() when you use it in the URL:
url(r'...', login_required(UserViewSet.as_view()))
or wrap the decorator with django.utils.decorators.method_decorator and apply it to the class dispatch method:
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
class UserViewSet(viewsets.ModelViewSet):
...
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
....
(see https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-class-based-views)
For class-based views you can incorporate this into a reusable PermissionRequiredMixin - see https://stackoverflow.com/a/6455140/839875
Alternatively you can raise a django.core.exceptions.PermissionDenied exception which Django will catch with django.views.defaults.permission_denied
(see https://docs.djangoproject.com/en/1.5/topics/http/views/#the-403-http-forbidden-view)