Tutorial ini menjelaskan cara memvalidasi alamat email selama pendaftaran pengguna.
Diperbarui 30/04/2015 :Menambahkan dukungan Python 3.
Dalam hal alur kerja, setelah pengguna mendaftarkan akun baru, email konfirmasi dikirim. Akun pengguna ditandai sebagai "belum dikonfirmasi" sampai pengguna, yah, "mengkonfirmasi" akun melalui instruksi di email. Ini adalah alur kerja sederhana yang diikuti sebagian besar aplikasi web.
Satu hal penting untuk dipertimbangkan adalah apa yang boleh dilakukan oleh pengguna yang belum dikonfirmasi. Dengan kata lain, apakah mereka memiliki akses penuh ke aplikasi Anda, akses terbatas/terbatas, atau tidak ada akses sama sekali? Untuk aplikasi dalam tutorial ini, pengguna yang belum dikonfirmasi dapat masuk tetapi mereka segera diarahkan ke halaman yang mengingatkan mereka bahwa mereka perlu mengonfirmasi akun mereka sebelum dapat mengakses aplikasi.
Sebelum memulai, sebagian besar fungsi yang akan kami tambahkan adalah bagian dari ekstensi Flask-User dan Flask-Security - yang menimbulkan pertanyaan mengapa tidak menggunakan ekstensi saja? Yah, pertama dan terpenting, ini adalah kesempatan untuk belajar. Selain itu, kedua ekstensi tersebut memiliki keterbatasan, seperti database yang didukung. Bagaimana jika Anda ingin menggunakan RethinkDB, misalnya?
Mari kita mulai.
Pendaftaran dasar labu
Kami akan mulai dengan boilerplate Flask yang mencakup pendaftaran pengguna dasar. Ambil kode dari repositori. Setelah Anda membuat dan mengaktifkan virtualenv, jalankan perintah berikut untuk memulai dengan cepat:
$ 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
Lihat readme untuk informasi lebih lanjut.
Dengan aplikasi berjalan, navigasikan ke http://localhost:5000/register dan daftarkan pengguna baru. Perhatikan bahwa setelah pendaftaran, aplikasi secara otomatis memasukkan Anda dan mengarahkan Anda ke halaman utama. Lihat-lihat, lalu jalankan kodenya - khususnya cetak biru “pengguna”.
Matikan server setelah selesai.
Perbarui aplikasi saat ini
Model
Pertama, mari tambahkan confirmed
ke User
. kami model di 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
Perhatikan bagaimana bidang ini default ke 'False'. Kami juga menambahkan confirmed_on
bidang, yang merupakan [datetime
] (https://realpython.com/python-datetime/). Saya ingin memasukkan bidang ini juga untuk menganalisis perbedaan antara registered_on
dan confirmed_on
tanggal menggunakan analisis kohort.
Mari kita mulai dari awal lagi dengan database dan migrasi kita. Jadi, lanjutkan dan hapus database, dev.sqlite , serta folder “migrasi”.
Kelola perintah
Selanjutnya, dalam manage.py , perbarui create_admin
perintah untuk mempertimbangkan bidang database baru:
@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()
Pastikan untuk mengimpor datetime
. Sekarang, lanjutkan dan jalankan kembali perintah berikut:
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
fungsi tampilan
Terakhir, sebelum kita dapat mendaftarkan pengguna lagi, kita perlu membuat perubahan cepat pada register()
lihat fungsi di project/user/views.py …
Ubah:
user = User(
email=form.email.data,
password=form.password.data
)
Kepada:
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Masuk akal? Pikirkan tentang mengapa kami ingin default confirmed
menjadi False
.
Oke. Jalankan aplikasi lagi. Arahkan ke http://localhost:5000/register dan daftarkan pengguna baru lagi. Jika Anda membuka database SQLite di Browser SQLite, Anda akan melihat:
Jadi, pengguna baru yang saya daftarkan, [email protected]
, tidak dikonfirmasi. Mari kita ubah itu.
Tambahkan konfirmasi email
Buat token konfirmasi
Konfirmasi email harus berisi URL unik yang hanya perlu diklik oleh pengguna untuk mengonfirmasi akunnya. Idealnya, URL akan terlihat seperti ini - http://yourapp.com/confirm/<id>
. Kuncinya di sini adalah id
. Kami akan mengkodekan email pengguna (bersama dengan stempel waktu) di id
menggunakan paket berbahayanya.
Buat file bernama project/token.py dan tambahkan kode berikut:
# 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
Jadi, di generate_confirmation_token()
fungsi kami menggunakan URLSafeTimedSerializer
untuk menghasilkan token menggunakan alamat email yang diperoleh saat pendaftaran pengguna. sebenarnya email dikodekan dalam token. Kemudian untuk mengonfirmasi token, di dalam confirm_token()
fungsi, kita dapat menggunakan loads()
metode, yang menggunakan token dan kedaluwarsa - berlaku selama satu jam (3.600 detik) - sebagai argumen. Selama token belum kedaluwarsa, maka akan mengembalikan email.
Pastikan untuk menambahkan SECURITY_PASSWORD_SALT
ke konfigurasi aplikasi Anda (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Perbarui register()
fungsi tampilan
Sekarang mari kita perbarui register()
lihat fungsi lagi dari 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)
Juga, pastikan untuk memperbarui impor:
from project.token import generate_confirmation_token, confirm_token
Menangani Konfirmasi Email
Selanjutnya, mari tambahkan tampilan baru untuk menangani konfirmasi email:
@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'))
Tambahkan ini ke project/user/views.py . Juga, pastikan untuk memperbarui impor:
import datetime
Di sini, kita memanggil confirm_token()
fungsi, meneruskan token. Jika berhasil, kami memperbarui pengguna, mengubah email_confirmed
atribut ke True
dan mengatur datetime
untuk saat konfirmasi terjadi. Selain itu, jika pengguna telah melalui proses konfirmasi - dan telah dikonfirmasi - maka kami akan memperingatkan pengguna tentang hal ini.
Buat template email
Selanjutnya, mari tambahkan template email dasar:
<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>
Simpan ini sebagai activate.html di "proyek/templat/pengguna". Ini mengambil satu variabel yang disebut confirm_url
, yang akan dibuat di register()
fungsi tampilan.
Kirim email
Mari kita buat fungsi dasar untuk mengirim email dengan sedikit bantuan dari Flask-Mail, yang sudah terinstal dan setup di project/__init__.py
.
Buat file bernama 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)
Simpan ini di folder "proyek".
Jadi, kita hanya perlu memberikan daftar penerima, subjek, dan template. Kami akan membahas pengaturan konfigurasi email sebentar lagi.
Perbarui register()
lihat fungsi di project/user/views.py (lagi!)
@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)
Tambahkan juga impor berikut:
from project.email import send_email
Di sini, kami menggabungkan semuanya. Fungsi ini pada dasarnya bertindak sebagai pengontrol (baik secara langsung maupun tidak langsung) untuk seluruh proses:
- Menangani pendaftaran awal,
- Buat token dan URL konfirmasi,
- Kirim email konfirmasi,
- Konfirmasi flash,
- Masukkan pengguna, dan
- Mengalihkan pengguna.
Apakah Anda memperhatikan _external=True
argumen? Ini menambahkan URL absolut lengkap yang menyertakan nama host dan port (http://localhost:5000, dalam kasus kami.)
Sebelum kita dapat mengujinya, kita perlu mengatur pengaturan email kita.
Surat
Mulailah dengan memperbarui BaseConfig()
di 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]'
Lihat dokumentasi Flask-Mail resmi untuk info lebih lanjut.
Jika Anda sudah memiliki akun GMAIL maka Anda dapat menggunakannya atau mendaftarkan akun GMAIL percobaan. Kemudian atur variabel lingkungan sementara di sesi shell saat ini:
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Jika akun GMAIL Anda memiliki autentikasi 2 langkah, Google akan memblokir upaya tersebut.
Sekarang mari kita uji!
Tes pertama
Jalankan aplikasi, dan navigasikan ke http://localhost:5000/register. Kemudian daftar dengan alamat email yang dapat Anda akses. Jika semuanya berjalan dengan baik, Anda akan memiliki email di kotak masuk Anda yang terlihat seperti ini:
Klik URL dan Anda akan dibawa ke http://localhost:5000/. Pastikan bahwa pengguna ada di database, bidang 'dikonfirmasi' adalah True
, dan ada datetime
terkait dengan confirmed_on
lapangan.
Bagus!
Menangani izin
Jika Anda ingat, di awal tutorial ini, kami memutuskan bahwa “pengguna yang belum dikonfirmasi dapat masuk tetapi mereka harus segera diarahkan ke halaman - sebut saja rute /unconfirmed
- mengingatkan pengguna bahwa mereka perlu mengonfirmasi akun mereka sebelum dapat mengakses aplikasi”.
Jadi, kita perlu-
- Tambahkan
/unconfirmed
rute - Tambahkan unconfirmed.html templat
- Perbarui
register()
fungsi tampilan - Buat dekorator
- Perbarui navigation.html templat
Tambahkan /unconfirmed
rute
Tambahkan rute berikut ke 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')
Anda pernah melihat kode serupa sebelumnya, jadi mari kita lanjutkan.
Tambahkan unconfirmed.html templat
{% 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 %}
Simpan ini sebagai unconfirmed.html di "proyek/templat/pengguna". Sekali lagi, ini semua harus langsung. Untuk saat ini, kami baru saja menambahkan URL tiruan untuk mengirim ulang email konfirmasi. Kami akan membahas ini lebih jauh.
Perbarui register()
fungsi tampilan
Sekarang cukup ubah:
return redirect(url_for("main.home"))
Kepada:
return redirect(url_for("user.unconfirmed"))
Jadi, setelah email konfirmasi terkirim, pengguna kini dialihkan ke kode /unconfirmed
rute.
Buat dekorator
# 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
Di sini kami memiliki fungsi dasar untuk memeriksa apakah pengguna belum dikonfirmasi. Jika belum dikonfirmasi, pengguna diarahkan ke /unconfirmed
rute. Simpan ini sebagai decorators.py di direktori “proyek”.
Sekarang, hiasi profile()
fungsi tampilan:
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Pastikan untuk mengimpor dekorator:
from project.decorators import check_confirmed
Perbarui navigation.html templat
Terakhir, perbarui bagian navigation.html berikut ini template-
Ubah:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
Kepada:
<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>
Saatnya menguji lagi!
Ujian kedua
Jalankan aplikasi, dan daftar lagi dengan alamat email yang dapat Anda akses. (Jangan ragu untuk menghapus pengguna lama yang Anda daftarkan sebelumnya dari database untuk digunakan kembali.) Sekarang Anda harus diarahkan ke http://localhost:5000/unconfirmed setelah pendaftaran.
Pastikan untuk menguji rute http://localhost:5000/profile. Ini akan mengarahkan Anda ke http://localhost:5000/unconfirmed.
Silakan dan konfirmasi email, dan Anda akan memiliki akses ke semua halaman. Boom!
Kirim ulang email
Akhirnya, mari buat tautan kirim ulang berfungsi. Tambahkan fungsi tampilan berikut ke 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'))
Sekarang perbarui unconfirmed.html templat:
{% 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 %}
Tes ketiga
Kamu tahu latihannya. Kali ini pastikan untuk mengirim ulang email konfirmasi baru dan menguji tautannya. Seharusnya berhasil.
Terakhir, apa yang terjadi jika Anda mengirim sendiri beberapa tautan konfirmasi? Apakah masing-masing valid? Ujilah. Daftarkan pengguna baru, lalu kirim beberapa email konfirmasi baru. Coba konfirmasi dengan email pertama. Apa itu bekerja? Itu harus. Apakah ini baik? Apakah menurut Anda email lain tersebut akan kedaluwarsa jika yang baru dikirim?
Lakukan penelitian tentang ini. Dan uji aplikasi web lain yang Anda gunakan. Bagaimana mereka menangani perilaku seperti itu?
Perbarui rangkaian pengujian
Baiklah. Jadi itu saja untuk fungsi utama. Bagaimana kalau kita memperbarui rangkaian pengujian saat ini karena rusak.
Jalankan tes:
$ python manage.py test
Anda akan melihat kesalahan berikut:
TypeError: __init__() takes at least 4 arguments (3 given)
Untuk memperbaikinya, kita hanya perlu memperbarui setUp()
metode di 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()
Sekarang jalankan tes lagi. Semua harus lulus!
Kesimpulan
Jelas ada banyak lagi yang bisa kita lakukan:
- Email kaya vs. teks biasa - Kami harus mengirimkan keduanya.
- Setel ulang email sandi - Email ini harus dikirimkan kepada pengguna yang lupa sandi.
- Pengelolaan pengguna - Kami harus mengizinkan pengguna untuk memperbarui email dan sandi mereka, dan saat email diubah, email tersebut harus dikonfirmasi lagi.
- Pengujian - Kami perlu menulis lebih banyak pengujian untuk mencakup fitur-fitur baru.
Unduh seluruh kode sumber dari repositori Github. Komentar di bawah dengan pertanyaan. Lihat bagian 2.
Selamat berlibur!