refactor(重构程序&目录结构)

This commit is contained in:
zhengxinonly
2022-01-23 00:43:39 +08:00
parent 3453b01938
commit 6978002393
60 changed files with 374 additions and 462 deletions
+6 -12
View File
@@ -1,26 +1,20 @@
import os
from flask import Flask
from applications.common.flask_uploads import configure_uploads
from common.flask_uploads import configure_uploads
from applications.common.utils.upload import photos
from applications.configs import common
from applications.extensions import init_plugs
from common.utils.upload import photos
from extensions import init_plugs
from applications.view import init_view
from applications.api import init_api
from applications.configs import config
import config
def create_app(config_name=None):
def create_app():
app = Flask('pear-admin-flask')
if not config_name:
# 尝试从本地环境中读取
config_name = os.getenv('FLASK_CONFIG', 'development')
# 引入数据库配置
app.config.from_object(common)
app.config.from_object(config[config_name])
app.config.from_object(config)
# 注册各种插件
init_plugs(app)
+10 -11
View File
@@ -1,20 +1,19 @@
from flask import jsonify
from flask_restful import Resource, reqparse
from applications.common.utils.http import success_api, fail_api
from applications.extensions import db
from applications.models import CompanyDepartment, CompanyUser
from common.utils.http import success_api, fail_api
from extensions import db
from models import DepartmentModels, UserModels
class DepartmentsResource(Resource):
def get(self):
dept_data = CompanyDepartment.query.order_by(CompanyDepartment.sort).all()
dept_data = DepartmentModels.query.order_by(DepartmentModels.sort).all()
# TODO dtree 需要返回状态信息
res = {
"status": {"code": 200, "message": "默认"},
"data": [
{
'deptId': item.id,
'parentId': item.parent_id,
@@ -45,7 +44,7 @@ class DepartmentsResource(Resource):
res = parser.parse_args()
dept = CompanyDepartment(
dept = DepartmentModels(
parent_id=res.parent_id,
dept_name=res.dept_name,
sort=res.sort,
@@ -63,7 +62,7 @@ class DepartmentsResource(Resource):
class DepartmentResource(Resource):
def get(self, dept_id):
dept = CompanyDepartment.query.filter_by(id=dept_id).first()
dept = DepartmentModels.query.filter_by(id=dept_id).first()
dept_data = {
'id': dept.id,
'dept_name': dept.dept_name,
@@ -96,15 +95,15 @@ class DepartmentResource(Resource):
"status": res.status,
"address": res.address
}
res = CompanyDepartment.query.filter_by(id=dept_id).update(data)
res = DepartmentModels.query.filter_by(id=dept_id).update(data)
if not res:
return fail_api(message="更新失败")
db.session.commit()
return success_api(message="更新成功")
def delete(self, dept_id):
ret = CompanyDepartment.query.filter_by(id=dept_id).delete()
CompanyUser.query.filter_by(dept_id=dept_id).update({"dept_id": None})
ret = DepartmentModels.query.filter_by(id=dept_id).delete()
UserModels.query.filter_by(dept_id=dept_id).update({"dept_id": None})
db.session.commit()
if ret:
return success_api(message="删除成功")
@@ -113,7 +112,7 @@ class DepartmentResource(Resource):
class DeptEnableResource(Resource):
def put(self, dept_id):
d = CompanyDepartment.query.get(dept_id)
d = DepartmentModels.query.get(dept_id)
if d:
d.status = not d.status
db.session.commit()
+14 -19
View File
@@ -5,9 +5,9 @@ from flask import request, jsonify, current_app
from flask_login import current_user
from flask_restful import Resource, reqparse
from applications.common.utils.http import success_api, fail_api
from applications.extensions import db
from applications.models import RightsPower, RightsRole
from common.utils.http import success_api, fail_api
from extensions import db
from models import RightModels, RoleModels
def get_render_config():
@@ -105,7 +105,6 @@ def make_menu_tree():
if p.type == 0 or p.type == 1:
powers.append(p)
# power_dict = marshal(powers, RightsPower.fields2()) # 生成可序列化对象
power_dict = [
{
'id': item.id,
@@ -118,8 +117,6 @@ def make_menu_tree():
'icon': item.icon,
'sort': item.sort,
'enable': item.enable,
'update_at': item.update_at.strftime('%Y-%m-%d %H:%M:%S'),
'create_at': item.create_at.strftime('%Y-%m-%d %H:%M:%S'),
} for item in powers
]
power_dict.sort(key=lambda x: x['id'], reverse=True)
@@ -143,15 +140,15 @@ def make_menu_tree():
# 删除权限(目前没有判断父节点自动删除子节点)
def remove_power(power_id):
power = RightsPower.query.filter_by(id=power_id).first()
power = RightModels.query.filter_by(id=power_id).first()
role_id_list = []
roles = power.role
for role in roles:
role_id_list.append(role.id)
roles = RightsRole.query.filter(RightsRole.id.in_(role_id_list)).all()
roles = RoleModels.query.filter(RoleModels.id.in_(role_id_list)).all()
for p in roles:
power.role.remove(p)
r = RightsPower.query.filter_by(id=power_id).delete()
r = RightModels.query.filter_by(id=power_id).delete()
db.session.commit()
return r
@@ -177,8 +174,8 @@ class RightRightsResource(Resource):
def get(self):
"""获取选择父节点"""
power = RightsPower.query.all()
# power_data = marshal(power, RightsPower.fields())
power = RightModels.query.all()
# power_data = marshal(power, RightModels.fields())
power_data = [
{
'powerId': item.id,
@@ -189,8 +186,6 @@ class RightRightsResource(Resource):
'parentId': item.parent_id,
'icon': item.icon,
'sort': item.sort,
'create_at': item.create_at.strftime('%Y-%m-%d %H:%M:%S'),
'update_at': item.update_at.strftime('%Y-%m-%d %H:%M:%S'),
'enable': item.enable,
} for item in power
]
@@ -211,7 +206,7 @@ class RightPowerResource(Resource):
def post(self, power_id):
res = parser_power.parse_args()
power = RightsPower(
power = RightModels(
icon=res.icon,
open_type=res.open_type,
parent_id=res.parent_id,
@@ -233,15 +228,15 @@ class RightPowerResource(Resource):
def delete(self, power_id):
# 删除权限(目前没有判断父节点自动删除子节点)
power = RightsPower.query.filter_by(id=power_id).first()
power = RightModels.query.filter_by(id=power_id).first()
role_id_list = []
roles = power.role
for role in roles:
role_id_list.append(role.id)
roles = RightsRole.query.filter(RightsRole.id.in_(role_id_list)).all()
roles = RoleModels.query.filter(RoleModels.id.in_(role_id_list)).all()
for p in roles:
power.role.remove(p)
r = RightsPower.query.filter_by(id=power_id).delete()
r = RightModels.query.filter_by(id=power_id).delete()
db.session.commit()
if r:
@@ -262,7 +257,7 @@ class RightPowerResource(Resource):
"url": res.power_url,
"sort": res.sort
}
power = RightsPower.query.filter_by(id=power_id).update(data)
power = RightModels.query.filter_by(id=power_id).update(data)
db.session.commit()
if not power:
@@ -273,7 +268,7 @@ class RightPowerResource(Resource):
class RightPowerEnableResource(Resource):
def put(self, right_id):
power = RightsPower.query.get(right_id)
power = RightModels.query.get(right_id)
if power:
power.enable = not power.enable
db.session.commit()
+20 -20
View File
@@ -1,28 +1,28 @@
from flask_restful import Resource, reqparse, marshal
from applications.common.utils.http import table_api, success_api, fail_api
from applications.extensions import db
from applications.models import RightsPower, RightsRole, CompanyUser
from common.utils.http import table_api, success_api, fail_api
from extensions import db
from models import RightModels, RoleModels, UserModels
def remove_role(role_id):
""" 删除角色 """
role = RightsRole.query.filter_by(id=role_id).first()
role = RoleModels.query.filter_by(id=role_id).first()
# 删除该角色的权限
power_id_list = []
for p in role.power:
power_id_list.append(p.id)
powers = RightsPower.query.filter(RightsPower.id.in_(power_id_list)).all()
powers = RightModels.query.filter(RightModels.id.in_(power_id_list)).all()
for p in powers:
role.power.remove(p)
user_id_list = []
for u in role.user:
user_id_list.append(u.id)
users = CompanyUser.query.filter(CompanyUser.id.in_(user_id_list)).all()
users = UserModels.query.filter(UserModels.id.in_(user_id_list)).all()
for u in users:
role.user.remove(u)
r = RightsRole.query.filter_by(id=role_id).delete()
r = RoleModels.query.filter_by(id=role_id).delete()
db.session.commit()
return r
@@ -46,11 +46,11 @@ class RoleRolesResource(Resource):
filters = []
if res.role_name:
filters.append(RightsRole.name.like('%' + res.role_name + '%'))
filters.append(RoleModels.name.like('%' + res.role_name + '%'))
if res.role_code:
filters.append(RightsRole.code.like('%' + res.role_code + '%'))
filters.append(RoleModels.code.like('%' + res.role_code + '%'))
paginate = RightsRole.query.filter(*filters).paginate(page=res.page, per_page=res.limit, error_out=False)
paginate = RoleModels.query.filter(*filters).paginate(page=res.page, per_page=res.limit, error_out=False)
return table_api(result={'items': [{'id': item.id,
'roleName': item.name,
@@ -59,7 +59,7 @@ class RoleRolesResource(Resource):
'comment': item.comment,
'details': item.details,
'sort': item.sort,
'create_at': str(item.create_at), } for item in paginate.items],
} for item in paginate.items],
'total': paginate.total}
, code=0)
@@ -85,7 +85,7 @@ class RoleRoleResource(Resource):
res = parser.parse_args()
role = RightsRole(
role = RoleModels(
details=res.details,
enable=res.enable,
code=res.role_code,
@@ -116,7 +116,7 @@ class RoleRoleResource(Resource):
"details": res.details
}
role = RightsRole.query.filter_by(id=role_id).update(data)
role = RoleModels.query.filter_by(id=role_id).update(data)
db.session.commit()
if not role:
return fail_api(message="更新角色失败")
@@ -127,7 +127,7 @@ class RoleEnableResource(Resource):
"""启用用户"""
def put(self, role_id):
ret = RightsRole.query.get(role_id)
ret = RoleModels.query.get(role_id)
ret.enable = not ret.enable
db.session.commit()
@@ -141,11 +141,11 @@ class RolePowerResource(Resource):
def get(self, role_id):
# 获取角色权限
role = RightsRole.query.filter_by(id=role_id).first()
role = RoleModels.query.filter_by(id=role_id).first()
# 获取权限列表的 id
check_powers_list = [rp.id for rp in role.power]
powers = RightsPower.query.all() # 获取所有的权限
powers = marshal(powers, RightsPower.fields())
powers = RightModels.query.all() # 获取所有的权限
powers = marshal(powers, RightModels.fields())
for i in powers:
if int(i.get("powerId")) in check_powers_list:
i["checkArr"] = "1"
@@ -166,14 +166,14 @@ class RolePowerResource(Resource):
power_list = res.power_ids.split(',')
""" 更新角色权限 """
role = RightsRole.query.filter_by(id=role_id).first()
role = RoleModels.query.filter_by(id=role_id).first()
power_id_list = []
for p in role.power:
power_id_list.append(p.id)
powers = RightsPower.query.filter(RightsPower.id.in_(power_id_list)).all()
powers = RightModels.query.filter(RightModels.id.in_(power_id_list)).all()
for p in powers:
role.power.remove(p)
powers = RightsPower.query.filter(RightsPower.id.in_(power_list)).all()
powers = RightModels.query.filter(RightModels.id.in_(power_list)).all()
for p in powers:
role.power.append(p)
db.session.commit()
+8 -9
View File
@@ -4,10 +4,10 @@ from flask import request, jsonify, current_app
from flask_restful import Resource
from sqlalchemy import desc
from applications.common.utils.http import fail_api, success_api, table_api
from applications.common.utils.upload import upload_one, delete_photo_by_id
from applications.extensions import db
from applications.models import FilePhoto
from common.utils.http import fail_api, success_api, table_api
from common.utils.upload import upload_one, delete_photo_by_id
from extensions import db
from models import PhotoModels
class FilePhotosResource(Resource):
@@ -15,11 +15,10 @@ class FilePhotosResource(Resource):
def get(self):
page = request.args.get('page', type=int)
limit = request.args.get('limit', type=int)
photo_paginate = FilePhoto.query.order_by(desc(FilePhoto.create_at)
).paginate(page=page,
photo_paginate = PhotoModels.query.order_by(desc(PhotoModels.create_at)
).paginate(page=page,
per_page=limit,
error_out=False)
# data = marshal(photo_paginate.items, FilePhoto.fields())
data = [
{
'id': item.id,
@@ -55,11 +54,11 @@ class FilePhotosResource(Resource):
"""图片批量删除"""
# TODO bugs 图片删除失败
ids = request.form.getlist('ids[]')
photo_name = FilePhoto.query.filter(FilePhoto.id.in_(ids)).all()
photo_name = PhotoModels.query.filter(PhotoModels.id.in_(ids)).all()
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
for p in photo_name:
os.remove(upload_url + '/' + p.name)
photo = FilePhoto.query.filter(FilePhoto.id.in_(ids)).delete(synchronize_session=False)
photo = PhotoModels.query.filter(PhotoModels.id.in_(ids)).delete(synchronize_session=False)
db.session.commit()
if photo:
return success_api(message="删除成功")
+5 -5
View File
@@ -3,10 +3,10 @@ from flask import session, redirect, url_for
from flask_login import current_user, login_user
from flask_restful import Resource, reqparse
from applications.common.gen_captcha import add_auth_session
from applications.common.utils.http import fail_api, success_api
from applications.common.utils.rights import record_logging
from applications.models import CompanyUser
from common.gen_captcha import add_auth_session
from common.utils.http import fail_api, success_api
from common.utils.rights import record_logging
from models import UserModels
class LoginResource(Resource):
@@ -28,7 +28,7 @@ class LoginResource(Resource):
if req.captcha != s_code:
return fail_api(message="验证码错误")
user = CompanyUser.query.filter_by(username=req.username).first()
user = UserModels.query.filter_by(username=req.username).first()
if user is None:
return fail_api(message="不存在的用户")
+8 -9
View File
@@ -1,11 +1,10 @@
# 个人中心
from flask import request, jsonify
from flask_login import login_required
from flask_restful import Resource, reqparse
from applications.common.utils.http import fail_api, success_api
from applications.extensions import db
from applications.models import CompanyUser
from common.utils.http import fail_api, success_api
from extensions import db
from models import UserModels
class UserStatusResource(Resource):
@@ -19,11 +18,11 @@ class UserStatusResource(Resource):
res = parser.parse_args()
if res.operate == 1:
user = CompanyUser.query.get(user_id)
user = UserModels.query.get(user_id)
user.enable = res.operate
message = success_api(message="启动成功")
else:
user = CompanyUser.query.filter_by(id=res.user_id).update({"enable": res.operate})
user = UserModels.query.filter_by(id=res.user_id).update({"enable": res.operate})
message = success_api(message="禁用成功")
if user:
db.session.commit()
@@ -37,7 +36,7 @@ class UserAvatarResource(Resource):
def put(self, user_id):
url = request.json.get("avatar").get("src")
ret = CompanyUser.query.get(user_id)
ret = UserModels.query.get(user_id)
ret.avatar = url
db.session.commit()
if not ret:
@@ -56,7 +55,7 @@ class UserInfoResource(Resource):
res = parser.parse_args()
ret = CompanyUser.query.get(user_id)
ret = UserModels.query.get(user_id)
ret.username = res.username
ret.realname = res.real_name
ret.remark = res.details
@@ -81,7 +80,7 @@ class UserPasswordResource(Resource):
return fail_api(message='确认密码不一致')
""" 修改当前用户密码 """
user = CompanyUser.query.get(user_id)
user = UserModels.query.get(user_id)
is_right = user.validate_password(res.oldPassword)
if not is_right:
return jsonify(success=False, message="旧密码错误")
+27 -27
View File
@@ -3,35 +3,35 @@ from flask_login import current_user
from flask_restful import Resource, reqparse
from sqlalchemy import desc
from applications.common.utils.http import fail_api, success_api, table_api
from applications.extensions import db
from applications.models import CompanyUser, RightsRole, CompanyDepartment
from applications.models import LoggingModel
from common.utils.http import fail_api, success_api, table_api
from extensions import db
from models import UserModels, RoleModels, DepartmentModels
from models import LogModel
def get_current_user_logs():
""" 获取当前用户日志 """
log = LoggingModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LoggingModel.create_at)).limit(10)
log = LogModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LogModel.create_at)).limit(10)
return log
def is_user_exists(username):
""" 判断用户是否存在 """
res = CompanyUser.query.filter_by(username=username).count()
res = UserModels.query.filter_by(username=username).count()
return bool(res)
def delete_by_id(_id):
""" 删除用户 """
user = CompanyUser.query.filter_by(id=_id).first()
user = UserModels.query.filter_by(id=_id).first()
roles_id = []
for role in user.role:
roles_id.append(role.id)
roles = RightsRole.query.filter(RightsRole.id.in_(roles_id)).all()
roles = RoleModels.query.filter(RoleModels.id.in_(roles_id)).all()
for r in roles:
user.role.remove(r)
res = CompanyUser.query.filter_by(id=_id).delete()
res = UserModels.query.filter_by(id=_id).delete()
db.session.commit()
return res
@@ -43,14 +43,14 @@ def batch_remove(ids):
def update_user_role(_id, roles_list):
user = CompanyUser.query.filter_by(id=_id).first()
user = UserModels.query.filter_by(id=_id).first()
roles_id = []
for role in user.role:
roles_id.append(role.id)
roles = RightsRole.query.filter(RightsRole.id.in_(roles_id)).all()
roles = RoleModels.query.filter(RoleModels.id.in_(roles_id)).all()
for r in roles:
user.role.remove(r)
roles = RightsRole.query.filter(RightsRole.id.in_(roles_list)).all()
roles = RoleModels.query.filter(RoleModels.id.in_(roles_list)).all()
for r in roles:
user.role.append(r)
db.session.commit()
@@ -72,17 +72,17 @@ class UserUsersResource(Resource):
filters = []
if res.real_name:
filters.append(CompanyUser.realname.like('%' + res.real_name + '%'))
filters.append(UserModels.realname.like('%' + res.real_name + '%'))
if res.username:
filters.append(CompanyUser.username.like('%' + res.username + '%'))
filters.append(UserModels.username.like('%' + res.username + '%'))
if res.dept_id:
filters.append(CompanyUser.dept_id == res.dept_id)
filters.append(UserModels.dept_id == res.dept_id)
paginate = CompanyUser.query.filter(*filters).paginate(page=res.page,
per_page=res.limit,
error_out=False)
paginate = UserModels.query.filter(*filters).paginate(page=res.page,
per_page=res.limit,
error_out=False)
dept_name = lambda dept_id: CompanyDepartment.query.filter_by(id=dept_id).first().dept_name if dept_id else ""
dept_name = lambda dept_id: DepartmentModels.query.filter_by(id=dept_id).first().dept_name if dept_id else ""
user_data = [{
'id': item.id,
'username': item.username,
@@ -111,7 +111,7 @@ class UserUsersResource(Resource):
if is_user_exists(res.username):
return fail_api(message="用户已经存在")
user = CompanyUser()
user = UserModels()
user.username = res.username
user.realname = res.real_name
user.set_password(res.password)
@@ -119,8 +119,8 @@ class UserUsersResource(Resource):
db.session.commit()
""" 增加用户角色 """
user = CompanyUser.query.filter_by(id=user.id).first()
roles = RightsRole.query.filter(RightsRole.id.in_(role_ids)).all()
user = UserModels.query.filter_by(id=user.id).first()
roles = RoleModels.query.filter(RoleModels.id.in_(role_ids)).all()
for r in roles:
user.role.append(r)
db.session.commit()
@@ -152,7 +152,7 @@ class UserUserResource(Resource):
if is_user_exists(res.username):
return fail_api(message="用户已经存在")
user = CompanyUser()
user = UserModels()
user.username = res.username
user.realname = res.real_name
user.set_password(res.password)
@@ -160,8 +160,8 @@ class UserUserResource(Resource):
db.session.commit()
""" 增加用户角色 """
user = CompanyUser.query.filter_by(id=user.id).first()
roles = RightsRole.query.filter(RightsRole.id.in_(role_ids)).all()
user = UserModels.query.filter_by(id=user.id).first()
roles = RoleModels.query.filter(RoleModels.id.in_(role_ids)).all()
for r in roles:
user.role.append(r)
db.session.commit()
@@ -189,7 +189,7 @@ class UserRoleResource(Resource):
role_ids = res.role_ids.split(',')
# 更新用户数据
CompanyUser.query.filter_by(id=user_id).update({'username': res.username,
UserModels.query.filter_by(id=user_id).update({'username': res.username,
'realname': res.real_name,
'dept_id': res.dept_id})
db.session.commit()
View File
-504
View File
@@ -1,504 +0,0 @@
# -*- coding: utf-8 -*-
"""
为了解决 flask_uploads 与 Werkzeug 2.0.1 冲突而直接复制源码进行适配
原始地址: https://github.com/maxcountryman/flask-uploads/blob/master/flask_uploads.py
"""
import sys
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
else:
string_types = basestring,
import os.path
import posixpath
from flask import current_app, send_from_directory, abort, url_for
from itertools import chain
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
from flask import Blueprint
# Extension presets
#: This just contains plain text files (.txt).
TEXT = ('txt',)
#: This contains various office document formats (.rtf, .odf, .ods, .gnumeric,
#: .abw, .doc, .docx, .xls, and .xlsx). Note that the macro-enabled versions
#: of Microsoft Office 2007 files are not included.
DOCUMENTS = tuple('rtf odf ods gnumeric abw doc docx xls xlsx'.split())
#: This contains basic image types that are viewable from most browsers (.jpg,
#: .jpe, .jpeg, .png, .gif, .svg, and .bmp).
IMAGES = tuple('jpg jpe jpeg png gif svg bmp'.split())
#: This contains audio file types (.wav, .mp3, .aac, .ogg, .oga, and .flac).
AUDIO = tuple('wav mp3 aac ogg oga flac'.split())
#: This is for structured data files (.csv, .ini, .json, .plist, .xml, .yaml,
#: and .yml).
DATA = tuple('csv ini json plist xml yaml yml'.split())
#: This contains various types of scripts (.js, .php, .pl, .py .rb, and .sh).
#: If your Web server has PHP installed and set to auto-run, you might want to
#: add ``php`` to the DENY setting.
SCRIPTS = tuple('js php pl py rb sh'.split())
#: This contains archive and compression formats (.gz, .bz2, .zip, .tar,
#: .tgz, .txz, and .7z).
ARCHIVES = tuple('gz bz2 zip tar tgz txz 7z'.split())
#: This contains shared libraries and executable files (.so, .exe and .dll).
#: Most of the time, you will not want to allow this - it's better suited for
#: use with `AllExcept`.
EXECUTABLES = tuple('so exe dll'.split())
#: The default allowed extensions - `TEXT`, `DOCUMENTS`, `DATA`, and `IMAGES`.
DEFAULTS = TEXT + DOCUMENTS + IMAGES + DATA
class UploadNotAllowed(Exception):
"""
This exception is raised if the upload was not allowed. You should catch
it in your view code and display an appropriate message to the user.
"""
def tuple_from(*iters):
return tuple(itertools.chain(*iters))
def extension(filename):
ext = os.path.splitext(filename)[1]
if ext.startswith('.'):
# os.path.splitext retains . separator
ext = ext[1:]
return ext
def lowercase_ext(filename):
"""
This is a helper used by UploadSet.save to provide lowercase extensions for
all processed files, to compare with configured extensions in the same
case.
.. versionchanged:: 0.1.4
Filenames without extensions are no longer lowercased, only the
extension is returned in lowercase, if an extension exists.
:param filename: The filename to ensure has a lowercase extension.
"""
if '.' in filename:
main, ext = os.path.splitext(filename)
return main + ext.lower()
# For consistency with os.path.splitext,
# do not treat a filename without an extension as an extension.
# That is, do not return filename.lower().
return filename
def addslash(url):
if url.endswith('/'):
return url
return url + '/'
def patch_request_class(app, size=64 * 1024 * 1024):
"""
By default, Flask will accept uploads to an arbitrary size. While Werkzeug
switches uploads from memory to a temporary file when they hit 500 KiB,
it's still possible for someone to overload your disk space with a
gigantic file.
This patches the app's request class's
`~werkzeug.BaseRequest.max_content_length` attribute so that any upload
larger than the given size is rejected with an HTTP error.
.. note::
In Flask 0.6, you can do this by setting the `MAX_CONTENT_LENGTH`
setting, without patching the request class. To emulate this behavior,
you can pass `None` as the size (you must pass it explicitly). That is
the best way to call this function, as it won't break the Flask 0.6
functionality if it exists.
.. versionchanged:: 0.1.1
:param app: The app to patch the request class of.
:param size: The maximum size to accept, in bytes. The default is 64 MiB.
If it is `None`, the app's `MAX_CONTENT_LENGTH` configuration
setting will be used to patch.
"""
if size is None:
if isinstance(app.request_class.__dict__['max_content_length'],
property):
return
size = app.config.get('MAX_CONTENT_LENGTH')
reqclass = app.request_class
patched = type(reqclass.__name__, (reqclass,),
{'max_content_length': size})
app.request_class = patched
def config_for_set(uset, app, defaults=None):
"""
This is a helper function for `configure_uploads` that extracts the
configuration for a single set.
:param uset: The upload set.
:param app: The app to load the configuration from.
:param defaults: A dict with keys `url` and `dest` from the
`UPLOADS_DEFAULT_DEST` and `DEFAULT_UPLOADS_URL`
settings.
"""
config = app.config
prefix = 'UPLOADED_%s_' % uset.name.upper()
using_defaults = False
if defaults is None:
defaults = dict(dest=None, url=None)
allow_extns = tuple(config.get(prefix + 'ALLOW', ()))
deny_extns = tuple(config.get(prefix + 'DENY', ()))
destination = config.get(prefix + 'DEST')
base_url = config.get(prefix + 'URL')
if destination is None:
# the upload set's destination wasn't given
if uset.default_dest:
# use the "default_dest" callable
destination = uset.default_dest(app)
if destination is None: # still
# use the default dest from the config
if defaults['dest'] is not None:
using_defaults = True
destination = os.path.join(defaults['dest'], uset.name)
else:
raise RuntimeError("no destination for set %s" % uset.name)
if base_url is None and using_defaults and defaults['url']:
base_url = addslash(defaults['url']) + uset.name + '/'
return UploadConfiguration(destination, base_url, allow_extns, deny_extns)
def configure_uploads(app, upload_sets):
"""
Call this after the app has been configured. It will go through all the
upload sets, get their configuration, and store the configuration on the
app. It will also register the uploads module if it hasn't been set. This
can be called multiple times with different upload sets.
.. versionchanged:: 0.1.3
The uploads module/blueprint will only be registered if it is needed
to serve the upload sets.
:param app: The `~flask.Flask` instance to get the configuration from.
:param upload_sets: The `UploadSet` instances to configure.
"""
if isinstance(upload_sets, UploadSet):
upload_sets = (upload_sets,)
if not hasattr(app, 'upload_set_config'):
app.upload_set_config = {}
set_config = app.upload_set_config
defaults = dict(dest=app.config.get('UPLOADS_DEFAULT_DEST'),
url=app.config.get('UPLOADS_DEFAULT_URL'))
for uset in upload_sets:
config = config_for_set(uset, app, defaults)
set_config[uset.name] = config
should_serve = any(s.base_url is None for s in set_config.values())
if '_uploads' not in app.blueprints and should_serve:
app.register_blueprint(uploads_mod)
class All(object):
"""
This type can be used to allow all extensions. There is a predefined
instance named `ALL`.
"""
def __contains__(self, item):
return True
#: This "contains" all items. You can use it to allow all extensions to be
#: uploaded.
ALL = All()
class AllExcept(object):
"""
This can be used to allow all file types except certain ones. For example,
to ban .exe and .iso files, pass::
AllExcept(('exe', 'iso'))
to the `UploadSet` constructor as `extensions`. You can use any container,
for example::
AllExcept(SCRIPTS + EXECUTABLES)
"""
def __init__(self, items):
self.items = items
def __contains__(self, item):
return item not in self.items
class UploadConfiguration(object):
"""
This holds the configuration for a single `UploadSet`. The constructor's
arguments are also the attributes.
:param destination: The directory to save files to.
:param base_url: The URL (ending with a /) that files can be downloaded
from. If this is `None`, Flask-Uploads will serve the
files itself.
:param allow: A list of extensions to allow, even if they're not in the
`UploadSet` extensions list.
:param deny: A list of extensions to deny, even if they are in the
`UploadSet` extensions list.
"""
def __init__(self, destination, base_url=None, allow=(), deny=()):
self.destination = destination
self.base_url = base_url
self.allow = allow
self.deny = deny
@property
def tuple(self):
return (self.destination, self.base_url, self.allow, self.deny)
def __eq__(self, other):
return self.tuple == other.tuple
class UploadSet(object):
"""
This represents a single set of uploaded files. Each upload set is
independent of the others. This can be reused across multiple application
instances, as all configuration is stored on the application object itself
and found with `flask.current_app`.
:param name: The name of this upload set. It defaults to ``files``, but
you can pick any alphanumeric name you want. (For simplicity,
it's best to use a plural noun.)
:param extensions: The extensions to allow uploading in this set. The
easiest way to do this is to add together the extension
presets (for example, ``TEXT + DOCUMENTS + IMAGES``).
It can be overridden by the configuration with the
`UPLOADED_X_ALLOW` and `UPLOADED_X_DENY` configuration
parameters. The default is `DEFAULTS`.
:param default_dest: If given, this should be a callable. If you call it
with the app, it should return the default upload
destination path for that app.
"""
def __init__(self, name='files', extensions=DEFAULTS, default_dest=None):
if not name.isalnum():
raise ValueError("Name must be alphanumeric (no underscores)")
self.name = name
self.extensions = extensions
self._config = None
self.default_dest = default_dest
@property
def config(self):
"""
This gets the current configuration. By default, it looks up the
current application and gets the configuration from there. But if you
don't want to go to the full effort of setting an application, or it's
otherwise outside of a request context, set the `_config` attribute to
an `UploadConfiguration` instance, then set it back to `None` when
you're done.
"""
if self._config is not None:
return self._config
try:
return current_app.upload_set_config[self.name]
except AttributeError:
raise RuntimeError("cannot access configuration outside request")
def url(self, filename):
"""
This function gets the URL a file uploaded to this set would be
accessed at. It doesn't check whether said file exists.
:param filename: The filename to return the URL for.
"""
base = self.config.base_url
if base is None:
return url_for('_uploads.uploaded_file', setname=self.name,
filename=filename, _external=True)
else:
return base + filename
def path(self, filename, folder=None):
"""
This returns the absolute path of a file uploaded to this set. It
doesn't actually check whether said file exists.
:param filename: The filename to return the path for.
:param folder: The subfolder within the upload set previously used
to save to.
"""
if folder is not None:
target_folder = os.path.join(self.config.destination, folder)
else:
target_folder = self.config.destination
return os.path.join(target_folder, filename)
def file_allowed(self, storage, basename):
"""
This tells whether a file is allowed. It should return `True` if the
given `werkzeug.FileStorage` object can be saved with the given
basename, and `False` if it can't. The default implementation just
checks the extension, so you can override this if you want.
:param storage: The `werkzeug.FileStorage` to check.
:param basename: The basename it will be saved under.
"""
return self.extension_allowed(extension(basename))
def extension_allowed(self, ext):
"""
This determines whether a specific extension is allowed. It is called
by `file_allowed`, so if you override that but still want to check
extensions, call back into this.
:param ext: The extension to check, without the dot.
"""
return ((ext in self.config.allow) or
(ext in self.extensions and ext not in self.config.deny))
def get_basename(self, filename):
return lowercase_ext(secure_filename(filename))
def save(self, storage, folder=None, name=None):
"""
This saves a `werkzeug.FileStorage` into this upload set. If the
upload is not allowed, an `UploadNotAllowed` error will be raised.
Otherwise, the file will be saved and its name (including the folder)
will be returned.
:param storage: The uploaded file to save.
:param folder: The subfolder within the upload set to save to.
:param name: The name to save the file as. If it ends with a dot, the
file's extension will be appended to the end. (If you
are using `name`, you can include the folder in the
`name` instead of explicitly using `folder`, i.e.
``uset.save(file, name="someguy/photo_123.")``
"""
if not isinstance(storage, FileStorage):
raise TypeError("storage must be a werkzeug.FileStorage")
if folder is None and name is not None and "/" in name:
folder, name = os.path.split(name)
basename = self.get_basename(storage.filename)
if name:
if name.endswith('.'):
basename = name + extension(basename)
else:
basename = name
if not self.file_allowed(storage, basename):
raise UploadNotAllowed()
if folder:
target_folder = os.path.join(self.config.destination, folder)
else:
target_folder = self.config.destination
if not os.path.exists(target_folder):
os.makedirs(target_folder)
if os.path.exists(os.path.join(target_folder, basename)):
basename = self.resolve_conflict(target_folder, basename)
target = os.path.join(target_folder, basename)
storage.save(target)
if folder:
return posixpath.join(folder, basename)
else:
return basename
def resolve_conflict(self, target_folder, basename):
"""
If a file with the selected name already exists in the target folder,
this method is called to resolve the conflict. It should return a new
basename for the file.
The default implementation splits the name and extension and adds a
suffix to the name consisting of an underscore and a number, and tries
that until it finds one that doesn't exist.
:param target_folder: The absolute path to the target.
:param basename: The file's original basename.
"""
name, ext = os.path.splitext(basename)
count = 0
while True:
count = count + 1
newname = '%s_%d%s' % (name, count, ext)
if not os.path.exists(os.path.join(target_folder, newname)):
return newname
uploads_mod = Blueprint('_uploads', __name__, url_prefix='/_uploads')
@uploads_mod.route('/<setname>/<path:filename>')
def uploaded_file(setname, filename):
config = current_app.upload_set_config.get(setname)
if config is None:
abort(404)
return send_from_directory(config.destination, filename)
class TestingFileStorage(FileStorage):
"""
This is a helper for testing upload behavior in your application. You
can manually create it, and its save method is overloaded to set `saved`
to the name of the file it was saved to. All of these parameters are
optional, so only bother setting the ones relevant to your application.
:param stream: A stream. The default is an empty stream.
:param filename: The filename uploaded from the client. The default is the
stream's name.
:param name: The name of the form field it was loaded from. The default is
`None`.
:param content_type: The content type it was uploaded as. The default is
``application/octet-stream``.
:param content_length: How long it is. The default is -1.
:param headers: Multipart headers as a `werkzeug.Headers`. The default is
`None`.
"""
def __init__(self, stream=None, filename=None, name=None,
content_type='application/octet-stream', content_length=-1,
headers=None):
FileStorage.__init__(self, stream, filename, name=name,
content_type=content_type, content_length=content_length,
headers=None)
self.saved = None
def save(self, dst, buffer_size=16384):
"""
This marks the file as saved by setting the `saved` attribute to the
name of the file it was saved to.
:param dst: The file to save to.
:param buffer_size: Ignored.
"""
if isinstance(dst, string_types):
self.saved = dst
else:
self.saved = dst.name
-43
View File
@@ -1,43 +0,0 @@
from captcha.image import ImageCaptcha
from io import BytesIO
from PIL import Image
from random import choices
from flask import session, make_response
from flask_login import current_user
def gen_captcha(content='0123456789'):
""" 生成验证码 """
image = ImageCaptcha()
# 获取字符串
captcha_text = "".join(choices(content, k=4))
# 生成图像
captcha_image = Image.open(image.generate(captcha_text))
return captcha_text, captcha_image
# 生成验证码
def get_captcha_image():
code, image = gen_captcha()
out = BytesIO()
session["code"] = code
image.save(out, 'png')
out.seek(0)
resp = make_response(out.read())
resp.content_type = 'image/png'
return resp, code
# 授权路由存入session
def add_auth_session():
role = current_user.role
user_power = []
for i in role:
if i.enable == 0:
continue
for p in i.power:
if p.enable == 0:
continue
user_power.append(p.code)
session['permissions'] = user_power
-36
View File
@@ -1,36 +0,0 @@
import typing as t
def success_api(message: str = "成功", code: int = 200) -> t.Dict:
""" 成功响应 默认值”成功“ """
return dict(success=True, message=message, code=code)
def fail_api(message: str = "失败", code: int = 404) -> t.Dict:
""" 失败响应 默认值“失败” """
return dict(success=False, message=message, code=code)
def table_api(success: bool = True,
message: str = "",
result: t.Union[dict, list] = None,
code: int = 0) -> t.Dict:
"""
动态表格渲染响应
此方法返回数据给前端
{
'success': True,
'code': 10002,
'message': '提示消息',
'result':{'items':[],'total': 100}
}
注:lay_ui 表格数据需要指定 code=0
"""
ret = {
'success': success,
'message': message,
'code': code,
'result': result,
}
return ret
-61
View File
@@ -1,61 +0,0 @@
import typing as t
from functools import wraps
from flask import abort, request, jsonify, session
from flask_login import login_required
from flask_login import current_user
from applications.extensions import db
from applications.models import LoggingModel
def record_logging(success: bool = True) -> None:
"""
记录用户日志数据
"""
info = {
'method': request.method,
'url': request.path,
'ip': request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'desc': str(dict(request.values)),
'uid': current_user.id,
'success': success
}
log = LoggingModel()
for key, value in info.items():
setattr(log, key, value)
db.session.add(log)
db.session.commit()
def view_logging_required(func: t.Callable) -> t.Callable:
"""
日志装饰器,用于记录请求
"""
@wraps(func)
def wrapper(*args, **kwargs) -> t.Callable:
record_logging()
return func(*args, **kwargs)
return wrapper
def permission_required(permission: str) -> t.Callable:
"""
权限装饰器,用于过滤需要的权限
"""
def decorator(func: t.Callable):
@wraps(func)
def wrapper(*args, **kwargs) -> t.Callable:
if permission not in session.get('permissions'):
record_logging(success=False)
abort(403)
return func(*args, **kwargs)
return wrapper
return decorator
-30
View File
@@ -1,30 +0,0 @@
import os
from flask import current_app
from applications.common.flask_uploads import UploadSet, IMAGES
from applications.extensions import db
from applications.models import FilePhoto
photos = UploadSet('photos', IMAGES)
def upload_one(photo, mime):
filename = photos.save(photo)
file_url = photos.url(filename)
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
size = os.path.getsize(upload_url + '/' + filename)
photo = FilePhoto(name=filename, href=file_url, mime=mime, size=size)
db.session.add(photo)
db.session.commit()
return file_url
def delete_photo_by_id(_id):
photo_name = FilePhoto.query.filter_by(id=_id).first().name
photo = FilePhoto.query.filter_by(id=_id).delete()
db.session.commit()
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
os.remove(upload_url + '/' + photo_name)
return photo
-1
View File
@@ -1 +0,0 @@
from .config import config
-24
View File
@@ -1,24 +0,0 @@
SYSTEM_NAME = "Pear Admin"
# 主题面板的链接列表配置
SYSTEM_PANEL_LINKS = [
{
"icon": "layui-icon layui-icon-auz",
"title": "官方网站",
"href": "http://www.pearadmin.com"
},
{
"icon": "layui-icon layui-icon-auz",
"title": "开发文档",
"href": "http://www.pearadmin.com"
},
{
"icon": "layui-icon layui-icon-auz",
"title": "开源地址",
"href": "https://gitee.com/Jmysy/Pear-Admin-Layui"
}
]
UPLOADED_PHOTOS_DEST = 'static/upload'
UPLOADED_FILES_ALLOW = ['gif', 'jpg']
# JSON配置
JSON_AS_ASCII = False
-54
View File
@@ -1,54 +0,0 @@
import logging
import os
class BaseConfig:
SECRET_KEY = os.getenv('SECRET_KEY', 'dev key')
# redis配置
REDIS_HOST = os.getenv('REDIS_HOST') or "127.0.0.1"
REDIS_PORT = int(os.getenv('REDIS_PORT') or 6379)
# mysql 配置
MYSQL_USERNAME = os.getenv('MYSQL_USERNAME') or "root"
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD') or "123456"
MYSQL_HOST = os.getenv('MYSQL_HOST') or "127.0.0.1"
MYSQL_PORT = int(os.getenv('MYSQL_PORT') or 3306)
MYSQL_DATABASE = os.getenv('MYSQL_DATABASE') or "PearAdminFlask"
UPLOADED_PHOTOS_DEST = '/static'
# mysql 数据库的配置信息
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USERNAME}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
# 默认日志等级
LOG_LEVEL = logging.WARN
class TestingConfig(BaseConfig):
""" 测试配置 """
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # 内存数据库
class DevelopmentConfig(BaseConfig):
""" 开发配置 """
SQLALCHEMY_DATABASE_URI = r'sqlite:///sql_pear_admin.db'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_ECHO = False
UPLOADED_PHOTOS_DEST = os.path.join(os.path.dirname(os.path.abspath(__name__)), 'static', 'upload')
class ProductionConfig(BaseConfig):
"""生成环境配置"""
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
SQLALCHEMY_POOL_RECYCLE = 8
LOG_LEVEL = logging.ERROR
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig
}
@@ -1,11 +0,0 @@
import json
import os
path = os.path.dirname(os.path.abspath(__file__))
cp_dept_data_list = json.loads(open(os.path.join(path, 'cp_dept.json'), encoding='utf-8').read())
cp_user_data_list = json.loads(open(os.path.join(path, 'cp_user.json'), encoding='utf-8').read())
file_photo_data_list = json.loads(open(os.path.join(path, 'file_photo.json'), encoding='utf-8').read())
rt_power_data_list = json.loads(open(os.path.join(path, 'rt_power.json'), encoding='utf-8').read())
rt_role_data_list = json.loads(open(os.path.join(path, 'rt_role.json'), encoding='utf-8').read())
rt_role_power_data_list = json.loads(open(os.path.join(path, 'rt_role_power.json'), encoding='utf-8').read())
rt_user_role_data_list = json.loads(open(os.path.join(path, 'rt_user_role.json'), encoding='utf-8').read())
@@ -1,72 +0,0 @@
[
{
"id": 1,
"parent_id": 0,
"dept_name": "总公司",
"sort": 1,
"leader": "就眠仪式",
"phone": "12312345679",
"email": "123qq.com",
"status": 1,
"comment": null,
"address": "这是总公司",
"create_at": null,
"update_at": "2021-06-01 17:23:20"
},
{
"id": 4,
"parent_id": 1,
"dept_name": "济南分公司",
"sort": 2,
"leader": "就眠仪式",
"phone": "12312345678",
"email": "1234qq.com",
"status": 1,
"comment": null,
"address": "这是济南",
"create_at": "2021-06-01 17:24:33",
"update_at": "2021-06-01 17:25:19"
},
{
"id": 5,
"parent_id": 1,
"dept_name": "唐山分公司",
"sort": 4,
"leader": "mkg",
"phone": "12312345678",
"email": "123@qq.com",
"status": 1,
"comment": null,
"address": "这是唐山",
"create_at": "2021-06-01 17:25:15",
"update_at": "2021-06-01 17:25:20"
},
{
"id": 7,
"parent_id": 4,
"dept_name": "济南分公司开发部",
"sort": 5,
"leader": "就眠仪式",
"phone": "12312345678",
"email": "123@qq.com",
"status": 1,
"comment": null,
"address": "测试",
"create_at": "2021-06-01 17:27:39",
"update_at": "2021-06-01 17:27:39"
},
{
"id": 8,
"parent_id": 5,
"dept_name": "唐山测试部",
"sort": 6,
"leader": "mkg",
"phone": "12312345678",
"email": "123@qq.com",
"status": 1,
"comment": null,
"address": "测试部",
"create_at": "2021-06-01 17:28:27",
"update_at": "2021-06-01 17:28:27"
}
]
@@ -1,38 +0,0 @@
[
{
"id": 1,
"username": "admin",
"password_hash": "pbkdf2:sha256:150000$raM7mDSr$58fe069c3eac01531fc8af85e6fc200655dd2588090530084d182e6ec9d52c85",
"create_at": null,
"update_at": "2021-06-01 17:28:55",
"enable": 1,
"realname": "超级管理",
"remark": "要是不能把握时机,就要终身蹭蹬,一事无成!",
"avatar": "http://127.0.0.1:5000/_uploads/photos/1617291580000.jpg",
"dept_id": 1
},
{
"id": 7,
"username": "test",
"password_hash": "pbkdf2:sha256:150000$cRS8bYNh$adb57e64d929863cf159f924f74d0634f1fecc46dba749f1bfaca03da6d2e3ac",
"create_at": "2021-03-22 20:03:42",
"update_at": "2021-06-01 17:29:47",
"enable": 1,
"realname": "超级管理",
"remark": "要是不能把握时机,就要终身蹭蹬,一事无成",
"avatar": "/static/admin/admin/images/avatar.jpg",
"dept_id": 1
},
{
"id": 8,
"username": "wind",
"password_hash": "pbkdf2:sha256:150000$skME1obT$6a2c20cd29f89d7d2f21d9e373a7e3445f70ebce3ef1c3a555e42a7d17170b37",
"create_at": "2021-06-01 17:30:39",
"update_at": "2021-06-01 17:30:52",
"enable": 1,
"realname": "风",
"remark": null,
"avatar": "/static/admin/admin/images/avatar.jpg",
"dept_id": 7
}
]
@@ -1,18 +0,0 @@
[
{
"id": 3,
"name": "6958819_pear-admin_1607443454_1.png",
"href": "http://127.0.0.1:5000/_uploads/photos/6958819_pear-admin_1607443454_1.png",
"mime": "image/png",
"size": "2204",
"create_time": "2021-03-19 18:53:02"
},
{
"id": 17,
"name": "1617291580000.jpg",
"href": "http://127.0.0.1:5000/_uploads/photos/1617291580000.jpg",
"mime": "image/png",
"size": "94211",
"create_time": "2021-04-01 23:39:41"
}
]
@@ -1,324 +0,0 @@
[
{
"id": 1,
"name": "系统管理",
"type": "0",
"code": "",
"url": null,
"open_type": null,
"parent_id": "0",
"icon": "layui-icon layui-icon-set-fill",
"sort": 1,
"create_time": null,
"update_time": null,
"enable": 1
},
{
"id": 3,
"name": "用户管理",
"type": "1",
"code": "admin:user:main",
"url": "/users/",
"open_type": "_iframe",
"parent_id": "1",
"icon": "layui-icon layui-icon layui-icon layui-icon layui-icon-rate",
"sort": 1,
"create_time": null,
"update_time": null,
"enable": 1
},
{
"id": 4,
"name": "权限管理",
"type": "1",
"code": "admin:power:main",
"url": "/rights/",
"open_type": "_iframe",
"parent_id": "1",
"icon": null,
"sort": 2,
"create_time": null,
"update_time": null,
"enable": 1
},
{
"id": 9,
"name": "角色管理",
"type": "1",
"code": "admin:role:main",
"url": "/admin/role",
"open_type": "_iframe",
"parent_id": "1",
"icon": "layui-icon layui-icon-username",
"sort": 2,
"create_time": "2021-03-16 22:24:58",
"update_time": "2021-03-25 19:15:24",
"enable": 1
},
{
"id": 13,
"name": "日志管理",
"type": "1",
"code": "admin:log:main",
"url": "/logs",
"open_type": "_iframe",
"parent_id": "1",
"icon": "layui-icon layui-icon-read",
"sort": 4,
"create_time": "2021-03-18 22:37:10",
"update_time": "2021-06-03 11:06:25",
"enable": 1
},
{
"id": 17,
"name": "文件管理",
"type": "0",
"code": "",
"url": "",
"open_type": "",
"parent_id": "0",
"icon": "layui-icon layui-icon-camera",
"sort": 2,
"create_time": "2021-03-19 18:56:23",
"update_time": "2021-03-25 19:15:08",
"enable": 1
},
{
"id": 18,
"name": "图片上传",
"type": "1",
"code": "admin:file:main",
"url": "/file",
"open_type": "_iframe",
"parent_id": "17",
"icon": "layui-icon layui-icon-camera",
"sort": 5,
"create_time": "2021-03-19 18:57:19",
"update_time": "2021-03-25 19:15:13",
"enable": 1
},
{
"id": 21,
"name": "权限增加",
"type": "2",
"code": "admin:power:add",
"url": "",
"open_type": "",
"parent_id": "4",
"icon": "layui-icon layui-icon-add-circle",
"sort": 1,
"create_time": "2021-03-22 19:43:52",
"update_time": "2021-03-25 19:15:22",
"enable": 1
},
{
"id": 22,
"name": "用户增加",
"type": "2",
"code": "admin:user:add",
"url": "",
"open_type": "",
"parent_id": "3",
"icon": "layui-icon layui-icon-add-circle",
"sort": 1,
"create_time": "2021-03-22 19:45:40",
"update_time": "2021-03-25 19:15:17",
"enable": 1
},
{
"id": 23,
"name": "用户编辑",
"type": "2",
"code": "admin:user:edit",
"url": "",
"open_type": "",
"parent_id": "3",
"icon": "layui-icon layui-icon-rate",
"sort": 2,
"create_time": "2021-03-22 19:46:15",
"update_time": "2021-03-25 19:15:18",
"enable": 1
},
{
"id": 24,
"name": "用户删除",
"type": "2",
"code": "admin:user:remove",
"url": "",
"open_type": "",
"parent_id": "3",
"icon": "layui-icon None",
"sort": 3,
"create_time": "2021-03-22 19:46:51",
"update_time": "2021-03-25 19:15:18",
"enable": 1
},
{
"id": 25,
"name": "权限编辑",
"type": "2",
"code": "admin:power:edit",
"url": "",
"open_type": "",
"parent_id": "4",
"icon": "layui-icon layui-icon-edit",
"sort": 2,
"create_time": "2021-03-22 19:47:36",
"update_time": "2021-03-25 19:15:22",
"enable": 1
},
{
"id": 26,
"name": "用户删除",
"type": "2",
"code": "admin:power:remove",
"url": "",
"open_type": "",
"parent_id": "4",
"icon": "layui-icon layui-icon-delete",
"sort": 3,
"create_time": "2021-03-22 19:48:17",
"update_time": "2021-03-25 19:15:23",
"enable": 1
},
{
"id": 27,
"name": "用户增加",
"type": "2",
"code": "admin:role:add",
"url": "",
"open_type": "",
"parent_id": "9",
"icon": "layui-icon layui-icon-add-circle",
"sort": 1,
"create_time": "2021-03-22 19:49:09",
"update_time": "2021-03-25 19:15:24",
"enable": 1
},
{
"id": 28,
"name": "角色编辑",
"type": "2",
"code": "admin:role:edit",
"url": "",
"open_type": "",
"parent_id": "9",
"icon": "layui-icon layui-icon-edit",
"sort": 2,
"create_time": "2021-03-22 19:49:41",
"update_time": "2021-03-25 19:15:25",
"enable": 1
},
{
"id": 29,
"name": "角色删除",
"type": "2",
"code": "admin:role:remove",
"url": "",
"open_type": "",
"parent_id": "9",
"icon": "layui-icon layui-icon-delete",
"sort": 3,
"create_time": "2021-03-22 19:50:15",
"update_time": "2021-03-25 19:15:26",
"enable": 1
},
{
"id": 30,
"name": "角色授权",
"type": "2",
"code": "admin:role:power",
"url": "",
"open_type": "",
"parent_id": "9",
"icon": "layui-icon layui-icon-component",
"sort": 4,
"create_time": "2021-03-22 19:50:54",
"update_time": "2021-03-25 19:15:26",
"enable": 1
},
{
"id": 31,
"name": "图片增加",
"type": "2",
"code": "admin:file:add",
"url": "",
"open_type": "",
"parent_id": "18",
"icon": "layui-icon layui-icon-add-circle",
"sort": 1,
"create_time": "2021-03-22 19:58:05",
"update_time": "2021-03-25 19:15:28",
"enable": 1
},
{
"id": 32,
"name": "图片删除",
"type": "2",
"code": "admin:file:delete",
"url": "",
"open_type": "",
"parent_id": "18",
"icon": "layui-icon layui-icon-delete",
"sort": 2,
"create_time": "2021-03-22 19:58:45",
"update_time": "2021-03-25 19:15:29",
"enable": 1
},
{
"id": 48,
"name": "部门管理",
"type": "1",
"code": "admin:dept:main",
"url": "/dept",
"open_type": "_iframe",
"parent_id": "1",
"icon": "layui-icon layui-icon-group",
"sort": 3,
"create_time": "2021-06-01 16:22:11",
"update_time": "2021-06-01 16:22:11",
"enable": 1
},
{
"id": 49,
"name": "部门增加",
"type": "2",
"code": "admin:dept:add",
"url": "",
"open_type": "",
"parent_id": "48",
"icon": "layui-icon None",
"sort": 1,
"create_time": "2021-06-01 17:35:52",
"update_time": "2021-06-01 17:36:15",
"enable": 1
},
{
"id": 50,
"name": "部门编辑",
"type": "2",
"code": "admin:dept:edit",
"url": "",
"open_type": "",
"parent_id": "48",
"icon": "layui-icon ",
"sort": 2,
"create_time": "2021-06-01 17:36:41",
"update_time": "2021-06-01 17:36:41",
"enable": 1
},
{
"id": 51,
"name": "部门删除",
"type": "2",
"code": "admin:dept:remove",
"url": "",
"open_type": "",
"parent_id": "48",
"icon": "layui-icon None",
"sort": 3,
"create_time": "2021-06-01 17:37:15",
"update_time": "2021-06-01 17:37:26",
"enable": 1
}
]
@@ -1,24 +0,0 @@
[
{
"id": 1,
"name": "管理员",
"code": "admin",
"remark": null,
"details": "管理员",
"sort": 1,
"create_time": null,
"update_time": null,
"enable": 1
},
{
"id": 2,
"name": "普通用户",
"code": "common",
"remark": null,
"details": "只有查看,没有增删改权限",
"sort": 2,
"create_time": "2021-03-22 20:02:38",
"update_time": "2021-04-01 22:29:56",
"enable": 1
}
]
@@ -1,40 +0,0 @@
[
[237, 1, 1],
[238, 3, 1],
[239, 4, 1],
[240, 9, 1],
[241, 12, 1],
[242, 13, 1],
[243, 17, 1],
[244, 18, 1],
[245, 21, 1],
[246, 22, 1],
[247, 23, 1],
[248, 24, 1],
[249, 25, 1],
[250, 26, 1],
[251, 27, 1],
[252, 28, 1],
[253, 29, 1],
[254, 30, 1],
[255, 31, 1],
[256, 32, 1],
[257, 44, 1],
[258, 45, 1],
[259, 46, 1],
[260, 47, 1],
[261, 48, 1],
[262, 49, 1],
[263, 50, 1],
[264, 51, 1],
[265, 1, 2],
[266, 3, 2],
[267, 4, 2],
[268, 9, 2],
[269, 12, 2],
[270, 13, 2],
[271, 17, 2],
[272, 18, 2],
[273, 44, 2],
[274, 48, 2]
]
@@ -1,5 +0,0 @@
[
[21, 1, 1],
[22, 7, 2],
[24, 8, 2]
]
-17
View File
@@ -1,17 +0,0 @@
from flask import Flask
from .init_databases import register_script
from .init_sqlalchemy import db, init_databases
from .init_login import init_login_manager
from .init_template_directives import init_template_directives
from .init_error_views import init_error_views
def init_plugs(app: Flask) -> None:
init_login_manager(app)
init_databases(app)
init_template_directives(app)
init_error_views(app)
# 生成测试数据的命令,生成环境可以注释
register_script(app)
@@ -1,76 +0,0 @@
from flask import Flask
import re
from datetime import datetime
date_str = re.compile('\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$')
def add_data(data_list, obj):
from applications.extensions import db
for _data in data_list:
dept = obj()
for key, value in _data.items():
if isinstance(value, str) and date_str.match(value):
value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
setattr(dept, key, value)
db.session.add(dept)
db.session.commit()
def register_script(app: Flask):
@app.cli.command()
def init_db():
"""数据库初始化"""
# 创建化部门数据
from applications.models import CompanyDepartment
from applications.configs.init_data import cp_dept_data_list
add_data(cp_dept_data_list, CompanyDepartment)
# 图片数据
from applications.models import FilePhoto
from applications.configs.init_data import file_photo_data_list
add_data(file_photo_data_list, FilePhoto)
# 初始化权限表数据
from applications.models import RightsPower
from applications.configs.init_data import rt_power_data_list
add_data(rt_power_data_list, RightsPower)
# 初始化角色表
from applications.models import RightsRole
from applications.configs.init_data import rt_role_data_list
# 角色权限关系表
from applications.extensions import db
from applications.configs.init_data import rt_role_power_data_list
for data in rt_role_power_data_list:
db.session.execute('insert into rt_role_power VALUES (%s, %s, %s);' % tuple(data))
db.session.commit()
add_data(rt_role_data_list, RightsRole)
# 管理员用户
from applications.models import CompanyUser
from applications.configs.init_data import cp_user_data_list
add_data(cp_user_data_list, CompanyUser)
# 用户角色表
from applications.extensions import db
from applications.configs.init_data import rt_user_role_data_list
for data in rt_user_role_data_list:
db.session.execute('insert into rt_user_role VALUES (%s, %s, %s);' % tuple(data))
db.session.commit()
@app.cli.command()
def turn():
"""清空数据库"""
from applications.extensions import db
db.drop_all()
db.create_all()
-12
View File
@@ -1,12 +0,0 @@
import os
from dotenv import load_dotenv
root_path = os.path.abspath(os.path.dirname(__file__)).split('applications')[0]
dot_env_path = os.path.join(root_path, '.env')
flask_env_path = os.path.join(root_path, '.flaskenv')
if os.path.exists(dot_env_path):
load_dotenv(dot_env_path)
if os.path.exists(flask_env_path):
load_dotenv(flask_env_path)
@@ -1,15 +0,0 @@
from flask import render_template
def init_error_views(app):
@app.errorhandler(403)
def page_not_found(e):
return render_template('errors/403.html'), 403
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
-15
View File
@@ -1,15 +0,0 @@
from flask_login import LoginManager
def init_login_manager(app):
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index.login'
login_manager.login_message = u'请登录以访问此页面'
@login_manager.user_loader
def load_user(user_id):
from applications.models import CompanyUser
user = CompanyUser.query.get(int(user_id))
return user
@@ -1,11 +0,0 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def init_databases(app: Flask):
db.init_app(app)
migrate.init_app(app, db)
@@ -1,7 +0,0 @@
from flask import session
def init_template_directives(app):
@app.template_global()
def authorize(power):
return bool(power in session.get('permissions'))
-4
View File
@@ -1,4 +0,0 @@
from applications.models.file import FilePhoto
from applications.models.log import LoggingModel
from applications.models.rights import RightsPower, RightsRole, role_power, user_role
from applications.models.users import CompanyDepartment, CompanyUser
-8
View File
@@ -1,8 +0,0 @@
from datetime import datetime
from applications.extensions import db
class BaseModel(object):
create_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
update_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
-1
View File
@@ -1 +0,0 @@
from .photo import FilePhoto
-12
View File
@@ -1,12 +0,0 @@
from applications.extensions import db
from ..base import BaseModel
class FilePhoto(db.Model, BaseModel):
__tablename__ = 'file_photo'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
href = db.Column(db.String(255))
mime = db.Column(db.CHAR(50), nullable=False)
size = db.Column(db.CHAR(30), nullable=False)
-16
View File
@@ -1,16 +0,0 @@
from flask_restful import fields
from applications.extensions import db
from .base import BaseModel
class LoggingModel(db.Model, BaseModel):
__tablename__ = 'lg_logging'
id = db.Column(db.Integer, primary_key=True)
method = db.Column(db.String(10))
uid = db.Column(db.Integer, default=None)
url = db.Column(db.String(255))
desc = db.Column(db.Text)
ip = db.Column(db.String(255))
success = db.Column(db.Boolean, default=True)
user_agent = db.Column(db.Text)
-19
View File
@@ -1,19 +0,0 @@
from applications.extensions import db
from .power import RightsPower
from .role import RightsRole
# 创建中间表
user_role = db.Table(
"rt_user_role", # 中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
db.Column("user_id", db.Integer, db.ForeignKey("cp_user.id"), comment='用户编号'), # 属性 外键
db.Column("role_id", db.Integer, db.ForeignKey("rt_role.id"), comment='角色编号'), # 属性 外键
)
# 创建中间表
role_power = db.Table(
"rt_role_power", # 中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
db.Column("power_id", db.Integer, db.ForeignKey("rt_power.id"), comment='用户编号'), # 属性 外键
db.Column("role_id", db.Integer, db.ForeignKey("rt_role.id"), comment='角色编号'), # 属性 外键
)
-18
View File
@@ -1,18 +0,0 @@
from applications.extensions import db
from ..base import BaseModel
class RightsPower(db.Model, BaseModel):
__tablename__ = 'rt_power'
id = db.Column(db.Integer, primary_key=True, comment='权限编号')
name = db.Column(db.String(255), comment='权限名称')
type = db.Column(db.SMALLINT, comment='权限类型')
code = db.Column(db.String(30), comment='权限标识')
url = db.Column(db.String(255), comment='权限路径')
open_type = db.Column(db.String(10), comment='打开方式')
parent_id = db.Column(db.Integer, db.ForeignKey("rt_power.id"), comment='父类编号')
icon = db.Column(db.String(128), comment='图标')
sort = db.Column(db.Integer, comment='排序')
enable = db.Column(db.Boolean, comment='是否开启')
parent = db.relationship("RightsPower", remote_side=[id]) # 自关联
-17
View File
@@ -1,17 +0,0 @@
from flask_restful import fields
from applications.extensions import db
from ..base import BaseModel
class RightsRole(db.Model, BaseModel):
__tablename__ = 'rt_role'
id = db.Column(db.Integer, primary_key=True, comment='角色ID')
name = db.Column(db.String(255), comment='角色名称')
code = db.Column(db.String(255), comment='角色标识')
enable = db.Column(db.Boolean, comment='是否启用')
comment = db.Column(db.String(255), comment='备注')
details = db.Column(db.String(255), comment='详情')
sort = db.Column(db.Integer, comment='排序')
power = db.relationship('RightsPower', secondary="rt_role_power", backref=db.backref('role'))
-2
View File
@@ -1,2 +0,0 @@
from .dept import CompanyDepartment
from .users import CompanyUser
-37
View File
@@ -1,37 +0,0 @@
from flask_restful import fields
from applications.extensions import db
from ..base import BaseModel
class CompanyDepartment(db.Model, BaseModel):
__tablename__ = 'cp_dept'
id = db.Column(db.Integer, primary_key=True, comment="部门ID")
parent_id = db.Column(db.Integer, comment="父级编号")
dept_name = db.Column(db.String(50), comment="部门名称")
leader = db.Column(db.String(50), comment="负责人")
phone = db.Column(db.String(20), comment="联系方式")
email = db.Column(db.String(50), comment="邮箱")
status = db.Column(db.Boolean, comment='状态(1开启,0关闭)')
comment = db.Column(db.Text, comment="备注")
address = db.Column(db.String(255), comment="详细地址")
sort = db.Column(db.Integer, comment="排序")
@staticmethod
def fields():
"""
定义模型的常用输出字段,新手请忽略。可以简化字段序列化操作,
详细操作请查看 flask-restful marshal 的用法
"""
return {
'deptId': fields.Integer(attribute="id"),
'parentId': fields.Integer(attribute="parent_id"),
'deptName': fields.String(attribute="dept_name"),
'sort': fields.Integer,
'leader': fields.String,
'phone': fields.String,
'email': fields.String,
'status': fields.Boolean,
'comment': fields.String,
'address': fields.String,
'create_at': fields.DateTime
}
-29
View File
@@ -1,29 +0,0 @@
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from flask_restful import fields
from applications.extensions import db
from ..base import BaseModel
class CompanyUser(db.Model, UserMixin, BaseModel):
__tablename__ = 'cp_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
username = db.Column(db.String(20), comment='用户名')
realname = db.Column(db.String(20), comment='真实名字')
mobile = db.Column(db.String(11), comment='电话号码')
avatar = db.Column(db.String(255), comment='头像', default="/static/admin/admin/images/avatar.jpg")
comment = db.Column(db.String(255), comment='备注')
password_hash = db.Column(db.String(128), comment='哈希密码')
enable = db.Column(db.Integer, default=0, comment='启用')
dept_id = db.Column(db.Integer, comment='部门id')
role = db.relationship('RightsRole', secondary="rt_user_role", backref=db.backref('user'), lazy='dynamic')
def set_password(self, password):
"""设置密码,对密码进行加密存储"""
self.password_hash = generate_password_hash(password)
def validate_password(self, password):
"""校验密码方法"""
return check_password_hash(self.password_hash, password)
+3 -3
View File
@@ -1,7 +1,7 @@
from flask import render_template, request
from applications.common.utils.rights import permission_required, view_logging_required
from applications.models import CompanyDepartment
from models import DepartmentModels
from common.utils.rights import permission_required, view_logging_required
from applications.view import index_bp
@@ -24,5 +24,5 @@ def add():
@permission_required("admin:dept:edit")
def edit():
dept_id = request.args.get("deptId", type=int)
dept = CompanyDepartment.query.get(dept_id)
dept = DepartmentModels.query.get(dept_id)
return render_template('admin/department/dept_edit.html', dept=dept)
+1 -1
View File
@@ -1,7 +1,7 @@
from flask import render_template
from applications.view import index_bp
from applications.common.utils.rights import view_logging_required, permission_required
from common.utils.rights import view_logging_required, permission_required
@index_bp.get('/file')
+8 -8
View File
@@ -1,9 +1,9 @@
from flask import Blueprint, request, render_template
from sqlalchemy import desc
from applications.common.utils.http import table_api
from applications.common.utils.rights import permission_required
from applications.models import LoggingModel
from common.utils.http import table_api
from common.utils.rights import permission_required
from models import LogModel
logs_bp = Blueprint('logs', __name__, url_prefix='/logs')
@@ -19,9 +19,9 @@ def index():
def login_log():
page = request.args.get('page', type=int)
limit = request.args.get('limit', type=int)
log_paginate = LoggingModel.query.filter_by(
log_paginate = LogModel.query.filter_by(
url='/api/v1/passport/login').order_by(
desc(LoggingModel.create_at)).paginate(
desc(LogModel.create_at)).paginate(
page=page, per_page=limit, error_out=False)
data = [
{
@@ -47,9 +47,9 @@ def login_log():
def operate_log():
page = request.args.get('page', type=int)
limit = request.args.get('limit', type=int)
log_paginate = LoggingModel.query.filter(
LoggingModel.url != '/api/v1/passport/login').order_by(
desc(LoggingModel.create_at)).paginate(
log_paginate = LogModel.query.filter(
LogModel.url != '/api/v1/passport/login').order_by(
desc(LogModel.create_at)).paginate(
page=page, per_page=limit, error_out=False)
data = [
{
+3 -3
View File
@@ -1,8 +1,8 @@
from flask import Blueprint, session, redirect, render_template, url_for
from flask import session, redirect, render_template, url_for
from flask_login import login_required, logout_user, current_user
from applications.common.gen_captcha import get_captcha_image
from applications.common.utils.http import success_api
from common.gen_captcha import get_captcha_image
from common.utils.http import success_api
# 获取验证码
from applications.view import index_bp
+3 -3
View File
@@ -1,7 +1,7 @@
from flask import render_template
from applications.common.utils.rights import permission_required, view_logging_required
from applications.models import RightsPower
from common.utils.rights import permission_required, view_logging_required
from models import RightModels
from applications.view import index_bp
@@ -16,7 +16,7 @@ def rights_index():
@view_logging_required
@permission_required("admin:power:edit")
def rights_edit(power_id):
power = RightsPower.query.filter_by(id=power_id).first()
power = RightModels.query.filter_by(id=power_id).first()
icon = str(power.icon).split()
if len(icon) == 2:
icon = icon[1]
+3 -3
View File
@@ -1,8 +1,8 @@
from flask import Blueprint, render_template
from applications.common.utils.rights import permission_required, view_logging_required
from common.utils.rights import permission_required, view_logging_required
from applications.models import RightsRole
from models import RoleModels
role_bp = Blueprint('role', __name__, url_prefix='/admin/role')
@@ -28,7 +28,7 @@ def power(role_id):
@view_logging_required
@permission_required("admin:role:edit")
def role_editor(role_id):
role = RightsRole.query.filter_by(id=role_id).first()
role = RoleModels.query.filter_by(id=role_id).first()
return render_template('admin/roles/roles_edit.html', role=role)
+7 -7
View File
@@ -2,8 +2,8 @@ from flask import render_template
from flask_login import login_required, current_user
from sqlalchemy import desc
from applications.common.utils.rights import permission_required, view_logging_required
from applications.models import LoggingModel, RightsRole, CompanyUser
from common.utils.rights import permission_required, view_logging_required
from models import LogModel, RoleModels, UserModels
from . import index_bp
@@ -19,7 +19,7 @@ def users_main():
@view_logging_required
@permission_required("admin:user:add")
def users_add_view():
roles = RightsRole.query.all()
roles = RoleModels.query.all()
return render_template('admin/users/users_add.html', roles=roles)
@@ -28,8 +28,8 @@ def users_add_view():
@permission_required("admin:user:edit")
def users_user_id_view(user_id):
# 获取编辑用户信息
user = CompanyUser.query.filter_by(id=user_id).first()
roles = RightsRole.query.all()
user = UserModels.query.filter_by(id=user_id).first()
roles = RoleModels.query.all()
checked_roles = []
for r in user.role:
checked_roles.append(r.id)
@@ -39,8 +39,8 @@ def users_user_id_view(user_id):
@index_bp.get('/users/center')
@login_required
def users_center():
user_logs = LoggingModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LoggingModel.create_at)).limit(10)
user_logs = LogModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LogModel.create_at)).limit(10)
return render_template('admin/users/profile.html', user_info=current_user, user_logs=user_logs)