I am beginner in Django REST framework and need your advice. I am developing a web service. The service has to provide REST interface to other services. The REST interface, which I need to implement, is not working with my models directly (I mean the get, put, post, delete operations). Instead, it provides other services with some calculation results. On a request my service makes some calculations and just returns the results back (doesn't store the results in its own database).
Below is my understanding of how that REST interface could be implemented. Correct me, if I am wrong.
Create class which makes the calculations. Name it 'CalcClass'. CalcClass uses the models in its work.
Params necessary for the calculations are passed to the constructor.
Implement the calc operation. It returns results as 'ResultClass'.
Create ResultClass.
Derived from object.
It just only has attributes containing the calc results.
One part of the calc results is represented as tuple of tuples. As I understand, it would be better for further serialization to implement a separate class for those results and add list of such objects to ResultClass.
Create Serializer for ResultClass.
Derive from serializers.Serializer.
The calc results are read-only, so use mostly Field class for fields, instead of specialized classes, such as IntegerField.
I should not impl save() method neither on ResultClass, nor on Serializer, because I am not going to store the results (I just want to return them on request).
Impl serializer for nested results (remember tuple of tuples mentioned above).
Create View to return calculation results.
Derive from APIView.
Need just get().
In get() create CalcClass with params retrieved from the request, call its calc(), get ResultClass, create Serializer and pass the ResultClass to it, return Response(serializer.data).
URLs
There is no api root in my case. I should just have URLs to get various calc results (calc with diff params).
Add calling format_suffix_patterns for api browsing.
Did I miss something? Is the approach is correct in general?
Django-rest-framework works well even without tying it to a model. Your approach sounds ok, but I believe you can trim some of the steps to get everything working.
For example, rest framework comes with a few built-in renderers. Out of the box it can return JSON and XML to the API consumer. You can also enable YAML by just installing the required python module. Django-rest-framework will output any basic object like dict, list and tuple without any extra work on your part.
So basically you only have to create the function or class that takes in arguments, does all of the required calculations and returns its results in a tuple to the REST api view. If JSON and/or XML fits your needs, django-rest-framework will take care of the serialization for you.
You can skip steps 2 and 3 in this case, and just use one class for calculations and one for presentation to the API consumer.
Here are a few snippets may help you out:
Please note that I have not tested this. It's only meant as an example, but it should work :)
The CalcClass:
class CalcClass(object):
def __init__(self, *args, **kw):
# Initialize any variables you need from the input you get
pass
def do_work(self):
# Do some calculations here
# returns a tuple ((1,2,3, ), (4,5,6,))
result = ((1,2,3, ), (4,5,6,)) # final result
return result
The REST view:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from MyProject.MyApp import CalcClass
class MyRESTView(APIView):
def get(self, request, *args, **kw):
# Process any get params that you may need
# If you don't need to process get params,
# you can skip this part
get_arg1 = request.GET.get('arg1', None)
get_arg2 = request.GET.get('arg2', None)
# Any URL parameters get passed in **kw
myClass = CalcClass(get_arg1, get_arg2, *args, **kw)
result = myClass.do_work()
response = Response(result, status=status.HTTP_200_OK)
return response
Your urls.py:
from MyProject.MyApp.views import MyRESTView
from django.conf.urls.defaults import *
urlpatterns = patterns('',
# this URL passes resource_id in **kw to MyRESTView
url(r'^api/v1.0/resource/(?P<resource_id>\d+)[/]?$', login_required(MyRESTView.as_view()), name='my_rest_view'),
url(r'^api/v1.0/resource[/]?$', login_required(MyRESTView.as_view()), name='my_rest_view'),
)
This code should output a list of lists when you access http://example.com/api/v1.0/resource/?format=json. If using a suffix, you can substitute ?format=json with .json. You may also specify the encoding you wish to get back by adding "Content-type" or "Accept" to the headers.
[
[
1,
2,
3
],
[
4,
5,
6
]
]
Related
I have a GET endpoint for a RESTful API I am creating using Django.
This endpoint reads three values from the URL's query string. A particular parameter in the query string has the potential to change the data (include extra fields and change the structure slightly) which is returned in the JSON response from this endpoint.
This endpoint is not tied directly to a single model. My view subclasses RetrieveAPIView, in the view, I override the get_object method and some logic is performed to query multiple models, make some decisions and return an ordered dictionary.
My problem is as follows:
I would like to document this endpoint using Swagger. I am using drf-yasg to generate my Swagger documentation. I have no serializers declared for this view; since I manually construct an ordered dictionary in get_object I don't see the purpose of declaring one. I'm not sure how to document my endpoint using drf-yasg.
I have discovered only one approach to document the endpoint, but it is quite ugly and bloats my code, and I'm wondering if there is a better approach.
By declaring an openapi.Response object, and supplying this object to the #swagger_auto_schema decorator Swagger displays the two possible responses, but describing the entire response really bloats my code. Here is an example if what currently works to document the endpoint:
class View(RetrieveAPIView):
http_method_names = ['get']
response_json = {
'Response if X query parameter is something': {
'key1': 'string',
'key3': {
'key4': 0,
'key5': 'string',
'key6': 'string'
}
},
'Response if X query parameter is something else': {
'key1': 'string',
'key2': 'string'
}
}
swagger_get_responses = openapi.Response(description='Description for the set of responses',
examples=response_json)
#swagger_auto_schema(responses={200: swagger_get_responses})
def get(self, request, *args, **kwargs):
return super().get(request, args, kwargs)
def get_object(self):
// Execute a method on a model that queries other models, performs some logic, and
// then returns an ordered dict which is then returned by this function.
Is there a better approach to this problem? Is there a better Django design pattern/feature of the Django framework or drf-yasg library that I can apply here that will help me handle multiple response bodies?
What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})
I have a complex DRF ViewSet that supports paging, filtering, sorting, etc. that backends a grid view. To build an "export" capability, I need to be able to take the same querystring that my endpoint uses, such as:
?obj_id=129&ordering=latitude&city__icontains=nap
And be able to, in Django, send that string into DRF somehow and get the fully-modified queryset after all the view's filters, sorts, etc are applied (the same way as the GET did). I could use the fully-rendered json response or some interim filter-applied queryset. Is it possible to use DRF in this way?
You should write a CSV renderer for your viewset and get that content-type to export the CSV.
There's even one already available.
Yes you could, if you already have a request object i.e. If you want to use this DRF viewset into another view which has the request object:
def another_view(request):
# make a copy of the `GET` attribute of request object
request.GET = request.GET.copy()
# now you can set the query params on this GET object
# ?obj_id=129&ordering=latitude&city__icontains=nap
request.GET['obj_id'] = 129
request.GET['ordering'] = 'latitude'
request.GET['city__icontains'] = 'nap'
# you can also set paging options in similar way
# now instantiate the viewset
vs = DRFViewset.as_view()
# call the view for response
# set kwargs as you need
response = vs(request, *args, **kwargs)
# response.data will have what you want here
Lets assume I've an API that expects a couple GET args: 'foo' and 'bar'. And I haven't defined serializer_class due to it isn't tied to any specific model:
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
class myAPI(GenericAPIView):
def get(self, request, format=None):
foo = request.GET.get('foo')
bar = request.GET.get('bar')
# do something with foo & bar
return Response({'fooplusbar': _something(foo,bar)})
Is there any way to tell to djangorestframework to build the form in this kind of situation?
The Django REST Framework only supports forms out of the box for POST, PUT and PATCH requests.
In those cases, the form is generated from the serializer. There is nothing wrong with defining a serializer which does not return Model objects, nor with using the serializer for form display only, and write e.g. a custom POST handler which does not make use of it. But for Django REST Framework to show a form, you must define a serializer.
It would be quite possible to add support for proper GET forms, but you'd need to extend rest_framework.renderers.BrowsableAPIRenderer.render to add the new form to the context, and write your own templates in rest_framework/api.html.
I'm adding a new type of user profile to site and this new type of user(say new_type) should not be able to reach the same views like the existings users.
My question is: how can i use different types of views according to user type using the same request paths without altering the existing view codes like adding
if user.profile_type == 'blah':
do_this
else:
do_that
to each view?
In detail:
i'd like to use "http://mysite.com/path/" for both types of users, running different logics and returning different displays without making differences in existing views (since there are lots of views to modify).
I'm thinking of adding different groups of views for new type, then override urls logic to resolve the request paths to relevant views, such as :
if user is of new_type
resolve path to related_view_for_new_type
else
resolve as usual
As a straight forward example: logging in admin and normal user from the same login url, and if user is admin, run the relevant views for admin and return django admin display to her, if normal user, then run the normal view and return normal website view to her, without rewriting or changing the url they are requesting. (/index/ for example)
Is it possible to extend urls in Django in such way and if so how, or should i give up overloading the same request paths and add '/new_type/' to urls (http://mysite.com/new_type/path/)for new_type users?
Thanks
To start with, what does it mean to have different types of users? A very simple way to do this would be to store an attribute on a user. That way, given a user object, you could look at this extra attribute to determine whether the user is of a special type. Django has a standard mechanism for storing additional attributes like this, which you can read about here.
Once you have a way of determining user types, you can create a single decorator and apply it to any view that needs to behave in the way you've described. Decorators are a great way of applying extra conditions or behaviour to existing functions. The logic in the decorator gets to work before and/or after the existing function, so it can very easily accomplish something like displaying a different template based on a the user's type.
Decorator functions look very odd when you first encounter them, but read it carefully and you'll soon get it. The decorator is a function itself, and you give it the function you want to decorate. It gives you back a new function, which is your old function wrapped with the extra logic.
I've written some untested example code below.
def template_based_on_user_type(special_template, ordinary_template):
def decorator(your_view_function):
def inner_decorator(request, *args, **kwargs):
# this is the logic that checks the user type before
# every invocation of this view:
if request.user.type == 'special_type':
template = special_template
else:
template = ordinary_template
# this is the invocation of the view function, with
# a custom template selected:
return your_view_function(request, template)
return inner_decorator
return decorator
#template_based_on_user_type('my-special-template.html', 'ordinary-template.html')
def my_view_function(request, template='default.html'):
# Do what you need to do here
render_to_response(template, data, RequestContext(request)
The syntax for applying a decorator is the "#" symbole, followed by the decorator function. The decorator is customized with the template names specified.
I solved this problem using decorator in urls.py:
def dispatch_by_user(staff_view, external_user_view):
def get_view(request, **kwargs):
if (is_staff_user(request.user)):
return staff_view(request, **kwargs)
else:
return external_user_view(request, **kwargs)
return login_required(get_view)
def is_staff_user(user):
return user.groups.filter(name="privileged-group").exists()
So patterns set as following:
urlpatterns = [
url(r'^$',
dispatch_by_user(
views.StaffHomeView.as_view(),
views.ExternalUserClientView.as_view()),
name='index'),
# ...
]
RTFM as usual :)
Here's the link to a possible solution :
method_splitter # http://www.djangobook.com/en/2.0/chapter08/
new_type related views' names will be derived from the originals by adding new_type_ to beginning of the name, such as index-> new_type_index
then i'll determine the view to return by simply checking the request.user.is_new_type attribute. ugly, but better than modifying gazillions of views.