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