The Flask Mega-Tutorial Part XXI: User Notifications

This is the twenty first installment of the Flask Mega-Tutorial series, in which I'm going to add a private message feature, along with user notifications that appear in the navigation bar without the need to refresh the page.

For your reference, below is a list of the articles in this series.

In this chapter I want to continue working on improving the user experience of my Microblog application. One aspect that applies to a lot of applications is the presentation of alerts or notifications to the user. Social applications show these notifications to let you know you've got new mentions or private messages, usually by showing a little badge with a number in the top navigation bar. While this is the most obvious usage, the notification pattern can be applied to a lot of other types of applications to inform the user that something requires their attention.

But to show you the techniques involved in building user notifications, I needed to extend Microblog with a feature that can benefit from them, so in the first part of this chapter I'm going to build a user messaging system that allows any user to send a private message to another user. This is actually simpler than it sounds, and it will be a good refresher on core Flask practices and a reminder of how lean, efficient and fun programming with Flask can be. And once the messaging system is in place, I'm going to discuss some options to implement a notification badge that shows a count of unread messages.

The GitHub links for this chapter are: Browse, Zip, Diff.

Private Messages

The private messaging feature that I'm going to implement is going to be very simple. When you visit the profile page of a user, there will be a link to send that user a private message. The link will take you to a new page in which a web form takes the message. To read messages sent to you, the navigation bar at the top of the page will have a new "Messages" link, that will take you to a page that is similar in structure to the index or explore pages, but instead of showing blog posts it will show messages other users sent you.

The following sections describe the steps I took to implement this feature.

Database Support for Private Messages

The first task is to extend the database to support private messages. Here is a new Message model:

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)

This model class is similar to the Post model, with the only difference that there are two user foreign keys, one for the sender and one for the recipient. The User model can get relationships for these two users, plus a new field that indicates what was the last time users read their private messages:

app/models.py: Private messages support in User model.

class User(UserMixin, db.Model):
    # ...
    messages_sent = db.relationship('Message',
                                    backref='author', lazy='dynamic')
    messages_received = db.relationship('Message',
                                        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()

The two relationships will return messages sent and received for a given user, and on the Message side of the relationship will add author and recipient back references. The reason why I used a author backref instead of a maybe more appropriate sender is that by using author I can then render these messages using the same logic that I use for blog posts. The last_message_read_time field will have the last time the user visited the messages page, and will be used to determine if there are unread messages, which will all have a timestamp newer than this field. The new_messages() helper method actually uses this field to return how many unread messages the user has. By the end of this chapter I will have this number as a nice badge in the navigation bar at the top of the page.

That completes the database changes, so now it is time to generate a new migration and upgrade the database with it:

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

Sending a Private Message

Next I'm going to work on sending messages. I'm going to need a simple web form that accepts the message:

app/main/forms.py: Private message form class.

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

And I also need the HTML template that renders this form on a web page:

app/templates/send_message.html: Send private message HTML template.

{% 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) }}
{% endblock %}

Next I'm going to add a new /send_message/<recipient> route to handle the actual sending of the private message:

app/main/routes.py: Send private message route.

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

# ...

@bp.route('/send_message/<recipient>', methods=['GET', 'POST'])
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,
        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)

I think the logic in this view function should be mostly self-explanatory. The action of sending a private message is simply carried out by adding a new Message instance to the database.

The last change that ties everything together is the addition of a link to the above route in the user profile page:

app/templates/user.html: Send private message link in user profile page.

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

Viewing Private Messages

The second big part of this feature is the viewing of private messages. For that I'm going to add another route at /messages that works in a fairly similar way to the index and explore pages, including full support for pagination:

app/main/routes.py: View messages route.

def messages():
    current_user.last_message_read_time = datetime.utcnow()
    page = request.args.get('page', 1, type=int)
    messages = current_user.messages_received.order_by(
            page=page, per_page=current_app.config['POSTS_PER_PAGE'],
    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)

The first thing I do in this view function is update the User.last_message_read_time field with the current time. This is basically marking all the messages that were sent to this user as read. Then I'm querying the Message model for the list of messages, sorted by timestamp from newer to older. I decided to reuse the POSTS_PER_PAGE configuration item here since the pages with posts and messages are going to look very much alike, but of course if the pages were to diverge, it may make sense to add a separate configuration variable for messages. The pagination logic is identical to what I used for posts, so this should all be familiar to you.

The view function above ends by rendering a new /app/templates/messages.html template file, which you can see below:

app/templates/messages.html: View messages HTML template.

{% 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') }}
            <li class="next{% if not next_url %} disabled{% endif %}">
                <a href="{{ next_url or '#' }}">
                    {{ _('Older messages') }} <span aria-hidden="true">&rarr;</span>
{% endblock %}

Here I resorted to another little trick. I noticed that Post and Message instances have pretty much the same structure, with the exception that Message gets an extra recipient relationship (that I don't need to show in the messages page, since it is always the current user). So I decided to reuse the app/templates/_post.html sub-template to also render private messages. For this reason, this template uses the strange for-loop for post in messages, so that all the references to post in the sub-template work with messages too.

To give users access to the new view function, the navigation page gets a new "Messages" link:

app/templates/base.html: Messages link in navigation bar.

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

The feature is now complete, but as part of all these changes there were some new texts that were added in a few places, and those need to be incorporated into the language translations. The first step is to update all the language catalogs:

(venv) $ flask translate update

Then each of the languages in app/translations need to have its messages.po file updated with the new translations. You can find the Spanish translations in the GitHub repository for this project or in the download zip file.

Static Message Notification Badge

Now the private messages feature is implemented, but of course there is nothing that tells a user that there are private messages waiting to be read. The simplest implementation of a navigation bar indicator can be rendered as part of the base template, using a Bootstrap badge widget:

app/templates/base.html: Static message count badge in navigation bar.

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

Here I'm invoking the new_messages() method I added to the User model above directly from the template, and storing that number in a new_messages template variable. Then if that variable is non-zero, I just add the badge with the number next to the Messages link. Here is how this looks on the page:

Messages Badge

Dynamic Message Notification Badge

The solution presented in the previous section is a decent and simple way to show a notification, but it has the disadvantage that the badge only appears when a new page is loaded. If the user spends a long time reading the content on one page without clicking on any links, new messages that come during that time will not be shown until the user finally does click on a link and loads a new page.

To make this application more useful to my users, I want the badge to update the count of unread messages on its own, without the user having to click on links and load new pages. One problem with the solution from the previous section is that the badge is only rendered to the page when the message count at the time the page loaded was non-zero. What's really more convenient is to always include the badge in the navigation bar, and mark it as hidden when the message count is zero. This would make it easy to make the badge visible using JavaScript:

app/templates/base.html: A JavaScript friendly unread messages badge.

                        <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 }}

With this version of the badge, I always include it, but the visibility CSS property is set to visible when new_messages is non-zero, or hidden if it is zero. I also added an id attribute to the <span> element that represents the badge, to make it easy to address this element using a $('#message_count') jQuery selector.

Next, I can code a short JavaScript function that updates this badge to a new number:

app/templates/base.html: Static message count badge in navigation bar.

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

This new set_message_count() function will set the number of messages in the badge element, and also adjust the visibility so that the badge is hidden when the count is 0 and visible otherwise.

Delivering Notifications to Clients

What remains now is to add a mechanism by which the client receives periodic updates regarding the number of unread messages the user has. When one of these updates occur, the client will call the set_message_count() function to make the update known to the user.

There are actually two methods for the server to deliver these updates to the client, and as you can probably guess, both have pros and cons, so which one to choose is largely dependent on the project. In the first approach, the client periodically asks the server for updates by sending an asynchronous request. The response from this request is a list of updates, which the client can use to update different elements of the page such as the unread message count badge. The second approach requires a special type of connection between the client and the server that allows the server to freely push data to the client. Note that regardless of the approach, I want to treat notifications as generic entities, so that I can extend this framework to support other types of events besides the unread messages badge.

The biggest thing the first solution has is that it is easy to implement. All I need to do is add yet another route to the application, say /notifications, which returns a JSON list of notifications. The client application then goes through the list of notifications and applies the necessary changes to the page for each one. The disadvantage of this solution is that there is going to be a delay between the actual event and the notification for it, because the client is going to request the list of notifications at regular intervals. For example, if the client is asking for notifications every 10 seconds, a notification can be received up to 10 seconds late.

The second solution requires changes at the protocol level, because HTTP does not have any provisions for a server to send data to the client without the client asking. By far the most common way to implement server initiated messages is by extending the server to support WebSocket connections in addition to HTTP. WebSocket is a protocol that unlike HTTP, establishes a permanent connection between the server and the client. The server and the client can both send data to the other party at any time, without the other side asking for it. The advantage of this mechanism is that whenever an event that is of interest to the client occurs, the server can send a notification, without any delays. The disadvantage is that WebSocket requires a more complicated setup than HTTP, because the server needs to maintain a permanent connection with each and every client. Imagine that a server that, for example, has four worker processes can typically serve a few hundred HTTP clients, because connections in HTTP are short lived and are constantly being recycled. The same server would be able to handle just four WebSocket clients, which in the vast majority of cases is going to be insufficient. It is for this limitation that WebSocket applications are typically designed around asynchronous servers, because these servers are more efficient at managing a large number of workers and active connections.

The good news is that regardless of the method that you use, in the client you will have a callback function that will be invoked with the list of updates. So I could start with the first solution, which is much easier to implement, and later, if I find it insufficient, migrate to a WebSocket server, which can be configured to invoke the same client callback. In my opinion, for this type of application the first solution is actually acceptable. A WebSocket based implementation would be useful for an application that requires updates to be delivered with near zero-latency.

In case you are curious, Twitter also uses the first approach for their navigation bar notifications. Facebook uses a variation of it called long polling, which addresses some of the limitations of straight polling while still using HTTP requests. Stack Overflow and Trello are two sites that implement WebSocket for their notifications. You can find what type of background activity occurs on any site by looking in the Network tab of the browser's debugger.

So let's go ahead and implement the polling solution. First, I'm going to add a new model to keep track of notifications for all users, along with a relationship in the user model.

app/models.py: Notification model.

import json
from time import time

# ...

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

    # ...

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

A notification is going to have a name, an associated user, a Unix timestamp and a payload. The timestamp gets its default value from the time.time() function. The payload is going to be different for each type of notification, so I'm writing it as a JSON string, as that will allow me to write lists, dictionaries or single values such as numbers or strings. I added the get_data() method as a convenience, so that the caller doesn't have to worry about the JSON deserialization.

These changes need to be included in a new database migration:

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

As a matter of convenience, I'm going to add the new Message and Notification models to the shell context, so that when I start a shell with the flask shell command, the model class is automatically imported for me:

microblog.py: Add Message model to shell context.

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

# ...

def make_shell_context():
    return {'db': db, 'User': User, 'Post': Post, 'Message': Message,
            'Notification': Notification}

I'm also going to add a add_notification() helper method in the user model to make it easier to work with these objects:

app/models.py: Notification model.

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

    def add_notification(self, name, data):
        n = Notification(name=name, payload_json=json.dumps(data), user=self)
        return n

This method not only adds a notification for the user to the database, but also ensures that if a notification with the same name already exists, it is removed first. The notification I'm going to work with is going to be called unread_message_count. If the database already has a notification with this name with, for example, a value of 3, whenever the user receives a new message and the message count goes to 4 I want to replace the old notification.

In any place where the unread message count changes, I need to call add_notification() so that I have my notifications for the user updated. There are two places where this changes. First, when the user receives a new private message, in the send_message() view function:

app/main/routes.py: Update user notification.

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

The second place where I need to notify the user is when the user goes to the messages page, at which point the unread count goes back to zero:

app/main/routes.py: View messages route.

def messages():
    current_user.last_message_read_time = datetime.utcnow()
    current_user.add_notification('unread_message_count', 0)
    # ...

Now that all the notifications for users are maintained in the database, I can add a new route that the client can use to retrieve notifications for the logged in user:

app/main/routes.py: Notifications view function.

from app.models import Notification

# ...

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])

This is a fairly simple function that returns a JSON payload with a list of notifications for the user. Each notification is given as a dictionary with three elements, the notification name, the additional data that pertains to the notification (such as the message count), and the timestamp. The notifications are delivered in the order they were created, from oldest to newest.

I do not want clients to get repeated notifications, so I'm giving them the option to only request notifications since a given time. The since option can be included in the query string of the request URL, with the unix timestamp of the starting time, as a floating point number. Only notifications that occurred after this time will be returned if this argument is included.

The final piece to complete this feature is to implement the actual polling in the client. The best place to do this is in the base template, so that all pages automatically inherit the behavior:

app/templates/base.html: Polling for notifications.

{% block scripts %}
        // ...
        {% 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')
                            since = notifications[i].timestamp;
            }, 10000);
        {% endif %}

This function is enclosed in a template conditional, because I want to poll for new messages only when the user is logged in. For users that are not logged in, this function will not be included.

You've already seen jQuery's $(function() { ...}) pattern in Chapter 20. This is how you register a function to execute after the page loads. For this feature, what I need to do on page load is to set up a regular timer that gets the notifications for the user. You've also seen the setTimeout() JavaScript function, which runs the function given as an argument after the specific time passes. The setInterval() function uses the same arguments as setTimeout(), but instead of firing the timer just once, it keeps calling the callback function at regular intervals. In this case my interval is set to 10 seconds (given in milliseconds), so I'm going to see the badge update with a resolution of roughly six times per minute.

The function associated with the interval timer issues an Ajax request for the new notifications route, and in its completion callback just iterates over the list of notifications. When a notification with name unread_message_count is received, the message count badge is adjusted by calling the function defined above with the count given in the notification.

The way I'm handling the since argument might be confusing. I start by initializing this argument to 0. The argument is always included in the request URL, but I can't generate the query string using Flask's url_for() like I've done before, because url_for() runs in the server once, and I need the since argument to dynamically update. The first time, the request is going to be sent to /notifications?since=0, but as soon as I receive a notification, I update since to its timestamp. This ensures that I don't receive duplicates, since I'm always asking to receive notifications that occurred since the last notification I've seen. It's also important to note that I declared the since variable outside of the interval function, because I did not want this to be a local variable, I want the same variable to be used in all invocations.

The easiest way to try this out is to use two different browser. Log in to Microblog on both browsers using different users. Then from one of the browsers send one or more messages to the other user. The other browser's navigation bar should update to show the count of messages that you sent in less than 10 seconds. And when you click on the Messages link the unread message count resets back to zero.


  • #26 Miguel Grinberg said 2019-01-02T13:59:37Z

    @Gege: The O'Reilly book is more centered around Flask, while the Mega-Tutorial book also includes other related topics such as full-text search, internationalization, etc.

  • #27 Kenedi nopriansyah said 2019-01-28T07:06:40Z

    sqlalchemy.exc.ArgumentError: Object User('superhero','superhero','superhero@yahoo.com') is not legal as a SQL literal value

  • #28 Miguel Grinberg said 2019-01-28T10:27:57Z

    @Kenedi: I need to see the complete error message please, including the stack trace.

  • #29 Kenedi nopriansyah said 2019-01-29T02:08:22Z

    sqlalchemy.exc.StatementError: (builtins.TypeError) float() argument must be a string or a number, not 'module' [SQL: 'INSERT INTO notification (name, auth_id, date_posted, payload_json) VALUES (?, ?, ?, ?)'] [parameters: [{'payload_json': '0', 'name': 'unread_message_count', 'auth_id': 2}]]

    class Auth(db.model): nofitications = db.relationship("Notification",backref="auth",lazy="dynamic") 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) messages = db.relationship("MessageSend",foreign_keys="MessageSend.auth_id", backref="author",lazy="dynamic") def messagesend(self,message): if not self.has_messages_send(message): send = MessageSend(auth_id=self.id,message_id=message.id) db.session.add(send) def messagenosend(self,message): if self.has_messages_send(message): MessageSend.query.filter_by(auth_id=self.id,message_id=message.id).delete() def has_messages_send(self,message): return MessageSend.query.filter( MessageSend.auth_id == self.id, MessageSend.message_id == message.id).count() > 0 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() def add_notifications(self,name,data): self.nofitications.filter_by(name=name).delete() n = Notification(name=name,payload_json=json.dumps(data),auth=self) db.session.add(n) return n

    class Notification(db.Model): id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(120),index=True) auth_id = db.Column(db.Integer,db.ForeignKey("auth.id")) date_posted = 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))

    where, my problem you can help me

  • #30 Miguel Grinberg said 2019-01-29T10:08:15Z

    @Kenedi: the default for date_posted should probably be time.time, a single time references the module, not the function.

  • #31 pongsaton rattanapriyanuch said 2019-02-11T08:43:30Z

    Hey I'm wondering. Is using SetInterval will affect performance??

  • #32 Miguel Grinberg said 2019-02-11T09:24:20Z

    @pongsaton: it really depends on what you are doing when the interval function is invoked. This is a common technique to do background tasks in a web page, but with everything, it should not be abused.

  • #33 Benjamin Dominguez said 2019-02-24T20:57:46Z

    Sorry if this was explained in a previous lesson (I'm bouncing around between chapters implementing new things into my web app as needed-- by the way, this series has been extremely helpful, thank you.) but what is up with _l and _ before some of the forms and messages to be displayed using Jinja 2 , for example {{ _l("Message") }}? Is this some javascript stuff I'm just not recognizing? Never seen that in python.

  • #34 Miguel Grinberg said 2019-02-24T21:36:06Z

    @Benjamin: That is for the internationalization of the app. See chapter 13.

  • #35 Kosta said 2019-07-09T10:58:53Z

    Hi Miguel! Thank you a lot for your blog! I've discovered strange behavior with float of 'since'. For example, we have notification with timestamp 1562667135.8978944 When I run app in a docker (mac os, locally) query like "..Notification.timestamp > 1562667135.8978944.." work correct, but when I test in a docker (ec2) return 'greater or equal' instead of expected 'greater than' value (response include Notification with ts 1562667135.8978944).

  • #36 Miguel Grinberg said 2019-07-09T16:22:32Z

    @Kosta: I don't see how that could be possible. I recommend that you add print statements for all the values that are part of this comparison to understand what's happening.

  • #37 Chris said 2019-07-23T20:11:10Z

    Hello again Miguel. I'm getting myself a little confused with the 'adding' of the message and updating the notification count in the send_message route: if form.validate_on_submit(): msg = Message(author=current_user, recipient=user, body=form.message.data) db.session.add(msg) user.add_notification('unread_message_count', user.new_messages()) db.session.commit()

    When I first saw this I didn't think it would work because I didn't think the 'INSERT' query for the new message would be triggered until db.session.commit() (i.e. after user.new_messages() is called to get the new unread message count).

    Does it work because the session is 'flushed' when Message.query.filter_by() is called in user.new_messages()?

    Sorry if this seems like a stupid question. Chris

  • #38 Miguel Grinberg said 2019-07-24T08:00:13Z

    @Chris: The message and notification objects are added in the same database transaction. They are both committed together at the end. The new_messages() query works because it is executed in the context of that same transaction. I honestly never looked at the implementation of this, but I agree with you that the most likely way this is implemented is by SQLAlchemy flushing the session down to the database before running the query.

  • #39 Hector Perez said 2019-10-21T22:00:44Z

    Hey Miguel. Thanks so much for the tutorial !

    I'm wondering if there's a way to have a an auto appearing / disappearing square with some info about the notification ?

    From what I read here, the polling causes the number at the top change, but no immediate message is ''flashed'' to the client about the msg.

    All the best !

    Thanks, Hector Perez

  • #40 Miguel Grinberg said 2019-10-22T15:27:04Z

    @Hector: You can add that functionality if you want a more visible notification, sure. You can use a JavaScript based alert box or popup, or if you prefer, the browser notification API (https://developer.mozilla.org/en-US/docs/Web/API/notification).

  • #41 Ram said 2019-11-14T12:32:28Z

    Hi Can I use this principle for Flask-SocketIO? Thanks.

  • #42 Miguel Grinberg said 2019-11-15T23:11:57Z

    @Ram: Yes. The JS logic to show notifications will remain mostly the same, but it will be triggered from a Socket.IO event instead of an Ajax response.

  • #43 Vitaliy said 2019-12-01T19:10:52Z

    Hi Miguel, looks like you forgot to add "from flask import jsonify" in main/route.py Or possibly that was added earlier and I missed it somehow... It's not a problem to checking your Git repo and add it but in this case tutorial becomes a bit less than perfect :) But anyway, your tutorials is the best! Clear, workable and covers a lot of important things! Thank you!

  • #44 Miguel Grinberg said 2019-12-01T20:12:11Z

    @Vitaliy: yeah, jsonify was used in the Ajax chapter first, so should have been in the code already.

  • #45 Kikatuso said 2019-12-06T17:38:19Z

    Hi, Your blog is really great and helpful. I have one question regarding this blog's subject. How would the relationship between the model table change if we were to send one message to many recipients? So one message would have one author and many recipients? How to then add Message objects?

    message=Post(body=form.body.data,author=current_user,recipient=???) db.session.add(message) db.session.commit()

    I do not know what to write in the place of question marks... thanks!

  • #46 Miguel Grinberg said 2019-12-07T11:47:11Z

    @Kikatuso: using the current database structure, you would need to insert a separate message for each recipient. You can also add a new table message_recipients with a one-to-many relationship from the messages table. This will replace the single recipient.

  • #47 Kikatuso said 2019-12-14T16:52:32Z

    Thanks you very much. Creating a different relationship between models helped me solve my problem. I actually have only one more small question. About the “since” variable that is initialised to be 0 in the beginning. This means that every time you refresh the page, since will go back to zero. So if you have notification that does not delete the previous notifications, so for instance you have a notification for somebody liking your post, then everytime you refresh a page you get all the notification you’ve ever been given. Which is not good. How do you make since run forever till when you register on the page? Or is this something that required more advanced notification system? I hope you understand what I mean. Thank you very much.

  • #48 Miguel Grinberg said 2019-12-14T23:53:56Z

    @Kikatuso: Some notifications (such as the unread message count) need to be repeated when you refresh the page. For notifications that you do not want to reappear after a page refresh you have two options. The simplest is to delete notifications once they are delivered to the client. A more sophisticated option is to add a "delivered" attribute to each notification, so that only new notifications are given to the client and not the old ones. Hope this helps!

  • #49 dhanipro said 2019-12-31T07:12:47Z

    hi miguel For notifications, why do you have to make a new model? is it possible if we add a field to the Model Message that is "is_read" has a boolean value? so for notifications we can sort by is_read = False?

  • #50 Miguel Grinberg said 2019-12-31T08:17:43Z

    @dhanipro: you can do that if you like, but then you have to manage the is_read flag for each notification, which is tedious.

Leave a Comment