I am using Django Rest Framework for my API project. Now i have one APIVIEW with post and get method. How can i add different endpoint only for a particular get or post.
class UserView(APIVIEW):
def get(self, request, format=None):
.....
pass
def post(self, request, format=None):
.....
pass
Now in the urls.py, I want something like this:
urlpatterns = [
url(r'^user\/?$', UserView.as_view()),
url(r'^user_content\/?$', UserView.as_view()),
]
user only accept GET-request and user_content only accept POST-request.
Do not do that. You already can handle different types of request separately in your APIView. You can create two different APIViews, or you can handle this in get or post methods. You can try something like this:
class UserView(APIView):
def get(self, request, format=None):
is_user_request = request.data.get('is_user_request', False)
if is_user_request:
# Handle your user request here and return JSOn
return JsonResponse({})
else:
# Handle your other requests here
return JsonResponse({})
def post(self, request, format=None):
is_user_content_request = request.data.get('is_user_content_request', False)
if is_user_content_request:
# Handle your user content request here and return JSOn
return JsonResponse({})
else:
# Handle your other type requests (if there is any) here
return JsonResponse({})
urlpatterns = [
url(r'^api/user$', UserView.as_view()),
]
This is just an example. If there are specific parameters for your each requests, you can identify your request's type from those parameters. You don't have to put extra boolean values like i did above. Check this way and see if that works for you.
Related
I'm making an user authentication app using Django authentication framework. I'm using provided views like LoginView and other views (from django.contrib.auth). Problem is when users (authenticated or anonymous) access urls:
path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
they see the these pages. for example the page that confirms that a link to reset their password has been sent to their email. while the user is already signed in and they didn't ask for password reset and there is no email send to their email because they never asked to reset their password. they just manually accessed email has been sent confirmation page 127.0.0.1:8000/account/password_reset/done/.
How to prevent them from accessing these urls or show them appropriate message?
For such cases, you need to generate a unique request no and send it through query params e.g. 127.0.0.1:8000/account/password_reset/done?req_no=XXXXXXXX or just add a lookup field in URL e.g.
path('password_reset/done/<int:req_no> or <str:req_no>', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
now you can get request no. in view, check if such request no exist if so send the templet for success message else redirect to home page or send 404 or anything you like,
to store the request no. you should create a new model and after sending the success templet, delete that req no from the database.
You can use the UserPassesTestMixin which can be imported from
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
then you can inherit the method you want to override in views,
for ex.
from django.contrib.auth.views.PasswordResetView
after inheriting the view you can perform the thing you want to do in test function
class ClassName(UserPassesTestMixin, PasswordResetView):
def test_func(self):
# do your thing
Thanks to answers i came up with this solution. (you can use value of was_sent to decide whatever). But as i'm new and have low experience there are major doubts about the practicality of this solution. comments are appreciated. (most of the code is copied from the source. added lines are marked with #)
class CustomPasswordResetDoneView(PasswordResetDoneView):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
self.request.session['was_sent'] = False #
return self.render_to_response(context)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session.get('was_sent', False) #
return context
class CustomPasswordResetView(PasswordResetView):
def get(self, request, *args, **kwargs):
self.request.session.get('was_sent', False) #
self.request.session['was_sent'] = False #
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
self.request.session['was_sent'] = True #
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session['was_sent'] #
return context
I am creating a simple rest api using django REST framework. I have successfully got the response by sending GET request to the api but since I want to send POST request, the django rest framework doesn't allow POST request by default.
As in image(below) only GET,HEAD, OPTIONS are allowed but not the POST request
The GET and POST methods inside of views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from profiles_api import serializers
from rest_framework import status
# Create your views here.
class HelloApiView(APIView):
"""Test APIView"""
#Here we are telling django that the serializer class for this apiViewClass is serializer.HelloSerializer class
serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Retruns a list of APIViews features."""
an_apiview = [
'Uses HTTP methods as fucntion (get, post, patch, put, delete)',
'It is similar to a traditional Django view',
'Gives you the most of the control over your logic',
'Is mapped manually to URLs'
]
#The response must be as dictionary which will be shown in json as response
return Response({'message': 'Hello!', 'an_apiview': an_apiview})
def post(self,request):
"""Create a hello message with our name"""
serializer = serializer.HelloSerializer(data=request.data)
if serializer.is_valid():
name = serializer.data.get('name')
message = 'Hello! {0}'.format(name)
return Response({'message':message})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How to allow POST requests in django REST framework?
The problem with the code was, you have added the def post() after the return statement.
To solve, just correct your indentation level as below,
class HelloApiView(APIView):
def get(self, request, format=None):
return Response()
def post(self, request):
return Response()
I have a django app that processes both POST and GET requests.
What is "the best" way to design the view? Should I have separate methods to handle each type of request? Or should I just use one method? Or should the methods be dependent on the functionality?
Thanks in advance.
You could use function based views or Class based views:
In the first case:
# function based views
def my_view(request):
if request.method == 'POST':
# Handle post method
else: # request.method == 'GET'
# Handle get method
In the second case:
# Class based views
class MyView(View): # Use the view that fix your needs
def get(self, request, *args, **kwargs):
# Handle get method
return HttpResponse()
def post(self, request, *args, **kwargs):
# Handle post method
return HttpResponse()
Essentially, I'm trying to find a good way to attach more views to a Router without creating a custom Router. What's a good way to accomplish this?
Here is something sort of equivalent to what I'm trying to accomplish. Variable names have been changed and the example method I want to introduce is extremely simplified for the sake of this question.
Router:
router = routers.SimpleRouter(trailing_slash=False)
router.register(r'myobjects', MyObjectViewSet, base_name='myobjects')
urlpatterns = router.urls
ViewSet
class MyObjectsViewSet(viewsets.ViewSet):
""" Provides API Methods to manage MyObjects. """
def list(self, request):
""" Returns a list of MyObjects. """
data = get_list_of_myobjects()
return Response(data)
def retrieve(self, request, pk):
""" Returns a single MyObject. """
data = fetch_my_object(pk)
return Response(data)
def destroy(self, request, pk):
""" Deletes a single MyObject. """
fetch_my_object_and_delete(pk)
return Response()
One example of another method type I need to include. (There are many of these):
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
The end-result is that the following URL would work correctly and be implemented 'cleanly'.
GET example.com/myobjects/123/locations
The answer given by mariodev above is correct, as long as you're only looking to make GET requests.
If you want to POST to a function you're appending to a ViewSet, you need to use the action decorator:
from rest_framework.decorators import action, link
from rest_framework.response import Response
class MyObjectsViewSet(viewsets.ViewSet):
# For GET Requests
#link()
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
# For POST Requests
#action()
def update_location(self, request, pk):
""" Updates the object identified by the pk """
location = self.get_object()
location.field = update_location_field() # your custom code
location.save()
# ...create a serializer and return with updated data...
Then you would POST to a URL formatted like:
/myobjects/123/update_location/
http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing has more information if you're interested!
You can now do this with the list_route and detail_route decorators: http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
For example:
from rest_framework.decorators import list_route
from rest_framework.response import Response
...
class MyObjectsViewSet(viewsets.ViewSet):
...
#list_route()
def locations(self, request):
queryset = get_locations()
serializer = LocationSerializer(queryset, many=True)
return Response(serializer.data)
You define method like you do now, but you need to use the same url as method name and add link decorator, so for
/myobjects/123/locations/
You add method like this
#link(permission_classes=[...])
def locations(self, request, pk=None):
...
and router will pick it automatically.
From Routing to extra methods on a ViewSet:
I think you may need to route the method by hand, i.e. The Old-Fashioned Way™.
First pull the method out as a separate view:
set_password_view = UserViewSet.as_view({'post': 'set_password'})
(or such)
Then assign your URL:
url(r'^users/username_available/$', set_password_view, name-=...)
(Or such)
There's a related question on SO.
If you want to extend a viewset with a view that is or should not directly be written inside your viewset, you can write a “wrapper” action to pass the data through.
For example, with class based views:
from somewhere import YourExternalClassView
class SomeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
#action(detail=True)
def your_action(self, request, pk):
return YourExternalClassView.as_view()(request, pk=pk)
How does it work?
On class based views, the as_view method returns a view function, to which we will pass the data we received from the action. The view will then hand over to process further.
For non-class based view, the views can be called/wrapped directly without .as_view(...)(...).
First of all I want both views use exact same URL because I don't want to make my URLConf more complicated. I want separate views for GET and POST to make my code cleaner. The code is something like this:
def view2 (request):
# handle POST request, possibly a ajax one
return HTTPRESPONSE(json_data, mimetype="Application/JSON")
def view1 (request):
if method == POST:
view2(request)
# What should I return here???
else:
# handle GET
return render(request, template, context)
My question is about the # What should I return here??? line. If I don't put a return there, error occurs:
not returning http response
But I already return an HTTP response in view2. How can I make this work?
Another, probably a bit cleaner way would be using class-based views
from django.views.generic import TemplateView
class View1(TemplateView):
def get(self, request, *args, **kwargs):
"""handle get request here"""
def post(self, request, *args, **kwargs):
"""handle post request here"""
def head(self, request, *args, **kwargs):
"""handle head request here. Yes, you can handle any kind of requests, not just get and post"""
Of course you can add common methods, __init__ (which is useless unless you are sure what you are doing), apply login_required (see this SO question) and pretty much everything you can do with django views (e.g. apply middleware, permissions, etc.) and python classes (e.g. inheritance, metaclasses/decorators, etc.)
Also, there's a whole bunch of generic class based view coming with Django to address common situations like list page, details page, edit page, etc.
You need to return the results of view2:
def view1 (request):
if request.method == 'POST':
return view2(request)
else:
# handle GET
return render(request, template, context)