Skip to content

Instantly share code, notes, and snippets.

@garrypolley
Created September 21, 2012 15:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save garrypolley/3762045 to your computer and use it in GitHub Desktop.
Save garrypolley/3762045 to your computer and use it in GitHub Desktop.
Django decorate 3rd party app's views
# -*- coding: utf-8 -*-
from django.conf.urls import include
def decorated_include(urls, decorator)
"""Used to decorate all urls in a 3rd party app with a specific decorator"""
urls_to_decorate = include(urls)
for url_pattern in urls_to_decorate
url_pattern._callback = decorator(url_pattern._callback)
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url
from django.contrib.auth.decorators import login_required
from .decorators import decorated_include
urlpatterns = patterns('',
url(r'^admin/', decorated_include('mongoanut.urls', login_required),
url(r'^auth/', include('social_auth.urls')),
)
@whatisjasongoldstein
Copy link

Just to make sure I understand what's going on (read: ignore me if I'm wrong)

  • Iterates through an app's urls
  • for each url object in the pattern, decorate it's callback
  • the callback is a view
  • therefore, all the views are decorated at runtime

Upsides:

  • Since it's only for reusable apps that you can't edit, you're only going to example this code from the top (your url's) anyway, so it's not that confusing
  • Only 5% evil, no need to shower after
  • It took me 15 seconds to read it and determine what's going on

Downsides:

  • You use a private Django method. The risk is the devs feel free to change those on a whim without telling anyone. But as long as you're not doing that all over the place, the odds of it burning you in a major way are pretty low.

@tBaxter
Copy link

tBaxter commented Sep 21, 2012

I think it's the most elegant, least intrusive solution to the problem I've seen. My only concerns are...

  1. Is a circular import possible .importing urls and decorated_include from each other?
  2. I'm still not sure why it was url_pattern.callback in my testing, but url_pattern._callback in yours. If .callback works, I think it would be better.

Copy link

ghost commented Sep 15, 2014

I'm working on a Django 1.6.5 site and this is what worked for me:

def decorated_include(urls, decorator):
    """Used to decorate all urls in a 3rd party app with a specific decorator"""
    urls_to_decorate = include(urls)
    for url_pattern in urls_to_decorate[0].urlpatterns:
        url_pattern._callback = decorator(url_pattern._callback)
    return urls_to_decorate

@alukach
Copy link

alukach commented May 10, 2017

This code seems to not cover the case where a URL pattern contains a RegexURLResolver rather than a RegexURLPattern (i.e., an nested include() call in the examined include() call). This is my solution around that:

from django.conf.urls import include


def process_url_patterns(patterns, *decorators):
    """ Recursively look through patterns for views and apply decorators """
    for url_pattern in patterns:
        if hasattr(url_pattern, 'url_patterns'):
            process_url_patterns(url_pattern.url_patterns, *decorators)
        else:
            for decorator in decorators:
                print("Decorating {} with {}".format(url_pattern, decorator))
                url_pattern.callback = decorator(url_pattern.callback)


def decorated_include(urls, *decorators):
    """
    Used to decorate all urls in a 3rd party app with specific decorators
    """
    urls_to_decorate, app_name, namespace = include(urls)
    process_url_patterns(urls_to_decorate.urlpatterns, *decorators)
    return urls_to_decorate, app_name, namespace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment