一個web應用的誕生(8)--博文發佈

這個系統一直號稱輕博客,但貌似博客的功能尚未實現,這一章將簡單的實現一個博客功能,首先,固然是爲數據庫建立一個博文表(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文件。

你可能已經注意到了,至少有四個問題

  1. markdown沒有轉義,顯示的依然是源碼
  2. 時間顯示的爲時間的格式化字符串形式
  3. 用戶頭像爲固定項
  4. 用戶姓名爲固定項

關於用戶的問題留待下一章在解決,當前首先解決前兩個問題,從第一個開始,這個問題的解決方式固然是將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還有更多的用法,具體請參閱相關文檔

這一章終於寫完了,再次感到對不起語文老師,無論怎麼說,總算在週日以前完成,雖然上週食言了,儘可能堅持吧!

相關文章
相關標籤/搜索