Django mixins for class-based-generic views - django

I am trying to implement staff_member_required mixins:
Here are the two ways I found on how to do so:
First:
class StaffRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
Second:
class StaffRequiredMixin(object):
#classmethod
def as_view(self, *args, **kwargs):
view = super(StaffRequiredMixin, self).as_view(*args, **kwargs)
return staff_member_required(view)
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
What I want to know is:
Why the second way is overriding the as_view() method and wrapping it with staff_member_required ?
Do we get any 'additional' advantages by doing so ?
I am new to these mixins. Please help.

TL; DR: they're close to the same, the difference is in checking is_active as well as is_staff and the error messages. You probably don't need both because the as_view override negates the need for the dispatch override anyway.
These are really just two ways of doing close to the same thing.
This code:
class StaffRequiredMixin(object):
#classmethod
def as_view(self, *args, **kwargs):
view = super(StaffRequiredMixin, self).as_view(*args, **kwargs)
return staff_member_required(view)
...could actually be used alone to implement the staff_member_required decorator. In this case the staff_member_required functionality gets called in the view's as_view() function (i.e., from as_view() in your URLConf).
This code:
class StaffRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
...filters users in the dispatch method. You can see in the Django codebase that as_view actually calls dispatch. This means that if you use both together you won't actually ever trigger the if not request.user.is_staff code in the dispatch method because any user who doesn't pass would have been filtered out in the as_view method.
The second difference is that staff_member_required is slightly different from what the first code does. If you check out the code, you'll notice that staff_member_required also checks whether the user's is_active flag passes (not just is_staff like in your dispatch decorator). It also doesn't pass the messages.error like in your code.

Related

call parent dispatch before child dispatch django

I have a mixin which beside other things simplifies call of request.user object.
class MyMixin(LoginRequiredMixin, View):
...
leader = False
employee = False
def dispatch(self, request, *args, **kwargs):
self.leader = request.user.is_leader()
self.employee = request.user.is_employee()
return super().dispatch(request, *args, **kwargs)
...
And I have a heir of DetailView which has it's own dispatch method.
class MyDetailView(MyMixin, DetailView):
def dispatch(self, request, *args, **kwargs):
if not self.leader:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
But as you could've told it ain't working. Is there a way to elegantly call parent dispatch method from it's heir?
You could make your attributes into properties so that the order of execution does not matter. Using cached_property will mean that the property is only evaluated once for each request
from django.utils.functional import cached_property
class MyMixin(LoginRequiredMixin, View):
#cached_property
def leader(self):
return self.request.user.is_leader()
#cached_property
def employee(self):
return self.request.user.is_employee()

Django Generic views not working for PUT , GET , UPDATE , PATCH

I have RetrieveUpdateDestroyAPIView view like this.
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = TasksSerializer
def get_queryset(self):
query_set=Task.objects.get(id=self.kwargs['id'])
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
and my urls like this
path('task_detail/<int:id>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task"),
I am trying to PUT , PATCH , GET but getting same error
{
"detail": "Not found.",
"status_code": 404
}
The issue is in the function get_queryset, It expects a queryset but yours returns a single object, that's what the get function does as described here. So, you need to either set the queryset class field or use the get_queryset function.
You don't need to look up the task object yourself, that's what the generic view does for you. also you don't have to specify the method handlers(get, post, i.e.) yourself, they are already generated because you use RetrieveUpdateDestroyAPIView class. Also, since the lookup field defaults to the primary key(id), so, you could omit that, too
Try this code
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
queryset = Task.objects.all()
serializer_class = TasksSerializer
and use pk instead of id
path('task_detail/<int:pk>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task")
or you could leave the lookup field as id and use it in the path function. It's pretty much the same thing, just saving some code

Decorator for CBV

I have this CBV:
class GetStuff(View):
def get(self, request, company_id):
...
I want to decorate the get function with a custom function which takes the request and company_id arguments and check some permissions.
Any idea on how to achieve this? Most information I've found about decorators focus on FBV.
This is what I have so far:
def custom_decorator(func_view):
def wrapper(request, company_id):
if not request.user.is_staff:
# do_something()
return func_view(request, company_id)
return wrapper
You have to decorate the dispatch method
To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class. django doc
from django.utils.decorators import method_decorator
class GetStuff(View):
#method_decorator(custom_decorator)
def dispatch(self, *args, **kwargs):
return super(GetStuff, self).dispatch(*args, **kwargs)
or
#method_decorator(custom_decorator, name='dispatch')
class GetStuff(View):
def dispatch(self, *args, **kwargs):
return super(GetStuff, self).dispatch(*args, **kwargs)
you can find other decorating techniques in the docs

How to define transaction.atomic in a Django class-based view?

I want the post method in the class-based view to be atomic. I have defined the class so:
class AcceptWith(View):
#method_decorator(login_required)
#method_decorator(user_passes_test(my_test))
#method_decorator(transaction.atomic)
def dispatch(self, *args, **kwargs):
return super(AcceptWith, self).dispatch(*args, **kwargs)
Is this correct?
Can I make only the post method atomic?
Assuming that you're defining your own method to handle the POST, just apply the transaction.atomic decorator directly to that method.
class AcceptWith(View):
#transaction.atomic
def post(self, request, *args, **kwargs):
# your code here will be executed atomically
I think you should not wrap whole method, also you may want custom handler execute after rollback
def post(self, request, *args, **kwargs)
try:
with transaction.atomic():
pass # CRUD operations
except IntegrityError:
handle_exception() # this will run after rollback
https://docs.djangoproject.com/en/dev/topics/db/transactions/

Can I redirect to another url in a django TemplateView?

I have a url mapping that looks like this:
url(r'^(?P<lang>[a-z][a-z])/$', MyTemplateView.as_view()),
There are only a few values that I accept for the lang capture group, that is: (1) ro and (2) en. If the user types http://server/app/fr/, I want to redirect it to the default http://server/app/en/.
How can I do this since MyTemplateView only has a method that is expected to return a dictionary?
def get_context_data(self, **kwargs):
return { 'foo': 'blah' }
I know this question is old, but I've just done this myself. A reason you may think you want to do it in get_context_data is due to business logic, but you should place it in dispatch.
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return redirect('home')
return super(MyTemplateView, self).dispatch(request, *args, **kwargs)
Keep your business logic in your dispatch and you should be golden.
Why only get_context_data?
Just set up your get handler to do a redirect if necessary.
def get(self, request, lang):
if lang == 'fr':
return http.HttpResponseRedirect('../en')
return super(MyTemplateView, self).get(request, lang)
A note from the future: it's now possible and probably simpler just to use RedirectView.
This worked for me using an UpdateView class in Django 3.1:
def get(self, request, *args, **kwargs):
if 1 == 1:
return HttpResponseRedirect(reverse_lazy("view_name_here"))
else:
return super().get(request, *args, **kwargs)
To determine this, I analyzed its base class (Cmd+Click in PyCharm), where I found the base method:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
You can find this and other methods in the Django source code: django/views/generic/edit.py