继续上一章节,上一章节讲的是用户编辑自己页面的内容,内容也是相对简单,真实姓名,来自哪里,自我介绍的信息,而email和用户名什么的,你自己也没地方改,因为在注册的时候就登记了。
但是作为网站管理者,你肯定需要拥有权限可以修改用户的资料,甚至说是为他们修改email的地址,或者是用户名,还有权限
别看只是多做一个修改他人用户信息的表单,这个章节里面有几个知识点需要特别注意
进入正题,既然可以修改的内容多了,那么我们肯定要新建一个表单,作为ADMIN可以看到并修改的表单
class EditProfileAdminForm(Form): email = StringField('Email',validators=[Required(),Length(1,64),Email()]) username = StringField('Username',validators=[ Required(),Length(1,64),Regexp('^[A-Za-z][A-Za-z0-9_.]*$',0, 'numbers,dots or underscores')]) confirmed = BooleanField('Confirmed') role = SelectField('Role',coerce = int) #注意,这里的Field是Select,也就是下拉选择列表 name = StringField('Real Name',validators=[Length(0,64)]) location = StringField('Location',validators=[Length(0,64)]) about_me = TextAreaField('About me') submit = SubmitField('Submit') def __init__(self,user,*args,**kwargs): #这里一定要注意到,在生成表单的时候,是需要带着参数user的!!! super(EditProfileAdminForm,self).__init__(*args,**kwargs) self.role.choices = [(role.id,role.name) for role in Role.query.order_by(Role.name).all()] self.user = user #上面的role.choices属性,是针对的表单role的选项,后面单独写 def validate_email(self,field): if field.data != self.user.email and User.query.filter_by(email = field.data).first(): #检验email是否发生更改且是否重复 raise ValidationError('Email already registered.') def validate_username(self,field): #检验username是否发生更改且是否重复 if field.data != self.user.username and User.query.filter_by(username = field.data).first(): raise ValidationError('Username already in use')
先写简单的:
1:关于validate_email和validate_username,前面的章节里面我们已经提到过了,在表单的设置里面,如果设定的函数是以validate_开头的,那么,他会在验证的时候,和上面表单的普通验证一起使用。 那他是如何一一对应的呢?其实就是 validate_email ,下划线后面的内容,对应上面表单的名字,再具体一些,对应的是form.email,也就是Form实例对象的email属性。
所以field.data这句意思实际上是form.email.data != self.user.username 。
这句话什么意思呢?就是当你作为管理者在修改用户资料的时候,如果资料发生了变化,且这个变化后的email 在数据库中搜索出来已经被人使用了
则会报错:Email already registered.
同样适用于修改用户username的时候
因为,对于所有用户来说,username和email,都应该是唯一的。
----------------------------------------------------------------------这里插播Flask 源码----------------------------------------------------------------------------------
app\main\forms.py内部
from flask.ext.wtf import Form
flask.wtf\__init__.py通过form.py内导入Form
from .form import Form
而form.py内的Form定义
class Form(SecureForm):
而SecureForm则是通过下面这行导入的
from wtforms.ext.csrf.form import SecureForm
之后在wtforms\ext\csrf\form.py可以看到
class SecureForm(Form)
好,我们看到最终还是从wtforms的form里面导入的Form
from wtforms.form import Form
所以,最后我们在Form类里面找到了这个validate_的定义
----------------------------------------------------------------------------源码寻找结束-------------------------------------------------------------------------------
2:我们来将关于SelectField和choices的内容
class EditProfileAdminForm(Form): #.. role = SelectField('Role',coerce = int) #.. def __init__(self,user,*args,**kwargs): super(EditProfileAdminForm,self).__init__(*args,**kwargs) self.role.choices = [(role.id,role.name) for role in Role.query.order_by(Role.name).all()]
从官方文档我们可以看到,你有2种选择方式,一种是固态的设置选项,比如下面文档截图里面的choices,写在SelectField内部
第二种是动态选项,是在额外的函数内定义,通过语言,动态地和作为类属性的SelectField链接
回到书上的例子,他显然是使用的动态设置,在__init__初始化函数里面,进行了choices的设置
for role in Role.query.order_by(Role.name).all() 这句话,通过Role内部的名字进行排列,并全部迭代出来
在前面的章节我们已经知道,目前我们有3个拥有name属性的role,name属性分别是 ‘User‘ , ‘Moderator‘ , ‘ADMINISTER‘
所以,对应的(role.id,role.name)就有3个选项
# 留个位置,写value和label对应的意思
这里插一个后面完成后的效果图,就能完全明白value和label对应的意思了
我们可以看到,对应我们的例子,显示出来的标签,是role.name,也就是元祖中的第二个参数label
而他对应的内部逻辑(也就是书上说的标识符),实际上就是role.id,我们可以在下面第二张图里面看到数据库的形式
所以,你选择了不同的角色name名字,就等于赋予了不同的id
这里额外再重复一点,就是在建立models的时候,Role类里面有一个relationship,他的backref是"role",我自己理解,这个就是对应User 类创建实例时,User能够通过role属性,来访问Role类,并将Role类的类对象,作为本身User的一个属性来起作用,说白了点,就是,将Role类的所有属性打包进一个实例,赋值给User的role属性。
所以,在书本上的例子里,你选择了不同的role,就等于选择了不同的用户权限等级。
表单部分写完了,就要写视图部分函数了
@main.route('/edit-profile/<int:id>',methods = ['GET','POST']) @login_required @admin_required def edit_profile_admin(id): #注意:这里的函数带参数id的 user = User.query.get_or_404(id) form = EditProfileAdminForm(user = user) if form.validate_on_submit(): user.email = form.email.data user.username = form.username.data user.confirmed = form.confirmed.data user.role = Role.query.get(form.role.data) user.name = form.name.data user.location = form.location.data user.about_me = form.about_me.data db.session.add(user) db.session.commit() flash('The profile has been updated.') return redirect(url_for('.user',username = user.username)) form.email.data = user.email form.username.data = user.username form.confirmed.data = user.confirmed form.role.data = user.role_id #注意:这里设置初始值的时候,用的是role_id,而role_id是外键,对应roles.id,就是roles表的id form.name.data = user.name form.location.data = user.location form.about_me.data = user.about_me return render_template('edit_profile.html',form = form,user = user)
上面这里,form.role.data预设值的这一环节,使用的是role_id来进行设置,role_id对应的是该用户,在roles表中对应的id,也就是权限等级。
class User(UserMixin,db.Model): #... <span style="white-space:pre"> </span>role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) #这里的role_id,等于是roles表里的id
我们还需要再探讨一下用于选择用户角色的SelectField。设定这个字段的初始值时,role_id 被赋值给了field.role.data,这么做的原因在于choices 属性中设置的元组列表
使用数字标识符表示各选项。表单提交后,id 从字段的data 属性中提取,并且查询时会使用提取出来的id 值加载角色对象。表单中声明SelectField 时使用coerce=int 参数,
其作用是保证这个字段的data 属性值是整数。
这里很重要的一点,就是红色提示的部分,choices的属性,虽然他显示的选项是字符串的名字,比如"User" "ADMINISTER"这样的,但是,他实际上的逻辑数据是以id来控制的.
而需要注意的一个重点是:不要把form.role.data和user.role搞混淆!!
form.role.data 他对应的其实是数字
而user.role他对应的是一个类对象!所以在提交表单时,他是通过user.role = Role.query.get(form.role.data)来提取的。
逻辑部分讲完了,就轮到渲染的页面了
作为管理员,肯定是在要看到的页面上多一个按钮,可以进行管理员模式的编辑
所以,代码如下,有判断语句,当你是管理员的时候,才会显示那个按钮
{% if current_user.is_administrator() %} <a class="btn btn-danger" href="{{ url_for('.edit_profile_admin', id=user.id) }}"> Edit Profile [Admin]</a>
效果图如下
在关于这个views函数如何对id取值这个问题上,纠结了整整半天
因为不知道这个id的值是如何取到的,本来去templates/user里面找,发现里面的user.id是由user生成的,但是user是通过views函数导入的,然而,user却又是通过id在数据库内取值取到的,这不是死循环了么?
这里其实自己走进了一个误区,也是自己前文没有看仔细
当前页面,其实是建立在xxxxxx/user/AllenXu上面的,而不是xxxxxx/edit-profile上面的
什么意思呢?就是比如作为管理员的你,进入了普通用户Bob的user页面,那么url肯定是xxxxxx/user/Bob
那这时候的user是谁呢?这时候当前页面的user这个变量,其实就是Bob
所以,作为管理员来说,按个红色的编辑按钮,是建立在user这个页面上的!!!所以当前的user才能取得到值。
并非是我之前以为的建立在edit页面内的!!
下面是user的views函数的定义,我们可以看到传入模板的user,其实是通过username来过滤出来的,而username就是这个页面属于哪个用户的意思
所以,在user/<username>这个页面上的所有操作,user这个变量是属于<username>这个用户的,而不是你管理员!!!
current_user是管理员
user是该页面所代表的用户