Chapter 10: Email Support (v0.10)
This commit is contained in:
parent
fdeb27d052
commit
14f6f99ea9
|
@ -5,6 +5,7 @@ from flask import Flask
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager
|
||||
from flask_mail import Mail
|
||||
from config import Config
|
||||
|
||||
app = Flask(__name__)
|
||||
|
@ -13,6 +14,7 @@ db = SQLAlchemy(app)
|
|||
migrate = Migrate(app, db)
|
||||
login = LoginManager(app)
|
||||
login.login_view = 'login'
|
||||
mail = Mail(app)
|
||||
|
||||
if not app.debug:
|
||||
if app.config['MAIL_SERVER']:
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
from threading import Thread
|
||||
from flask import render_template
|
||||
from flask_mail import Message
|
||||
from app import app, mail
|
||||
|
||||
|
||||
def send_async_email(app, msg):
|
||||
with app.app_context():
|
||||
mail.send(msg)
|
||||
|
||||
|
||||
def send_email(subject, sender, recipients, text_body, html_body):
|
||||
msg = Message(subject, sender=sender, recipients=recipients)
|
||||
msg.body = text_body
|
||||
msg.html = html_body
|
||||
Thread(target=send_async_email, args=(app, msg)).start()
|
||||
|
||||
|
||||
def send_password_reset_email(user):
|
||||
token = user.get_reset_password_token()
|
||||
send_email('[Microblog] Reset Your Password',
|
||||
sender=app.config['ADMINS'][0],
|
||||
recipients=[user.email],
|
||||
text_body=render_template('email/reset_password.txt',
|
||||
user=user, token=token),
|
||||
html_body=render_template('email/reset_password.html',
|
||||
user=user, token=token))
|
12
app/forms.py
12
app/forms.py
|
@ -32,6 +32,18 @@ class RegistrationForm(FlaskForm):
|
|||
raise ValidationError('Please use a different email address.')
|
||||
|
||||
|
||||
class ResetPasswordRequestForm(FlaskForm):
|
||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
password2 = PasswordField(
|
||||
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
|
||||
class EditProfileForm(FlaskForm):
|
||||
username = StringField('Username', validators=[DataRequired()])
|
||||
about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from datetime import datetime
|
||||
from hashlib import md5
|
||||
from app import db, login
|
||||
from time import time
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import jwt
|
||||
from app import app, db, login
|
||||
|
||||
|
||||
followers = db.Table(
|
||||
|
@ -59,6 +61,20 @@ class User(UserMixin, db.Model):
|
|||
own = Post.query.filter_by(user_id=self.id)
|
||||
return followed.union(own).order_by(Post.timestamp.desc())
|
||||
|
||||
def get_reset_password_token(self, expires_in=600):
|
||||
return jwt.encode(
|
||||
{'reset_password': self.id, 'exp': time() + expires_in},
|
||||
app.config['SECRET_KEY'], algorithm='HS256')
|
||||
|
||||
@staticmethod
|
||||
def verify_reset_password_token(token):
|
||||
try:
|
||||
id = jwt.decode(token, app.config['SECRET_KEY'],
|
||||
algorithms=['HS256'])['reset_password']
|
||||
except:
|
||||
return
|
||||
return User.query.get(id)
|
||||
|
||||
|
||||
@login.user_loader
|
||||
def load_user(id):
|
||||
|
|
|
@ -4,8 +4,9 @@ from flask_login import login_user, logout_user, current_user, login_required
|
|||
from werkzeug.urls import url_parse
|
||||
from app import app, db
|
||||
from app.forms import LoginForm, RegistrationForm, EditProfileForm, \
|
||||
EmptyForm, PostForm
|
||||
EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm
|
||||
from app.models import User, Post
|
||||
from app.email import send_password_reset_email
|
||||
|
||||
|
||||
@app.before_request
|
||||
|
@ -91,6 +92,37 @@ def register():
|
|||
return render_template('register.html', title='Register', form=form)
|
||||
|
||||
|
||||
@app.route('/reset_password_request', methods=['GET', 'POST'])
|
||||
def reset_password_request():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('index'))
|
||||
form = ResetPasswordRequestForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user:
|
||||
send_password_reset_email(user)
|
||||
flash('Check your email for the instructions to reset your password')
|
||||
return redirect(url_for('login'))
|
||||
return render_template('reset_password_request.html',
|
||||
title='Reset Password', form=form)
|
||||
|
||||
|
||||
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
|
||||
def reset_password(token):
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('index'))
|
||||
user = User.verify_reset_password_token(token)
|
||||
if not user:
|
||||
return redirect(url_for('index'))
|
||||
form = ResetPasswordForm()
|
||||
if form.validate_on_submit():
|
||||
user.set_password(form.password.data)
|
||||
db.session.commit()
|
||||
flash('Your password has been reset.')
|
||||
return redirect(url_for('login'))
|
||||
return render_template('reset_password.html', form=form)
|
||||
|
||||
|
||||
@app.route('/user/<username>')
|
||||
@login_required
|
||||
def user(username):
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<p>Dear {{ user.username }},</p>
|
||||
<p>
|
||||
To reset your password
|
||||
<a href="{{ url_for('reset_password', token=token, _external=True) }}">
|
||||
click here
|
||||
</a>.
|
||||
</p>
|
||||
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
|
||||
<p>{{ url_for('reset_password', token=token, _external=True) }}</p>
|
||||
<p>If you have not requested a password reset simply ignore this message.</p>
|
||||
<p>Sincerely,</p>
|
||||
<p>The Microblog Team</p>
|
|
@ -0,0 +1,11 @@
|
|||
Dear {{ user.username }},
|
||||
|
||||
To reset your password click on the following link:
|
||||
|
||||
{{ url_for('reset_password', token=token, _external=True) }}
|
||||
|
||||
If you have not requested a password reset simply ignore this message.
|
||||
|
||||
Sincerely,
|
||||
|
||||
The Microblog Team
|
|
@ -22,4 +22,8 @@
|
|||
<p>{{ form.submit() }}</p>
|
||||
</form>
|
||||
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
|
||||
<p>
|
||||
Forgot Your Password?
|
||||
<a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Reset Your Password</h1>
|
||||
<form action="" method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
<p>
|
||||
{{ form.password.label }}<br>
|
||||
{{ form.password(size=32) }}<br>
|
||||
{% for error in form.password.errors %}
|
||||
<span style="color: red;">[{{ error }}]</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.password2.label }}<br>
|
||||
{{ form.password2(size=32) }}<br>
|
||||
{% for error in form.password2.errors %}
|
||||
<span style="color: red;">[{{ error }}]</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>{{ form.submit() }}</p>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,16 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Reset Password</h1>
|
||||
<form action="" method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
<p>
|
||||
{{ form.email.label }}<br>
|
||||
{{ form.email(size=64) }}<br>
|
||||
{% for error in form.email.errors %}
|
||||
<span style="color: red;">[{{ error }}]</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>{{ form.submit() }}</p>
|
||||
</form>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue