# 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)
```

看起來很正常,不過感覺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已有權限的部份也預設勾選,一樣的,請不要過於在意版的美醜,這部份可以再依個人需求調整即可。

:::
## 總結
這個範例產生的比較久,除了目前在研究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>