本实战内容包括: 分页,markdown支持
分页
利用Flask-SQLAlchemy提供的paginate
方法:
page页数是唯一需求的参数,error_out=True如果页数超出了范围则返回404
# ...
pagination = query.order_by(Post.timestamp.desc()).paginate( # paginate是Flask-SQLAlchemy提供的分页方法
page, per_page=current_app.config['FLASKR_POSTS_PER_PAGE'], error_out=False
)
paginate()
方法的返回值是一个Pagination类对象,这个类在Flask-SQLAlchemy中定义。这个对象包含很多属性,用于在模版中生成分页链接,因此将其作为参数传入模版,分页对象属性简介:
属性 | 说明 |
---|---|
items | 当前页面中的记录 |
query | 分页的源查询 |
page | 当前页数 |
prev_num | 上一页页数 |
next_num | 下一页页数 |
has_next | 如果有下一页,返回True |
has_prev | 如果有上一页,返回True |
pages | 总页数 |
per_page | 每页显示的记录数 |
total | 查询返回的记录总数 |
prev() | 上一页分页对象 |
next() | 下一页分页对象 |
配合Bootstrap的分页CSS,实现分页模版宏:
其中最关键的就是分页对象的iter_pages()方法
iter_pages()返回一个迭代器,返回一个在分页中显示页数的列表。这个这个列表最左边显示left_edge
页,最右边显示right_edge
页 当前页面左边显示left_current
页,当前页面右边显示right_current
页;例如,在一个100页的列表中。当前页面第50页,使用默认配置,该方法返回值如下:1, 2, None, 48, 49, 50, 51, 52, 53, 54, 55, None, 99, 100 (None表示页数之间的间隔)
iter_pages(left_edge=2, left_current=2, right_current=5, right_edge=2)
{% macro pagination_widget(pagination, endpoint, fragment='') %}
<ul class="pagination">
{# 没有前一页,添加class=disble,不可点击,a href=##}
<li{% if not pagination.has_prev %} class="disabled"{% endif %}>
<a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{{ fragment }}{% 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) }}{{ fragment }}">{{ p }}</a>
</li>
{% else %}
<li>
<a href="{{ url_for(endpoint, page = p, **kwargs) }}{{ fragment }}">{{ p }}</a>
</li>
{% endif %}
{% else %}
<li class="disabled"><a href="#">…</a></li>
{% endif %}
{% endfor %}
{# 没有后一页,添加class=disble,不可点击,a href=##}
<li{% if not pagination.has_next %} class="disabled"{% endif %}>
<a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{{ fragment }}{% else %}#{% endif %}">
»
</a>
</li>
</ul>
{% endmacro %}
在其他默认里使用分页宏
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "_macros.html" as macros %}
<div class="pagination">
{{ macros.pagination_widget(pagination, '.index') }}
</div>
MarkDown
实现这个功能需要用到一些包:
- PageDown: 使用JavaScript实现的客户端Markdown到HTML的转换
- Flask-PageDown: 为Flask包转的PageDown, 把PageDown集成到Flask-WTF的表单中
- MarkDown: 使用python实现服务器端markdown到html的转换
- Bleach: 使用python实现的HTML清理器
使用Flask-PageDown
Flask-PageDown扩展定义了一个PageDownField类,这个类和WTForms中的TextAreaField接口一致。使用PageDownField之前。先要初始化扩展:
from flask_pagedown import PageDown
pagedown = PageDown()
pagedown.init_app(app)
将表单模型中的TextAreaField类型替换为PageDownFiled类型
from flask_pagedown.fields import PageDownField
class PostForm(Form):
# body = TextAreaField("What's on your mind?", validators=[DataRequired()])
body = PageDownField("What's on your mind?", validators=[DataRequired()])
submit = SubmitField('Submit')
在模版中添加pagedown的js
{% block scripts %}
{{ super() }}
{{ pagedown.include_pagedown() }}
{% endblock %}
在服务器上处理markdown到html并存入DB
先使用python markdown将markdown转换成html,在使用bleach清理,确保只保存只允许的html标签,在数据模型中添加转换方法:
from markdown import markdown
import bleach
class Poset(db.Model):
# ...
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))
# on_changed_body 函数注册在 body 字段上,是 SQLAlchemy“set”事件的监听程序,这意 味着只要这个类实例的 body 字段设了新值,函数就会自动被调用。
db.event.listen(Post.body, 'set', Post.on_changed_body)
在模版中显示
{% if post.body_html %}
{{ post.body_html | safe }} # 告诉jinja不转移html
{% else %}
{{ post.body }}
{% endif %}
网友评论