日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Python個人博客程序開發實例后臺編寫_python

作者:皮皮要HAPPY ? 更新時間: 2023-01-07 編程語言

本篇博客將是Python個人博客程序開發實例的最后一篇。本篇文章將會詳細介紹博客后臺的編寫。

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

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

{% 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="#"       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') }}"  >Post</a>
                <a class="dropdown-item" href="{{ url_for('admin.new_category') }}"  >Category</a>
                <a class="dropdown-item" href="{{ url_for('admin.new_link') }}"  >Link</a>
            </div>
        </li>
        <li class="nav-item dropdown">
            <a href="#"       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') }}"  >Post</a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_category') }}"  >Category</a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_comment') }}"  >
                    Comment
                    {% if unread_comments %}
                        <span class="badge badge-success">{{ unread_comments }}</span>
                    {% endif %}
                </a>
                <a class="dropdown-item" href="{{ url_for('admin.manage_link') }}"  >Link</a>
            </div>
        </li>
        {{ render_nav_item('admin.settings', 'Settings') }}
    {% endif %}
</ul>

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

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

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

@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.文章管理

我們要分別為分類、文章和評論創建單獨的管理頁面,這些內容基本相同,因此本節會以文章的管理主頁作為介紹的重點。另外,分類的創建、編輯和刪除與文章的創建、編輯和刪除實現代碼基本相同,這里也將以文章相關操作的實現作為介紹重點。

1.1 文章管理主頁

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

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

在這個視圖渲染的 manage_category.html 模板中,我們以表格的形式顯示文章列表,依次渲染出文章的標題、所屬的分類、發表時間、文章字數、包含的評論數量以及相應的操作按鈕。

{% 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') }}"  >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) }}"  >{{ post.title }}</a></td>
        <td><a href="{{ url_for('blog.show_category', category_id=post.category.id) }}"  >{{ 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"  >{{ 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) }}"  >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 %}

每一個文章記錄的左側都顯示一個序號標記。如果單獨使用 loop.index 變量渲染數量標記,那么每一頁的文章記錄都將從 1 到 15 重復(配置變量 BLUELOG_MANAGE_POST_PER_PAGE 的值),因為每一頁最多只有 15 條文章記錄。正確的評論數量標記可以通過 “當前迭代數 + ((當前頁數 - 1) × 每頁記錄數)” 的形式獲取。

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

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

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

1.2 創建文章

博客最重要的功能就是撰寫文章,new_post 視圖負責渲染創建文章的模板,并處理頁面中表單提交的 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 來建立關系,即 category_id=form.category.data。在程序中,為了便于理解,均使用將具體對象賦值給關系屬性的方式來建立關系。

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

當請求類型為 GET 時,這個視圖會實例化用于創建文章的 PostForm 表單,并將其傳入模板。在渲染的模板 new_post.html 中,我們使用 Bootstrap-Flask 提供的 render_form() 宏渲染表單。因為 PostForm 表單類中使用了擴展 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 的資源包我們已經下載并放到 static 目錄下,這里只需要加載 ckeditor.js 文件即可。因為 CKEditor 編輯器只在創建或編輯文章的頁面使用,所以可以只在這些頁面加載對應的資源,而不是在基模板中加載。

1.3 編輯與刪除

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

@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 視圖的工作可以概括為:首先從數據庫中獲取指定 id 的文章。如果是 GET 請求,使用文章的數據作為表單數據,然后渲染模板。如果是 POST 請求,即用戶單擊了提交按鈕,則根據表單的數據更新文章記錄的數據。

和保存文章時的做法相反,通過把數據庫字段的值分別賦給表單字段的數據,在渲染表單時,這些值會被填充到對應的 input 標簽的 value 屬性中,從而顯示在輸入框內。需要注意,因為表單中的分類字段是存儲分類記錄的 id 值,所以這里使用 post.category_id 作為 form.category.data 的值。

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

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()

這個視圖通過設置 methods 參數實現僅允許 POST 方法。因為在文章管理頁面和文章內容頁面都包含刪除按鈕,所以這里使用 redirect_back() 函數來重定向回上一個頁面。

2.評論管理

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

<div class="float-right">
    <a class="btn btn-light btn-sm"
       href="{{ url_for('.reply_comment', comment_id=comment.id) }}"  >Reply</a>
    {% if current_user.is_authenticated %}
        <a class="btn btn-light btn-sm" href="mailto:{{ comment.email }}"  >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>

因為刪除按鈕同時會被添加到評論管理頁面的評論列表中,所以我們在刪除評論的 URL 后附加了 next 參數,用于重定向回上一個頁面。如果當前用戶是管理員,我們還會顯示除了管理員發表的評論以外的評論者郵箱,渲染成 mailto 鏈接。

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

2.1 關閉評論

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

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

然后我們需要在模板中評論區右上方添加一個開關按鈕:

{% 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 %}

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

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

另外,在設置回復評論狀態的 reply_comment 視圖中,我們在開始添加一個 if 判斷,如果對應文章不允許評論,那么就直接重定向回文章頁面。

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

我們根據 post.can_comment 的值來渲染不同的按鈕文本和表單 action 值。因為這個功能很簡單,所以兩個按鈕指向同一個 URL,URL 對應的 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()

我們當然可以分別創建一個 enable_comment()disable_comment() 視圖函數來開啟和關閉評論,但是因為比較簡單,所以我們可以將這兩個操作統一在 set_comment() 視圖函數中完成。在這個視圖函數里,我們首先獲取文章對象,然后根據文章的 can_comment 的值來設置相反的布爾值。

最后,我們還需要在評論表單的渲染代碼前添加一個判斷語句。如果管理員關閉了當前博客的評論,那么一個相應的提示會取代評論表單,顯示在評論區底部。

{% 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 %}

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

2.2 評論審核

對于沒有通過審核的評論,在評論表格的操作列要添加一個批準按鈕。如果評論對象的 reviewed 字段值為 False,則顯示 “批準” 按鈕,并將該行評論以橙色背景顯示(添加 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>

因為這個操作會修改數據,我們同樣需要使用表單 form 元素來提交 POST 請求。批準按鈕指向的 approve_comment 視圖僅監聽 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 視圖中,我們將對應的評論記錄的 reviewed 字段設為 Ture,表示通過審核。通過審核后的評論會顯示在文章頁面下方的評論列表中。雖然評論的批準功能只在管理評論頁面提供,我們仍然在這里使用 redirect_back() 函數返回上一個頁面,這是因為評論管理頁面根據查詢參數 filter 的值會顯示不同的過濾結果,而在 “全部” 和 “未讀” 結果中的未讀評論記錄都會有 “Approve” 按鈕,所以我們需要重定向回正確的過濾分類下。

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

2.3 篩選評論

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

@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 視圖附加一個路由,比如 @admin_bp.route(‘/comment/manage/<filter>’),通過 URL 變量 filter 獲取。另外,在 URL 規則中使用 any 轉換器可以指定可選值。

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

<ul class="nav nav-pills">
    <li class="nav-item">
        <a class="nav-link disabled" href="#"      >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') }}"  >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') }}"  >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') }}"  >From Admin</a>
    </li>
</ul>

三個選項的 URL 都指向 manage_comment 視圖,但都附加了查詢參數 filter 的對應值。

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

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

3.分類管理

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

  • 禁止刪除默認分類。
  • 刪除某一分類時前,把該分類下的所有文章移動到默認分類中。

為了避免用戶刪除默認分類,首先在模板中渲染分類列表時需要添加一個 if 判斷,避免為默認分類渲染編輯和刪除按鈕。在刪除分類的視圖函數中,我們仍然需要再次驗證被刪除的分類是否是默認分類。在視圖函數中使用刪除分類時,我們首先判斷分類的 id,如果是默認分類(因為默認分類最先創建,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'))

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

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) 獲取默認分類記錄。這個方法迭代要刪除分類的所有相關文章記錄,為這些文章重新指定分類為默認分類,然后 db.session.delete() 方法刪除分類記錄,最后提交數據庫會話。

到目前為止,Bluelog 程序的開發已經基本結束了。

原文鏈接:https://blog.csdn.net/be_racle/article/details/127876102

欄目分類
最近更新