Xây dựng ứng dụng web CRUD với Python và Flask - Phần thứ nhất
Trong hướng dẫn ba phần này, ta sẽ xây dựng ứng dụng web quản lý nhân viên CRUD (Tạo, Đọc, Cập nhật, Xóa) bằng cách sử dụng Flask , một microframework cho Python. Tôi đã đặt tên ứng dụng là Project Dream Team và nó sẽ có các tính năng sau:
- User sẽ có thể đăng ký và đăng nhập với quyền là nhân viên
- Administrator sẽ có thể tạo, cập nhật và xóa các phòng ban và role
- Người quản trị sẽ có thể phân công nhân viên vào một bộ phận và phân công role của họ
- Administrator sẽ có thể xem tất cả nhân viên và thông tin chi tiết của họ
Phần Một sẽ bao gồm:
- Cài đặt database
- Mô hình
- Di cư
- Trang chủ
- Xác thực
Sẵn sàng? Bắt đầu!
Yêu cầu
Hướng dẫn này được xây dựng dựa trên hướng dẫn giới thiệu của tôi, Bắt đầu với Flask , bắt đầu lại nơi nó đã dừng lại. Nó giả định bạn đã cài đặt các phần phụ thuộc sau:
- Python 2.7
- Bình giữ nhiệt
- virtualenv (và, tùy chọn, virtualenvwrapper )
Bạn nên cài đặt và kích hoạt một môi trường ảo. Bạn cũng nên có cấu trúc file và folder sau:
├── dream-team
       ├── app
       │   ├── __init__.py
       │   ├── templates
       │   ├── models.py
       │   └── views.py
       ├── config.py
       ├── requirements.txt
       └── run.py
Cấu trúc dự án này  group  các thành phần tương tự của ứng dụng lại với nhau. Thư mục dream-team chứa tất cả các file  dự án. Thư mục app là gói ứng dụng và chứa các module  khác nhau nhưng được liên kết với nhau của ứng dụng. Tất cả các mẫu được lưu trữ trong folder  templates , tất cả các mô hình nằm trong file  models.py và tất cả các tuyến nằm trong file  views.py . Các run.py  file  là điểm nhập của ứng dụng, config.py  file  có chứa các cấu hình ứng dụng, và các requirements.txt  file  chứa các phụ thuộc phần mềm cho ứng dụng.
Nếu bạn chưa cài đặt các cài đặt này, vui lòng truy cập hướng dẫn giới thiệu và bắt kịp!
Cài đặt database
Flask có hỗ trợ cho một số hệ thống quản lý database quan hệ, bao gồm SQLite , MySQL và PostgreSQL . Đối với hướng dẫn này, ta sẽ sử dụng MySQL. Nó phổ biến và do đó có rất nhiều hỗ trợ, ngoài khả năng mở rộng, bảo mật và phong phú về tính năng.
Ta sẽ cài đặt những thứ sau (nhớ kích hoạt môi trường ảo của bạn):
- Flask-SQLAlchemy : Điều này sẽ cho phép ta sử dụng SQLAlchemy , một công cụ hữu ích để sử dụng SQL với Python. SQLAlchemy là một Object Relational Mapper (ORM), nghĩa là nó kết nối các đối tượng của một ứng dụng với các bảng trong hệ thống quản lý database quan hệ. Các đối tượng này có thể được lưu trữ trong database và được truy cập mà không cần viết SQL thô. Điều này rất tiện lợi vì nó đơn giản hóa các truy vấn có thể phức tạp nếu được viết bằng SQL thô. Ngoài ra, nó làm giảm nguy cơ tấn công SQL injection vì ta không xử lý đầu vào của SQL thô. 
- MySQL-Python : Đây là một giao diện Python cho MySQL. Nó sẽ giúp ta kết nối database MySQL với ứng dụng. 
$ pip install flask-sqlalchemy mysql-python
Sau đó, ta sẽ tạo database MySQL. Đảm bảo bạn đã cài đặt và chạy MySQL, sau đó đăng nhập với quyền user root :
$ mysql -u root
mysql> CREATE USER 'dt_admin'@'localhost' IDENTIFIED BY 'dt2016';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE DATABASE dreamteam_db;
Query OK, 1 row affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON dreamteam_db . * TO 'dt_admin'@'localhost';
Query OK, 0 rows affected (0.00 sec)
Bây giờ  ta  đã tạo một  user  mới dt_admin với password  dt2016 , tạo một database  mới dreamteam_db và cấp cho  user  mới tất cả các  quyền  database .
 Tiếp theo, hãy chỉnh sửa config.py . Xóa mọi mã hiện tại và thêm mã sau:
# config.py
class Config(object):
    """
    Common configurations
    """
    # Put any configurations here that are common across all environments
class DevelopmentConfig(Config):
    """
    Development configurations
    """
    DEBUG = True
    SQLALCHEMY_ECHO = True
class ProductionConfig(Config):
    """
    Production configurations
    """
    DEBUG = False
app_config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig
}
Bạn nên chỉ cấu hình cho các môi trường khác nhau. Trong file ở trên, ta có các cấu hình cụ thể để phát triển, ta sẽ sử dụng trong khi xây dựng ứng dụng và chạy nó local , cũng như production , ta sẽ sử dụng khi ứng dụng được triển khai.
Một số biến cấu hình hữu ích là:
-  TESTING: cài đặt này thànhTruekích hoạt chế độ kiểm tra của tiện ích mở rộng Bình. Điều này cho phép ta sử dụng các thuộc tính thử nghiệm, chẳng hạn như có thể có chi phí thời gian chạy tăng lên, chẳng hạn như các trình trợ giúp hấp dẫn nhất. Nó phải được đặt thànhTruetrong cấu hình để thử nghiệm. Nó mặc định làFalse.
-  DEBUG: cài đặt này thànhTruekích hoạt chế độ gỡ lỗi trên ứng dụng. Điều này cho phép ta sử dụng trình gỡ lỗi Flask trong trường hợp ngoại lệ không được xử lý và cũng tự động reload ứng dụng khi nó được cập nhật. Tuy nhiên, nó phải luôn được đặt thànhFalsetrong quá trình production . Nó mặc định làFalse.
-  SQLALCHEMY_ECHO: đặt giá trị này thànhTruegiúp ta gỡ lỗi bằng cách cho phép SQLAlchemy ghi lại lỗi.
Bạn có thể tìm thêm các biến cấu hình Flask tại đây và các biến cấu hình SQLAlchemy tại đây .
 Tiếp theo, tạo một folder  instance trong folder  dream-team , rồi tạo một file  config.py bên trong nó.  Ta  sẽ đặt các biến cấu hình ở đây sẽ không được đẩy sang kiểm soát version  do tính chất nhạy cảm của chúng. Trong trường hợp này,  ta  đặt khóa bí mật cũng như URI database  chứa password   user  database .
# instance/config.py
SECRET_KEY = 'p9Bv<3Eid9%$i01'
SQLALCHEMY_DATABASE_URI = 'mysql://dt_admin:dt2016@localhost/dreamteam_db'
Bây giờ, hãy chỉnh sửa file  app/__init__.py . Xóa bất kỳ mã hiện có nào và thêm mã sau:
# app/__init__.py
# third-party imports
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# local imports
from config import app_config
# db variable initialization
db = SQLAlchemy()
def create_app(config_name):
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_object(app_config[config_name])
    app.config.from_pyfile('config.py')
    db.init_app(app)
    return app
 Ta  đã tạo một hàm, create_app , được đặt tên cấu hình, tải cấu hình chính xác từ file  config.py , cũng như các cấu hình từ file  instance/config.py .  Ta  cũng đã tạo một đối tượng db mà  ta  sẽ sử dụng để tương tác với database .
 Tiếp theo, hãy chỉnh sửa file  run.py :
# run.py
import os
from app import create_app
config_name = os.getenv('FLASK_CONFIG')
app = create_app(config_name)
if __name__ == '__main__':
    app.run()
 Ta  tạo ứng dụng bằng cách chạy hàm create_app và nhập tên cấu hình.  Ta  lấy điều này từ biến môi trường OS FLASK_CONFIG . Bởi vì  ta  đang trong quá trình phát triển,  ta  nên đặt biến môi trường thành development .
 Hãy chạy ứng dụng  đảm bảo  mọi thứ hoạt động như mong đợi. Trước tiên, hãy xóa file  app/views.py cũng như folder  app/templates vì  ta  sẽ không cần chúng về sau. Tiếp theo, thêm một tuyến tạm thời vào file  app/__init__.py như sau:
# app/__init__.py
# existing code remains
def create_app(config_name):
    # existing code remains
    # temporary route
    @app.route('/')
    def hello_world():
        return 'Hello, World!'
    return app
Hãy chắc chắn rằng bạn  cài đặt  FLASK_CONFIG và FLASK_APP biến môi trường trước khi chạy ứng dụng:
$ export FLASK_CONFIG=development
$ export FLASK_APP=run.py
$ flask run
 * Serving Flask app "run"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Ta có thể thấy chuỗi "Hello, World" mà ta đặt trong tuyến đường. Ứng dụng này đang hoạt động tốt cho đến nay.
Mô hình
 Bây giờ để làm việc trên các mô hình.  Lưu ý  một mô hình là một đại diện của một bảng database  trong mã.  Ta   cần  ba mô hình: Employee , Department và Role .
 Nhưng trước tiên, hãy cài đặt Flask-Login , nó sẽ giúp  ta  quản lý  user  và xử lý việc đăng nhập, đăng xuất và phiên  user . Mô hình Employee sẽ kế thừa từ lớp UserMixin của Flask-Login, điều này sẽ giúp  ta  sử dụng các thuộc tính và phương thức của nó dễ dàng hơn.
$ pip install flask-login
Để sử dụng Flask-Login,  ta  cần tạo một đối tượng LoginManager và khởi tạo nó trong file  app/__init__.py . Đầu tiên, hãy xóa tuyến đường mà  ta  đã thêm trước đó, sau đó thêm tuyến đường sau:
# app/__init__.py
# after existing third-party imports
from flask_login import LoginManager
# after the db variable initialization
login_manager = LoginManager()
def create_app(config_name):
    # existing code remains
    login_manager.init_app(app)
    login_manager.login_message = "You must be logged in to access this page."
    login_manager.login_view = "auth.login"
    return app
Ngoài việc khởi tạo đối tượng LoginManager,  ta  cũng đã thêm một login_view và login_message vào nó. Bằng cách này, nếu  user  cố gắng truy cập một trang mà họ không được phép, nó sẽ chuyển hướng đến dạng xem được chỉ định và hiển thị thông báo được chỉ định.  Ta  chưa tạo chế độ xem auth.login , nhưng  ta  sẽ tạo khi xác thực.
 Bây giờ, hãy thêm mã sau vào file  app/models.py :
# app/models.py
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app import db, login_manager
class Employee(UserMixin, db.Model):
    """
    Create an Employee table
    """
    # Ensures table will be named in plural and not in singular
    # as is the name of the model
    __tablename__ = 'employees'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(60), index=True, unique=True)
    username = db.Column(db.String(60), index=True, unique=True)
    first_name = db.Column(db.String(60), index=True)
    last_name = db.Column(db.String(60), index=True)
    password_hash = db.Column(db.String(128))
    department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    is_admin = db.Column(db.Boolean, default=False)
    @property
    def password(self):
        """
        Prevent pasword from being accessed
        """
        raise AttributeError('password is not a readable attribute.')
    @password.setter
    def password(self, password):
        """
        Set password to a hashed password
        """
        self.password_hash = generate_password_hash(password)
    def verify_password(self, password):
        """
        Check if hashed password matches actual password
        """
        return check_password_hash(self.password_hash, password)
    def __repr__(self):
        return '<Employee: {}>'.format(self.username)
# Set up user_loader
@login_manager.user_loader
def load_user(user_id):
    return Employee.query.get(int(user_id))
class Department(db.Model):
    """
    Create a Department table
    """
    __tablename__ = 'departments'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), unique=True)
    description = db.Column(db.String(200))
    employees = db.relationship('Employee', backref='department',
                                lazy='dynamic')
    def __repr__(self):
        return '<Department: {}>'.format(self.name)
class Role(db.Model):
    """
    Create a Role table
    """
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), unique=True)
    description = db.Column(db.String(200))
    employees = db.relationship('Employee', backref='role',
                                lazy='dynamic')
    def __repr__(self):
        return '<Role: {}>'.format(self.name)
Trong Employee mô hình,  ta  tận dụng một số phương pháp bảo mật helper tiện dụng Werkzeug của, generate_password_hash , cho phép  ta  password  băm, và check_password_hash , cho phép  ta  đảm bảo password  băm phù hợp với password . Để tăng cường bảo mật,  ta  có một phương pháp password  đảm bảo  password  không bao giờ có thể bị truy cập; thay vào đó một lỗi sẽ được nâng lên.  Ta  cũng có hai lĩnh vực chính nước ngoài, department_id và role_id , mà tham khảo các ID của các bộ phận và  role  giao cho người lao động.
  Lưu ý   ta  có một trường is_admin được đặt thành False theo mặc định.  Ta  sẽ overrides  điều này khi tạo  admin-user . Ngay sau mô hình Employee ,  ta  có một user_loader gọi lại user_loader , mà Flask-Login sử dụng để  reload  đối tượng  user  từ ID  user  được lưu trữ trong phiên.
 Mô hình Department và Role khá giống nhau. Cả hai đều có trường name và description . Ngoài ra, cả hai đều có mối quan hệ một-nhiều với mô hình Employee (một bộ phận hoặc  role  có thể có nhiều nhân viên).  Ta  xác định điều này trong cả hai mô hình bằng cách sử dụng trường employees . backref cho phép  ta  tạo ra một tài sản mới trên Employee mô hình như vậy mà  ta  có thể sử dụng employee.department hoặc employee.role để có được những bộ phận hoặc  role  giao cho nhân viên đó. lazy xác định cách dữ liệu sẽ được tải từ database ; trong trường hợp này, nó sẽ được tải động, lý tưởng để quản lý các bộ sưu tập lớn.
Di cư
Di chuyển cho phép ta quản lý các thay đổi mà ta thực hiện đối với các mô hình và phổ biến những thay đổi này trong database . Ví dụ: nếu sau này ta thực hiện thay đổi đối với một trường trong một trong các mô hình, tất cả những gì ta cần làm là tạo và áp dụng di chuyển, và database sẽ phản ánh thay đổi.
  Ta  sẽ bắt đầu bằng cách cài đặt Flask-Migrate , sẽ xử lý việc di chuyển database  bằng cách sử dụng Alembic, một công cụ di chuyển database  nhẹ. Alembic phát ra các ALTER đến database , do đó  áp dụng các thay đổi  được thực hiện đối với các mô hình. Nó cũng tự động tạo các tập lệnh di chuyển tối giản, có thể phức tạp để viết.
$ pip install flask-migrate
 Ta   cần  chỉnh sửa file  app/__init__.py :
# app/__init__.py
# after existing third-party imports
from flask_migrate import Migrate
# existing code remains
def create_app(config_name):
    # existing code remains
    migrate = Migrate(app, db)
    from app import models
    return app
 Ta  đã tạo một đối tượng migrate sẽ cho phép  ta  chạy di chuyển bằng Flask-Migrate.  Ta  cũng đã nhập các mô hình từ gói app . Tiếp theo,  ta  sẽ chạy lệnh sau để tạo một repository  di chuyển:
$ flask db init
Điều này tạo ra một folder  migrations trong folder  dream-team :
└── migrations
    ├── README
    ├── alembic.ini
    ├── env.py
    ├── script.py.mako
    └── versions
Tiếp theo, ta sẽ tạo lần di chuyển đầu tiên:
$ flask db migrate
Cuối cùng, ta sẽ áp dụng việc di chuyển:
$ flask db upgrade
Ta đã tạo thành công các bảng dựa trên các mô hình ta đã viết! Hãy kiểm tra database MySQL để xác nhận điều này:
$ mysql -u root
mysql> use dreamteam_db;
mysql> show tables;
+------------------------+
| Tables_in_dreamteam_db |
+------------------------+
| alembic_version        |
| departments            |
| employees              |
| roles                  |
+------------------------+
4 rows in set (0.00 sec)
Bản thiết kế
Bản thiết kế rất tuyệt vời để tổ chức ứng dụng flask thành các thành phần, mỗi thành phần có các dạng và dạng xem riêng. Tôi thấy rằng các bản thiết kế tạo nên một cấu trúc dự án gọn gàng và có tổ chức hơn vì mỗi bản thiết kế là một thành phần riêng biệt giải quyết một chức năng cụ thể của ứng dụng. Mỗi bản thiết kế thậm chí có thể có tiền tố URL cắt hoặc domain phụ của riêng nó. Bản thiết kế đặc biệt thuận tiện cho các ứng dụng lớn.
Ta sẽ có ba bản thiết kế trong ứng dụng này:
- Trang chủ - cái này sẽ có giao diện trang chủ và trang tổng quan
- Administrator - điều này sẽ có tất cả các biểu mẫu và chế độ xem administrator (bộ phận và role )
- Auth - cái này sẽ có tất cả các biểu mẫu và chế độ xác thực (đăng ký và đăng nhập)
Tạo các file và folder có liên quan để cấu trúc folder của bạn giống như sau:
└── dream-team
    ├── app
    │   ├── __init__.py
    │   ├── admin
    │   │   ├── __init__.py
    │   │   ├── forms.py
    │   │   └── views.py
    │   ├── auth
    │   │   ├── __init__.py
    │   │   ├── forms.py
    │   │   └── views.py
    │   ├── home
    │   │   ├── __init__.py
    │   │   └── views.py
    │   ├── models.py
    │   ├── static
    │   └── templates
    ├── config.py
    ├── instance
    │   └── config.py
    ├── migrations
    │   ├── README
    │   ├── alembic.ini
    │   ├── env.py
    │   ├── script.py.mako
    │   └── versions
    │       └── a1a1d8b30202_.py
    ├── requirements.txt
    └── run.py
Tôi đã chọn không có folder  static và templates cho mỗi bản thiết kế, vì tất cả các mẫu ứng dụng sẽ kế thừa từ cùng một mẫu cơ sở và sử dụng cùng một file  CSS. Thay vào đó, folder  templates sẽ có các folder  con cho mỗi bản thiết kế để các mẫu bản thiết kế có thể được  group  lại với nhau.
 Trong mỗi file  __init__.py của blueprint,  ta  cần tạo một đối tượng Blueprint và khởi tạo nó bằng một cái tên.  Ta  cũng cần nhập các khung nhìn.
# app/admin/__init__.py
from flask import Blueprint
admin = Blueprint('admin', __name__)
from . import views
# app/auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views
# app/home/__init__.py
from flask import Blueprint
home = Blueprint('home', __name__)
from . import views
Sau đó,  ta  có thể đăng ký các bản thiết kế trên ứng dụng trong file  app/__init__.py , như sau:
# app/__init__.py
# existing code remains
def create_app(config_name):
    # existing code remains
    from app import models
    from .admin import admin as admin_blueprint
    app.register_blueprint(admin_blueprint, url_prefix='/admin')
    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint)
    from .home import home as home_blueprint
    app.register_blueprint(home_blueprint)
    return app
 Ta  đã nhập từng đối tượng kế hoạch chi tiết và đăng ký nó. Đối với kế hoạch chi tiết dành cho admin ,  ta  đã thêm tiền tố url, /admin . Điều này  nghĩa là  tất cả các chế độ xem cho bản thiết kế này sẽ được truy cập trong trình duyệt với admin tiền tố url.
Bản thiết kế nhà
 Đã đến lúc làm việc để hoàn thành các bản thiết kế!  Ta  sẽ bắt đầu với bản thiết kế home , sẽ có trang chủ cũng như trang tổng quan.
# app/home/views.py
from flask import render_template
from flask_login import login_required
from . import home
@home.route('/')
def homepage():
    """
    Render the homepage template on the / route
    """
    return render_template('home/index.html', title="Welcome")
@home.route('/dashboard')
@login_required
def dashboard():
    """
    Render the dashboard template on the /dashboard route
    """
    return render_template('home/dashboard.html', title="Dashboard")
Mỗi hàm view có một decorator, home.route , có một tham số là đường dẫn URL (hãy nhớ rằng home là tên của bản thiết kế như được chỉ định trong file  app/home/__init__.py ). Mỗi chế độ xem xử lý các yêu cầu đến URL được chỉ định.
 Chế độ xem homepage hiển thị mẫu trang chủ, trong khi chế độ xem dashboard quan hiển thị mẫu trang tổng quan.  Lưu ý  chế độ xem dashboard có trang trí login_required ,  nghĩa là   user  phải đăng nhập để truy cập nó.
 Bây giờ để làm việc trên mẫu cơ sở, mà tất cả các mẫu khác sẽ kế thừa từ đó. Tạo file  base.html trong folder  app/templates và thêm mã sau:
<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ title }} | Project Dream Team</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
    <link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
</head>
<body>
    <nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
        <div class="container topnav">
          <div class="navbar-header">
              <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                  <span class="sr-only">Toggle navigation</span>
                  <span class="icon-bar"></span>
                  <span class="icon-bar"></span>
                  <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand topnav" href="{{ url_for('home.homepage') }}">Project Dream Team</a>
          </div>
          <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav navbar-right">
                  <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
                  <li><a href="#">Register</a></li>
                  <li><a href="#">Login</a></li>
              </ul>
          </div>
        </div>
    </nav>
    <div class="wrapper">
      {% block body %}
      {% endblock %}
      <div class="push"></div>
    </div>
    <footer>
        <div class="container">
            <div class="row">
                <div class="col-lg-12">
                    <ul class="list-inline">
                        <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
                        <li class="footer-menu-divider">⋅</li>
                        <li><a href="#">Register</a></li>
                        <li class="footer-menu-divider">⋅</li>
                        <li><a href="#">Login</a></li>
                    </ul>
                    <p class="copyright text-muted small">Copyright © 2016. All Rights Reserved</p>
                </div>
            </div>
        </div>
    </footer>
</body>
</html>
 Lưu ý   ta  sử dụng # cho liên kết Đăng ký và Đăng nhập.  Ta  sẽ cập nhật điều này khi  ta  đang làm việc trên bản thiết kế auth .
 Tiếp theo, tạo một home folder  bên trong app/templates folder . Mẫu trang chủ, index.html , sẽ đi vào bên trong nó:
<!-- app/templates/home/index.html -->
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block body %}
<div class="intro-header">
    <div class="container">
        <div class="row">
            <div class="col-lg-12">
                <div class="intro-message">
                    <h1>Project Dream Team</h1>
                    <h3>The best company in the world!</h3>
                    <hr class="intro-divider">
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}
Bên trong folder  static , thêm folder  css và img . Thêm file  CSS sau, style.css , vào folder  static/css của bạn ( lưu ý  bạn  cần  hình nền, intro-bg.jpg , cũng như biểu tượng yêu thích trong folder  static/img của bạn):
/* app/static/css/style.css */
body, html {
    width: 100%;
    height: 100%;
}
body, h1, h2, h3 {
    font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-weight: 700;
}
a, .navbar-default .navbar-brand, .navbar-default .navbar-nav>li>a {
  color: #aec251;
}
a:hover, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav>li>a:hover {
  color: #687430;
}
footer {
    padding: 50px 0;
    background-color: #f8f8f8;
}
p.copyright {
    margin: 15px 0 0;
}
.alert-info {
    width: 50%;
    margin: auto;
    color: #687430;
    background-color: #e6ecca;
    border-color: #aec251;
}
.btn-default {
    border-color: #aec251;
    color: #aec251;
}
.btn-default:hover {
    background-color: #aec251;
}
.center {
    margin: auto;
    width: 50%;
    padding: 10px;
}
.content-section {
    padding: 50px 0;
    border-top: 1px solid #e7e7e7;
}
.footer, .push {
  clear: both;
  height: 4em;
}
.intro-divider {
    width: 400px;
    border-top: 1px solid #f8f8f8;
    border-bottom: 1px solid rgba(0,0,0,0.2);
}
.intro-header {
    padding-top: 50px;
    padding-bottom: 50px;
    text-align: center;
    color: #f8f8f8;
    background: url(../img/intro-bg.jpg) no-repeat center center;
    background-size: cover;
    height: 100%;
}
.intro-message {
    position: relative;
    padding-top: 20%;
    padding-bottom: 20%;
}
.intro-message > h1 {
    margin: 0;
    text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
    font-size: 5em;
}
.intro-message > h3 {
    text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
}
.lead {
    font-size: 18px;
    font-weight: 400;
}
.topnav {
    font-size: 14px;
}
.wrapper {
  min-height: 100%;
  height: auto !important;
  height: 100%;
  margin: 0 auto -4em;
}
Chạy ứng dụng; bạn có thể xem trang chủ bây giờ.

Bản thiết kế xác thực
 Đối với bản thiết kế auth ,  ta  sẽ bắt đầu bằng cách tạo các biểu mẫu đăng ký và đăng nhập.  Ta  sẽ sử dụng Flask-WTF , cho phép  ta  tạo các biểu mẫu an toàn (nhờ bảo vệ CSRF và hỗ trợ reCAPTCHA).
pip install Flask-WTF
Bây giờ để viết mã cho các biểu mẫu:
# app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired, Email, EqualTo
from ..models import Employee
class RegistrationForm(FlaskForm):
    """
    Form for users to create new account
    """
    email = StringField('Email', validators=[DataRequired(), Email()])
    username = StringField('Username', validators=[DataRequired()])
    first_name = StringField('First Name', validators=[DataRequired()])
    last_name = StringField('Last Name', validators=[DataRequired()])
    password = PasswordField('Password', validators=[
                                        DataRequired(),
                                        EqualTo('confirm_password')
                                        ])
    confirm_password = PasswordField('Confirm Password')
    submit = SubmitField('Register')
    def validate_email(self, field):
        if Employee.query.filter_by(email=field.data).first():
            raise ValidationError('Email is already in use.')
    def validate_username(self, field):
        if Employee.query.filter_by(username=field.data).first():
            raise ValidationError('Username is already in use.')
class LoginForm(FlaskForm):
    """
    Form for users to login
    """
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')
Flask-WTF có một số trình xác nhận giúp việc viết biểu mẫu trở nên dễ dàng hơn nhiều. Tất cả các trường trong các mô hình đều có trình xác thực DataRequired ,  nghĩa là   user  sẽ được yêu cầu điền vào tất cả chúng để đăng ký hoặc đăng nhập.
 Đối với biểu mẫu đăng ký,  ta  yêu cầu  user  điền địa chỉ email, tên  user , tên, họ và password  của họ hai lần.  Ta  sử dụng Email validator  đảm bảo  các định dạng email hợp lệ được sử dụng (ví dụ như some-name@some-domain.com .)  Ta  sử dụng EqualTo validator để  xác nhận  password và confirm_password trường trong RegistrationForm trận đấu.  Ta  cũng tạo các phương thức ( validate_email và validate_username )  đảm bảo  rằng email và tên  user  đã nhập chưa được sử dụng trước đó.
 Trường submit trong cả hai biểu mẫu sẽ được biểu diễn dưới dạng một nút mà  user  sẽ có thể nhấp vào để đăng ký và đăng nhập tương ứng.
Với các biểu mẫu đã có, ta có thể viết các dạng xem:
# app/auth/views.py
from flask import flash, redirect, render_template, url_for
from flask_login import login_required, login_user, logout_user
from . import auth
from forms import LoginForm, RegistrationForm
from .. import db
from ..models import Employee
@auth.route('/register', methods=['GET', 'POST'])
def register():
    """
    Handle requests to the /register route
    Add an employee to the database through the registration form
    """
    form = RegistrationForm()
    if form.validate_on_submit():
        employee = Employee(email=form.email.data,
                            username=form.username.data,
                            first_name=form.first_name.data,
                            last_name=form.last_name.data,
                            password=form.password.data)
        # add employee to the database
        db.session.add(employee)
        db.session.commit()
        flash('You have successfully registered! You may now login.')
        # redirect to the login page
        return redirect(url_for('auth.login'))
    # load registration template
    return render_template('auth/register.html', form=form, title='Register')
@auth.route('/login', methods=['GET', 'POST'])
def login():
    """
    Handle requests to the /login route
    Log an employee in through the login form
    """
    form = LoginForm()
    if form.validate_on_submit():
        # check whether employee exists in the database and whether
        # the password entered matches the password in the database
        employee = Employee.query.filter_by(email=form.email.data).first()
        if employee is not None and employee.verify_password(
                form.password.data):
            # log employee in
            login_user(employee)
            # redirect to the dashboard page after login
            return redirect(url_for('home.dashboard'))
        # when login details are incorrect
        else:
            flash('Invalid email or password.')
    # load login template
    return render_template('auth/login.html', form=form, title='Login')
@auth.route('/logout')
@login_required
def logout():
    """
    Handle requests to the /logout route
    Log an employee out through the logout link
    """
    logout_user()
    flash('You have successfully been logged out.')
    # redirect to the login page
    return redirect(url_for('auth.login'))
Cũng giống như trong bản thiết kế home , mỗi chế độ xem ở đây xử lý các yêu cầu đến URL được chỉ định. Dạng xem register tạo một thể hiện của lớp mô hình Employee bằng cách sử dụng dữ liệu biểu mẫu đăng ký để điền các trường, sau đó thêm nó vào database . Điều này chắc chắn đăng ký một nhân viên mới.
 Dạng xem login truy vấn database  để kiểm tra xem nhân viên có tồn tại địa chỉ email  trùng với  email được cung cấp trong dữ liệu biểu mẫu đăng nhập hay không. Sau đó, nó sử dụng phương thức verify_password để kiểm tra xem password  trong database  cho nhân viên có  trùng với  password  được cung cấp trong dữ liệu biểu mẫu đăng nhập hay không. Nếu cả hai điều này đều đúng, nó sẽ tiến hành đăng nhập  user  bằng phương thức login_user do Flask-Login cung cấp.
 Chế độ xem logout có trang trí login_required ,  nghĩa là   user  phải đăng nhập để truy cập nó. Nó gọi phương thức logout_user do Flask-Login cung cấp để đăng xuất  user .
 Lưu ý sử dụng phương pháp flash , cho phép  ta  sử dụng tính năng nhấp nháy thông báo của Flask. Điều này cho phép  ta  thông báo phản hồi cho  user , chẳng hạn như thông báo cho họ về việc đăng ký thành công hoặc đăng nhập không thành công.
 Cuối cùng, hãy làm việc trên các mẫu. Đầu tiên,  ta  sẽ cài đặt Flask-Bootstrap để  ta  có thể sử dụng các thư viện wtf và utils của nó. Thư viện wtf sẽ cho phép  ta  nhanh chóng tạo các biểu mẫu trong các mẫu dựa trên các biểu mẫu trong file  forms.py Thư viện utils sẽ cho phép  ta  hiển thị các thông báo flash mà  ta  đã  cài đặt  trước đó để đưa ra phản hồi cho  user .
pip install flask-bootstrap
 Ta  cần chỉnh sửa file  app/__init__.py để sử dụng Flask-Bootstrap:
# app/__init__.py
# after existing third-party imports
from flask_bootstrap import Bootstrap
# existing code remains
def create_app(config_name):
    # existing code remains
    Bootstrap(app)
    from app import models
    # blueprint registration remains here
    return app
 Ta  đã thực hiện khá nhiều chỉnh sửa đối với file  app/__init__.py . Đây là version  cuối cùng của file  và nó sẽ trông như thế nào tại thời điểm này ( lưu ý  tôi đã sắp xếp lại các lần nhập và biến theo thứ tự bảng chữ cái):
# app/__init__.py
# third-party imports
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
# local imports
from config import app_config
db = SQLAlchemy()
login_manager = LoginManager()
def create_app(config_name):
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_object(app_config[config_name])
    app.config.from_pyfile('config.py')
    Bootstrap(app)
    db.init_app(app)
    login_manager.init_app(app)
    login_manager.login_message = "You must be logged in to access this page."
    login_manager.login_view = "auth.login"
    migrate = Migrate(app, db)
    from app import models
    from .admin import admin as admin_blueprint
    app.register_blueprint(admin_blueprint, url_prefix='/admin')
    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint)
    from .home import home as home_blueprint
    app.register_blueprint(home_blueprint)
    return app
 Ta  cần hai mẫu cho bản thiết kế auth : register.html và login.html ,  ta  sẽ tạo trong folder  auth bên trong folder  templates .
<!-- app/templates/auth/register.html -->
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block body %}
<div class="content-section">
  <div class="center">
    <h1>Register for an account</h1>
    <br/>
    {{ wtf.quick_form(form) }}
  </div>
</div>
{% endblock %}
<!-- app/templates/auth/login.html -->
{% import "bootstrap/utils.html" as utils %}
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block body %}
<div class="content-section">
  <br/>
  {{ utils.flashed_messages() }}
  <br/>
  <div class="center">
    <h1>Login to your account</h1>
    <br/>
    {{ wtf.quick_form(form) }}
  </div>
</div>
{% endblock %}
Các biểu mẫu được tải từ file  app/auth/views.py , nơi  ta  chỉ định file  mẫu nào sẽ hiển thị cho mỗi chế độ xem. Nhớ các liên kết Đăng ký và Đăng nhập trong mẫu cơ sở? Hãy cập nhật chúng ngay bây giờ để  ta  có thể truy cập các trang từ menu:
<!-- app/templates/base.html -->
<!-- Modify nav bar menu -->
<ul class="nav navbar-nav navbar-right">
    <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
    <li><a href="{{ url_for('auth.register') }}">Register</a></li>
    <li><a href="{{ url_for('auth.login') }}">Login</a></li>
</ul>
<!-- Modify footer menu -->
<ul class="list-inline">
    <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
    <li class="footer-menu-divider">⋅</li>
    <li><a href="{{ url_for('auth.register') }}">Register</a></li>
    <li class="footer-menu-divider">⋅</li>
    <li><a href="{{ url_for('auth.login') }}">Login</a></li>
</ul>
Chạy lại ứng dụng và nhấp vào liên kết menu Đăng ký và Đăng nhập. Bạn sẽ thấy các mẫu được tải với biểu mẫu thích hợp.
 Cố gắng điền vào mẫu đăng ký;  bạn có thể  đăng ký một nhân viên mới. Sau khi đăng ký, bạn sẽ được chuyển hướng đến trang đăng nhập, nơi bạn sẽ thấy thông báo flash mà  ta  đã  cấu hình  trong file  app/auth/views.py , mời bạn đăng nhập. 


 Đăng nhập sẽ thành công; tuy nhiên bạn sẽ gặp lỗi Template Not Found sau khi đăng nhập, vì mẫu dashboard.html chưa được tạo. Hãy làm điều đó ngay bây giờ:
<!-- app/templates/home/dashboard.html -->
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block body %}
<div class="intro-header">
    <div class="container">
        <div class="row">
            <div class="col-lg-12">
                <div class="intro-message">
                    <h1>The Dashboard</h1>
                    <h3>We made it here!</h3>
                    <hr class="intro-divider">
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}
Làm mới trang. Bạn sẽ nhận thấy rằng menu  chuyển  vẫn có liên kết đăng ký và đăng nhập, mặc dù  ta  đã đăng nhập.  Ta   cần  sửa đổi nó để hiển thị liên kết đăng xuất khi  user  đã được xác thực.  Ta  cũng sẽ bao gồm Hi, username! thông báo trong thanh  chuyển :
<!-- app/templates/base.html -->
<!-- In the head tag, include link to Font Awesome CSS so we can use icons -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Modify nav bar menu -->
<ul class="nav navbar-nav navbar-right">
    {% if current_user.is_authenticated %}
      <li><a href="{{ url_for('home.dashboard') }}">Dashboard</a></li>
      <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
      <li><a><i class="fa fa-user"></i>  Hi, {{ current_user.username }}!</a></li>
    {% else %}
      <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
      <li><a href="{{ url_for('auth.register') }}">Register</a></li>
      <li><a href="{{ url_for('auth.login') }}">Login</a></li>
    {% endif %}
</ul>
<!-- Modify footer menu -->
<ul class="list-inline">
    <li><a href="{{ url_for('home.homepage') }}">Home</a></li>
    <li class="footer-menu-divider">⋅</li>
    {% if current_user.is_authenticated %}
      <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}">Register</a></li>
      <li class="footer-menu-divider">⋅</li>
      <li><a href="{{ url_for('auth.login') }}">Login</a></li>
    {% endif %}
</ul>
Lưu ý cách  ta  sử dụng câu lệnh if-else trong các mẫu. Ngoài ra, hãy lưu ý đến proxy current_user do Flask-Login cung cấp, cho phép  ta  kiểm tra xem  user  có được xác thực hay không và lấy tên  user  của  user . 

Đăng xuất sẽ đưa bạn trở lại trang đăng nhập:

 Việc cố gắng truy cập trang tổng quan mà không cần đăng nhập sẽ chuyển hướng bạn đến trang đăng nhập và hiển thị thông báo  ta  đã đặt trong file  app/__init__.py : 

Lưu ý URL được cấu hình sao cho khi bạn đăng nhập, bạn sẽ được chuyển hướng đến trang mà bạn đã cố gắng truy cập ban đầu, trong trường hợp này là trang tổng quan.
Kết luận
Đó là nó cho Phần Một! Ta đã đề cập khá nhiều: cài đặt database MySQL, tạo mô hình, di chuyển database và xử lý đăng ký, đăng nhập và đăng xuất. Thật tốt vì đã làm được điều đó!
Xem không gian này cho Phần thứ hai, sẽ bao gồm chức năng CRUD của ứng dụng, cho phép admin-user thêm, liệt kê, chỉnh sửa và xóa các phòng ban và role cũng như chỉ định chúng cho nhân viên.
Các tin liên quan
Cách cài đặt và sử dụng GoAccess Web Log Analyzer trên Ubuntu 20.042020-09-15
Phông chữ có thể thay đổi trên web bằng CSS
2020-09-01
Làm thế nào để tạo một Web Scraper đồng thời với Puppeteer, Node.js, Docker và Kubernetes
2020-08-19
Cách tạo ứng dụng web tiến bộ với Angular
2020-07-09
Cách cài đặt Django Web Framework trên Ubuntu 20.04
2020-07-06
Cách tạo chế độ xem để phát triển web Django
2020-05-14
Cách tạo chế độ xem để phát triển web Django
2020-05-14
Cách tạo ứng dụng web bằng Flask trong Python 3
2020-04-16
Cách tạo web server trong Node.js bằng module HTTP
2020-04-10
Mã thông báo web JSON (JWT) trong Express.js
2020-02-19
 

