Reuse test_func for UserPassesTestMixin in Django - django

Suppose I have 2 view classes
class View1(LoginRequiredMixin, UserPassesTestMixin, View):
def get(self, request, *args, **kwargs):
#Do something and render a page
def test_func(self):
#Validation logic
class View2(LoginRequiredMixin, UserPassesTestMixin, View):
def get(self, request, *args, **kwargs):
#Do something and response with JsonResponse
def test_func(self):
#The exact same validation logics as the test_func for View1
How can I in my Django code don't repeat the same test_func twice?

I found the answer
class MyPermissionRules(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
#my rules here
class View1(MyPermissionRules, View):
def get(self, request, *args, **kwargs):
#Do something and render a page
class View2(MyPermissionRules, View):
def get(self, request, *args, **kwargs):
#Do something and response with JsonResponse
In this way the same rule can be reused

Related

#permission_required decorator returns no user attribute in View error

I'm working with Django-admin panel. I have created a custom view file to add a file manager.
To make file uploading safe, I just added permission_required decorator. But it throws an error 'FileBrowser' object has no attribute 'user'.
Here is my code.
class FileBrowser(ListView):
model = File
paginate_by = 30
template_name = "file_manager/browser.html"
extra_context = {
'file_type': type,
'title': "Media Browser",
}
#permission_required('file_manager.view_file')
def dispatch(self, request, *args, **kwargs):
file_type = request.GET.get('type')
self.queryset = File.objects.filter(type=file_type)
self.extra_context['value_to'] = request.GET.get('value_to')
self.extra_context['image_to'] = request.GET.get('image_to')
self.extra_context['form'] = FileForm()
return super().dispatch(request, *args, **kwargs)
You can not decorate the method like that, since such decorator does not expect self as first parameter. It thus sees self as the request parameter.
What you can do is work with a #method_decorator decorator, like:
from django.utils.decorators import method_decorator
#method_decorator(permission_required('file_manager.view_file'), name='dispatch')
class FileBrowser(ListView):
# …
def dispatch(self, request, *args, **kwargs):
# …
For a class-based view however, you can work with the PermissionRequiredMixin [Django-doc]
from django.contrib.auth.mixins import PermissionRequiredMixin
class FileBrowser(PermissionRequiredMixin, ListView):
permission_required = 'file_manager.view_file'
# …
def dispatch(self, request, *args, **kwargs):
# …
There was a small mistake in my code. But I didn't noticed that. I simply forgot to use #method_decorator and directly wrote #permission_required decorator.
This was what I wrote.
#permission_required('file_manager.view_file')
def dispatch(self, request, *args, **kwargs):
.......
.......
return super().dispatch(request, *args, **kwargs)
This is what I changed to:
#method_decorator(permission_required('file_manager.view_file'))
def dispatch(self, request, *args, **kwargs):
.......
.......
return super().dispatch(request, *args, **kwargs)
Now it's working fine.

DRF, caching for as_view()?

In my view, I often use APIView's as_view() to generate json.
I'd like to cache the response and tried the following but it won't work
def some_complex_view(self, request, *args, **kwargs):
pass
#method_decorator(cache_page(60, key_prefix='drf'))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
Then, I call
def my_view(request, *args, **kwargs):
json_data = MyViewSet.as_view({'get': 'some_complex_view'})(request, format='json')
data = {
'my_data': json_data
}
return render(request, 'my_template.html', data)
It correctly caches when I request the view using browser, but it won't when using as_view()
There are a few strategies listed in the CBV docs:
Add the decorator in your urls.py route, e.g., login_required(ViewSpaceIndex.as_view(..))
Decorate your CBV's dispatch method with a method_decorator e.g.,
from django.utils.decorators import method_decorator
#method_decorator(login_required, name='dispatch')
class MyViewSet(TemplateView):
template_name = 'secret.html'
Before Django 1.9 you can't use method_decorator on the class, so you have to override the dispatch method:
class MyViewSet(TemplateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(MyViewSet, self).dispatch(*args, **kwargs)

Django class based views: variable across classes

I have a mixin called GroupAwareMixin in a mixins.py file:
class GroupAwareMixin(object):
group = None
def get_group(self):
self.group = self.bridge.get_group()
def dispatch(self, request, *args, **kwargs):
if not self.group:
self.get_group()
In the views.py file I have the following ListView which inherits from the above GroupAwareMixin:
class ChatListView(LoginRequiredMixin, GroupAwareMixin, ListView):
model = Chat
template_name = 'chat/home.html'
Further I have a Chat class in my views.py file, where I would like to access the group variable from the GroupAwareMixin class above. How I am able to access the group variable in the Chat class?
class Chat(ws.WS, ChatListView):
def on_message(self, websocket, msg):
slug = self.group
I tried to inherit from ChatListView, but the self.group in the Chat class is None.
The LoginRequiredMixin defines the following:
class LoginRequiredMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
if self.raise_exception:
raise PermissionDenied # return a forbidden response
else:
return redirect_to_login(request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
return super(LoginRequiredMixin, self).dispatch(
request, *args, **kwargs)

Why does UpdateView need to have model/queryset/get_queryset defined when using form_class as opposed to CreateView?

Works like a charm:
MyCreateView(CreateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
But the following doesn't:
MyUpdateView(UpdateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
I get this error:
MyUpdateView is missing a queryset. Define MyUpdateView.model, MyUpdateView.queryset, or override MyUpdateView.get_queryset().
Why does an UpdateView need model, queryset or get_queryset defined to not cause an error while CreateView doesn't? Shouldn't it be able to automatically derive it from the Model used in the ModelForm?
Currently (django 1.5.1 official release) UpdateView is calling self.get_object() to be able to provide instance object to Form.
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L217:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
And self.get_object method needs one of this properties declared: model, queryset or get_queryset
Whereas CreateView don't call self.get_object().
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L194:
def get(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
You might have a problem in your urls.py file.
What I think you wrote in it is:
url(r'foldername/(?P[0-9]+)/$', views.UpdateView.as_view(), name='update'),
but you have to change UpdateView to MyUpdateView, like this:
url(r'foldername/(?P[0-9]+)/$', views.MyUpdateView.as_view(), name='update'),

How to decorate different http method in a single class based view

For example, I have a class based view which allows both GET and POST method, as below,
class ViewOne(View):
def post(self, request, *args, **kwargs):
...
def get(self, request, *args, **kwargs):
...
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewOne, self).dispatch(*args, **kwargs)
Now, both GET and POST are login_required. But what if I want only POST to be login_required?
Hm... Is it not working?
class ViewOne(View):
#method_decorator(login_required)
def post(self, request, *args, **kwargs):
...
def get(self, request, *args, **kwargs):
...
Why don't create two classes, use also django-braces ;)
class ViewOne(View):
def get(self, request, *args, **kwargs):
...
class ViewTwo(LoginRequiredMixin, ViewOne):
def post(self, request, *args, **kwargs):
...