Django REST Framework #detail_route not working as expected - python

I can't get #detail_route to work as I think it is supposed to.
I have two API calls I want to handle:
/movie/
/movie/highlight
I am trying to use #detail_route to pickup the /movie/highlight url in the viewset.
My urls.py looks like this:
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UsersViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'movie', views.MovieViewSet)
My views.py looks like this:
rom django.contrib.auth.models import User, Group
from movies.models import Movie
from api.serializers import UserSerializer, GroupSerializer, MovieSerializer
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework import renderers
from rest_framework import viewsets
from rest_framework.decorators import detail_route
# Code from DRF quickstart tutorial
class UsersViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
# Code from DRF quickstart tutorial
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
# MY CODE
class MovieViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Movie.objects.all().order_by('-title')
serializer_class = MovieSerializer
#detail_route(renderer_classes=(renderers.StaticHTMLRenderer,))
def highlight(self, request, *args, **kwargs):
snippet = "Highlight"
return Response(snippet)
The serializers.py looks like this:
from django.contrib.auth.models import User, Group
from movies.models import *
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
# My code...
class MovieSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Movie
fields = ('title', 'details')
If I try the /movie/ url API call it works as expected. If I try the /movie/highlight/ call I get a 404 error.
I am a newbie to DRF so suspect I am doing something very silly here but can't find out what from the various docs and tutorials I have scanned.

You need to use #list_route decorator instead of #detail_route decorator.
This will generate the url movie/highlight/.
Using #list_route decorated method generates the url of type {prefix}/{methodname}/ whereas detail_route decorated method generateds url of type {prefix}/{lookup}/{methodname}/. Here methodname is name of your method and lookup is the lookup value on which lookup is performed to get the object for detail view.
class MovieViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Movie.objects.all().order_by('-title')
serializer_class = MovieSerializer
# use list_route decorator
#list_route(renderer_classes=(renderers.StaticHTMLRenderer,))
def highlight(self, request, *args, **kwargs):
snippet = "Highlight"
return Response(snippet)

If you register your URLs using routers.DefaultRouter(), it will generate the URL /movie/pk/highlight instead of /movie/highlight. If you want a custom URL other than the pre-generated one, use URL mapping.

Related

API Query in Django Rest Framework

I have created an API from Database, I can view the API but I am unable to do a query via URL for example: 127.0.0.1:8000/author?author_id=9, I am not sure where to add the query code. I want to filter using fields. Here is my models.py
class AuthorAPI(models.Model):
author_id=models.IntegerField()
name=models.TextField()
author_img_url=models.TextField()
title=models.TextField()
first_published_at=models.DateTimeField()
excerpt=models.TextField()
class Meta:
db_table = 'view_author'
serializers.py
from rest_framework import serializers
from .models import SortAPI, AuthorAPI
class AuthorAPISerializer(serializers.ModelSerializer):
class Meta:
model=AuthorAPI
fields='__all__'
views.py
from .serializers import APISerializer,AuthorAPISerializer
from .models import SortAPI, AuthorAPI
from rest_framework.response import Response
from rest_framework.decorators import api_view
#api_view(['GET'])
def getauthor(request):
if request.method == 'GET':
results = AuthorAPI.objects.all()
serialize = AuthorAPISerializer(results, many=True)
return Response(serialize.data)
In your views, use a ModelViewset
And add the fliter_backend attribute:
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
See here in the docs:
https://www.django-rest-framework.org/api-guide/filtering/#setting-filter-backends
class AuthorViewset(viewsets.ReadOnlyModelViewset):
serializer_class = AuthorAPISerializer
queryset = AuthorAPI.objects.all()
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
IMPORTANT
Using django_filter will require you to install additional requirements, buts its well worth it, see installation steps for django_filter here:
https://django-filter.readthedocs.io/en/stable/guide/install.html
And in your urls.py you need to register your viewser with a SimpleRouter as described in the docs here:
https://www.django-rest-framework.org/api-guide/viewsets/#example
Additionally, you'll need to set the filterset_fields to tell DRF what fields you want to allow the user to filter with.
As specified in the docs here:
https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html#using-the-filterset-fields-shortcut
And important word of warning which might not be emphasised enough in the documentation is this point:
Note that using filterset_fields and filterset_class together is not supported.
Once complete, if you browse to /author you should see some filter controls available, et voila
You can use request.GET to get data from URL parameters.
Give this a try
#api_view(['GET'])
def getauthor(request):
if request.method == 'GET':
results = AuthorAPI.objects.all()
# get author_id from the url query parameter
author_id = request.GET.get('author_id', None)
#if author_id is present in the url query parameter then filter the resluts queryset based on the author_id
if author_id:
results = results.filter(author_id=author_id)
serialize = AuthorAPISerializer(results, many=True)
return Response(serialize.data)
Thanks to Swift but there was some errors, viewsets.ReadOnlyModelViewset wasn't working perfectly so I tried this
views.py
import django_filters.rest_framework
from django.contrib.auth.models import User
from rest_framework import generics,viewsets,filters
from django_filters.rest_framework import DjangoFilterBackend,OrderingFilter
from rest_framework.pagination import PageNumberPagination
from rest_framework.renderers import JSONRenderer
class CustomPagination(PageNumberPagination):
page_size = 50
page_size_query_param = 'page_size'
max_page_size = 1000
class AuthorViewset(generics.ListAPIView):
renderer_classes = [JSONRenderer]
pagination_class = CustomPagination
serializer_class = AuthorAPISerializer
queryset = AuthorAPI.objects.all()
filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]
filterset_fields = ['name', 'id','author_id','status','title','first_published_at','story_type']
search_fields=['id','author_id','name','title','first_published_at']
ordering_fields=['id','author_id','name','title','first_published_at']
class Meta:
name="AuthorViewset"
I could have used Sumithran's answer but it was a bit complex if I would like to allow multiple fields because for every field I had to add the same code with some modification which could increase code lines.

How to add some cutom code to post method in django rest framework

I am new to django rest framework. I was trying to build a API where I want to edit the POST method so that I could execute some actions and change some data from the POST body.
I tried to follow some documentations and Guide from django rest framework website but didnt followed any. Please help me.
Here I need to set some values for some fields that are going to be saved in the database.
views.py
from .models import LoginActivity
from .serializers import LoginActivitySerializers
class LoginActivity(viewsets.ModelViewSet):
queryset = LoginActivity.objects.all()
serializer_class = LoginActivitySerializers
urls.py
from django.urls import path, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'LoginActivity', views.LoginActivity, basename='LoginActivity')
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
appname='api'
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
serializers.py
from rest_framework import serializers
from .models import LoginActivity
class LoginActivitySerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginActivity
fields = ('id', 'user_id', 'datetimelog', 'device_os', 'device_token', 'device_model')
Please Help me.
you can override rest framework create method which come from viewsets.ModelViewSet and update request data or perform some other actions.
from .models import LoginActivity
from .serializers import LoginActivitySerializers
class LoginActivity(viewsets.ModelViewSet):
queryset = LoginActivity.objects.all()
serializer_class = LoginActivitySerializers
def create(self, request, *args, **kwargs):
# here you have your post data in request.data
data = request.data
# you can do some action here just before create action
# after that you can call super method or return your response
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(status=status.HTTP_201_CREATED)

How to create several resources with one request Django Rest Framework?

I got a model Slider that has a simple one to many relationship with ImageSlider, thanks to ModelViewSet I can create sliders and then create ImageSliders and asociate them with a slider. But I would also like to be able to just upload a bunch of images and make several SliderImages and asociate them with the same slider I mean I want to have both options but Im not sure how to do that and how to test it with something like Postman. I hope you guys can help me understand that since Im still learning about DRF
Slider View
"""Slider view"""
# Django REST Framework
from rest_framework import viewsets
# Serializers
from api.sliders.serializers import SliderModelSerializer
# Models
from api.sliders.models import Slider
class SliderViewSet(viewsets.ModelViewSet):
"""Slider viewset"""
queryset = Slider.objects.all()
serializer_class = SliderModelSerializer
Slider Image View
"""Slider Images view"""
# Django
from django.shortcuts import get_object_or_404
# Django REST Framework
from rest_framework import viewsets
# Serializers
from api.sliders.serializers import SliderImageModelSerializer
# Models
from api.sliders.models import SliderImage
class SliderImageViewSet(viewsets.ModelViewSet):
"""Slider Image viewset"""
queryset = SliderImage.objects.all()
serializer_class = SliderImageModelSerializer
Slider serializer
"""Slider Serializers"""
# Django Rest Framework
from rest_framework import serializers
# Serializers
from api.sliders.serializers import SliderImageModelSerializer
# Model
from api.sliders.models import Slider
class SliderModelSerializer(serializers.ModelSerializer):
""" Slider Model Serializer. """
images = SliderImageModelSerializer(many=True, read_only=True)
class Meta:
"""Meta class"""
model = Slider
fields = (
'id',
'status',
'images',
)
Slider Image Serializer
"""SliderImage Serializers"""
# Django Rest Framework
from rest_framework import serializers
# Model
from api.sliders.models import SliderImage
class SliderImageModelSerializer(serializers.ModelSerializer):
""" Slider Image Model Serializer. """
slider_id = serializers.IntegerField()
class Meta:
""" Meta class """
model = SliderImage
fields = (
'id',
'url',
'link',
'slider_id',
)
I only care about uploading the files and creating/storing the resources, the other fields in ImageSlider aren't necessary
You can make another endpoint for that purpose, using actions.
Assuming a SliderImage is already created, it would be something like:
class SliderImageViewSet(viewsets.ModelViewSet):
"""Slider Image viewset"""
queryset = SliderImage.objects.all()
serializer_class = SliderImageModelSerializer
#action(detail=True, methods=['post'])
def images(self, request, pk=None):
user = self.get_object()
serializer = SliderImageSerializer(data=request.data)
if serializer.is_valid():
user.save()
return Response({'status': 'images saved'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
With SliderImageSerializer being your new serializer and your endpoint the same as registered to SliderImageViewSet, but with /<id>/images at the end, where id is the id of your SliderImage previously created.

How to return html or json from ViewSets depending on client django rest framework

I've created an APIs endpoints for mobile developers with all of the logic there. Now I need to create a web version of this app. Is it possible to not write the views specifically for this, but use already existing one in APIs?
In API I am using Django Rest Framework ViewSets.
Assume that this is my models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=20)
description = models.CharField(max_length=100, blank=True, null=True)
Then this is going to be my serializers.py:
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'description')
read_only_fields = ('id',)
viewsets.py:
from rest_framework import viewsets
from .serializers import PostSerializer
from .models import Post
class PostViewSet(viewsets.ModelViewSet):
model = Post
queryset = Post.objects.all()
serializer_class = PostSerializer
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'posts', viewsets.Post, base_name='post')
urlpatterns = router.urls
I've seen this HTML & Forms in docs, but if I put renderer_classes and template_name it just renders html with the parameters that I want, but I want to have a separate endpoints for just returning json for mobile developers and rendering a user-friendly html for web version. Is it possible to do this and if it is, how do I handle this?
I think you will have to manually create a separate endpoint to handle HTML requests, but you can easily achieve this using Template Renderers

Bulk Update data in Django Rest Framework

I am creating a Notification apps by Django Rest Framework which users can MARK AS READ ALL notification by using PATCH API in frontend. How can I Bulk Update data can do this task.
This serializer and viewset below just for PATCH only one notification object, but I want to do it all with Notifications which have field is_read = False
Edited with the right way
My Serializers:
class NotificationEditSerializer(ModelSerializer):
class Meta:
model = Notification
fields = (
'id',
'is_read'
)
My Viewset:
from rest_framework.response import Response
class NotificationListAPIView(ReadOnlyModelViewSet):
queryset = Notification.objects.all()
permission_classes = [AllowAny]
serializer_class = NotificationEditSerializer
lookup_field = 'id'
#list_route(methods=['PATCH'])
def read_all(self, request):
qs = Notification.objects.filter(is_read=False)
qs.update(is_read=True)
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)
My URL:
from rest_framework import routers
router.register(r'notifications/read_all', NotificationListAPIView)
You can try to use list_route for example:
from rest_framework.response import Response
from rest_framework.decorators import list_route
class NotificationListAPIView(ReadOnlyModelViewSet):
#YOUR PARAMS HERE
#list_route()
def read_all(self, request):
qs = Notification.objects.filter(is_read=False)
qs.update(is_read=True)
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)
the api is available by ^YOUCURRENTURL/read_all/$ more details marking-extra-actions-for-routing
NOTE! since DRF 3.10 #list_route() decorator was removed, you should use #action(detail=False) instead, I used #action(detail=False, methods=['PATCH']) to bulk patch, for example Thank you #PolYarBear

Categories

Resources