Advertisement
  1. Code
  2. Python

Pengenalan Flask: Masuk dan Keluar

Scroll to top
Read Time: 27 min

() translation by (you can also view the original English article)

Banyak aplikasi web membutuhkan user untuk masuk dan keluar untuk bisa menjalankakn pekerjaan penting (seperti tugas administratif). Pada artikel ini, kita akan membuat sistem otentikasi untuk aplikasi kita.

Pada artikel sebelumnya, kita membuat halaman kontak menggunakan ekstensi Flask-WTF dan Flask-Mail. Kita akan kembali menggunakan Flask-WTF, kali ini untuk memvalidasi usernama dan password user. Kita akan menyimpan kredensial tersebut ke dalam  database menggunakan ekstensi bernama Flask-SQLAlchemy.

Kamu bisa menemukan source code untuk tutorial ini di GitHub. Saat mengikuti tutorial ini, jika kamu melihat keterangan seperti Checkpoint: 13_packaged_app, artinya kamu bisa ganti ke GIT branch bernama "13_packaged_app" dan relihat kode di titik tersebut dalam artikel.


Mengembangkan aplikasi

Sejauh ini, aplikasi Flask kita cukup sederhana. Aplikasi kita berisi sebagian besar halaman statik;  jadi kita bisa menyusunnya sebagai modul Python. Tapi sekarang kita perlu menyusun ulang aplikasi kita untuk memudahkan pengelolaan dan pengembangan. Dokumentasi Flask menyarankan kita menyusun ulang aplikasi sebagai paket Python, jadi kita mulai dari situ.

Saat ini aplikasi kita disusun seperti ini:

1
flaskapp/
2
└── app/
3
        ├── static/
4
        ├── templates/
5
        ├── forms.py
6
        ├── routes.py
7
        └── README.md

Untuk mengubah strukturnya sebagai sebuah package, kita buat folder di dalam app/ bernama intro_to_flask/. Lalu pindahkan static/, templates/, forms.py, dan routes.py ke intro_to_flask/. Lalu hapus file .pyc yang tersisa.

1
flaskapp/
2
└── app/
3
        ├── intro_to_flask/
4
        │      ├── static/
5
        │      ├── templates/
6
        │      ├── forms.py
7
        │      ├── routes.py
8
        └── README.md

Lalu, buat sebuah file baru bernama __init__.py dan tempatkan di dalam intro_to_flask/. File ini dibutuhkan untuk membuat Python menganggap folder intro_to_flask/ sebagai package.

1
flaskapp/
2
└── app/
3
        ├── intro_to_flask/
4
        │      ├── __init__.py
5
        │      ├── static/
6
        │      ├── templates/
7
        │      ├── forms.py
8
        │      ├── routes.py
9
        └── README.md

Ketika aplikasi kita adalah sebuah modul Python, import dan konfigurasi tin gkakt aplikasi ditentukan di routes.py. Sekarang aplikasi menjadi sebuah package Python, kita akan pindahkan pengaturan dari routes.py ke __init__.py.

app/intro_to_flask/__init__.py

1
from flask import Flask
2
3
app = Flask(__name__)
4
5
app.secret_key = 'development key'
6
7
app.config["MAIL_SERVER"] = "smtp.gmail.com"
8
app.config["MAIL_PORT"] = 465
9
app.config["MAIL_USE_SSL"] = True
10
app.config["MAIL_USERNAME"] = 'contact@example.com'
11
app.config["MAIL_PASSWORD"] = 'your-password'
12
13
from routes import mail
14
mail.init_app(app)
15
16
import intro_to_flask.routes

Bagian atas dari routes.py sekarang terlihat seperti ini:

app/intro_to_flask/routes.py

1
from intro_to_flask import app
2
from flask import render_template, request, flash
3
from forms import ContactForm
4
from flask.ext.mail import Message, Mail
5
6
mail = Mail()
7
.
8
.
9
.
10
# @app.route() mappings start here

Sebelumnya kita memiliki app.run() di dalam routes.py, yang membuat kita bisa menulis $ python routes.py untuk menjalankan aplikasi. Karena aplikasi sekarang dibuat menjadi package, kita perlu menggunakan strategi yang berbeda. Dokumentasi Flask menyarankan untuk menambahkan file baru bernama runserver.py dan menyimpannya di dalam app/. Kita lakukan itu sekarang:

1
flaskapp/
2
└── app/
3
        ├── intro_to_flask/
4
        │      ├── __init__.py
5
        │      ├── static/
6
        │      ├── templates/
7
        │      ├── forms.py
8
        │      ├── routes.py
9
        ├── runserver.py        
10
        └── README.md

Sekarang ambil app.run() dari routes.py dan simpan di dalam runserver.py.

app/runserver.py

1
from intro_to_flask import app
2
3
app.run(debug=True)

Sekarang kamu bisa menulis $ python runserver.py dan melihat aplikasi di browser. Berikut adalah bagaimana kamu masuk ke lingkungan development dan menjalankan aplikasi:

1
$ cd flaskapp/
2
$ . bin/activate
3
$ cd app/
4
$ python runserver.py

Aplikasi sekarang sudah menjadi package, sekarang kita siap untuk melanjutkan dan menginstall database untuk menyimpan data pengguna.

-- Checkpoint: 13_packaged_app --


Flask-SQLAlchemy

Kita akan menggunakan MySQL untuk database engine dan ekstensi Flask-SQLAlchemy untuk mengatur semua interaksi database.

Flask-SQLAlchemy menggunakan object Python, bukan statement SQL untuk melakukan query pada database. Contohnya, daripada menulis SELECT * FROM users WHERE firstname = "lalith", kamu menulis User.query.filter_by(username="lalith").first().

Moral dari cerita ini bukan untuk sepenuhnya bergantung pada lapisan abstraksi seperti Flask-SQLAlchemy, tapi agar kita mengetahui dan bisa memilih kapan library itu berguna untuk memenuhi kebutuhan kita.

Tapi kenapa kita tidak menulis statement SQL biasa? Apa keuntungan menggunakan sintaks aneh ini? Seperti banyak hal lain, menggunakan Flask-SQLAlchemy atau lapisan abstraksi database lain, tergantung pada kebutuhan dan preferensimu. Menggunakan Flask-SQLAlchemy membuat kamu bekerja dengan database menggunakan kode Python, bukan SQL. Dengan ini kamu tidak perlu memiliki statemen SQL yang tersebar di dalam kode Python dan itu adalah hal yang baik, dari sudut pandang kualitas kode.

Selain itu, jika diimplementasi dengan benar, menggunakan Flask-SQLAlchemy akan membantu membuat aplikasi menjadi independen dari database. Jika kamu membuat aplikasi di atas MySQL dan memutuskan untuk berpindah ke engine database lain, kamu tidak perlu menulis ulang kode database yang sudah ada. Kamu cukup mengganti Flask-SQLAlchemy dengan lapisan abstraksi database tanpa banyak masalah. Bisa mengubah komponen dengan mudah disebut modularity, dan ciri-ciri dari aplikasi yang dirancang dengan baik.

Di sisi lain, mungkin akan intuitif dan mudah dibaca jika kamu menulis statemen SQL dibanding belajar bagaimana menerjemahkannya ke bahasa ekspresi Flask-SQLAlchemy. Untuknya, kita bisa menulis statement SQL di Flask-SQLAlchemy, jika memang kamu membutuhkannya.

Moral dari cerita ini bukan untuk sepenuhnya bergantung pada lapisan abstraksi seperti Flask-SQLAlchemy, tapi agar kita mengetahui dan bisa memilih kapan library itu berguna untuk memenuhi kebutuhan kita. Untuk query database pada artikel ini, saya akan tunjukkan versi bahasa ekspresi dan statement SQL yang bersangkutan.

Menginstall MySQL

Periksa apakah komputermu sudah memiliki MySQL dengan menjalankan perintah berikut di terminal:

1
$ mysql --version

Jika kamu melihat nomor versi, kamu bisa melompat ke bagian "membuat database". Jika perintah tidak ditemukan, kamu perlu menginstall MySQL. Dengan berbagai jenis sistem operasi di luar sana, saya akan memanfaatkan Google untuk menyediakan instruksi instalasi yang cocok untuk OSmu. Instalasi tersebut biasanya berisi menjalankan perintah atau aplikasi. Contohnya, perintah Linuxnya adalah:

1
$ sudo apt-get install mysql-server mysql-client

Membuat Database

Setelah MySQL diinstall, buat database untuk aplikasimu bernama 'development'. Kamu bisa melakukan ini dari antarmuka web seperti phpMyAdmin atau dari command line seperti berikut ini:

1
$ mysql -u username -p
2
Enter password:
3
4
mysql> CREATE DATABASE development;

Menginstall Flask-SQLAlchemy

Di dalam lingkungan development yang terisolasi, install Flask-SQLAlchemy.

1
$ pip install flask-sqlalchemy

Saat saya coba menginstal Flask-SQLAlchemy, saya menerima error menyatakan instalasi gagal. Saya mencari error tersebut dan menemukan bahwa orang lain memecahkan masalah tersebut dengan menginstall libmysqlclient15-dev, yang menginstall file development MySQL. Jika instalasi Flask-SQLAlchemymu gagal, cari error tersebut di Google, atau tinggalkan komentar agar kami bisa bantu.

Mengatur Flask-SQLAlchemy

Sama seperti Flask-Mail, kita perlu mengatur Flask-SQLAlchemy agar dia tahu di mana database development berada. Pertama, kita buat file baru bernama models.py, dan tambahkan kode berikut

app/intro_to_flask/models.py

1
from flask.ext.sqlalchemy import SQLAlchemy
2
3
db = SQLAlchemy()

Di sini kita import kelas SQLAlchemy dari Flask-SQLAlchemy (baris pertama) dan buat variabel bernama db, berisi instans dari kelas SQLAlchemy (baris tiga).

Lalu, buka __init__.py dan tambahkan baris berikut setelah mail.init_app(app) dan sebelum import intro_to_flask.routes.

app/intro_to_flask/__init__.py

1
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://your-username:your-password@localhost/development'
2
3
from models import db
4
db.init_app(app)

Kita coba bahas satu per satu:

  1. Baris pertama memberi tahu aplikasi Flask untuk menggunakan database 'development'. Kita menentukan ini dengan URI data yang mengikuti pola mysql://username:password@server/database. Server adalah 'localhost' karena kita mengembangkan aplikasi secara lokal. Pastikan mengisi username dan password MySQL.
  2. db, instans dari SQLAlchemy dibuat pada models.py, masih tidak mengetahui database mana yang perlu digunakan. Jadi kita import dari models.py (baris tiga) dan mengikatnya ke aplikasi kita (baris empat) jadi dia juga tahu untuk menggunakan database 'development'. Sekarang kita bisa mengquery database 'development' melalui objek db.

Sekarang pengaturan kita sudah selesai, kita pastikan semua bekerja dengan baik. Buka routes.py dan buat pemetaan URL sementara agar kita bisa menguji query dengan mudah.

app/intro-to-flask/routes.py

1
from intro_to_flask import app
2
from flask import Flask, render_template, request, flash, session, redirect, url_for
3
from forms import ContactForm, SignupForm, SigninForm
4
from flask.ext.mail import Message, Mail
5
from models import db
6
.
7
.
8
.
9
@app.route('/testdb')
10
def testdb():
11
  if db.session.query("1").from_statement("SELECT 1").all():
12
    return 'It works.'
13
  else:
14
    return 'Something is broken.'

Pertama kita import objek database (db) dari models.py (baris lima). Lalu kita buat pemetaan URL sementara (baris 9-14) di mana kita membuat query untuk memastikan aplikasi Flask terhubung dengan database 'development'. Sekarang ketika kita mengunjungi URL /testdb, sebuah test query akan dilakukan (baris 11); ini ekivalen dengan statemen SELECT 1; Jika semua berjalan dengan baik, kita akan melihat "It works" pada browser. Jika tidak, kita akan lihat pesan error menuliskan apa yang salah.

Saya mendapat error ketika mengunjungi URL /testdb: ImportError: No module named MySQLdb. Ini artinya saya tidak memiliki library mysql-python terinstall, jadi saya coba menginstallnya dengan mengetik perintah berikut:

1
$ pip install mysql-python

Instalasi tersebut gagal juga. Pesan error baru menyarankan untuk menjalankan easy_install -U distribute dan mencoba kembali instalasis mysql-python lagi. Saya melakukannya, seperti berikut:

1
$ easy_install -U distribute
2
$ pip install mysql-python

Kali ini instalasi mysql-python berhasil, dan saya mendapat pesan "It works" di browser. Alasan saya mencatat pesan error yang diterima dan apa yang saya lakukan untuk memecahkannya karena menginstall dan menghubungkan web ke database bisa sedikit rumit. Jika kamu mendapat pesan error, jangan putus asa. Cari tahu di Google pesan error tersebut atau tinggalkan komentar, kita akan cari tahu bersama.

Begitu query test berhasil, hapus URL sementara dari routes.py. Pastikan mempertahankan "from models import db", karena kita akan gunakan bagian itu.

-- Checkpoint: 14_db_config --


Membuat model pengguna

Tidak baik untuk menyimpan password dalam teks biasa, terkait masalah keamanan.

Di dalam database 'development', kita perlu membuat tabel pengguna di mana kita bisa menyimpan informasi dari setiap pengguna. Informasi yang ingin kita simpan adalah nama depan, nama belakang, email, dan password.

Tidak baik untuk menyimpan password dalam teks biasa, terkait masalah keamanan. Jika seorang hacker mendapat akses database kita, mereka bisa melihat kredensial setiap pengguna. Satu cara untuk mempertahankank diri dari serangan seperti itu adalah untuk mengenkripsi password dengan fungsi hash dan salt (data acak), lalu menyimpan nilai yang dienkripsi dalam database, bukan teks biasa. Ketika seorang pengguna masuk kembali, kita akan periksa password yang dikirim, melakukakn hash, dan memeriksa apakah sama dengan hash yang ada di database. Werkzeug, library sumber Flask dibangun, menyediakan fungsi generate_password_hash dan check_password_hash untuk kedua kebutuhan tersebut.

Berikut adalah kolom-kolom yang kita butuhkan untuk tabel pengguna:

Kolom Tipe Batasan
uid int Kunci Primer, Auto Increment
firstname varchar(100)
lastname varchar(100)
email varchar(120) Unique
password varchar(54)

Seperti sebelumnya, kamu bisa membuat tabel ini dari antarmuka web seperti phpMyAdmin atau dari command line, seperti berikut ini:

1
mysql> CREATE TABLE users (
2
uid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
3
firstname VARCHAR(100) NOT NULL,
4
lastname VARCHAR(100) NOT NULL,
5
email VARCHAR(120) NOT NULL UNIQUE,
6
pwdhash VARCHAR(100) NOT NULL
7
);

Berikutnya, pada models.py, kita buat kelas sebagai model dari pengguna dengan atribut nama pertama, nama belakang, email, dan password.

app/intro_to_flask/models.py

1
from flask.ext.sqlalchemy import SQLAlchemy
2
from werkzeug import generate_password_hash, check_password_hash
3
4
db = SQLAlchemy()
5
6
class User(db.Model):
7
  __tablename__ = 'users'
8
  uid = db.Column(db.Integer, primary_key = True)
9
  firstname = db.Column(db.String(100))
10
  lastname = db.Column(db.String(100))
11
  email = db.Column(db.String(120), unique=True)
12
  pwdhash = db.Column(db.String(54))
13
  
14
  def __init__(self, firstname, lastname, email, password):
15
    self.firstname = firstname.title()
16
    self.lastname = lastname.title()
17
    self.email = email.lower()
18
    self.set_password(password)
19
    
20
  def set_password(self, password):
21
    self.pwdhash = generate_password_hash(password)
22
  
23
  def check_password(self, password):
24
    return check_password_hash(self.pwdhash, password)

Kita gunakan fungsi set_password() untuk mengatur hash dengan salt dari password, tidak menggunakan teks passwordnya.

Baris satu dan empat sudah ada pada models.py, jadi kita mulai dari baris dua dengan mengimpor generate_password_hash dan check_password_hash dari Werkzeug. Lalu, kita buat kelas baru bernama User, diturunkan dari kelas Model pada objek database db.

Di dalam kelas User, kita buat atribut untuk nama, primary key, nama depan, nama belakang, email, dan password (baris 10-14). Lalu kita buat konstruktor yang mengatur atribut kelas (baris 17-20). Kita simpan nama dalam kapital dan alamat email dalam huruf kecil untuk memastikan kecocokan, bagaimanapun pengguna menulis data diri pada proses sign in berikutnya.

Kita gunakan fungsi sets_password (baris 22-23) untuk mengatur password dengan salt, bukan menggunakan teks passwordnya. Terakhir, kita memiliki fungsi bernama check_password yang menggunakan check_password_hash, untuk memeriksa kredensial pengguna saat sign in (baris 25-26).

-- Checkpoint: 15_user_model --

Keren! Kita sudah membuat tabel dan model pengguna, yang akan menjadi dasar sistem otentifikasi kita. Sekarang kita buat komponen antarmuka dari sistem otentifikasi ini: halaman pendaftaran.


Membangun halaman pendaftaran

Perencanaan

Lihat gambar Fig. 1 di bawah ini sebagai gambaran sistem pendaftaran.

The Sign up process.The Sign up process.The Sign up process.

Fig. 1

Implemen SSL untuk keseluruhan situs agar password dan token session tidak bisa dicuri.

Kita lihat gambar di atas:

  1. Seorang pengguna mengunjungi URL /signup untuk membuat akun baru. Halaman ini diterima dari request HTTP GET dan ditampilkan di browser.
  2. Pengguna mengisi form dengan nama depan, nama belakang, email, dan password.
  3. Ketika pengguna menekan tombol 'create account', form mengirim data ke server dengan request HTTP POST.
  4. Pada server, sebuah fungsi memvalidasi data.
  5. Jika satu atau lebih field tidak lolos validasi, halaman pendaftaran dimuat ulang dengan pesan error, memberi tahu pengguna untuk mencoba lagi.
  6. Jika semua field valid, sebuah objek User baru akand ibuat dan disimpan ke database. Pengguna akan masuk dan diarahkan ke halaman profil.

Langkah ini akan terlihat familiar, karena mirip dengan langkah kita membuat form kontak. Di sini, kita tidak mengirim email di akhir, melainkan menyimpan data pengguna ke database. Artikel sebelumnya sudah menjelaskan cara membuat form dengan detail, saya akan bergerak lebih cepat agar lebih cepat masuk bagian yang menarik.

Membuat form pendaftaran

Kita sudah menginstall Flask-WTF di artikel sebelumnya, jadi kita lanjutkan dengan membuat form baru di dalam forms.py.

app/intro_to_flask/forms.py

1
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField, validators, ValidationError, PasswordField
2
from models import db, User
3
.
4
.
5
.
6
class SignupForm(Form):
7
  firstname = TextField("First name",  [validators.Required("Please enter your first name.")])
8
  lastname = TextField("Last name",  [validators.Required("Please enter your last name.")])
9
  email = TextField("Email",  [validators.Required("Please enter your email address."), validators.Email("Please enter your email address.")])
10
  password = PasswordField('Password', [validators.Required("Please enter a password.")])
11
  submit = SubmitField("Create account")
12
13
  def __init__(self, *args, **kwargs):
14
    Form.__init__(self, *args, **kwargs)
15
16
  def validate(self):
17
    if not Form.validate(self):
18
      return False
19
    
20
    user = User.query.filter_by(email = self.email.data.lower()).first()
21
    if user:
22
      self.email.errors.append("That email is already taken")
23
      return False
24
    else:
25
      return True

Kita mulai dengan mengimpor satu kelas Flask-WTF bernama PasswordField (baris satu), seperti TextField tapi bedanya dia menghasilkank textbox password. Kita mulai database objek db dan model User untuk menangani logika validasi khusus di dalam kelas SignupForm; jadi kita impor mereka juga (baris dua).

Lalu kita buat kelas baru bernama SignupForm berisi field untuk setiap informasi pengguna yang ingin kita kumpulkan (baris 7-11). Ada validator presence di setiap field untuk memastikan field tersebut diisi, dan validator format yang membutuhkan alamat email mengikuti pola: user@example.com.

Lalu, kita tulis konstruktor sederhana untuk kelas tersebut yang hanya akan memanggil konstruktur kelas dasar (baris 13-14).

Jadi kita sudah menambahkan validator untuk field form, tapi kita memerlukan validator tambahan yang memastikan tidak ada akun dengan email yang sama seperti yang dimasukkan oleh pengguna. Untuk melakukan ini kita menghubungkan proses validasi ke validasi pada Flask-WTF.

Sekarang di dalam fungsi validate(), kita pastikan validator presence dan format berjalan dengan memanggil fungsi validate() dari kelas dasar; jika form belum diisi dengan baik, validate() akan mengembalikan False (baris 16-17).

Berikutnya kita detailkan validator khusus kita. Kita mulai dengan mengquery database dengan email yang dimasukkan oleh pengguna (baris 18). Jika kamu ingat pada file models.py, alamat email diubah menjadi huruf kecil untuk memastikan kecocokkan bagaimanapun alamat email tersebut diketik. Ekspresi Flask-SQLAlchemy ini sama dengan statemen SQL berikut:

1
SELECT * FROM users 
2
  WHERE email = self.email.data.lower() 
3
  LIMIT 1

Jika data seorang pengguna sudah ada berdasarkan email yang dikirim, validasi akan gagal dan menghasilkan pesan error berikut: "That email is already taken" (baris 21-22).

Menggunakan Form Pendaftaran

Sekarang kita buat pemetaan URL baru dan template halaman baru untuk form pendaftaran. Buka routes.py dan impor form pendaftaran baru agar bisa kita gunakan.

app/intro_to_flask/routes.py

1
from intro_to_flask import app
2
from flask import render_template, request, flash
3
from forms import ContactForm, SignupForm

Berikutnya, buat pemetaan URL baru.

app/intro_to_flask/routes.py

1
@app.route('/signup', methods=['GET', 'POST'])
2
def signup():
3
  form = SignupForm()
4
  
5
  if request.method == 'POST':
6
    if form.validate() == False:
7
      return render_template('signup.html', form=form)
8
    else:   
9
      return "[1] Create a new user [2] sign in the user [3] redirect to the user's profile"
10
  
11
  elif request.method == 'GET':
12
    return render_template('signup.html', form=form)

Di dalam fungsi signup(), kita buat variabel bernama form yang berisi antarmuka yang bisa digunakan dari kelas SignupForm. Jika sebuah request GET dikirimkan, kita akan mengembalikan template halaman signup.html yang berisi form pendaftaran yang perlu diisi oleh pengguna.

Jika tidak, kita hanya akan melihat teks sementara. Untuk sekarang, teks tersebut mendaftar tiga aksi yang harusnya dilakukan saat sebuah form dikirim secara sukses. Kita akan kembali dan mengubah teks ini dengan kode asli dari bagian "The First Signup" di bawah.

Sekarang kita sudah membuat pemetaan URL, langkah berikutnya adalah membuat template halaman signup.html dan menyimpannya di dalam folder templates/.

app/intro_to_flask/templates/signup.html

1
{% extends "layout.html" %}
2
3
{% block content %}
4
  <h2>Sign up</h2>
5
6
  {% for message in form.firstname.errors %}
7
    <div class="flash">{{ message }}</div>
8
  {% endfor %}
9
  
10
  {% for message in form.lastname.errors %}
11
    <div class="flash">{{ message }}</div>
12
  {% endfor %}
13
  
14
  {% for message in form.email.errors %}
15
    <div class="flash">{{ message }}</div>
16
  {% endfor %}
17
  
18
  {% for message in form.password.errors %}
19
    <div class="flash">{{ message }}</div>
20
  {% endfor %}
21
  
22
  <form action="{{ url_for('signup') }}" method=post>
23
    {{ form.hidden_tag() }}
24
    
25
    {{ form.firstname.label }}
26
    {{ form.firstname }}
27
    
28
    {{ form.lastname.label }}
29
    {{ form.lastname }}
30
    
31
    {{ form.email.label }}
32
    {{ form.email }}
33
    
34
    {{ form.password.label }}
35
    {{ form.password }}
36
    
37
    {{ form.submit }}
38
  </form>
39
    
40
{% endblock %}

Template ini terlihat seperti contact.html. Pertama kita proses semua bagian dan menampilkan pesan error jika dibutuhkan. Lalu kita biarkan Jinja2 membuat sebagian besar form HTML untuk kita. Ingat bagaimana dalam kelas form Signup kita menambahkan pesar error "That email is already taken" menjadi self.email.errors? Itu adalah objek yang sama yang diproses oleh Jinja2 dalam template ini.

Perbedaan dari template contact.html adalah tidak adanya logika if...else.

Dalam template ini, kita ingin mendaftar dan memasukkan pengguna saat mengirim isi form. Hal ini terjadi di backend, jadi statement if...else tidak dibutuhkan.

Akhirnya, tambahkan aturan-aturan CSS ini ke dalam file main.css agar formulir pendaftaran terlihat bagus dan cantik.

app/intro_to_flask/static/css/main.css

1
/* Signup form */
2
form input#firstname,
3
form input#lastname,
4
form input#password {
5
  width: 400px;
6
  background-color: #fafafa;
7
  -webkit-border-radius: 3px;
8
     -moz-border-radius: 3px;
9
          border-radius: 3px;
10
  border: 1px solid #cccccc;
11
  padding: 5px;
12
  font-size: 1.1em;
13
}
14
15
form input#password {
16
  margin-bottom: 10px;
17
}

Mari kita periksa halaman pendaftaran yang baru dibuat dengan mengetik:

1
$ cd app/
2
$ python runserver.py

Dan buka http://localhost:5000/signup di browser web favoritmu.

The sign up page.The sign up page.The sign up page.

Bagus! Kami sudah membuat formulir pendaftaran dari awal, menangani validasi kompleks, dan menciptakan sebuah halaman pendaftaran dengan pesan kesalahan yang berguna.

Checkpoint: 16_signup_form

Jika salah satu dari langkah-langkah ini tidak jelas, silakan luangkan waktu untuk meninjau artikel sebelumnya. Artikel itu mencakup setiap langkah secara lebih rinci, dan saya mengikuti langkah yang sama dari artikel ini, untuk membuat formulir pendaftaran tadi.


Pendaftaran pertama

Mari kita mulai dengan mengganti string sementara dalam routes.py. Fungsi signup() dengan beberapa kode asli. Setelah pengisian formulir yang sukses, kita perlu membuat objek User baru, simpan ke database, masukkan pengguna dan arahkan ke halaman profil pengguna. Mari kita lakukan langkah demi langkah, dimulai dengan menciptakan objek User baru dan menyimpannya ke database.

Menyimpan objek pengguna baru

Tambahkan baris lima dan 17-19 ke

routes.py.

app/intro_to_flask/routes.py

1
from intro_to_flask import app
2
from flask import render_template, request, flash, session, url_for, redirect
3
from forms import ContactForm, SignupForm
4
from flask.ext.mail import Message, Mail
5
from models import db, User
6
.
7
.
8
.
9
@app.route('/signup', methods=['GET', 'POST'])
10
def signup():
11
  form = SignupForm()
12
  
13
  if request.method == 'POST':
14
    if form.validate() == False:
15
      return render_template('signup.html', form=form)
16
    else:
17
      newuser = User(form.firstname.data, form.lastname.data, form.email.data, form.password.data)
18
      db.session.add(newuser)
19
      db.session.commit()
20
      
21
      return "[1] Create a new user [2] sign in the user [3] redirect to the user's profile"
22
  
23
  elif request.method == 'GET':
24
    return render_template('signup.html', form=form)

Pertama, kita mengimpor kelas User dari models.py agar kita dapat menggunakannya dalam fungsi signup() (baris 5). Kemudian kita membuat sebuah objek User baru yang disebut newuser dan mengisinya dengan data isian formulir pendaftaran (baris 17).

Selanjutnya, kita tambahkan newuser ke sesi objek database (baris 18), yang merupakan versi Flask-SQLAlchemy dari transaksi database biasa. Fungsi add() menghasilkan pernyataan INSERT dengan menggunakan atribut objek User. Pernyataan SQL yang setara untuk ekspresi Flask-SQLAlchemy ini adalah:

1
  INSERT INTO users (firstname, lastname, email, pwdhash)
2
  VALUES (form.firstname.data, form.lastname.data, form.email.data, form.password.data)

Terakhir, kita perbarui database dengan data pengguna baru dengan melakukan transaksi (baris 19).

SIgn-in pengguna

Selanjutnya, kita perlu memasukkan pengguna. Aplikasi Flask perlu tahu bahwa permintaan halaman berikutnya muncul dari browser pengguna yang telah berhasil masuk. Kita dapat melakukannya dengan menetapkan cookie di browser pengguna yang mengandung semacam ID dan dihubungkan dengan kunci kredensial pengguna di app Flask.

Dengan cara ini, ID dalam cookie browser akan dilempar ke app pada setiap permintaan halaman berikutnya, dan app akan mencari ID untuk menentukan apakah itu dipetakan dengan kredensial pengguna yang sah.

Jika berhasil, app memungkinkan akses ke bagian-bagian dari situs web yang membutuhkan kamu untuk masuk terlebih dahulu. Kombinasi memiliki kunci disimpan pada klien dan nilai yang disimpan di server disebut sebuah sesi.

Flask memiliki sebuah objek session untuk memenuhi fungsi ini. Flask menyimpan kunci sesi pada cookie yang aman pada klien dan nilai sesi di aplikasi. Mari kita gunakan dalam fungsi signup() kita.

app/intro_to_flask/routes.py

1
from flask import render_template, request, flash, session
2
.
3
.
4
.
5
@app.route('/signup', methods=['GET', 'POST'])
6
def signup():
7
  form = SignupForm()
8
  
9
  if request.method == 'POST':
10
    if form.validate() == False:
11
      return render_template('signup.html', form=form)
12
    else:
13
      newuser = User(form.firstname.data, form.lastname.data, form.email.data, form.password.data)
14
      db.session.add(newuser)
15
      db.session.commit()
16
      
17
      session['email'] = newuser.email
18
      
19
      return "[1] Create a new user [2] sign in the user [3] redirect to the user's profile"
20
  
21
  elif request.method == 'GET':
22
    return render_template('signup.html', form=form)

Kita mulai dengan mengimpor objek session dari Flask pada baris satu. Selanjutnya, kita akan mengaitkan kunci 'email' dengan nilai email dari user yang baru terdaftar (baris 17). Objek session akan mengurus hashing 'email' ke enkripsi ID dan menyimpannya dalam cookie pada browser pengguna. Pada titik ini, pengguna masuk ke aplikasi kita.

Mengarahkan ke halaman profil

Langkah terakhir adalah untuk mengarahkan pengguna ke halaman profil setelah masuk. Kita akan menggunakan fungsi url_for (yang kita sudah lihat dalam layout.html dan contact.html) bersamaan dengan fungsi redirect() dari Flask.

app/intro_to_flask/routes.py

1
2
from intro_to_flask import app
3
from flask import render_template, request, flash, session, url_for, redirect
4
.
5
.
6
.
7
@app.route('/signup', methods=['GET', 'POST'])
8
def signup():
9
  form = SignupForm()
10
  
11
  if request.method == 'POST':
12
    if form.validate() == False:
13
      return render_template('signup.html', form=form)
14
    else:
15
      newuser = User(form.firstname.data, form.lastname.data, form.email.data, form.password.data)
16
      db.session.add(newuser)
17
      db.session.commit()
18
      
19
      session['email'] = newuser.email
20
      return redirect(url_for('profile'))
21
  
22
  elif request.method == 'GET':
23
    return render_template('signup.html', form=form)

pada baris dua, kita mengimpor fungsi url_for() dan redirect() dari Flask. Kemudian pada baris 19, kita ganti string sementara kita dengan redirect ke URL /profile. Kita tidak memiliki pemetaan URL untuk /profile, jadi mari kita buat pemetaan itu berikutnya.

app/intro_to_flask/routes.py

1
2
@app.route('/profile')
3
def profile():
4
5
  if 'email' not in session:
6
    return redirect(url_for('signin'))
7
8
  user = User.query.filter_by(email = session['email']).first()
9
10
  if user is None:
11
    return redirect(url_for('signin'))
12
  else:
13
    return render_template('profile.html')

Di sini akhirnya kita bisa melihat sesi digunakan. Kita mulai pada baris empat dengan mengambil cookie browser dan memeriksa jika itu berisi kunci bernama 'email'. Jika tidak ada, itu berarti pengguna tidak diotentifikasi, jadi kita arahkan pengguna ke halaman signin (kita akan membuat ini pada bagian berikutnya).

Jika kunci 'email' ada, kita lihat nilai email sisi server pengguna yang terkait dengan kunci tersebut menggunakan session['email'], dan kemudian query database untuk mencari pengguna terdaftar dengan alamat email yang sama (baris tujuh). Pernyataan SQL setara untuk ekspresi Flask-SQLAlchemy ini adalah:

1
SELECT * FROM users WHERE email = session['email'];

Jika pengguna terdaftar tidak ditemukan, kita akan mengarahkan ke halaman pendaftaran. Jika tidak, kita tampilkan template profile.html. Mari kita buat profile.html sekarang.

app/intro_to_flask/templates/profile.html

1
{% extends "layout.html" %}
2
{% block content %}
3
  <div class="jumbo">
4
    <h2>Profile<h2>
5
    <h3>This is {{ session['email'] }}'s profile page<h3>
6
  </div>
7
{% endblock %}

Saya buat template profil ini sederhana. Jika kita fokus pada baris lima, kamu akan lihat bahwa kita dapat menggunakan objek session Flask di dalam template Jinja2 . Di sini, saya sudah menggunakannya untuk membuat sebuah string tertentu, tetapi kamu dapat menggunakan kemampuan ini untuk mengambil jenis informasi pengguna lain.

Kita akhirnya siap untuk melihat hasil dari semua kerja keras kita. Ketik perintah berikut pada terminalmu:

1
$ python runserver.py

Buka http://localhost:5000/ pada browser web favoritmu, dan selesaikan proses sign up. Kamu akan disambut dengan halaman profil yang terlihat seperti screenshot berikut:

A successful sign up!A successful sign up!A successful sign up!

Memasukkan pengguna adalah pencapaian besar untuk aplikasi kita. Kita dapat memodifikasi kode dalam fungsi /signup() kita dan memanfaatkan sistem otentikasi sehingga memungkinkan pengguna untuk login masuk dan keluar dari aplikasi.

Checkpoint: 17_profile_page


Membangun sebuah halaman Signin

Membuat halaman signin ini mirip dengan menciptakan halaman pendaftaran — kita harus membuat form untuk signin, pemetaan URL, dan web template. Mari kita mulai dengan membuat kelas SigninForm di forms.py.

app/intro_to_flask/forms.py

1
class SigninForm(Form):
2
  email = TextField("Email",  [validators.Required("Please enter your email address."), validators.Email("Please enter your email address.")])
3
  password = PasswordField('Password', [validators.Required("Please enter a password.")])
4
  submit = SubmitField("Sign In")
5
  
6
  def __init__(self, *args, **kwargs):
7
    Form.__init__(self, *args, **kwargs)
8
9
  def validate(self):
10
    if not Form.validate(self):
11
      return False
12
    
13
    user = User.query.filter_by(email = self.email.data.lower()).first()
14
    if user and user.check_password(self.password.data):
15
      return True
16
    else:
17
      self.email.errors.append("Invalid e-mail or password")
18
      return False

Kelas SigninForm sama dengan kelas SignupForm. Untuk memasukkan pengguna, kita perlu untuk mengambil email dan sandi, jadi kita membuat dua field tersebut dengan validator keberadaan dan validator format (baris2-3). Kemudian kita mendefinisikan validator khusus dalam fungsi validate() (baris 10-15). Kali ini validator perlu memastikan bahwa pengguna ada di database dan memiliki password yang benar. Jika ditemukan data dengan informasi yang disediakan, kita periksa apakah password sudah sesuai (baris 14). Jika cocok, validasi berhasil (baris 15), jika sebaliknya pengguna akan mendapatkan pesan kesalahan.

Selanjutnya, mari kita membuat pemetaan URL di routes.py.

app/intro_to_flask/routes.py

1
2
...
3
from forms import ContactForm, SignupForm, SigninForm
4
.
5
.
6
.
7
@app.route('/signin', methods=['GET', 'POST'])
8
def signin():
9
  form = SigninForm()
10
  
11
  if request.method == 'POST':
12
    if form.validate() == False:
13
      return render_template('signin.html', form=form)
14
    else:
15
      session['email'] = form.email.data
16
      return redirect(url_for('profile'))
17
                
18
  elif request.method == 'GET':
19
    return render_template('signin.html', form=form)

Sekali lagi, fungsi signin() ini mirip dengan fungsi signup(). Kita impor SigninForm (baris 2), agar kita dapat menggunakannya dalam fungsi signin(). Kemudian di signin(), kita kembalikan template signin.html untuk permintaan GET (baris 17-18).

Jika form telah dikirim (POST) dan setiap validasi gagal, form signin dimuat ulang dengan pesan kesalahan berguna (baris 11-12). Jika berhasil, masukkan pengguna dengan menciptakan sesi baru dan arahkan ke halaman profil mereka (baris 14-15).

Terakhir, mari kita membuat web template signin.html.

app/intro_to_flask/templates/signin.html

1
{% extends "layout.html" %}
2
3
{% block content %}
4
  <h2>Sign In</h2>
5
6
  {% for message in form.email.errors %}
7
    <div class="flash">{{ message }}</div>
8
  {% endfor %}
9
  
10
  {% for message in form.password.errors %}
11
    <div class="flash">{{ message }}</div>
12
  {% endfor %}
13
  
14
  <form action="{{ url_for('signin') }}" method=post>
15
    {{ form.hidden_tag() }}
16
    
17
    {{ form.email.label }}
18
    {{ form.email }}
19
    
20
    {{ form.password.label }}
21
    {{ form.password }}
22
    
23
    {{ form.submit }}
24
  </form>
25
    
26
{% endblock %}

Mirip dengan template signup.html, pertama kita proses semua dan tampilkan pesan kesalahan, lalu kita biarkan Jinja2 membuat form untuk kita.

Denga begitu halaman signin sudah selesai. Kunjungi http://localhost:5000/signin untuk mencobanya. Cobalah untuk masuk, kamu akan diarahkan ke halaman profilmu.

A successful sign in.A successful sign in.A successful sign in.

Checkpoint: 18_signin_form


Keluar

Dalam bagian "Signup Pertama" di atas, kita lihat bahwa "sign in" berarti menyimpan cookie di browser pengguna berisi ID dan ID yang berhubungan dengan data pengguna di app Flask. Oleh karena itu, "sign out" berarti membersihkan cookie di browser dan melepaskan data pengguna.

Ini dapat dicapai dalam satu baris: session.pop('email', None).

Kita tidak perlu form atau web template untuk sign out. Yang kita butuhkan hanya pemetaan URL di routes.py, yang menghapus sesi dan mengarahkan ke halaman depan. Pemetaan tersebut sangat sederhana:

app/intro_to_flask/routes.py

1
@app.route('/signout')
2
def signout():
3
4
  if 'email' not in session:
5
    return redirect(url_for('signin'))
6
    
7
  session.pop('email', None)
8
  return redirect(url_for('home'))

Pengguna tidak diotentikasi jika cookie browser tidak mengandung sebuah kunci bernama 'email', dalam hal ini, kami hanya mengarahkan ke halaman signin (baris 4-5). Jika berjasil, kita akan mengakhiri sesi (baris tujuh) dan mengarahkan kembali ke halaman depan (baris 8).

Kamu dapat menguji fungsi keluar dengan mengunjungi http://localhost:5000/signout. Jika kamu sudah sign in, app akan keluar dan mengarahkan ulang ke http://localhost:5000/. Setelah kamu sign out, cobalah mengunjungi halaman profil http://localhost:5000/profile. Kamu seharusnya tidak diizinkan untuk melihat halaman profil jika kamu tidak sign in, dan app akan mengarahkan kamu kembali ke halaman Signin.

Checkpoint: 19_signout


Merapikan

Sekarang kita perlu untuk memperbarui header situs dengan link navigasi "Sign Up", "Masuk", "Profil" dan "Sign Out". Link harus berubah berdasarkan apakah pengguna sudah masuk atau tidak. Jika pengguna saat ini tidak masuk, link untuk "Sign Up" dan "Sign In" harus terlihat. Saat pengguna sudah masuk, kita ingin link untuk "Profile" dan "Sign Out" muncul, lalu menyembunyikan link "Sign Up" dan "Sign In".

Jadi bagaimana kita bisa melakukan ini? Ingat kembali ke template profile.html dimana kita menggunakan objek session Flask. Kita dapat menggunakan objek session untuk menunjukkan link navigasi berdasarkan status otentikasi pengguna. Mari kita buka layout.html dan buat perubahan berikut:

app/intro_to_flask/templates/layout.html

1
<!DOCTYPE html>
2
<html>
3
  <head>
4
    <title>Flask App</title>
5
    <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
6
  </head>
7
  <body>
8
9
  <header>
10
    <div class="container">
11
      <h1 class="logo">Flask App</h1>
12
      <nav>
13
        <ul class="menu">
14
          <li><a href="{{ url_for('home') }}">Home</a></li>
15
          <li><a href="{{ url_for('about') }}">About</a></li>
16
          <li><a href="{{ url_for('contact') }}">Contact</a></li>
17
          {% if 'email' in session %}
18
          <li><a href="{{ url_for('profile') }}">Profile</a></li>
19
          <li><a href="{{ url_for('signout') }}">Sign Out</a></li>
20
          {% else %}
21
          <li><a href="{{ url_for('signup') }}">Sign Up</a></li>
22
          <li><a href="{{ url_for('signin') }}">Sign In</a></li>
23
          {% endif %}
24
        </ul>
25
      </nav>
26
    </div>
27
  </header>
28
29
    <div class="container">
30
      {% block content %}
31
      {% endblock %}
32
    </div>
33
34
  </body>
35
</html>

Mulai pada baris 17, kita gunakan sintaks if...else dari Jinja2 dan objek session() untuk memeriksa apakah cookie browser berisi kunci 'email'. Jika ada, maka pengguna sudah masuk dan bisa melihat link navigasi "Profile" dan "Sign Out". Jika tidak, pengguna statusnya tidak masuk dan akan melihat link ke "Sign Up" dan "Sign In".

Cobalah! Lihat bagaimana link navigasi muncul dan menghilang dengan kita masuk dan keluar dari aplikasi.

Sisa tugas terakhir adalah masalah yang mirip: ketika pengguna masuk, kita tidak ingin dia bisa mengunjungi halaman pendaftaran dan login. Tidak masuk akal bagi pengguna yang sudah masuk untuk mengotentikasi diri lagi. Jika pengguna yang sudah masuk mencoba untuk mengunjungi halaman ini, mereka harus diarahkan ke halaman profil mereka. Buka routes.py dan tambahkan potongan kode berikut ke awal fungsi signup() dan signin():

1
if 'email' in session:
2
  return redirect(url_for('profile'))

Berikut adalah bagaimana file routes.py file terlihat setelah menambahkan potongan kode tersebut:

app/intro_to_flask/routes.py

1
2
@app.route('/signup', methods=['GET', 'POST'])
3
def signup():
4
  form = SignupForm()
5
6
  if 'email' in session:
7
    return redirect(url_for('profile')) 
8
.
9
.
10
.
11
@app.route('/signin', methods=['GET', 'POST'])
12
def signin():
13
  form = SigninForm()
14
15
  if 'email' in session:
16
    return redirect(url_for('profile'))  
17
  ...

Dan dengan itu kita selesai! Coba kunjungi halaman "signup" atau "signin" ketika kamu sudah masuk, untuk mengujinya.

Checkpoint: 20_visibility_control


Kesimpulan

Kita telah membuat banyak hal dalam artikel ini. Kami telah membuat aplikasi Flask dari modul Python sederhana dan mengubahnya menjadi sebuah aplikasi yang terorganisasi dengan baik, mampu menangani otentikasi pengguna.

Ada beberapa ide pengembangan untuk aplikasi ini. Berikut adalah beberapa diantaranya:

  • Bolehkan pengguna masuk dengan account yang sudah ada, seperti akun Google mereka, dengan menambahkan dukungan untuk OpenID.
  • Berikan pengguna kemampuan untuk memperbarui informasi akun mereka, serta menghapus akun mereka.
  • Bolehkan pengguna membuat ulang sandi jika mereka lupa.
  • Menerapkan sistem otorisasi.
  • Rilis ke server produksi. Perhatikan bila menggunakan aplikasi ini untuk produksi, kamu perlu untuk menerapkan SSL di keseluruhan situs agar password dan token sesi tidak dapat disadap. Jika kamu menggunakan Heroku, kamu dapat menggunakan sertifikat SSL mereka.

Jadi maju dan terus pelajari Flask, dan bangun aplikasi keren kamu berikutnya! Terima kasih untuk membaca.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.