Chapter 21: User Notifications (v0.21)
This commit is contained in:
		
							parent
							
								
									aa3abc034b
								
							
						
					
					
						commit
						e4ae1095d3
					
				| 
						 | 
					@ -41,3 +41,9 @@ class SearchForm(FlaskForm):
 | 
				
			||||||
        if 'csrf_enabled' not in kwargs:
 | 
					        if 'csrf_enabled' not in kwargs:
 | 
				
			||||||
            kwargs['csrf_enabled'] = False
 | 
					            kwargs['csrf_enabled'] = False
 | 
				
			||||||
        super(SearchForm, self).__init__(*args, **kwargs)
 | 
					        super(SearchForm, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MessageForm(FlaskForm):
 | 
				
			||||||
 | 
					    message = TextAreaField(_l('Message'), validators=[
 | 
				
			||||||
 | 
					        DataRequired(), Length(min=1, max=140)])
 | 
				
			||||||
 | 
					    submit = SubmitField(_l('Submit'))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,8 +5,9 @@ from flask_login import current_user, login_required
 | 
				
			||||||
from flask_babel import _, get_locale
 | 
					from flask_babel import _, get_locale
 | 
				
			||||||
from guess_language import guess_language
 | 
					from guess_language import guess_language
 | 
				
			||||||
from app import db
 | 
					from app import db
 | 
				
			||||||
from app.main.forms import EditProfileForm, EmptyForm, PostForm, SearchForm
 | 
					from app.main.forms import EditProfileForm, EmptyForm, PostForm, SearchForm, \
 | 
				
			||||||
from app.models import User, Post
 | 
					    MessageForm
 | 
				
			||||||
 | 
					from app.models import User, Post, Message, Notification
 | 
				
			||||||
from app.translate import translate
 | 
					from app.translate import translate
 | 
				
			||||||
from app.main import bp
 | 
					from app.main import bp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,3 +166,51 @@ def search():
 | 
				
			||||||
        if page > 1 else None
 | 
					        if page > 1 else None
 | 
				
			||||||
    return render_template('search.html', title=_('Search'), posts=posts,
 | 
					    return render_template('search.html', title=_('Search'), posts=posts,
 | 
				
			||||||
                           next_url=next_url, prev_url=prev_url)
 | 
					                           next_url=next_url, prev_url=prev_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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)
 | 
				
			||||||
 | 
					        user.add_notification('unread_message_count', user.new_messages())
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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()
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from hashlib import md5
 | 
					from hashlib import md5
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
from time import time
 | 
					from time import time
 | 
				
			||||||
from flask import current_app
 | 
					from flask import current_app
 | 
				
			||||||
from flask_login import UserMixin
 | 
					from flask_login import UserMixin
 | 
				
			||||||
| 
						 | 
					@ -72,6 +73,15 @@ class User(UserMixin, db.Model):
 | 
				
			||||||
        primaryjoin=(followers.c.follower_id == id),
 | 
					        primaryjoin=(followers.c.follower_id == id),
 | 
				
			||||||
        secondaryjoin=(followers.c.followed_id == id),
 | 
					        secondaryjoin=(followers.c.followed_id == id),
 | 
				
			||||||
        backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')
 | 
					        backref=db.backref('followers', lazy='dynamic'), 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)
 | 
				
			||||||
 | 
					    notifications = db.relationship('Notification', backref='user',
 | 
				
			||||||
 | 
					                                    lazy='dynamic')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return '<User {}>'.format(self.username)
 | 
					        return '<User {}>'.format(self.username)
 | 
				
			||||||
| 
						 | 
					@ -121,6 +131,17 @@ class User(UserMixin, db.Model):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        return User.query.get(id)
 | 
					        return User.query.get(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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_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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login.user_loader
 | 
					@login.user_loader
 | 
				
			||||||
def load_user(id):
 | 
					def load_user(id):
 | 
				
			||||||
| 
						 | 
					@ -137,3 +158,25 @@ class Post(SearchableMixin, db.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return '<Post {}>'.format(self.body)
 | 
					        return '<Post {}>'.format(self.body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,16 @@
 | 
				
			||||||
                    {% if current_user.is_anonymous %}
 | 
					                    {% if current_user.is_anonymous %}
 | 
				
			||||||
                    <li><a href="{{ url_for('auth.login') }}">{{ _('Login') }}</a></li>
 | 
					                    <li><a href="{{ url_for('auth.login') }}">{{ _('Login') }}</a></li>
 | 
				
			||||||
                    {% else %}
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <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>
 | 
				
			||||||
                    <li><a href="{{ url_for('main.user', username=current_user.username) }}">{{ _('Profile') }}</a></li>
 | 
					                    <li><a href="{{ url_for('main.user', username=current_user.username) }}">{{ _('Profile') }}</a></li>
 | 
				
			||||||
                    <li><a href="{{ url_for('auth.logout') }}">{{ _('Logout') }}</a></li>
 | 
					                    <li><a href="{{ url_for('auth.logout') }}">{{ _('Logout') }}</a></li>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
| 
						 | 
					@ -115,5 +125,25 @@
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        function set_message_count(n) {
 | 
				
			||||||
 | 
					            $('#message_count').text(n);
 | 
				
			||||||
 | 
					            $('#message_count').css('visibility', n ? 'visible' : 'hidden');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        {% 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>
 | 
					    </script>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					{% 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">←</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">→</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					{% 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 %}
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,9 @@
 | 
				
			||||||
                    </form>
 | 
					                    </form>
 | 
				
			||||||
                </p>
 | 
					                </p>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if user != current_user %}
 | 
				
			||||||
 | 
					                <p><a href="{{ url_for('main.send_message', recipient=user.username) }}">{{ _('Send private message') }}</a></p>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ msgid ""
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: PROJECT VERSION\n"
 | 
					"Project-Id-Version: PROJECT VERSION\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
					"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
				
			||||||
"POT-Creation-Date: 2017-11-25 18:23-0800\n"
 | 
					"POT-Creation-Date: 2017-11-25 18:26-0800\n"
 | 
				
			||||||
"PO-Revision-Date: 2017-09-29 23:25-0700\n"
 | 
					"PO-Revision-Date: 2017-09-29 23:25-0700\n"
 | 
				
			||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
					"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
				
			||||||
"Language: es\n"
 | 
					"Language: es\n"
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@ msgstr "Tu contraseña ha sido cambiada."
 | 
				
			||||||
msgid "About me"
 | 
					msgid "About me"
 | 
				
			||||||
msgstr "Acerca de mí"
 | 
					msgstr "Acerca de mí"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/forms.py:13 app/main/forms.py:28
 | 
					#: app/main/forms.py:13 app/main/forms.py:28 app/main/forms.py:44
 | 
				
			||||||
msgid "Submit"
 | 
					msgid "Submit"
 | 
				
			||||||
msgstr "Enviar"
 | 
					msgstr "Enviar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,47 +106,59 @@ msgstr "Dí algo"
 | 
				
			||||||
msgid "Search"
 | 
					msgid "Search"
 | 
				
			||||||
msgstr "Buscar"
 | 
					msgstr "Buscar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/main/forms.py:43
 | 
				
			||||||
 | 
					msgid "Message"
 | 
				
			||||||
 | 
					msgstr "Mensaje"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:36
 | 
					#: app/main/routes.py:36
 | 
				
			||||||
msgid "Your post is now live!"
 | 
					msgid "Your post is now live!"
 | 
				
			||||||
msgstr "¡Tu artículo ha sido publicado!"
 | 
					msgstr "¡Tu artículo ha sido publicado!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:87
 | 
					#: app/main/routes.py:94
 | 
				
			||||||
msgid "Your changes have been saved."
 | 
					msgid "Your changes have been saved."
 | 
				
			||||||
msgstr "Tus cambios han sido salvados."
 | 
					msgstr "Tus cambios han sido salvados."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:92 app/templates/edit_profile.html:5
 | 
					#: app/main/routes.py:99 app/templates/edit_profile.html:5
 | 
				
			||||||
msgid "Edit Profile"
 | 
					msgid "Edit Profile"
 | 
				
			||||||
msgstr "Editar Perfil"
 | 
					msgstr "Editar Perfil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:101 app/main/routes.py:117
 | 
					#: app/main/routes.py:108 app/main/routes.py:124
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "User %(username)s not found."
 | 
					msgid "User %(username)s not found."
 | 
				
			||||||
msgstr "El usuario %(username)s no ha sido encontrado."
 | 
					msgstr "El usuario %(username)s no ha sido encontrado."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:104
 | 
					#: app/main/routes.py:111
 | 
				
			||||||
msgid "You cannot follow yourself!"
 | 
					msgid "You cannot follow yourself!"
 | 
				
			||||||
msgstr "¡No te puedes seguir a tí mismo!"
 | 
					msgstr "¡No te puedes seguir a tí mismo!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:108
 | 
					#: app/main/routes.py:115
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "You are following %(username)s!"
 | 
					msgid "You are following %(username)s!"
 | 
				
			||||||
msgstr "¡Ahora estás siguiendo a %(username)s!"
 | 
					msgstr "¡Ahora estás siguiendo a %(username)s!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:120
 | 
					#: app/main/routes.py:127
 | 
				
			||||||
msgid "You cannot unfollow yourself!"
 | 
					msgid "You cannot unfollow yourself!"
 | 
				
			||||||
msgstr "¡No te puedes dejar de seguir a tí mismo!"
 | 
					msgstr "¡No te puedes dejar de seguir a tí mismo!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/main/routes.py:124
 | 
					#: app/main/routes.py:131
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "You are not following %(username)s."
 | 
					msgid "You are not following %(username)s."
 | 
				
			||||||
msgstr "No estás siguiendo a %(username)s."
 | 
					msgstr "No estás siguiendo a %(username)s."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/_post.html:14
 | 
					#: app/main/routes.py:170
 | 
				
			||||||
 | 
					msgid "Your message has been sent."
 | 
				
			||||||
 | 
					msgstr "Tu mensaje ha sido enviado."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/main/routes.py:172
 | 
				
			||||||
 | 
					msgid "Send Message"
 | 
				
			||||||
 | 
					msgstr "Enviar Mensaje"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/_post.html:16
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "%(username)s said %(when)s"
 | 
					msgid "%(username)s said %(when)s"
 | 
				
			||||||
msgstr "%(username)s dijo %(when)s"
 | 
					msgstr "%(username)s dijo %(when)s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/_post.html:25
 | 
					#: app/templates/_post.html:27
 | 
				
			||||||
msgid "Translate"
 | 
					msgid "Translate"
 | 
				
			||||||
msgstr "Traducir"
 | 
					msgstr "Traducir"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,15 +178,19 @@ msgstr "Explorar"
 | 
				
			||||||
msgid "Login"
 | 
					msgid "Login"
 | 
				
			||||||
msgstr "Ingresar"
 | 
					msgstr "Ingresar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/base.html:35
 | 
					#: app/templates/base.html:36 app/templates/messages.html:4
 | 
				
			||||||
 | 
					msgid "Messages"
 | 
				
			||||||
 | 
					msgstr "Mensajes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/base.html:45
 | 
				
			||||||
msgid "Profile"
 | 
					msgid "Profile"
 | 
				
			||||||
msgstr "Perfil"
 | 
					msgstr "Perfil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/base.html:36
 | 
					#: app/templates/base.html:46
 | 
				
			||||||
msgid "Logout"
 | 
					msgid "Logout"
 | 
				
			||||||
msgstr "Salir"
 | 
					msgstr "Salir"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/base.html:73
 | 
					#: app/templates/base.html:83
 | 
				
			||||||
msgid "Error: Could not contact server."
 | 
					msgid "Error: Could not contact server."
 | 
				
			||||||
msgstr "Error: el servidor no pudo ser contactado."
 | 
					msgstr "Error: el servidor no pudo ser contactado."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -183,40 +199,53 @@ msgstr "Error: el servidor no pudo ser contactado."
 | 
				
			||||||
msgid "Hi, %(username)s!"
 | 
					msgid "Hi, %(username)s!"
 | 
				
			||||||
msgstr "¡Hola, %(username)s!"
 | 
					msgstr "¡Hola, %(username)s!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/index.html:17 app/templates/user.html:31
 | 
					#: app/templates/index.html:17 app/templates/user.html:34
 | 
				
			||||||
msgid "Newer posts"
 | 
					msgid "Newer posts"
 | 
				
			||||||
msgstr "Artículos siguientes"
 | 
					msgstr "Artículos siguientes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/index.html:22 app/templates/user.html:36
 | 
					#: app/templates/index.html:22 app/templates/user.html:39
 | 
				
			||||||
msgid "Older posts"
 | 
					msgid "Older posts"
 | 
				
			||||||
msgstr "Artículos previos"
 | 
					msgstr "Artículos previos"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/messages.html:12
 | 
				
			||||||
 | 
					msgid "Newer messages"
 | 
				
			||||||
 | 
					msgstr "Mensajes siguientes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/messages.html:17
 | 
				
			||||||
 | 
					msgid "Older messages"
 | 
				
			||||||
 | 
					msgstr "Mensajes previos"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/search.html:4
 | 
					#: app/templates/search.html:4
 | 
				
			||||||
msgid "Search Results"
 | 
					msgid "Search Results"
 | 
				
			||||||
msgstr "Resultados de Búsqueda"
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/search.html:12
 | 
					#: app/templates/search.html:12
 | 
				
			||||||
msgid "Previous results"
 | 
					msgid "Previous results"
 | 
				
			||||||
msgstr "Resultados previos"
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/search.html:17
 | 
					#: app/templates/search.html:17
 | 
				
			||||||
msgid "Next results"
 | 
					msgid "Next results"
 | 
				
			||||||
msgstr "Resultados próximos"
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/send_message.html:5
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "Send Message to %(recipient)s"
 | 
				
			||||||
 | 
					msgstr "Enviar Mensaje a %(recipient)s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:8
 | 
					#: app/templates/user.html:8
 | 
				
			||||||
msgid "User"
 | 
					msgid "User"
 | 
				
			||||||
msgstr "Usuario"
 | 
					msgstr "Usuario"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:11
 | 
					#: app/templates/user.html:11 app/templates/user_popup.html:9
 | 
				
			||||||
msgid "Last seen on"
 | 
					msgid "Last seen on"
 | 
				
			||||||
msgstr "Última visita"
 | 
					msgstr "Última visita"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:13
 | 
					#: app/templates/user.html:13 app/templates/user_popup.html:11
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "%(count)d followers"
 | 
					msgid "%(count)d followers"
 | 
				
			||||||
msgstr "%(count)d seguidores"
 | 
					msgstr "%(count)d seguidores"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:13
 | 
					#: app/templates/user.html:13 app/templates/user_popup.html:11
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "%(count)d following"
 | 
					msgid "%(count)d following"
 | 
				
			||||||
msgstr "siguiendo a %(count)d"
 | 
					msgstr "siguiendo a %(count)d"
 | 
				
			||||||
| 
						 | 
					@ -225,14 +254,18 @@ msgstr "siguiendo a %(count)d"
 | 
				
			||||||
msgid "Edit your profile"
 | 
					msgid "Edit your profile"
 | 
				
			||||||
msgstr "Editar tu perfil"
 | 
					msgstr "Editar tu perfil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:17
 | 
					#: app/templates/user.html:17 app/templates/user_popup.html:14
 | 
				
			||||||
msgid "Follow"
 | 
					msgid "Follow"
 | 
				
			||||||
msgstr "Seguir"
 | 
					msgstr "Seguir"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/user.html:19
 | 
					#: app/templates/user.html:19 app/templates/user_popup.html:16
 | 
				
			||||||
msgid "Unfollow"
 | 
					msgid "Unfollow"
 | 
				
			||||||
msgstr "Dejar de seguir"
 | 
					msgstr "Dejar de seguir"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app/templates/user.html:22
 | 
				
			||||||
 | 
					msgid "Send private message"
 | 
				
			||||||
 | 
					msgstr "Enviar mensaje privado"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: app/templates/auth/login.html:12
 | 
					#: app/templates/auth/login.html:12
 | 
				
			||||||
msgid "New User?"
 | 
					msgid "New User?"
 | 
				
			||||||
msgstr "¿Usuario Nuevo?"
 | 
					msgstr "¿Usuario Nuevo?"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
from app import create_app, db, cli
 | 
					from app import create_app, db, cli
 | 
				
			||||||
from app.models import User, Post
 | 
					from app.models import User, Post, Message, Notification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = create_app()
 | 
					app = create_app()
 | 
				
			||||||
cli.register(app)
 | 
					cli.register(app)
 | 
				
			||||||
| 
						 | 
					@ -7,4 +7,5 @@ cli.register(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.shell_context_processor
 | 
					@app.shell_context_processor
 | 
				
			||||||
def make_shell_context():
 | 
					def make_shell_context():
 | 
				
			||||||
    return {'db': db, 'User': User, 'Post': Post}
 | 
					    return {'db': db, 'User': User, 'Post': Post, 'Message': Message,
 | 
				
			||||||
 | 
					            'Notification': Notification}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					"""private messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Revision ID: d049de007ccf
 | 
				
			||||||
 | 
					Revises: 834b1a697901
 | 
				
			||||||
 | 
					Create Date: 2017-11-12 23:30:28.571784
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from alembic import op
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revision identifiers, used by Alembic.
 | 
				
			||||||
 | 
					revision = 'd049de007ccf'
 | 
				
			||||||
 | 
					down_revision = '2b017edaa91f'
 | 
				
			||||||
 | 
					branch_labels = None
 | 
				
			||||||
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.create_table('message',
 | 
				
			||||||
 | 
					    sa.Column('id', sa.Integer(), nullable=False),
 | 
				
			||||||
 | 
					    sa.Column('sender_id', sa.Integer(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('recipient_id', sa.Integer(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('body', sa.String(length=140), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('timestamp', sa.DateTime(), nullable=True),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['recipient_id'], ['user.id'], ),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['sender_id'], ['user.id'], ),
 | 
				
			||||||
 | 
					    sa.PrimaryKeyConstraint('id')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    op.create_index(op.f('ix_message_timestamp'), 'message', ['timestamp'], unique=False)
 | 
				
			||||||
 | 
					    op.add_column('user', sa.Column('last_message_read_time', sa.DateTime(), nullable=True))
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.drop_column('user', 'last_message_read_time')
 | 
				
			||||||
 | 
					    op.drop_index(op.f('ix_message_timestamp'), table_name='message')
 | 
				
			||||||
 | 
					    op.drop_table('message')
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					"""notifications
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Revision ID: f7ac3d27bb1d
 | 
				
			||||||
 | 
					Revises: d049de007ccf
 | 
				
			||||||
 | 
					Create Date: 2017-11-22 19:48:39.945858
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from alembic import op
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revision identifiers, used by Alembic.
 | 
				
			||||||
 | 
					revision = 'f7ac3d27bb1d'
 | 
				
			||||||
 | 
					down_revision = 'd049de007ccf'
 | 
				
			||||||
 | 
					branch_labels = None
 | 
				
			||||||
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.create_table('notification',
 | 
				
			||||||
 | 
					    sa.Column('id', sa.Integer(), nullable=False),
 | 
				
			||||||
 | 
					    sa.Column('name', sa.String(length=128), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('user_id', sa.Integer(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('timestamp', sa.Float(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('payload_json', sa.Text(), nullable=True),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
 | 
				
			||||||
 | 
					    sa.PrimaryKeyConstraint('id')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    op.create_index(op.f('ix_notification_name'), 'notification', ['name'], unique=False)
 | 
				
			||||||
 | 
					    op.create_index(op.f('ix_notification_timestamp'), 'notification', ['timestamp'], unique=False)
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.drop_index(op.f('ix_notification_timestamp'), table_name='notification')
 | 
				
			||||||
 | 
					    op.drop_index(op.f('ix_notification_name'), table_name='notification')
 | 
				
			||||||
 | 
					    op.drop_table('notification')
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
		Loading…
	
		Reference in New Issue