personalBlog採用典型的博客佈局,左側三分之二爲主體,顯示文章列表、正文;右側三分之一爲邊欄,顯示分爲類列表、社交連接等。如今的工做是將HTML文件加工爲模板,並建立對應的表單類,在模板中渲染。javascript
並不是全部的頁面都須要添加邊欄,因此咱們不能把它放到基模板中。爲了不重複和易於維護,咱們把邊欄部分的代碼放到了局部模板_sidebar.html中。除了基模板base.html和存儲宏的macros.html模板,personalBlog程序的博客前臺使用的模板以下所示:css
index.html 主頁;html
about.html 關於頁面;java
_sidebar.html 邊欄;jquery
category.html 分類頁面;shell
post.html 文章頁面;bootstrap
login.html 登陸頁面;緩存
400.html;cookie
404.html;app
500.html;
personalBlog中將會用到400錯誤響應,表示無效請求,因此咱們添加了對應的錯誤頁面模板,在前面介紹工廠函數時咱們已經編寫了對應的錯誤處理函數。
博客後臺使用的模板以下所示:
manager_category.html 分類管理頁面;
new_category.html 新建分類頁面;
edit_category.html 編輯分類頁面;
manage_post.html 文章管理頁面;
new_post.html 新建文章頁面;
edit_post.html 編輯文章頁面;
settings.html 博客設置頁面;
manage_comment.html 評論管理頁面。
這些模板根據類別分別放到了templates目錄下的auth、admin、blog和errors子文件夾中,只有基模板在templates跟目錄內。基模板中定義了程序的基本樣式,包括導航欄和頁腳,以下所示。
personalblog/templates/base.html: 基模板
<!DOCTYPE html> <html lang="en"> <head> {% block head %} <meta charset="utf-8"> <meta name="viepoint" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>{% block title %}{% endblock title %} - PersonalBlog</title> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/%s.min.css' % request.cookies.get('theme', 'perfect_blue')) }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css"> {% endblock head %} </head> <body> {% block nav %} <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container"> <a class="navbar-brand" href="/">PersonalBlog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarColor01"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="/">Home</a>a> </li> </ul> </div> </div> </nav> {% endblock nav %} <main class="container"> {% block content %}{% endblock content %} {% block footer %} <footer> </footer> {% endblock footer %} </main> {% block scripts %} <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.2.1.slim.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/popper.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='jsj/bootstrap.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script> {{ moment.include_moment(local_js=url_for('static', filename='js/moment-with-locales.min.js')) }} {% endblock %} </body> </html>
除了基本的HTML結構,咱們還在基模板中加載了Favicon、自定義CSS、JavaScript文件,以及Bootstrap、Moment.js所需的資源文件,並建立了一些塊用於在字模板中繼承。
Bootstrap默認的樣式足夠沒關,但也許你已經感到厭倦了。Bootswatch(https://bootswatch.com/)以及StartBootstrap(https://startbootstrap.com/)等網站上提供了許多免費的Bootstrap主題文件,你能夠爲本身的程序選擇一個。你須要下載對應的CSS文件,保存到static/css目錄下,替換Bootstrap的CSS文件(bootstrap.min.css),清楚緩存並從新加載頁面便可看到新的樣式。
基模板中的一些代碼咱們會在下面詳細介紹,其餘模板的實現咱們則會在實現具體的功能時介紹。
在基模板的導航欄以及博客主頁中須要使用博客的標題、副標題等存儲在管理員對象上的數據,爲了不在每一個視圖函數中渲染模板時傳入這些數據,咱們在模板上下文函數中像模板上下文添加了管理員對象變量(admin)。另外,在多個頁面中都包含的邊欄中包含分類列表,咱們也把分類數據傳入到模板上下文中,以下所示。
personalBlog/__init__.py: 處理模板上下文
def create_app(config_name = None): if config_name is None: config_name = os.getenv('FLASK_CONFIG', 'development') app = Flask('personalBlog') app.config.from_object(config[config_name]) register_logging(app) # 註冊日誌處理器 register_extensions(app) # 註冊擴展(擴展初始化) register_blueprints(app) # 註冊藍本 register_commands(app) # 註冊自定義shell命令 register_errors(app) # 註冊錯誤處理函數 register_shell_context(app) # 註冊錯誤處理函數 register_template_context(app) # 註冊模板上下文處理函數 return app def register_template_context(app): @app.context_processor def make_template_context(): admin = Admin.query.first() categories = Category.query.order_by(Category.name).all() return dict(admin=admin, categories=categories)
獲取分類記錄時,咱們使用order_by()對記錄進行排序,傳入的規則是分類模型的name字段,這會對分類按字母順序排列。在邊欄模板(_sidebar.html)中,咱們迭代categories變量,渲染分類列表,以下所示:
personalBlog/templates/blog/_sidebar.html: 邊欄局部模板
{% if categories %} <div class="card mb-3"> <div class="card-header">Categories</div> <ul class="list-group list-group-flush"> {% for category in categories %} <li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> <a href="{{ url_for('blog.show_category', category_id=category.id) }}"> {{ category.name }} </a> <span class="badge badge-primary badge-pill">{{ category.posts|length }}</span> </li> {% endfor %} </ul> </div> {% endif %}
除了分類的名稱,咱們還在每個分類的右側顯示了與分類對應的文章總數,總數經過對分類對象的posts關係屬性添加length過濾器獲取。分類鏈接指向的blog.show_category視圖咱們將在後面介紹。
在基模板(base.html)和主頁模板(index.html)中,咱們能夠直接使用傳入的admin對象獲取博客的標題和副標題。以主頁模板爲例:
personalBlog/templates/blog/index.html:
{% extends 'base.html' %} {% from 'bootstrap/ pagination.html' import render_pager %} {% block title %}Home{% endblock %} {% block content %} <div class="page-header"> <h1 class="display-3">{{ admin.blog_title|default('Blog Title') }}</h1> <h4 class="text-muted"> {{ admin.blog_sub_title|default('Blog Subtitle' }}</h4> </div> <div class="row"> <div class="col-sm-8"> {% include 'blog/_posts.html' %} {% if posts %} <div class="page-footer">{{ render_page(pagination }}</div> {% endif %} </div> <div class="col-sm-4 sidebar"> {% include 'blog/_sidebar.html' %} </div> </div> {% endblock %}
導航欄上的按鈕應該再對應的頁面顯示激活狀態。舉例來講,當用戶單擊導航欄上的「關於」按鈕打開關於頁面時,「關於」按鈕應該高亮顯示。Bootstrap爲導航連接提供了一個active類來顯示激活狀態,咱們須要爲當前頁面對應的按鈕添加active類。
這個功能能夠經過判斷請求的端點來實現,對request對象調用endpoint屬性便可得到當前的請求端點。若是當前的端點與導航連接指向的端點相同,就爲它添加active類,顯示激活樣式,以下所示:
<li {% if request.endpoint == 'blog.index' %}class="active"{% endif %}><a href="{{ url_for('blog.index') }}">Home</a> </li>
有些教程中會使用endswith()方法來比較端點結尾。可是藍本擁有獨立的端點命名空間,即「<藍本命>.<端點名>」,不一樣的端點可能會擁有相同的結尾,好比blog.index和auth.index,這時使用endswith()會致使判斷錯誤,因此最妥善的作法是比較完整的端點值。
每一個導航按鈕的代碼都基本相同,後面咱們還會添加更多的導航連接。若是把這部分代碼放到宏裏,而後正在須要的地方根據指定的參數調用,就可讓模板更加整潔易讀了。下面是用於渲染導航連接的nav_link()宏:
{% macro nav_link(endpoint, text) -%} <li class="nav-item {% if request.endpoint and request.endpoint == endpoint %}active{% endif %}"> <a class="nav-link" href="{{ url_for(endpoint, **kwargs) }}">{{ text }}</a> </li> {%- endmacro %}
nav_link()宏接收完整的端點值和按鈕文本做爲參數,返回完整的導航連接。由於錯誤頁面沒有端點值,當渲染錯誤頁面的導航欄時,連接會出現request.endpoint爲None的錯誤。爲了不這個錯誤,須要在nav_link()宏的if判斷中額外添加一個判斷條件,確保端點不爲None。
藉助nav_link宏,渲染導航連接的代碼會變得很是簡單:
{% from 'macros.html' import nav_link %} ... <ul class="navbar-nav mr-auto"> {{ nav_link('index', 'Home') }} {{ nav_link('about', 'About') }} </ul> ...
不過在personalBlog的模板中咱們並無使用這個nav_link()宏,由於Bootstrap-Flask提供了一個更加完善的render_nav_item()宏,它的用法和咱們建立的nav_link()宏基本相同。這個宏能夠在模板中經過bootstrap/nav.html路徑導入,它支持的經常使用參數以下所示:
咱們目前的Flash消息應用了Bootstrap的alert-info樣式,單一的樣式使消息的類別和等級難以區分,更合適的作法是爲不一樣類別的消息應用不一樣的樣式。好比,當用戶訪問出錯時顯示一個黃色的警告消息;而不一樣的提示消息則使用藍色的默認樣式。bootstrap爲提醒消息(Alert)提供了8種基本的樣式類,即alert-primary、alert-secondary、alert-success、alert-danger、alert-warning、alert-light、alert-dark,以下所示:
要開啓消息分類,咱們首先要在消息渲染函數get_flashed_messages()中將with_categories參數設爲True。這時會把消息迭代爲一個相似於「(分類,消息)」的元祖,咱們使用消息分類字符來構建樣式類,以下所示:
personalBlog/templates/base.html: 渲染分類消息
<main class="container"> {% for message in get_flashed_messages(with_categories=True) %} <div class="alert alert-{{ message[0] }}" role="alert"> <button type="button" class="close" data-dismiss="alert">×</button> {{ message[1] }} </div> {% endfor %} {% block content %}{% endblock content %} {% block footer %} <footer> </footer> {% endblock footer %} </main>
樣式類經過「alert-{{ message[0] }}」形式構建,因此在調用flash()函數時,消息的類別做爲第二個參數傳入(primary、secondary、success、danger、warning、light、dark中的一個)。好比,下面的消息使用success分類,在渲染時會使用alert-success樣式類:
flash(u'發表成功!', 'success')
若是你不想使用Bootstrap,或是想添加一個自定義分類,能夠經過在css文件中添加新的消息樣式的css類實現。好比下面的CSS類實現了一個自定義消息樣式類alert-matrix:
.alert-matrix{ color: #66ff66; background-color: #000000; border-color: #ebccd1; }
在調用flash()函數時,則使用「matrix」做爲分類:
flash(u'發表成功!', 'matrix')