elm_flask 项目日志 -- flask后台管理系统开发设计

后台管理项目日志

项目初始化阶段

仓库使用
  1. 注册码云平台
  2. 创建工程仓库
  3. 创建自己的开发分支
  4. clone/push/pull的操作

新建开发分支

能够在远程分支进行pull和push

项目搭建
  1. 搭建项目基本框架目录结构

  2. 测试首页是否显示

工厂函数的设计

插件的注册顺序及循环引用如何解决

# 工厂函数,注册顺序通过函数来保证
from flask import Flask

from apps.model.user_model import User
from flask_session import Session


def register_bp(app: Flask):
    from apps.cms import cms_bp
    app.register_blueprint(cms_bp)


def register_db(app: Flask):
    from apps.model import db
    db.init_app(app=app)


def log_manager(app: Flask):
    from apps.libs.login_helper import login_manager
    app.config['SECRET_KEY'] = '1234567'
    login_manager.init_app(app)


def create_cms_app(config: str):
    app = Flask(__name__)
    # 设置配置文件
    app.config.from_object(config)
    # 注册数据库
    # 注册session
    Session(app=app)
    register_db(app)
    # 注册蓝图
    register_bp(app)
    # flask_login的注册
    log_manager(app)
    return app

redis的配置
  • 将session存入redis
def get_redis():
    from redis import Redis
    	return Redis(host='主机号', port=端口号)   # 可以使用远程的服务器
# app.config文件,配置
SESSION_TYPE = 'redis'
SESSION_REDIS = get_redis()
from flask_session import Session
# 将应用注册到Session
Session(app=app)

商家用户管理模块的注册功能

数据模型建立
数据模型建立
  1. 数据模型基类
    1.1 实现通用id/status属性
    1.2 form.data自动赋值方法
 # 设置方法,将表单提交的数据自动 对象.属性=值,进行存储,作用于字段较多的情况下,以免写重复代码,自动赋值
 def set_attrs(self, formdata):
     for k, v in formdata.items():
         if hasattr(self, k) and k != 'id':
            setattr(self, k, v)
商家用户信息模型的设计
  1. 密码 哈希校验加盐
    2.1 _password的使用

数据模型基类的__abstract__意义
数据模型属性一键赋值,根据字典key结构
哈希加盐的方法
@property语法

# 密码加盐
class User(BaseModel):
    username = db.Column(db.String(30))
    _password = db.Column("password", db.String(128))
    
    @property
    def password(self):
        return self._password

    @password.setter
    def password(self, pwd):
        self._password = generate_password_hash(pwd)

    def check_password(self, pwd):
        return check_password_hash(self._password, pwd)
数据库表的产生
  1. 配置数据库ORM引擎

  2. db.create_all() 或 migrate

    from flask_migrate import Migrate, MigrateCommand
    # 配置迁移,app为你的应用,db为数据库实例  db = SQLAlchemy()
    migrate = Migrate(app=app, db=db)
    manager.add_command("db", MigrateCommand)
    
商家注册Form类设计
  1. 继承WTForms
    1.1 用户名不能重复

    1.2 使用继承方式来实现登录form和注册form类设计

实现自定义校验器

# 自定义校验器
def validate_username(self, obj):
    username = User.query.filter_by(username=obj.data).first()
    if username:
        # return "用户名已经存在"
        raise validators.ValidationError(message="用户名已经存在")
商家注册视图功能实现
  1. 使用BS4模板设计template
  2. 验证后数据写入数据库

写入数据库成功后,为什么一定要重定向

如何设置静态文件的存储目录

# 模型类的设置
class Shop(BaseModel):
    shop_name = db.Column(db.String(100), comment='店铺名称')
    shop_img = db.Column(db.String(200), comment='店铺图片', default="")
    .......
    
    # return字段名
    def keys(self):
        return "shop_name","shop_img"
    
    # 将字段名自动匹配返回一个字典格式的键值对
    def __getitem__(self, item):
        if hasattr(self, item):
            return getattr(self, item)

商家用户管理模块的登录功能

商家登陆Form类设计
商家登陆视图函数实现
  1. 验证用户名和密码正确性
class LoginForm(Form):
    username = StringField(
        label='用户名',
        validators=[
            validators.DataRequired("请输入用户名"),
            validators.Length(min=2, message="用户名不小于2位")
        ]
    )
    password = PasswordField(
        label="密码",
        validators=[
            validators.DataRequired('请输入密码'),
            validators.Length(min=6, message="密码位数不小于6位")
        ],
        # 渲染前端样式
        # render_kw=({"class": "input_contorl"})
    )
flask-login插件的初始化
  1. 实例化对象及注册方式
  2. 修改原始商家用户表信息已适应插件的接口
  1. flask-login的特性:
    1.1 提供全局current_user对象,方便视图函数调用;
    1.2 提供is_authenticated属性,判断是否为认证用户;
    1.3 提供login_require装饰器,限制非登陆用户访问视图函数,自动跳转登陆页面;
  2. login_manager对象的注册及属性
    2.1 利用login_user使用session进行
    2.2 利用login_out注销session
# flask_login的使用  *****数据model表需要继承UserMixin
from flask_login import LoginManager

from apps.forms.user_forms import User
from apps.model import db

login_manager = LoginManager()
login_manager.login_view = 'cms.login'
login_manager.login_message = 'please login!'
login_manager.session_protection = 'strong'
app.config['SECRET_KEY'] = '1234567'
login_manager.init_app(app)

@login_manager.user_loader
def user_loader(id):
    return db.session.query(User).filter_by(id=id).first()

# 在login中注册  login_user(user)
@cms_bp.route('/login/', methods=['POST', 'GET'])
def login():
    form = LoginForm(request.form)
    if request.method == "POST" and form.validate():
        username = request.form.get('username')
        password = request.form.get('password')
        user = db.session.query(User).filter(User.username == username).first()
        login_user(user)
        next = request.args.get('next')
        if user:
            if user.check_password(password):
                return redirect(next or url_for('cms.myindex'))
            else:
                pass_err = "密码不正确"
                return render_template('mylogin.html', pass_err=pass_err, form=form)
        else:
            name_err = "用户名不正确"
            return render_template('mylogin.html', name_err=name_err, form=form)
    return render_template('mylogin.html', form=form)

# 退出登录
@cms_bp.route('/logout/')
@login_required
def logout():
    logout_user()
    return redirect(url_for('cms.login'))
模板类的修改
  1. 导航栏根据用户登陆情况显示不同内容
  2. 首页根据登陆显示不同提示

DTL中自动加载current_user对象

# 用户如果登录则显示用户名,否则给出登录和注册的链接
{% if current_user.is_authenticated %}
   <li class="layui-nav-item">
       <a href="javascript:;">
           {{ current_user.username }}
       </a>
   <dl class="layui-nav-child">
       <dd><a href="" target="page_content">基本资料</a></dd>
       <dd><a href="">安全设置</a></dd>
   </dl>
   </li>
   <li class="layui-nav-item"><a href="{{ url_for("cms.logout") }}">退了</a></li>
 {% else %}
   <li class="layui-nav-item"><a href="{{ url_for("cms.login") }}">登录</a></li>
   <li class="layui-nav-item"><a href="{{ url_for("cms.register") }}">注册</a></li>
 {% endif %}

商家开店功能

店铺数据模型设计
  1. pub_id的设计
  2. 店铺和商家的关系设计
# 简单的采用uuid生成唯一的编码
import uuid
pub_id = str(uuid.uuid4)[:16]
店铺form类设计
  1. Field增加前端样式
  2. 价钱格式变化
 # 渲染前端样式 Filed中添加render_kw
 render_kw=({"class": "input_contorl"})
店铺添加视图函数设计
  1. 添加所属用户

  2. 产生对外16位UUID

    根据每个商家id添加其所属的店铺

    生成pub_id作为用户可以查看的id号,使用uuid生成唯一编号

商家店铺更新功能
  1. 店铺更新视图函数设计
    1.1 使用同样的templates
    1.2 使用同样的form

如果是渲染通用的模板,可以通过模板语言if进行判断,达到使用同一个模板

# 在店铺更新时,防止修改pub_id,验证pub_id是否合法
def check_pub(pub_id):
    shop = db.session.query(Shop).filter_by(pub_id=pub_id).first()
    myshop = ShopForm(data=dict(shop))   # 在shop模型中设置keys和__getitem__方法
    if not myshop:
        return abort(404)
    else:
        return myshop
商家店铺表格显示
  1. 首页显示
    1.1 table模板的使用
菜品分类添加/更新功能
  1. 分类添加视图函数设计
    1.1 添加分类的路由设计

    传递店铺的id,根据店铺id给每个店铺添加分类,在根据分类的id,给分类添加商品

菜品详情添加/更新功能
  1. 菜品详情form设计
    1.1 下拉框的设计
# 将验证设置为SelectField,在添加choices属性
class ShopinfoForm(Form):
    category_id = SelectField(label='所属分类id')

    def __init__(self, date, *args, **kwargs):
        super(ShopinfoForm, self).__init__(*args, **kwargs)
        # 使用列表生成式,得到choices的值,data为分类的对象
        self.category_id.choices = [(v.id, v.name) for v in date]
# 视图中的使用
cate=db.session.query(FoodCategory.name,FoodCategory.id).filter_by(shop_id=food.shop_id).all()
forms = ShopinfoForm(cate)
# 将forms渲染到模板中会得到一个下拉框的html
菜品分类显示功能
  1. 表格显示
  2. 平均价钱、统计个数
# 计算平均值和分类的商品数量,将数据存入字典,方便在html中进行迭代渲染
shop = db.session.query(Shop).all()
dic = {} 
    for v in shop:
        mycate = db.session.query(FoodCategory).filter_by(shop_id=v.id).all()
        for i in mycate:
            go = db.session.query(FoodInfo).filter_by(category_id=i.id).all()
            price = 0
            for v in i.foods:
                price += v.goods_price
            if len(go):
                dic[uuid.uuid4()] = [i.food.shop_name, i.name, len(go), price / len(go)]
菜品详情显示功能
  1. 层级显示

    使用外键关联查询

七牛云图片的上传

可查看七牛云上传图片的SDK

from flask import Flask, render_template, request, jsonify
from qiniu import Auth, put_data

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


# 原始上传文件的示例
@app.route('/upload_raw/', methods=['GET', 'POST'])
def upload_raw():
    file_obj = request.files.get('f1')
    file_obj.save('a.png')
    return 'success'


# 七牛云上传
@app.route('/upload_qiniu/', methods=['GET', 'POST'])
def upload_qiniu():
    file = request.files.get('f2')
    # 七牛云的密钥管理可以查看
    access_key = '******'
    secret_key = '******'

    q = Auth(access_key=access_key, secret_key=secret_key)
    # elm-flask创建的存储空间名
    token = q.upload_token('elm-flask')

    ret, info = put_data(up_token=token, key=None, data=file.read())
    print(ret.get('key'))
    return "success"


# 只提供token接口
@app.route('/uptoken/')
def uptoken():
     # 七牛云的密钥管理可以查看
    access_key = '*****'
    secret_key = '*****'

    q = Auth(access_key=access_key, secret_key=secret_key)
     # elm-flask创建的存储空间名
    token = q.upload_token('elm-flask')
    return jsonify({"uptoken": token})


if __name__ == '__main__':
    app.run(debug=True)

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>上传文件示例</title>
</head>
<body>
{#  普通上传   #}
<form action="/upload_raw/" method="post" enctype="multipart/form-data">
    <label for="fid"><input type="file" id="fid" name="f1"></label>
    <input type="submit" value="上传">
</form>
<br>
{#  七牛云上传   #}
<form action="/upload_qiniu/" method="post" enctype="multipart/form-data">
    <label for="fid2"><input type="file" id="fid2" name="f2"></label>
    <input type="submit" value="上传">
</form>

<br>
<br>
{#  七牛云使用前端上传   #}
<script src="https://cdn.staticfile.org/Plupload/2.1.1/moxie.js"></script>
<script src="https://cdn.staticfile.org/Plupload/2.1.1/plupload.dev.js"></script>
<script src="https://cdn.staticfile.org/qiniu-js-sdk/1.0.14-beta/qiniu.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename="itqiniu.js") }}"></script>
<button id="upload-btn">上传文件</button>
<input type="text" id="image-input">
<img src="" alt="" id="img" width="50px">
<script>
    window.onload = function () {
        itqiniu.setUp({
            'domain': 'http://pekvn6or3.bkt.clouddn.com/',
            'browse_btn': 'upload-btn',
            'uptoken_url': '/uptoken/',
            'success': function (up, file, info) {
                var image_url = file.name;
                var image_input = document.getElementById('image-input');
                image_input.value = image_url;

                var img = document.getElementById('img');
                img.setAttribute('src', image_url);
            }
        });
    }
</script>
</body>
</html>
// js上传七牛云
window.onload = function () {
    itqiniu.setUp({
        // domain 为七牛云创建空间所生成的域名
        'domain': 'http://pk7rw1paf.bkt.clouddn.com/',
        // browse_btn 为html的button的id值
        'browse_btn': 'upload-btn',
        'uptoken_url': '/uptoken/',
        'success': function (up, file, info) {
            let image_url = file.name;
            let image_input = document.getElementById('image-input');
            image_input.value = image_url;
			// 将上传的图片回显到页面上
            let img = document.getElementById('image-show');
            img.setAttribute('src', image_url);
        }
    });
};
var itqiniu = {
	'setUp': function(args) {
		var domain = args['domain'];
		var params = {
            browse_button:args['browse_btn'],
			runtimes: 'html5,flash,html4', //上传模式,依次退化
			max_file_size: '500mb', //文件最大允许的尺寸
			dragdrop: false, //是否开启拖拽上传
			chunk_size: '4mb', //分块上传时,每片的大小
			uptoken_url: args['uptoken_url'], //ajax请求token的url
			domain: domain, //图片下载时候的域名
			get_new_uptoken: false, //是否每次上传文件都要从业务服务器获取token
			auto_start: true, //如果设置了true,只要选择了图片,就会自动上传
            unique_names: false,
            save_key: true,
            multi_selection: false,
            filters: {
                mime_types :[
                    {title:'Image files',extensions: 'jpg,gif,png,jpeg'},
                    {title:'Video files',extensions: 'flv,mpg,mpeg,avi,wmv,mov,asf,rm,rmvb,mkv,m4v,mp4'}
                ]
            },
			log_level: 5, //log级别
			init: {
				'FileUploaded': function(up,file,info) {
					if(args['success']){
						var success = args['success'];
						var obj = JSON.parse(info);
						var domain = up.getOption('domain');
						file.name = domain + obj.key;
						success(up,file,info);
					}
				},
				'Error': function(up,err,errTip) {
					if(args['error']){
						var error = args['error'];
						error(up,err,errTip);
					}
				},
                'UploadProgress': function (up,file) {
                    if(args['progress']){
                        args['progress'](up,file);
                    }
                },
                'FilesAdded': function (up,files) {
                    if(args['fileadded']){
                        args['fileadded'](up,files);
                    }
                },
                'UploadComplete': function () {
                    if(args['complete']){
                        args['complete']();
                    }
                }
			}
		};

		// 把args中的参数放到params中去
		for(var key in args){
			params[key] = args[key];
		}
		var uploader = Qiniu.uploader(params);
		return uploader;
	}
};

部分界面显示效果

前段代码由layui 进行修改

店铺首页

商品列表
分类添加

qq第三方登录

采用js sdk,详情可参考腾讯开发平台
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <script type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js" data-callback="true"
    data-appid="APPID" data-redirecturi="回调地址" charset="utf-8"></script>
</head>

<body>
    <a href="https://graph.qq.com/oauth2.0/authorize?client_id=101536330&response_type=token&scope=all&redirect_uri=回调地址" title="QQ登陆" class="login100-social-item bg2">
     <i class="fa fa-qq"></i>
     </a>

<script src="{{ url_for('static',filename='vendor/jquery/jquery-3.2.1.min.js') }}"></script>

<script type="text/javascript">
    //加上setTimeout是为了防止qq回调时,还未初始化完成就调用qq的API
    setTimeout(function () {
        //从页面收集OpenAPI必要的参数。get_user_info不需要输入参数,因此paras中没有参数
        var paras = {};
        //用js SDK调用OpenAPI
        QC.api("get_user_info", paras)
        //指定接口访问成功的接收函数,s为成功返回Response对象
            .success(function (s) {
                //成功回调,通过s.data获取OpenAPI的返回数据
                QC.Login.getMe(function (openId, accessToken) { 
                    qq_Login(s, openId, accessToken); //对应JS的qq_Login()的方法
                });
            })
            //指定接口访问失败的接收函数,f为失败返回Response对象
            .error(function (f) {
                //失败回调
                alert("使用QQ登录失败");
            });
    }, 200);

    // 使用ajax向后台传递需要的参数
    function qq_Login(s, openId, accessToken) {
        //后台需要的参数
        var params = {
            'openId': openId,
            'nickName': s.data.nickname,
            'avatar': s.data.figureurl_qq_1,
            'sex': s.data.gender = "男" ? 1 : 0
        };
        $.post("{{ url_for('cms.login') }}", params, function (result) {
            if (result.dealFlag = '1') {
                alert("欢迎登陆" + s.data.nickname);
                // 登录成功后跳转到验证通过后的界面
                window.location.href = "{{ url_for('cms.myindex') }}";
            } else {
                alert("登陆失败");
                window.location.href = "{{ url_for('cms.login') }}"
            }
        })
    }
</script>
</body>
</html>
后台路由应该与回调地址的路由一致
# 后台视图获取ajax传递的参数值,进行数据库的操作
data = request.form.get('nickName')
openId = request.form.get('openId')
avatar = request.form.get('avatar')
# return 渲染回调函数所在的界面html
return render_template('xxxx.html')

# 返回登录成功的标识
return json.dumps({"dealFlag ": "1"})
# 前端ajax根据接收的json数据进行跳转
 if (result.dealFlag = '1') {
    alert("欢迎登陆" + s.data.nickname);
	// 登录成功后跳转到验证通过后的界面
    window.location.href = "{{ url_for('cms.myindex') }}";
  } else {
    alert("登陆失败");
    window.location.href = "{{ url_for('cms.login') }}"
            }
前端界面的渲染
1. 可以根据获取的参数值直接传递到对应的前端页面进行渲染
2. 使用flask-login将用户信息注册到session中,在页面中直接根据{{ current_user.数据库字段名 }
后台处理
# 路由设置为回调地址路由
@cms_bp.route('/afterlogin.do', methods=['POST', 'GET'])
def login():
    form = LoginForm(request.form)
    if request.method == "POST":
        # 接收前端传递回来的数据
        data = request.form.get('nickName')
        openId = request.form.get('openId')
        avatar = request.form.get('avatar')
        if data:
            user = db.session.query(User).filter(User.username == data).first()
            if not user:
                user = User()
                user.username = data
                user.password = openId
                user.status = avatar
                db.session.add(user)
                db.session.commit()
            user = db.session.query(User).filter(User.username == data).first()
            # 将用户注册到flask_login中,可以直接使用current_user将数据渲染到前端页面
            login_user(user)
            if user:
                # 返回json数据,前端接收后进行跳转判断
                return json.dumps({"dealFlag ": "1"})
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值