Define & Use Custom renderer Django Rest Framework View - python

I'm trying to override a CSV renderer import for a django rest framework view. Here's how so:
class CustomCSVRenderer(BaseCSVRenderer):
def render():
do something
def tablize():
do something
I've defined the CustomCSVRenderer in the same python class views.py as the view in question:
class MyView(ListAPIView, CustomMixinSet):
renderer_classes = (CustomRenderer, JSONRenderer)
When I try to debug this implementation, my pdb debugger never hits the CustomCSVRenderer and instead I get a response based on some underlying renderer used by django restframework.
What could be the issue? How do I know what renderer django rest framework is using?

As #Daniel Roseman stated in the comment section, you'll need to do a little bit more in order to make this custom renderer work.
From the docs:
To implement a custom renderer, you should override BaseRenderer, set the .media_type and .format properties, and implement the .render(self, data, media_type=None, renderer_context=None) method.
Thus, your CustomCSVRenderer should look like this:
class CustomCSVRenderer(BaseCSVRenderer):
media_type = 'text/csv'
format = 'csv'
def render(self, data, media_type=None, renderer_context=None):
...

Related

How to re-use GET call of another class-based api view

I have a hard time trying to re-use a get call from an existing APIView in another APIVIew.
I have a class-based DRF view:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SomethingList.as_view()), #legacy url
# in views.py
class SomethingList(generics.ListCreateAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
# override get, because of some required custom action
def get(self, request, *args, **kwargs):
# do some custom actions (scan folder on filesystem)
...
return super().get(request, *args, **kwargs)
The above view both provides a get (list) and post (create) API interface. As intended. I've augmented it with DRF-spectacular information (not shown here) to generate my swagger docs.
Now, I have another (legacy) URL defined that should do exactly the same as the get (list) call above. Currently, this legacy url also points to the SomethingList.
But ... the legacy URL should NOT provide the post (create) interface, and I want to mark it as 'deprecated' in swagger using drf-spectacular. So I figured I need a separate class to restrict to get() and add the #extend_schema decorator
So I though of re-using the existing SomethingList.get functionality as follows:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SimilarSomethingList.as_view()), # ! points to new class
# in views.py
class SomethingList(generics.ListCreateAPIView):
...
class SimilarSomethingList(generics.ListAPIView): #ListAPIView only!
#extend_schema(summary="Deprecated and other info..")
def get(self, request, *args, **kwargs):
view = SomethingList.as_view()
return view.get(request, *args, **kwargs)
However, this doesn't work. I get AttributeError: 'function' object has no attribute 'get'
I tried a couple of variations, but couldn't get that working either.
Question:
How can I reuse the get() call from another APIView? Should be simple, so I'm likely overlooking something obvious.
Set http_method_names to the class view.
class SomethingList(generics.ListCreateAPIView):
http_method_names = ['get', 'head']
reference: https://stackoverflow.com/a/31451101/13022138

Permission class on #detail_route not working, Django Rest Framework

I'm new to DRF, but I'm trying to use a permission class on a #detail_route using the method in this stack thread: Using a permission class on a detail route
My code currently looks like this :
#detail_route(methods=['GET'], permission_classes=[IsStaffOrRestaurantUser])
def restaurant_dishes_ready_for_pickup(self, request, pk=None):
...stuff....
class IsStaffOrRestaurantUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(request)
print(view)
print(obj)
return False
The print statements never get executed... I'm probably missing something but I've looked through the documentation and can't really figure it out, is my approach right at all? Thanks!
EDIT:
I realize in our code already that we have this snippet in our Viewset, is it possible to override this in the decorator?
def get_permissions(self):
# Limit to listing and getting for non Admin user
if self.request.method in permissions.SAFE_METHODS:
return (permissions.AllowAny(),)
return (IsAdminUser(),)
Not sure if it's the most elegant solution, but you might be able to upgrade get_permissions() like so:
def get_permissions(self):
# check additional route specifics
path = self.request.path
if ("restaurant_dishes_ready_for_pickup" in path):
return (IsStaffOrRestaurantUser,)
# Limit to listing and getting for non Admin user
if (self.request.method in permissions.SAFE_METHODS):
return (permissions.AllowAny,)
return (IsAdminUser,)
PS: Also maybe return permission class objects instead of instances in get_permissions().
Quote from the documentation:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
So you'll need to call explicitly the permission check.
Note that you could have that for free if you were using a RetrieveAPIView instead of a function based view for example.

What is the minimum amount of boilerplate for negotiating content in Django?

In a Django application, I have more than a handful of views which return JSON, with an invocation similar to:
return HttpResponse(json.dumps(content), mimetype="application/json")
I want to start creating views that return either HTML or JSON depending on the Accept headers from the request. Possibly other types, too, but those are the main ones. I also want to get multiple URLs routed to this view; the file extensions ".html" and ".json" help tell clients which types they should Accept when making their request, and I want to avoid the "?format=json" antipattern.
What's the correct, blessed way to do this in Django with a minimum of boilerplate or repeated code?
(Edit: Rephrase in order to better follow SO's community guidelines.)
I think a class-based view mixin (django 1.3+) is the easiest way to do this. All your views would inherit from a base class that contains logic to respond with the appropriate content.
I think I may not be seeing your big picture here but this is what I would do:
Have a html template that you render when html is requested and keep your json.dumps(content) for when json is requested. Seems to be obvious but I thought i should mention it anyway.
Set your URLs to send you "json" or 'html'. :
(r'^some/path/(?P<url_path>.*)\.(?P<extension>html|json)$', 'some.redirect.view'),
(r'^/(?P<extension>html|json)/AppName', include(MyApp)),
# etc etc
and your view:
def myRedirectView(request, url_path, extension):
view, args, kwargs = resolve("/" + extension + "/" + urlPath)
kwargs['request'] = request
return view(*args, **kwargs)
I know this is a bit vague because I haven't fully thought it through but its where I would start.
I have addressed this by creating a generic view class, based on Django's own generic.View class, that defines a decorator 'accept_types'. This modifies the view to which it is applied so that it returns None if the indicated content-type is not in the Accept header. Then, the get() method (which is called by the generic.View dispatcher) looks like this:
def get(self, request):
self.request = request # For clarity: generic.View does this anyway
resultdata = { 'result': data, etc. }
return (
self.render_uri_list(resultdata) or
self.render_html(resultdata) or
self.error(self.error406values())
)
The actual view renderers are decorated thus:
#ContentNegotiationView.accept_types(["text/uri-list"])
def render_uri_list(self, resultdata):
resp = HttpResponse(status=200, content_type="text/uri-list")
# use resp.write(...) to assemble rendered response body
return resp
#ContentNegotiationView.accept_types(["text/html", "application/html", "default_type"])
def render_html(self, resultdata):
template = loader.get_template('rovserver_home.html')
context = RequestContext(self.request, resultdata)
return HttpResponse(template.render(context))
The (one-off) generic view class that declares the decorator looks like this:
class ContentNegotiationView(generic.View):
"""
Generic view class with content negotiation decorators and generic error value methods
Note: generic.View dispatcher assigns HTTPRequest object to self.request.
"""
#staticmethod
def accept_types(types):
"""
Decorator to use associated function to render the indicated content types
"""
def decorator(func):
def guard(self, values):
accept_header = self.request.META.get('HTTP_ACCEPT',"default_type")
accept_types = [ a.split(';')[0].strip().lower()
for a in accept_header.split(',') ]
for t in types:
if t in accept_types:
return func(self, values)
return None
return guard
return decorator
(The parameter handling in the decorator should be generalized - this code works, but is still in development as I write this. The actual code is in GitHub at https://github.com/wf4ever/ro-manager/tree/develop/src/roverlay/rovweb/rovserver, but in due course should be separated to a separate package. HTH.)

Including repeated view function into multiple views?

I have the following function in many views of many of my apps. Like the following:
def json_response(data):
return HttpResponse(
simplejson.dumps(data),
content_type = 'application/json; charset=utf8'
)
How would I include this on all of my apps' views.py? Define it in a single app and just import from it?
from main.global import simplejson
Also, is there a github page of a well organized django project that I can look at?
If you are using django 1.3, a class based view can be used to abstract this function. You would simply extend your view from a base view that would return json of whatever is passed in. You would save a file with this class at some common location (as described in the answer linked in the comment by Ignacio).
In fact, this is one of the example types in the documentation for class based views:
from django import http
from django.utils import simplejson as json
class JSONResponseMixin(object):
def render_to_response(self, context):
"Returns a JSON response containing 'context' as payload"
return self.get_json_response(self.convert_context_to_json(context))
def get_json_response(self, content, **httpresponse_kwargs):
"Construct an `HttpResponse` object."
return http.HttpResponse(content,
content_type='application/json',
**httpresponse_kwargs)
def convert_context_to_json(self, context):
"Convert the context dictionary into a JSON object"
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return json.dumps(context)
This is how you would use it (also from the documentation):
class HybridDetailView(JSONResponseMixin,
SingleObjectTemplateResponseMixin, BaseDetailView):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
if self.request.GET.get('format','html') == 'json':
return JSONResponseMixin.render_to_response(self, context)
else:
return SingleObjectTemplateResponseMixin.render_to_response(self, context)
Yes, you can just define it in a single view, or a utils file, or whatever you want, and just import it in all of your views. I frequently do this with ubiquitous functions.

Python+Pyramid+Mako: What is the difference between the context in event, context in view and context in template?

I've been trying hard to understand this, but can't quite put a finger on a precise documentation about it. I am quite confused about the different meaning of context in this Python Pyramid+Mako setup.
Here is some code snippets (tell me if you need more context):
class Root(object):
request = None
def __init__(self, request):
self.request = request
#events.subscriber(events.BeforeRender)
def add_renderer_globals(event):
event[u'c'] = event[u'request'].tmpl_context
print u"add_renderer_globals(): request.tmpl_context={0}".format(event[u'request'].tmpl_context)
print u"add_renderer_globals(): context={0}".format(event[u'context'])
#view.view_config(route_name='login', request_method='GET', renderer='login.mako')
def login_get(context, request):
print u"login_get(): context={0}".format(context)
return {}
[...]
cfg = config.Configurator(root_factory=Root,
package=MyPKG,
settings=settings,
session_factory=pyramid_beaker.session_factory_from_settings(settings),
)
cfg.add_route(name='login', pattern='/login')
cfg.scan()
and in my mako template, just to have an example, I only have:
Mako template context=${context}
So I would make a request and I get the following outputs from console or browser:
login_get(): context=<MyPKG.Root object at 0x1523c90>
add_renderer_globals(): request.tmpl_context=<pyramid.request.TemplateContext object at 0x12fbc50>
add_renderer_globals(): context=<MyPKG.Root object at 0x1523c90>
Mako template context=<mako.runtime.Context object at 0x15a4950>
My question is: What are the differences, and what do you use them for? I'm also confused why semantically, I declared root_factory=MyPKG.Root and it becomes context=MyPKG.Root in my view and my subscriber.
Thanks for any hint to help me understand.
First, ignore request.tmpl_context. This is just a dictionary on the request object that you can add stuff to and is not normally used in Pyramid applications at all. It's a step-child from the Pylons merge.
There are two context objects when using Mako. The first (mako.runtime.Context) is supplied by Mako: http://docs.makotemplates.org/en/latest/runtime.html#context
Pyramid typically exposes the traversal context (MyPKG.Root) as context in your templates. However, Mako already has a variable using that name. :-( Thus, Pyramid's context is actually named _context.

Categories