这个系统一直号称轻博客,但貌似博客的功能尚未实现,这一章将简单的实现一个博客功能,首先,固然是为数据库建立一个博文表(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还有更多的用法,具体请参阅相关文档
这一章终于写完了,再次感到对不起语文老师,无论怎么说,总算在周日以前完成,虽然上周食言了,尽可能坚持吧!