flask框架
首先用flask搭建一个本地的网站,对其进行配置
from flask import Flask
app = Flask(__name__)
app.debug = True
debug为TRUE时表示在pycharm中对网页的任何修改都可以随时的显示在网页中。
from flask_sqlalchemy import SQLAlchemy
app.config['SECRET_KEY'] = 'daxiongketang'
app.config['SQLALCHEMY_DATABASE_URI'] = (
'mysql+pymysql://root:root@localhost/daxiong?charset=utf8mb4'
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
对该网站进行配置,网站会使用到MySQL,而在pycharm中用pymysql来启动数据库
路由:用装饰器制定特定的网页路径,当输入该路径时就会执行特定的函数
@app.route('/')
def index():
return 'hello world'
所以当网址为http://127.0.0.1:5000/时就会自动执行这个index函数,网页会显示hello world
if __name__ == "__main__":
app.run();
当执行app.run方法时就可以看到网页了
数据库的设计
有三张数据表分别为course,sale,user,表示课程信息,销售情况,用户信息。 课程信息与销售情况时一对一的关系,所以销售情况要以course_id作为外键,而课程信息与用户时多对多的关系。
这里使用到了sqlalchemy: 介绍一下orm与sqlalchemy ORM 全称 Object Relational Mapping, 叫对象关系映射。简单的说,ORM 将数据库中的表与面向对象语言中的类建立了一种对应关系。这样,我们要操作数据库,数据库中的表或者表中的一条记录就可以直接通过操作类或者类实例来完成。
数据表声明
class Course(db.Model):
course_id = db.Column(db.String(100),primary_key=True,nullable=False)
product_id = db.Column(db.String(100),nullable=False)
product_type = db.Column(db.Integer, nullable=False)
product_name = db.Column(db.String(125), nullable=False)
provider = db.Column(db.String(125), nullable=False)
score = db.Column(db.Float(2))
score_level = db.Column(db.Integer)
learner_count = db.Column(db.Integer)
lesson_count = db.Column(db.Integer)
lector_name = db.Column(db.String(125))
original_price = db.Column(db.Float(2))
discount_price = db.Column(db.Float(2))
discount_rate = db.Column(db.Float(2))
img_url = db.Column(db.String(125))
big_img_url = db.Column(db.String(125))
description = db.Column(db.Text)
class Sale(db.Model):
id = db.Column(db.Integer,autoincrement=True,primary_key=True,nullable=False)
course_id = db.Column(db.String(100),db.ForeignKey('course.course_id'))
product_name = db.Column(db.String(125), nullable=False)
learner_count = db.Column(db.Integer)
create_time = db.Column(db.Date,default= datetime.today())
course = db.relationship('Course',backref=db.backref('sale',lazy='dynamic'))
表建立好之后就可以运行:
if __name__ == "__main__":
db.create_all()
这样就会在MySQL中创建两个表course与sale
flask_script与flask_migrate
Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任务;使得脚本和系统分开;原文链接
manager.add_command('db', MigrateCommand)
manager.add_command('import_data', ImportData())
Flask-Migrate是一个为Flask应用处理SQLAlchemy数据库迁移的扩展,使得可以通过Flask的命令行接口或者Flask-Scripts对数据库进行操作。原文链接
将数据导入数据库
当在终端执行import_data时会执行将数据导入数据库的操作,而将Excel文件中的内容导入数据库要用到openpyxl模块
import os
from flask_script import Command
import openpyxl
from app.config import BASE_DIR
from app.models import Course, Sale
from app import db
class ImportData(Command):
def run(self):
print('导入开始')
dir = os.path.join(BASE_DIR, 'c_excel')
for file_name in os.listdir(dir):
file_path = os.path.join(dir, file_name)
self.save_to_mysql(file_path)
print('导入完成')
def save_to_mysql(self, file_path):
fileds = ['course_id', 'product_id', 'product_name', 'product_type', 'provider', 'score', 'score_level',
'learner_count', 'lesson_count', 'lector_name', 'original_price', 'discount_price', 'discount_rate',
'img_url', 'big_img_url', 'description']
create_time = file_path.split('.')[0][-10:]
wb = openpyxl.load_workbook(file_path)
index = wb.sheetnames
ws = wb[index[0]]
for row in ws.iter_rows(min_row=2):
data = [cell.value for cell in row]
dict_val = dict(zip(fileds, data))
course = Course(**dict_val)
self.save_course(course);
sale = Sale(course_id=dict_val['course_id'], product_name=dict_val['product_name'],
learner_count=dict_val['learner_count'], create_time=create_time)
self.save_sale(sale, create_time)
wb.close()
def save_course(self, course):
try:
db.session.merge(course)
db.session.commit()
except:
db.session.rollback()
def save_sale(self, sale, create_time):
try:
data = Sale.query.filter_by(course_id=sale.course_id, create_time=create_time).first()
if not data:
db.session.add(sale)
db.session.commit()
except:
db.session.rollback()
if __name__ == "__main__":
print(BASE_DIR)
config.py
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
关于openpyxl:openpyxl讲解
网页设计
- 首页:
首页会展示免费课程以及热门课程,均展示6条
@app.route('/')
def index():
free_course = Course.query.filter_by(original_price=0).order_by(
desc('learner_count')).limit(6).all()
hot_course = Course.query.filter(Course.original_price>0).order_by(
desc('score')).limit(6).all()
return render_template('index.html', free_course=free_course, hot_course=hot_course)
当网址后加/index就会展示首页网页(render_template实现) 参考链接:render_template讲解
{% extends 'base.html' %}
{% block content %}
<div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carouselExampleIndicators" data-slide-to="0" class="active"></li>
<li data-target="#carouselExampleIndicators" data-slide-to="1"></li>
</ol>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="../static/images/banner1.png"
class="d-block w-100" alt="..." width="100%" height="380px">
</div>
<div class="carousel-item">
<img src="../static/images/banner2.png"
class="d-block w-100" alt="..." height="380px">
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div class="course_list" >
<div style="text-align:center;padding-top:20px;">
<h3>
<img src="../static/images/title_left.png" height="28px">
<span>免费课程</span>
<img src="../static/images/title_right.png" height="28px">
</h3>
</div>
<div class="container">
<div class="row" style="text-align:center;">
{% for course in free_course %}
<div class="col-sm-4" style="padding: 20px">
<a href="/course/{{course.course_id}}" style="text-decoration: none;color:inherit;">
<div class="card" style="height:280px">
<img src="{{ course.big_img_url }}" class="card-img-top"
alt="..." height="170px">
<div class="card-body">
<p class="card-title" style="font-weight: bold;">{{ course.product_name }}</p>
<p class="card-text">免费</p>
</div>
</div>
</a>
</div>
{% endfor%}
</div>
</div>
</div>
<div class="course_list" >
<div style="text-align:center;padding-top:20px;">
<h3>
<img src="../static/images/title_left.png" height="28px">
<span>热门课程</span>
<img src="../static/images/title_right.png" height="28px">
</h3>
</div>
<!--热门课程-->
<div class="container">
<div class="row" style="text-align:center;">
{% for course in hot_course %}
<div class="col-sm-4" style="padding: 20px">
<a href="/course/{{ course.course_id }}" style="text-decoration: none;color:inherit;">
<div class="card" style="height:280px">
<img src="{{ course.big_img_url }}" class="card-img-top"
alt="..." height="170px">
<div class="card-body">
<p class="card-title" style="font-weight: bold;">{{ course.product_name }}</p>
<p class="card-text">¥{{ course.original_price }}元</p>
</div>
</div>
</a>
</div>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
{% endblock %}
关于课程详细信息的路由:
@app.route('/course/<course_id>')
def detail(course_id):
course = Course.query.filter_by(course_id=course_id).first_or_404()
return render_template('detail.html', course=course)
details.html:课程的详细信息
{% extends 'base.html' %}
{% block content%}
<div class="container">
<div class="row" style="margin:20px auto;">
<div class="col-sm-6">
<img src="{{ course.big_img_url }}" class="card-img-top"
alt="..." width="450px" height="260px">
</div>
<div class="col-sm-6">
<h4>{{ course.product_name }}</h4>
<div class="course-info">课程讲师:{{ course.lector_name }}</div>
<div class="course-info">所属机构:{{ course.provider }}</div>
<div class="course-info">课程评分:{{ course.score }}</div>
<div class="course-info">学习人数:{{ course.learner_count}}</div>
<div class="course-price">
{% if course.discount_price %}
¥{{course.discount_price}}
<span style="color: #666666;font-size: 14px;text-decoration: line-through;">¥{{course.original_price}}</span>
{% elif not course.original_price %}
免费
{% else %}
¥{{course.original_price}}
{% endif %}
</div>
<div>
{% if current_user.is_authenticated %}
<button class="btn btn-success" id="collect">
收藏
</button>
{% endif %}
<a href="https://study.163.com/course/introduction/{{course.course_id}}.htm" target="view_window">
<button type="button" class="btn btn-danger" >直达网易云</button>
</a>
</div>
</div>
</div>
<div class="col-sm">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="课程介绍" aria-selected="true">课程介绍</a>
</li>
<li class="nav-item">
<a class="nav-link" id="week-tab" data-toggle="tab" href="#week" role="tab" aria-controls="本周销量" aria-selected="false">最近一周销量</a>
</li>
<li class="nav-item">
<a class="nav-link" id="month-tab" data-toggle="tab" href="#month" role="tab" aria-controls="本月销量" aria-selected="true">最近一个月销量</a>
</li>
<li class="nav-item">
<a class="nav-link" id="year-tab" data-toggle="tab" href="#year" role="tab" aria-controls="年度销量" aria-selected="false">每月销量</a>
</li>
</ul>
<div class="tab-content" id="myTabContent" style="min-height:260px;padding:20px;background:#fbfbfb;">
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
{% for line in course.description.splitlines() %}
{{ line }} <br>
{% endfor %}
</div>
<div class="tab-pane fade" id="week" role="tabpanel" aria-labelledby="week-tab">
<div id="sale-week" style="width: 1000px;height:400px;"></div>
</div>
<div class="tab-pane fade" id="month" role="tabpanel" aria-labelledby="month-tab">
<div id="sale-month" style="width: 1000px;height:400px;"></div>
</div>
<div class="tab-pane fade" id="year" role="tabpanel" aria-labelledby="year-tab">
<div id="sale-year" style="width: 1000px;height:400px;"></div>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.7.0/echarts-en.common.js"></script>
<script type="text/javascript">
$('.nav-link').click(function(){
var id = $(this).attr('id')
var type = id.split("-")[0]
var echarts_id = 'sale-' + type
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById(echarts_id));
// 指定图表的配置项和数据
myChart.setOption({
backgroundColor: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#c86589'
},
{
offset: 1,
color: '#06a7ff'
}
], false),
title: {
text: "OCTOBER 2015",
left: "center",
bottom: "5%",
textStyle: {
color: "#fff",
fontSize: 16
}
},
grid: {
top: '20%',
left: '10%',
right: '10%',
bottom: '15%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
axisLabel: {
margin: 30,
color: '#ffffff63'
},
axisLine: {
show: false
},
axisTick: {
show: true,
length: 25,
lineStyle: {
color: "#ffffff1f"
}
},
splitLine: {
show: true,
lineStyle: {
color: '#ffffff1f'
}
}
},
yAxis: [{
type: 'value',
position: 'right',
axisLabel: {
margin: 20,
color: '#ffffff63'
},
axisTick: {
show: true,
length: 15,
lineStyle: {
color: "#ffffff1f",
}
},
splitLine: {
show: true,
lineStyle: {
color: '#ffffff1f'
}
},
axisLine: {
lineStyle: {
color: '#fff',
width: 2
}
}
}],
series: [{
name: '注册总量',
type: 'line',
smooth: true, //是否平滑曲线显示
showAllSymbol: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
normal: {
color: "#fff", // 线条颜色
},
},
label: {
show: true,
position: 'top',
textStyle: {
color: '#fff',
}
},
itemStyle: {
color: "red",
borderColor: "#fff",
borderWidth: 3
},
tooltip: {
show: false
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#eb64fb'
},
{
offset: 1,
color: '#3fbbff0d'
}
], false),
}
},
data: [393, 438, 485, 631, 689, 824, 987, 1000, 1100, 1200]
}]
});
// 异步加载数据
var url = '/course_data/' + {{ course.course_id }} + '/type/'+ type
$.get(url).done(function (data) {
data = JSON.parse(data)
// 填入数据
myChart.setOption({
title: {
text: data.title,
},
xAxis: {
data: data.categories
},
series: [{
// 根据名字对应到相应的系列
name: '销量',
data: data.data
}]
});
});
});
</script>
<script>
$('#collect').click(function(){
// 异步加载数据
var url = '/collect/' + {{ course.course_id }}
that = this
<!-- 异步提交后this不指代原来的对象-->
$.get(url).done(function (data) {
if (data.result == 'error'){
alert('系统异常,请稍后重试!')
}
// 请求成功,修改对于文字
var text = $(that).text();
if (text.trim() == '收藏'){
$(that).text('取消收藏');
} else {
$(that).text('收藏');
}
})
})
</script>
{% endblock %}
这里的详细信息会展示最近一个月,一周的销量,使用到了异步加载技术以及用echarts表格展示销量变化情况 异步加载参考:参考博客 表格展示:
@app.route('/course_data/<course_id>/type/<type>')
def course_data(course_id,type):
if type == 'week':
title = '最近一周销量'
condition = 'DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= date(create_time)'
sql = f'select create_time,learner_count from sale where course_id = {course_id} and {condition} order by create_time'
sale_data = db.session.execute(sql)
elif type == 'month':
title = '最近一个月销量'
condition = 'DATE_SUB(CURDATE(),INTERVAL 1 MONTH) <= date(create_time)'
sql = f'select create_time,learner_count from sale where course_id = {course_id} and {condition} order by create_time'
sale_data = db.session.execute(sql)
else:
title = '本年度每月销量'
days = []
current_year = datetime.datetime.today().year
for i in range(1,13):
days.append(f'{current_year}-{i:02}-01')
condition = f'create_time in {tuple(days)}'
sql = f'select create_time,learner_count from sale where course_id = {course_id} and {condition} order by create_time'
sale_data = db.session.execute(sql)
data = {}
create_time = []
learner_count = []
for item in sale_data:
print(item)
create_time.append(item[0].strftime('%m-%d'))
learner_count.append(item[1])
data['title'] = title
data['categories'] = create_time
data['data'] = learner_count
print(data)
result = json.dumps(data)
return result
|