# Flask實作_權限控管_08_角色權限管理 ###### tags: `python` `flask` Form的實作上會自己製做一個Checkbox來做選單,這部份的來源連結可見<a href="#Checkbox">官方參考說明</a>。 有了這個Chcekbox選單之後,實作權限管理就變的容易了,原本想法上是利用兩個SelectMultipleField來做調控,但這還需要再另外搭配jquery,想了想,還是先從簡單的方向來完成這個實作比較重要,過多的技巧都不如簡單的解決問題來的好。 ## 作業說明 預計的工作有: 1. 新增表單_FormRole_Func_manager 2. 新增頁面_Role_Func_manager.html 3. 新增View_Function_Role_Func_manager 我們就從新增表單開始,原生WTForm並沒有多選的Checkbox表單,因此需要自己建立。 :::success * 文件:app_blog\main\form.py * 說明:新增FormRole_Func_manager來做管理界面 ```python= # 追加import SelectMultipleField, widgets from wtforms import SelectMultipleField, widgets # 建立MultiCheckboxField class MultiCheckboxField(SelectMultipleField): widget = widgets.ListWidget(prefix_label=False) option_widget = widgets.CheckboxInput() class FormRole_Func_manager(FlaskForm): """ 角色權限管理界面 """ all_function_option = MultiCheckboxField('all_function', coerce=int) submit = SubmitField('submit') ``` 第5行:建立一個MultiCheckboxField,參考官方文件建立。(見參考連結) 第9行:這邊不設置選項(choices),只是單純的透過範例來了解可以在不同的地方產生選項 ::: 簡單的利用一個View Function來做測試,確認我們的MultiCheckbox是正常的。 :::warning 測試: 測試說明:確認MultiCheckbox,html的部份請依標準製作即可。 ```python= @main.route('/role_func_manager/<int:role_id>/', methods=['GET', 'POST']) def role_func_manager(role_id): """ 角色權限管理 取得角色目標權限以及未存在的權限 :param role_id:角色id :return: """ form = FormRole_Func_manager() # 取得目前擁有的View function list all_funcs = Func.query.with_entities(Func.id, Func.func_module_name).all() # 設置checkbox的項目 form.all_function_option.choices = [(id, role) for id, role in all_funcs] return render_template('main/Role_Func_manager.html', form=form) ``` ![](https://i.imgur.com/P2bBJmO.png) 看起來很正常,不過感覺quick_form似乎沒有很漂亮的排版,這次我們就自己手動來小排版吧。 ::: 接著要加入一個HTML,這次很明顯的無法直接利用quick_form,自己動手也複習一下。 :::success * 文件:templates\main\Role_Func_manager.html * 說明:建置角色權限調整頁面 ```htmlmixed= {% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %} Role Manager {% endblock %} {% block page_content %} <div class="page-header"> <h1>Role Manager</h1> <h2>Modify Role Permission</h2> </div> <div class="col-md-4"> <form method="POST" action> {{form.hidden_tag()}} <fieldset class="form-group"> <legend class="border-bottom mb-4">User Choices</legend> {% if form.all_function_option.errors %} <div class="invalid-feedback"> {% for error in form.all_function_option.errors %} <span>{{ error }}</span> {% endfor %} </div> {% endif %} {% for choice in form.all_function_option %} <div class="form-check"> {{ choice(class="form-check-input")}} {{ choice.label(class="form-check-label") }} </div> {% endfor %} </fieldset> <div class="form-group"> {{ form.submit(class="btn btn-outline-info") }} </div> </form> </div> {% endblock %} ``` 建置到這邊,如果還有不解的,就快速的看過一次jinja2的說明文件,相信可以馬上的回想起基礎。 ::: 最後,就是完整一個View Function :::success * 文件:app_blog\main\view.py * 說明:建置一個View Function ```python= @main.route('/role_func_manager/<int:role_id>/', methods=['GET', 'POST']) def role_func_manager(role_id): """ 角色權限管理 取得角色目標權限以及未存在的權限 :param role_id:角色id :return: """ form = FormRole_Func_manager() # 取得角色 role = Role.query.filter_by(id=role_id).first() # 取得目前擁有的View function list all_funcs = Func.query.with_entities(Func.id, Func.func_module_name).all() # 設置checkbox的項目 form.all_function_option.choices = [(id, role) for id, role in all_funcs] # 以該角色目前擁有的權限做為預設值 form.all_function_option.default = [role.id for role in role.funcs] if form.validate_on_submit(): # 取得選取得View function項目 funcs = Func.query.filter(Func.id.in_(form.all_function_option.data)) # 先清空 role.funcs.clear() for func in funcs: # 後寫入 role.funcs.append(func) db.session.add(role) db.session.commit() return redirect(url_for('main.role_manager_r', page=1)) # 務必執行,預設值才會成功 form.process() return render_template('main/Role_Func_manager.html', form=form) ``` 程式碼以註解方式說明,這邊對於多的多的更新一直在思考怎麼處理較好,最後用了一個很笨的方式來處理,也注意到`form.process()`的部份,放錯地方會造成`csfr_token`的資料遺失 現在,我們的版面看起來正常多了,並且該角色id已有權限的部份也預設勾選,一樣的,請不要過於在意版的美醜,這部份可以再依個人需求調整即可。 ![](https://i.imgur.com/tvmfOqk.png) ::: ## 總結 這個範例產生的比較久,除了目前在研究celery與rabbitmq之外,就是在`form.process()`的部份疏失,一開始放錯了地方,放置於`if form.validate....:`之上,這造成了在post之後會再先執行一次`form.process()`,也因此造成了`The CSRF token is missing`。 另外,在sqlalchemy的many to many更新中,在下苦思不到較好的處理方式,所以用了一個比較笨的方式,先清空後寫入,如果有前輩有較佳的處理方式也請指導。 最後,在更新之後我們會將使用者導到角色列表,這邊理論上可以再加入一個超連結來接到權限編輯,整個流程會較佳,這部份相信也都可以快速處理,在下就不再範例。 ## 參考 <span id="Checkbox">[Checkbox WTForm](https://wtforms.readthedocs.io/en/stable/specific_problems.html)</span>