python实现矢量分级渲染_用 Python 撸一个 Web 服务器-第4章:动态渲染数据
上一章中為了盡快讓 Todo List 程序跑起來(lái),并沒(méi)有完全按照 MVC 模式編寫(xiě)程序。這一章就讓我們一起實(shí)現(xiàn)一個(gè)完整的 MVC 模式 Todo List 程序首頁(yè)。
使用模型操作數(shù)據(jù)
我們來(lái)分析下請(qǐng)求 Todo List 程序首頁(yè)時(shí),模型層需要做哪些事情。當(dāng)一個(gè)請(qǐng)求到達(dá)首頁(yè)視圖函數(shù) index 時(shí),它需要做兩件事情,首先調(diào)用模型層獲取全部的 todo 數(shù)據(jù),然后將 todo 數(shù)據(jù)動(dòng)態(tài)填充到 index.html 模板中。
調(diào)用模型層獲取全部的 todo 數(shù)據(jù),只需要在模型層編寫(xiě)讀取 todo/db/todo.json 文件數(shù)據(jù)的代碼即可。在這之前,我們需要先確定 todo 在文件中存儲(chǔ)的格式。
Todo List 程序中 todo 需要存儲(chǔ)的數(shù)據(jù)只有一個(gè),就是 todo 的內(nèi)容。所以我們可以將 todo 以如下格式存儲(chǔ)到 todo/db/todo.json 文件:// todo_list/todo/db/todo.json
[
{
"id": 1,
"content": "hello world"
},
{
"id": 2,
"content": "你好,世界!"
}
]
這是一個(gè)標(biāo)準(zhǔn)的 JSON 格式,每一個(gè)對(duì)象代表了一條 todo,content 字段即為 todo 內(nèi)容,id 作為每條數(shù)據(jù)的索引不會(huì)展示在頁(yè)面中,方便我們對(duì)數(shù)據(jù)進(jìn)行排序、快速查找等操作。
為了簡(jiǎn)化程序,我將數(shù)據(jù)存儲(chǔ)在 JSON 文件中而不是數(shù)據(jù)庫(kù)中。存儲(chǔ)到文件的格式多種多樣,但 JSON 格式是一種非常流行且友好的數(shù)據(jù)格式,在 Python 中也能夠很方便的對(duì) JSON 格式的文件進(jìn)行讀寫(xiě)操作。
注意:JSON 文件不支持注釋,所以如果你打算直接從上面示例中復(fù)制數(shù)據(jù)到 todo.json 文件時(shí),需要去掉頂部文件名注釋。
如果 todo/db/todo.json 文件內(nèi)容為空,使用 Python 讀取時(shí)會(huì)拋出 JSONDecodeError 異常,起碼要保證其內(nèi)部有一個(gè)空數(shù)組 [] 存在,才能正常讀取。
確定了 todo/db/todo.json 文件數(shù)據(jù)格式,就可以編寫(xiě)在模型層讀取 todo 數(shù)據(jù)的代碼了:# todo_list/todo/models.py
import os
import json
from todo.config import BASE_DIR
class Todo(object):
"""
Todo 模型類(lèi)
"""
def __init__(self, **kwargs):
self.id = kwargs.get('id')
self.content = kwargs.get('content', '')
@classmethod
def _db_path(cls):
"""獲取存儲(chǔ) todo 數(shù)據(jù)文件的絕對(duì)路徑"""
# 返回 'todo_list/todo/db/todo.json' 文件的絕對(duì)路徑
path = os.path.join(BASE_DIR, 'db/todo.json')
return path
@classmethod
def _load_db(cls):
"""加載 JSON 文件中所有 todo 數(shù)據(jù)"""
path = cls._db_path()
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
@classmethod
def all(cls, sort=False, reverse=False):
"""獲取全部 todo"""
# 這一步用來(lái)將所有從 JSON 文件中讀取的 todo 數(shù)據(jù)轉(zhuǎn)換為 Todo 實(shí)例化對(duì)象,方便后續(xù)操作
todo_list = [cls(**todo_dict) for todo_dict in cls._load_db()]
# 對(duì)數(shù)據(jù)按照 id 進(jìn)行排序
if sort:
todo_list = sorted(todo_list, key=lambda x: x.id, reverse=reverse)
return todo_list
定義 Todo 模型類(lèi)來(lái)操作 todo 數(shù)據(jù)。Todo 模型類(lèi)的 all 方法用來(lái)讀取全部的 todo 數(shù)據(jù),在其內(nèi)部將所有從 JSON 文件中讀取的 todo 數(shù)據(jù)轉(zhuǎn)換為 Todo 實(shí)例化對(duì)象并組裝成 list 返回。all 方法還可以對(duì)數(shù)據(jù)進(jìn)行排序,排序操作實(shí)際上轉(zhuǎn)發(fā)給了 Python 內(nèi)置的 sorted 函數(shù)來(lái)完成。
有了全部的 todo 數(shù)據(jù),下一步操作就是將 todo 數(shù)據(jù)動(dòng)態(tài)填充到 todo/templates/index.html 模板中。
使用模板引擎渲染 HTML
上一章實(shí)現(xiàn)的 Todo List 程序返回的首頁(yè)數(shù)據(jù)都是固定寫(xiě)死在 todo/templates/index.html 代碼中的。現(xiàn)在需要?jiǎng)討B(tài)填充 todo 內(nèi)容,我們需要學(xué)習(xí)一個(gè)新的概念叫作 模板渲染。
首先我們編寫(xiě)的 HTML 頁(yè)面不再是完全使用 HTML 的標(biāo)簽來(lái)編寫(xiě),而需要使用一些占位變量來(lái)替換需要?jiǎng)討B(tài)填充的部分,這樣編寫(xiě)出來(lái)的 HTML 頁(yè)面通常稱(chēng)為模板。將 HTML 模板讀取到內(nèi)存中,使用真實(shí)的 todo 數(shù)據(jù)來(lái)替換掉占位變量而獲得最終將要返回的字符串?dāng)?shù)據(jù),這個(gè)過(guò)程稱(chēng)為渲染。能夠?qū)崿F(xiàn)讀取 HTML 中的占位變量并正確替換為真實(shí)值的代碼稱(chēng)為模板引擎。
Todo List 程序首頁(yè)主體部分代碼如下:
Todo List
- Hello World
- 你好,世界!
其中每一個(gè) li 標(biāo)簽代表一條 todo,顯然 todo 的條數(shù)是不確定的,所以每一個(gè) li 標(biāo)簽都需要?jiǎng)討B(tài)生成。根據(jù)這段 HTML 代碼,可以編寫(xiě)出如下模板:
Todo List
{% for todo in todo_list %}
{{ todo.content }}{% endfor %}
這段模板代碼中只保留了一對(duì) li 標(biāo)簽,它被嵌套在 for 循環(huán)中,for 語(yǔ)句塊從 {% for todo in todo_list %} 開(kāi)始,到 {% endfor %} 結(jié)束。todo_list 變量是在模板渲染階段傳進(jìn)來(lái)的由所有 todo 對(duì)象組成的 list,list 中有多少個(gè)元素就會(huì)渲染多少個(gè) li 標(biāo)簽。for 循環(huán)內(nèi)部使用了循環(huán)變量 todo,{{ todo.content }} 表示獲取 todo 變量的 content 屬性,這與 Python 中獲取對(duì)象的屬性語(yǔ)法相同。
了解了模板語(yǔ)法,我們還需要有一個(gè)能夠讀懂模板語(yǔ)法的模板引擎。Todo List 程序的 HTML 模板只會(huì)用到 for 循環(huán)和模板變量這兩種語(yǔ)法,所以我們將要實(shí)現(xiàn)的模板引擎只需要能夠解析這兩種語(yǔ)法即可。# todo_list/todo/utils.py
class Template(object):
"""模板引擎"""
def __init__(self, text, context):
# 保存最終結(jié)果
self.result = []
# 保存從 HTML 中解析出來(lái)的 for 語(yǔ)句代碼片段
self.for_snippet = []
# 上下文變量
self.context = context
# 使用正則匹配出所有的 for 語(yǔ)句、模板變量
self.snippets = re.split('({{.*?}}|{%.*?%})', text, flags=re.DOTALL)
# 標(biāo)記是否為 for 語(yǔ)句代碼段
is_for_snippet = False
# 遍歷所有匹配出來(lái)的代碼片段
for snippet in self.snippets:
# 解析模板變量
if snippet.startswith('{{'):
if is_for_snippet is False:
# 去掉花括號(hào)和空格,獲取變量名
var = snippet[2:-2].strip()
# 獲取變量的值
snippet = self._get_var_value(var)
# 解析 for 語(yǔ)句
elif snippet.startswith('{%'):
# for 語(yǔ)句開(kāi)始代碼片段 -> {% for todo in todo_list %}
if 'in' in snippet:
is_for_snippet = True
self.result.append('{}')
# for 語(yǔ)句結(jié)束代碼片段 -> {% endfor %}
else:
is_for_snippet = False
snippet = ''
if is_for_snippet:
# 如果是 for 語(yǔ)句代碼段,需要進(jìn)行二次處理,暫時(shí)保存到 for 語(yǔ)句片段列表中
self.for_snippet.append(snippet)
else:
# 如果是模板變量,直接將變量值追加到結(jié)果列表中
self.result.append(snippet)
def _get_var_value(self, var):
"""根據(jù)變量名獲取變量的值"""
# 如果 '.' 不在變量名中,直接在上下文變量中獲取變量的值
if '.' not in var:
value = self.context.get(var)
# '.' 在變量名中(對(duì)象.屬性),說(shuō)明是要獲取對(duì)象的屬性
else:
obj, attr = var.split('.')
value = getattr(self.context.get(obj), attr)
# 保證返回的變量值為字符串
if not isinstance(value, str):
value = str(value)
return value
def _parse_for_snippet(self):
"""解析 for 語(yǔ)句片段代碼"""
# 保存 for 語(yǔ)句片段解析結(jié)果
result = []
if self.for_snippet:
# 解析 for 語(yǔ)句開(kāi)始代碼片段
# '{% for todo in todo_list %}' -> ['for', 'todo', 'in', 'todo_list']
words = self.for_snippet[0][2:-2].strip().split()
# 從上下文變量中獲取 for 語(yǔ)句中的可迭代對(duì)象
iter_obj = self.context.get(words[-1])
# 遍歷可迭代對(duì)象
for i in iter_obj:
# 遍歷 for 語(yǔ)句片段的代碼塊
for snippet in self.for_snippet[1:]:
# 解析模板變量
if snippet.startswith('{{'):
# 去掉花括號(hào)和空格,獲取變量名
var = snippet[2:-2].strip()
# 如果 '.' 不在變量名中,直接將循環(huán)變量 i 賦值給 snippet
if '.' not in var:
snippet = i
# '.' 在變量名中(對(duì)象.屬性),說(shuō)明是要獲取對(duì)象的屬性
else:
obj, attr = var.split('.')
# 將對(duì)象的屬性值賦值給 snippet
snippet = getattr(i, attr)
# 保證變量值為字符串
if not isinstance(snippet, str):
snippet = str(snippet)
# 將解析出來(lái)的循環(huán)變量結(jié)果追加到 for 語(yǔ)句片段解析結(jié)果列表中
result.append(snippet)
return result
def render(self):
"""渲染"""
# 獲取 for 語(yǔ)句片段解析結(jié)果
for_result = self._parse_for_snippet()
# 將渲染結(jié)果組裝成字符串并返回
return ''.join(self.result).format(''.join(for_result))
def render_template(template, **context):
"""渲染模板"""
# 讀取 'todo_list/todo/templates' 目錄下的 HTML 文件內(nèi)容
template_dir = os.path.join(BASE_DIR, 'templates')
path = os.path.join(template_dir, template)
with open(path, 'r', encoding='utf-8') as f:
# 將從 HTML 中讀取的內(nèi)容傳遞給模板引擎
t = Template(f.read(), context)
# 調(diào)用模板引擎的渲染方法,實(shí)現(xiàn)模板渲染
return t.render()
Template 類(lèi)就是我們?yōu)?Todo List 程序?qū)崿F(xiàn)的模板引擎。模板引擎的代碼有些復(fù)雜,我寫(xiě)了比較詳細(xì)的注釋來(lái)幫助你理解。模板渲染的大概過(guò)程如下:
首先實(shí)例化 Template 對(duì)象,Template 對(duì)象的初始化方法 __init__ 需要傳遞兩個(gè)參數(shù),分別是 HTML 字符串和保存了模板所需變量的 dict,在初始化時(shí)會(huì)解析出 HTML 中所有的 for 語(yǔ)句和模板變量,模板變量直接被替換為對(duì)應(yīng)的值,for 語(yǔ)句代碼段則被暫存起來(lái),等到需要真正渲染模板時(shí),調(diào)用模板引擎實(shí)例對(duì)象的 render 方法,完成 for 語(yǔ)句的解析和值替換,最終將渲染結(jié)果組裝成字符串并返回。
render_template 函數(shù)的代碼也做了相應(yīng)的調(diào)整,它的功能不再只是讀取 HTML 內(nèi)容,而是需要在內(nèi)部調(diào)用模板引擎獲取渲染結(jié)果。
對(duì)于基礎(chǔ)薄弱的讀者來(lái)說(shuō)可能模板引擎部分的代碼不太好理解,那么暫時(shí)先不必深究,你只需要知道模板引擎干了什么,明白它的原理無(wú)非是將 HTML 字符串中的模板語(yǔ)法全部找出來(lái),然后根據(jù)語(yǔ)法規(guī)則將其替換成真正的變量值,最后渲染成正確的 HTML。本質(zhì)上還是字符串的拼接,就像 Python 字符串的 format 方法一樣,它能夠找到字符串中的花括號(hào) {},然后替換成傳遞給它的參數(shù)值。
MVC 模式的 Todo List 程序首頁(yè)
我們已經(jīng)介紹了使用模型操作數(shù)據(jù)和使用模板引擎渲染 HTML,現(xiàn)在就可以用動(dòng)態(tài)渲染的 HTML 首頁(yè)替換之前的靜態(tài)首頁(yè)了。
修改首頁(yè) todo/templates/index.html 的 HTML 代碼為一個(gè)模板:
Todo ListTodo List
{% for todo in todo_list %}
{{ todo.content }}{% endfor %}
這里我暫時(shí)去掉了 HTML 頂部的 CSS 樣式,因?yàn)槲覀兊哪0逡娌恢С诌@種直接將 CSS 嵌入在 HTML 中的寫(xiě)法,之后我會(huì)介紹如何通過(guò) link 標(biāo)簽來(lái)引入外部樣式。
我們還要對(duì) index 視圖函數(shù)做些修改,在視圖函數(shù)內(nèi)部調(diào)用 Todo 模型的 all 方法來(lái)獲取所有 todo,然后傳遞給模板引擎對(duì) HTML 進(jìn)行渲染,得到最終結(jié)果。修改后的代碼如下:# todo_list/todo/controllers.py
from todo.utils import render_template
from todo.models import Todo
def index():
"""首頁(yè)視圖函數(shù)"""
# 倒序排序,最近添加的 todo 排在前面
todo_list = Todo.all(sort=True, reverse=True)
context = {
'todo_list': todo_list,
}
return render_template('index.html', **context)
在終端中進(jìn)入項(xiàng)目根目錄 todo_list/ 下,使用 Python 運(yùn)行 server.py 文件,將得到經(jīng)過(guò)動(dòng)態(tài)渲染的 Todo List 程序首頁(yè):
現(xiàn)在 Todo List 程序首頁(yè)已經(jīng)是動(dòng)態(tài)渲染的了,下一章我們就來(lái)解決樣式問(wèn)題。
聯(lián)系我:
總結(jié)
以上是生活随笔為你收集整理的python实现矢量分级渲染_用 Python 撸一个 Web 服务器-第4章:动态渲染数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql nosql sqlite_自
- 下一篇: svn版本库浏览器_svn:版本库xxx