DEV Community

guzmanojero
guzmanojero

Posted on • Originally published at stackoverflow.com

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

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/




Enter fullscreen mode Exit fullscreen mode

Top comments (0)