Този урок подробно описва как да потвърдите имейл адресите по време на регистрация на потребител.
Актуализирано на 30.04.2015 г. :Добавена поддръжка на Python 3.
По отношение на работния процес, след като потребител регистрира нов акаунт, се изпраща имейл за потвърждение. Потребителският акаунт е маркиран като „непотвърден“, докато потребителят не „потвърди“ акаунта чрез инструкциите в имейла. Това е прост работен процес, който повечето уеб приложения следват.
Едно важно нещо, което трябва да вземете предвид, е какво е позволено да правят непотвърдени потребители. С други думи, имат ли пълен достъп до вашето приложение, ограничен/ограничен достъп или изобщо нямат достъп? За приложението в този урок непотвърдените потребители могат да влязат, но те незабавно се пренасочват към страница, която им напомня, че трябва да потвърдят акаунта си, преди да имат достъп до приложението.
Преди да започнем, по-голямата част от функционалността, която ще добавим, е част от разширенията Flask-User и Flask-Security - което повдига въпроса защо просто не използвате разширенията? Е, на първо място, това е възможност за учене. Освен това и двете разширения имат ограничения, като поддържаните бази данни. Ами ако искате да използвате RethinkDB, например?
Да започнем.
Основна регистрация на флакона
Ще започнем с шаблон на Flask, който включва основна регистрация на потребител. Вземете кода от хранилището. След като създадете и активирате virtualenv, изпълнете следните команди, за да започнете бързо:
$ pip install -r requirements.txt
$ export APP_SETTINGS="project.config.DevelopmentConfig"
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
$ python manage.py runserver
Вижте readme за повече информация.
Когато приложението работи, отидете на http://localhost:5000/register и регистрирайте нов потребител. Забележете, че след регистрация приложението автоматично ви влиза и ви пренасочва към главната страница. Огледайте се наоколо, след това преминете през кода – по-специално „потребителския“ план.
Убийте сървъра, когато приключите.
Актуализирайте текущото приложение
Модели
Първо, нека добавим confirmed
поле на нашия User
модел в project/models.py :
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
admin = db.Column(db.Boolean, nullable=False, default=False)
confirmed = db.Column(db.Boolean, nullable=False, default=False)
confirmed_on = db.Column(db.DateTime, nullable=True)
def __init__(self, email, password, confirmed,
paid=False, admin=False, confirmed_on=None):
self.email = email
self.password = bcrypt.generate_password_hash(password)
self.registered_on = datetime.datetime.now()
self.admin = admin
self.confirmed = confirmed
self.confirmed_on = confirmed_on
Забележете как това поле е по подразбиране на „False“. Добавихме и confirmed_on
поле, което е [datetime
] (https://realpython.com/python-datetime/). Искам да включа и това поле, за да анализирам разликата между registered_on
и confirmed_on
дати с помощта на кохортен анализ.
Нека започнем изцяло отначало с нашата база данни и миграции. Така че, продължете и изтрийте базата данни, dev.sqlite , както и папката „migrations“.
Команда за управление
След това в manage.py , актуализирайте create_admin
команда, за да вземете предвид новите полета на базата данни:
@manager.command
def create_admin():
"""Creates the admin user."""
db.session.add(User(
email="[email protected]",
password="admin",
admin=True,
confirmed=True,
confirmed_on=datetime.datetime.now())
)
db.session.commit()
Не забравяйте да импортирате datetime
. Сега продължете и изпълнете отново следните команди:
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
функция за преглед
И накрая, преди да можем да регистрираме потребител отново, трябва да направим бърза промяна в register()
функция за преглед в project/user/views.py …
Промяна:
user = User(
email=form.email.data,
password=form.password.data
)
До:
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Има смисъл? Помислете защо бихме искали да зададем по подразбиране confirmed
до False
.
Добре. Стартирайте приложението отново. Отидете до http://localhost:5000/register и регистрирайте нов потребител отново. Ако отворите вашата база данни SQLite в браузъра SQLite, трябва да видите:
И така, новият потребител, който регистрирах, [email protected]
, не се потвърждава. Нека променим това.
Добавяне на потвърждение по имейл
Генериране на токен за потвърждение
Потвърждението по имейл трябва да съдържа уникален URL адрес, върху който потребителят просто трябва да щракне, за да потвърди акаунта си. В идеалния случай URL адресът трябва да изглежда така - http://yourapp.com/confirm/<id>
. Ключът тук е id
. Ще кодираме потребителския имейл (заедно с времева марка) в id
използвайки неговия опасен пакет.
Създайте файл, наречен project/token.py и добавете следния код:
# project/token.py
from itsdangerous import URLSafeTimedSerializer
from project import app
def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT'])
def confirm_token(token, expiration=3600):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
try:
email = serializer.loads(
token,
salt=app.config['SECURITY_PASSWORD_SALT'],
max_age=expiration
)
except:
return False
return email
И така, в generate_confirmation_token()
функция използваме URLSafeTimedSerializer
за генериране на токен, използвайки имейл адреса, получен по време на регистрацията на потребител. действителното имейлът е кодиран в токена. След това, за да потвърдите маркера, в confirm_token()
функция, можем да използваме loads()
метод, който приема токена и срока на валидност - валиден за един час (3600 секунди) - като аргументи. Докато токенът не е изтекъл, той ще върне имейл.
Не забравяйте да добавите SECURITY_PASSWORD_SALT
към конфигурацията на приложението ви (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Актуализиране на register()
функция за преглед
Сега нека актуализираме register()
функция за преглед отново от project/user/views.py :
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
Също така, не забравяйте да актуализирате импортираните:
from project.token import generate_confirmation_token, confirm_token
Обработка на потвърждението по имейл
След това нека добавим нов изглед за обработка на потвърждението по имейл:
@user_blueprint.route('/confirm/<token>')
@login_required
def confirm_email(token):
try:
email = confirm_token(token)
except:
flash('The confirmation link is invalid or has expired.', 'danger')
user = User.query.filter_by(email=email).first_or_404()
if user.confirmed:
flash('Account already confirmed. Please login.', 'success')
else:
user.confirmed = True
user.confirmed_on = datetime.datetime.now()
db.session.add(user)
db.session.commit()
flash('You have confirmed your account. Thanks!', 'success')
return redirect(url_for('main.home'))
Добавете това към project/user/views.py . Също така не забравяйте да актуализирате импортираните:
import datetime
Тук ние наричаме confirm_token()
функция, предаваща токена. Ако е успешен, актуализираме потребителя, променяйки email_confirmed
атрибут на True
и задаване на datetime
за кога е извършено потвърждението. Освен това, в случай че потребителят вече е минал през процеса на потвърждение - и е потвърден - тогава ние предупреждаваме потребителя за това.
Създайте шаблона за имейл
След това нека добавим основен шаблон за имейл:
<p>Welcome! Thanks for signing up. Please follow this link to activate your account:</p>
<p><a href="{{ confirm_url }}">{{ confirm_url }}</a></p>
<br>
<p>Cheers!</p>
Запазете това като activate.html в „проект/шаблони/потребител”. Това отнема една променлива, наречена confirm_url
, който ще бъде създаден в register()
функция за преглед.
Изпратете имейл
Нека създадем основна функция за изпращане на имейли с малко помощ от Flask-Mail, която вече е инсталирана и настроена в project/__init__.py
.
Създайте файл, наречен email.py :
# project/email.py
from flask.ext.mail import Message
from project import app, mail
def send_email(to, subject, template):
msg = Message(
subject,
recipients=[to],
html=template,
sender=app.config['MAIL_DEFAULT_SENDER']
)
mail.send(msg)
Запазете това в папката „проект“.
Така че просто трябва да предадем списък с получатели, тема и шаблон. След малко ще се справим с настройките за конфигурация на пощата.
Актуализиране на register()
функция за преглед в project/user/views.py (отново!)
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(user.email, subject, html)
login_user(user)
flash('A confirmation email has been sent via email.', 'success')
return redirect(url_for("main.home"))
return render_template('user/register.html', form=form)
Добавете и следния импорт:
from project.email import send_email
Тук събираме всичко. Тази функция основно действа като контролер (пряко или непряко) за целия процес:
- Управление на първоначалната регистрация,
- Генерирайте токен и URL за потвърждение,
- Изпратете имейл за потвърждение,
- Потвърждение на Flash,
- Влезте в потребителя и
- Пренасочване на потребителя.
Забелязахте ли _external=True
аргумент? Това добавя пълния абсолютен URL адрес, който включва името на хоста и порта (http://localhost:5000, в нашия случай.)
Преди да можем да тестваме това, трябва да настроим настройките си за поща.
Поща
Започнете с актуализиране на BaseConfig()
в project/config.py :
class BaseConfig(object):
"""Base configuration."""
# main config
SECRET_KEY = 'my_precious'
SECURITY_PASSWORD_SALT = 'my_precious_two'
DEBUG = False
BCRYPT_LOG_ROUNDS = 13
WTF_CSRF_ENABLED = True
DEBUG_TB_ENABLED = False
DEBUG_TB_INTERCEPT_REDIRECTS = False
# mail settings
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
# gmail authentication
MAIL_USERNAME = os.environ['APP_MAIL_USERNAME']
MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD']
# mail accounts
MAIL_DEFAULT_SENDER = '[email protected]'
Вижте официалната документация на Flask-Mail за повече информация.
Ако вече имате акаунт в GMAIL, можете да го използвате или да регистрирате тест GMAIL акаунт. След това задайте временно променливите на средата в текущата сесия на обвивката:
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Ако вашият GMAIL акаунт има удостоверяване в 2 стъпки, Google ще блокира опита.
Сега нека тестваме!
Първи тест
Стартирайте приложението и отидете до http://localhost:5000/register. След това се регистрирайте с имейл адрес, до който имате достъп. Ако всичко е минало добре, трябва да имате имейл във входящата си поща, който изглежда така:
Щракнете върху URL адреса и трябва да бъдете отведени до http://localhost:5000/. Уверете се, че потребителят е в базата данни, полето „потвърдено“ е True
и има datetime
свързано с confirmed_on
поле.
Хубаво!
Разрешения за работа
Ако си спомняте, в началото на този урок решихме, че „непотвърдени потребители могат да влизат, но те трябва незабавно да бъдат пренасочени към страница – нека извикаме маршрута /unconfirmed
- напомняне на потребителите, че трябва да потвърдят акаунта си, преди да имат достъп до приложението.”
И така, трябва да...
- Добавете
/unconfirmed
маршрут - Добавете unconfirmed.html шаблон
- Актуализирайте
register()
функция за преглед - Създайте декоратор
- Актуализиране на navigation.html шаблон
Добавете /unconfirmed
маршрут
Добавете следния маршрут към project/user/views.py :
@user_blueprint.route('/unconfirmed')
@login_required
def unconfirmed():
if current_user.confirmed:
return redirect('main.home')
flash('Please confirm your account!', 'warning')
return render_template('user/unconfirmed.html')
Виждали сте подобен код преди, така че да продължим напред.
Добавете unconfirmed.html шаблон
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="/">Resend</a>.</p>
{% endblock %}
Запазете това като unconfirmed.html в „проект/шаблони/потребител”. Отново всичко това трябва да е просто. Засега просто добавихме фиктивен URL адрес за повторно изпращане на имейла за потвърждение. Ще разгледаме това по-нататък.
Актуализирайте register()
функция за преглед
Сега просто променете:
return redirect(url_for("main.home"))
До:
return redirect(url_for("user.unconfirmed"))
И така, след като имейлът за потвърждение бъде изпратен, потребителят вече е пренасочен към /unconfirmed
маршрут.
Създайте декоратор
# project/decorators.py
from functools import wraps
from flask import flash, redirect, url_for
from flask.ext.login import current_user
def check_confirmed(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if current_user.confirmed is False:
flash('Please confirm your account!', 'warning')
return redirect(url_for('user.unconfirmed'))
return func(*args, **kwargs)
return decorated_function
Тук имаме основна функция, за да проверим дали даден потребител не е потвърден. Ако не е потвърдено, потребителят се пренасочва към /unconfirmed
маршрут. Запазете това като decorators.py в директорията „project“.
Сега украсете profile()
функция за преглед:
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Не забравяйте да импортирате декоратора:
from project.decorators import check_confirmed
Актуализиране на navigation.html шаблон
Накрая актуализирайте следната част от navigation.html шаблон-
Промяна:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
До:
<ul class="nav navbar-nav">
{% if current_user.confirmed and current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% elif current_user.is_authenticated() %}
<li><a href="{{ url_for('user.unconfirmed') }}">Confirm</a></li>
{% endif %}
</ul>
Време е да тествате отново!
Втори тест
Стартирайте приложението и се регистрирайте отново с имейл адрес, до който имате достъп. (Чувствайте се свободни първо да изтриете стария потребител, който сте регистрирали преди, от базата данни, за да го използвате отново.) Сега трябва да бъдете пренасочени към http://localhost:5000/unconfirmed след регистрация.
Уверете се, че сте тествали маршрута http://localhost:5000/profile. Това трябва да ви пренасочи към http://localhost:5000/unconfirmed.
Продължете и потвърдете имейла и ще имате достъп до всички страници. Бум!
Повторно изпращане на имейл
И накрая, нека накараме връзката за повторно изпращане да работи. Добавете следната функция за изглед към project/user/views.py :
@user_blueprint.route('/resend')
@login_required
def resend_confirmation():
token = generate_confirmation_token(current_user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(current_user.email, subject, html)
flash('A new confirmation email has been sent.', 'success')
return redirect(url_for('user.unconfirmed'))
Сега актуализирайте unconfirmed.html шаблон:
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="{{ url_for('user.resend_confirmation') }}">Resend</a>.</p>
{% endblock %}
Трети тест
Знаеш тренировката. Този път не забравяйте да изпратите отново нов имейл за потвърждение и да тествате връзката. Би трябвало да работи.
И накрая, какво ще стане, ако си изпратите няколко връзки за потвърждение? Всеки валиден ли е? Тествайте го. Регистрирайте нов потребител и след това изпратете няколко нови имейла за потвърждение. Опитайте да потвърдите с първия имейл. Проработи ли? Би трябвало. Това добре ли е? Смятате ли, че тези други имейли трябва да изтекат, ако бъде изпратен нов?
Направете малко проучване за това. И тествайте други уеб приложения, които използвате. Как се справят с подобно поведение?
Актуализиране на тестовия пакет
Добре. Така че това е всичко за основната функционалност. Какво ще кажете да актуализираме текущия тестов пакет, тъй като той е повреден.
Изпълнете тестовете:
$ python manage.py test
Трябва да видите следната грешка:
TypeError: __init__() takes at least 4 arguments (3 given)
За да коригираме това, просто трябва да актуализираме setUp()
метод в project/util.py :
def setUp(self):
db.create_all()
user = User(email="[email protected]", password="admin_user", confirmed=False)
db.session.add(user)
db.session.commit()
Сега стартирайте тестовете отново. Всичко трябва да мине!
Заключение
Очевидно можем да направим още много:
- Имейли с богат и обикновен текст – трябва да изпращаме и двете.
- Имейл за нулиране на паролата – Те трябва да бъдат изпратени на потребители, които са забравили паролите си.
- Управление на потребителите – Трябва да позволим на потребителите да актуализират своите имейли и пароли и когато имейл се промени, той трябва да бъде потвърден отново.
- Тестване – Трябва да напишем още тестове, за да покрием новите функции.
Изтеглете целия изходен код от хранилището на Github. Коментирайте по-долу с въпроси. Вижте част 2.
Весели празници!