What is the reason for the nested template directory structure in Django?
Published: 2023-10-29 13:40:14

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:

```javascript
    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_2`I 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

NOTE: Originally posted in StackOverflow
https://stackoverflow.com/a/75861673/6710903
 


**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/