Python個人博客程序開發(fā)實例信息顯示
Python個人博客程序開發(fā)實例框架設(shè)計中,我們已經(jīng)完成了 數(shù)據(jù)庫設(shè)計、數(shù)據(jù)準備、模板架構(gòu)、表單設(shè)計、視圖函數(shù)設(shè)計、電子郵件支持 等總體設(shè)計的內(nèi)容,本篇博客將介紹博客前臺的實現(xiàn)。博客前臺需要開放給所有用戶,這里包括 顯示文章列表、博客信息、文章內(nèi)容和評論 等功能。
1.分頁顯示文章列表
為了在主頁顯示文章列表,我們要先在渲染主頁模板的 index
視圖中的數(shù)據(jù)庫中獲取所有文章記錄并傳入模板。
@blog_bp.route('/') def index():
在主頁模板(index.html
)中,我們使用 for 語句迭代所有文章記錄,依次渲染文章標題、發(fā)表時間和正文。
因為我們已經(jīng)生成了虛擬數(shù)據(jù),其中包含 50 篇文章?,F(xiàn)在運行程序,首頁會顯示一個很長的文章列表,根據(jù)創(chuàng)建的隨機日期排序,最新發(fā)表的排在上面。
1.1 獲取分頁記錄
如果所有的文章都在主頁顯示,無疑將會延長頁面加載時間。而且用戶需要拖動滾動條來瀏覽文章,太長的網(wǎng)頁會讓人感到沮喪,從而降低用戶體驗度。更好的做法是對文章數(shù)據(jù)進行分頁處理,每一頁只顯示少量的文章,并在頁面底部顯示一個分頁導(dǎo)航條,用戶通過單擊分頁導(dǎo)航上的頁數(shù)按鈕來訪問其他頁的文章。Flask-SQLAlchemy
提供了簡單的分頁功能,使用 paginate()
查詢方法可以分頁獲取文章記錄。
@blog_bp.route('/') def index(): page = request.args.get('page', 1, type=int) # 從查詢字符串獲取當前頁數(shù) per_page = current_app.config['BLUELOG_POST_PER_PAGE'] # 每頁數(shù)量 pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=per_page) # 分頁對象 posts = pagination.items # 當前頁數(shù)的記錄列表 return render_template('blog/index.html', pagination=pagination, posts=posts)
為了實現(xiàn)分頁,我們把之前的查詢執(zhí)行函數(shù) all()
換成了 paginate()
,它接收的兩個最主要的參數(shù)分別用來決定把記錄分成幾頁 per_page
,返回哪一頁的記錄 page
。page
參數(shù)代表當前請求的頁數(shù),我們從請求的查詢字符串(request.args
)中獲取,如果沒有設(shè)置則使用默認值 1,指定 int
類型可以保證在參數(shù)類型錯誤時使用默認值;per_page
參數(shù)設(shè)置每頁返回的記錄數(shù)量,為了方便統(tǒng)一修改,這個值從配置變量 BLUELOG_POST_PER_PAGE
獲取。
調(diào)用查詢方法 paginate()
會返回一個 Pagination
類實例,它包含分頁的信息,我們將其稱為分頁對象。對這個 pagination
對象調(diào)用 items
屬性會以列表的形式返回對應(yīng)頁數(shù)(默認為第一頁)的記錄。在訪問這個 URL 時,如果在 URL 后附加了查詢參數(shù) page
來指定頁數(shù),例如 http://localhost:5000/?page=2
,這時發(fā)起請求調(diào)用 items
變量將會獲得第二頁的 10 條記錄。
1.2 渲染分頁導(dǎo)航部件
我們不能讓用戶通過在 URL 中附加查詢字符串來實現(xiàn)分頁瀏覽,而是應(yīng)該在頁面底部提供一個分頁導(dǎo)航部件。這個分頁導(dǎo)航部件應(yīng)該包含上一頁、下一頁以及跳轉(zhuǎn)到每一頁的按鈕,每個按鈕都包含指向主頁的 URL,而且 URL 中都添加了對應(yīng)的查詢參數(shù) page
的值。使用 paginate
方法時,它會返回一個 Pagination
類對象,這個類包含很多用于實現(xiàn)分頁導(dǎo)航的方法和屬性,我們可以用它來獲取所有關(guān)于分頁的信息。
對于博客來說,設(shè)置一個簡單的包含上一頁、下一頁按鈕的分頁部件就足夠了。在視圖函數(shù)中,我們將分頁對象 pagination
傳入模板,然后在模板中使用它提供的方法和屬性來構(gòu)建分頁部件。
Bootstrap-Flask
已經(jīng)內(nèi)置了一個包含同樣功能,而且提供更多自定義設(shè)置的 render_pager()
宏。除此之外,它還提供了一個 render_pagination()
宏,可以用來渲染一個標準的 Bootstrap Pagination
分頁導(dǎo)航部件。render_pagination()
宏支持的常用參數(shù)如下表所示。
{% from 'bootstrap/pagination.html' import render_pager %} ... {{ render_pager(pagination) }}
2.顯示文章正文
文章頁面通過 show_post
視圖渲染,路由的 URL 規(guī)則中包含一個 post_id
變量,我們將 post_id
作為主鍵值來查詢對應(yīng)的文章對象,并傳入模板 post.html
中渲染。
@blog_bp.route('/post/<int:post_id>') def show_post(post_id): post = Post.query.get_or_404(post_id) return render_template('blog/post.html', post=post)
3.文章固定鏈接
在 Bluelog 程序中,文章的固定鏈接使用文章記錄的 id
值來構(gòu)建,比如 http://example.com/post/120
表示 id
為 120
的文章。如果你想要一個可讀性更強、對用戶和搜索引擎更友好的固定鏈接,可以考慮把標題轉(zhuǎn)換成英文或拼音,使用處理后的標題(即 slug)構(gòu)建固定鏈接,比如 http://example.com/post/hello-world
表示標題為 Hello World 的文章。
在 Bluelog 中,為了方便用戶獲取固定鏈接,我們在文章正文下面添加了一個分享按鈕,這個分享按鈕用來打開包含文章固定鏈接的模態(tài)框(Modal,又被譯為模態(tài)對話框)。
4.顯示分類文章列表
分頁處理在數(shù)量上讓文章更有組織性,但在文章內(nèi)容上,我們還需要添加分類來進一步組織文章。在渲染分類頁面的 show_category
視圖中,首先需要獲取對應(yīng)的分類記錄,然后獲取分類下的所有文章,進行分頁處理,最后將分類記錄 category
、分頁文章記錄 posts
和分頁對象 pagination
都傳入模板。
@blog_bp.route('/category/<int:category_id>') def show_category(category_id): category = Category.query.get_or_404(category_id) page = request.args.get('page', 1, type=int) per_page = current_app.config['BLUELOG_POST_PER_PAGE'] pagination = Post.query.with_parent(category).order_by(Post.timestamp.desc()).paginate(page, per_page) posts = pagination.items return render_template('blog/category.html', category=category, pagination=pagination, posts=posts)
我們需要獲取對應(yīng)分類下的所有文章,如果我們直接調(diào)用 category.posts
,會以列表的形式返回該分類下的所有文章對象,但是我們需要對這些文章記錄附加其他查詢過濾器和方法,所以不能使用這個方法。在上面的查詢中,我們?nèi)匀粡?post
模型出發(fā),使用 with_parent()
查詢方法傳入分類對象,最終篩選出屬于該分類的所有文章記錄。因為調(diào)用 with_parent()
查詢方法會返回查詢對象,所以我們可以繼續(xù)附加其他查詢方法來過濾文章記錄。
5.顯示評論列表
評論列表在顯示文章的頁面顯示,我們首先在顯示文章的 show_post
視圖中獲取對應(yīng)的文章,然后使用 Comment.query.with_parent(post)
方法獲取文章所屬的評論,并對其進行排序和分頁處理(per_page
的值通過配置變量 BLUELOG_COMMENT_PER_PAGE
獲?。?,獲取對應(yīng)頁數(shù)的評論記錄,最后傳入模板中。
@blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST']) def show_post(post_id): post = Post.query.get_or_404(post_id) page = request.args.get('page', 1, type=int) per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] pagination = Comment.query.with_parent(post).filter_by(reviewed=True).order_by(Comment.timestamp.asc()).paginate(page, per_page) comments = pagination.items return render_template('blog/post.html', post=post, pagination=pagination, form=form, comments=comments)
評論列表里僅需要列出通過審核的評論,所以在視圖函數(shù)里的數(shù)據(jù)庫查詢使用filter_by(reviewed=True)
來篩選出通過審核的評論記錄。雖然這個篩選也可以通過在模板中迭代評論列表時通過 reviewed
屬性實現(xiàn),但更合理的做法是盡量在視圖函數(shù)中實現(xiàn)邏輯操作。
評論是個人博客唯一的社交元素,故不僅要實現(xiàn)添加評論功能,還要在評論上添加回復(fù)按鈕,這樣可以使作者和評論者之間的雙向交流變成不同用戶之間的多維交流。在頁面中,評論有多種組織方式,比如將回復(fù)通過縮進嵌套到父評論下面的嵌套式、所有評論都對齊列出的平鋪式。Bluelog 中將使用平鋪式顯示評論列表,回復(fù)的評論會顯示一個回復(fù)標記,并在正文添加被回復(fù)的評論內(nèi)容。
我們在文章正文下方渲染評論列表和分頁導(dǎo)航部件。
評論的下方使用 Bootstrap-Flask 提供的 render_pagination()
來渲染一個標準分頁導(dǎo)航部件。在調(diào)用 render_pagination()
宏時,除了傳入分頁對象 pagination
外,我們還使用關(guān)鍵字 fragment
傳入了向分頁按鈕的鏈接中添加的 URL 片段(評論區(qū)元素的 id
為 comments
),以便單擊分頁按鈕后跳轉(zhuǎn)到頁面的評論部分,而不是停在頁面頂部。
6.發(fā)表評論與回復(fù)
因為評論表單要顯示在文章頁面的評論列表下方,所以評論數(shù)據(jù)的驗證和保存在 show_post
視圖中處理。
from bluelog.models import Comment from bluelog.forms import AdminCommentForm, CommentForm from bluelog.emails import send_new_comment_email @blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST']) def show_post(post_id): post = Post.query.get_or_404(post_id) page = request.args.get('page', 1, type=int) per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] pagination = Comment.query.with_parent(post).filter_by(reviewed=True).order_by(Comment.timestamp.asc()).paginate(page, per_page) comments = pagination.items if current_user.is_authenticated: # 如果當前用戶已登錄,使用管理員表單 form = AdminCommentForm() form.author.data = current_user.name form.email.data = current_app.config['BLUELOG_EMAIL'] form.site.data = url_for('.index') from_admin = True reviewed = True else: # 未登錄則使用普通表單 form = CommentForm() from_admin = False reviewed = False if form.validate_on_submit(): author = form.author.data email = form.email.data site = form.site.data body = form.body.data comment = Comment( author=author, email=email, site=site, body=body, from_admin=from_admin, post=post, reviewed=reviewed) db.session.add(comment) db.session.commit() if current_user.is_authenticated: # 根據(jù)登錄狀態(tài)不同顯示不同的提示信息 flash('Comment published.', 'success') else: flash('Thanks, your comment will be published after reviewed.', 'info') send_new_comment_email(post) # 發(fā)送郵件給管理員 return redirect(url_for('.show_post', post_id=post_id)) return render_template('blog/post.html', post=post, pagination=pagination, form=form, comments=comments)
current_user.is_authenticated
變量是從 Flask-Login
導(dǎo)入的,這個布爾值代表當前用戶的登錄狀態(tài)。
在處理評論時,我們主要需要對管理員和匿名用戶做出區(qū)分。首先通過 current_user.is_authenticated
屬性判斷當前用戶的認證狀態(tài):如果當前用戶已經(jīng)通過認證,那么就實例化管理員表單類 AdminCommentForm
,并把表單類中的姓名(author)、電子郵箱(email)、站點(site)這三個隱藏字段預(yù)先賦予正確的值,創(chuàng)建 from_admin
和 reviewed
變量,兩者均設(shè)為 True
;如果當前用戶是匿名用戶,則實例化普通的評論表單類 CommentForm
,創(chuàng)建 from_admin
和 reviewed
變量,兩者均設(shè)為 False
。
在表單提交并通過驗證后,我們像往常一樣獲取數(shù)據(jù)并保存。實例化 Comment
類時,傳入的 from_admin
和 reviewed
參數(shù)值使用對應(yīng)的變量。在保存評論記錄后,我們也需要根據(jù)當前用戶的認證狀態(tài)閃現(xiàn)不同的消息:如果當前用戶是管理員,發(fā)送 “提交評論成功”;如果是匿名用戶,則發(fā)送 “評論已進入審核隊列,審核通過后將顯示在評論列表中”,另外還要調(diào)用 send_new_comment_email()
函數(shù)向管理員發(fā)送一個提醒郵件,傳入文章對象(post)作為參數(shù)。
7.支持回復(fù)評論
我們已經(jīng)在數(shù)據(jù)庫中添加了評論與被回復(fù)評論的鄰接列表關(guān)系,那么如何實現(xiàn)回復(fù)功能呢?首先,需要知道當用戶單擊回復(fù)按鈕時,對應(yīng)的是哪一條評論??梢酝ㄟ^渲染一個隱藏的表單來存儲被回復(fù)評論的 id
,然后在用戶提交表單時再查找它。更簡單的做法是添加一個新的視圖,通過路由 URL 規(guī)則中的變量來傳遞這個值,我們在前面編寫評論列表模板時加入了回復(fù)按鈕。
<a class="btn btn-primary btn-sm" href="{{ url_for('.reply_comment',comment_id=comment.id) }}" rel="external nofollow" ></a>
@blog_bp.route('/reply/comment/<int:comment_id>') def reply_comment(comment_id): comment = Comment.query.get_or_404(comment_id) if not comment.post.can_comment: flash('Comment is disabled.', 'warning') return redirect(url_for('.show_post', post_id=comment.post.id)) return redirect( url_for('.show_post', post_id=comment.post_id, reply=comment_id, author=comment.author) + '#comment-form')
在這個視圖函數(shù)的 return
語句中,我們將程序重定向到原來的文章頁面。附加的關(guān)鍵字參數(shù)除了必須的 post_id
變量外,我們還添加了兩個多余的參數(shù):reply
和 author
,對應(yīng)的值分別是被回復(fù)評論的 id
和被回復(fù)評論的作者。url_for()
函數(shù)后附加的 URL 片段 #comment-form
用來將頁面焦點跳到評論表單的位置。
當使用 url_for()
函數(shù)構(gòu)建 URL 時,任何多余的關(guān)鍵字參數(shù)(即未在目標端點的 URL)都會被自動轉(zhuǎn)換為查詢字符串。當我們單擊某個評論右側(cè)的回復(fù)按鈕時,重定向后的頁面 URL 將會是http://localhost:5000/photo/23?id=4&author=peter#comment-form。
簡單來說,reply_comment
視圖扮演了中轉(zhuǎn)站的角色。它把通過 URL 變量接收的數(shù)據(jù)通過查詢字符串傳遞給了需要處理評論的視圖。
下一步,我們需要在回復(fù)狀態(tài)添加提示,在評論表單上方顯示一個回復(fù)提醒條,讓用戶知道自己現(xiàn)在處于回復(fù)狀態(tài)。我們在模板中評論表單上方通過 request.args
屬性獲取查詢字符串傳遞的信息以在回復(fù)提示條顯示被回復(fù)的用戶名稱。
{% if request.args.get('reply') %} <div class="alert alert-dark"> Reply to <strong>{{ request.args.get('author') }}</strong>: <a class="float-right" href="{{ url_for('.show_post',post_id=post.id) }}" rel="external nofollow" >Cancel</a> </div> {% endif %}
在 show_post
視圖中,處理評論的代碼也要進行相應(yīng)更新。
from bluelog.emails import send_new_reply_email @blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST']) def show_post(post_id): ... if form.validate_on_submit(): ... replied_id = request.args.get('reply') if replied_id: # 如果 URL 中 reply 查詢參數(shù)存在,那么說明是回復(fù) replied_comment= Comment.query.get_or_404(replied_id) comment.replied = replied_comment send_new_reply_email(replied_comment) # 發(fā)送郵件給被回復(fù)用戶 ...
新添加的 if
語句判斷請求 URL 的查詢字符串中是否包含 replied_id
的值,如果包含,則表示提交的評論是一條回復(fù)。我們根據(jù) relied_id
的值查找對應(yīng)的評論對象,然后存儲到被提交評論的 replied
屬性以建立數(shù)據(jù)庫關(guān)系,最后調(diào)用 send_new_reply_email()
函數(shù)發(fā)送提示郵件給被回復(fù)的評論的作者,傳入被回復(fù)評論作為參數(shù)。
8.網(wǎng)站主題切換
主題切換的功能很簡單,具體原理就是根據(jù)用戶的選擇加載不同的 CSS 文件。為了方便切換,我們在程序 static 目錄下的 CSS 文件夾中下載了兩個 Bootswatch 中的 Bootstrap 主題 CSS 文件,分別命名為 perfect_blue.min.css
和 black_swan.min.css
。
在配置文件中,我們新建一個變量,保存主題名稱(與 CSS 文件名相對應(yīng))和顯示名稱的映射字典:
# ('theme name', 'display name') BLUELOG_THEMES = {'perfect_blue': 'Perfect Blue', 'black_swan': 'Black Swan'}
為了讓這個功能能夠被所有用戶使用,我們將會把這個主題選項的值存儲在客戶端 cookie
中,新創(chuàng)建的 change_theme
視圖用于將主題名稱保存到名為 theme
的 cookie
中。
@blog_bp.route('/change-theme/<theme_name>') def change_theme(theme_name): if theme_name not in current_app.config['BLUELOG_THEMES'].keys(): abort(404) response = make_response(redirect_back()) response.set_cookie('theme', theme_name, max_age=30 * 24 * 60 * 60) return response
視圖函數(shù)中的 if
判斷用來確保 URL 變量中的主題名稱在支持的范圍內(nèi),如果出錯就返回 404
錯誤響應(yīng)。
我們使用 make_response()
方法生成一個重定向響應(yīng),這里使用了重定向到上一個頁面的重定向輔助函數(shù) redirect_back()
,因為主題切換下拉列表將添加在邊欄,用戶可能在任一頁面切換主題。通過對響應(yīng)對象 response
調(diào)用 set_cookie
設(shè)置 cookie
,將主題的名稱保存在名為 theme
的 cookie
中,我們使用 max_age
參數(shù)將 cookie
的過期時間設(shè)為 30 天。
在基模板的 < head> 元素內(nèi),我們根據(jù)用戶的 theme cookie
的值來加載對應(yīng)的 CSS 文件,如果 theme cookie
的值不存在,則會使用默認值 perfect_blue
,加載默認的 perfect_blue.min.css
。
<link rel="stylesheet" href="{{ url_for('static', filename='css/%s.min.css' % request.cookies.get('theme', 'perfect_blue')) }}" rel="external nofollow" type="text/css">
在邊欄最下方,我們添加用于設(shè)置主題的下拉選擇列表。
<div class="dropdown"> <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Change Theme </button> <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> {% for theme_name, display_name in config.BLUELOG_THEMES.items() %} <a class="dropdown-item" href="{{ url_for('blog.change_theme', theme_name=theme_name, next=request.full_path) }}" rel="external nofollow" > {{ display_name }}</a> {% endfor %} </div> </div>
在上面的 HTML 代碼中,我們通過迭代主題配置變量 BLUELOG_THEMES
,渲染出下拉選項,選項的 URL 指向 change_theme
端點,傳入主題名稱作為 URL 變量 theme_name
的值?,F(xiàn)在,如果在下拉框中選擇 Black Swan,theme cookie
的值就會被設(shè)為 black_swan
,頁面重載后會加載 black_swan.min.css
,從而起到切換主題的效果。
到此這篇關(guān)于Python個人博客程序開發(fā)實例信息顯示的文章就介紹到這了,更多相關(guān)Python個人博客內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Pandas數(shù)值排序 sort_values()的使用
本文主要介紹了Pandas數(shù)值排序 sort_values()的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2022-07-07Python調(diào)用graphviz繪制結(jié)構(gòu)化圖形網(wǎng)絡(luò)示例
今天小編就為大家分享一篇Python調(diào)用graphviz繪制結(jié)構(gòu)化圖形網(wǎng)絡(luò)示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11django中使用POST方法獲取POST數(shù)據(jù)
這篇文章主要介紹了django中使用POST方法獲取POST數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2019-08-08聊聊python 邏輯運算及奇怪的返回值(not,and,or)問題
在Python中,真值為假的對象,包括False,None,數(shù)字0,空字符串以及空的容器類型,除此以外的任何對象均為真,本文重點給大家介紹python 邏輯運算及奇怪的返回值(not,and,or)問題,感興趣的朋友一起看看吧2022-03-03Python詳細講解圖像處理的而兩種庫OpenCV和Pillow
這篇文章介紹了Python使用OpenCV與Pillow分別進行圖像處理的方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06Python?Excel操作從零學(xué)習掌握openpyxl用法
這篇文章主要為大家介紹了Python?Excel操作從零學(xué)習掌握openpyxl用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08