flask-博客文章
提交和显示博客文章
文章模型
class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
博客文章表单
app/main/forms.py
class PostForm(FlaskForm): body = TextAreaField(u'说说',validators=[Required()]) submit = SubmitField(u'发表')
处理博客文章的首页路由
app/main/views.py
@main.route('/',methods=['GET','POST']) def index(): form = PostForm() if current_user.can(Permission.WRITE_ARTICLES)and \ form.validate_on_submit(): post = Post(body=form.body.data,author=current_user._get_current_object()) db.session.add(post) db.session.commit() return redirect(url_for('.index')) posts = Post.query.order_by(Post.timestamp.desc()).all() return render_template('index.html',form=form,posts=posts)
显示博客文章的首页模板
{% extends "base.html" %} {% import 'bootstrap/wtf.html'as wtf %} {% block title %}Flasky{% endblock %} {% block page_content %} <div class="page-header"> <h1>欢迎 {% if current_user.is_authenticated %}{{ current_user.username }}{% else %}{% endif %}!</h1> </div> <div> {% if current_user.can(Permission.WRITE_ARTICLES) %} {{ wtf.quick_form(form) }} {% endif %} </div> <br/> <br/> <br/> <ul class="posts" style="list-style: none"> {% for post in posts %} <li class="post"> <div class="post-thumbnail"> <a href="{{ url_for('.user',username=post.author.username) }}"> <img class="img-rounded profile-thumbnail" src="{{ post.author.gravatar(size=40) }}" style="float: left"> </a> </div> <div class="post-date" style="float: right">{{ moment(post.timestamp).fromNow() }}</div> <div class="post-author"> <a href="{{ url_for('.user',username=post.author.username) }}" style="padding-left: 20px"> {{ post.author.username }} </a> </div> <div class="post-body" style="padding-left: 60px;padding-bottom: 30px" >{{ post.body }}</div> </li> {% endfor %} </ul> {% endblock %}
在资料页中显示博客文章
P118
创建虚拟博客文章数据
安装:pip install forgerypy
生成虚拟用户和博客文章
class User(UserMixin,db.Model):
#...
@staticmethod
def generate_fake(count=100):
from sqlalchemy.exc import IntegrityError
from random import seed
import forgery_py
seed()
for i in range(count):
u = User(email=forgery_py.internet.email_address(),
username=forgery_py.internet.user_name(True),
password=forgery_py.lorem_ipsum.word(),
confirmed=True,
name=forgery_py.name.full_name(),
location=forgery_py.address.city(),
about_me=forgery_py.lorem_ipsum.sentence(),
member_since=forgery_py.date.date(True))
db.session.add(u)
try:
db.session.commit()
except IntegrityError:
db.session.rollback()
class Post(db.Model):
@staticmethod
def generate_fake(count=100):
from random import seed, randint
import forgery_py
seed()
user_count = User.query.count()
for i in range(count):
u = User.query.offset(randint(0, user_count - 1)).first()
p = Post(body=forgery_py.lorem_ipsum.sentences(randint(1, 5)),
timestamp=forgery_py.date.date(True),
author=u)
db.session.add(p)
db.session.commit()
在puython shell中生成
>>>User.genetate_fake(100)
>>>User.genetate_fake(100)
在页面中渲染数据
@main.route('/',methods=['GET','POST']) def index(): #... page = request.args.get('page',1,type=int) pagination = Post.query.order_by(Post.timestamp.desc()).paginate( page,per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], error_out=False ) posts = pagination.items return render_template('index.html',form=form,posts=posts,pagination=pagination)
添加分页导航
分页导航宏
app/templates/_macros.html
{% macro pagination_widget(pagination, endpoint) %} <ul class="pagination pagination-lg"> <li{% if not pagination.has_prev %} class="disabled"{% endif %}> <a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{% else %}#{% endif %}"> « </a> </li> {% for p in pagination.iter_pages() %} {% if p %} {% if p == pagination.page %} <li class="active"> <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a> </li> {% else %} <li> <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a> </li> {% endif %} {% else %} <li class="disabled"><a href="#">…</a></li> {% endif %} {% endfor %} <li{% if not pagination.has_next %} class="disabled"{% endif %}> <a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{% else %}#{% endif %}"> » </a> </li> </ul> {% endmacro %}
在博客文章列表下面添加分页导航:
app/templates/index.html:
{% extends "base.html" %} {% import 'bootstrap/wtf.html'as wtf %} {% import "_macros.html" as macros %} #... {% include '_posts.html' %} #文章 {% if pagination %} <div class="pagination " style="float: right"> {{ macros.pagination_widget(pagination , '.index') }} </div> {% endif %}
使用Markdown和Flask-PageDown支持富文本文章
如果用户想发布长文章,就会觉得在格式上受到了限制。那么就需要使用输入文章的多行文本输入框升级,让其支持Markdown语法,还要添加富文本文章的预览功能
安装: pip install flask-pagedown markdown bleach
使用Flask-PageDown
初始化:
from flask_pagedown import PageDown pagedown = PageDown() def create_app(config_name): pagedown.init_app(app) return app
若想把首页的输入框改成Markdown富文本编辑器,PostForm表单中的body字段进行修改。
from flask_pagedown.fields import PageDownField class PostForm(FlaskForm): body = PageDownField(u'发表文章',validators=[Required()]) submit = SubmitField(u'确定')
Flask-Pagedown模板声明
{% block scripts %} {{ super() }} {{ pagedown.include_pagedown() }} {% endblock %}
在服务器上处理富文本
提交表单后,POST请求只会发送纯Markdown文本,页面中显示的HTML预览会被丢掉。安全起见,只提交Markdown源文本,在服务器上使用Markdown将其转换成HTML。得到HTML后,再使用Bleach进行清理,确保其中只包含几个允许使用的HTML标签
class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) author_id = db.Column(db.Integer, db.ForeignKey('users.id')) body_html = db.Column(db.Text) @staticmethod def on_changed_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))
markdown()函数初步把Markdown文本转换成HTML。然后把得到的结果和允许使用的HTML标签列表传给clean()函数。clean()函数删除所有不在白名单的标签。转换的最后一步由linkify()函数完成,这个函数由Bleach提供,把纯文本中的URL转换成适当的<a>标签。
最后,如果post.body_html字段存在,还要把post.body换成post.body_html:
app/templates/_posts.hrml:在模板中使用文章内容的HTML格式
<div class="post-body" > {% if post.body_html %} {{ post.body_html | safe }} {% else %} {{ post.body }} {% endif %} </div>
博客文章的固定链接
app/main/views.py:文章的固定链接
@main.route('/post/<int:id>') def post(id): post = Post.query.get_or_404(id) return render_template('post.html',posts=[post])
post.html模板接受一个列表作为参数,这个列表就是要渲染的文章。这里必须要传入列表,因为只有这样,index.html和user。html引用的_posts.html模板才能在这个页面中使用。
app/templates/_posts.html:文章的固定链接
<a href="{{ url_for('.post',id=post.id) }}"> <span class="label label-default">文章</span> </a>
app/templates/post.html:固定链接模板
{% extends 'base.html' %} {% block title %}Flask - Post{% endblock %} {% block page_content %} {% include '_posts.html' %} {% endblock %}
博客文章编辑器
aapp/main/views.py:编辑博客文章的路由,管理员可以编辑所有的文章
@main.route('/edit/<int:id>',methods=['GET','POST']) @login_required def edit(id): post = Post.query.get_or_404(id) if current_user != post.author and \ not current_user.can(Permission.ADMINISTER): abort(403) form = PostForm() if form.validate_on_submit(): post.body = form.body.data db.session.add(post) db.session.commit() flash('The post has been updated.') return redirect(url_for('.post',id=post.id)) form.body.data = post.body return render_template('edit_post.html',form=form)
app/templates/_posts.html:编辑博客文章的链接
{% if current_user == post.author %} <a href="{{ url_for('.edit',id=post.id) }}"> <span class="label label-primary">编辑</span> </a> {% elif current_user.is_administrator() %} <a href="{{ url_for('.edit',id=post.id) }}"> <span class="label label-danger">编辑</span> </a> {% endif %}
app/templates/edit_post.html:编辑博客文章的模板.
{% extends 'base.html' %} {% import 'bootstrap/wtf.html'as wtf %} {% block title %}Flask-edit{% endblock %} {% block page_content %} <div class="page-header"> <h1>Edit Post</h1> </div> <div> {{ wtf.quick_form(form) }} </div> {% endblock %} {% block scripts %} {{ super() }} {{ pagedown.include_pagedown() }} {% endblock %}