Thứ ba, 15/09/2020 | 00:00 GMT+7

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:

  1. User sẽ có thể đăng ký và đăng nhập với quyền là nhân viên
  2. Administrator sẽ có thể tạo, cập nhật và xóa các phòng ban và role
  3. 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ọ
  4. 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:

  1. Cài đặt database
  2. Mô hình
  3. Di cư
  4. Trang chủ
  5. 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:

  1. Python 2.7
  2. Bình giữ nhiệt
  3. 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 , MySQLPostgreSQL . Đố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):

  1. 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ô.

  2. 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à:

  1. TESTING : cài đặt này thành True kí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ành True trong cấu hình để thử nghiệm. Nó mặc định là False .
  2. DEBUG : cài đặt này thành True kí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ành False trong quá trình production . Nó mặc định là False .
  3. SQLALCHEMY_ECHO : đặt giá trị này thành True giú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_CONFIGFLASK_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 , DepartmentRole .

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_viewlogin_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_idrole_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 DepartmentRole khá giống nhau. Cả hai đều có trường namedescription . 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:

  1. Trang chủ - cái này sẽ có giao diện trang chủ và trang tổng quan
  2. 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 )
  3. 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 statictemplates 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 cssimg . 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 passwordconfirm_password trường trong RegistrationForm trận đấu. Ta cũng tạo các phương thức ( validate_emailvalidate_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 wtfutils 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.htmllogin.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.


Tags:

Các tin liên quan

Cách cài đặt và sử dụng GoAccess Web Log Analyzer trên Ubuntu 20.04
2020-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