一个web应用的诞生(8)--博文发布
这个系统一直号称轻博客,但貌似博客的功能还没有实现,这一章将简单的实现一个博客功能,首先,当然是为数据库创建一个博文表(models\post.py):
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字段,这个是参考了微博以及目前市面上的一些轻博产品,每个人可以随心所以的发布轻博客,不限制必须发布正规的博客。
同时修改用户表与博文表关联
class User(UserMixin,db.Model):
...
posts=db.relationship("Post",backref="author",lazy='dynamic')
然设置博文表单(forms\PostForm.py):
from flask_wtf import FlaskForm
from wtforms import TextAreaField,SubmitField
from wtforms.validators import DataRequired
class PostForm(FlaskForm):
body=TextAreaField("分享一下现在的心情吧!",validators=[DataRequired()])
submit=SubmitField("提交")
模仿一下微博或现有的轻博产品,首先首页会有一个发布框,用于一键发送消息,如:
或
当然,还有轻博的鼻祖,墙外的Tumblr都是如此设计,我们也当然要追求流行的设计趋势,利用用户的固有习惯,最求最精致的感觉(好吧,其实就是抄袭),所以首页也要有此功能,修改首页的视图方法:
@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:
python manage.py db migrate #更新迁移脚本
python manage.py db upgrade #更新db
然后修改base.html,为导航条新增搜索框:
{%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没有转义,显示的依然是源码
- 时间显示的为时间的格式化字符串形式
- 用户头像为固定项
- 用户姓名为固定项
关于用户的问题留待下一章在解决,当前首先解决前两个问题,从第一个开始,这个问题的解决方式当然是将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还有更多的用法,具体请参阅相关文档
这一章终于写完了,再次感到对不起语文老师,不管怎么说,总算在周日之前完成,虽然上周食言了,尽量坚持吧!
一个web应用的诞生(8)--博文发布的更多相关文章
- 一个web应用的诞生(1)--初识flask
基于flask的web应用的诞生 Flask是一个非常优秀的web框架,它最大的特点就是保持一个简单而易于扩展的小核心,其他的都有用户自己掌握,并且方便替换,甚至,你可以在社区看到众多开源的,可直接用 ...
- 一个web应用的诞生(9)--回到用户
在开始之前,我们首先根据之前的内容想象一个场景,用户张三在网上浏览,看到了这个轻博客,发现了感兴趣的内容,于是想要为大家分享一下心情,恩?发现需要注册,好,输入用户名,密码,邮箱,并上传头像后,就可以 ...
- 一个web应用的诞生(10)--关注好友
下面回到首页中,使用一个账户登录,你肯定已经注意到了这里的内容: 没错,现在都是写死的一些固定信息,其中分享数量很容易就可以获取,只需要修改首页模板: <p class="text-m ...
- 一个web应用的诞生(11)--列表分页
上章的结束,若在实际开发过程中,会发现一个问题,那就首页或关注分享,是一下子按时间顺序全部显示出来,这在实际项目中不可能出现的,想想实际中的产品是如何做的? 一般来说,无非是两种,一种是使用页码,来进 ...
- 一个web应用的诞生(7)--结构调整
现在所有的Py代码均写在default.py文件中,很明显这种方法下,一旦程序变的负责,那么无论对于开发和维护来说,都会带来很多问题. Flask框架并不强制要求项目使用特定的组织结构,所以这里使用的 ...
- 一个web应用的诞生(11)--在探首页
就要面对本章的一个难点了,说是难点可能仅仅对于我来说,毕竟我是一个js渣,既然首页打算使用动态加载的形式,那么与后台交互的方式就要进行选择,目前比较流行的为RESTful的形式,关于RESTful的文 ...
- 一个web应用的诞生(12)--再探首页
就要面对本章的一个难点了,说是难点可能仅仅对于我来说,毕竟我是一个js渣,既然首页打算使用动态加载的形式,那么与后台交互的方式就要进行选择,目前比较流行的为RESTful的形式,关于RESTful的文 ...
- 一个web应用的诞生(7)
现在所有的Py代码均写在default.py文件中,很明显这种方法下,一旦程序变的负责,那么无论对于开发和维护来说,都会带来很多问题. Flask框架并不强制要求项目使用特定的组织结构,所以这里使用的 ...
- 一个web应用的诞生--使用模板
经过了第一章的内容,已经可以做出一些简单的页面,首先用这种方式做一个登录页面,首先要创建一个login的路由方法: @app.route("/login",methods=[&qu ...
随机推荐
- SQL Server-索引故事的遥远由来,原来是这样的?
前言 前段时间工作比较忙,每天回来也时不时去写有关ASP.NET Core的文章,无论是项目当中遇到的也好还是自学的也好都比较严谨的去叙述,喜欢分享,乐于分享这是我一直以来的态度,当然从中也会有些许错 ...
- POI-处理大Excel文件(xls)
最近需要处理一个比较大的excel文件,但是poi在处理文件时会抛出OOM导致程序崩溃,查看官方文档看到可以以流式的方式读取excel避免读取大文件时的OOM.本文主要记述xls的处理. 环境模拟 先 ...
- iOS核心笔记—源代码管理工具-SVN
源代码管理工具-SVN 一. 源代码管理工具概述 1. 源代码管理工具的作用? > 能追踪一个项目从诞生一直到定案的过程 > 记录一个项目的所有内容变化,无限制返回 > 查看特定版本 ...
- T-SQL 语句
表的创建:1.创建列(字段):列名+类型2.设置主键列(primary key):能够唯一标识一条数据3.设置唯一(unique):内容不能重复4.外键关系:一张表(从表)其中的某列引用自另外一张表( ...
- css之描点定位方式
<!-- 描点定位的两张方式 --> <!-- 1.通过id定位 --> <!-- 2.通过name定位 只能用a--> <div> <a hre ...
- Linux驱动技术(一) _内存申请
先上基础,下图是Linux的内存映射模型,其中体现了Linux内存映射的几个特点: 每一个进程都有自己的进程空间,进程空间的0-3G是用户空间,3G-4G是内核空间 每个进程的用户空间不在同一个物理内 ...
- canvas绘制圆形进度条(或显示当前已浏览网页百分比)
使用canvas绘制圆形进度条,或者是网页加载进度条 或者是显示你浏览了本网页多少-- 由于个浏览器的计算差异,打开浏览器时 初始值有所不同,但是当拉倒网页底部时,均显示100%. 兼容性:测试浏览器 ...
- 初学bootstrap ---栅格系统
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Redis 学习数据类型
该文使用centos6.5 64位 redis-3.2.8 [root@localhost bin]# netstat -tunpl |grep 6379 查看redis 是否启动成功 一.Stri ...
- 这是一款可以查阅Github上的热门趋势的APP
随时查阅当前Github上的热门趋势.使用Material Design设计风格,和流行的MVP+Retrofit+RxJava框架.数据抓取自https://github.com/trending ...