Chapter 14: Ajax (v0.14)

This commit is contained in:
Miguel Grinberg 2017-10-05 15:34:15 -07:00
parent 8a56c34ce2
commit 7f7c8ef5d7
No known key found for this signature in database
GPG Key ID: 36848B262DF5F06C
9 changed files with 123 additions and 16 deletions

View File

@ -86,6 +86,7 @@ class Post(db.Model):
body = db.Column(db.String(140)) body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
language = db.Column(db.String(5))
def __repr__(self): def __repr__(self):
return '<Post {}>'.format(self.body) return '<Post {}>'.format(self.body)

View File

@ -1,13 +1,16 @@
from datetime import datetime from datetime import datetime
from flask import render_template, flash, redirect, url_for, request, g from flask import render_template, flash, redirect, url_for, request, g, \
jsonify
from flask_login import login_user, logout_user, current_user, login_required from flask_login import login_user, logout_user, current_user, login_required
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
from flask_babel import _, get_locale from flask_babel import _, get_locale
from langdetect import detect, LangDetectException
from app import app, db from app import app, db
from app.forms import LoginForm, RegistrationForm, EditProfileForm, \ from app.forms import LoginForm, RegistrationForm, EditProfileForm, \
EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm
from app.models import User, Post from app.models import User, Post
from app.email import send_password_reset_email from app.email import send_password_reset_email
from app.translate import translate
@app.before_request @app.before_request
@ -24,7 +27,12 @@ def before_request():
def index(): def index():
form = PostForm() form = PostForm()
if form.validate_on_submit(): if form.validate_on_submit():
post = Post(body=form.post.data, author=current_user) try:
language = detect(form.post.data)
except LangDetectException:
language = ''
post = Post(body=form.post.data, author=current_user,
language=language)
db.session.add(post) db.session.add(post)
db.session.commit() db.session.commit()
flash(_('Your post is now live!')) flash(_('Your post is now live!'))
@ -198,3 +206,11 @@ def unfollow(username):
return redirect(url_for('user', username=username)) return redirect(url_for('user', username=username))
else: else:
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/translate', methods=['POST'])
@login_required
def translate_text():
return jsonify({'text': translate(request.form['text'],
request.form['source_language'],
request.form['dest_language'])})

BIN
app/static/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@ -14,7 +14,17 @@
{{ _('%(username)s said %(when)s', {{ _('%(username)s said %(when)s',
username=user_link, when=moment(post.timestamp).fromNow()) }} username=user_link, when=moment(post.timestamp).fromNow()) }}
<br> <br>
{{ post.body }} <span id="post{{ post.id }}">{{ post.body }}</span>
{% if post.language and post.language != g.locale %}
<br><br>
<span id="translation{{ post.id }}">
<a href="javascript:translate(
'#post{{ post.id }}',
'#translation{{ post.id }}',
'{{ post.language }}',
'{{ g.locale }}');">{{ _('Translate') }}</a>
</span>
{% endif %}
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -53,4 +53,18 @@
{{ super() }} {{ super() }}
{{ moment.include_moment() }} {{ moment.include_moment() }}
{{ moment.lang(g.locale) }} {{ moment.lang(g.locale) }}
<script>
function translate(sourceElem, destElem, sourceLang, destLang) {
$(destElem).html('<img src="{{ url_for('static', filename='loading.gif') }}">');
$.post('/translate', {
text: $(sourceElem).text(),
source_language: sourceLang,
dest_language: destLang
}).done(function(response) {
$(destElem).text(response['text'])
}).fail(function() {
$(destElem).text("{{ _('Error: Could not contact server.') }}");
});
}
</script>
{% endblock %} {% endblock %}

21
app/translate.py Normal file
View File

@ -0,0 +1,21 @@
import json
import requests
from flask_babel import _
from app import app
def translate(text, source_language, dest_language):
if 'MS_TRANSLATOR_KEY' not in app.config or \
not app.config['MS_TRANSLATOR_KEY']:
return _('Error: the translation service is not configured.')
auth = {
'Ocp-Apim-Subscription-Key': app.config['MS_TRANSLATOR_KEY'],
'Ocp-Apim-Subscription-Region': 'westus2'}
r = requests.post(
'https://api.cognitive.microsofttranslator.com'
'/translate?api-version=3.0&from={}&to={}'.format(
source_language, dest_language), headers=auth, json=[
{'Text': text}])
if r.status_code != 200:
return _('Error: the translation service failed.')
return r.json()[0]['translations'][0]['text']

View File

@ -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-10-03 15:49-0700\n" "POT-Creation-Date: 2017-10-05 15:32-0700\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"
@ -82,57 +82,65 @@ msgstr "Dí algo"
msgid "Search" msgid "Search"
msgstr "Buscar" msgstr "Buscar"
#: app/routes.py:30 #: app/routes.py:37
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/routes.py:66 #: app/routes.py:73
msgid "Invalid username or password" msgid "Invalid username or password"
msgstr "Nombre de usuario o contraseña inválidos" msgstr "Nombre de usuario o contraseña inválidos"
#: app/routes.py:92 #: app/routes.py:99
msgid "Congratulations, you are now a registered user!" msgid "Congratulations, you are now a registered user!"
msgstr "¡Felicitaciones, ya eres un usuario registrado!" msgstr "¡Felicitaciones, ya eres un usuario registrado!"
#: app/routes.py:107 #: app/routes.py:114
msgid "Check your email for the instructions to reset your password" msgid "Check your email for the instructions to reset your password"
msgstr "Busca en tu email las instrucciones para crear una nueva contraseña" msgstr "Busca en tu email las instrucciones para crear una nueva contraseña"
#: app/routes.py:124 #: app/routes.py:131
msgid "Your password has been reset." msgid "Your password has been reset."
msgstr "Tu contraseña ha sido cambiada." msgstr "Tu contraseña ha sido cambiada."
#: app/routes.py:152 #: app/routes.py:159
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/routes.py:157 app/templates/edit_profile.html:5 #: app/routes.py:164 app/templates/edit_profile.html:5
msgid "Edit Profile" msgid "Edit Profile"
msgstr "Editar Perfil" msgstr "Editar Perfil"
#: app/routes.py:166 app/routes.py:182 #: app/routes.py:173 app/routes.py:189
#, 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/routes.py:169 #: app/routes.py:176
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/routes.py:173 #: app/routes.py:180
#, 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/routes.py:185 #: app/routes.py:192
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/routes.py:189 #: app/routes.py:196
#, 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/translate.py:10
msgid "Error: the translation service is not configured."
msgstr "Error: el servicio de traducciones no está configurado."
#: app/translate.py:17
msgid "Error: the translation service failed."
msgstr "Error el servicio de traducciones ha fallado."
#: app/templates/404.html:4 #: app/templates/404.html:4
msgid "Not Found" msgid "Not Found"
msgstr "Página No Encontrada" msgstr "Página No Encontrada"
@ -154,6 +162,10 @@ msgstr "El administrador ha sido notificado. ¡Lamentamos la inconveniencia!"
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:19
msgid "Translate"
msgstr "Traducir"
#: app/templates/base.html:4 #: app/templates/base.html:4
msgid "Welcome to Microblog" msgid "Welcome to Microblog"
msgstr "Bienvenido a Microblog" msgstr "Bienvenido a Microblog"
@ -178,6 +190,10 @@ msgstr "Perfil"
msgid "Logout" msgid "Logout"
msgstr "Salir" msgstr "Salir"
#: app/templates/base.html:73
msgid "Error: Could not contact server."
msgstr "Error: el servidor no pudo ser contactado."
#: app/templates/index.html:5 #: app/templates/index.html:5
#, python-format #, python-format
msgid "Hi, %(username)s!" msgid "Hi, %(username)s!"

View File

@ -14,4 +14,5 @@ class Config(object):
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['your-email@example.com'] ADMINS = ['your-email@example.com']
LANGUAGES = ['en', 'es'] LANGUAGES = ['en', 'es']
MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')
POSTS_PER_PAGE = 25 POSTS_PER_PAGE = 25

View File

@ -0,0 +1,28 @@
"""add language to posts
Revision ID: 2b017edaa91f
Revises: ae346256b650
Create Date: 2017-10-04 22:48:34.494465
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2b017edaa91f'
down_revision = 'ae346256b650'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('language', sa.String(length=5), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'language')
# ### end Alembic commands ###