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

guzmanojero

guzmanojero

Posted on October 29, 2023

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

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


Some people say:

"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,
            ...
    }]
Enter fullscreen mode Exit fullscreen mode
  • 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',
            ],},]
Enter fullscreen mode Exit fullscreen mode

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/ 
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

And APP_DIRS: True Django will look fot templates here.

/path/to/myproject/polls/templates/
/path/to/myproject/music/templates/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The structure is:

appname: Blog

model: Post

View type: List

appname/<model_name>_<view_type>.html
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
💖 💪 🙅 🚩
guzmanojero
guzmanojero

Posted on October 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related