记录一次完整的flask小型应用开发(4)

创建博客列表

现在所有的东西都准备好了,可以开始创建博客引擎。
首先我们需要创建一个新的博客模型Post:

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'))
并且在user模型中建立relationship

我们准备在首页直接展示用户的博客,所以也在首页页面显示一个表单,用于写博客,还有一个提交按钮:
我们先创建表单:main/forms.py

class PostForm(FlaskForm):
    # 用于首页写博客的表单
    body = TextAreaField('wirte your blog here !', validators=[DataRequired()])
    submit = SubmitField('submit')

接下来修改主页面的路由,之前写的代码是测试小功能用的,所以可以直接替换为:

# 这个路由用于主页显示博客列表,并且在列表上方显示一个写博客表单
@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())
        # current_user由flask login提供,通过线程内的代理对象实现
        # 数据库需要真正的用户对象,所以使用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)

老样子,我们实现了路由功能,还需要渲染模板:

{% block title %}Flasky{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Hello, {% if current_user.is_authenticated %}{{ current_user.username }}{% else %}Stranger{% endif %}!</h1>
</div>

<div>
    {% if current_user.can(Permission.WRITE_ARTICLES) %}
        {{ wtf.quick_form(form) }}
    {% endif %}
</div>

<ul class='posts'>
    {% for post in posts %}
    <li class="post">
        # 用户头像部分
        <div class="profile-thumbnail">
            <a href="{{ url_for('.user', username=post.author.username) }}">
                <img class="img-rounded profile-thumbnail" src="{{ post.author.gravatar(size=40) }}">
            </a>
        </div>
        # 实体文字内容部分
        <div class="post-content">
            # 博客发布日期,计算的是发布日期距离今天有几天
            <div class="post-date">{{ moment(post.timestamp).formNow() }}</div>
            # 博客的作者,可以点击进入作者主页
            <div class="post-author"><a href="{{ url_for('.user', username=post.author.username) }}">{{ post.author.username }}</a></div>
            # 博客的内容
            <div class="post-body">{{ post.body }}</div>
        </div>
    </li>
    {% endfor %}
</ul>
{% endblock %}

然后为了让页面更美化,我们用上了css来布局:

.profile-thumbnail {
    position: absolute;
}
.profile-header {
    min-height: 260px;
    margin-left: 280px;
}
ul.posts {
    list-style-type: none;
    padding: 0px;
    margin: 16px 0px 0px 0px;
    border-top: 1px solid #e0e0e0;
}
ul.posts li.post {
    padding: 8px;
    border-bottom: 1px solid #e0e0e0;
}
ul.posts li.post:hover {
    background-color: #f0f0f0;
}
div.post-date {
    float: right;
}
div.post-author {
    font-weight: bold;
}
div.post-thumbnail {
    position: absolute;
}
div.post-content {
    margin-left: 48px;
    min-height: 48px;
}

在base.html中加上关联css:

<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">

改变各种小bug后,我们可以看到正常的页面了!!!
现在我们成功地在主页上显示了所有的博客,下面我们实现在用户个人主页页面上显示所有的博客文章,所以我们在渲染user.html模板的时候,我们多传入一个参数即user.posts,找到这个用户的所有博客,然后在user.html加上我们之前在主页面的博客列表即可:

# 为每个用户定义个人资料页面路由
@main.route('/user/<username>')
def user(username):
    user = User.query.filter_by(username=username).first()
    # 在数据库中搜索URL指定的用户名
    if user is None:
        abort(404)
    posts = user.posts.order_by(Post.timestamp.desc()).all()
    return render_template('user.html', user=user, posts=posts)

博客列表分页

现在我们考虑到博客列表可能会非常长,所以一个页面显示所有的博客是不现实也不美观的,为了方便测试,我们需要创建虚拟博客文章数据,这样数据库内就有大量数据供我们测试:这里我们使用ForgeryPy或者faker第三方库。
所以我们在app下面创建fake.py文件定义函数用于生成虚拟数据:然后发现报错,cannot import name current_app!!!!!

尝试另一种方法:在models的类里面写入静态方法:

@staticmethod
    # 这是静态方法,即此类不需要实例化就可以调用这个方法
    def generate_fake_user(count=100):
        fake = Faker()
        i = 0
        while i < count:
            u = User(email=fake.email(),
                     username=fake.user_name(),
                     password='password',
                     confirmed=True,
                     name=fake.name(),
                     location=fake.city(),
                     about_me=fake.text(),
                     member_since=fake.past_date())
            db.session.add(u)
            try:
                db.session.commit()
                i += 1
            except IntegrityError:
                db.session.rollback()
@staticmethod
    def generate_fake_post(count=100):
        fake = Faker()
        user_count = User.query.count()
        for i in range(count):
            u = User.query.offset(randint(0, user_count - 1)).first()
            # 随机文章生成的时候,我们需要随机指定一个用户来拥有这篇文章
            # 使用offset()查询过滤器,会跳过参数中指定的记录数量,所以会获得随机的用户
            p = Post(body=fake.text(),
                     timestamp=fake.past_date(),
                     author=u)
            db.session.add(p)
        db.session.commit()

然后运行 python manage.py shell
shell内运行:Post.generate_fake_post(100)
发现报错说Post是undefined,然后修改manage.py文件加上Post:

def make_shell_context():
    return dict(db=db, User=User, Role=Role, Post=Post)

因为现在是分页博客列表,所以我们需要修改首页的路由:

# 这个路由用于主页显示博客列表,并且在列表上方显示一个写博客表单
@main.route('/', methods=['GET', 'POST'])
def index():
    form = PostForm()
    if current_user.can(Permission.WRITE) and form.validate_on_submit():
        post = Post(body=form.body.data,
                    author=current_user._get_current_object())
        # current_user由flask login提供,通过线程内的代理对象实现
        # 数据库需要真正的用户对象,所以使用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()
    # 下面将上一行修改为分页显示所有博客
    page = request.args.get('page', 1, type=int)
    # 渲染的页数从请求的查询字符串request.args中获取,默认渲染第一页
    pagination = Post.query.order_by(Post.timestamp.desc()).\
        paginate(page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], error_out=False)
    # 这里把all()换成了sqlalchemy中的paginate()方法
    # 页数是第一个参数,也是唯一必须的参数
    # per_page显示一页显示的个数,默认是显示20个记录
    # 最后一个参数用于如果请求的页数超出了请求范围,那么404
    posts = pagination.items
    return render_template('index.html', form=form, posts=posts, Permission=Permission, pagination=pagination)

现在成功分页,如果要访问第二页则URL加上?page=2
但是我们肯定是需要一个底部的分页导航来跳转到不同的页面。
paginate()方法返回一个Pagination对象,里面有很多属性,用于生成分页链接。

所有这个强大的对象以及分页css类,可以让我们完成一个分页模板宏:

{% macro pagination_widget(pagination, endpoint) %}
<ul class="pagination">
    # 这里创建分页导航最左边的角标,用于跳转到前一页
    # 如果当前页为第一页,那么使之失效
    <li{% if not pagination.has_prev %} class="disabled" {% endif %}>
        <a href="{% if pagination.has_prev %} {{ url_for(endpoint, page=pagination.page - 1, **kwargs) }}{% else %}#{% endif %}">
            &laquo;
        </a>
    </li>
    
    # 这里中间部分用于显示所有的页面数字
    # iter_pages()迭代器返回所有的页面链接
    {% 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="#">&hellip;</a></li>
        {% endif %}
    {% endfor %}
    
    #  这里创建最右边的角标,用于跳转到上一页
    <li{% if not pagination.has_next %} class="disabled" {% endif %}>
        <a href="{% if pagination.has_next %} {{ url_for(endpoint, page=pagination.page + 1, **kwargs) }}{% else %}#{% endif %}">
            &raquo;
        </a>
    </li>
                
</ul>
{% endmacro %}

然后我们将这个模板放在index.html和user.html的后面来渲染出这个分页导航:

{% import "_macros.html" as macros %}
<div class="pagination">
    {{ macros.pagination_widget(pagination, '.index')}}
</div>

成功,下面正确显示出分页导航栏!!!!

使用MarkDown编辑器

要想实现这个功能我们需要安装一些额外的包
pip install flask-pagedown markdown bleach
首先我们初始化这个拓展:

from flask_pagedown import PageDown
pagedown = PageDown()
def create_app(config_name):
	pagedown.init_app(app)

然后我们需要把首页中的编辑器转换成MarkDown富文本编辑器:
body = PageDownField('wirte your blog here !', validators=[DataRequired()])

最后,我们需要使用预览功能,因此我们需要在模板中修改,拓展直接提供了一个模板宏,直接CDN加载即可。

{% block scripts %}
{{ super() }}
{{ pagedown.include_pagedown() }}
{% endblock %}

给每一个文章添加单独URL

首先制作单独链接的路由功能:

@main.route('/post/<int:id>')
def post(id):
    post = Post.query.get_or_404(id)
    return render_template('post.html', post=post)

老样子,单独页面需要模板:

{% extends "base.html" %}
{% block page_content %}
<ul class='posts'>
    <li class="post">
        <div class="profile-thumbnail">
            <a href="{{ url_for('.user', username=post.author.username) }}">
                <img class="img-rounded profile-thumbnail" src="{{ post.author.gravatar(size=40) }}">
            </a>
        </div>
        <div class="post-content">
            <div class="post-date">{{ moment(post.timestamp).fromNow() }}</div>
            <div class="post-author"><a href="{{ url_for('.user', username=post.author.username) }}">{{ post.author.username }}</a></div>
            <div class="post-body">{{ post.body }}</div>
        </div>
    </li>
</ul>
{% endblock %}

那怎么样才会使用这个功能呢,如何进入这个模板页面呢,我们在index.html的博客列表里面,将博客文字部分套上a标签,然后使用url_for()方法调用这个路由就可以啦!

博客文章编辑器

首先我们来实现这个功能的路由:

@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:
        abort(403)
    form = PostForm()
    if form.validate_on_submit():
        post.body = form.body.data
        db.session.add(post)
        db.session.commit()
        flash('you have updated your blog')
        return redirect(url_for('main.post', id=post.id))
    form.body.data = post.body
    return render_template('edit_post.html', form=form)

展示页面的模板:edit_post.html

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Edit your blog{% endblock %}

{% block page_content %}
<div>
    {% if current_user.can(Permission.WRITE) %}
        {{ wtf.quick_form(form) }}
    {% endif %}
</div>
{% endblock %}

{% block scripts %}
{{ super() }}
{{ pagedown.include_pagedown() }}
{% endblock %}

然后在主页面的每一个博客条目上添加一个edit按钮,即可以调用和这个路由跳转到编辑页面上!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值