返回介绍

私有消息

发布于 2025-01-02 21:54:00 字数 7615 浏览 0 评论 0 收藏

我要实现的私有消息功能非常简单。 当你访问用户的个人主页时,会显示一个可以向该用户发送私有消息链接。 该链接将带你进入一个新的页面,在新页面中,可以在 Web 表单中发送消息。 要阅读发送给你的消息,页面顶部的导航栏将会有一个新的“消息”链接,它会将你带到与主页或发现页面相似的页面,但不会显示用户动态,它会显示其他用户发送给你的消息。

以下小节介绍了实现此功能所需的各个步骤。

私有消息的数据库支持

第一项任务是扩展数据库以支持私有消息。 这是一个新的 Message 模型:

app/models.py :Message 模型。

class Message(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    sender_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    recipient_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)

    def __repr__(self):
        return '<Message {}>'.format(self.body)

这个模型类与 Post 模型相似,唯一的区别是有两个用户外键,一个用于发信人,另一个用于收信人。 User 模型可以获得这两个用户的关系,以及一个新字段,用于指示用户最后一次阅读他们的私有消息的时间:

app/models.py :User 模型对私有消息的支持。

class User(UserMixin, db.Model):
    # ...
    messages_sent = db.relationship('Message',
                                    foreign_keys='Message.sender_id',
                                    backref='author', lazy='dynamic')
    messages_received = db.relationship('Message',
                                        foreign_keys='Message.recipient_id',
                                        backref='recipient', lazy='dynamic')
    last_message_read_time = db.Column(db.DateTime)

    # ...

    def new_messages(self):
        last_read_time = self.last_message_read_time or datetime(1900, 1, 1)
        return Message.query.filter_by(recipient=self).filter(
            Message.timestamp > last_read_time).count()

这两个关系将返回给定用户发送和接收的消息,并且在关系的 Message 一侧将添加 authorrecipient 回调引用。 我之所以使用 author 回调而不是更适合的 sender ,是因为通过使用 author ,我可以使用我用于用户动态的相同逻辑渲染这些消息。 last_message_read_time 字段将存储用户最后一次访问消息页面的时间,并将用于确定是否有比此字段更新时间戳的未读消息。 new_messages() 辅助方法实际上使用这个字段来返回用户有多少条未读消息。 在本章的最后,我将把这个数字作为页面顶部导航栏中的一个漂亮的徽章。

完成了数据库更改后,现在是时候生成新的迁移并使用它升级数据库了:

(venv) $ flask db migrate -m "private messages"
(venv) $ flask db upgrade

发送一条私有消息

下一步设计发送消息。我需要一个简单的 Web 表单来接收消息:

app/main/forms.py :私有消息表单类。

class MessageForm(FlaskForm):
    message = TextAreaField(_l('Message'), validators=[
        DataRequired(), Length(min=0, max=140)])
    submit = SubmitField(_l('Submit'))

而且我还需要在网页上呈现此表单的 HTML 模板:

app/templates/send_message.html :发送私有消息 HTML 模板。

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

{% block app_content %}
    <h1>{{ _('Send Message to %(recipient)s', recipient=recipient) }}</h1>
    <div>
        <div>
            {{ wtf.quick_form(form) }}
        </div>
    </div>
{% endblock %}

接下来,我将添加一个新的 /send_message/ 路由来处理实际发送的私有消息:

app/main/routes.py :发送私有消息的视图函数。

from app.main.forms import MessageForm
from app.models import Message

# ...

@bp.route('/send_message/<recipient>', methods=['GET', 'POST'])
@login_required
def send_message(recipient):
    user = User.query.filter_by(username=recipient).first_or_404()
    form = MessageForm()
    if form.validate_on_submit():
        msg = Message(author=current_user, recipient=user,
                      body=form.message.data)
        db.session.add(msg)
        db.session.commit()
        flash(_('Your message has been sent.'))
        return redirect(url_for('main.user', username=recipient))
    return render_template('send_message.html', title=_('Send Message'),
                           form=form, recipient=recipient)

这个视图函数中的逻辑显而易见。 发送私有消息的操作只需在数据库中添加一个新的“消息”实例即可。

将所有内容联系在一起的最后一项更改是在用户个人主页中添加上述路由的链接:

app/templates/user.html :个人主页中添加发送私有消息的链接。

                {% if user != current_user %}
                <p>
                    <a href="{{ url_for('main.send_message',
                                        recipient=user.username) }}">
                        {{ _('Send private message') }}
                    </a>
                </p>
                {% endif %}

查看私有消息

这个功能的第二大部分是查看私有信息。 为此,我添加另一条路由 /messages ,该路由与主页和发现页面非常相似,包括分页的完全支持:

app/main/routes.py :查看消息视图函数。

@bp.route('/messages')
@login_required
def messages():
    current_user.last_message_read_time = datetime.utcnow()
    db.session.commit()
    page = request.args.get('page', 1, type=int)
    messages = current_user.messages_received.order_by(
        Message.timestamp.desc()).paginate(
            page, current_app.config['POSTS_PER_PAGE'], False)
    next_url = url_for('main.messages', page=messages.next_num) \
        if messages.has_next else None
    prev_url = url_for('main.messages', page=messages.prev_num) \
        if messages.has_prev else None
    return render_template('messages.html', messages=messages.items,
                           next_url=next_url, prev_url=prev_url)

我在这个视图函数中做的第一件事是用当前时间更新 User.last_message_read_time 字段。 这会将发送给该用户的所有消息标记为已读。 然后,我查询消息模型以获得消息列表,并按照最近的时间戳进行排序。我决定在这里复用 POSTS_PER_PAGE 配置项,因为用户动态和消息的页面看起来非常相似,但是如果发生了分歧,为消息添加单独的配置变量也是有意义的。 分页逻辑与我用于用户动态的逻辑完全相同,因此这对你来说应该很熟悉。

上面的视图函数通过渲染一个新的 /app/templates/messages.html 模板文件结束,该模板如下:

app/templates/messages.html :查看消息 HTML 模板。

{% extends "base.html" %}

{% block app_content %}
    <h1>{{ _('Messages') }}</h1>
    {% for post in messages %}
        {% include '_post.html' %}
    {% endfor %}
    <nav aria-label="...">
        <ul>
            <li>
                <a href="{{ prev_url or '#' }}">
                    <span aria-hidden="true">&larr;</span> {{ _('Newer messages') }}
                </a>
            </li>
            <li>
                <a href="{{ next_url or '#' }}">
                    {{ _('Older messages') }} <span aria-hidden="true">&rarr;</span>
                </a>
            </li>
        </ul>
    </nav>
{% endblock %}

在这里,我采取了另一个小技巧。 我注意到除了 Message 具有额外的 recipient 关系(我不需要在消息页面中显示,因为它总是当前用户), PostMessage 实例具有几乎相同的结构。 所以我决定复用 app/templates/_post.html 子模板来渲染私有消息。 出于这个原因,这个模板使用了奇怪的 for 循环 for post in messages ,以便私有消息的渲染也可以套用到子模板上。

要让用户访问新的视图函数,导航页面需要生成一个新的“消息”链接:

app/templates/base.html :导航栏中的消息链接。

                    {% if current_user.is_anonymous %}
                    ...
                    {% else %}
                    <li>
                        <a href="{{ url_for('main.messages') }}">
                            {{ _('Messages') }}
                        </a>
                    </li>
                    ...
                    {% endif %}

该功能现已完成,但作为所有更改的一部分,还有一些新的文本被添加到几个位置,并且需要将这些文本合并到语言翻译中。 第一步是更新所有的语言目录:

(venv) $ flask translate update

然后, app/translations 中的每种语言都需要使用新翻译更新其 messages.po 文件。 你可以在本项目的 GitHub 代码库中找到西班牙语翻译,或者直接 下载 zip 文件

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。