I'm structuring a Django API with rest framework, I read the docs and DRF only makes a crud (get, post, patch, delete) from a model. Now the deal is how I can make custom actions with DRF.
Example:
api/v1/model/custom_action
Code:
class DistrictViewSet(viewsets.ModelViewSet):
queryset = District.objects.all()
serializer_class = DistrictSerializer
def custom_action(request, param):
# do many actions and return as Json Object
urls.py
url(r'api/v1/', include(router.urls))
Where router
router.register(r'model',api.ModelViewSet)
I'm correct with this or I need to create another modelview, customize the code and add it to router list?
You can add custom actions as you have done but you may need the #action decorator to configure the url to apply to a single object, or many.
#action(detail=True) adds pk to the url, as it applies to one object.
The url is generated from the action name, so for example
#action(detail=True)
def custom_action(self):
pass
Would yield the url ^<app_name>/{pk}/custom_action/$
You may find this useful:
https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
Related
What is the best way to get DRF API view data into another Django view?
Currently I am just calling the view through requests module:
response = requests.get('my_api_view')
but is there a cleaner way?
I can also just instantiate the API view class in another view, but this will give me unserialized objects.
Not sure what you mean by getting unserialized objects. You can do the following if you're using function-based views:
def view(request):
# some stuff done
return Response(<result>)
def another_view(request)
return view(request)
If you're views are class based then you can do the following:
class AClassBasedView(SomeMixin, SomeOtherMixin):
def get(self, request):
# do something with the request
return Response(<some result>)
class AnotherClassBasedView(SomeMixin, SomeOtherMixin):
def compute_context(self, request, username):
#some stuff here here
return AnotherClassBasedView.as_view()(request)
Both of these will return a <class 'rest_framework.response.Response'> object which can be passed further.
I'm not sure what exactly you want to achieve but wanting to call a view from another view maybe a sign of bad architecture. It could mean that you have a business logic implemented in the second view which you want to access in the first. Usually the rule of thumb is to move such a common logic somewhere else so it could be used by different views.
Again I don't know what you want to achieve but this is a possibilty.
Prologue:
I have seen this question arising in more than one posts:
Django Rest Framework - APIView Pagination
Pagination not working in DRF APIView
Django rest framework global pagination parameters not working for ModelViewSet
and can also be applied here:
Combine ListModelMixin with APIView to show pagination
I have composed an example on SO Documentation to unify my answers in the above questions but since the Documentation will get shutdown on August 8 2017, I will follow the suggestion of this widely upvoted and discussed meta answer and transform my example to a self-answered post.
Of course I would be more than happy to see any different approach as well!!
Question:
I want to use a Non Generic View/Viewset (eg: APIView) on a Django Rest Framework project.
As I read on the pagination documentation:
Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the mixins.ListModelMixin and generics.GenericAPIView classes for an example.
Can I still continue using a non generic view/viewset?
How can I implement pagination on it?
We can find a solution without the need to reinvent the wheel:
Let's have a look on how the generics pagination is implemented:
django-rest-framework/rest_framework/generics.py.
That is exactly what we are going to use to our view as well!
Let's assume that we have a global pagination setup like the following in:
settings.py:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.DESIRED_PAGINATION_STYLE',
'PAGE_SIZE': 100
}
In order not to bloat our view/viewset's code, we can create a custom mixin to store our pagination code:
class MyPaginationMixin(object):
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination
is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(
queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given
output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
Then on views.py:
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from my_app.mixins import MyPaginationMixin
class MyView(APIView, MyPaginationMixin):
queryset = OurModel.objects.all()
serializer_class = OurModelSerializer
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
# We need to override the get method to insert pagination
def get(self, request):
...
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
And now we have an APIView with pagination.
Im kind of new to Django Rest Framework. I know it is possible to post data using the Browsable API, I just don't know how. I have this simple view:
class ProcessBill(APIView):
def post(self, request):
bill_data = request.data
print(bill_data)
return Response("just a test", status=status.HTTP_200_OK)
When I go to the url that points to this view, I get the rest_framework browsable api view with the response from the server method not allowed which is understandable cause I am not setting a def get() method. But ... how can I POST the data? I was expecting a form of some kind somewhere.
EDIT
This is a screenshot of how the browsable API looks for me, it is in spanish. The view is the same I wrote above but in spanish. As you can see ... no form for POST data :/ .
Since you are new I will recommend you to use Generic views, it will save you lot of time and make your life easier:
class ProcessBillListCreateApiView(generics.ListCreateAPIView):
model = ProcessBill
queryset = ProcessBill.objects.all()
serializer_class = ProcessBillSerializer
def create(self, request, *args, **kwargs):
bill_data = request.data
print(bill_data)
return bill_data
I will recommend to go also through DRF Tutorial to the different way to implement your endpoint and some advanced feature like Generic views, Permessions, etc.
Most likely the user has read-only permission. If this is the case make sure the user is properly authenticated or remove the configuration from the projects settings.py if it is not necessary as shown below.
```#'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
#],```
Read more on permissions here.
I've created some views that subclass directly from viewsets.ViewSet rather than from ModelViewSet, many (but not all) of which don't have serializers declared on the class explicitly because I don't need them that way. An example of one such viewset is a "verify email" viewset, that simply takes in a verification code, and then the .list() method on the ViewSet looks up the verification code and verifies the user. No serializer or model necessary, and I use ViewSet only so that I can properly register the view in the router.
However, these views are all lacking the form automatically generated at the bottom of the Browseable API pages that the ModelViewSet pages usually have for POST and PUT requests.
What do I need to do or specify to get these forms to appear with my preferred fields for these ViewSet subclasses?
you need to have serializer for the viewset.
BrowsableAPIRenderer returns in it's get_context method:
'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
'post_form': self.get_rendered_html_form(data, view, 'POST', request),
and get_rendered_html_form is looking for serializer defined for view.
it make sense, because you can't process data sent by POST or PUT without serializer.
We use class based views for most of our project. We have run into an issue when we try and create a CSV Mixin that will allow the user to export the information from pretty much any page as a CSV file. Our particular problem deals with CSV files, but I believe my question is generic enough to relate to any file type.
The problem we are having is that the response from the view is trying to go to the template (say like from django.views.generic import TemplateView). We specify the template in the urls.py file.
url(r'^$', MyClassBasedView.as_view(template_name='my_template.html'))
How can you force the response to bypass the template and just return a standard HttpResponse? I'm guessing you'll need to override a method but I'm not sure which one.
Any suggestions?
EDIT1: It appears that I was unclear as to what we are trying to do. I have rendered a page (via a class based view) and the user will see reports of information. I need to put in a button "Export to CSV" for the user to press and it will export the information on their page and download a CSV on to their machine.
It is not an option to rewrite our views as method based views. We deal with almost all class based view types (DetailView, ListView, TemplateView, View, RedirectView, etc.)
This is a generic problem when you need to provide different responses for the same data. The point at which you would want to interject is when the context data has already been resolved but the response hasn't been constructed yet.
Class based views that resolve to the TemplateResponseMixin have several attributes and class methods that control how the response object is constructed. Do not be boxed into thinking that the name implies that only HTML responses or those that need template processing can only be facilitated by this design. Solutions can range from creating custom, reusable response classes which are based on the behavior of the TemplateResponse class or creating a reusable mixin that provides custom behavior for the render_to_response method.
In lieu of writing a custom response class, developers more often provide a custom render_to_response method on the view class or separately in a mixin, as it's pretty simple and straightforward to figure out what's going on. You'll sniff the request data to see if some different kind of response has to be constructed and, if not, you'll simply delegate to the default implementation to render a template response.
Here's how one such implementation might look like:
import csv
from django.http import HttpResponse
from django.utils.text import slugify
from django.views.generic import TemplateView
class CSVResponseMixin(object):
"""
A generic mixin that constructs a CSV response from the context data if
the CSV export option was provided in the request.
"""
def render_to_response(self, context, **response_kwargs):
"""
Creates a CSV response if requested, otherwise returns the default
template response.
"""
# Sniff if we need to return a CSV export
if 'csv' in self.request.GET.get('export', ''):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % slugify(context['title'])
writer = csv.writer(response)
# Write the data from the context somehow
for item in context['items']:
writer.writerow(item)
return response
# Business as usual otherwise
else:
return super(CSVResponseMixin, self).render_to_response(context, **response_kwargs):
Here's where you can also see when a more elaborate design with custom response classes might be needed. While this works perfectly for adding ad-hoc support for a custom response type, it doesn't scale well if you wanted to support, say, five different response types.
In that case, you'd create and test separate response classes and write a single CustomResponsesMixin class which would know about all the response classes and provide a custom render_to_response method that only configures self.response_class and delegates everything else to the response classes.
How can you force the response to bypass the template and just return
a standard HttpResponse?
This kinda defeats the point of using a TemplateView. If the thing you're trying to return isn't a templated response, then it should be a different view.
However...
I'm guessing you'll need to override a method but I'm not sure which one.
...if you prefer to hack it into an existing TemplateView, note from the source code...
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
A view that renders a template. This view will also pass into the context
any keyword arguments passed by the url conf.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
...so you'd have to override the get() method so it doesn't call render_to_response() when returning your CSV. For example...
class MyClassBasedView(TemplateView):
def get(self, request, *args, **kwargs):
if request.GET['csv'].lower() == 'true':
# Build custom HTTP response
return my_custom_response
else:
return TemplateView.get(request, *args, **kwargs)
If you need a generic mixin for all subclasses of View, I guess you could do something like...
class MyMixin(object):
def dispatch(self, request, *args, **kwargs):
if request.GET['csv'].lower() == 'true':
# Build custom HTTP response
return my_custom_response
else:
return super(MyMixin, self).dispatch(request, *args, **kwargs)
class MyClassBasedView(MyMixin, TemplateView):
pass