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')
]
Related
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/
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})
I have an API endpoint which activates a user's account. It expects uid and token in form data.
Certainly, it is a UX flaw to expect the user to POST to my endpoint
So they get an email with a link (containing a uid and token) for activating their account
like: http://example.com/api/auth/account/activate/{uid}/{token}
e.g. https://example.com/api/auth/account/activate/MzA/562-5bb096a051c6210994fd
So when clicked, the view for that link POSTs their data to the API endpoint and returns a templated page. My working implementation:
# urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('activate/<str:uid>/<str:token>', views.ActivationView.as_view(), name='activate')
]
# views.py
from django.views.generic.base import TemplateView
from djoser.views import ActivationView as DjoserActivationView
from django.test.client import RequestFactory
class ActivationView(TemplateView):
template_name = "users/activation.html"
def dispatch(self, request, *args, **kwargs):
form_data = {
'uid': kwargs['uid'],
'token': kwargs['token']
}
alt_request = RequestFactory().post('/api/auth/users/confirm/', form_data)
activate_view_response = DjoserActivationView.as_view()(alt_request)
kwargs['activation_status_code'] = activate_view_response.status_code
return super().dispatch(request, *args, **kwargs)
However, I do not think it is okay for me to be using RequestFactory the way I did here. I don't know any other way I could achieve this. What options are available with Django?
It looks like the logic in ActivationView is implemented as a single method, _action. So you could make your own view a subclass of that view and call the method in get.
class ActivationView(DjoserActivationView):
template_name = "users/activation.html"
def get(self, request, **kwargs):
serializer = self.get_serializer(data=kwargs)
if serializer.is_valid():
response = self._action(serializer)
kwargs['activation_status_code'] = activate_view_response.status_code
return super().dispatch(request, *args, **kwargs)
Note, I'm not familiar with Djoser and I don't really understand why they want you to implement it as a POST in the first place; they presumably have a good reason for doing so, so you should think carefully about whether you really want to do this.
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
I am using Django rest framework for designing API, and I have below code
urls.py
from .views import UserView, UserDetails
urlpatterns = [
url(r'^user/', UserView.as_view(), name = 'users'),
url(r'^user/(?P<user_id>[0-9]+)/', UserDetails.as_view(), name = 'users_detail'),
]
views.py
from rest_framework.decorators import api_view
from rest_framework import permissions
class UserView(APIView):
def get(self, request, format=None):
print "I am in userview !!"
.....
.....
return Response(users.data)
def post(self, request, format=None):
.....
.....
return Response(data)
class UserDetails(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, *args, **kwargs):
import ipdb; ipdb.set_trace()
return Response('OK')
And the endpoints that I am trying are below
http://localhost:8000/api/user/
http://localhost:8000/api/user/1/
The problem what I am having is both the above URL requests are going to same UserView class, but actually
http://localhost:8000/api/user/ should go to UserView class which is correct and happening now,
and http://localhost:8000/api/user/1/ should go to UserDetails class which is not happening right now and the request was still going to 'UserView' class and I don't know why, can anyone please let me know what's wrong in my code?
You need to terminate your url patterns.
url(r'^user/$', ...),
url(r'^user/(?P<user_id>[0-9]+)/$', ...),