33

I try to keep a somewhat consistent naming scheme on my HTML templates. I.e. index.html for main, delete.html for delete page and so forth. But the app_directories loader always seems to load the template from the app that's first alphabetically.

Is there any way to always check for a match in the calling app's templates directory first?

Relevant settings in my settings.py:

PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))

TEMPLATE_LOADERS = (
    'django.template.loaders.app_directories.load_template_source',
    'django.template.loaders.filesystem.load_template_source',
)
TEMPLATE_DIRS = (
    os.path.join(PROJECT_PATH, 'templates'),
)

I've tried changing the order of TEMPLATE_LOADERS, without success.


Edit as requested by Ashok:

Dir structure of each app:

templates/
    index.html
    add.html
    delete.html
    create.html
models.py
test.py
admin.py
views.py

In each app's views.py:

def index(request):
    # code...
    return render_to_response('index.html', locals())

def add(request):
    # code...
    return render_to_response('add.html', locals())

def delete(request):
    # code...
    return render_to_response('delete.html', locals())

def update(request):
    # code...
    return render_to_response('update.html', locals())
jmagnusson
  • 5,799
  • 4
  • 43
  • 38
  • 1
    can you provide the structure of your 'templates' directory and a sample code of how you are loading/rendering the template – Ashok Jun 22 '10 at 12:18
  • The app_directories loader sounds a little misleading to me. I would have assumed that it would load templates from the correct applications directory just like you. After all, isn't the point of a django app to be able to create separated slices? – Adam Oct 04 '12 at 15:04

3 Answers3

56

The reason for this is that the app_directories loader is essentially the same as adding each app's template folder to the TEMPLATE_DIRS setting, e.g. like

TEMPLATE_DIRS = (
    os.path.join(PROJECT_PATH, 'app1', 'templates'),
    os.path.join(PROJECT_PATH, 'app2', 'template'),
    ...
    os.path.join(PROJECT_PATH, 'templates'),
)

The problem with this is that as you mentioned, the index.html will always be found in app1/templates/index.html instead of any other app. There is no easy solution to magically fix this behavior without modifying the app_directories loader and using introspection or passing along app information, which gets a bit complicated. An easier solution:

  • Keep your settings.py as-is
  • Add a subdirectory in each app's templates folder with the name of the app
  • Use the templates in views like 'app1/index.html' or 'app2/index.html'

For a more concrete example:

project
    app1
        templates
            app1
                index.html
                add.html
                ...
        models.py
        views.py
        ...
    app2
        ...

Then in the views:

def index(request):
    return render_to_response('app1/index.html', locals())

You could even write a wrapper to automate prepending the app name to all your views, and even that could be extended to use introspection, e.g.:

def render(template, data=None):
    return render_to_response(__name__.split(".")[-2] + '/' + template, data)

def index(request):
    return render('index.html', locals())

The _____name_____.split(".")[-2] assumes the file is within a package, so it will turn e.g. 'app1.views' into 'app1' to prepend to the template name. This also assumes a user will never rename your app without also renaming the folder in the templates directory, which may not be a safe assumption to make and in that case just hard-code the name of the folder in the templates directory.

Daniel
  • 8,212
  • 2
  • 43
  • 36
  • 2
    This is pretty much the only thing I miss from CakePHP :) I hope they add it some day. – jmagnusson Jun 23 '10 at 09:10
  • It's a good workaround for a broken django feature (it's a compromise for a very simple-to-extend templating system); but it still obliges to add the 'appname' folder to your app template folder, and 'appname' might not be the one you want to use in your project... – Stefano Jan 31 '11 at 10:40
  • This is a nice and simple solution, as long as there aren't nested apps... ;) – Jorge Leitao May 27 '13 at 17:51
  • This adding many templates is no more idiomatic for Django. Currently you can use "'APP_DIRS' : True" instead. It recursively includes template dirs to main project – cengineer May 29 '17 at 14:32
  • @cengineer The setting `"APP_DIRS": True` would always work. If the template name is the same, it still needs some prefix to make the template loader to distinguish them. But I wonder if I create a subdirectory `templates` folder at root, and put all the app's template there, could it be a good solution? – Alston Jul 12 '17 at 14:23
  • 1
    @Stallman I think it will be ok. But personally I would prefer to put templates to their related apps to prevent from confusion. For example, if I have 2 apps called 'first' and 'second' and 'change_form.html' templates for each, I would put it in 'first/templates/admin/change_form.html' and 'second/templates/admin/change_form.html'. But of course it depends on you. If you want to collect them in 1 folder located in the root then do it. And in that situation I would have 'templates/first/admin/change_form.html' and 'templates/second/admin/change_form.html'. I think both solutions as ok. – cengineer Jul 13 '17 at 08:21
  • We have to edit this post `render_to_response` method is deprecated since version 2.0. [LINK](https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#render-to-response) – R. García Jun 18 '18 at 20:44
7

I know this is an old thread, but I made something reusable, that allows for simpler namespacing. You could load the following as a Template Loader. It will find appname/index.html in appname/templates/index.html.

Gist available here: https://gist.github.com/871567

"""
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 AppDirecotry 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))
hidde-jan
  • 71
  • 1
  • 1
  • 1
    Well it works exactly like ` django.template.loaders.app_directories.Loader` in Django 1.5.x. And the problem is still not solved. – Shailen Aug 27 '13 at 21:14
6

The app_loader looks for templates within your applications in order that they are specified in your INSTALLED_APPS. (http://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types).

My suggestion is to preface the name of your template file with the app name to avoid these naming conflicts.

For example, the template dir for app1 would look like:

templates/
    app1_index.html
    app1_delete.html
    app1_add.html
    app1_create.html
Chris Lawlor
  • 47,306
  • 11
  • 48
  • 68
  • Thank you Chris ! after a long time searching the answer for me was - the app was not listed in INSTALLED_APPS, therefore it was only looking in the base templates folder, not the folder local to the app. For me this is because my settings.py and my local_settings.py were out of sync because the only the settings.py is version controlled, after a pull and update it had a new app, but my local_settings.py overwrote it. – jowan sebastian Apr 11 '16 at 20:02