Мега-Учебник Flask, Часть XXI: Уведомления пользователей

Original author: Miguel Grinberg
  • Translation
  • Tutorial

(издание 2018)


Miguel Grinberg




Туда Сюда


Это двадцать первая часть Мега-Учебника Flask, в которой я добавлю функцию личных сообщений, а также уведомления пользователей, которые появляются на панели навигации без необходимости обновления страницы.


Под спойлером приведен список всех статей серии 2018 года.



Примечание 1: Если вы ищете старые версии данного курса, это здесь.


Примечание 2: Если вдруг Вам захотелось бы выступить в поддержку моей(Мигеля) работы, или просто не имеете терпения дожидаться статьи неделю, я (Мигель Гринберг)предлагаю полную версию данного руководства(на английском языке) в виде электронной книги или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.


В этой главе я хочу продолжить работу над улучшением пользовательского интерфейса приложения Microblog. Одним из аспектов, который применяется ко многим приложениям, является представление предупреждений или уведомлений пользователю. Социальные сети показывают эти уведомления, чтобы вы знали, что у вас есть новые упоминания или личные сообщения, обычно показывая небольшой значок с номером в верхней панели навигации. Хотя это наиболее очевидное использование, шаблон уведомления может быть применен ко многим другим типам приложений, чтобы сообщить пользователю, что что-то требует их внимания.


Но чтобы показать вам методы, связанные с созданием пользовательских уведомлений, мне нужно было расширить функционал Microblog. Поэтому в первой части этой главы я собираюсь построить систему обмена сообщениями пользователей, которая позволяет любому пользователю отправлять личное сообщение другому пользователю. Это на самом деле проще, чем кажется, и это будет хороший повод освежить на практике основы работы с Flask и напоминанием о том, каким эффективным и интересным является программирование с помощью Flask. И как только система обмена сообщениями будет установлена, я собираюсь обсудить некоторые варианты реализации значка уведомления, который показывает количество непрочитанных сообщений.


Ссылки GitHub для этой главы: Browse, Zip, Diff.


Личные сообщения


Функция личных сообщений, которую я собираюсь реализовать, будет очень простой. Когда вы посещаете страницу профиля пользователя, там будет ссылка, для отправки этому пользователю личного сообщения. Ссылка переведет вас на новую страницу, в которой есть веб-форма приема сообщения. Для чтения сообщений, отправленных вам, панель навигации в верхней части страницы будет иметь новую ссылку "Messages" ("Сообщения"), которая приведет вас на страницу, похожую по структуре на страницы index или explore, но вместо показа сообщений в блоге она будет показывать сообщения других пользователей, отправленных вам.


В следующих разделах описаны шаги, которые я принял для реализации этой функции.


Поддержка баз данных для личных сообщений


Первая задача заключается в расширении базы данных для поддержки личных сообщений. Вот новая модель сообщения:


app/models.py: Message model.

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 model).

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 будут добавлены ссылки на author и recipient. Причина, по которой я использовал backref (обратную ссылку) author вместо, возможно, более подходящего sender (отправитель), заключается в том, что с помощью author я могу визуализировать эти сообщения, используя ту же логику, которую я использую для сообщений в блоге. Поле last_message_read_time будет содержать last time, когда пользователь последний раз посещал страницу сообщений, и будет использоваться, чтобы определить, есть ли непрочитанные сообщения, которые будут иметь метку времени более новую, чем в этом поле. Вспомогательный метод new_messages() фактически использует это поле для возврата количества непрочитанных сообщений пользователя. К концу этой главы у меня будет это число отображаться в качестве значка на панели навигации в верхней части страницы.


Это завершает изменения базы данных, поэтому теперь пришло время создать новую миграцию и обновить базу данных с ней:


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

Отправка личного сообщения


Далее я собираюсь работать над отправкой сообщений. Мне понадобится простая веб-форма, позволяющая набрать сообщение:


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 class="row">
        <div class="col-md-4">
            {{ wtf.quick_form(form) }}
        </div>
    </div>
{% endblock %}

Далее я собираюсь добавить new /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)

Я думаю, что логика в этой функции должна быть в основном понятной. Действие отправки личного сообщения просто выполняется путем добавления нового экземпляра Message в базу данных.


Последнее изменение, связывающее все вместе, — это добавление ссылки на указанный выше маршрут на странице профиля пользователя:


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: Просмотр messages route.

@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 поля с текущим временем. Маркирую все сообщения, которые были отправлены этому пользователю как прочитанные. Затем я запрашивая модель Message список сообщений, отсортированных по метке от новых к старым. Я решил использовать элемент POSTS_PER_PAGE конфигурации со страницы постов и сообщений, которые будут очень похожи, но, конечно, если страницы будут расхожими, возможно, имеет смысл добавить отдельную переменную для сообщений. Логика разбиения на страницы идентична тому, что я использовал для сообщений, поэтому все это должно быть вам знакомо.


Функция view выше заканчивается рендерингом файла шаблона /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 class="pager">
            <li class="previous{% if not prev_url %} disabled{% endif %}">
                <a href="{{ prev_url or '#' }}">
                    <span aria-hidden="true">&larr;</span> {{ _('Newer messages') }}
                </a>
            </li>
            <li class="next{% if not next_url %} disabled{% endif %}">
                <a href="{{ next_url or '#' }}">
                    {{ _('Older messages') }} <span aria-hidden="true">&rarr;</span>
                </a>
            </li>
        </ul>
    </nav>
{% endblock %}

Здесь я прибегнул к еще одному маленькому трюку. Я заметил, что экземпляры Post и Message имеют почти одинаковую структуру, за исключением того, что Message получает дополнительную связь с recipient (что мне не нужно показывать на странице Сообщений, так как это всегда текущего пользователя). Поэтому я решил повторно использовать суб-шаблон app/templates/_post.html также для отображения личных сообщений. По этой причине этот шаблон использует специфический цикл for-loop для for post in messages, так что все ссылки на post в суб-шаблоне также работают с сообщениями.


Чтобы предоставить пользователям доступ к новой функции просмотра, страница навигации получает новую ссылку "Messages":


app/templates/base.html: Messages link в панели навигации.

                {% 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-файле.


Статический Значок Уведомления


Теперь функция личных сообщений реализована, но, конечно, нет ничего, что говорит пользователю, что есть личные сообщения, ожидающие прочтения. Простейшая реализация индикатора панели навигации может быть отображена как часть базового шаблона с помощью виджета Bootstrap badge:


app/templates/base.html: Значок счетчика статических сообщений в навигационной панели.

                ...
                <li>
                    <a href="{{ url_for('main.messages') }}">
                        {{ _('Messages') }}
                        {% set new_messages = current_user.new_messages() %}
                        {% if new_messages %}
                        <span class="badge">{{ new_messages }}</span>
                        {% endif %}
                    </a>
                </li>
                ...

Здесь я вызываю метод new_messages(), который я добавил в модель User выше непосредственно из шаблона, и сохраняю это число в переменной шаблона new_messages. Затем, если эта переменная не равна нулю, я просто добавляю значок с номером рядом со ссылкой Messages. Вот как это выглядит на странице:



Динамический Значок Уведомления


Решение, представленное в предыдущем разделе, является достойным и простым способом показать уведомление, но у него есть недостаток, заключающийся в том что значок появляется только при загрузке новой страницы. Если пользователь долго читает контент на одной странице, не нажимая на какие-либо ссылки, новые сообщения, которые приходят в течение этого времени, не будут отображаться, пока пользователь, наконец, не нажмет на ссылку и не загрузит новую страницу.


Чтобы сделать это приложение более полезным для моих пользователей, я хочу, чтобы значок обновлял количество непрочитанных сообщений самостоятельно, без необходимости нажимать на ссылки и загружать новые страницы. Одна из проблем с решением из предыдущего раздела заключается в том, что значок отображается на странице только тогда, когда количество сообщений в момент загрузки страницы было ненулевым. Что действительно более удобно, так это всегда включать значок в панель навигации и отмечать его как скрытый, когда количество сообщений равно нулю. Это позволит легко сделать значок видимым с помощью JavaScript:


app/templates/base.html: Значок непрочитанных сообщений, совместимый с JavaScript.

                <li>
                    <a href="{{ url_for('main.messages') }}">
                        {{ _('Messages') }}
                        {% set new_messages = current_user.new_messages() %}
                        <span id="message_count" class="badge"
                              style="visibility: {% if new_messages %}visible
                                                 {% else %}hidden {% endif %};">
                            {{ new_messages }}
                        </span>
                    </a>
                </li>

В этой версии значка он всегда включен. Но! Свойство CSS visible установлено в visible, когда new_messages ненулевое и скрыто, если равно нулю. Я также добавил атрибут id к элементу <span>, который представляет значок, чтобы упростить обращение к этому элементу с помощью селектора jQuery $('#message_count').


Теперь я могу кодировать короткую функцию JavaScript, которая обновляет этот значок до нового номера:


app/templates/base.html: Значок счетчика статических сообщений в навигационной панели.

...
{% block scripts %}
    <script>
        // ...
        function set_message_count(n) {
            $('#message_count').text(n);
            $('#message_count').css('visibility', n ? 'visible' : 'hidden');
        }
    </script>
{% endblock %}

Новая функция set_message_count() устанавливает количество сообщений в элементе badge, а также настраивает видимость так, чтобы значок был скрыт, когда счетчик равен 0, и виден в противном случае.


Доставка уведомлений клиентам


Теперь остается добавить механизм, с помощью которого клиент получает периодические обновления относительно количества непрочитанных сообщений, которые имеет пользователь. Когда происходит одно из этих обновлений, клиент вызывает функцию set_message_count(), чтобы сделать обновление известным пользователям.


На самом деле есть два метода на сервере, чтобы доставить эти обновления клиенту, и, как вы, вероятно, догадываетесь, у обоих есть плюсы и минусы, поэтому какой из них выбрать в значительной степени зависит от проекта. При первом подходе клиент периодически запрашивает у сервера обновления, отправляя асинхронный запрос. Ответ на этот запрос представляет собой список обновлений, которые клиент может использовать для обновления различных элементов страницы, таких как значок счетчика непрочитанных сообщений. Второй подход требует особого типа соединения между клиентом и сервером, что позволяет серверу свободно передавать данные клиенту. Обратите внимание, что независимо от подхода, я хочу рассматривать уведомления как общие сущности, чтобы я мог расширить эту платформу для поддержки других типов событий, кроме значка непрочитанных сообщений.


Самое главное, что есть в первом решении, это то, что его легко реализовать. Все, что мне нужно сделать, это добавить еще один маршрут к приложению, say /notifications, который вернет JSON список уведомлений. Затем клиентское приложение просматривает список уведомлений и применяет необходимые изменения к странице для каждого из них. Недостатком этого решения является то, что будет задержка между фактическим событием и уведомлением для него, потому что клиент будет запрашивать список уведомлений через регулярные промежутки времени. Например, если клиент запрашивает уведомления каждые 10 секунд, уведомление может быть получено с задержкой до 10 секунд.


Второе решение требует изменений на уровне протокола, поскольку HTTP не имеет никаких условий для отправки сервером данных на клиент без запроса клиента. На сегодняшний день наиболее распространенным способом реализации сообщений, инициированных сервером, является расширение сервера для поддержки соединений WebSocket в дополнение к HTTP. WebSocket — это протокол, который в отличие от HTTP устанавливает постоянное соединение между сервером и клиентом. Сервер и клиент могут одновременно отправлять данные другой стороне, не обращая внимания на другую сторону. Преимущество этого механизма заключается в том, что всякий раз, когда возникает событие, представляющее интерес для клиента, сервер может отправлять уведомление без каких-либо задержек. Недостатком является то, что WebSocket требует более сложной настройки, чем HTTP, потому что серверу необходимо поддерживать постоянное соединение с каждым клиентом. Представьте себе, что сервер, который, например, имеет четыре рабочих процесса, обычно может обслуживать несколько сотен HTTP-клиентов, поскольку соединения в HTTP недолговечны и постоянно перерабатываются. Тот же сервер сможет обрабатывать только четыре клиента WebSocket, которые в подавляющем большинстве случаев будут недостаточными. Именно для этого ограничения приложения WebSocket обычно разрабатываются вокруг асинхронных серверов, поскольку эти серверы более эффективны при управлении большим числом рабочих и активных соединений.


Хорошей новостью является то, что независимо от метода, который вы используете, в клиенте у вас будет функция обратного вызова (callback), которая будет вызвана со списком обновлений. Поэтому я мог бы начать с первого варианта, которое гораздо проще реализовать, а затем, если я найду его недостаточным, перейду на сервер WebSocket, который можно настроить для вызова того же обратного вызова клиента. На мой взгляд, для данного вида применения первое решение на самом деле приемлемо. Реализация на основе WebSocket была бы полезна для приложения, которое требует, чтобы обновления доставлялись с почти нулевой задержкой.


Если вам интересно, Twitter также использует первый подход для уведомлений панели навигации. Facebook использует вариант, называемый Long_polling, который устраняет некоторые ограничения прямого опроса, все еще используя HTTP-запросы. Stack Overflow и Trello — это два Сайта, которые реализуют WebSocket для своих уведомлений. Вы можете узнать, какой тип фоновой активности происходит на любом сайте, просмотрев вкладку Network в отладчике браузера.


Итак, давайте продолжим и реализуем решение опроса. Во-первых, я собираюсь добавить новую модель, чтобы отслеживать уведомления для всех пользователей, а также отношения в пользовательской модели.


app/models.py: Модель уведомлений.

import json
from time import time

# ...

class User(UserMixin, db.Model):
    # ...
    notifications = db.relationship('Notification', backref='user',
                                    lazy='dynamic')

    # ...

class Notification(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), index=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    timestamp = db.Column(db.Float, index=True, default=time)
    payload_json = db.Column(db.Text)

    def get_data(self):
        return json.loads(str(self.payload_json))

Уведомление будет иметь имя, связанного пользователя, метку времени Unix и полезную нагрузку. Метка времени получает свое значение по умолчанию от функции time.time(). Полезная нагрузка будет отличаться для каждого типа уведомления, поэтому я пишу ее как строку JSON, поскольку это позволит мне писать списки, словари или отдельные значения, такие как числа или строки. Для удобства я добавил метод get_data(), чтобы вызывающему не пришлось беспокоиться о десериализации JSON.


Эти изменения необходимо включить в новую миграцию базы данных:


(venv) $ flask db migrate -m "notifications"
(venv) $ flask db upgrade

Для удобства я собираюсь добавить новые модели Message и Notification в контекст оболочки, чтобы при запуске оболочки с помощью команды flask shell класс модели автоматически импортировался для меня:


microblog.py: Добавление модели сообщения в контекст оболочки.

# ...
from app.models import User, Post, Notification, Message

# ...

@app.shell_context_processor
def make_shell_context():
    return {'db': db, 'User': User, 'Post': Post, 'Message': Message
            'Notification': Notification}

Я также собираюсь добавить вспомогательный метод add_notification() в пользовательскую модель, чтобы упростить работу с этими объектами:


app/models.py: Модель уведомления.

class User(UserMixin, db.Model):
    # ...

    def add_notification(self, name, data):
        self.notifications.filter_by(name=name).delete()
        n = Notification(name=name, payload_json=json.dumps(data), user=self)
        db.session.add(n)
    return n

Этот метод не только добавляет уведомление для пользователя в базу данных, но также гарантирует, что если уведомление с тем же именем уже существует, оно будет удалено. Уведомление, с которым я собираюсь работать, будет называться unread_message_count. Если в базе данных уже есть уведомление с этим именем, например, с 3-мя значениями на момент, когда пользователь получает новое сообщение, а количество сообщений дойдет до 4, я хочу заменить старое уведомление.


В любом месте, где изменяется количество непрочитанных сообщений, мне нужно вызвать add_notification(), чтобы обновить мои уведомления для пользователя. Есть два места, где это изменяется. Во-первых, когда пользователь получает новое личное сообщение в функции send_message():


app/main/routes.py: Обновление уведомления пользователя.

@bp.route('/send_message/<recipient>', methods=['GET', 'POST'])
@login_required
def send_message(recipient):
    # ...
    if form.validate_on_submit():
        # ...
        user.add_notification('unread_message_count', user.new_messages())
        db.session.commit()
        # ...
    # ...

Второе место, где мне нужно уведомить пользователя, — это когда пользователь переходит на страницу сообщений, после чего счетчик непрочитанных сообщений возвращается к нулю:


app/main/routes.py: Просмотр messages route.

@bp.route('/messages')
@login_required
def messages():
    current_user.last_message_read_time = datetime.utcnow()
    current_user.add_notification('unread_message_count', 0)
    db.session.commit()
    # ...

Теперь, когда все уведомления для пользователей поддерживаются в базе данных, я могу добавить новый маршрут, который клиент может использовать для извлечения уведомлений для зарегистрированного пользователя:


app/main/routes.py: Функция просмотра уведомлений.

from app.models import Notification

# ...

@bp.route('/notifications')
@login_required
def notifications():
    since = request.args.get('since', 0.0, type=float)
    notifications = current_user.notifications.filter(
        Notification.timestamp > since).order_by(Notification.timestamp.asc())
    return jsonify([{
        'name': n.name,
        'data': n.get_data(),
        'timestamp': n.timestamp
    } for n in notifications])

Это довольно простая функция, которая возвращает полезную нагрузку в JSON со списком уведомлений для пользователя. Каждое уведомление предоставляется как словарь с тремя элементами, именем уведомления, дополнительными данными, относящимися к уведомлению (например, количеством сообщений), и меткой времени. Уведомления доставляются в том порядке, в котором они были созданы, от самого старого до самого нового.


Я не хочу, чтобы клиенты получали повторные уведомления, поэтому я даю им возможность запрашивать уведомления только с определенного времени. Опция since «поскольку» может быть включена в строку запроса URL-адреса с временной отметкой времени запуска в качестве числа с плавающей точкой. Только уведомления, которые произошли после этого времени, будут возвращены, если этот аргумент включен.


В заключении, чтобы завершить эту функцию требуется выполнить фактический опрос в клиенте. Лучшее место для этого — в базовом шаблоне, так что все страницы автоматически наследуют поведение:


app/templates/base.html: Опрос для уведомлений.

...
{% block scripts %}
    <script>
        // ...
        {% if current_user.is_authenticated %}
        $(function() {
            var since = 0;
            setInterval(function() {
                $.ajax('{{ url_for('main.notifications') }}?since=' + since).done(
                    function(notifications) {
                        for (var i = 0; i < notifications.length; i++) {
                            if (notifications[i].name == 'unread_message_count')
                                set_message_count(notifications[i].data);
                            since = notifications[i].timestamp;
                        }
                    }
                );
            }, 10000);
        });
        {% endif %}
    </script>

Эта функция заключена в шаблон conditional, потому что я хочу опрашивать новые сообщения только тогда, когда пользователь вошел в систему. Для пользователей, которые не вошли в систему, эта функция не будет включена.


Вы уже видели $(function() { ...}) jQuery's в главе 20. Это способ регистрации функции для выполнения после загрузки страницы. Для этой функции мне нужно настроить обычный Таймер, который получает уведомления для пользователя. Вы также видели функцию setTimeout() JavaScript, которая запускает функцию, заданную в качестве аргумента после определенного времени. Функция setInterval() использует те же аргументы, что и функция setTimeout(), но вместо запуска таймера только один раз она продолжает вызывать функцию обратного вызова через регулярные промежутки времени. В этом случае мой интервал установлен в 10 секунд (Задается в миллисекундах), поэтому я собираюсь увидеть обновление значка с разрешением примерно шесть раз в минуту.


Функция, связанная с интервальным таймером, выдает Ajax-запрос для нового маршрута уведомлений, а при завершении обратного вызова просто перебирает список уведомлений. При получении уведомления с именем unread_message_count значок количества Сообщений настраивается путем вызова функции, определенной выше, с количеством, указанным в уведомлении.


То, как я обрабатываю аргумент since, может выглядеть запутанным. Я начинаю с инициализации этого аргумента равным 0. Аргумент всегда включен в запроса URL-адреса, но я не могу сгенерировать строку запроса с использованием url_for() Flask, как это было раньше, потому что url_for() запускается на сервере один раз, и мне нужен аргумент since для динамического обновления. В первый раз запрос будет отправлен в /notifications?since=0, но как только я получаю уведомление, я обновляю since до отметки времени. Это гарантирует, что я не получу дубликаты, так как я всегда прошу получать уведомления, которые произошли с момента последнего уведомления, которое я видел. Также важно отметить, что я объявлял переменную since за пределами функции интервалов, потому что я не хотел, чтобы это была локальная переменная, я хочу, чтобы одна и та же переменная использовалась во всех вызовах.


Самый простой способ попробовать это использовать два разных браузера. Войдите в микроблог в обоих браузерах, используя разных пользователей. Затем из одного из браузеров отправьте одно или несколько сообщений другому пользователю. Панель навигации другого браузера должна обновиться, чтобы показать количество сообщений, отправленных менее чем за 10 секунд. И при нажатии на ссылку "Messages" непрочитанных сообщений сбрасываться на ноль.


Туда Сюда

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 1

    0
    Огромное спасибо за вашу работу!
    Просто титанический труд!
    Очень хочется увидеть продолжение!

    Only users with full accounts can post comments. Log in, please.