This web site has been retired. Please follow my activities at pztrick.com.

pZtrick.com

the personal site of patrick paul

How to Strip the Django @staff_member_required Decorator

| Comments

Today, I was bashing my head on the wall trying to figure out why the AJAX image/file upload for django-wysiwyg-redactor was returning a cryptic CSRF token error message and 403 Forbidden response. I tried injecting the CSRF token into the Redactor forms using jQuery – which didn’t work – and then tried csrf_exempt-decorating the forms – which also didn’t work.

I probably spent way too much time on these two hacks before actually looking into the source code…

RedactorUploadView redactor/views.py
1
2
3
4
5
6
7
8
9
10
class RedactorUploadView(FormView):
    # ... snip ...

    @method_decorator(csrf_exempt)
    @method_decorator(staff_member_required)
    def dispatch(self, request, *args, **kwargs):
        return super(RedactorUploadView, self).dispatch(request, *args,
                                                        **kwargs)

    # ... snip ...

And there, quite plainly, you can see the staff_member_required decorator. No wonder my uploads were failing – albeit with a misguiding CSRF error message. I was accessing a staff-restricted view with a regular User role.

I had already hacked my urls.py to decorate each Redactor view with the csrf_exempt token, and now needed to figure out how to strip a decorator in Python. Which you can’t.

urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ...
from django.views.decorators.csrf import csrf_exempt

urlpatterns = patterns('',
    # ...

    # Core applications urls
    url(r'^', include('core.urls')),
    # (r'^redactor/', include('redactor.urls')),  # commented out

   )

import redactor.urls
import core.utils
for up in redactor.urls.urlpatterns:
    new = url(up.regex.pattern, csrf_exempt(up.callback), name=up.name)
    urlpatterns += patterns('', new )

From this point in attempting to hack a solution, I was essentially invoking: csrf_exempt(csrf_exempt(staff_member_required(RedactorUploadView))) (That’s more the order of operations than what syntactically occurs with decorators in Python…).

…which of course doesn’t solve the problem of the Staff role requirement. The challenge: Strip the staff_member_required decorator. Somehow. (I didn’t feel like forking the repo or sub-classing the Redactor views in my own project… :)

The hack solution: Fake it.

@strip_staff_member_required
1
2
3
4
5
6
7
8
from functools import wraps
def strip_staff_member_required(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        request.user.is_staff = True
        request.user.save = None
        return view_func(request, *args, **kwargs)
    return _wrapped_view

This worked swimmingly, and as long as I don’t wrap a view which invokes .save() on the request.user object, I should be OK.

Edit: It was easy enough to strip the bound .save() method to (hopefully) prevent inadvertently promoting a User to Staff.

Comments