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

Python個(gè)人博客程序開發(fā)實(shí)例框架設(shè)計(jì)

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

本文要學(xué)習(xí)的示例程序是一個(gè)個(gè)人博客程序:Bluelog。博客是典型的 CMSContent Management System,內(nèi)容管理系統(tǒng)),通常由兩部分組成:一部分是博客前臺(tái),用來展示開放給所有用戶的博客內(nèi)容;另一部分是博客后臺(tái),這部分內(nèi)容僅開放給博客管理員,用來對博客資源進(jìn)行添加、修改和刪除等操作。

1.數(shù)據(jù)庫(models.py)

from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from bluelog.extensions import db

1.1 管理員 Admin

class Admin(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True) # 主鍵字段
    username = db.Column(db.String(20))          # 用戶名
    password_hash = db.Column(db.String(128))    # 密碼散列值
    blog_title = db.Column(db.String(60))        # 博客標(biāo)題
    blog_sub_title = db.Column(db.String(100))   # 博客副標(biāo)題
    name = db.Column(db.String(30))              # 用戶姓名
    about = db.Column(db.Text)                   # 關(guān)于信息
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    def validate_password(self, password):
        return check_password_hash(self.password_hash, password)

1.2 分類 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')  # 分類和文章之間是一對多關(guān)系
    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()

1.3 文章 Post

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)         # 主鍵字段
    title = db.Column(db.String(60))                     # 標(biāo)題
    body = db.Column(db.Text)                            # 正文
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)  # 時(shí)間戳
    can_comment = db.Column(db.Boolean, default=True)    # 是否能被評(píng)論
    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))   # 所屬分類,外鍵字段
    category = db.relationship('Category', back_populates='posts')  # 分類和文章之間是一對多關(guān)系
    comments = db.relationship('Comment', back_populates='post', cascade='all, delete-orphan')  # 文章和評(píng)論是一對多關(guān)系

Comment 模型中創(chuàng)建的外鍵字段 post_id 存儲(chǔ) Post 記錄的主鍵值。我們在這里設(shè)置了級(jí)聯(lián)刪除,也就是說,當(dāng)某個(gè)文章記錄被刪除時(shí),該文章所屬的所有評(píng)論也會(huì)一并被刪除,所以在刪除文章時(shí)不用手動(dòng)刪除對應(yīng)的評(píng)論。

1.4 評(píng)論 Comment

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)        # 主鍵字段
    author = db.Column(db.String(30))                   # 作者
    email = db.Column(db.String(254))                   # 電子郵件
    site = db.Column(db.String(255))                    # 站點(diǎn)
    body = db.Column(db.Text)                           # 正文
    from_admin = db.Column(db.Boolean, default=False)   # 是否是管理員的評(píng)論
    reviewed = db.Column(db.Boolean, default=False)     # 是否通過審核
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)  # 時(shí)間戳
    replied_id = db.Column(db.Integer, db.ForeignKey('comment.id'))   # 外鍵
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))         # 外鍵
    post = db.relationship('Post', back_populates='comments')         # 文章和評(píng)論是一對多關(guān)系
    replies = db.relationship('Comment', back_populates='replied', cascade='all, delete-orphan')  # 設(shè)置級(jí)聯(lián)刪除
    replied = db.relationship('Comment', back_populates='replies', remote_side=[id]) # 自關(guān)聯(lián)多對一需用 remote_side=id 指定 ‘一' 的一方

博客程序中的評(píng)論要支持存儲(chǔ)回復(fù)。我們想要為評(píng)論添加回復(fù),并在獲取某個(gè)評(píng)論時(shí)可以通過關(guān)系屬性獲得相對應(yīng)的回復(fù),這樣就可以在模板中顯示出評(píng)論之間的對應(yīng)關(guān)系。那么回復(fù)如何存儲(chǔ)在數(shù)據(jù)庫中呢?

你當(dāng)然可以再為回復(fù)創(chuàng)建一個(gè) Reply 模型,然后使用一對多關(guān)系將評(píng)論和回復(fù)關(guān)聯(lián)起來。但是我們將介紹一個(gè)更簡單的解決辦法,因?yàn)榛貜?fù)本身也是評(píng)論,如果可以在評(píng)論模型內(nèi)建立層級(jí)關(guān)系,那么就可以在一個(gè)模型中表示評(píng)論和回復(fù)。

這種在同一個(gè)模型內(nèi)的一對多關(guān)系在 SQLAlchemy 中被稱為鄰接列表關(guān)系(Adjacency List Relationship)。具體來說,我們需要在 Comment 模型中添加一個(gè)外鍵指向它自身。這樣我們就得到一種層級(jí)關(guān)系:每個(gè)評(píng)論對象都可以包含多個(gè)子評(píng)論,即回復(fù)。

這個(gè)關(guān)系和我們之前熟悉的一對多關(guān)系基本相同。仔細(xì)回想一下一對多關(guān)系的設(shè)置,我們需要在 “多” 這一側(cè)定義外鍵,這樣 SQLAlchemy 就會(huì)知道哪邊是 “多” 的一側(cè)。這時(shí)關(guān)系對 “多” 這一側(cè)來說就是多對一關(guān)系。但是在鄰接列表關(guān)系中,關(guān)系的兩側(cè)都在同一個(gè)模型中,這時(shí) SQLAlchemy 就無法分辨關(guān)系的兩側(cè)。在這個(gè)關(guān)系函數(shù)中,通過將 remote_side 參數(shù)設(shè)為 id 字段,我們就把 id 字段定義為關(guān)系的遠(yuǎn)程側(cè)(Remote Side),而 replied_id 就相應(yīng)地變?yōu)楸镜貍?cè)(Local Side),這樣反向關(guān)系就被定義為多對一,即多個(gè)回復(fù)對應(yīng)一個(gè)父評(píng)論。

集合關(guān)系屬性 replies 中的 cascade 參數(shù)設(shè)為 all,因?yàn)槲覀兤谕男Ч?,?dāng)父評(píng)論被刪除時(shí),所有的子評(píng)論也隨之刪除。

1.5 社交鏈接 Link

程序還包含了一個(gè)添加社交鏈接的功能。

class Link(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    url = db.Column(db.String(255))

2.生成虛擬數(shù)據(jù)(fakes.py)

from faker import Faker
fake = Faker()
def fake_admin():
def fake_categories(count=10):
def fake_posts(count=50):
def fake_links():

3.模板

3.1 模板上下文

在基模板的導(dǎo)航欄以及博客主頁中需要使用博客的標(biāo)題、副標(biāo)題等存儲(chǔ)在管理員對象上的數(shù)據(jù),為了避免在每個(gè)視圖函數(shù)中渲染模板時(shí)傳入這些數(shù)據(jù),我們在模板上下文處理函數(shù)中向模板上下文添加了管理員對象變量(admin)。另外,在多個(gè)頁面中都包含的邊欄中包含分類列表,我們也把分類數(shù)據(jù)傳入到模板上下文中。

from bluelog.models import Admin, Category
def create_app(config_name=None):
    ...
	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)

在基模板 base.html 和主頁模板 index.html 中,我們可以直接使用傳入的 admin 對象獲取博客的標(biāo)題和副標(biāo)題。

<div class="page-header">
<h1 class="display-3">{{ admin.blog_title|default('Blog Title') }}</h1>
<h4 class="text-muted">&nbsp;{{ admin.blog_sub_title|default('Blog Subtitle') }}</h4>
</div>

3.2 渲染導(dǎo)航鏈接

導(dǎo)航欄上的按鈕應(yīng)該在對應(yīng)的頁面顯示激活狀態(tài)。舉例來說,當(dāng)用戶單擊導(dǎo)航欄上的 “關(guān)于” 按鈕打開關(guān)于頁面時(shí),“關(guān)于” 按鈕應(yīng)該高亮顯示。Bootstrap 為導(dǎo)航鏈接提供了一個(gè) active 類來顯示激活狀態(tài),我們需要為當(dāng)前頁面對應(yīng)的按鈕添加 active 類。

這個(gè)功能可以通過判斷請求的端點(diǎn)來實(shí)現(xiàn),對 request 對象調(diào)用 endpoint 屬性即可獲得當(dāng)前的請求端點(diǎn)。如果當(dāng)前的端點(diǎn)與導(dǎo)航鏈接指向的端點(diǎn)相同,就為它添加 active 類,顯示激活樣式。

<li {% if request.endpoint == 'blog.index' %}class="active"{% endif %}>
<a href="{{ url_for('blog.index') }}" rel="external nofollow" >Home</a>
</li>

有些教程中會(huì)使用 endswith() 方法來比較端點(diǎn)結(jié)尾。但是藍(lán)本擁有獨(dú)立的端點(diǎn)命名空間,即 “<藍(lán)本名>.<端點(diǎn)名>”,不同的端點(diǎn)可能會(huì)擁有相同的結(jié)尾,比如 blog.indexauth.index,這時(shí)使用 endswith() 會(huì)導(dǎo)致判斷錯(cuò)誤,所以最妥善的做法是比較完整的端點(diǎn)值。

不過在 Bluelog 的模板中我們并沒有使用這個(gè) nav_link() 宏,因?yàn)?Bootstrap-Flask 提供了一個(gè)更加完善的 render_nav_item() 宏,它的用法和我們創(chuàng)建的 nav_link() 宏基本相同。這個(gè)宏可以在模板中通過 bootstrap/nav.html 路徑導(dǎo)入,它支持的常用參數(shù)如下表所示。

3.3 Flash消息分類

我們目前的 Flash 消息應(yīng)用了 Bootstrap 的 alert-info 樣式,單一的樣式使消息的類別和等級(jí)難以區(qū)分,更合適的做法是為不同類別的消息應(yīng)用不同的樣式。比如,當(dāng)用戶訪問出錯(cuò)時(shí)顯示一個(gè)黃色的警告消息;而普通的提示信息則使用藍(lán)色的默認(rèn)樣式。Bootstrap 為提醒消息(Alert)提供了 8 種基本的樣式類,即 alert-primary、alert-secondaryalert-success、alert-dangeralert-warning、alert-light、alert-dark

要開啟消息分類,我們首先要在消息渲染函數(shù) get_flashed_messages 中將 with_categories 參數(shù)設(shè)為 True。這時(shí)會(huì)把消息迭代為一個(gè)類似于(分類,消息)的元組,我們使用消息分類字符來構(gòu)建樣式類。

<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">&times;</button>
		{{ message[1] }}
	</div>
	{% endfor %}
	...
</main>

4.表單(forms.py)

Bluelog 中主要包含下面這些表單:登錄表單、文章表單、分類表單、評(píng)論表單、博客設(shè)置表單。這里我們僅介紹登錄表單、文章表單、分類表單和評(píng)論表單,其他的表單在實(shí)現(xiàn)上基本相同,不再詳細(xì)介紹。

刪除資源也需要使用表單來實(shí)現(xiàn),這里之所以沒有創(chuàng)建表單類,是因?yàn)楹竺嫖覀儠?huì)介紹在實(shí)現(xiàn)刪除操作時(shí)為表單實(shí)現(xiàn) CSRF 保護(hù)的更方便的做法,屆時(shí)表單可以手動(dòng)在模板中寫出。

4.1 登錄表單

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
    password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)])
    remember = BooleanField('Remember me')
    submit = SubmitField('Log in')

登錄表單由用戶名字段 username、密碼字段 password、“記住我” 復(fù)選框 remember 和 “提交” 按鈕 submit 組成。

4.2 文章表單

from flask_ckeditor import CKEditorField
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, SelectField
from wtforms.validators import DataRequired, Length
from bluelog.models import Category
class PostForm(FlaskForm):
    title = StringField('Title', validators=[DataRequired(), Length(1, 60)])
    category = SelectField('Category', coerce=int, default=1)
    body = CKEditorField('Body', validators=[DataRequired()])
    submit = SubmitField()
    def __init__(self, *args, **kwargs):
        super(PostForm, self).__init__(*args, **kwargs)
        self.category.choices = [(category.id, category.name)
                                 for category in Category.query.order_by(Category.name).all()]

文章創(chuàng)建表單由標(biāo)題字段 title、分類選擇字段 category、正文字段 body 和 “提交” 按鈕組成,其中正文字段使用 Flask-CKEditor 提供的 CKEditorField 字段。

下拉列表字段使用 WTForms 提供的 SelectField 類來表示 HTML 中的 標(biāo)簽。下拉列表的選項(xiàng)(即 標(biāo)簽)通過參數(shù) choices 指定。choices 必須是一個(gè)包含兩元素元組的列表,列表中的元組分別包含選項(xiàng)值和選項(xiàng)標(biāo)簽。我們使用分類的 id 作為選項(xiàng)值,分類的名稱作為選項(xiàng)標(biāo)簽,這兩個(gè)值通過迭代 Category.query.order_by(Category.name).all() 返回的分類記錄實(shí)現(xiàn)。選擇值默認(rèn)為字符串類型,我們使用 coerce 關(guān)鍵字指定數(shù)據(jù)類型為整型。default 用來設(shè)置默認(rèn)的選項(xiàng)值,我們將其指定為 1,即默認(rèn)分類的 id。

因?yàn)?Flask-SQLAlchemy 依賴于程序上下文才能正常工作(內(nèi)部使用 current_app 獲取配置信息),所以這個(gè)查詢調(diào)用要放到構(gòu)造方法中執(zhí)行,在構(gòu)造方法中對 self.category.choices 賦值的效果和在類中實(shí)例化 SelectField 類并設(shè)置 choices 參數(shù)相同。

4.3 分類表單

from wtforms import StringField, SubmitField, ValidationError
from wtforms import DataRequired
from bluelog.models import Category
class CategoryForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired(), Length(1, 30)])
    submit = SubmitField()
    def validate_name(self, field):
        if Category.query.filter_by(name=field.data).first():
            raise ValidationError('Name already in use.')

分類創(chuàng)建字段僅包含分類名稱字段(name)和提交字段。分類的名稱要求不能重復(fù),為了避免寫入重復(fù)的分類名稱導(dǎo)致數(shù)據(jù)庫出錯(cuò),我們在 CategoryForm 類中添加了一個(gè) validate_name 方法,作為 name 字段的自定義行內(nèi)驗(yàn)證器,它將在驗(yàn)證 name 字段時(shí)和其他驗(yàn)證函數(shù)一起調(diào)用。在這個(gè)驗(yàn)證方法中,我們使用字段的值 filed.data 作為 name 列的參數(shù)值進(jìn)行查詢,如果查詢到已經(jīng)存在同名記錄,那么就拋出 ValidationError 異常,傳遞錯(cuò)誤消息作為參數(shù)。

4.4 評(píng)論表單

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Email, URL, Length, Optional
class CommentForm(FlaskForm):
    author = StringField('Name', validators=[DataRequired(), Length(1, 30)])
    email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)])
    site = StringField('Site', validators=[Optional(), URL(), Length(0, 255)])
    body = TextAreaField('Comment', validators=[DataRequired()])
    submit = SubmitField()

在這個(gè)表單中,email 字段使用了用于驗(yàn)證電子郵箱地址的 Email 驗(yàn)證器。另外,因?yàn)樵u(píng)論者的站點(diǎn)是可以留空的字段,所以我們使用 Optional 驗(yàn)證器來使字段可以為空。site 字段使用 URL 驗(yàn)證器確保輸入的數(shù)據(jù)為有效的 URL

和匿名用戶的表單不同,管理員不需要填寫諸如姓名、電子郵箱等字段。我們單獨(dú)為管理員創(chuàng)建了一個(gè)表單類,這個(gè)表單類繼承自 CommentForm 類。

class AdminCommentForm(CommentForm):
    author = HiddenField()
    email = HiddenField()
    site = HiddenField()

在這個(gè)表單中,姓名、Email、站點(diǎn)字段使用 HiddenField 類重新定義。這個(gè)類型代表隱藏字段,即 HTML 中的 < input type=“hidden” >。

5.視圖函數(shù)(blueprints:admin、auth、blog)

在上面我們已經(jīng)創(chuàng)建了所有必須的模型類、模板文件和表單類。經(jīng)過程序規(guī)劃和設(shè)計(jì)后,我們可以創(chuàng)建大部分視圖函數(shù)。這些視圖函數(shù)暫時(shí)沒有實(shí)現(xiàn)具體功能,僅渲染對應(yīng)的模板,或是重定向到其他視圖。以 blog 藍(lán)本為例。

from flask import render_template, Blueprint
blog_bp = Blueprint('blog', __name__)
@blog_bp.route('/')
def index():
	return render_template('blog/index.html')
@blog_bp.route('/about')
def about():
	return render_template('blog/about.html')
@blog_bp.route('/category/<int:category_id>')
def show_category(category_id):
	return render_template('blog/category.html')
@blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST'])
def show_post(post_id):
	return render_template('blog/post.html')

blog 藍(lán)本類似,我們在 blueprints 子包中創(chuàng)建了 auth.pyadmin.py 腳本,這些腳本中分別創(chuàng)建了 authadmin 藍(lán)本,藍(lán)本實(shí)例的名稱分別為 auth_bpadmin_bp。

6.電子郵件支持(emails.py)

因?yàn)椴┛鸵С衷u(píng)論,所以我們需要在文章有了新評(píng)論后發(fā)送郵件通知管理員。而且,當(dāng)管理員回復(fù)了讀者的評(píng)論后,也需要發(fā)送郵件提醒讀者。

因?yàn)猷]件的內(nèi)容很簡單,我們將直接在發(fā)信函數(shù)中寫出正文內(nèi)容,這里只提供了 HTML 正文。我們有兩個(gè)需要使用電子郵件的場景:

  • 當(dāng)文章有新評(píng)論時(shí),發(fā)送郵件給管理員;
  • 當(dāng)某個(gè)評(píng)論被回復(fù)時(shí),發(fā)送郵件給被回復(fù)用戶。

為了方便使用,我們在 emails.py 中分別為這兩個(gè)使用場景創(chuàng)建了特定的發(fā)信函數(shù),可以直接在視圖函數(shù)中調(diào)用。這些函數(shù)內(nèi)部則通過調(diào)用我們創(chuàng)建的通用發(fā)信函數(shù) send_mail() 來發(fā)送郵件。

from flask import url_for
def send_mail(subject, to, html):
	...
def send_new_comment_email(post):
    post_url = url_for('blog.show_post', post_id=post.id, _external=True) + '#comments'
    send_mail(subject='New comment', to=current_app.config['BLUELOG_EMAIL'],
              html='<p>New comment in post <i>%s</i>, click the link below to check:</p>'
                   '<p><a href="%s" rel="external nofollow"  rel="external nofollow" >%s</a></P>'
                   '<p><small style="color: #868e96">Do not reply this email.</small></p>'
                   % (post.title, post_url, post_url))

send_new_comment_email() 函數(shù)用來發(fā)送新評(píng)論提醒郵件。我們通過將 url_for() 函數(shù)的 _external 參數(shù)設(shè)為 True 來構(gòu)建外部鏈接。鏈接尾部的 #comments 是用來跳轉(zhuǎn)到頁面評(píng)論部分的URL片段(URL fragment),comments 是評(píng)論部分 div 元素的 id 值。這個(gè)函數(shù)接收表示文章的 post 對象作為參數(shù),從而生成文章正文的標(biāo)題和鏈接。

URL 片段又稱片段標(biāo)識(shí)符(fragment identifier),是 URL 中用來標(biāo)識(shí)頁面中資源位置的短字符,以 # 開頭,對于 HTML 頁面來說,一個(gè)典型的示例是文章頁面的評(píng)論區(qū)。假設(shè)評(píng)論區(qū)的 div 元素 idcomment,如果我們訪問 http://example.com/post/7#comment,頁面加載完成后將會(huì)直接跳到評(píng)論部分。

def send_new_reply_email(comment):
    post_url = url_for('blog.show_post', post_id=comment.post_id, _external=True) + '#comments'
    send_mail(subject='New reply', to=comment.email,
              html='<p>New reply for the comment you left in post <i>%s</i>, click the link below to check: </p>'
                   '<p><a href="%s" rel="external nofollow"  rel="external nofollow" >%s</a></p>'
                   '<p><small style="color: #868e96">Do not reply this email.</small></p>'
                   % (comment.post.title, post_url, post_url))

send_new_reply_email() 函數(shù)則用來發(fā)送新回復(fù)提醒郵件。這個(gè)發(fā)信函數(shù)接收 comment 對象作為參數(shù),用來構(gòu)建郵件正文,所屬文章的主鍵值通過 comment.post_id 屬性獲取,標(biāo)題則通過 comment.post.title 屬性獲取。

在 Bluelog 源碼中,我們沒有使用異步的方式發(fā)送郵件,如果你希望編寫一個(gè)異步發(fā)送郵件的通用發(fā)信函數(shù) send_mail(),可以使用以下方式。

from threading import Thread
from flask import current_app
from flask_mail import Message
from bluelog.extensions import mail
def _send_async_mail(app, message):
    with app.app_context():
        mail.send(message)
def send_mail(subject, to, html):
    app = current_app._get_current_object()
    message = Message(subject, recipients=[to], html=html)
    thr = Thread(target=_send_async_mail, args=[app, message])
    thr.start()
    return thr

需要注意的是,因?yàn)槲覀兊某绦驅(qū)嵗峭ㄟ^工廠函數(shù)構(gòu)建的,所以實(shí)例化 Thread 類時(shí),我們使用代理對象 current_app 作為 args 參數(shù)列表中 app 的值。另外,因?yàn)樵谛陆ǖ木€程時(shí)需要真正的程序?qū)ο髞韯?chuàng)建上下文,所以我們不能直接傳入 current_app,而是傳入對 current_app 調(diào)用 _get_current_object() 方法獲取到的被代理的程序?qū)嵗?/p>

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

相關(guān)文章

最新評(píng)論