Flask學習「一 」(按鈕,角色,菜單,用戶,權限)

Flask學習「一」(按鈕,角色,菜單,用戶,權限)

很榮幸有時間能靜下心來寫在這篇文章,前段時間寫了一些沒有營養的文章對那些關注我的同學來說非常抱歉,接下來的一段日子裏會圍繞近期所做的Flask項目寫一系列的博客,以記錄自己的不足。
鑑於可能有些小白可能會看到這篇文章,於是我儘量寫的通俗易懂。
接下來進入正題,我這篇文章要寫的是一個系統的權限部分。權限的控制對於一個優秀的系統來說至關重要,但是對於權限的設計和把空是比較麻煩的。
一般如果我們不考慮按鈕的話,邏輯大致如下:
把菜單和權限、權限用戶關聯起來。
1、用戶頁面,可以增刪改查,並且還要有一個分配權限的按鈕。
2、權限頁面,可以增刪改查,並且有一個分配用戶的按鈕和一個分配菜單的按鈕。
3、建立兩個表,分別爲用戶權限表(保存用戶ID和權限ID)、權限菜單表(保存權限ID和菜單ID)。
4、當在用戶頁面中選中一個用戶,點擊用戶的“分配權限”按鈕時,打開展示所有權限的頁面(並把用戶ID傳進去),左邊展示所有還沒有分配的權限列表,右邊展現已經分配的權限列表,然後選擇需要分配的左邊權限後,點擊分配,把數據分配到右邊已分配的列表中,然後點擊“確定”按鈕,把用戶ID和選擇的權限ID保存到用戶權限表。
5、當在權限頁面選中一個權限,並點擊“分配用戶”時,處理方式和4相同,當選擇需要分配權限的用戶後,同樣把用戶ID和權限ID保存到用戶權限表。
6、當在權限頁面選中一個權限,並點擊“分配菜單”時,打開一個樹展現所有菜單的頁面,每個樹節點前面有一個複選框,並把這個權限已經分配的樹默認選中,然後在要分配的菜單節點樹前面的複選框上選中,最後保存數據,把權限Id和所有選中的菜單ID保存到權限菜單表。
7、當用戶登陸系統的時候,首先檢查用戶輸入的口令信息,如果口令正確,再根據用戶倒查用戶權限表,再通過用戶權限表查到的權限,到權限菜單表查詢相應的菜單,再把相應的菜單展示出來。
上面便是不考慮按鈕的情況下的業務邏輯,其實加上按鈕的話也是差不多的,因爲按鈕隸屬於菜單,只有給某個用戶分配了某個角色,這個用戶才能在登錄的時候看到他所擁有角色對應下的菜單和按鈕,這樣即完成了角色的權限控制。
接下來開始我們的項目。
首先根據上面的業務描述,我們大概可以用到的表和字段如下:
user表(id,name,tel,email,password) # 用戶表
role表(id,name,description) # 角色表
user_role表(id,user_id,role_id) # 用戶角色表
menu表(id,parent_id,lay,name,code,description) # 菜單表
action表(id,menu_id,name,code,description) # 按鈕表
role_menu(id,role_id,menu_id) # 角色菜單表
role_action(id,role_id,action_id) # 角色按鈕表

user_id
role_id
role_id
menu_id
role_id
action_id
menu_id
user
user_role
role
role_menu
menu
role_action
action

emmm,這幾張表的關係大概如上吧。
大概邏輯有了,現在開始寫代碼:

#----------------------------------start-----------------------------------#
'''
我們的框架使用Flask+sqlalchemy+flask_restplus
sqlalchemy爲ORM數據庫映射 PS:sqlalchemy真的非常強大 使用起來非常方便
flask_restplus是swagger所呈現出來的一種網頁端接口測試工具 最大的有點是可以避免寫接口文檔
'''
# 根據user_id查詢 required=True爲必填項
page_parser.add_argument('user_id', type=int, required=True, location='args')
# 用戶角色post新增/修改傳入參數
user_role_model = api.model('RoleUserRole', {
    'role_id_list': fields.String('role id list 以逗號隔開","'),
    'user_id': fields.Integer
})

# flask_restplus頁面展示url /flask路由註冊/需註冊到藍圖上
@api.route('/role_by_user')
# flask_restplus定義每一個類名展現在swagger的NameSpace上
class RoleByUser(Resource):
    @api.expect(page_parser)
    ‘’‘
    查詢已經分配過角色的用戶 以用戶爲主體
    ’‘’
    def get(self):
    	# 自定義驗證傳入參數是否合法
        form = RoleByUserForm().validate_for_api()
        #實現代碼模塊化 將可複用查詢條件拆分出來 放在最後定義成了一個單獨的方法
        task_filter = _form_and_task(form)
        user_id = form.user_id.data
        # 增加查詢條件
        task_filter.append(UserRole.user_id == user_id)
        page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id) \
        	# *task_filter爲可變參數,可以傳入元組/列表
            .filter(*task_filter).order_by(
            # sqlalchemy根據創建時間排序
            text('role.create_time desc')
            # paginate()分頁對象 傳入定義號的頁數
        ).paginate()
        return Role().page(page)


@api.route('/role_by_not_user')
class RoleByNotUser(Resource):
	# 和上面的類似 查詢未分配角色的用戶
    @api.expect(page_parser)
    def get(self):
        form = RoleByUserForm().validate_for_api()
        task_filter = _form_and_task(form)
        user_id = form.user_id.data
        # UserRole.user_id != user_id 查詢未分配角色的用戶
        task_filter.append(UserRole.user_id != user_id)
        page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id) \
            .filter(*task_filter).order_by(
            text('role.create_time desc')
        ).paginate()
        return Role().page(page)
	# 新增 一個用戶可能對應多個角色 傳入role_id_list
    @api.expect(user_role_model)
    def post(self):
        form = RoleUserPostForm().validate_for_api()
        user_id, role_id_list = form.user_id.data, form.role_id_list.data
        # 傳入role_id_list使用“,”分開 使用split從每個“,”處分開
        if role_id_list:
            user_role_list = []
            role_id_list = role_id_list.split(',')
            # 遍歷role_id_list 將每個role_id存入上面定義的user_role_list列表中
            # 調用我們自定義的save_all方法 將每個role_id存入UserRole表
            for role_id in role_id_list:
                user_role = UserRole()
                user_role.role_id = role_id
                user_role.user_id = int(user_id)
                user_role_list.append(user_role)
            UserRole().save_all(user_role_list)

’‘’
權限設置
’‘’
role_action_menu_parser = reqparse.RequestParser()
role_action_menu_parser.add_argument('role_id', type=int, required=True, location='args')

menu_action_lists = api.model('RoleActionMenuList', {
    'mid': fields.Integer,
    'type': fields.Integer
})
# 接收的參數爲menu_action_list和role_id,menu_action_list中存的是mid和type
# 這裏的type是爲了區分菜單和按鈕 0-菜單 1-按鈕
role_action_menu_model = api.model('RoleActionMenu', {
    'menu_action_list': fields.List(fields.Nested(menu_action_lists)),
    'role_id': fields.Integer
})

‘’‘
namedtuple(命名元組)是繼承自tuple的子類 namedtuple創建一個和tuple類似的對象 而且對象擁有可訪問的屬性 
普通tuple類型的成員 只能通過索引訪問 namedtuple在此基礎上還提供了通過名稱訪問的方式
’‘’
# 我們使用一個命名元組來定義按鈕和菜單的樹形集合
menu_action_tree = namedtuple('MenuActionTree', ['id', 'name', 'parent_id', 'lay', 'is_select', 'has_child', 'type'])

@api.route('/role_action_menu')
class RoleActionMenu(Resource):
    @api.expect(role_action_menu_parser)
    ‘’‘
    查詢該角色所能查到的所有的菜單和按鈕
    ’‘’
    def get(self):
        form = RoleIdForm().validate_for_api()
        role_id = form.role_id.data
        menus = Menu.query.filter().all()  # 菜單
        actions = Action.query.filter().all()  # 按鈕
        # 通過自定義樹形菜單和按鈕列表,通過role_id查詢拼接當前角色所能看到的菜單和按鈕
        # 分別構造拼接菜單和按鈕樹形集合 並將菜單和按鈕的樹形合併
        menu_action_trees = _menu_tree(role_id, menus)
        menu_action_trees += _action_tree(role_id, actions, menus)
        # 通過自定義get_tree方法將最後合併好的數據集合轉化爲json傳給前臺
        tree = get_tree(menu_action_trees)
        return tree
	‘’‘
	新增角色菜單和按鈕
	’‘’
    @api.expect(role_action_menu_model)
    def post(self):
        form = RoleMenuActionForm().validate_for_api()
        role_id, menu_action_list = form.role_id.data, form.menu_action_list.data
        # 過濾 區分菜單和按鈕
        if menu_action_list:
            role_action = list(filter(lambda x: x['type'] == 1, menu_action_list))
            role_menu = list(filter(lambda x: x['type'] == 0, menu_action_list))
            # 使用自定義方法分別儲存菜單和按鈕到role_menu和role_action表
            with db.auto_commit():
                _save_menu(role_id, role_menu)
                _save_action(role_id, role_action)

# 存儲菜單
def _save_menu(role_id, role_menu):
	# 每次在存之前我們先刪除該角色之前存儲過的菜單
    RoleMenu.query.filter_by(role_id=role_id).delete()
    new_role_menu = []
    # 遍歷role_menu表 通過role_id將給該角色添加菜單
    for m in role_menu:
        role_menu = RoleMenu()
        role_menu.role_id = role_id
        role_menu.menu_id = m['mid']
        new_role_menu.append(role_menu)
    RoleMenu().save_all(new_role_menu)

#存儲按鈕
def _save_action(role_id, role_action):
	# 和菜單一樣 在新增之前我們要刪除之前存儲過的按鈕
    RoleAction.query.filter_by(role_id=role_id).delete()
    new_role_action = []
    # 遍歷role_acton表通過role_id將給該角色添加角色
    for m in role_action:
        role_action = RoleAction()
        role_action.role_id = role_id
        role_action.action_id = m['mid']
        new_role_action.append(role_action)
    RoleAction().save_all(new_role_action)

# 拼接action樹形
def _action_tree(role_id, actions, menus):
    menu_action_trees = []
    role_actions = RoleAction.query.filter(UserRole.role_id == role_id).all()
    for action in actions:
        action_parent = list(filter(lambda x: x.id == action.menu_id, menus))
        # 判斷層級
        if len(action_parent) > 0:
            lay = action_parent[0].lay + 1
        else:
            lay = 0
		# 是否選中 1-選中 0-未選中
        is_select = [False for role_action in role_actions if role_action.action_id == action.id]
        if is_select:
            is_select = 1
        else:
            is_select = 0
        # 按照前面定義命名元組參數順序按照順序傳入對應參數
        mct = menu_action_tree(
            str(action.id) + '$action',  # 爲了區分按鈕和菜單id使用$action分割
            action.name,
            action.menu_id,
            lay,
            is_select,
            0,  # 按鈕是最後一級
            1  # 0-菜單 1-按鈕
        )

        menu_action_trees.append(mct)
    return menu_action_trees

# 拼接菜單樹形
def _menu_tree(role_id, menus):
    menu_action_trees = []
    role_menus = RoleMenu.query.filter(UserRole.role_id == role_id).all()
    for menu in menus:
   		# 通過列表推導式判斷有無選中
        is_select = [False for role_menu in role_menus if role_menu.menu_id == menu.id]
        if is_select:
            is_select = 1
        else:
            is_select = 0
        mct = menu_action_tree(
            menu.id,
            menu.name,
            menu.parent_id,
            menu.lay,
            is_select,
            0,  # 不好判斷暫定爲0
            0  # 0-菜單 1-按鈕
        )

        menu_action_trees.append(mct)
    return menu_action_trees

# 通過姓名模糊查詢
def _form_and_task(form):  
    name = form.name.data
    task_filter = [
        Role.name.like('%' + name + '%') if name is not None else text(''),
    ]
    return task_filter
#----------------------------------end------------------------------------#

這樣我們就完成了按鈕,角色,菜單,用戶,權限的校驗,文中少數自定義類或方法由於寫在了基類中,等到後面會慢慢列出。另外文章前面是以用戶爲主體的角色綁定用戶,在用戶頁面還應該有以角色爲主體的用戶綁定角色,但是兩者都不盡相同,因此在本文中暫不列出,後續如果有需要的話再補上!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章