第15章 统计管理模块开发
15-1 定时器统计入口模型 + 会员统计功能 (runserver 添加命令)
from application import app, manager, db
from application import app, manager
from flask_script import Server
from jobs.launcher import runjob
from flask_migrate import Migrate, MigrateCommand
import www
# python manage.py runserver
manager.add_command('runserver', Server(host='0.0.0.0', port=app.config['SERVER_PORT'], use_debugger=True, use_reloader=True))
# database migrate comand
"""
python manage.py db init
python manage.py db migrate
python manage.py db upgrade
"""
Migrate(app, db)
manager.add_command('db', MigrateCommand)
# web server
manager.add_command('runserver', Server(
# host='0.0.0.0',
port=app.config['SERVER_PORT'],
use_debugger=True
))
# job entrance
manager.add_command('runjob', runjob())
def main():
manager.run()
if __name__ == '__main__':
try:
import sys
sys.exit(main())
except Exception as e:
import traceback
traceback.print_exc() # 打印错误
# -*- coding: utf-8 -*-
from application import app, manager
from flask_script import Command, Option
import sys, argparse, traceback
"""
python manage.py runjob -m Test ( jobs/tasks/Test.py )
python manage.py runjob -m test/Index ( jobs/tasks/test/Index.py )
* name or flags - 名称或选项字符串列表, e.g. foo or -f, --foo.
* action - 参数如果定义了选项,表示这是一个操作参数,至于调用时做哪种操作由用户输入或者default决定。
* nargs - 应该使用的命令行参数数。.
* const - 某些动作或参数个数的常数值。.
* default - 如果命令行没有对输入这个参数相应的值,则此参数用default给出的值.
* type -将用户输入的值转化为哪种类型.
* choices - 参数可输入值的范围或选择.
* required - 命令行输入的值是否可以被忽略(布尔量).
* help - 参数的简要描述.
* metavar - useage中显示的参数的名称.
* dest - 要添加到解析参数返回的对象中的属性的名称.
"""
class runjob(Command):
"""
运行job
"""
help = description = 'Runs the job'
capture_all_args = True
def run(self, *args, **kwargs):
args = sys.argv[2:]
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument("-m", "--name", dest="name", metavar="name", help="指定job名", required=True)
parser.add_argument("-a", "--act", dest="act", metavar="act", help="Job动作", required=False)
parser.add_argument("-p", "--param", dest="param", nargs="*", metavar="param", help="业务参数", default='',
required=False)
params = parser.parse_args(args)
params_dict = params.__dict__
ret_params = {}
for item in params_dict.keys():
ret_params[item] = params_dict[item]
if "name" not in ret_params or not ret_params['name']:
return self.tips()
module_name = ret_params['name'].replace("/", ".")
try:
import_string = "from jobs.tasks.%s import JobTask as job_target" % (module_name)
# exec(source, globals=None, locals=None, /)动态执行Python代码
exec(import_string, globals())
target = job_target()
target.run(ret_params)
except Exception as e:
traceback.print_exc()
def tips(self):
tip_msg = '''
请正确调度Job
python manage runjob -m Test ( jobs/tasks/Test.py )
python manage runjob -m test/Index ( jobs/tasks/test/Index.py )
python manage.py runjob -m stat/daily -a site -p 2020-03-22 # 全站统计
'''
app.logger.info(tip_msg)
return False
15-2 会员、商品售卖统计任务
# -*- coding: utf-8 -*-
import random, datetime
from sqlalchemy import func
from application import app, db
from common.models.member.Member import Member
from common.models.food.food import Food
from common.libs.Helper import getFormatDate, getCurrentDate
from common.models.food.food_sale_change_log import FoodSaleChangeLog
from common.models.food.WxShareHistory import WxShareHistory
from common.models.pay.pay_order import PayOrder
from common.models.stat.stat_daily_food import StatDailyFood
from common.models.stat.stat_daily_member import StatDailyMember
from common.models.stat.stat_daily_site import StatDailySite
"""
python manager.py runjob -m stat/daily -a member|food|site/test -p 2018-07-01
"""
class JobTask():
def __init__(self):
pass
def run(self, params):
act = params['act'] if 'act' in params else ''
date = params['param'][0] if params['param'] and len(params['param']) else getFormatDate(format="%Y-%m-%d")
if not act:
return
date_from = date + " 00:00:00"
date_to = date + " 23:59:59"
func_params = {
'act': act,
'date': date,
'date_from': date_from,
'date_to': date_to
}
if act == "member":
self.statMember(func_params)
elif act == "food":
self.statFood(func_params)
elif act == "site":
self.statSite(func_params)
elif act == "test":
self.test()
app.logger.info("it's over~~")
return
def statMember(self, params):
"""
会员统计
:param params:
:return:
"""
act = params['act']
date = params['date']
date_from = params['date_from']
date_to = params['date_to']
app.logger.info("act:{0},from:{1},to:{2}".format(act, date_from, date_to))
member_list = Member.query.all()
if not member_list:
app.logger.info("no member list")
return
for member_info in member_list:
tmp_stat_member = StatDailyMember.query.filter_by(date=date, member_id=member_info.id).first()
if tmp_stat_member:
tmp_model_stat_member = tmp_stat_member
else:
tmp_model_stat_member = StatDailyMember()
tmp_model_stat_member.date = date
tmp_model_stat_member.member_id = member_info.id
tmp_stat_pay = db.session.query(func.sum(PayOrder.total_price).label("total_pay_money")) \
.filter(PayOrder.member_id == member_info.id, PayOrder.status == 1) \
.filter(PayOrder.created_time >= date_from, PayOrder.created_time <= date_to).first()
tmp_stat_share_count = WxShareHistory.query.filter(PayOrder.member_id == member_info.id) \
.filter(PayOrder.created_time >= date_from, PayOrder.created_time <= date_to).count()
# 当日分享总次数
tmp_model_stat_member.total_shared_count = tmp_stat_share_count
# 当日付款总金额
tmp_model_stat_member.total_pay_money = tmp_stat_pay[0] if tmp_stat_pay[0] else 0.00
tmp_model_stat_member.created_time = getCurrentDate()
'''
为了测试效果模拟数据
'''
tmp_model_stat_member.total_shared_count = random.randint(50, 100)
tmp_model_stat_member.total_pay_money = random.randint(1000, 1010)
tmp_model_stat_member.updated_time = getCurrentDate()
db.session.add(tmp_model_stat_member)
db.session.commit()
return
def statFood(self, params):
"""
food统计
:param params:
:return:
"""
act = params['act']
date = params['date']
date_from = params['date_from']
date_to = params['date_to']
app.logger.info("act:{0},from:{1},to:{2}".format(act, date_from, date_to))
stat_food_list = db.session.query(FoodSaleChangeLog.food_id,
func.sum(FoodSaleChangeLog.quantity).label("total_count"),
func.sum(FoodSaleChangeLog.price).label("total_pay_money")) \
.filter(FoodSaleChangeLog.created_time >= date_from, FoodSaleChangeLog.created_time <= date_to) \
.group_by(FoodSaleChangeLog.food_id).all()
if not stat_food_list:
app.logger.info("no data")
return
for item in stat_food_list:
tmp_food_id = item[0]
tmp_stat_food = StatDailyFood.query.filter_by(date=date, food_id=tmp_food_id).first()
if tmp_stat_food:
tmp_model_stat_food = tmp_stat_food
else:
tmp_model_stat_food = StatDailyFood()
tmp_model_stat_food.date = date
tmp_model_stat_food.food_id = tmp_food_id
tmp_model_stat_food.created_time = getCurrentDate()
# 售卖总数量
tmp_model_stat_food.total_count = item[1]
# 总售卖金额
tmp_model_stat_food.total_pay_money = item[2]
tmp_model_stat_food.updated_time = getCurrentDate()
'''
为了测试效果模拟数据
'''
tmp_model_stat_food.total_count = random.randint(50, 100)
tmp_model_stat_food.total_pay_money = random.randint(1000, 1010)
db.session.add(tmp_model_stat_food)
db.session.commit()
return
15-3 全站统计任务和历史数据初始化技巧
def statSite(self, params):
"""
site全站统计
:param params:
:return:
"""
act = params['act']
date = params['date']
date_from = params['date_from']
date_to = params['date_to']
app.logger.info("act:{0},from:{1},to:{2}".format(act, date_from, date_to))
stat_pay = db.session.query(func.sum(PayOrder.total_price).label("total_pay_money")) \
.filter(PayOrder.status == 1) \
.filter(PayOrder.created_time >= date_from, PayOrder.created_time <= date_to).first()
stat_member_count = Member.query.count()
stat_new_member_count = Member.query.filter(Member.created_time >= date_from,
Member.created_time <= date_to).count()
stat_order_count = PayOrder.query.filter_by(status=1) \
.filter(PayOrder.created_time >= date_from, PayOrder.created_time <= date_to) \
.count()
stat_share_count = WxShareHistory.query.filter(WxShareHistory.created_time >= date_from
, WxShareHistory.created_time <= date_to).count()
tmp_stat_site = StatDailySite.query.filter_by(date=date).first()
if tmp_stat_site:
tmp_model_stat_site = tmp_stat_site
else:
tmp_model_stat_site = StatDailySite()
tmp_model_stat_site.date = date
tmp_model_stat_site.created_time = getCurrentDate()
# 当日应收总金额
tmp_model_stat_site.total_pay_money = stat_pay[0] if stat_pay[0] else 0.00
# 会员总数
tmp_model_stat_site.total_new_member_count = stat_new_member_count
# 当日新增会员数
tmp_model_stat_site.total_member_count = stat_member_count
# 当日订单数
tmp_model_stat_site.total_order_count = stat_order_count
# 当日分享数
tmp_model_stat_site.total_shared_count = stat_share_count
tmp_model_stat_site.updated_time = getCurrentDate()
'''
为了测试效果模拟数据
'''
tmp_model_stat_site.total_pay_money = random.randint(1000, 1010)
tmp_model_stat_site.total_new_member_count = random.randint(50, 100)
tmp_model_stat_site.total_member_count += tmp_model_stat_site.total_new_member_count
tmp_model_stat_site.total_order_count = random.randint(900, 1000)
tmp_model_stat_site.total_shared_count = random.randint(1000, 2000)
db.session.add(tmp_model_stat_site)
db.session.commit()
def test(self):
now = datetime.datetime.now()
for i in reversed(range(1, 30)):
date_before = now + datetime.timedelta(days=-i)
date = getFormatDate(date=date_before, format="%Y-%m-%d")
tmp_params = {
'act': 'test',
'date': date,
'date_from': date + " 00:00:00",
'date_to': date + " 23:59:59"
}
self.testFood(date)
self.statFood(tmp_params)
self.statMember(tmp_params)
self.statSite(tmp_params)
def testFood(self, date):
list = Food.query.all()
if list:
for item in list:
model = FoodSaleChangeLog()
model.food_id = item.id
model.quantity = random.randint(1, 10)
model.price = model.quantity * item.price
model.member_id = 1
model.created_time = date + " " + getFormatDate(format="%H:%M:%S")
db.session.add(model)
db.session.commit()
15-4 仪表板统计数据展示
# -*- coding: utf-8 -*-
import datetime
from flask import Blueprint
from common.libs.Helper import ops_render, getFormatDate
from common.models.stat.stat_daily_site import StatDailySite
route_index = Blueprint('index_page', __name__)
@route_index.route('/')
def index():
resp_data = {
'data': {
'finance': {
'today': 0,
'month': 0
},
'member': {
'today_new': 0,
'month_new': 0,
'total': 0
},
'order': {
'today': 0,
'month': 0
},
'shared': {
'today': 0,
'month': 0
},
}
}
now = datetime.datetime.now()
date_before_30days = now + datetime.timedelta(days=-30)
date_from = getFormatDate(date=date_before_30days, format="%Y-%m-%d")
date_to = getFormatDate(date=now, format="%Y-%m-%d")
list = StatDailySite.query.filter(StatDailySite.date >= date_from) \
.filter(StatDailySite.date <= date_to).order_by(StatDailySite.id.asc()) \
.all()
data = resp_data['data']
if list:
for item in list:
data['finance']['month'] += item.total_pay_money
data['member']['month_new'] += item.total_new_member_count
data['member']['total'] = item.total_member_count
data['order']['month'] += item.total_order_count
data['shared']['month'] += item.total_shared_count
if getFormatDate(date=item.date, format="%Y-%m-%d") == date_to:
data['finance']['today'] = item.total_pay_money
data['member']['today_new'] = item.total_new_member_count
data['order']['today'] = item.total_order_count
data['shared']['today'] = item.total_shared_count
return ops_render("index/index.html", resp_data)
数据库反向生成ORM模型
flask-sqlacodegen "mysql://root:123456@localhost:3306/movie" --tables movie_daily_member --outfile "member.py" --flask
第15章 统计管理模块开发 - hcharts
{% extends 'admin/admin.html' %}
{% block content %}
<section class="content-header">
<h1>某柠檬管理系统</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 首页</a></li>
<li class="active">网站统计</li>
</ol>
</section>
<!-- <div id="page-wrapper" class="gray-bg" style="background-color: #ffffff;">-->
<section class="content" id="showcontent">
<div class="wrapper wrapper-content">
</div>
<div class="row">
<div class="col-lg-1">
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label label-primary pull-right">日统计</span>
<h5>播放概况</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{ data.play.today }}</h1>
<small>播放总数:{{ data.play.total }}</small>
<small>近30日:{{ data.play.month }}</small>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label label-primary pull-right">日统计</span>
<h5>会员</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{ data.member.today_new }}</h1>
<small>会员总数:{{ data.member.total }}</small>
<small>近30日新增:{{ data.member.month_new }}</small>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label label-primary pull-right">日统计</span>
<h5>评论</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{ data.comment.today }}</h1>
<small>近30日新增:{{ data.comment.month }}</small>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12" id="member_order" style="height: 400px;border: 1px solid #e6e6e6;padding-top: 20px;">
使用highchart画图
</div>
<div class="col-lg-12" id="finance" style="height: 400px;border: 1px solid #e6e6e6;padding-top: 20px;">
使用highchart画图
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<!--<script src="{{ url_for('static',filename='plugins/jquery-2.1.1.js') }}"></script>-->
<script src="{{ url_for('static',filename='chart/bootstrap/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static',filename='chart/plugins/layer/layer.js') }}"></script>
<script src="{{ url_for('static',filename='chart/common.js') }}"></script>
<script src="{{ url_for('static',filename='chart/plugins/highcharts/highcharts.js') }}"></script>
<script src="{{ url_for('static',filename='chart/chart.js') }}"></script>
<script src="{{ url_for('static',filename='chart/index/index.js') }}"></script>
<script>
var dashboard_index_ops = {
init: function () {
this.drawChart();
},
drawChart: function () {
charts_ops.setOption();
$.ajax({
url: common_ops.buildUrl("dashboard"),
dataType: 'json',
success: function (res) {
charts_ops.drawLine($('#member_order'), res.data)
}
});
$.ajax({
url: common_ops.buildUrl("finance"),
dataType: 'json',
success: function (res) {
charts_ops.drawLine($('#finance'), res.data)
}
});
}
};
$(document).ready(function () {
$("#g-1").addClass("active");
$("#g-1-1").addClass("active");
dashboard_index_ops.init();
});
</script>
{% endblock %}
用的到毕设上,效果还不错