更多创客作品,请关注笔者网站园丁鸟,搜集全球极具创意,且有价值的创客作品
ROS机器人知识请关注,diegorobot
业余时间完成的一款在线统计过程分析工具SPC,及SPC知识分享网站qdo
对于多租户的SAAS系统,所有的操作都是以组织为单位的,所以相对于传统的单用户系统的用户权限管理,增加了一层组织的维度,一个注册企业下,又可以有完整的用户权限管理系统。
如下是用权限系统的关系图:
组织在SAAS系统中的一切资源的最高阶组织形式,所以其他的对象都应该有一个组织的属性,对于用户也是如从,应该属于某个组织,组织与用户的关系应该是一对多的关系,如下是组织的Model对象。
class Organization(db.Model):
"""
Create a Organization table
"""
__tablename__ = 'organizations'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
key=db.Column(db.String(64), unique=True)
country = db.Column(db.String(64))
state = db.Column(db.String(64))
city = db.Column(db.String(64))
address = db.Column(db.String(64))
status = db.Column(db.Integer)#0:disable,1:enable,2:temp for first register
description = db.Column(db.String(200))
users = db.relationship('User', backref='Organization',
lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)
用户用户可以登录到系统进行相关功能的操作,每个用户都属于某个组织,可以根据权限操作此组织下的资源,数据。每个用户都有一组角色信息,根据角色来判断其权限,如下是用户的model,由于后续将使用Flask-login进行用户登录注册的管理,所以User类继承自UserMixin,在其中扩展了Organization_id与组织相关联,其他的属性可以根据需求进行扩展。
class User(UserMixin, db.Model):
"""
Create an User table
"""
# Ensures table will be named in plural and not in singular
# as is the name of the model
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(60), index=True, unique=True)
username = db.Column(db.String(60), index=True)
first_name = db.Column(db.String(60), index=True)
last_name = db.Column(db.String(60), index=True)
mobilephone = db.Column(db.String(20),index=True)
password_hash = db.Column(db.String(128))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'))
status = db.Column(db.Integer)#0:disable,1:enable
avatar = db.Column(db.String(60))# avatar pic name
roles_user = db.relationship('Role_User', backref='User',
lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)
@property
def password(self):
"""
Prevent pasword from being accessed
"""
raise AttributeError(_('password is not a readable attribute.'))
@password.setter
def password(self, password):
"""
Set password to a hashed password
"""
self.password_hash = generate_password_hash(password)
def check_password_hash(self, password):
return check_password_hash(self.password_hash,password)
def verify_password(self, password):
"""
Check if hashed password matches actual password
"""
return check_password_hash(self.password_hash, password)
def has_permission(self,permission):
"""
Check if hashed the permission
"""
for ru in self.roles_user:
role=Role.query.filter(Role.id==ru.role_id).first()
if role.name==permission:
return True
return False
has_permission函数根据权限名称来检索此用户是否有对应的角色权限
角色:每个角色表示一组操作的权限,可以操作系统相应的资源数据,用户与角色是多对多的关系,如下是角色的model,与用户类对象通过Role_User表进行关联。
class Role(db.Model):
"""
Create a Role table
"""
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
status = db.Column(db.Integer)#0:disable,1:enable
description = db.Column(db.String(100))
caption = db.Column(db.String(60))
users = db.relationship('Role_User', backref='Role',
lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)
def __repr__(self):
return ''.format(self.name)
class Role_User(db.Model):
"""
Create a Role_User table
"""
__tablename__ = 'role_users'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return ''.format(self.name)
对用户的操作,我们主要有如下三个操作,每个操作都对应相应的form。
class LoginForm(FlaskForm):
"""
Form for users to login
"""
email = StringField('Email Address', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(message= _('the password can not be null.'))])
remember_me = BooleanField('Remember me')
submit = SubmitField('Sign In')
def validate_email(self, field):
if not User.query.filter_by(email=field.data).first():
raise ValidationError(_('Invalid email.'))
class RegistrationForm(FlaskForm):
"""
Form for users to create new account
"""
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(message= _('the password can not be null.'))])
password_again = PasswordField('Password again', validators=[DataRequired(message= _('the password again can not be null.'))])
agree_policy = BooleanField('Agree Policy')
submit = SubmitField('Register')
def validate_email(self, field):
if User.query.filter_by(email=field.data).first():
raise ValidationError(_('Email is already in use.'))
def validate_agree_policy(self, field):
if not field.data:
raise ValidationError('you must agree the policy.')
def validate_password_again(self, field):
if field.data!=self._fields['password'].data:
raise ValidationError('Inconsistent password twice')
class UserForm(FlaskForm):
"""
Form for edit user
"""
user_id = IntegerField('id')
email = StringField('email', validators=[DataRequired()])
username = StringField('username', validators=[DataRequired()])
mobilephone = StringField('mobile phone')
password = PasswordField('password')
is_admin = StringField('is admin')
is_resource_admin = StringField('is resource admin')
is_generic_user = StringField('is generic user')
submit = SubmitField('Submit')
def validate_email(self, field):
if self._fields['user_id'].data == -1:
if User.query.filter(User.email==field.data).first():
raise ValidationError(_('The user email is already in use.'))
else:
if User.query.filter(User.email==field.data,User.id!=self._fields['user_id'].data).first():
raise ValidationError(_('The user email is already in use.'))
def validate_mobilephone(self, field):
if self._fields['user_id'].data == -1:
if User.query.filter(User.mobilephone==field.data,User.organization_id==current_user.organization_id).first():
raise ValidationError(_('The user mobilephone is already in use.'))
else:
if User.query.filter(User.mobilephone==field.data,User.organization_id==current_user.organization_id,User.id!=self._fields['user_id'].data).first():
raise ValidationError(_('The user mobilephone is already in use.'))
def validate_password(self, field):
if self._fields['user_id'].data == -1:
if field.data is None:
raise ValidationError(_('the password can not be null.'))
def validate_is_admin(self, field):
if self._fields['user_id'].data == -1:
if field.data=='is_admin':
licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='admin_numbers').first()
quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
if quantity>=licenses.quantity:
raise ValidationError(_('the numbers of admin you have created has more than the numbers of your license.'))
def validate_is_resource_admin(self, field):
if self._fields['user_id'].data == -1:
if field.data=='is_resource_admin':
licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='resource_admin_numbers').first()
quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
if quantity>=licenses.quantity:
raise ValidationError(_('the numbers of resource admin you have created has more than the numbers of your license.'))
def validate_is_generic_user(self, field):
if self._fields['user_id'].data == -1:
if field.data=='is_generic_user':
licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='generic_user_numbers').first()
quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
if quantity>=licenses.quantity:
raise ValidationError(_('the numbers of generic user you have created has more than the numbers of your license.'))
前端页面都使用bootstrap进行设计,对应数据格式的常规验证都在前端进行,都是基本的h5页面,这里只贴代码
登录页面
{{_('Do not have an account?')}} {{_('Sign Up')}}
注册页面
{{_('Already have account?')}} {{_('Log In')}}
新建/编辑用户
{% extends "layout.html" %}
{% block content %}
{{_('New User')}}
{{_('Info')}}
{% endblock %}
注册登录都使用Flask-login,只是在注册页面,增加了为新用户新建一个临时的组织信息。
@auth.route('/register', methods=['GET', 'POST'])
def register():
"""
Handle requests to the /register route
Add an user to the database through the registration form
"""
form = RegistrationForm()
if form.validate_on_submit():
new_organization=Organization();
new_organization.name=form.email.data
new_organization.description=form.email.data
new_organization.country="China"
new_organization.state="ShangHai"
new_organization.city="ShangHai"
new_organization.address="ShangHai"
new_organization.status=2#temp status
new_organization.key=str(uuid.uuid4()).upper().replace('-','')
db.session.add(new_organization)
db.session.flush()
user = User(email=form.email.data,username=form.email.data,password=form.password.data,organization_id=new_organization.id,status=1)
db.session.add(user)
db.session.flush()
role = Role.query.filter(Role.name=='admin').first()
user_role=Role_User(user_id=user.id,role_id=role.id)
db.session.add(user_role)
db.session.commit()
flash(_('You have successfully registered! You may now login.'))
# redirect to the login page
return redirect(url_for('auth.login'))
# load registration template
return render_template('register.html', form=form, title='Register')
新建用户
新建用户操作是由组织管理员完成,需要添加用户信息,同时为用户分配的角色,需要操作user,role_user两张表
@auth.route('/users/add', methods=['GET', 'POST'])
@login_required
def user_add():
"""
Add a users to the database
"""
form = UserForm()
if form.validate_on_submit():
user = User(email=form.email.data,username=form.username.data,password=form.password.data,
mobilephone=form.mobilephone.data,
organization_id=current_user.organization_id)
try:
# add user to the database
db.session.add(user)
db.session.flush()
if form.is_admin.data=='is_admin':
admin_role = Role.query.filter(Role.name=='admin').first()
user_role=Role_User(user_id=user.id,role_id=admin_role.id)
db.session.add(user_role)
if form.is_resource_admin.data=='is_resource_admin':
resource_admin_role = Role.query.filter(Role.name=='resource_admin').first()
user_role=Role_User(user_id=user.id,role_id=resource_admin_role.id)
db.session.add(user_role)
if form.is_generic_user.data=='is_generic_user':
generic_user_role = Role.query.filter(Role.name=='generic_user').first()
user_role=Role_User(user_id=user.id,role_id=generic_user_role.id)
db.session.add(user_role)
db.session.commit()
flash(_('You have successfully added a new user.'))
except Exception as e:
# in case user name already exists
db.session.rollback()
flash(_('User name already exists.'))
current_app.logger.exception(e)
# redirect to the user page
return redirect(url_for('auth.users_list'))
# load user template
return render_template('user_add.html',c_user=current_user,
form=form, title='Add user')