欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python個(gè)人博客程序開發(fā)實(shí)例后臺編寫

 更新時(shí)間:2022年12月09日 10:47:33   作者:皮皮要HAPPY  
這篇文章主要介紹了怎樣用Python來實(shí)現(xiàn)一個(gè)完整的個(gè)人博客系統(tǒng),我們通過實(shí)操上手的方式可以高效的鞏固所學(xué)的基礎(chǔ)知識,感興趣的朋友一起來看看吧

本篇博客將是Python個(gè)人博客程序開發(fā)實(shí)例的最后一篇。本篇文章將會詳細(xì)介紹博客后臺的編寫。

為了支持管理員管理文章、分類、評論和鏈接,我們需要提供后臺管理功能。通常來說,程序的這一部分被稱為管理后臺、控制面板或儀表盤等。這里通常會提供網(wǎng)站的資源信息和運(yùn)行狀態(tài),管理員可以統(tǒng)一查看和管理所有資源。管理員面板通常會使用獨(dú)立樣式的界面,所以你可以為這部分功能的模板創(chuàng)建一個(gè)單獨(dú)的基模板。為了保持簡單,Bluelog 的管理后臺和前臺頁面使用相同的樣式。

Bluelog 的管理功能比較簡單,我們沒有提供一個(gè)管理后臺主頁,取而代之的是,我們在導(dǎo)航欄上添加鏈接作為各個(gè)管理功能的入口。

{% from 'bootstrap/nav.html' import render_nav_item %}
...
<ul class="nav navbar-nav navbar-right">
    {% if current_user.is_authenticated %}
        <li class="nav-item dropdown">
            <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  class="nav-link dropdown-toggle" data-toggle="dropdown" role="button"
               aria-haspopup="true"
               aria-expanded="false">
                New <span class="caret"></span>
            </a>
            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                <a class="dropdown-item" href="{{ url_for('admin.new_post') }}" rel="external nofollow" >Post</a>
                <a class="dropdown-item" href="{{ url_for('admin.new_category') }}" rel="external nofollow" >Category</a>
                <a class="dropdown-item" href="{{ url_for('admin.new_link') }}" rel="external nofollow" >Link</a>
            </div>
        </li>
        <li class="nav-item dropdown">
            <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  class="nav-link dropdown-toggle" data-toggle="dropdown" role="button"
               aria-haspopup="true"
               aria-expanded="false">
                Manage <span class="caret"></span>
                {% if unread_comments %}
                    <span class="badge badge-success">new</span>
                {% endif %}
            </a>
            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                <a class="dropdown-item" href="{{ url_for('admin.manage_post') }}" rel="external nofollow" >Post</a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_category') }}" rel="external nofollow" >Category</a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_comment') }}" rel="external nofollow" >
                    Comment
                    {% if unread_comments %}
                        <span class="badge badge-success">{{ unread_comments }}</span>
                    {% endif %}
                </a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_link') }}" rel="external nofollow" >Link</a>
            </div>
        </li>
        {{ render_nav_item('admin.settings', 'Settings') }}
    {% endif %}
</ul>

通過添加if判斷,使這些鏈接均在 current_user.is_authenticatedTrue,即用戶已登入的情況下才會渲染。Manage 下拉按鈕中包含管理文章、分類、評論的鏈接,New 下拉按鈕包含創(chuàng)建文章、分類的鏈接。

當(dāng)博客中有用戶提交了新的評論時(shí),我們需要在導(dǎo)航欄中添加提示。為此,我們在 Manage 按鈕的文本中添加了一個(gè) if 判斷,如果 unread_comments 變量的值不為 0,就渲染一個(gè) new 標(biāo)記(badge)。相同的,在下拉列表中的“管理評論”鏈接文本中,如果 unread_comments 變量不為 0,就渲染出待審核的評論數(shù)量標(biāo)記。

這個(gè) unread_comments 變量存儲了待審核評論的數(shù)量,為了能夠在基模板中使用這個(gè)變量,我們需要在 bluelog//init.py 中創(chuàng)建的模板上下文處理函數(shù)中查詢未審核的評論數(shù)量,并傳入模板上下文。這個(gè)變量只在管理員登錄后才可使用,所以通過添加if判斷實(shí)現(xiàn)根據(jù)當(dāng)前用戶的認(rèn)證狀態(tài)來決定是否執(zhí)行查詢。

@app.context_processor
def make_template_context():
	...
	if current_user.is_authenticated
		unread_comments = Comment.query.filter_by(reviewed=False).count()
	else:
		unread_comments = None
	return dict(unread_comments=unread_comments)

1.文章管理

我們要分別為分類、文章和評論創(chuàng)建單獨(dú)的管理頁面,這些內(nèi)容基本相同,因此本節(jié)會以文章的管理主頁作為介紹的重點(diǎn)。另外,分類的創(chuàng)建、編輯和刪除與文章的創(chuàng)建、編輯和刪除實(shí)現(xiàn)代碼基本相同,這里也將以文章相關(guān)操作的實(shí)現(xiàn)作為介紹重點(diǎn)。

1.1 文章管理主頁

我們在渲染文章管理頁面的 manage_post 視圖時(shí),要查詢所有文章記錄,并進(jìn)行分頁處理,然后傳入模板中。

@admin_bp.route('/post/manage')
@login_required
def manage_post():
    page = request.args.get('page', 1, type=int)
    pagination = Post.query.order_by(Post.timestamp.desc()).paginate(
        page, per_page=current_app.config['BLUELOG_MANAGE_POST_PER_PAGE'])
    posts = pagination.items
    return render_template('admin/manage_post.html', page=page, pagination=pagination, posts=posts)

在這個(gè)視圖渲染的 manage_category.html 模板中,我們以表格的形式顯示文章列表,依次渲染出文章的標(biāo)題、所屬的分類、發(fā)表時(shí)間、文章字?jǐn)?shù)、包含的評論數(shù)量以及相應(yīng)的操作按鈕。

{% extends 'base.html' %}
{% from 'bootstrap/pagination.html' import render_pagination %}
{% block title %}Manage Posts{% endblock %}
{% block content %}
<div class="page-header">
    <h1>Posts
        <small class="text-muted">{{ pagination.total }}</small>
        <span class="float-right"><a class="btn btn-primary btn-sm"
                                     href="{{ url_for('.new_post') }}" rel="external nofollow" >New Post</a></span>
    </h1>
</div>
{% if posts %}
<table class="table table-striped">
    <thead>
    <tr>
        <th>No.</th>
        <th>Title</th>
        <th>Category</th>
        <th>Date</th>
        <th>Comments</th>
        <th>Words</th>
        <th>Actions</th>
    </tr>
    </thead>
    {% for post in posts %}
    <tr>
        <td>{{ loop.index + ((page - 1) * config.BLUELOG_MANAGE_POST_PER_PAGE) }}</td>
        <td><a href="{{ url_for('blog.show_post', post_id=post.id) }}" rel="external nofollow" >{{ post.title }}</a></td>
        <td><a href="{{ url_for('blog.show_category', category_id=post.category.id) }}" rel="external nofollow" >{{ post.category.name }}</a>
        </td>
        <td>{{ moment(post.timestamp).format('LL') }}</td>
        <td><a href="{{ url_for('blog.show_post', post_id=post.id) }}#comments" rel="external nofollow" >{{ post.comments|length }}</a></td>
        <td>{{ post.body|striptags|length }}</td>
        <td>
            <form class="inline" method="post"
                  action="{{ url_for('.set_comment', post_id=post.id, next=request.full_path) }}">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
                <button type="submit" class="btn btn-warning btn-sm">
                    {% if post.can_comment %}Disable{% else %}Enable{% endif %} Comment
                </button>
            </form>
            <a class="btn btn-info btn-sm" href="{{ url_for('.edit_post', post_id=post.id) }}" rel="external nofollow" >Edit</a>
            <form class="inline" method="post"
                  action="{{ url_for('.delete_post', post_id=post.id, next=request.full_path) }}">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
                <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');">Delete
                </button>
            </form>
        </td>
    </tr>
    {% endfor %}
</table>
<div class="page-footer">{{ render_pagination(pagination) }}</div>
{% else %}
<div class="tip"><h5>No posts.</h5></div>
{% endif %}
{% endblock %}

每一個(gè)文章記錄的左側(cè)都顯示一個(gè)序號標(biāo)記。如果單獨(dú)使用 loop.index 變量渲染數(shù)量標(biāo)記,那么每一頁的文章記錄都將從 1 到 15 重復(fù)(配置變量 BLUELOG_MANAGE_POST_PER_PAGE 的值),因?yàn)槊恳豁撟疃嘀挥?15 條文章記錄。正確的評論數(shù)量標(biāo)記可以通過 “當(dāng)前迭代數(shù) + ((當(dāng)前頁數(shù) - 1) × 每頁記錄數(shù))” 的形式獲取。

刪除操作會修改數(shù)據(jù)庫,為了避免 CSRF 攻擊,我們需要使用表單 form 元素來提交 POST 請求,表單中必須使用 CSRFProtect 提供的 csrf_token() 函數(shù)渲染包含 CSRF 令牌的隱藏字段,字段的 name 值需要設(shè)為 csrf_token。另外,用來刪除文章的視圖也需要設(shè)置僅監(jiān)聽 POST 方法。

文章的編輯和刪除按鈕并排顯示,由于兩個(gè)按鈕離得很近,可能會導(dǎo)致誤操作。而且一旦單擊刪除按鈕,文章就會立刻被刪除,故我們需要添加一個(gè)刪除確認(rèn)彈窗。對于我們的程序來說,使用瀏覽器內(nèi)置的確認(rèn)彈窗已經(jīng)足夠,只需要在 button 標(biāo)簽中添加一個(gè) onclick 屬性,設(shè)置為一行 JavaScript 代碼:return confirm(),在 confirm() 中傳入提示信息作為參數(shù)。運(yùn)行程序后,當(dāng)用戶單擊文章下方的刪除按鈕,會執(zhí)行這行代碼,跳出包含傳入信息的確認(rèn)彈窗,這會打開瀏覽器內(nèi)置的 confirm 彈窗組件。

當(dāng)用戶單擊確認(rèn)后,confirm() 會返回 True,這時(shí)才會訪問鏈接中的 URL。除了管理頁面,我們還在文章內(nèi)容頁面添加了編輯和刪除按鈕。文章管理頁面和文章正文頁面都包含刪除按鈕,但卻存在不同的行為:對于文章管理頁面來說,刪除文章后我們希望仍然重定向回文章管理頁面,所以對應(yīng)的 URL 中的 next 參數(shù)使用 request.full_path 獲取當(dāng)前路徑;而對于文章正文頁面,刪除文章后,原 URL 就不再存在,這時(shí)需要重定向到主頁,所以將 next 設(shè)為主頁 URL。

1.2 創(chuàng)建文章

博客最重要的功能就是撰寫文章,new_post 視圖負(fù)責(zé)渲染創(chuàng)建文章的模板,并處理頁面中表單提交的 POST 請求。

from bluelog.forms import PostForm
from bluelog.models import Post, Category
@admin_bp.route('/post/new', methods=['GET', 'POST'])
@login_required
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        title = form.title.data
        body = form.body.data
        category = Category.query.get(form.category.data)
        post = Post(title=title, body=body, category=category)
        # same with:
        # category_id = form.category.data
        # post = Post(title=title, body=body, category_id=category_id)
        db.session.add(post)
        db.session.commit()
        flash('Post created.', 'success')
        return redirect(url_for('blog.show_post', post_id=post.id))
    return render_template('admin/new_post.html', form=form)

這里也可以直接通過將表單 category 字段的值賦給 Post 模型的外鍵字段 Post.category_id 來建立關(guān)系,即 category_id=form.category.data。在程序中,為了便于理解,均使用將具體對象賦值給關(guān)系屬性的方式來建立關(guān)系。

表單驗(yàn)證失敗會重新渲染模板,并顯示錯誤消息。表單驗(yàn)證成功后,我們需要保存文章數(shù)據(jù)。各個(gè)表單字段的數(shù)據(jù)都通過 data 屬性獲取,創(chuàng)建一個(gè)新的 Post 實(shí)例作為文章對象,將表單數(shù)據(jù)賦值給對應(yīng)的模型類屬性。另外,因?yàn)楸韱畏诸愖侄危?code>PostForm.category)的值是分類記錄的 id 字段值,所以我們需要從 Category 模型查詢對應(yīng)的分類記錄,然后通過 Post 模型的 category 關(guān)系屬性來建立關(guān)系,即 category=Category.query.get(form.category.data)。將新創(chuàng)建的 post 對象添加到新數(shù)據(jù)庫會話并提交后,使用 redirect() 函數(shù)重定向到文章頁面,將新創(chuàng)建的 post 對象的 id 作為 URL 變量傳入 url_for() 函數(shù)。

當(dāng)請求類型為 GET 時(shí),這個(gè)視圖會實(shí)例化用于創(chuàng)建文章的 PostForm 表單,并將其傳入模板。在渲染的模板 new_post.html 中,我們使用 Bootstrap-Flask 提供的 render_form() 宏渲染表單。因?yàn)?PostForm 表單類中使用了擴(kuò)展 Flask-CKEditor 提供的 CKEditor 字段,所以在模板中需要加載 CKEditor 資源,并使用 ckeditor.config() 方法加載 CKEditor 配置。

{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}
{% block title %}New Post{% endblock %}
{% block content %}
    <div class="page-header">
        <h1>New Post</h1>
    </div>
    {{ render_form(form) }}
{% endblock %}
{% block scripts %}
    {{ super() }}
    <script type="text/javascript" src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}"></script>
    {{ ckeditor.config(name='body') }}
{% endblock %}

CKEditor 的資源包我們已經(jīng)下載并放到 static 目錄下,這里只需要加載 ckeditor.js 文件即可。因?yàn)?CKEditor 編輯器只在創(chuàng)建或編輯文章的頁面使用,所以可以只在這些頁面加載對應(yīng)的資源,而不是在基模板中加載。

1.3 編輯與刪除

編輯文章的具體實(shí)現(xiàn)和撰寫新文章類似,這兩個(gè)功能使用同一個(gè)表單類 PostForm,而且視圖函數(shù)和模板文件都基本相同,主要的區(qū)別是我們需要在用戶訪問編輯頁面時(shí)把文章數(shù)據(jù)預(yù)先放置到表單中。

@admin_bp.route('/post/<int:post_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_post(post_id):
    form = PostForm()
    post = Post.query.get_or_404(post_id)
    if form.validate_on_submit():
        post.title = form.title.data
        post.body = form.body.data
        post.category = Category.query.get(form.category.data)
        db.session.commit()
        flash('Post updated.', 'success')
        return redirect(url_for('blog.show_post', post_id=post.id))
    form.title.data = post.title
    form.body.data = post.body
    form.category.data = post.category_id
    return render_template('admin/edit_post.html', form=form)

edit_post 視圖的工作可以概括為:首先從數(shù)據(jù)庫中獲取指定 id 的文章。如果是 GET 請求,使用文章的數(shù)據(jù)作為表單數(shù)據(jù),然后渲染模板。如果是 POST 請求,即用戶單擊了提交按鈕,則根據(jù)表單的數(shù)據(jù)更新文章記錄的數(shù)據(jù)。

和保存文章時(shí)的做法相反,通過把數(shù)據(jù)庫字段的值分別賦給表單字段的數(shù)據(jù),在渲染表單時(shí),這些值會被填充到對應(yīng)的 input 標(biāo)簽的 value 屬性中,從而顯示在輸入框內(nèi)。需要注意,因?yàn)楸韱沃械姆诸愖侄问谴鎯Ψ诸愑涗浀?id 值,所以這里使用 post.category_id 作為 form.category.data 的值。

通過 delete_post 視圖可以刪除文章,我們首先從數(shù)據(jù)庫中獲取指定 id 的文章記錄,然后使 db.session.delete() 方法刪除記錄并提交數(shù)據(jù)庫。

from bluelog.utils import redirect_back
@admin_bp.route('/post/<int:post_id>/delete', methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    db.session.delete(post)
    db.session.commit()
    flash('Post deleted.', 'success')
    return redirect_back()

這個(gè)視圖通過設(shè)置 methods 參數(shù)實(shí)現(xiàn)僅允許 POST 方法。因?yàn)樵谖恼鹿芾眄撁婧臀恼聝?nèi)容頁面都包含刪除按鈕,所以這里使用 redirect_back() 函數(shù)來重定向回上一個(gè)頁面。

2.評論管理

在編寫評論管理頁面前,我們要在文章內(nèi)容頁面的評論列表中添加刪除按鈕。

<div class="float-right">
    <a class="btn btn-light btn-sm"
       href="{{ url_for('.reply_comment', comment_id=comment.id) }}" rel="external nofollow" >Reply</a>
    {% if current_user.is_authenticated %}
        <a class="btn btn-light btn-sm" href="mailto:{{ comment.email }}" rel="external nofollow" >Email</a>
        <form class="inline" method="post"
              action="{{ url_for('admin.delete_comment', comment_id=comment.id, next=request.full_path) }}">
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
            <button type="submit" class="btn btn-danger btn-sm"
                    onclick="return confirm('Are you sure?');">Delete
            </button>
        </form>
    {% endif %}
</div>

因?yàn)閯h除按鈕同時(shí)會被添加到評論管理頁面的評論列表中,所以我們在刪除評論的 URL 后附加了 next 參數(shù),用于重定向回上一個(gè)頁面。如果當(dāng)前用戶是管理員,我們還會顯示除了管理員發(fā)表的評論以外的評論者郵箱,渲染成 mailto 鏈接。

和文章管理頁面類似,在評論管理頁面我們也會將評論以表格的形式列出,這里不再給出具體代碼。和文章管理頁面相比,評論管理頁面主要有兩處不同:添加批準(zhǔn)評論的按鈕以及在頁面上提供評論數(shù)據(jù)的篩選功能,我們將重點(diǎn)介紹這兩個(gè)功能的實(shí)現(xiàn)。在前臺頁面,除了評論刪除按鈕,我們還要向管理員提供關(guān)閉評論的功能,我們先來看看評論開關(guān)的具體實(shí)現(xiàn)。

2.1 關(guān)閉評論

盡管交流是社交的基本要素,但有時(shí)作者也希望不被評論打擾。為了支持評論開關(guān)功能,我們需要在 Post 模型中添加一個(gè)類型為 db.Booleancan_comment 字段,用來存儲是否可以評論的布爾值,默認(rèn)值為 True

class Post(db.Model):
	...
	can_comment = db.Column(db.Boolean, default=True)

然后我們需要在模板中評論區(qū)右上方添加一個(gè)開關(guān)按鈕:

{% if current_user.is_authenticated %}
    <form class="float-right" method="post"
          action="{{ url_for('admin.set_comment', post_id=post.id, next=request.full_path) }}">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
        <button type="submit" class="btn btn-warning btn-sm">
            {% if post.can_comment %}Disable{% else %}Enable{% endif %} Comment
        </button>
    </form>
{% endif %}

在管理文章的頁面,我們還在每一個(gè)文章的操作區(qū)添加了關(guān)閉和開啟評論的按鈕,渲染的方式基本相同,具體可以到源碼倉庫中查看。

<button type="submit" class="btn btn-warning btn-sm">
    {% if post.can_comment %}Disable{% else %}Enable{% endif %} Comment
</button>

另外,在設(shè)置回復(fù)評論狀態(tài)的 reply_comment 視圖中,我們在開始添加一個(gè) if 判斷,如果對應(yīng)文章不允許評論,那么就直接重定向回文章頁面。

@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')

我們根據(jù) post.can_comment 的值來渲染不同的按鈕文本和表單 action 值。因?yàn)檫@個(gè)功能很簡單,所以兩個(gè)按鈕指向同一個(gè) URL,URL 對應(yīng)的 set_comment 視圖如下所示。

@admin_bp.route('/post/<int:post_id>/set-comment', methods=['POST'])
@login_required
def set_comment(post_id):
    post = Post.query.get_or_404(post_id)
    if post.can_comment:
        post.can_comment = False
        flash('Comment disabled.', 'success')
    else:
        post.can_comment = True
        flash('Comment enabled.', 'success')
    db.session.commit()
    return redirect_back()

我們當(dāng)然可以分別創(chuàng)建一個(gè) enable_comment()disable_comment() 視圖函數(shù)來開啟和關(guān)閉評論,但是因?yàn)楸容^簡單,所以我們可以將這兩個(gè)操作統(tǒng)一在 set_comment() 視圖函數(shù)中完成。在這個(gè)視圖函數(shù)里,我們首先獲取文章對象,然后根據(jù)文章的 can_comment 的值來設(shè)置相反的布爾值。

最后,我們還需要在評論表單的渲染代碼前添加一個(gè)判斷語句。如果管理員關(guān)閉了當(dāng)前博客的評論,那么一個(gè)相應(yīng)的提示會取代評論表單,顯示在評論區(qū)底部。

{% from 'bootstrap/form.html' import render_form %}
...
{% if post.can_comment %}
    <div id="comment-form">
        {{ render_form(form, action=request.full_path) }}
    </div>
{% else %}
    <div class="tip"><h5>Comment disabled.</h5></div>
{% endif %}

為了避免表單提交后因?yàn)?URL 中包含 URL 片段而跳轉(zhuǎn)到頁面的某個(gè)位置(Html 錨點(diǎn)),這里顯式地使用 action 屬性指定表單提交的目標(biāo) URL,使用 request.full_path 獲取不包含 URL 片段的當(dāng)前 URL(但包含我們需要的查詢字符串)。

2.2 評論審核

對于沒有通過審核的評論,在評論表格的操作列要添加一個(gè)批準(zhǔn)按鈕。如果評論對象的 reviewed 字段值為 False,則顯示 “批準(zhǔn)” 按鈕,并將該行評論以橙色背景顯示(添加 table-warning 樣式類)。

<td>
    {% if not comment.reviewed %}
        <form class="inline" method="post"
              action="{{ url_for('.approve_comment', comment_id=comment.id, next=request.full_path) }}">
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
            <button type="submit" class="btn btn-success btn-sm">Approve</button>
        </form>
    {% endif %}
    ...
</td>

因?yàn)檫@個(gè)操作會修改數(shù)據(jù),我們同樣需要使用表單 form 元素來提交 POST 請求。批準(zhǔn)按鈕指向的 approve_comment 視圖僅監(jiān)聽 POST 方法。

@admin_bp.route('/comment/<int:comment_id>/approve', methods=['POST'])
@login_required
def approve_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    comment.reviewed = True
    db.session.commit()
    flash('Comment published.', 'success')
    return redirect_back()

approve_comment 視圖中,我們將對應(yīng)的評論記錄的 reviewed 字段設(shè)為 Ture,表示通過審核。通過審核后的評論會顯示在文章頁面下方的評論列表中。雖然評論的批準(zhǔn)功能只在管理評論頁面提供,我們?nèi)匀辉谶@里使用 redirect_back() 函數(shù)返回上一個(gè)頁面,這是因?yàn)樵u論管理頁面根據(jù)查詢參數(shù) filter 的值會顯示不同的過濾結(jié)果,而在 “全部” 和 “未讀” 結(jié)果中的未讀評論記錄都會有 “Approve” 按鈕,所以我們需要重定向回正確的過濾分類下。

為了正確返回上一個(gè)頁面,在表單 action 屬性中的 URL 后需要將 next 查詢參數(shù)的值設(shè)為 request.full_path 以獲取包含查詢字符串的完整路徑。

2.3 篩選評論

因?yàn)樵u論的數(shù)據(jù)比較復(fù)雜,我們需要在管理頁面提供評論的篩選功能。評論主要分為三類:所有評論、未讀評論和管理員發(fā)布的評論。我們將使用查詢參數(shù) filter 傳入篩選的評論類型,這三種類型分別使用 allunreadadmin 表示。在渲染評論管理主頁的 manage_comment 視圖中,我們從請求對象中獲取鍵為 filter 的查詢參數(shù)值,然后根據(jù)這個(gè)值獲取不同類別的記錄。

@admin_bp.route('/comment/manage')
@login_required
def manage_comment():
    filter_rule = request.args.get('filter', 'all')  # 'all', 'unreviewed', 'admin'
    page = request.args.get('page', 1, type=int)
    per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE']
    if filter_rule == 'unread':
        filtered_comments = Comment.query.filter_by(reviewed=False)
    elif filter_rule == 'admin':
        filtered_comments = Comment.query.filter_by(from_admin=True)
    else:
        filtered_comments = Comment.query
    pagination = filtered_comments.order_by(Comment.timestamp.desc()).paginate(page, per_page=per_page)
    comments = pagination.items
    return render_template('admin/manage_comment.html', comments=comments, pagination=pagination)

除了通過查詢字符串獲取篩選條件,也可以為 manage_comment 視圖附加一個(gè)路由,比如 @admin_bp.route(‘/comment/manage/<filter>’),通過 URL 變量 filter 獲取。另外,在 URL 規(guī)則中使用 any 轉(zhuǎn)換器可以指定可選值。

manage_comment.html 模板中,我們添加一排導(dǎo)航標(biāo)簽按鈕,分別用來獲取 “全部” “未讀” 和 “管理員” 類別的評論

<ul class="nav nav-pills">
    <li class="nav-item">
        <a class="nav-link disabled" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Filter </a>
    </li>
    <li class="nav-item">
        <a class="nav-link {% if request.args.get('filter', 'all') == 'all' %}active{% endif %}"
           href="{{ url_for('admin.manage_comment', filter='all') }}" rel="external nofollow" >All</a>
    </li>
    <li class="nav-item">
        <a class="nav-link {% if request.args.get('filter') == 'unread' %}active{% endif %}"
           href="{{ url_for('admin.manage_comment', filter='unread') }}" rel="external nofollow" >Unread {% if unread_comments %}<span
                class="badge badge-success">{{ unread_comments }}</span>{% endif %}</a>
    </li>
    <li class="nav-item">
        <a class="nav-link {% if request.args.get('filter') == 'admin' %}active{% endif %}"
           href="{{ url_for('admin.manage_comment', filter='admin') }}" rel="external nofollow" >From Admin</a>
    </li>
</ul>

三個(gè)選項(xiàng)的 URL 都指向 manage_comment 視圖,但都附加了查詢參數(shù) filter 的對應(yīng)值。

再次提醒一下,當(dāng)使用 url_for 生成 URL 時(shí),傳入的關(guān)鍵字參數(shù)如果不是 URL 變量,那么會作為查詢參數(shù)附加在 URL 后面。

這里的導(dǎo)航鏈接沒有使用 render_nav_item(),為了更大的靈活性而選擇手動處理。在模板中,我們通過 request.args.get(‘filter’,‘all’) 獲取查詢參數(shù) filter 的值來決定是否為某個(gè)導(dǎo)航按鈕添加 active 類。默認(rèn)激活 All 按鈕,如果用戶單擊了篩選下拉列表中的 “Unread” 選項(xiàng),客戶端會發(fā)出一個(gè)請求到 http://localhost:5000/manage/comment?filter=unread,manage_comment 視圖就會返回對應(yīng)的未讀記錄,而模板中的 Unread 導(dǎo)航按鈕也會顯示激活狀態(tài),這時(shí)操作區(qū)域也會顯示一個(gè) Approve 按鈕。

3.分類管理

分類的管理功能比較簡單,這里不再完整講解,具體可以到源碼倉庫中查看。分類的刪除值得一提,實(shí)現(xiàn)分類的刪除功能有下面兩個(gè)要注意的地方:

  • 禁止刪除默認(rèn)分類。
  • 刪除某一分類時(shí)前,把該分類下的所有文章移動到默認(rèn)分類中。

為了避免用戶刪除默認(rèn)分類,首先在模板中渲染分類列表時(shí)需要添加一個(gè) if 判斷,避免為默認(rèn)分類渲染編輯和刪除按鈕。在刪除分類的視圖函數(shù)中,我們?nèi)匀恍枰俅悟?yàn)證被刪除的分類是否是默認(rèn)分類。在視圖函數(shù)中使用刪除分類時(shí),我們首先判斷分類的 id,如果是默認(rèn)分類(因?yàn)槟J(rèn)分類最先創(chuàng)建,id 為 1),則返回錯誤提示。

@admin_bp.route('/category/<int:category_id>/delete', methods=['POST'])
@login_required
def delete_category(category_id):
    category = Category.query.get_or_404(category_id)
    if category.id == 1:
        flash('You can not delete the default category.', 'warning')
        return redirect(url_for('blog.index'))
    category.delete()
    flash('Category deleted.', 'success')
    return redirect(url_for('.manage_category'))

上面的視圖函數(shù)中,刪除分類使用的 delete() 方法是我們在 Category 類中創(chuàng)建的方法,這個(gè)方法實(shí)現(xiàn)了第二個(gè)功能:將被刪除分類的文章的分類設(shè)為默認(rèn)分類,然后刪除該分類記錄。

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique=True)
    posts = db.relationship('Post', back_populates='category')
    def delete(self):
        default_category = Category.query.get(1)
        posts = self.posts[:]
        for post in posts:
            post.category = default_category
        db.session.delete(self)
        db.session.commit()

我們使用 Category.query.get(1) 獲取默認(rèn)分類記錄。這個(gè)方法迭代要刪除分類的所有相關(guān)文章記錄,為這些文章重新指定分類為默認(rèn)分類,然后 db.session.delete() 方法刪除分類記錄,最后提交數(shù)據(jù)庫會話。

到目前為止,Bluelog 程序的開發(fā)已經(jīng)基本結(jié)束了。

到此這篇關(guān)于Python個(gè)人博客程序開發(fā)實(shí)例后臺編寫的文章就介紹到這了,更多相關(guān)Python個(gè)人博客內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 通過Python收集匯聚MySQL 表信息的實(shí)例詳解

    通過Python收集匯聚MySQL 表信息的實(shí)例詳解

    這篇文章主要介紹了通過Python收集匯聚MySQL 表信息的實(shí)例代碼,核心代碼是創(chuàng)建保存數(shù)據(jù)的腳本,收集的功能腳本,代碼簡單明了,需要的朋友可以參考下
    2021-10-10
  • 利用Python實(shí)現(xiàn)RSA加密解密方法實(shí)例

    利用Python實(shí)現(xiàn)RSA加密解密方法實(shí)例

    過去幾天我一直在嘗試用Python實(shí)現(xiàn)RSA算法,下面這篇文章主要給大家介紹了關(guān)于利用Python實(shí)現(xiàn)RSA加密解密的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Python pandas RFM模型應(yīng)用實(shí)例詳解

    Python pandas RFM模型應(yīng)用實(shí)例詳解

    這篇文章主要介紹了Python pandas RFM模型應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了pandas RFM模型的概念、原理、應(yīng)用及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-11-11
  • python使用pyecharts庫畫地圖數(shù)據(jù)可視化的實(shí)現(xiàn)

    python使用pyecharts庫畫地圖數(shù)據(jù)可視化的實(shí)現(xiàn)

    這篇文章主要介紹了python使用pyecharts庫畫地圖數(shù)據(jù)可視化的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Scrapy?之中間件(Middleware)的具體使用

    Scrapy?之中間件(Middleware)的具體使用

    本文主要介紹了Scrapy?之中間件(Middleware)的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 高效使用Python字典的清單

    高效使用Python字典的清單

    字典(dict)對象是 Python 最常用的數(shù)據(jù)結(jié)構(gòu),本文給大家介紹使用Python字典的清單,感興趣的朋友一起看看吧
    2018-04-04
  • Python調(diào)用golang代碼詳解

    Python調(diào)用golang代碼詳解

    這篇文章主要給大家介紹了關(guān)于Python調(diào)用golang代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-02-02
  • Python Django Cookie 簡單用法解析

    Python Django Cookie 簡單用法解析

    這篇文章主要介紹了Python Django Cookie 簡單用法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • python3+PyQt5實(shí)現(xiàn)拖放功能

    python3+PyQt5實(shí)現(xiàn)拖放功能

    這篇文章主要為大家詳細(xì)介紹了python3+PyQt5實(shí)現(xiàn)拖放功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • 使用python將時(shí)間轉(zhuǎn)換為指定的格式方法

    使用python將時(shí)間轉(zhuǎn)換為指定的格式方法

    今天小編就為大家分享一篇使用python將時(shí)間轉(zhuǎn)換為指定的格式方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-11-11

最新評論