這個系統一直號稱輕博客,但貌似博客的功能尚未實現,這一章將簡單的實現一個博客功能,首先,固然是爲數據庫建立一個博文表(models\post.py):css
from .. import db from datetime import datetime class Post(db.Model): __tablename__='posts' id=db.Column(db.Integer,primary_key=True) body=db.Column(db.Text) createtime=db.Column(db.DateTime,index=True,default=datetime.utcnow) author_id=db.Column(db.Integer,db.ForeignKey("users.id"))
你可能注意到了,這個博文表並無title字段,這個是參考了微博以及目前市面上的一些輕博產品,每一個人能夠隨心因此的發佈輕博客,不限制必須發佈正規的博客。html
同時修改用戶表與博文表關聯前端
class User(UserMixin,db.Model): ... posts=db.relationship("Post",backref="author",lazy='dynamic')
然設置博文表單(forms\PostForm.py):python
from flask_wtf import FlaskForm from wtforms import TextAreaField,SubmitField from wtforms.validators import DataRequired class PostForm(FlaskForm): body=TextAreaField("分享一下如今的心情吧!",validators=[DataRequired()]) submit=SubmitField("提交")
模仿一下微博或現有的輕博產品,首先首頁會有一個發佈框,用於一鍵發送消息,如:數據庫
或flask
固然,還有輕博的鼻祖,牆外的Tumblr都是如此設計,咱們也固然要追求流行的設計趨勢,利用用戶的固有習慣,最求最精緻的感受(好吧,其實就是抄襲),因此首頁也要有此功能,修改首頁的視圖方法:bootstrap
@main.route("/",methods=["GET","POST"]) def index(): form=PostForm() if form.validate_on_submit(): post=Post(body=form.body.data,author_id=1) #先寫死 由於沒有設置權限系統 db.session.add(post); return redirect(url_for(".index")) #跳回首頁 posts=Post.query.order_by(Post.createtime.desc()).all() #首頁顯示已有博文 按時間排序 return render_template("index.html",site_name='myblog',form=form,posts=posts)
而後還要對首頁進行修改,一樣,追求現有的流行趨勢,來設計出一個原型圖:安全
你可能已經注意到了,這個原型中的頭像,用戶表中是沒有的,不過這個不用擔憂,再之後在實現,如今暫時先使用一張固定的圖片,重點實現博文的功能,固然,爲了可以預覽首頁,首先須要更新db:markdown
python manage.py db migrate #更新遷移腳本 python manage.py db upgrade #更新db
而後修改base.html,爲導航條新增搜索框:session
{%extends "bootstrap/base.html "%} {% block title%}牛博客 {% endblock %}<!--覆蓋title標籤--> {% block navbar %} <nav class="navbar navbar-inverse"><!-- 導航部分 --> <div class="navbar-header"> <a class="navbar-brand" href="#"> NBlog </a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li><a href="/">首頁</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if current_user.is_authenticated %} <li><p class="navbar-text"><a href="#" class="navbar-link">{{current_user.username}}</a> 您好</p></li> <li><a href="{{url_for('auth.logout')}}">登出</a></li> {% else %} <li><a href="{{url_for('auth.login')}}">登陸</a></li> {% endif %} </ul> <form class="navbar-form navbar-right"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">搜索</button> </form> </div><!-- /.navbar-collapse --> </nav> {% endblock %} {% block content %} <!--具體內容--> {% block main %} <!--實際具體內容--> <div class="container"></div> {% endblock %} {% block footer %} <!--頁腳--> <div class="container navbar-fixed-bottom"> <div class="center-block text-center"> footer </div> </div> {% endblock %} {% endblock %}
注意在content block內,新增了兩個block,main和footer,即之後原有content內的內容均放到main中,使其自動加載頁腳
而後修改首頁內容部分,以完成靜態頁面(注意柵格系統):
{% extends "base.html" %} {% block main %} <div class="container"> <div class="row"> <div class="col-xs-12 col-md-8 col-md-8 col-lg-8"> <div> <textarea class="form-control" rows="3"></textarea> <br> <button class="btn btn-default" type="submit">分享</button> </div> <br> <div> <div class="bs-callout" > <div class="row"> <div class="col-sm-2 col-md-2"> <img src="http://ojzct6bcl.bkt.clouddn.com/cblog/pflask08/201703172224.jpg" alt="..."> </div> <div class="col-sm-10 col-md-10"> <div> <p>Options for individual tooltips can alternatively be specified through the use of data attributes, as explained above.</p> </div> <div> <a class="text-left" href="#">李四</a> <span class="text-right">發表於5分鐘前</span> </div> </div> </div> </div> <div class="bs-callout bs-callout-d bs-callout-last" > <div class="row"> <div class="col-sm-2 col-md-2"> <img src="http://ojzct6bcl.bkt.clouddn.com/cblog/pflask08/201703172224.jpg" alt="..."> </div> <div class="col-sm-10 col-md-10"> <div> <p>Options for individual tooltips can alternatively be specified through the use of data attributes, as explained above.</p> </div> <div> <a class="text-left" href="#">李四</a> <span class="text-right">發表於5分鐘前</span> </div> </div> </div> </div> </div> </div> <div class="col-md-4 col-md-4 col-lg-4"> <img src="http://ojzct6bcl.bkt.clouddn.com/cblog/pflask08/201703172228.jpg" alt="..." class="headimg img-thumbnail"> <br><br> <p class="text-muted">我已經分享<span class="text-danger">55</span>條心情</p> <p class="text-muted">我已經關注了<span class="text-danger">7</span>名好友</p> <p class="text-muted">我已經被<span class="text-danger">8</span>名好友關注</p> </div> </div> </div> {% endblock %}
還有一個簡單的css(原諒個人css渣),
.bs-callout { padding: 10px; margin: 0px 0; border: 1px solid #ccc; border-left-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-radius: 3px; } .bs-callout-d{ background: #f3f3f3; } .bs-callout-last{ border-bottom-width: 1px; } .bs-callout div img{ width:100px;height:100px;} .headimg{width:250px;height:250px;}
好 跑起來看看如今的樣子:
看上去貌似還不錯,固然,也許是由於模特的顏值高,接下來就開始實現功能,首先注意一點,目前沒有用戶權限系統, 而是在模型中寫死只要發送用戶即爲1,無論怎麼說,先使用wtf將發送框做爲表單輸入,很是簡單,即將文本框和按鈕部分替換爲:
{{ wtf.quick_form(form) }}
而後就能夠直接輸入信息,提交,一切就是這麼簡單:
點擊提交,查詢數據庫:
一切很是完美,可是,連個富文本框都沒有,怎麼好意思叫博客呢,雖然是輕博客,而做爲一個bigger高高的輕博客,那怕簡單的富文本框也要bigger搞搞,那麼bigger滿滿的markdown就浮出水面,包括這篇博客,都是用markdown來寫,下面就開始集成markdown插件,第一步固然仍是安裝:
pip3.6 install flask-pagedown markdown bleach
注意這裏使用了一種新的安裝形式,一次性安裝多個功能,他們分別爲:
flask-pagedown flask插件,前端能夠有經過js的方式支持markdown腳本的能力
markdown 爲py提供markdown腳本與html轉換的能力
html清理器
第二部也和以前同樣,在系統內進行註冊:
... from flask_pagedown import PageDown ... pagedown=PageDown(); ... def create_app(): ... pagedown.init_app(app) ...
而後使用起來也很是簡單,修改PostForm文件:
from flask_wtf import FlaskForm from wtforms import TextAreaField,SubmitField from flask_pagedown.fields import PageDownField from wtforms.validators import DataRequired class PostForm(FlaskForm): body=PageDownField("分享一下如今的心情吧!",validators=[DataRequired()]) submit=SubmitField("分享")
markdown固然須要模板,PageDown一樣也提供了一個模板宏,來實現這個功能,它是經過js來實現:
{% block scripts %} {{ super() }} {{ pagedown.include_pagedown() }} {% endblock%}
跑起來看看效果:
Perfect!!
下面來保存,db中查看:
能夠看到,提交的至少文本框中的markdown的源碼,這樣固然很是完美,沒有問題,可是返回頁面中顯示的狀況如何呢?接下來完成博文列表功能,依然很簡單,將原來的列表部分修改成:
{% for post in posts %} <div class="bs-callout {% if loop.index % 2 ==0 %} bs-callout-d {% endif %} {% if loop.last %} bs-callout-last {% endif %}" > <div class="row"> <div class="col-sm-2 col-md-2"> <img src="http://ojzct6bcl.bkt.clouddn.com/cblog/pflask08/201703172224.jpg" alt="..."> </div> <div class="col-sm-10 col-md-10"> <div> <p>{{post.body}}</p> </div> <div> <a class="text-left" href="#">李四</a> <span class="text-right">發表於{{}}</span> </div> </div> </div> </div> {% endfor %}
注意兩個if 的做用,第一個爲當偶數行的時候增長一個背景色,第二個的做用爲最後一個item增長一個下邊框,具體內容看css文件。
你可能已經注意到了,至少有四個問題
關於用戶的問題留待下一章在解決,當前首先解決前兩個問題,從第一個開始,這個問題的解決方式固然是將markdown轉義爲html代碼,首先想到的是在客戶端解決,由於以前既然已經能夠預覽,那麼必定能夠經過pagedown插件進行轉換,這個想法是正確的,可是,這樣作的一個後果就是每次渲染頁面的時候都要進行轉換,效率並非很高,因此,咱們能夠在提交以後,存入數據庫以前進行轉換,即db中新增一個字段保存html源碼,下面修改Post.py文件:
... import bleach from markdown import markdown class Post(db.Model): ... body_html=db.Column(db.Text) ... def on_change_body(target,value,oldvalue,initiator): allowed_tags=['a','abbr','acronym','b','blockquote','code','em','i', 'li','ol','pre','strong','ul','h1','h2','h3','p'] target.body_html=bleach.linkify(bleach.clean(markdown(value,output_format='html'),tags=allowed_tags, strip=True)) db.event.listen(Post.body,"set",Post.on_change_body)
這段代碼的意思是建立一個轉換函數on_change_body,而且經過SqlAlchemy的set事件進行監聽,即每次body的值更新的時候,HTML的值都會更新
在靜態方法中allowed_tag爲一個白名單,markdown方法將markdown源碼轉換後,bleach會清除全部不在白名單上的代碼,來確保安全。
繼續修改模板代碼爲:
<p> {% if post.body_html%} {{post.body_html|safe}} {% else %} {{post.body}} {% endif %} </p>
即當body_html不爲空的時候,顯示body_html的內容,不然顯示body的內容,參數safe的意思爲jinja2框架不對後臺傳入的html標籤進行轉義,而是直接注入,OK,看看渲染的結果:
ok 顯示的結果很是好,接下來讓咱們繼續看看關於時間的問題吧
當想要實現一個小功能的時候,通常來說有兩種方法,第一種就是本身造一個輪子,第二種就是使用一個已有的輪子,本身造輪子的固然會有不少問題,好比圓不圓了,是否是耐用,能走什麼路況等,但對於已有輪子,通常來講這些問題都通過了系統的測試,本身所須要面對的僅僅是這個輪子和這輛車是否是匹配,是否是合適的問題,而幸運的是,對於如今這個功能,正好有一個合適的輪子,那就是第一步固然仍是安裝:
pip3.6 install Flask-Moment
而後進行註冊:
from flask_moment import Moment ... moment=Moment() def create_app(): ... moment.init_app(app) ...
用法很是簡單,首先修改base.html代碼,引入moment庫(js庫,其實徹底能夠本身使用cdn導入):
{% block scripts %} {{ super() }} {{ moment.include_moment()}} {% endblock %}
而後修改index.html的時間的部分:
<span class="text-right">發表於{{ moment( post.createtime).fromNow(refresh=True)}}</span>
refresh的意思爲在不刷新的頁面下,能夠動態變化時間,運行一下看看效果:
能夠看到,時間格式化已經完成,可是顯示的是英文,顯然不是咱們須要的,國際化的方式也很是簡單,修改base.html代碼爲:
{% block scripts %} {{ super() }} {{ moment.include_moment()}} {{ moment.lang("zh-CN") }} {% endblock %}
設置一下語言便可,運行顯示:
正確顯示時間,如今新發布一條,而且爲了美觀,在時間塊前增長一個空格,顯示結果爲:
so good!關於moment還有更多的用法,具體請參閱相關文檔
這一章終於寫完了,再次感到對不起語文老師,無論怎麼說,總算在週日以前完成,雖然上週食言了,儘可能堅持吧!