Django Rest Framework add class level method to api - python

I am using Django Rest Framework to create my API. I am using #link to return information about a particular object.
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": object.info()})
GET: /someclass/1/info
What I would like to do is extend the method so I can access it at the "class level" so that my api could accept a list of objects
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
if isinstance(s, str):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": obj.info()})
else:
objs = models.SomeClass.objects.filter(pk__in=pk).all()
return Response({"infos": [obj.info() for obj in objs]})
GET: /someclass/1/info
GET: /someclass/info?pk=1&pk=2&pk=3
Is there a way I can add a class level method to my api? Or will I need to create a new class to handle the one api call?
PS: I don't mind if I need to have a separate method to make this work

The dynamically generated routes using the #link or #action decorators are hard-coded to look like /someclass/<pk>/<methodname>/. You can expose a /someclass/info endpoint by adding a custom route, e.g.:
class MyRouter(DefaultRouter):
routes = [
Route(
url=r'^{prefix}/((?P<pk>\d+)/)?info$',
mapping={'get': 'info'},
name='{basename}-info',
initkwargs={}
)
] + DefaultRouter.routes
Then your info method might look like this:
def info(self, request, pk=None):
if pk:
obj = SomeClass.objects.get(pk=pk)
return Response({'info': obj.info()})
else:
objs = SomeClass.objects.filter(pk__in=request.GET.getlist('pk'))
return Response({'infos': [obj.info() for obj in objs]})
(Note the absence of the #link decorator.)

Related

Django use custom method name in Django class view

I have Url like /foo/bar and Class based view has been defined as below.
class FooBar(View):
def handle_post_bar(self, request):
pass
def handle_get_bar(self, request):
pass
def handle_put_bar(self, request):
pass
In url i define as path('foo/bar/', FooBar.as_view())
Based on the http method and path i would like build view method names ex: handle_{0}_{1}'.format(method, path)
Please suggest me how to achive this, this should be common to all the urls and views.
i tried exploring the possibility of django middleware, but endedup in no luck.
Okey, it's certainly possible, you should write your logic like this:
class FooBar(View):
func_expr = 'handle_{0}_bar'
#csrf_exempt
def dispatch(self, request, *args, **kwargs):
method = request.method.lower()
func = self.func_expr.format(method)
if hasattr(self, func):
return getattr(self, func)(request)
raise Http404
def handle_post_bar(self, request):
print('POST')
return JsonResponse({'result': 'POST'})
def handle_get_bar(self, request):
print('GET')
return JsonResponse({'result': 'GET'})
def handle_put_bar(self, request):
print('PUT')
return JsonResponse({'result': 'PUT'})
It works for me:
Generally things like this you code on method called dispatch.
If you want to achieve it on more views (not only one) without repeating code, you should write own mixin that uses this logic.

django variable available to all the views

With context_processors it' s easy to define a callable which results variables available to all the templates. Is there any similar technique which makes a variable available to all the views? Is that somehow possible? Maybe with some workaround?
Django: 2.2
Python: 3.5.3
.
You may want to implement a custom Middleware.
https://docs.djangoproject.com/en/dev/topics/http/middleware/
This lets you execute custom code for every request and attach the results to the request object, which is then accessible in your view.
You can try sending your variable to the context of each class based view by having a parent class and inheriting all the views from that.
class MyMixin(object):
def get_context_data(self, **kwargs):
context = super(MyMixin, self).get_context_data(**kwargs)
myvariable = "myvariable"
context['variable'] = myvariable
return context
# then you can inherit any kind of view from this class.
class MyListView(MyMixin, ListView):
def get_context_data(self, **kwargs):
context = super(MyListView, self).get_context_data(**kwargs)
... #additions to context(if any)
return context
Or if you are using function based views, you can use a separate function which can update your context dict.
def update_context(context): #you can pass the request object here if you need
myvariable = "myvariable"
context.update({"myvariable": myvariable})
return context
def myrequest(request):
...
context = {
'blah': blah
}
new_context = update_context(context)
return render(request, "app/index.html", new_context)

Flask-Admin different forms and column_list for different roles

Following up on this question Flask-Admin Role Based Access - Modify access based on role I don't understand how to implement role-based views, especially regarding the form and column_lists.
Say I want MyModelView to show different columns if the user is a regular user or a superuser.
Overriding is_accessible in MyModelView has no effect at all
from flask_security import Security, SQLAlchemyUserDatastore, current_user
class MyModelView(SafeModelView):
# ...
def is_accessible(self):
if current_user.has_role('superuser'):
self.column_list = superuser_colum_list
self.form_columns = superuser_form_columns
else:
self.column_list = user_colum_list
self.form_columns = user_form_columns
return super(MyModelView, self).is_accessible()
# Has same effect as
def is_accessible(self):
return super(MyModelView, self).is_accessible()
and defining conditional class attributes does not work either as current_user is not defined (NoneType error as per AttributeError on current_user.is_authenticated()). Doing the same in the ModelView's __init__ being equivalent, current_user is still not defined
class MyModelView(SafeModelView):
#[stuff]
if current_user.has_role('superuser'):
column_list = superuser_colum_list
form_columns = superuser_form_columns
else:
column_list = user_colum_list
form_columns = user_form_columns
#[other stuff]
FYI, SafeModelView can be any class inheriting from dgBaseView in the previously mentioned question.
I usually define view class attributes such as column_list as properties. It allows you to add some dynamic logic to them:
from flask import has_app_context
from flask_security import current_user
class MyModelView(SafeModelView):
#property
def column_list(self):
if has_app_context() and current_user.has_role('superuser'):
return superuser_column_list
return user_column_list
#property
def _list_columns(self):
return self.get_list_columns()
#_list_columns.setter
def _list_columns(self, value):
pass
The problem with using this approach (and why your reassigning of column_list values in is_accessible function took no effect) is that many view attributes are cached on application launch and stored in private attributes. column_list for example is cached in _list_columns attribute so you need to redefine it as well. You can look how this caching works in flask_admin.model.base.BaseModelView._refresh_cache method.
Flask has_app_context method is needed here because first column_list read is happened on application launch when your current_user variable has no meaningful value yet.
The same can be done with form_columns attribute. The properties will look like this:
#property
def form_columns(self):
if has_app_context() and current_user.has_role('superuser'):
return superuser_form_columns
return user_form_columns
#property
def _create_form_class(self):
return self.get_create_form()
#_create_form_class.setter
def _create_form_class(self, value)
pass
#property
def _edit_form_class(self):
return self.get_edit_form()
#_edit_form_class.setter
def _edit_form_class(self, value):
pass

Can I use class method in views.py?

Now my code is like:
def use_item(request):
itemname = request.get('value')
if itemname == 'item1':
#do something
if itemname == 'item2':
#do something else
Can I do it in the following way?
views.py
class use_item():
def use_item(self,request):
itemname = request.get('value')
use = getattr(self,itemname) # say itemname is 'item1'
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
I've tried the second method but it seems that I was not doing it right.
And the reason I want to do it in this way is that I hope to group the methods that they'd be more organized.
the actual code
#views.py
class use_item():
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def jelly(self,request,topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
#urls.py
url(r'^use/item/(?P<topic_id>\d+)/$', 'use_item.use_item', name='use_item'),
If you want to have a better organization of your code, and reuse some code accross different views, instead of pasting it where you need, you may use the Django class based views:
# views.py
from django.views import View
class use_item(View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
# urls.py
from package.views import use_item
urlpatterns = [
# [...]
url(r'^use/item/(?P<topic_id>\d+)/$', use_item.as_view(), name='use_item'),
# [...]
]
But, if at some point you need to call item1() or item2() from another view (is it the reason you mentioned the other view jelly ?), you will see that it is not possible.
One solution could be moving the common methods in another class and make sure your views inherit it. This is often called mixins in django world.
# views.py
from django.views import View
class ItemsRelatedLMixin:
def item1(self, request):
#do something
def item2(self, request):
#do something else
class use_item(ItemsRelatedLMixin, View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
class jelly(ItemsRelatedLMixin, View):
def get(self, request, topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
Now, your views jelly and use_item call the common methods. Of course you can define new methods in a view to make them available only from that view. You could also create class members to store values you use often etc. Keep in mind that each request received by Django will trigger creation of a new instance of your view class (you can't keep data stored between 2 requests in class members).
In Django, view functions are usually organized in modules and not in classes.
To keep things organized, use more than one views module: views.py, foo_views.py, bar_views.py, or: views/__init__.py, views/foo.py, views/bar.py.
You need to provide the view in the signature of the class. i.e.:
from django.views import [your_View_name]
Then provide the same view in class definition;
class use_item(your_View_name):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
If you are defining your class for the same view,
class use_item(self):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
You may refer Django docs on Class-Based-View for more in-depth knowledge.
UPDATE:
When you are calling your function useitem you need to use the instance of your class as follows:
user_instance = views.use_item() //Create instance of your class
user_instance.useritem() //call your function using above instance

Howto create Python Pyramid view class without need to specify 'name' for each method

I have several view classes in my Python Pyramid project added via add_handler:
config.add_handler('export_index', '/export', handler=ExportViews, action='index')
class ExportViews(ConfigViewBase):
#action(request_method='POST', name='index',
request_param='ftp_export.form.submitted')
#action(request_method='POST', name='index', xhr=True, renderer='json',
request_param='ftp_export.form.submitted')
def ftp_export(self):
#process form
return {}
#action(request_method='GET')
def index(self):
return {}
Is it possible to do the same having:
config.add_handler('export_index', '/export', handler=ExportViews)
class ExportViews(ConfigViewBase):
#action(request_method='POST',
request_param='ftp_export.form.submitted')
#action(request_method='POST', xhr=True, renderer='json',
request_param='ftp_export.form.submitted')
def ftp_export(self):
#process form
return {}
#action(request_method='GET')
def __call__(self):
return {}
So the __call__ was called when browser gets page, and ftp_export should be called when I post form on the same page. Now I get page not found error
Thank You.
You can do this with traversal. Traversal rocks :)
class Root(object):
def __getitem__(self, name):
if name == "export":
return ExportSomething(self)
if name == "export_something_else":
return ExportSomethingElse(self)
class ExportSomething(object):
implements(IViewable, IExportable)
def view(self, request):
return "Hi"
def export(self, request):
return "something else"
#view_config(context=IViewable, request_method="GET")
def view_viewable(conext, request):
return context.view(request)
#view_config(context=IExportable, request_method="POST")
def export_exportable(conext, request):
return context.export(request)
then you can implement a bunch of ExportThis and ExportThat classes, make them implement IViewable and IExportable interfaces, make them returned from Root.__getitem__ and everything magically works. Or, if you don't need multiple exporters you can omit interfaces and bind views directly to ExportSomething class. Or you can instantiate different instances of ExportSomething in getitem and make it to... I don't know, view/export different files/reports.

Categories

Resources