How to automatically generate django rest framework documentation with parameters? - django

I use redoc + drf_yasg to generate the documentation of my api made with django-rest-framwork.
It works well as soon as I add an entry point it appears well in the documentation.
However, I don't understand how to add in the documentation the search parameters that can be given during the query.
For example, in the list function, I can pass 2 optional parameters, an id and name:
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)
I don't see any mention of the parameters in the documentation. How can I add them?
I don't know if this helps but here is how I implemented redoc:
schema_view = get_schema_view(
openapi.Info(
title="test API",
default_version='v1',
description=f"""
# Public API
**Reserved to authenticated users**
Token Lifetime :
* ACCESS_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME'))}
* REFRESH_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('REFRESH_TOKEN_LIFETIME'))}
### Links
[Get access token](************)
### Format
Use `?format=` option in url:
* `?format=api` (default)
* `?format=json`
""",
terms_of_service="",
contact=openapi.Contact(email="***********"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

You have to add your parameters to your view if you want to give info about them. I use drf_yasg with swagger but maybe this might help.
Here's an example.
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
id_machine = openapi.Parameter('id_machine', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False)
name = openapi.Parameter('account', openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)

Related

partial update in django rest viewset(not modelviewset)

i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see

How to assign multiple api views to single endpoint in django rest framework?

I have a model named Article and a few api views for it. They are divided for diffrent purposes (for example ArticleUpdateAPI class for UPDATE http method, ArticleDeleteAPI for DELETE method etc). In urls.py they are separated to diffrent endpoints (aritcle/pk/update, /article/pk/delete etc).
As I know, it's not good practice to build endpoint like this, so I want to bind them to single url and use diffrent classes for handling diffrent http methods. Is it possible and how? Examples are below
ArticleAPI.py
class ArticlePostAPI(generics.CreateAPIView):
serializer_class = ArticleSerializer
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
"comment": CommentSerializer.data
}, status=201)
class ArticleRetrieveAPI(generics.RetrieveAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.AllowAny
]
class ArticleListAPI(generics.ListAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.order_by('number', 'headline')
permission_classes = [
permissions.AllowAny
]
class ArticleUpdateAPI(generics.UpdateAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.IsAuthenticated
]
lookup_field = 'pk'
def update(self, request, *args, **kwargs):
instance = self.get_object()
if request.user != instance.author:
return Response({
"errors": "Logged in user and author must be same"
}, status=403)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
urls.py
urlpatterns = [
...
# Article API
path('article/post/', ArticlePostAPI.as_view(), name='article_creation'),
path('article/<int:pk>/', ArticleRetrieveAPI.as_view(), name='article_retrieve'),
path('article/', ArticleListAPI.as_view(), name='article_list'),
path('article/<int:pk>/update/', ArticleUpdateAPI.as_view(), name='article_update'),
]
Use ModelViewSet for simplifying it.
Using this you can perform all http method actions with a single url and single view.
from rest_framework import viewsets
class ArticleView(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [permissions.AllowAny]
urls.py
router = DefaultRouter()
router.register(r'articles', views.ArticleView, basename='articles')
here
GET - /articles/ - gives list of all articles
GET - /articles/id/ - gives one object
POST - /articles/ - creates an article
PUT - /articles/id/ - updates an article
PATCH - /articles/id/ - partially updates an article
DELETE - /articles/id/ - deletes an article

custom pagination not woking in apiview of Django rest

I have a custom pagination class in a separate file and until now I have been importing it in a ListAPIView, but this time I tried with APIView, but it didn't work.
My pagination class:
class CustomPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'page_size' : 15,
'results': data
})
I am trying to use custom pagination because I can have the count of the objects as well.
My view where I try to implement the pagination:
from apps.products.api.pagination import CustomPagination
class CouponView(APIView):
permission_classes = [AllowAny]
#pagination_class = CustomPagination
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc, request)
serializer = CouponSerializer(result_page,many=True)
return Response (serializer.data,status=status.HTTP_200_OK)
APIView doesn't support this feature. You'll either need to leverage ListApiView or similar generics (such as RetrieveApiView), or use the ReadOnlyModelViewSet. These both support pagination, and give you get_object.
Your code seems to be implementing the basic ViewSet features (list + retrieve). The issue with the API views is that these two both use get, so they can't be combined into one view. Using a viewset will solve that.
class CouponReadOnly(ReadOnlyModelViewSet):
pagination_class = SomeClass
serializer_class = CouponSerializer
queryset = Coupon.objects.all()
And then use a Router in your urls.py to link it up, and let the router generate the routes.
# in your urls.py --------------
router = SimpleRouter()
router.register("coupons", CouponReadOnly, "coupon")
urlpatterns = [...] # existing routes
urlpatterns += router.urls
If you want to do this manually with 2 views, then you can use the generics and manually create the paths, like you already are:
class CouponListView(ListAPIView):
# you said this one already works
class CouponGetView(RetrieveAPIView):
queryset = Coupon.objects.all()
serializer_class = CouponSerializer
urlpatterns = [
path("coupons/", CouponListView.as_view(), name="coupon-list"),
path("coupons/<int:pk>", CouponGetView.as_view(), name="coupon-get"),
]
You should use get_paginated_response in GenericAPIView for custom pagination.
from apps.products.api.pagination import CustomPagination
from rest_framework.generics import GenericAPIView
class CouponView(GenericAPIView):
permission_classes = [AllowAny]
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
self.pagination_class = CustomPagination
page = self.paginate_queryset(abc)
serializer = serializer(page, many=True, context={'request': request})
return self.get_paginated_response(serializer.data)
However, let apply the correct RESTFul API to get list and detail, so you will not custom more.
List coupons: GET /api/coupons/
Get coupon detail: GET /api/coupons/<coupon_id/
class CouponView(ListAPIView, RetrieveAPIView):
permission_classes = [AllowAny]
pagination_class = CustomPagination
queryset = Coupons.objects.all()
serializer_class = CouponSerializer
I did the following and it worked:
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc,request)
serializer = CouponSerializer(result_page,many=True)
# return Response (serializer.data,status=status.HTTP_200_OK)
return paginator.get_paginated_response(serializer.data)

Django filter not working

my filter isn't working Whenever I access http://localhost:8080/payables/invoices/?status=NOT_PAID It just returns all the invoices. I have no runtime error, the parameter I enter simply seems to be ignored. I really don't understand, other than that, it works well.
views.py
class InvoiceViewSet(viewsets.ViewSet):
serializer_class = InvoiceSerializer
filter_backend = filters.DjangoFilterBackend
filter_fields = ('status','supplier',)
def list(self,request,):
queryset = Invoice.objects.filter()
serializer = InvoiceSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Invoice.objects.filter()
invoice = get_object_or_404(queryset, pk=pk)
serializer = InvoiceSerializer(invoice)
return Response(serializer.data)
class InvoiceItemViewSet(viewsets.ViewSet):
serializer_class = InvoiceItemSerializer
def list(self,request,invoice_pk=None):
queryset = InvoiceItem.objects.filter(invoice=invoice_pk)
serializer = InvoiceItemSerializer(queryset,many=True)
return Response(serializer.data)
def retrieve(self,request,pk,invoice_pk):
queryset = InvoiceItem.objects.filter(pk=pk,invoice=invoice_pk)
invoice_item = get_object_or_404(queryset,pk=pk)
serializer = InvoiceItemSerializer(invoice_item)
return Response(serializer.data)
url.py
from django.conf.urls import url, include
#viewset
from rest_framework_nested import routers
from payables.views import InvoiceViewSet,InvoiceItemViewSet
router = routers.SimpleRouter()
router.register(r'invoices', InvoiceViewSet,base_name='invoices')
invoice_item_router = routers.NestedSimpleRouter(router,r'invoices',lookup='invoice')
invoice_item_router.register(r'items', InvoiceItemViewSet, base_name='invoice_items')
urlpatterns = [
url(r'^',include(router.urls)),
url(r'^',include(invoice_item_router.urls))
]
It is because you are explicitly creating the queryset and hence the filter backend is never used:
queryset = Invoice.objects.filter()
I suggest looking at ModelViewSet. In that case you just have to pass queryset at the view level and rest will be taken care of.
instead of queryset = Invoice.objects.filter()
with queryset = self.filter_queryset(self.get_queryset()).filter()
instead of queryset = Invoice.objects.filter()
use queryset = self.get_queryset()
self.get_queryset() returns the filtered object list

DjangoRestFramework - How to filter ViewSet's object list based on end-users input?

This is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, location=self.request.user.userextended.location)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self,
'location': self.request.user.userextended.location
}
#detail_route(methods=['post'], permission_classes=[IsFromLocationOrReadOnly])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
This is my router / urls.py:
router = routers.DefaultRouter()
router.register(r'posts', views.PostViewSet)
So when I go to /posts I get a list of all posts. What I want is to be able to allow the end-user to go to a specific URL like so: /posts/username and when he does, I want to give him all the posts of that specific username (the filtering will be simple. Something along these lines:
queryset = Post.objects.filter(username=usernameProvidedByTheURL)
How do I go about doing this? Is it possible using DRF?
In your url:
url(r'^/post/(?P<username>\w+)/?$', PostViewSet.as_view({'get': 'list'})),
Then in your PostViewSet, overwrite the get_queryset() method to filter the data by username
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
UPDATE
If you want to keep /post/ endpoint to retrieve all post. Then you need to create an extra view to handle /post/username
class PostsListByUsername(generics.ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
Then in your urls.py
url(r'^/post/(?P<username>\w+)/?$', PostsListByUsername.as_view()),
Note:
In your get_serializer_context method, you don't need to return request, format and view. DRF will append it for you.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'location': self.request.user.userextended.location
}