Chapter 14: Ajax (v0.14)
This commit is contained in:
		
							parent
							
								
									8fce57e248
								
							
						
					
					
						commit
						5040abda19
					
				|  | @ -91,6 +91,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'), index=True) |     user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True) | ||||||
|  |     language = db.Column(db.String(5)) | ||||||
|     author = db.relationship('User', back_populates='posts') |     author = db.relationship('User', back_populates='posts') | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|  |  | ||||||
|  | @ -3,11 +3,13 @@ from flask import render_template, flash, redirect, url_for, request, g | ||||||
| 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 +26,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 +205,12 @@ 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(): | ||||||
|  |     data = request.get_json() | ||||||
|  |     return {'text': translate(data['text'], | ||||||
|  |                               data['source_language'], | ||||||
|  |                               data['dest_language'])} | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 673 B | 
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -56,5 +56,22 @@ | ||||||
|     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script> |     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script> | ||||||
|     {{ moment.include_moment() }} |     {{ moment.include_moment() }} | ||||||
|     {{ moment.lang(g.locale) }} |     {{ moment.lang(g.locale) }} | ||||||
|  |     <script> | ||||||
|  |       async function translate(sourceElem, destElem, sourceLang, destLang) { | ||||||
|  |         document.getElementById(destElem).innerHTML =  | ||||||
|  |           '<img src="{{ url_for('static', filename='loading.gif') }}">'; | ||||||
|  |         const response = await fetch('/translate', { | ||||||
|  |           method: 'POST', | ||||||
|  |           headers: {'Content-Type': 'application/json; charset=utf-8'}, | ||||||
|  |           body: JSON.stringify({ | ||||||
|  |             text: document.getElementById(sourceElem).innerText, | ||||||
|  |             source_language: sourceLang, | ||||||
|  |             dest_language: destLang | ||||||
|  |           }) | ||||||
|  |         }) | ||||||
|  |         const data = await response.json(); | ||||||
|  |         document.getElementById(destElem).innerText = data.text; | ||||||
|  |       } | ||||||
|  |     </script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | 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': 'westus' | ||||||
|  |     } | ||||||
|  |     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'] | ||||||
|  | @ -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!" | ||||||
|  |  | ||||||
|  | @ -13,4 +13,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 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,32 @@ | ||||||
|  | """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! ### | ||||||
|  |     with op.batch_alter_table('post', schema=None) as batch_op: | ||||||
|  |         batch_op.add_column(sa.Column('language', sa.String(length=5), nullable=True)) | ||||||
|  | 
 | ||||||
|  |     # ### end Alembic commands ### | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def downgrade(): | ||||||
|  |     # ### commands auto generated by Alembic - please adjust! ### | ||||||
|  |     with op.batch_alter_table('post', schema=None) as batch_op: | ||||||
|  |         batch_op.drop_column('language') | ||||||
|  | 
 | ||||||
|  |     # ### end Alembic commands ### | ||||||
		Loading…
	
		Reference in New Issue