STT Tên thư mục Diễn giải
1 db Dữ liệu lưu trữ.
2 template Thư mục chứa các thiết kế giao diện(*.html).
3 static Thư mục chứa hình ảnh, file CSS, Bootstrap, Js.
4 venv Thư mục chứa môi trường, các thư viện của chương trình.
5 migration Thư mục chứa các file chuyển đổi dữ liệu.
6 src Thư mục chứa các cấu hình và tính năng của hệ thống.
7 modules Thư mục chứa các file code tính năng của hệt thống.
8 config Thư mục chứa các file môi trường tùy chỉnh của hệ thống.
1.2. Blueprint
Blueprint trong Flask được áp dụng vào cấu trúc của hệ thống CRM là một cấu trúc luận lý đại diện cho một phần của ứng dụng. Blueprint trong hệ thống CRM bao gồm các thành phần như là định tuyến (route), hàm hiển thị, form, template và các file tĩnh và các yếu tố cần thiết liên quan đến một chức năng nhất định trong hệ thống. Được trình bày trong Hình 63 (mục màu tím).
Sau đây là sơ đồ chi tiết của blueprint xác thực chức năng khách hàng (client) trong Hệ thống quản lý quan hệ khách hàng (CRM):
Modules/
|_______ Client/ thư mục cho blueprint
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 63
| | |_______________ __init__.py khởi tạo blueprint
| | |_______________ Client_form.py form thông tin khách hàng
| |_____ Templates/ blueprint templates
| | |_______________ Add-client.html | | |_______________ Client.html | | |_______________ Client-details.html
| |_____ __init__.py nơi đăng ký blueprint
| |_____ Client_controller.py định tuyến chức năng client
| |_____ Client_models.py tạo các class để quản lý ở
Client_controller.py
|__init__.py nơi đăng ký blueprint
Định nghĩa các định tuyến cho blueprint trong hệ thống CRM, sử dụng decorator @client_module.route thay vì @app.route. Bên cạnh đó, cần phải cập nhật các lời gọi hàm url_for() để tạo ra các URL.
Đối với các hàm hiển thị được định nghĩa trực tiếp từ ứng dụng, tham số đầu tiên sẽ là tên hàm hiển thị. Nhưng đối với các định tuyến được định nghĩa bên trong blueprint được sử dụng trong CRM, tham số này bao gồm cả tên của blueprint và tên hàm hiển thị và được ngăn cách bằng dấu chấm.
Cụ thể khi lập trình phải thay thế tất cả các lời gọi hàm url_for('client') với url_for('client.add') và tương tự như vậy cho các trường hợp khác. Đăng ký blueprint khách hàng (client) với ứng dụng như sau:
Khi gọi hàm register_blueprint() trong hệ thống với chức năng client, sử dụng một tham số mới là url_prefix. Tham số này là tùy chọn, nhưng Flask cho phép kết nối một blueprint vào một tiền tố URL, nhờ đó mọi URL trong blueprint client sẽ được bắt đầu với tiền tố này. Mục đích áp dụng để phân biệt rõ các URL giữa các blueprint khác nhau trong hệ thống CRM.
Trong trường hợp này, mọi URL từ blueprint khách hàng sẽ được bắt đầu với tiền tố /client. Ví dụ như URL cho trang tạo thông tin khách hàng sẽ là http://localhost:5000/client/add. Bởi vì hệ thống sử dụng hàm url_for() để tạo ra các URL, tất cả các URL trong blueprint này sẽ được kết hợp với tiền tố này một cách tự động.
2. Code xử lý
Code xử lý trong hệ thống CRM bao gồm các chức năng xem, thêm, xóa, sửa thơng tin trong các chức năng tương ứng như xem danh sách thông tin khách hàng, xem thông tin chi tiết khách hàng, tạo thông tin khách hàng, sửa thơng tin khách hàng, xóa thơng tin khách hàng. Q trình thêm, xóa, sửa đổi với các mục khác thực hiện tương tự …
Ngoài ra, mẫu code xử lý với tính năng quản lý khách hàng (client) được sử dụng để diễn giải cách thức xử lý trong hệ thống CRM.
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 64
2.1. Xem danh sách thông tin khách hàng
2.1.1. Client Model - Tạo các class để quản lý trong Client Controller
import json
from datetime import datetime
from sqlalchemy import ForeignKey, Sequence from sqlalchemy.orm import relationship from src import db
class Client(db.Model):
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, Sequence('client_id_seg'), primary_key=True) name = db.Column(db.String(50), nullable=False)
phone = db.Column(db.String(50), nullable=False) email = db.Column(db.String(100), nullable=False) website = db.Column(db.String(50), nullable=True) vat_number = db.Column(db.String(50), nullable=True) address = db.Column(db.String(100), nullable=True) zip_code = db.Column(db.String(50), nullable=True)
user_id = db.Column(db.Integer, ForeignKey('user.email')) user = relationship('User', backref='clients')
client_status_id = db.Column(db.Integer, ForeignKey('client_status.id')) client_status = relationship('ClientStatus', backref='clients')
country_id = db.Column(db.Integer, ForeignKey('country.id')) country = relationship('Country', backref='clients')
currency_id = db.Column(db.Integer, ForeignKey('currency.id')) currency = relationship('Currency', backref='clients')
def __init__(self, name: str, phone: str, email: str, website: str, vat_number: str, address: str, zip_code: str,
client_status_id: str, country_id: str, currency_id: str):
"""
The constructor for Client model. :param name: The company's name
:param phone: The company's phone number :param email: The email address of the company
:param website: The website url address of the company :param vat_number: The VAT number of the company :param address: The main address of the company :param zip_code: The ZIP Code unit
:param client_status_id: The client status is active or unActive :param country_id: The country's company
:param currency_id: The currency's unit """ self.name = name self.phone = phone self.email = email self.website = website self.vat_number = vat_number self.address = address self.zip_code = zip_code self.client_status_id = client_status_id self.country_id = country_id
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 65
self.currency_id = currency_id
def get_status_class(self):
if self.client_status_id == 1:
return "border border-green-500 text-green-700"
else:
return "border border-red-500 text-red-700"
class ClientStatus(db.Model):
id = db.Column(db.Integer, Sequence('client_status_id_seq'),
primary_key=True)
text = db.Column(db.String(50), nullable=False)
def __repr__(self):
return '<Client Status: {} with {}>'.format(self.id, self.text)
class JsonEcodedDict(db.TypeDecorator):
impl = db.Text
def process_bind_param(self, value, dialect): if value is None:
return '{}'
else:
return json.dumps(value)
def process_result_value(self, value, dialect): if value is None:
return {} else:
return json.loads(value)
class ClientOrder(db.Model):
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, Sequence('client_order_id_seg'),
primary_key=True)
invoice = db.Column(db.String(50), unique=True, nullable=False) status = db.Column(db.String(50), default='Pending', nullable=False) date_created = db.Column(db.DateTime, default=datetime.utcnow,
nullable=False)
orders = db.Column(JsonEcodedDict)
client_id = db.Column(db.Integer, ForeignKey('client.id'), nullable=False) client = relationship('Client', backref='client_orders')
def __repr__(self):
return '<Client Order: {} with {}>'.format(self.invoice)
class ClientOrderHistory(db.Model):
id = db.Column(db.Integer, primary_key=True)
invoice = db.Column(db.String(20), unique=False,nullable=False) status = db.Column(db.String(20), default='Pending', nullable=False) customer_id = db.Column(db.Integer, unique=False, nullable=False) customer_name = db.Column(db.String(50), unique=False)
country = db.Column(db.String(50), unique=False) city = db.Column(db.String(50), unique=False) contact = db.Column(db.String(50), unique=False) zipcode = db.Column(db.String(50), unique=False)
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 66
product_id = db.Column(db.Integer, unique=False, nullable=False) product_name = db.Column(db.String(80), nullable=False)
product_price = db.Column(db.Numeric(10, 2), nullable=False) product_quantity = db.Column(db.Integer, nullable=False)
product_detail = db.Column(db.String(80), default='None', nullable=False) product_brand = db.Column(db.String(50), default='None', unique=False) product_category = db.Column(db.String(50), default='None', unique=False) payment_method = db.Column(db.String(50), default='Cash', unique=False) product_delivered= db.Column(db.String(20), default='False', unique=False) delivered_time = db.Column(db.DateTime, default=datetime.utcnow,
nullable=False)
order_date = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) product_cancel = db.Column(db.Boolean, nullable=False, default=False)
def __repr__(self):
return'<ClientOrder %r>' % self.invoice
2.1.2. Client Controller – Định tuyến chức năng Client
from flask import Blueprint, render_template, redirect, flash, session, url_for,
request, make_response
from flask_login import login_required, current_user from src import db
from src.main.modules.client import Client, ClientStatus, ClientOrder from src.main.modules.client.forms import ClientForm, ClientStatusForm from src.main.modules.country import Country
from src.main.modules.currency import Currency
import secrets import pdfkit
client_module = Blueprint('client', __name__, static_folder='static',
template_folder='templates')
@client_module.route('/', methods=['GET', 'POST']) @login_required
def client():
if current_user.is_authenticated:
return render_template('client.html', user=current_user) return redirect('/')
@client_module.route('/add', methods=['GET', 'POST']) @login_required
def add():
form = ClientForm()
# select customer status form client status table
form.inputClientStatus.choices = [(p.id, p.text) for p in
db.session.query(ClientStatus).all()]
# select currency unit form currency table
form.inputCurrency.choices = [(p.id, p.name) for p in
db.session.query(Currency).all()]
# select country form country table
form.inputCountry.choices = [(p.id, p.name) for p in
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 67
if form.validate_on_submit(): name = form.inputName.data phone = form.inputPhone.data email = form.inputEmail.data website = form.inputWebsite.data vat_number = form.inputVatNumber.data address = form.inputAddress.data zip_code = form.inputZIPCode.data client_status_id = form.inputClientStatus.data country_id = form.inputCountry.data currency_id = form.inputCurrency.data
client = Client(name=name, phone=phone, email=email, website=website,
vat_number=vat_number, address=address,
zip_code=zip_code, client_status_id=client_status_id, country_id=country_id,
currency_id=currency_id)
# add user email to owner customer
client.user = current_user
db.session.add(client) db.session.commit()
return redirect('/client')
form.inputClientStatus.render_kw = {'readonly': 'true', 'style': 'pointer-
events: none'}
return render_template('add-client.html', form=form, user=current_user)
@client_module.route('/edit/<int:id>', methods=['GET', 'POST']) @login_required
def edit(id):
form = ClientForm()
# re-index customer status form client status table
# on get request -- showing the form view
form.inputClientStatus.choices = [(p.id, p.text) for p in
db.session.query(ClientStatus).all()]
# re-index currency unit form currency table
form.inputCurrency.choices = [(p.id, p.name) for p in
db.session.query(Currency).all()]
# re-index country form country table
form.inputCountry.choices = [(p.id, p.name) for p in
db.session.query(Country).all()] the_client = db.session.query(Client).get(id) if form.validate_on_submit(): the_client.name = form.inputName.data the_client.phone = form.inputPhone.data the_client.email = form.inputEmail.data the_client.website = form.inputWebsite.data the_client.vat_number = form.inputVatNumber.data the_client.address = form.inputAddress.data the_client.zip_code = form.inputZIPCode.data the_client.client_status_id = form.inputClientStatus.data the_client.country_id = form.inputCountry.data the_client.currency_id = form.inputCurrency.data db.session.commit()
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 68
return redirect('/client') form.inputName.default = the_client.name form.inputPhone.default = the_client.phone form.inputEmail.default = the_client.email form.inputWebsite.default = the_client.website form.inputVatNumber.default = the_client.vat_number form.inputAddress.default = the_client.address form.inputZIPCode.default = the_client.zip_code form.inputClientStatus.default = the_client.client_status_id form.inputCountry.default = the_client.country_id form.inputCurrency.default = the_client.currency_id form.process()
return render_template('/add-client.html', form=form, user=current_user)
@client_module.route('/delete/<int:id>', methods=['GET', 'POST']) @login_required def delete(id): the_client = db.session.query(Client).filter_by(id=id).first() db.session.delete(the_client) db.session.commit() return redirect(f"/client") @client_module.route('/view/<int:id>', methods=['GET']) @login_required def view(id): if current_user.is_authenticated: the_client = db.session.query(Client).get(id) if not the_client.user == current_user:
flash('You don\'t have any customer information') return redirect('/')
return render_template('client-details.html', client=the_client,
user=current_user)
return redirect('/')
def update_shopping_cart():
for key, shopping in session['Shoppingcart'].items(): session.modified = True
del shopping['colors']
return update_shopping_cart
@client_module.route('/get-order', methods=['GET', 'POST']) @login_required def get_order(): if current_user.is_authenticated: # client_id = current_user.email client_id = request.form.get('client_id') invoice = secrets.token_hex(5) update_shopping_cart try:
the_order = ClientOrder(invoice=invoice, client_id=client_id,
orders=session['Shoppingcart'])
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 69
db.session.commit()
session.pop('Shoppingcart')
flash('Your order has been sent successfully', 'success') return redirect(url_for('orders', invoice=invoice))
except Exception as e: print(e)
flash('Some thing went wrong while get order', 'danger') return redirect(url_for('pos.view_cart')) @client_module.route('/orders/<invoice>') @login_required def orders(invoice): if current_user.is_authenticated: grandTotal = 0 subTotal = 0 orders = ClientOrder.query.filter_by(invoice=invoice).order_by(ClientOrder.id.desc()).fir st()
for _key, product in orders.orders.items():
discount = (product['discount'] / 100) * float(product['price']) subTotal += float(product['price']) * int(product['quantity']) subTotal -= discount
tax = ("%.2f" % (.06 * float(subTotal)))
grandTotal = ("%.2f" % (1.06 * float(subTotal))) else:
return redirect(url_for('/'))
return render_template('/client-order.html', invoice=invoice, tax=tax,
subTotal=subTotal, grandTotal=grandTotal, orders=orders, user=current_user) @client_module.route('/get_pdf/<invoice>', methods=['POST']) @login_required def get_pdf(invoice): if current_user.is_authenticated: grandTotal = 0 subTotal = 0 if request.method == "POST": orders = ClientOrder.query.filter_by(invoice=invoice).order_by(ClientOrder.id.desc()).fir st()
for _key, product in orders.orders.items():
discount = (product['discount']/100) * float(product['price']) subTotal += float(product['price']) * int(product['quantity']) subTotal -= discount
tax = ("%.2f" % (.06 * float(subTotal)))
grandTotal = float("%.2f" % (1.06 * subTotal))
rendered = render_template('/pdf.html', invoice=invoice, tax=tax,
grandTotal=grandTotal, orders=orders) pdf = pdfkit.from_string(rendered, False) response = make_response(pdf) response.headers['content-Type'] ='application/pdf' response.headers['content-Disposition'] ='inline; filename='+invoice+'.pdf' return response return request(url_for('orders'))
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 70
2.1.3. Đăng ký Blueprint
from .client_model import Client, ClientStatus, ClientOrder, ClientOrderHistory from .client_controller import client_module
2.1.4. Form thông tin khách hàng
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField from wtforms.validators import DataRequired
class ClientForm(FlaskForm):
inputName = StringField(label='Name', validators=[
DataRequired(message='Please fill out the name field'),
], render_kw={"placeholder": "Client name"})
inputPhone = StringField(label='Phone Number', validators=[
DataRequired(message='Please fill out the phone number field'),
], render_kw={"placeholder": "Phone number"})
inputEmail = StringField(label='Email address', validators=[
DataRequired(message='Please fill out the email address field'),
], render_kw={"placeholder": "Email address"})
inputWebsite = StringField(label='Website', validators=[
DataRequired(message='Please fill out the website url field'),
], render_kw={"placeholder": "Website URL"})
inputVatNumber = StringField(label='VAT Number', validators=[ DataRequired(message='Please fill out the VAT number field'),
], render_kw={"placeholder": "VAT number"})
inputAddress = StringField(label='Address', validators=[ DataRequired(message='Please fill out the address field'),
], render_kw={"placeholder": "Address"})
inputZIPCode = StringField(label='ZIP Code', validators=[ DataRequired(message='Please fill out the ZIP code field'),
], render_kw={"placeholder": "ZIP code"})
inputClientStatus = SelectField('Client Status', coerce=int)
inputCountry = SelectField('Country', coerce=int)
inputCurrency = SelectField('Currency', coerce=int)
class ClientStatusForm(FlaskForm):
inputText = StringField(label='Status', validators=[
DataRequired(message='Please fill out the status field'),
], render_kw={"placeholder": "Status"})
2.1.5. Khởi tạo Blueprint
from .client_form import ClientForm, ClientStatusForm
2.1.6. Khai báo các định tuyến được định nghĩa bên trong Blueprint
Sau khi lập trình xử lý và định tuyến các class trong chức năng quản lý thông tin khách hàng, để chức năng hoạt động được, cần phải khai báo các định tuyến được định nghĩa bên trong blueprint client của hệ thống.
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 71
2.1.6.1. Main/__init__.py
from flask import Flask
class App(Flask):
def __init__(self, instance_path: str): super(App, self).__init__(
import_name=__name__,
instance_path=instance_path, instance_relative_config=True )
# assigning the base templates & static folder
self.template_folder = './base/templates' self.static_folder = './base/static' # loading environment variables self.load_environment_variables()
# registering essential partials for the app
self.register_blueprints()
self.register_login_manager()
def register_blueprints(self): """
Registering the app's blueprints. """
from src.main.modules.client import client_module
self.register_blueprint(client_module, url_prefix="/client")
def register_login_manager(self): # adding login manager
from flask_login import LoginManager
login_manager = LoginManager() login_manager.login_view = "auth.login" login_manager.init_app(self) @login_manager.user_loader def load_user(email): # registering user_loader
from src.main.modules.user import User
return User.query.get(email)
def load_environment_variables(self): """
Loading the configured environment variables. """
# Load the default configuration (../config/default.py)
self.config.from_object('config.default')
# Load the file specified by the APP_CONFIG_FILE environment variable
# Variables defined here will override those in the default configuration
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 72
2.1.6.2. Src/__init__.py
import sys import os
from pathlib import Path def add_sys_paths():
print('\n[ADDING PATHS TO THE PYTHON ENVIRONMENT...]')
# getting the current file's absolute path
CURRENT_FILE_ABSOLUTE_PATH = Path(__file__).absolute()
# WORKING_DIR: src
WORKING_DIR = os.path.abspath(os.path.join(CURRENT_FILE_ABSOLUTE_PATH, '../'))
# ROOT_DIR (includes the: src ; scripts ; venv ; ..
ROOT_DIR = os.path.abspath(os.path.join(CURRENT_FILE_ABSOLUTE_PATH, '../../'))
# appending the WORKING_DIR, ROOT_DIR to the python environment
sys.path.append(WORKING_DIR)
sys.path.append(ROOT_DIR)
print('\n[PATHS IN THE PYTHON ENVIRONMENT...]:') print('\n'.join(sys.path), '\n')
return WORKING_DIR, ROOT_DIR
def create_app():
# initializing the app
print("\n[INITIALIZING THE APP...]")
from src.main import App
app = App(instance_path=add_sys_paths()[0])
print("\n[INITIALIZING THE DATABASE...]") db.init_app(app=app)
# migrating Models to DB
from flask_migrate import Migrate
print("\n\n[MIGRATING THE DATABASE...]") migrate = Migrate()
with app.app_context():
# allow dropping column for sqlite
if db.engine.url.drivername == 'sqlite': migrate.init_app(app, db, render_as_batch=True) else: migrate.init_app(app, db) # importing models import src.main.modules.user.user_model
# place your new model (want to be created in the app.sqlite) here
import src.main.modules.client.client_model
print('\n\n[NEW APP RETURNED...]') return app
Đề tài đồ án tốt nghiệp 2018 – 2021 Trường Đại học Bà Rịa – Vũng Tàu
SINH VIÊN THỰC HIỆN: BÙI VĂN HUÂN 73
print("\n[DEFINING THE DATABASE INSTANCE...]") from flask_sqlalchemy import SQLAlchemy