5đź‘Ť
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:
- templates related to your specific site or project can live inside the directory pointed by the
TEMPLATES['DIRS']
setting; - templates related to a specific app, that could be served as is if you make your app pluggable, should live inside
'./appname/templates/appname/'
(andTEMPLATES['APP_DIRS']
must be True). This way you avoid name conflicts between files inside this folder anf files from outside.
0đź‘Ť
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
isTrue
, 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
DOCUMENTATION.
- https://docs.djangoproject.com/en/4.1/ref/templates/api/
- https://docs.djangoproject.com/en/4.1/topics/templates/
- https://docs.djangoproject.com/en/4.2/intro/tutorial03/#write-views-that-actually-do-something
- https://github.com/django/django/blob/main/django/template/loaders/app_directories.py
- https://github.com/django/django/blob/main/django/template/utils.py
- https://docs.djangoproject.com/en/4.1/topics/class-based-views/generic-display/
- [Django]-Deploying Django with gunicorn No module named ImportError: No module named validation
- [Django]-How to expose manytomany field as a resource with tastypie?