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
Related
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
I have a model which relates to many models, like this:
class Father:
son = # Foreign key to Son model
class Son:
#property
def son_daughters:
if ... :
obj = TypeA.objects.get(...)
elif ... :
obj = TypeB.objects.get(...)
else:
obj = TypeC.objects.get(...)
return obj
I would like to get Father data from daughter name or type. I have this filter class where I need to send two query set parameters related to daughter in order to get daughter ids and apply it as a filter to Father. This is my filter class:
class FatherFilter(django_filters.rest_framework.FilterSet):
def daughter(self, method_name, args, **kwargs):
print(method_name, args, kwargs)
...
daughter_id = django_filters.NumberFilter(method=daughter)
But when I call this endpoint I just get one query parameter and not all.
Is there a way to get the query parameters inside this method instead of just one?
Thanks in advance.
In order to achieve this, I found that Django Rest Framework has a class that extends from django_filters. This class is called BaseFilterBackend and can be used to extend the default backend for filtering any request. So what I did was adding a class extended from BaseFilterBackend like this:
from rest_framework import filters
class FatherFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
daughter_id = request.query_params.get("daughter_id", None)
daughter_name = request.query_params.get("daughter_name", None)
if daughter_id and daughter_name:
kwargs = {
daughter_name: daughter_id
}
queryset = queryset.filter(**kwargs)
return queryset
This filter will apply before others, so even if you are using a FilterSet class, you will not lose the filters from your BaseFilterBackend. The problem with this solution is that relies in rest_framework.filters package, a filter that its not related to django_filters.
This might not be the best way to achieve this, so if you have better ideas please add them to help others with a similar problem.
In rest api view I need to have 2 objects. For example:
class Foo(models.Model):
....
class Bar(models.Model):
....
What is correct way to get them? I mean how should I configure urls? I think this is not really good practice: url(r'^foo/(?P<pk>\d+)/bar/(?P<pk>\d+)/$', FooBarView.as_view())
Or: url(r'^foobar/$', FooBarView.as_view()) and then pass parameters: ?foo=1&bar=2.
I think it is more like a design problem.
Which one is better? I will say it depends on what is the relationship between model foo and model bar.
If you get model Class and model student, and you want to get student info and base on which class and relative student_number, like each class will have a student no.1.
you can go:
url(r'^class/(?P<class_pk>\d+)/student/(?P<student_pk>\d+)/$')
If you want to get both model information, but you want put them into a statistic table,
you can go:
url(r'^statistic/$')
then pass parameters like:
?class=1&student=2
These are just simple examples, there should be other cases, and you should use other way to design your URL API.
For django >= 1.5 You could pass parameters to class view doing somethin like this:
class Foo(TemplateView):
template_name = "yourtemplatesdir/template.html"
# Set any other attributes here
# dispatch is called when the class instance loads
def dispatch(self, request, *args, **kwargs):
self.id = kwargs.get('foo_id', "other_def_id")
self.barid = kwargs.get('bar_id', "other_def_id")
# depending on your definition, this class might be different
class Bar(TemplateView):
template_name = "yourtemplatesdir/template.html"
# Set any other attributes here
# this method might not be necessary
# dispatch is called when the class instance loads
def dispatch(self, request, *args, **kwargs):
self.id = kwargs.get('bar_id', "other_def_id")
In your url conf something like:
from .views import FooBar
urlpatterns = patterns('',
url(
regex=r'^(?P<foo_id>\d+)/(?P<bar_id>\d+)/$'
view=FooBar.as_view(),
name='viewname'
),
)
Then, in your template view for FooBar, you could do:
{{ view.id }}
{{ view.barid }}
I hope it helps, it could be more detailed if you provide further information about the object information needed in your 'action'.
I have a filter where I need to access the request.user. However, django-filter does not pass it. Without using the messy inspect.stack() is there a way to get the current user in the method member_filter below?
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
def member_filter(self, queryset, value):
# get current user here so I can filter on it.
return queryset.filter(user=???)
For example this works but feels wrong...
def member_filter(self, queryset, value):
import inspect
request_user = None
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request_user = frame_record[0].f_locals['request'].user
print(request_user)
is there maybe a way to add this to some middleware that injects user into all methods? Or is there a better way?
Yes, you can do it, and it's very easy.
First, define __init__ method in your ClubFilter class that will take one extra argument:
class ClubFilter(django_filters.FilterSet):
# ...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(ClubFilter, self).__init__(*args, **kwargs)
With having your user saved into attribute inside ClubFilter, you can use it in your filter. Just remember to pass current user from your view inside FilterSet.
Try self.request.user.
Why it must work.
you can access the request instance in FilterSet.qs property, and then filter the primary queryset there.
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
#property
def qs(self):
primary_queryset=super(ClubFilter, self).qs
return primary_queryset.filter(user=request.user)
I would like to use an existing models.py filtering classmethod in my API.
The thing is I want to avoid to write the same logic twice, and want to keep logic in models (not in API). Here is what I did for now:
In models.py:
class Deal(models.Model):
# some attributes
#classmethod
def get_filtered_deals(cls, client, owner):
return cls.objects.filter(... # complex filtering rules here, I want to keep this logic here, not duplicate it in api.py!!!
But I'm stuck because I don't know how to call the get_filtered_deals() classmethod in my Deal linked resource in Tastypie. I tried something like this:
class Deals(ModelResource):
class Meta(CommonResourceMeta):
queryset = Deal.objects.all()
resource_name = "deals"
list_allowed_methods = ['get']
authorization = DealAuthorization()
class DealAuthorization(Authorization):
def read_list(self, object_list, bundle):
return object_list.get_filtered_deals(
owner=bundle.request.user,
client=int(request.GET['arg2']))
This obviously does not work since object_list has no method named get_filtered_deals
Thanks for your help!
That was pretty simple...
class DealAuthorization(Authorization):
def read_list(self, object_list, bundle):
object_list = Deal.get_filtered_deals(
owner=bundle.request.user,
client=int(request.GET['arg2']))
return object_list