DjangoREST APIView - different url params for different methods - django

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
...

Related

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

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/

Validate pk as int in drf viewset retrieve url

Code looks as follows:
class UserViewSet(ViewSet):
# ... Many other actions
def list(self):
# list implementation
def retrieve(self, request, pk):
# manual pk int validation
router = DefaultRouter()
router.register(r"users", UserViewSet, basename="users")
urlpatterns = router.urls
Right now pk is not validated as int therefore a request to db is made, which I want to avoid. Is there any way I can add that type of validation in urls?
I can achieve that without using router like this:
urlpatterns = [
path('users/<int:pk>/', UserViewSet.as_view({'get': 'retrieve'}),
# many other actions have to be added seperately
]
But I have many actions in my viewset and all of them have to be added separately. Is there a cleaner way to do so or a package?
Use lookup_value_regex attribute as,
class UserViewSet(ViewSet):
lookup_value_regex = '\d+'
...

How to accept form data and return it along with the results of some processing in Django RESTFramework?

I am trying to understand Django RESTFramework. I am already familiar with Django. I want to create an endpoint that accepts some text data and processes it and returns it to the user along with the results of the processing (in text). I have completed a couple of tutorials on the topic but I still don't understand how it works. Here is an example from a working tutorial project. How can I edit it to achieve my goal? It all looks automagical.
# views.py
from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
​
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
# Here I would like to accept form data and process it before returning it along with the
# results of the processing.
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Okay, I think you are a newbie in Django rest and try to understand its flow so I can explain it with an example of a subscription plan.
First, create a model in models.py file
from django.db import models
class SubscriptionPlan(models.Model):
plan_name = models.CharField(max_length=255)
monthly_price = models.IntegerField()
yearly_price = models.IntegerField()
Then create views in a view.py file like
from rest_framework.views import APIView
class SubscriptionCreateAPIView(APIView):
serializer_class = SubscriptionSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{'message': 'Subscription plan created successfully.',
'data': serializer.data},
status=status.HTTP_201_CREATED
)
and then define a serializer for validation and fields in which we can verify which fields will be included in the request and response object.
serializers.py
from rest_framework import serializers
from .models import SubscriptionPlan
class SubscriptionSerializer(serializers.ModelSerializer):
plan_name = serializers.CharField(max_length=255)
monthly_price = serializers.IntegerField(required=True)
yearly_price = serializers.IntegerField(required=True)
class Meta:
model = SubscriptionPlan
fields = (
'plan_name', 'monthly_price', 'yearly_price',
)
def create(self, validated_data):
return SubscriptionPlan.objects.create(**validated_data)
Now add urls in src/subsciption_module/urls.py
from django.urls import path
from .views import SubscriptionCreateAPIView
app_name = 'subscription_plan'
urlpatterns = [
path('subscription_plan/', SubscriptionCreateAPIView.as_view()),
]
At the end include module url in root urls.py file where your main urls will be located. It will be the same directory which contains settings.py and wsgi.py files.
src/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('src.subscription_plan.urls', namespace='subscription_plan')),
]
That's it. This is how flow works in django rest and you can process data and display data in this way. For more details you can refer django rest docs.
But this is not in any way different from what you do with plain Django. Your SnippetDetail view is just a class-based view, and like any class-based view if you want to do anything specific you override the relevant method. In your case, you probably want to override update() to do your custom logic when receiving a PUT request to update data.

Django ,View class,There are two put methods

environment: django==1.11.11, rest_framework
Having methods with the same name handle different routes in the same view class or view set, using the decorator ?
example:
class Indexs(APIView):
#decorator("detailed/")
def get(self, request):
pass
#decorator("list/")
def get(self, request):
pass
# supplemental
I want to handle two GET requests in the same view class. One GET request details, the other request list content, but the same name request is overwritten
urls.py
from django.conf.urls import url
from indexs import views
urlpatterns = [
# url(r'category/$', views.Indexs.as_view()),
url(r'list/$', views.Indexs.as_view()),
url(r'detailed/$', views.Indexs.as_view()),
]
views.py
class Indexs(APIView):
def get(self, request):
return Response({"list": "list"})
def get(self, request):
return Response({"detailed": "detailed"})
http://127.0.0.1/list and http://127.0.0.1/detailed
Return results are all
{
"detailed": "detailed"
}
but i want
http://127.0.0.1/list Return results
{
"list": "list"
}
http://127.0.0.1/detailed
{
"detailed": "detailed"
}
Learn more about implementing class based views in django rest framewrok documentation it's pretty much straight forward. Django Rest Framework makes it easy to write DRY code with built in Generic views.
For example,an in built generic view like ListCreateAPIView below will perform GET and POST Http methods.
You can get a list of snippets and snippet detail from your urls:
urls.py
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
views.py
class SnippetAPIView(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

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.