Django学习之十三:提高页面开发效率减少冗余的模板系统

时间:2022-07-21 14:54:37

Django 模板

模板按照我的理解,就是让html中内容不固定,让html内容已后端的方式动态起来(虽然前端mvvm框架也也开始有模板概念,所以广义说模板概念不限于后端)。但是html基础的内容还是是固定的。模板通过类编程的模板语法,可以将html模板中的动态内容,通过后端程序的计算传入核心数据,最后通过模板语法得到一个完整的html。模板的构造核心就是:模板语法和上下文数据(渲染引擎的全局数据和后端代码传入的数据);模板的驱动就是模板引擎(如Jinja2,django内置的DTL)。模板语法的数据来自于上下文数据,使得模板可以动态的生成html内容,关键让类似内容的构造更加高效,如for循环渲染列表。模板语法还提供模板与模板间存在关系:继承关系和包含关系。模板间的关系时的开发网页减少大量的冗余内容。

后端使用模板,多用于开发访问量较小的后台管理系统。

模板语法

模版文件中使用的variables会被上下文字典中的对应的key的值所替代。

模版文件中使用的tags会被引擎执行一段相应的逻辑。

逻辑语法

  • {% if condition %} {% else %} 条件
  • {% for %} {% endfor%} 迭代

函数式过滤器

  • 说明: filter是用于转变 variable 和 tag's arguments 的
  • 例如: {{ django|title }} 其上下文 {'django': 'the web framework'}
  • 渲染后:'The Web Framework'
  • filter参数:有些filter是可以传入参数的,参数传入方式是filter:argument;
  • 例如: {{ datetime|date:"Y-m-d" }}

内置filter

内置filter: 参见官档索引

功能tag

  • 语法: {% tag %}
  • tag用法: 不同tag有自己的处理逻辑,包括用户自定义的tag,并且tag可以接受参数,有些tag有开始和结束tag对。
  • {% csrf_token %} 无参数
  • {% cycle 'odd' 'even' %} 有参数
  • {% if user.is_authenticated %} Hello, {{ user.username }}. {% endif %} 有开始和结束tag对的。
  • 说明:tag有内置,也可自定义。

注释

  • 语法:{# this won't be rendered #}
  • 作用:就是注释,不会被渲染。
  • 多行注释利用tag: {% comment %} 多行注释内容 {% endcomment %}
  • 说明: 为什么不用html的注释,因为模版语言不止用于html文本,其它文本也可以,所以在模版语言这一层来进行注释才行。注意,注释不能嵌套。

内置tag

内置tag:参见官档索引

导入三方tag and filter(load)

{% load static %} 加载static app中templatetags目录下的static文件中定义的tag。

load同时也会导入tag和filter

过滤器和功能tag的区别

Tags的功能比filter要复杂的多,因为tag几乎可以做任何事情,包括最重要的渲染模版inclusion_tag。而filter主要是对数据进行处理。

自定义tag和filter

  1. 前提:最常见是自定义的tag是在对应的app目录下的创建的,所以要将app注册到settings的installed_apps列表中。
  2. 在app目录下创建templatetags目录,名字必须固定,这个是默认查找加载指定自定义tag和filter文件的地方,并在目录创建__init__.py文件。(是django默认的finder查找逻辑限制了这个名字)
  3. 创建py文件,自定义tag就放在文件中。文件名一定要小心,不要和其它app注册的冲突了。自定义filter和tag,如:
# my_tags.py
from django import template register = template.Library() # register变量名固定 @register.filter # 自定义filter
def multi_tag(v1, v2):
return v1 * v2 @register.simple_tag # 自定义simple tag
def tag_add(v1, v2):
return v1 + v2
  1. 自定义好自己的tags和filters,重要的一步就是将他们扩展使用到template引擎中,让他们生效就要使用{% load %} tag.
  2. 在模板文件中添加load标签后,需要重启服务。这点很关键啊!还有load后面的自定义tag保存的文件,不用引号,直接load后面跟文件名就行了
  3. 在模版中使用自定义tag和filter。模版间继承关系,是不会继承{% load %} 标签的,所以每个模版中要使用自定义的tag和filter都需要再次{% load %}一次。

上下文数据

  • 语法: {{ variable }}
  • 用法: My first name is {{ first_name }}. My last name is {{ last_name }}.
  • 非全局变量,需要提供的上下文字典(或者叫环境或者叫作用域):{'first_name': 'John', 'last_name': 'Doe'}
  • 通过render后产生的结果:My first name is John. My last name is 'Doe'.
  • 如果变量指向的也是一个字典:那么使用dotted可以访问了字典中的值:{{ my_dict.key }} {{ my_object.attribute }} {{ my_list.0 }}

全局数据

内置:官档

  • request对象自动传入

传入数据

  • render('inde.html', {已字典结构传入})

模板间关系

继承关系

通过{% extends '父模板.hmtl' %}'

包含关系

通过{% include '插入的模板.html' %} , 说明插入的模板可以使用数据

inclusion_tag关系

inclusion_tag 通过自定义tag形式,tag绑定了一个模板,tag函数处理逻辑放回一个上下文字典供绑定的模板渲染。这种也相当于是模板的包含关系,只不过是通过一个自定义的inclusion_tag进行封装。且让插座与插头对接更为明确,就是提供给tag提供参数,参数就是对接的规范了。由于是tag的形式,tag函数代码可以访问后台的所有数据。

inclusion_tag 示例详解

      下面代码将一个模版通过使用自定义的类型为inclusion tag即进行渲染使用:

      这个自定义tag是怎么做到的呢?

      首先,我们利用这个tag的场景是:多个url页面都要用到相同的页面布局内容。如:博客系统中的个人站点的用户文章列表,标签列表,公告;这些对于这个用户的站点内容都是一样的。这些共同的东西要怎么才能重复利用呢?对于学习了template继承知识的同学,可能想到的是使用继承关系既可以了。继承是没错,但是相同部分的内容,要提供给模版语言的数据还是要给予的,不同的是这些数据在各自的视图view函数中,要去重复的获取数据这些重复的获取数据的代码,在这些视图之间都是一样的。虽然模版得到了继承,但是模版要用到的数据还是造成了重复代码。要解决这个问题方式一:可以将获取数据的代码,封装到一个函数代码块中,这样能解决重复问题。这是利用基于模版的继承为主要思想的思路,因为模版必须继承才行。

      上面提到的模版继承思路,有一个局限性就是耦合度太高,必须继承模版。有没有什么方法不用继承模版就可以实现相同页面块的即插即用(继承方式无法即插即用)。django的一个自定义tag类型,给我们提供了一种即插即用的思路,这种思路是基于模版语言的tag对应一个python函数逻辑的思想。只需要自定义一个tag,tag就可以在任何的模版中插入使用。tag要做的就是返回一个渲染了的在前面提到的重复页面就行了。(这个就类似与include内置tag功能的一样,不同的是,include的页面是死的页面;而这里自定义的tag是可以利用模版语言结合上下文数据,动态的渲染出这个即插即用的页面)。这个指定tag就是django的inclusion tags。

inlucsion tags 的使用需要准备两个东西:一个即插即用的template,另一个就是渲染模版的上下文context data数据了。这个数据就是我们第一种思想中提到的要通过一个封装函数获取的数据。其实参数就是通过被插入的主模版能够给我们提供的数据了。即插即用,解耦的思想很棒!下面过一个列子来实践这个即插即用的思想。

友情提醒一句:利用这个思想,首先是目的和使用场景,前端页面要重复使用有且并且要重复的页面的上下文数据获取较多比较麻烦且重复

开始示例:

  1. 模版stuff_list.html:

{# 公告 #}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">公告</h3>
</div>
<div class="panel-body">
<ul class="panel-list" type="none">
<li><span>用户别名: {{ user_obj.username }}</span></li>
<li><span>园龄: {{ age_days }} 天</span></li>
<li><span>随笔数: {{ user_obj.article_set.count }} 篇</span></li>
</ul>
</div>
</div> {#随笔分类#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div> <div class="list-group">
{% for item in article_category_queryset %}
<a href="" class="list-group-item">{{ item.category__title }} ({{ item.count }})</a>
{% endfor %}
</div>
</div> {#标签#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">我的标签</h3>
</div> <div class="list-group">
{% for item in article_tag_queryset %}
<a href="" class="list-group-item">{{ item.title }} ({{ item.count }})</a>
{% endfor %}
</div>
</div> {#归档#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔归档</h3>
</div> <div class="list-group">
{% for item in article_archive_queryset %}
<a href="" class="list-group-item">{{ item.y_m }} ({{ item.count }})</a>
{% endfor %}
</div>
</div>
  1. 自定义的tag文件my_tags_filters.py:

重复获取的上下问数据就在该文件中函数中实现,返回一个上下文数据字典。在利用渲染装饰器来装饰这个函数,装饰器函数要带入要渲染的模版文件。相当于就是装饰器给我们做渲染模版作用,我们的自定义函数来获取查询数据,返回渲染需要的上下文数据就行。

from django import template
from myblog import models
from django.utils import timezone
from django.db.models import Count register = template.Library() @register.inclusion_tag('myblog/stuff_list.html') # 绑定我们的模板了
def stuff_list(user_obj): # 参数需要一个,这里就是用户对象
# 计算园龄 以天为单位
curr_time = timezone.now()
create_time = user_obj.create_time
interval_timedelta = curr_time - create_time
age_days = interval_timedelta.days user_articles = models.Article.objects.filter(user=user_obj) # 分类
article_category_queryset = user_articles.values('category__pk').annotate(
count=Count('pk')).values('category__title', 'count') # 标签
# article_tag_queryset = user_articles.values('tag__pk').annotate(count=Count('pk')).values('tag__title', 'count') 这个计算出来是一个{'None': 6} 把None做了一个分类,下面分组方式就是一个[]的queryset。两种看来还是不一样。
article_tag_queryset = models.Tag.objects.filter(blog=user_obj.blog).values('pk').annotate(
count=Count('article__pk')).values('title', 'count')
print(article_tag_queryset) ### article_archive_queryset = user_articles.extra(select={'y_m': "date_format(create_time, '%%Y-%%m')"}).values(
'y_m').annotate(count=Count('pk')).values('y_m', 'count')
print(article_archive_queryset) ### # 这就是即插即用模版的上下文数据;通过一个user_obj参数我们就得到了要渲染到绑定模板中的上下文数据了。
return {'user_obj': user_obj,
'age_days': age_days,
'article_category_queryset': article_category_queryset,
'article_tag_queryset': article_tag_queryset,
'article_archive_queryset': article_archive_queryset,}
  1. 使用我们的inclusion_tag了,将my_tags_filters.py 放入到app目录下的templatetags目录下,然后再要使用的模板中进行{% load my_tags_filters %} 就可以了。然后使用tag {% stuff_list site_user %} 注意要传递我们的参数。

小结

  1. 从模板关系可以发散出:其实继承是非常好的内容重用减少冗余的设计,同时包含关系,也可以说是可插拔模式,插入即用,还可复用。包含关系多是用在画面构建方面,如设计图,html模板,画作等。
  2. 什么时候继承,什么时候包含:继承是框架,包含是插拔插件,包含是对继承的补充。继承局限更强,包含可跨继承。比如说,一个父模板,所有的子模版都是在继承父类的基础上修改覆盖。而包含可以在多个父模板中使用,突破了必须在一个父模板下的限制,但是包含不是随便插入,是类似插座和插头的概念,被插入模板需要提供给插入模板相关数据的。。
  3. 金字塔结构:通过模板引擎解析模板语法,根据传入上下文环境渲染出动态内容。模板语法。传入数据。

    tag和filter。模板关系。全局参数和传入参数。

    内置tag_filter和自定义tag_filter,tag功能强于filter。继承,include,inclusion_tag。request/auth。