Django 模板变量使用
上一节中,我们简单介绍了模板相关的概念,并手动实践了 python 中的两个流行的模板库 Mako 和 Jinja2。接下来,我们将深入了解如何在 Django 中使用模板系统。
1. Django 中的模板系统
Django 中自带了一个模板系统,叫做 Django Template Language (DTL),通过该引擎,我们可以方便的加载模板文件到内存中进行编译并动态插入数据,最后返回转换后的文本内容。在 Django 中模板引擎的配置同样是在 settings.py 中的,具体配置如下:
TEMPLATES = [{# 指定模板引擎'BACKEND': 'django.template.backends.django.DjangoTemplates',# 指定模板文件目录'Dirs': [os.path.join(BASE_DIR, 'templates')],'APP_Dirs': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},]
如果我们想让 Django 使用 Jinja2 模板引擎,可以将上述配置中的 BACKEND
的值进行替换:
'BACKEND': 'django.template.backends.jinja2.Jinja2',
又或者我们可以同时支持多个模板引擎,只不过指定的模板目录不同即可:
TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates','Dirs': [os.path.join(BASE_DIR, 'templates')],'APP_Dirs': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},}, { 'BACKEND': 'django.template.backends.django.DjangoTemplates','Dirs': ['/root/test/html/jinja2'],}]
Django 中提供了两个方法加载模板:get_template() 方法和 select_template() 方法。从源码中可以看到,这两个方法的实现几乎是一样的,只不过前者只能输入一个模板文件并返回 Template 对象,而后者可以输入多个模板文件但也只返回第一个存在的模板文件的 Template 对象。
# django/template/loader.pyfrom . import enginesfrom .exceptions import TemplateDoesNotExistdef get_template(template_name, using=None): Load and return a template for the given name. Raise TemplateDoesNotExist if no such template exists. chain = [] engines = _engine_list(using) # 遍历模板引擎 for engine in engines: try: return engine.get_template(template_name) except TemplateDoesNotExist as e: chain.append(e) # 找不到匹配的模板引擎,就直接抛出TemplateDoesNotExist异常 raise TemplateDoesNotExist(template_name, chain=chain)def select_template(template_name_list, using=None): Load and return a template for one of the given names. Try names in order and return the first template found. Raise TemplateDoesNotExist if no such template exists. if isinstance(template_name_list, str): raise TypeError( 'select_template() takes an iterable of template names but got a ' 'string: %r. Use get_template() if you want to load a single ' 'template by name.' % template_name_list ) chain = [] engines = _engine_list(using) for template_name in template_name_list: for engine in engines: try: return engine.get_template(template_name) except TemplateDoesNotExist as e: chain.append(e) if template_name_list: raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain) else: raise TemplateDoesNotExist(No template names provided)# 忽略其他代码
从 django 的源代码中,可以看到 _engine_list() 方法就是获取我们前面的引擎列表,然后开始找这个引擎对应的目录下是否有输入的模板文件。如果一旦根据模板文件找到了匹配的模板引擎,查询工作就会停止,直接使用该模板引擎对模板文件生成一个 Template 对象并返回。
对于不同的模板引擎,返回的 Template 对象是不同的,但是 Template 对象必须包含一个 render() 方法:
Template.render(context=None, request=None)
接下来,我们会使用两个模板文件进行一个简单的实验。实验的环境还是在原来的 first-django-app 工程中进行。首先,我们准备两个模板文件 index.html 和 index.html.j2。其中 index.html 放到 django 工程下的 template 目录中,对应着 Django 内置的模板引擎; index.html.j2 则放到 /root/test/html/ 目录下,对应着 jinja2 模板引擎。
(django-manual) [root@server first_django_app]# python manage.py ShellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux Type help, copyright, credits or license for more information.(InteractiveConsole)>>> from django.template.loader import get_template>>> get_template('index.html')<django.template.backends.django.Template object at 0x7f132afe08e0>>>> get_template('index.html.j2')<django.template.backends.jinja2.Template object at 0x7f132a952a60>>>>
可以看到,对应的模板文件都找到了对应的模板引擎。接下来,我们来对 Django 内置的模板引擎进行测试:
(django-manual) [root@server first_django_app]# cat templates/index.html <!DOCTYPE html><html lang=en><head><Meta charset=UTF-8><title>Title</title></head><body><h1>{{ title }}</h1><p>{{ content }}</p></body></html>(django-manual) [root@server first_django_app]# python manage.py ShellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux Type help, copyright, credits or license for more information.(InteractiveConsole)>>> from django.template.loader import get_template>>> tf = get_template('index.html')>>> print(tf.render(context={'title': '标题文件', 'content': '这是正文'}))<!DOCTYPE html><html lang=en><head><Meta charset=UTF-8><title>Title</title></head><body><h1>标题文件</h1><p>这是正文</p></body></html>
可以看到,Django 内置的模板引擎已经正确地将模板文件进行了转换,将模板变量进行替换,得到了我们想要的HTML 文本。
2. Django 中的 render 函数
有了上面的基础,我们来看看前面给出的返回动态 HTML 的 render 函数:
def index(request, *args, **kwargs): return render(request, index.html, {title:首页, content: 这是正文})
我们可以通过源码追踪以下该函数的执行过程。首先 render 函数定义在 django/shortcut.py 中,代码内容如下:
# django/shortcut.py# ...def render(request, template_name, context=None, content_type=None, status=None, using=None): Return a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments. content = loader.render_to_string(template_name, context, request, using=using)return HttpResponse(content, content_type, status)# ...
可以看到,最核心的转换代码是 loader.render_to_string
方法,我们继续追进去,就可以看到和前面熟悉的测试代码了:
# django/template/loader.py# ...def render_to_string(template_name, context=None, request=None, using=None): Load a template and render it with a context. Return a string. template_name may be a string or a list of strings. if isinstance(template_name, (list, tuple)):template = select_template(template_name, using=using)else:template = get_template(template_name, using=using)return template.render(context, request)# ...
render_to_string()
方法的第一步是判断 template_name
是列表还是元组。如果是列表或者元祖,则使用select_template()
方法得到对应的模板对象,否则使用 get_template()
方法。最后由template.render()
方法加上对应的模板数据生成我们想要的 HTML 文本。可以看到最后的 render()
方法依旧是返回的 HttpResponse
示例,只不过 content 参数正好是动态生成的 HTML 文本。
3. 小结
今天我们探究了 Django 中模板系统的配置和使用。此外还追踪了 render() 方法的源码,对 Django 中的模板系统以及提供的一些封装的内部函数有了更清楚的认识。接下来,我们将深入学习 Django 内置的模板语言-DTL,彻底掌握复杂模板文件的编写。