What is the reason for the nested template directory structure in Django?

Question:

The django template folder requires creating a subfolder with the name of the app which then contains the template files. Why is this necessary, when python manage.py collectstatic can infer this information while traversing all directories? It seems very redundant.

Asked By: 2080

||

Answers:

First of all, Django does not require this specific folder structure for templates to work, it is just a stablished pattern to do so. And, of course, it has a rationale, as pointed in the official doc:

Template namespacing

Now we might be able to get away with putting our templates directly
in polls/templates (rather than creating another polls subdirectory),
but it would actually be a bad idea. Django will choose the first
template it finds whose name matches, and if you had a template with
the same name in a different application, Django would be unable to
distinguish between them. We need to be able to point Django at the
right one, and the easiest way to ensure this is by namespacing them.
That is, by putting those templates inside another directory named for
the application itself.

You can reference to this question or that another for concrete cases.

In a nutshell, by following this pattern you can have your templates organized in 2 groups:

  1. templates related to your specific site or project can live inside the directory pointed by the TEMPLATES['DIRS'] setting;
  2. templates related to a specific app, that could be served as is if you make your app pluggable, should live inside './appname/templates/appname/' (and TEMPLATES['APP_DIRS'] must be True). This way you avoid name conflicts between files inside this folder anf files from outside.
Answered By: Rodrigo Rodrigues

This is a long answer. The idea is to really explain how Django works when loading a template. I even show you some Django source code to explain some points.

Django uses engines to load the templates. This answers works for the default engine DjangoTemplates (django.template.backends.django.DjangoTemplates)


Let’s review your comment:

"The django template folder requires creating a subfolder with the
name of the app which then contains the template files."

No, Django doesn’t require you to create a subfolder with the name of the app inside the templates folder. IT IS NOT A REQUIREMENT, it is just a recommendation.

But why? Let’s see it in steps.

1) Where does Django look for template files?

Django searches for template directories in a number of places,
depending on your template loading settings.

There are two places that are defined in the settings.py file. When configuring TEMPLATES you have DIRS and APP_DIRS. Like this:

TEMPLATES = [{
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR/"templates"],
        'APP_DIRS': True,
        ...
}]
  • DIRS:

Is a list of directories where Django looks for template source
files…

This should be set to a list of strings that contain full
paths to your template directories…

Your templates can go anywhere you want, as long as the directories and templates are readable by the web server…

Example:

TEMPLATES = [{
        'DIRS': [
            '/home/html/templates/lawrence.com',
            '/home/html/templates/default',
        ],},]

So you have no required structure from Django. You can have as many directories you want, where you want, as long as the paths are listed here and readable by the web server. A common case is when you want to have a project folder for your templates that all apps are going to use:

'DIRS': [BASE_DIR/"templates"]

This tells Django to find the files in the templates folder located in the base directory (the root level).

  • APP_DIRS:

Tells whether the engine should look for templates inside installed
applications…

When APP_DIRS is True, DjangoTemplates engines look for templates in the templates subdirectory of installed applications…

By convention DjangoTemplates looks for a “templates” subdirectory in
each of the INSTALLED_APPS…

For each app in INSTALLED_APPS, the loader looks for a templates
subdirectory. If the directory exists, Django looks for templates in
there…

It’s a boolean value, True or False.
If True, it will look for a templates subdirectory inside each app:

project/
  appname/
    templates/ 

This is the only structure that Django requires from you. If you want to have templates in each app, you must have a templates subfolder in the app directory.

If you have this apps:

INSTALLED_APPS = ['myproject.polls', 'myproject.music']

And APP_DIRS: True Django will look fot templates here.

/path/to/myproject/polls/templates/
/path/to/myproject/music/templates/

2) Why do people recommend having another folder with the app name inside the templates folder (in installed apps)?

It’s a matter of orginizing your code and helping Django find the file that you really requested (template namespacing).

Something important to remember: when the template engine loads templates, it checks the template directories in the order they are defined in the DIRS setting. If a template with the same name is found in multiple directories, the first one found is used.

After checking the DIRS directories, the template engine looks for templates in the APP_DIRS directories. If a template with the same name is found in multiple application directories, the one in the first application listed in INSTALLED_APPS is used.

Let’s see what could happen if we don’t do template namespacing. We have this folders:

INSTALLED_APPS = ['project.app_1', 'project.app_2']

project/
  app_1/
    templates/
      detail.html
  app_2/
    templates/
      detail.html

If I’m working in the app_2 view and want to load the template detail.html I would have a surprise. Instead of loading the template from app_2I would load the template from app_1. This is because the files have the same name and app_1 comes first in INSTALLED_APPS.

To avoid this problem we add the app’s name inside the template folder.

project/
  app_1/
    templates/
      app_1/
        detail.html
  app_2/
    templates/
      app_2/
        detail.html

To load the template from the view I would need "app_2/detail.html". I’m being much more specific.

3) You can check this in the Django source code.

Go to django/django/template/loaders/app_directories.py and you’ll find:

class Loader(FilesystemLoader):
    def get_dirs(self):
        return get_app_template_dirs("templates")

Which calls get_app_template_dirs() and passes "template" as argument.

django/django/template/utils.py

@functools.lru_cache
def get_app_template_dirs(dirname):
    """
    Return an iterable of paths of directories to load app templates from.
    dirname is the name of the subdirectory containing templates inside installed applications.
    [NOTE: Remember that "templates" was passed as argument, the dirname]
    """

    template_dirs = [
        Path(app_config.path) / dirname
        for app_config in apps.get_app_configs()
        if app_config.path and (Path(app_config.path) / dirname).is_dir()
    ]
    # Immutable return value because it will be cached and shared by callers.
    return tuple(template_dirs)

With Path(app_config.path)/dirname you get appname/templates/.

For each installed app found here for app_config in apps.get_app_configs().

If the dirs exists if app_config.path and (Path(app_config.path) / dirname).is_dir()

template_dirs are all the dirs in the installed apps that have a template foler.

4) If you’re working with Class Based Views (CBV) you can get some functionality done for you.

You have this list view for the Post model in the Blog app.

class BlogListView(ListView):
    model = Post

In the absence of an explicit template name, Django will infer one from the object’s (model’s) name. In this example:

blog/post_list.html

The structure is:

appname: Blog

model: Post

View type: List

appname/<model_name>_<view_type>.html

And as you already know, Django looks for a “templates” subdirectory in each of the INSTALLED_APPS.

So, if APP_DIRS: True the full path that the Class Based View would expect to load the templates is:

/path/to/project/blog/templates/blog/post_list.html

This is a predefined requirement of the CBV, but it can be modified if
you define a template_name argument.

class BlogListView(ListView):
    model = Post
    template_name = "blog/the_super_list_of_posts.html"

You can check this in the source code:

Go to django/django/views/generic/list.py

class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    """Mixin for responding with a template and list of objects."""

     template_name_suffix = "_list"
     
    def get_template_names(self):
    """
    Return a list of template names to be used for the request. Must return
    a list. May not be called if render_to_response is overridden.
    """
        try:
            names = super().get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

    # If the list is a queryset, we'll invent a template name based on the
    # app and model name. This name gets put at the end of the template
    # name list so that user-supplied names override the automatically-
    # generated ones.
        if hasattr(self.object_list, "model"):
            opts = self.object_list.model._meta
            names.append(
                "%s/%s%s.html"
                % (opts.app_label, opts.model_name, 
self.template_name_suffix)
        )
        elif not names:
            raise ImproperlyConfigured(
                "%(cls)s requires either a 'template_name' attribute "
                "or a get_queryset() method that returns a QuerySet."
               % {
                    "cls": self.__class__.__name__,
                 }
        )
        return names

You have:

names = super().get_template_names() # get's the appname/templates
...
names.append(
    "%s/%s%s.html"
    % (opts.app_label, opts.model_name, self.template_name_suffix)
)

Where:

opts.app_label: is the app’s name

opts.model_name: is the model’s name

self.template_name_suffix: is the suffix, in this case "_list"

All together they make the default’s template name that the CBV looks for:

app_label/templates/app_label/<model_name>_<template_name_suffix>.html

DOCUMENTATION.

  1. https://docs.djangoproject.com/en/4.1/ref/templates/api/
  2. https://docs.djangoproject.com/en/4.1/topics/templates/
  3. https://docs.djangoproject.com/en/4.2/intro/tutorial03/#write-views-that-actually-do-something
  4. https://github.com/django/django/blob/main/django/template/loaders/app_directories.py
  5. https://github.com/django/django/blob/main/django/template/utils.py
  6. https://docs.djangoproject.com/en/4.1/topics/class-based-views/generic-display/
Answered By: Guzman Ojero
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.