Flask Web 开发 用户资料_3

Posted bestallen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask Web 开发 用户资料_3相关的知识,希望对你有一定的参考价值。

继续上一章节,上一章节讲的是用户编辑自己页面的内容,内容也是相对简单,真实姓名,来自哪里,自我介绍的信息,而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:我们来将关于SelectFieldchoices的内容

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是该页面所代表的用户