Skip to content

Instantly share code, notes, and snippets.

@hidde-jan
Created March 15, 2011 21:52
Show Gist options
  • Save hidde-jan/871567 to your computer and use it in GitHub Desktop.
Save hidde-jan/871567 to your computer and use it in GitHub Desktop.
Django template loader that allows for `namespaced` loading of templates.
"""
Wrapper for loading templates from "templates" directories in INSTALLED_APPS
packages, prefixed by the appname for namespacing.
This loader finds `appname/templates/index.html` when looking for something
of the form `appname/index.html`.
"""
from django.template import TemplateDoesNotExist
from django.template.loaders.app_directories import app_template_dirs, Loader as BaseAppLoader
class Loader(BaseAppLoader):
'''
Modified AppDirectory Template Loader that allows namespacing templates
with the name of their app, without requiring an extra subdirectory
in the form of `appname/templates/appname`.
'''
def load_template_source(self, template_name, template_dirs=None):
try:
app_name, template_path = template_name.split('/', 1)
except ValueError:
raise TemplateDoesNotExist(template_name)
if not template_dirs:
template_dirs = (d for d in app_template_dirs if
d.endswith('/%s/templates' % app_name))
return iter(super(Loader, self).load_template_source(template_path,
template_dirs))
@saul
Copy link

saul commented Apr 8, 2013

Great stuff! However I noticed that this does not work on Windows systems (or any systems that do not use / as their path separator).

Add an import for the os module, and change line 25 to:

template_dirs = (d for d in app_template_dirs if d.endswith('%(sep)s%(app_name)s%(sep)stemplates' % {'sep': os.sep, 'app_name': app_name}))

@michaelcoconnor
Copy link

Thanks much for putting this up. From Django 1.8 on you must substitute get_app_template_dirs for app_template_dirs in your line 10, and then set app_template_dirs = get_app_template_dirs('templates').

@michaelcoconnor
Copy link

Upon further investigation I find that it's also necessary, with Django 1.8 at least and probably with earlier versions, to add if app_name in [u'admin', u'auth', u'admindocs']: template_path = app_name + '/' + template_path just before the return statement. Otherwise the admin app index.html won't load (and I assume that the story is the same for admindocs and auth because their template directories are in like locations, as discovered upon printing out `app_template_dirs').

Contrary to the link below the window that I'm typing in, it's not possible to upload the modified file either as .txt or .py.

@michaelcoconnor
Copy link

I forgot to mention, concerning Django 1.8 (and 1.8+) see also this doc and this one too for other required changes to 1.7 code wrt templates.

@michaelcoconnor
Copy link

OK, here's what I wound up with. It's working with Django 1.8 and Python 2.7 (Markdown code highlighting doesn't work right with this much text):

############################
from django.template import TemplateDoesNotExist
from django.template.loaders.app_directories import get_app_template_dirs, Loader as BaseAppLoader
app_template_dirs = get_app_template_dirs('templates')
from os import sep

class Loader(BaseAppLoader):

def load_template_source(self, template_name, template_dirs=None):
    try:
        app_name, template_path = template_name.split(sep, 1)
    except ValueError:
        raise TemplateDoesNotExist(template_name)

    if not template_dirs:
        if app_name == 'registration':
            template_dirs = (d for d in app_template_dirs if
                    d.endswith( '{}{}{}templates'.format(sep, 'admin', sep) ) )
        elif app_name == 'admin_doc':
            template_dirs = (d for d in app_template_dirs if
                    d.endswith( '{}{}{}templates'.format(sep, 'admindocs', sep) ) )
        else:
            template_dirs = (d for d in app_template_dirs if
                    d.endswith( '{}{}{}templates'.format(sep, app_name, sep) ) )

    if app_name in [u'admin', u'auth', u'admindocs']: template_path = app_name + sep + template_path

    if app_name == 'admin_doc': template_path = 'admin_doc' + sep + template_path
    if app_name == 'registration': template_path = 'registration' + sep + template_path

    return iter(super(Loader, self).load_template_source(template_path, template_dirs))

########################

So things got squirrelly, due to all of the changes that were necessary to make admin work, and also, from the admin GUI, the Documentation and Change Password links to work. Alternatively, the code as I left it after my first comment above can work if in your TEMPLATES list in settings.py you put for the dict 'DIRS' key entry a listing of the admin, admindocs and auth template directories, such as:

        '/pathto/djangoenv/local/lib/python2.7/site-packages/django/contrib/admin/templates',
        '/pathto/djangoenv/local/lib/python2.7/site-packages/django/contrib/auth/templates',
        '/pathto/djangoenv/local/lib/python2.7/site-packages/django/contrib/admindocs/templates',

But those paths being installation-specific, for now I'm sticking with the code of this comment.

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