Django商城的数据渲染与展示


目录:

商城的数据渲染与展示

​ Django作为Web框架,需要一种很便利的方法动态地生成HTML网页,因此有了模板这个概念。模板包含所需HTML的部分代码以及一些特殊语法,特殊语法用于描述如何将视图传递的数据动态插入HTML网页中。

​ Django可以配置一个或多个模板引擎(甚至是0个,如前后端分离,Django只提供API接口,无须使用模板引擎),模板引擎有Django模板语言(Django Template Language,DTL)和Jinja2。Django模板语言是Django内置的功能之一,它包含了模板上下文(亦可称为模板变量)、标签和过滤器,各个功能说明如下:

​ 模板上下文是以变量的形式写入模板文件里面,变量值由视图函数或视图类传递所得。标签是对模板上下文进行控制输出,比如模板上下文的判断和循环控制等。

​ 模板继承隶属于标签,它是将每个模板文件重复的代码抽取出来并写在一个共用的模板文件中,其他模板文件通过继承共用模板文件来实现完整的网页输出。过滤器是对模板上下文进行操作处理,比如模板上下文的内容截取、替换或格式转换等。

商城基础模板设计

从2.2节的网页静态界面看到,所有网页的顶部都是相同的,包含了商品搜索功能和网站导航,如图6-1所示。

​ 由于每个网页的顶部都相同,也就说这部分的HTML代码是完全相同的,因此我们可以将这部分代码单独抽取出来并放置在一个文件中,然后在每个网页的代码中调用该文件,这样符合代码重复使用的设计思路,便于日后的维护和管理。在项目babys的templates文件夹新建文件base.html,如图6-2所示。

​ 该文件用于存放每个网页顶部的HTML代码,其他HTML文件调用base.html文件是通过Django模板语法实现的。我们在PyCharm中打开base.html文件并编写以下代码:

在上述代码中,我们使用HTML代码注释对代码进行功能划分,如<!- …………①………… ->,一共分为5个部分,每个部分的说明如下:(1)标注①是使用Django内置模板语法static调用静态资源,比如{% static 'css/main.css'%}是调用babys\pstatic\css\main.css文件;在title标签中使用双中括号展示变量title的值,比如{{title}},变量title来自视图函数或视图类。

(2)标注②是使用Django内置模板语法url生成路由地址,比如{% url 'index:index' %}是生成网站首页的路由地址,其中index:index的第一个index代表babys文件夹urls.py定义的路由命名空间namespace;第二个代表项目应用index的urls.py定义的路由命名name。

(3)标注③是使用HTML的form标签生成网页表单,用于实现网页的商品搜索功能,表单以GET请求方式访问,访问地址暂设为首页,按照设计说明,访问地址应设为商品列表页,由于尚未开发商品列表页,因此暂时设为首页。

(4)标注④是使用Django内置模板语法if判断变量classContent的值,变量classContent由视图函数或视图类定义,如果变量classContent符合判断条件,则为当前标签添加样式class="active"。

(5)标注⑤是使用Django内置模板语法block设置文件调用入口,比如{% block content%}{% endblock content %},只需在其他的HTML文件中编写该语法即可实现调用,此处一共定义了三个调用接口:content、footer和script。

商城首页模板设计

​ 之前已在项目应用index的views.py定义视图函数indexView和视图类indexClassView,不管是视图函数或视图类,两者都定义了变量title、classContent、commodityInfos、clothes、food和goods,并且这些变量将在模板文件base.html和index.html里使用。

​ 我们打开模板文件index.html,在该文件中调用模板文件base.html,并使用视图函数indexView或视图类indexClassView定义的变量生成网页数据,模板文件index.html的代码如下:

根据模板文件index.html实现的功能划分,可分为5部分,每部分的功能说明如下:

​ (1)标注①是使用Django内置语法extends调用模板文件base.html,使base.html和index.html产生关联;使用{%load static %}读取静态资源,静态资源的加载过程由配置文件settings.py的配置属性INSTALLED_APPS的django.contrib.staticfiles完成。

(2)标注②是重写base.html定义的接口content,在网页中添加广告轮播功能,该功能暂以单张图片为主,图片来自静态资源文件pstatic/img/banner1.jpg。

(3)标注③是实现“今日必抢”的商品热销功能,该功能共分为2页,每页自动进行轮播展示,每一页展示4件热销商品信息,每个商品显示商品名称、商品主图、商品原价和折后价。

​ 由于“今日必抢”分为2页展示,因此HTML代码相应定义了2个<div class="item-box">标签,这2个标签使用了模板语法for和if对变量commodityInfos进行遍历和判断。

​ 变量commodityInfos共有8个商品信息,第一个div标签获取前4件商品信息,第二个标签获取最后4件商品信息,通过模板语法if控制变量commodityInfos的商品展示。

​ 变量commodityInfos每次遍历是获取某个商品的所有信息,并从这些信息中获取商品名称、商品主图、商品原价和折后价,详细说明如下:

​ ①{% url 'commodity:detail' c.id %}是使用商品的主键字段id生成对应的商品详细页的路由地址,当单击商品即可查看商品详细页。模板语法url是获取Django定义的路由列表,commodity:detail代表项目应用commodity的urls.py定义的路由detail;c.id代表当前遍历对象的主键id,并作为路由detail的路由变量。

​ ②{{ c.img.url }}代表当前遍历对象的字段img,由于变量commodityInfos是模型CommodityInfos的数据对象,字段img是FileField类型,因此可以使用c.img.url获取文件的路径地址,即商品主体的路径地址。

​ ③{{ c.name }}获取当前遍历对象的字段name,用于显示商品名称。

​ ④{{ c.discount|floatformat:'2' }}获取当前遍历对象的字段discount,用于显示商品折后价,并使用过滤器floatformat(过滤器也是模板语法)保留字段discount的2位小数。

​ ⑤{{ c.price|floatformat:'2' }}获取当前遍历对象的字段price,用于显示商品原价,并使用过滤器floatformat(过滤器也是模板语法)保留字段price的2位小数。

(4)标注④是实现某分类的商品热销功能,分别为“宝宝服饰”、“奶粉辅食”和“宝宝用品”,对应变量clothes、food和goods。每个分类只显示5件销量最高的商品信息,并展示每件商品的主图和设置商品详细页的路由地址,如变量clothes,每次遍历对象为c,其中{% url 'commodity:detail' c.id %}是生成商品详细页的路由地址,{{ c.img.url }}是生成商品主图的路径地址。

(5)标注⑤是重写base.html定义的接口footer和script。接口footer是生成首页的底部信息,效果如图6-3所示;接口script是编写“今日必抢”的页面轮播功能。

​ 综合上述,我们已完成共用模板文件base.html和首页模板文件index.html的代码编写,由于模板文件base.html和index.html使用了其他网页的路由地址,为了能使网站正常运行,分别对项目应用commodity和shopper的urls.py定义的路由信息取消代码注释(因为4.2节执行数据迁移的时候已对路由信息执行代码注释),并在views.py定义相应的视图函数,详细代码如下:

在运行网站首页之前,确保数据表commodity_types和commodity_commodityinfos存在测试数据,网站的数据主要是由Admin后台管理系统负责管理和维护,数据表的每个字段具有一定的数据格式,因此读者可以从源码文件chapter6\babys.sql导入测试数据。

除此之外,商品主图文件应放置在项目文件夹media/imgs。我们在PyCharm运行项目babys,控制台会提示警告信息,如图6-4所示,警告信息提示路由地址的首个字符为“/”,如无必要建议去掉字符“/”。警告信息通过简单规则检测路由地址是否符合命名标准,此处警告信息可以忽略,如果读者略有强迫症,亦可以自行重新定义路由信息。

最后在浏览器访问http://127.0.0.1:8000/即可看到商城首页,如图6-5所示。

模板上下文

​ 模板上下文(亦可以称为模板变量)是模板中基本的组成单位,上下文的数据由视图函数或视图类传递。它以{{ variable }}表示,variable是上下文的名称,它支持Python所有的数据类型,如字典、列表、元组、字符串、整型或实例化对象等。上下文的数据格式不同,在模板里的使用方式也有所差异,如下所示:

​ 从上述代码发现,如果上下文的数据带有属性,就可以在上下文的末端使用“.”来获取某个属性的值。比如上下文为字典或实例化对象,在上下文末端使用“.”并写入属性名称即可在网页上显示该属性的值;若上下文为元组或列表,则在上下文末端使用“.”并设置索引下标来获取元组或列表的某个元素值。

​ 如果视图没有为模板上下文传递数据或者模板上下文的某个属性、索引下标不存在,Django就会将其设为空值。例如获取variable2的属性age,由于上述的variable2并不存在属性age,因此网页上将会显示“

”。在PyCharm的Debug调试模式里分析Django模板引擎的运行过程。

​ 打开函数render所在的源码文件,变量content是模板文件的解析结果,它是由函数render_to_string完成解析过程的,如图6-6所示。

​ 想要分析Django模板引擎的解析过程,还需要从函数render_to_string深入分析,通过PyCharm打开函数render_to_string的源码信息,发现它调用了函数get_template或select_template,我们沿着函数调用的方向去探究整个解析过程,梳理函数之间的调用关系,最终得出模板解析过程,如图6-7所示。

整个解析过程调用了多个函数和类方法,每个函数和类方法在源码里都有功能注释,这里不再详细讲述,你可自行在源码里查阅。

内置标签及自定义

标签是对模板上下文进行控制输出,它是以{% tag %}表示的,其中tag是标签的名称,Django内置了许多模板标签,比如{%if %}(判断标签)、{% for %}(循环标签)或{% url %}(路由标签)等。

​ 内置的模板标签可以在Django源码(\django\template\defaulttags.py)里找到定义过程,每个内置标签都有功能注释和使用方法,这里只列举常用的内置标签,如表6-1所示。

在上述常用标签中,每个标签的使用方法都是各不相同的。下面通过简单的例子来进一步了解标签的使用方法:

在for标签中,模板还提供了一些特殊的变量来获取for标签的循环信息,变量说明如表6-2所示。

上述变量来自于forloop对象,该对象是在模板引擎解析for标签时生成的。通过简单的例子来进一步了解forloop的使用,例子如下:

除了使用内置的模板标签之外,我们还可以自定义模板标签。以MyDjango为例,在PyCharm新建项目MyDjango和创建项目应用index,然后在项目的根目录下创建新的文件夹,文件夹名称可自行命名,本示例命名为mydefined;

​ 然后在该文件夹下创建初始化文件__init__.py和templatetags文件夹,其中templatetags文件夹的命名是固定不变的;最后在templatetags文件夹里创建初始化文件__init__.py和自定义标签文件mytags.py,项目的目录结构如图6-8所示。

​ 由于在项目的根目录下创建了mydefined文件夹,因此在配置文件settings.py的属性INSTALLED_APPS里添加mydefined,否则Django在运行时无法加载mydefined文件夹的内容,配置信息如下:

​ 下一步在项目的mytags.py文件里自定义标签,我们将定义一个名为reversal的标签,它是将标签里的数据进行反转处理,定义过程如 下:

在mytags.py文件里分别定义了类ReversalNode和函数do_reversal,两者实现功能说明如下:

​ 函数do_reversal经过装饰器register.tag(name='reversal')处理,这是让函数执行模板标签注册,标签名称由装饰器参数name进行命名,如果没有设置参数name,就以函数名作为标签名称。函数名没有具体要求,一般以“do_标签名称”或“标签名称”作为命名规范。

​ 函数参数parse是解析器对象,当Django运行时,它将所有标签和过滤器进行加载并生成到parse对象,在解析模板文件里面的标签时,Django就会从parse对象查找对应的标签信息。

​ 函数参数token是模板文件使用标签时所传递的数据对象,主要包括标签名和数据内容。函数do_reversal对参数token使用split_contents()方法(Django的内置方法)进行取值处理,从中获取数据value,并将value传递给自定义模板节点类ReversalNode。类ReversalNode是将value执行字符串反转处理,并生成模板节点对象,用于模板引擎解析HTML语言。

为了验证自定义标签reversal的功能,我们分别在项目应用index的url.py、views.py和templates文件夹的模板文件index.html里编写以下代码:

​ 在模板文件index.html中使用自定义标签时,必须使用{% loadmytags %}将自定义标签文件导入,告知模板引擎从哪里查找自定义标签,否则无法识别自定义标签,并提示TemplateSyntaxError异常。运行MyDjango项目,在浏览器上访问127.0.0.1:8000,网页上会将“Django”反转显示,如图6-9所示。

综上所述,我们发现自定义标签reversal的定义方式与内置标签的定义方式是相同的,两者最大的区别在于:

自定义标签需要在项目里搭建目录环境。

在使用时需要在模板文件里导入自定义标签文件。

模板文件的继承关系

​ 模板继承是通过模板标签来实现的,其作用是将多个模板文件的共同代码集中在一个新的模板文件中,然后各个模板可以直接调用新的模板文件,从而生成HTML网页,这样可以减少模板之间重复的代码,范例如下:

上述代码是一个完整的模板文件,一个完整的模板通常有和两部分,而每个模板的和的内容都会有所不同,因此除了这两部分的内容之外,可以将其他内容写在共用模板文件里。

​ 以MyDjango为例,在PyCharm新建项目MyDjango和创建项目应用index,并且在templates文件夹里创建base.html文件,该文件作为共用模板,如图6-10所示,然后在base.html文件编写以下代码:

​ 在base.html的代码中看到,<title>写在模板标签{% blocktitle %}{% endblock %}里面,而<body>里的内容改为{%block body %}{% endblock %}。block标签是为其他模板文件调用时提供内容重写的接口,body是对这个接口进行命名。在一个模板中可以添加多个block标签,只要每个block标签的命名不相同即可。接着在模板index.html中调用共用模板base.html,代码如下:

模板index.html调用共用模板base.html的实质是由模板继承实现的,调用步骤如下:

在模板index.html中使用{% extends "base.html" %}来继承模板base.html的所有代码。通过使用标签{% block title %}{% block body %}来重写模板base.html的网页内容。如果没有使用标签block重写共用模板的内容,网页内容将由共用模板提供。比如模板index.html没有使用标签{% block title %}重新定义<title>,那么网页标题内容应由模板base.html设置的<title>提供。标签block必须使用{% endblock %}结束block标签。

​ 从模板index.html看到,模板继承与Python的类继承原理是一致的,通过继承方式使其具有父类的功能和属性,同时也可以通过重写来实现复杂多变的开发需求。为了验证模板继承是否正确,运行MyDjango并访问127.0.0.1:8000,查看网页标题(标题由模板base.html的<title>提供)和网页信息(重写模板base.html的{% blockbody %}),如图6-11所示。

内置过滤器及自定义

​ 过滤器主要是对上下文的内容进行操作处理,如替换、反序和转义等。通过过滤器处理上下文可以将其数据格式或内容转化为我们想要的显示效果,而且相应减少视图的代码量。过滤器的使用方法如下:

{{ variable | filter  }}

​ 若上下文设有过滤器,则模板引擎在解析上下文时,首先由过滤器filter处理上下文variable,然后将处理后的结果进行解析并显示在网页上。variable代表模板上下文,管道符号“|”代表当前上下文使用过滤器,filter代表某个过滤器。单个上下文可以支持多个过滤器同时使用,例如:

{{ variable | filter | lower  }}

​ 在使用的过程中,有些过滤器还可以传入参数,但仅支持传入一个参数。带参数的过滤器与参数之间使用冒号隔开,并且两者之间不能留有空格,例如:

{{ variable | date:'D d M'  }}

​ Django的内置过滤器可以在源码(\django\template\defaultfilters.py)里找到具体的定义过程,如表6-3所示。

​ 使用过滤器的过程中,上下文、管道符号“|”和过滤器之间没有规定使用空格隔开,但为了符合编码的规范性,建议使用空格隔开。倘若过滤器需要设置参数,过滤器、冒号和参数之间不能有空格,否则会提示异常信息,如图6-12所示。

​ 在实际开发中,如果内置过滤器的功能不太适合开发需求,我们可以自定义过滤器来解决问题。以6.4节的MyDjango为例,在mydefined的templatetags里创建myfilter.py文件,并在该文件里编写以下代码:

​ 过滤器与标签的自定义过程有相似之处,但过滤器的定义过程比标签更简单,只需定义相关函数即可。上述定义的过滤器是实现模板上下文的字符替换,定义过程说明如下:

函数do_replace由装饰器register.filter(name='replace')处理,对函数执行过滤器注册操作。装饰器参数name用于为过滤器命名,如果没有设置参数name,就以函数名作为过滤器名。函数名没有具体要求,一般以“do_过滤器名称”或“过滤器名称”作为命名规范。参数value代表使用当前过滤器的模板上下文,参数agrs代表过滤器的参数。函数将参数agrs以冒号进行分割,用于参数value(模板上下文)进行字符串替换操作,函数必须将处理结果返回,否则在使用过程中会出现异常信息。

​ 为了验证自定义过滤器replace的功能,将项目应用index的views.py和templates文件夹的模板文件index.html的代码进行修改:

模板文件index.html使用自定义过滤器时,需要使用{% loadmyfilter %}导入过滤器文件,这样模板引擎才能找到自定义过滤器,否则会提示TemplateSyntaxError异常。过滤器replace将模板上下文value进行字符串替换,将value里面的Python替换成Django,运行结果如图6-13所示。