Url for custom method in django apiview - django

I am new in Django and I am bit confused in Django apiview for custom method.
In ApiView, How can I create a custom method and How to call from axios.
For example
Here is my View
class TimeSheetAPIView(APIView):
#action(methods=['get'], detail=False)
def getbytsdate(self, request):
return Response({"timesheet":"hello from getbydate"})
def get(self,request,pk=None):
if pk:
timesheet=get_object_or_404(TimeSheet.objects.all(),pk=pk)
serializer = TimeSheetSerializer(timesheet)
return Response({serializer.data})
timesheet=TimeSheet.objects.all()
serializer = TimeSheetSerializer(timesheet,many=True)
return Response({"timesheet":serializer.data})
Here is my URL=>
url(r'^timesheets_ts/(?P<pk>\d+)/$', TimeSheetAPIView.as_view()),
url(r'^timesheets_ts/', TimeSheetAPIView.as_view()),
Normally my url would be like=>
api/timesheet_ts/
this one will get all of my records.
So my question is how can I setup URL for getbytsdate or getbyname or other some kind of custom get method? and how can I call?
I tried like this way=>
url(r'^timesheets_ts/getbytsdate/(?P<tsdate>[-\w]+)/$', TimeSheetAPIView.as_view()),
and I called like that
api/timesheets_ts/getbytsdate/?tsdate='test'
Its not work.
So please can u explain for the custom method in apiview and url setting?

In addition to your implementation, you just need to show your custom get request to your urls.py. Edit your urls.py as follows:
# urls.py
timesheet_getbytsdate_detail = TimeSheetAPIView.as_view({'get': 'getbytsdate'})
timesheet_detail = TimeSheetAPIView.as_view({'get': 'retrieve'})
urlpatterns = [
url(r'^timesheets_ts/getbytsdate/(?P<tsdate>[-\w]+)/$', getbytsdate_detail),
url(r'^timesheets_ts/(?P<pk>[0-9]+)/', timesheet_detail),
]
EDIT: You need to use the combination viewsets.GenericViewSet and mixins.RetrieveModelMixin instead of APIVewto get use of that:
class TimeSheetAPIView(viewsets.GenericViewSet, mixins.RetrieveModelMixin):
#action(methods=['get'], detail=False)
def getbytsdate(self, request):
return Response({"timesheet":"hello from getbydate"})
def retrieve(self, request, *args, **kwargs):
timesheet=self.get_object()
serializer = TimeSheetSerializer(timesheet)
return Response({serializer.data})
timesheet=TimeSheet.objects.all()
serializer = TimeSheetSerializer(timesheet,many=True)
return Response({"timesheet":serializer.data})

Related

What is the meaning of detail argument in Django ViewSet?

I create a custom action method in Django ViewSet and I see detail argument. If I set detail=True I can't call this method from URL but if I set detail=False, I can call this method. May I know what is the meaning of detail argument?
Here is my viewset = >
class TimeSheetViewSet(viewsets.ModelViewSet):
queryset = TimeSheet.objects.all()
serializer_class = TimeSheetSerializer
#action(methods=['get'], detail=True)
def byhello(self, request):
return Response({"From Hello":"Got it"})
Here are router and URL patterns =>
router.register('timesheets_ts', TimeSheetViewSet, base_name='timesheets')
urlpatterns = [
path('api/', include(router.urls))
]
As the docs states, if you pass detail=True it means that that router will return you a single object whilst if you don't pass detail=True or pass detail=False it will return a list of objects.
One thing to have in mind is that if you are not doing anything or don’t need a single object in this function, you can set the detail=False
In your case it’d be something like:
#action(methods=['get'], detail=True)
def byhello(self, request, pk=None):
self.object = self.get_object()
return Response({"From Hello":"Got it"})

HttpResponse error django generic template

I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset

Exclude Endpoint HTTP methods from Django REST Swagger

Is there a way to hide just some of the methods of an endpoint and not the whole endpoint? (e.g. show the POST method but hide the DELETE one)
where I have tried to customize the documentation using the AutoSchema
For example an endpoint like
router.register(r'audittrial', AuditTrialViewSet, 'AuditTrial')
would have the following schema defined
class AuditTrialCustomView(AutoSchema):
#staticmethod
def get_field(name, required, location, schema, description):
return coreapi.Field(
name=name,
required=required,
location=location,
schema=schema,
description=description
)
def get_manual_fields(self, path, method):
extra_fields = []
if method == 'GET':
extra_fields = [
self.get_field("from", False, "query", coreschema.String(), "Date of the start of the Audit Trial"),
....
]
return extra_fields
Is there any method I would be able to achieve this?
DRF has following example - see if it helps you.
class CustomAutoSchema(AutoSchema):
def get_link(self, path, method, base_url):
# override view introspection here...
#api_view(['GET'])
#schema(CustomAutoSchema())
def view(request):
return Response({"message": "Hello for today! See you tomorrow!"})
so that api_view decorator should help you. it takes list of methods in list as an argument.

Get unpaginated results from Django REST Framework

I have pagination enabled by default, which is based on PageNumberPagination; this had sufficed until now as the API was only used by a front-end. Now we try to build automation on top of it, and I’d like to pass the full, unpaginated result set back to the client.
Is there a way to disable pagination for specific requests, e.g. if a request parameter is passed?
I used a similer appraoch to accepted answer
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if request.query_params.get('get_all', False) == 'true':
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
now if you pass ?get_all=true while making the request, you will get unpaginated response.
If you are using page number pagination style, the below solution may be better.
def paginate_queryset(self, queryset, request, view=None):
if 'page' not in request.query_params:
return None
return super().paginate_queryset(queryset, request, view)
So you simply send a request without the page query_param.
I actually did go with a custom pagination class:
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if getattr(request, 'get_all', False):
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
Now I just have to set request.get_all = True in my viewset and I get all the items.
thnaks to this answer,
Else, if you use limit/offset you can use this in Generic List API View class:
def paginate_queryset(self, queryset):
if 'limit' not in self.request.query_params:
return None
return super().paginate_queryset(queryset)
It worked with python 3.9/ Django 4.0, In my case, this method had no argument named request and view, so I fixed it.
this will also won't render paginator json, when the response is not paginated.
You can approximate this with a request limit set to an unfeasibly large number, but you need to configure Django first:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
Then make a ridiculously high limit request:
GET https://api.example.org/accounts/?limit=999999999999
LimitOffsetPagination
- django-rest-framework.org
Actually DRF docs for this is very unclear, and there is no clear answer address this issue. After reading the source code, the followling code works for me.
from rest_framework.pagination import LimitOffsetPagination
class Unpaginated(LimitOffsetPagination):
def paginate_queryset(self, queryset, request, view=None):
self.count = self.get_count(queryset)
self.limit = self.get_limit(request)
self.offset = self.get_offset(request)
self.request = request
self.display_page_controls = False
return list(queryset)
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all().order_by("-id")
pagination_class = Unpaginated
The key here is to override the paginate_queryset function in base class and return the whole queryset.
However, overriding this function is not documented at all in the docs or I just missed it.
https://www.django-rest-framework.org/api-guide/pagination/
https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py
I solve this problem in the following way:
make your own pagination class and inherit it from PageNumberPagination
redefine or add your code (with super()) in get_page_size function.
For me works this case:
def get_page_size(self, request):
super().get_page_size(request)
.......
self.page_size = self.max_page_size
return self.page_size

adding metadata to filtering in django rest framework

I have a generic ListCreateAPIView view. I've implemented a get_queryset function that performs a search. The function parses the query, extract tags and terms and returns a query set.
def get_queryset(self):
query = self.request.QUERY_PARAMS.get('query', None)
# No deleted items
queryset = Items.objects.filter(deleted__isnull=True)
if query is None:
return queryset
predicates = []
# Generate predicates from query
queryset = queryset.filter(reduce(__and__,predicates))
return queryset
What is the best way to add metadata to the response with data from the get_queryset function ?
I'm looking for something similar to the way pagination works.
{
query : {
terms : ['term1','term2'],
tags : ['tag1','tag2'] ,
}
results : [
{ name : 'item1', .... }
{ name : 'item2', .... }
]
}
EDIT
So i created a custom FilterBackend for the filtering and I now have an instance of the request and the response. Looking at the pagination code for django rest i see it's wrapping the results in serializer. The pagination is build into the view class so the fw invokes the serialization if a paginator is detected. Looking at the search api did not produce any new ideas.
My question remains, What is the best, and least intrusive way, of adding metadata from the filter backend to the response ?
One way i can think of (and one that i don't like) is to overload the matadata onto the request in the filter backend and override finalize_response in the view - without a doubt the worst way to do it.
I'm not sure it's the best way, but I would probably override get to simply intercept the response object and modify response.data however you wish. Something as simple as
from rest_framework import generics
class SomeModelList(generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer
def get(self, request, *args, **kwargs):
response = super(SomeModelList, self).get(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
If you found yourself repeating this for multiple list views you could keep yourself DRY using a Mixin and include it in your list API classes:
from rest_framework import generics
from rest_framework.mixins import ListModelMixin
class IncludeQueryListMixin(ListModelMixin):
def list(self, request, *args, **kwargs):
response = super(IncludeQueryListMixin, self).list(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
class SomeModelList(IncludeQueryListMixin, generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer