Flask邀请制用户管理

相比于用户注册管理员审核的机制,由管理员直接增加用户的“邀请制”显然更加简单,在小范围的使用中也比较方便。邀请制最大的问题是,至少得有一个管理员。服务初始化添加管理员后,如何保证不掉进没有管理员的尴尬坑呢?

修改用户权限时检查

如果系统中只有一个管理员,而这个管理员又在尝试降低自己的权限,这是绝对不能允许的,完成交班之前哪儿也不能去。以下是参考代码:

class EditUserForm(Form):
    email = StringField(u'邮箱', validators=[Required(), Length(1, 64), Email()])
    nickname = StringField(u'昵称', validators=[Required(), Length(1, 64)])
    roleid = SelectField(u'权限', coerce=int)
    password = PasswordField(u'密码', filters=[lambda x: x or None])
    submit = SubmitField(u'更新用户')

    def __init__(self, original_nickname, *args, **kwargs):
        Form.__init__(self, *args, **kwargs)
        self.original_nickname = original_nickname

    def validate(self):
        if not Form.validate(self):
            return False
        if User.query.filter_by(email=self.email.data).first().role.name == 'Administrator':
            adminid = Role.query.filter_by(name='Administrator').first().id
            if self.roleid.data != adminid:
                if len(User.query.filter_by(role_id=adminid).all()) < 2:
                    self.roleid.errors.append(u'至少保留一个Administrator')
                    return False
        if self.nickname.data == self.original_nickname:
            return True
        user = User.query.filter_by(nickname=self.nickname.data).first()
        if user != None:
            self.nickname.errors.append(u'昵称已存在,请重新选择')
            return False
        return True

不允许删除管理员账户

“先打倒再干掉”是一个比较符合逻辑的过程,如果不允许直接删除管理员账户,结合上面的修改检查,就可以保证系统一直至少有一个管理员存在了。以下是参考代码:

@manage.route('/del-user/<id>')
@login_required
@admin_required
def del_user(id):
    user = User.query.get(id)
    if user is not None:
        if user.role.name != 'Administrator':
            db.session.delete(user)
            db.session.commit()
            flash(u'成功删除用户')
        else:
            flash(u'无法删除管理员账户,请降权后删除')
            return redirect(url_for('.index'))
    else:
        abort(404)
    return redirect(url_for('.index'))

单元测试

测试自己程序的逻辑还是很有必要的,以下是两个测试用例:

def test_edit_user(self):
    self.add_user('foo@example.com', 'Administrator')
    self.add_user('bar@example.com', 'User')
    self.login_user('foo@example.com', 'foo')
    rid = Role.query.filter_by(name='Moderator').first().id
    # 拒绝无效的用户id
    data={'0-email': 'bar@example.com',
            '0-nickname': 'foobar',
            '0-roleid': rid,
            '0-password': None}
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'更新用户错误' in rv.data)
    # 至少存留一个管理员
    uid = str(User.query.filter_by(email='foo@example.com').first().id)
    data={uid+'-email': 'foo@example.com',
            uid+'-nickname': 'foo',
            uid+'-roleid': rid,
            uid+'-password': None}
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'至少保留一个Administrator' in rv.data)
    # 保持昵称唯一
    uid = str(User.query.filter_by(email='bar@example.com').first().id)
    rid = Role.query.filter_by(name='Administrator').first().id
    data={uid+'-email': 'bar@example.com',
            uid+'-nickname': 'foobar',
            uid+'-roleid': rid,
            uid+'-password': None}
    data.update({uid+'-nickname': 'foo'})
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'昵称已存在' in rv.data)
    # 拒绝无效的权限id
    data.update({uid+'-nickname': 'foobar', uid+'-roleid': -1})
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'Not a valid choice' in rv.data)
    # 密码字段提供与否都可以通过
    data.update({uid+'-roleid': rid})
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'成功更新用户' in rv.data)
    data.update({uid+'-password': 'foobar'})
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'成功更新用户' in rv.data)
    # bar以及是管理员了,现在foo应该可以给自己降权
    rid = Role.query.filter_by(name='Moderator').first().id
    uid = str(User.query.filter_by(email='foo@example.com').first().id)
    data={uid+'-email': 'foo@example.com',
            uid+'-nickname': 'foo',
            uid+'-roleid': rid,
            uid+'-password': None}
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'成功更新用户' in rv.data)
    rv = self.client.get(url_for('main.account'))
    self.assertTrue(b'用户角色: Moderator' in rv.data)
    self.client.get(url_for('main.logout'), follow_redirects=True)
    # bar可以使用其新密码登陆并具有管理员权限
    self.login_user('bar@example.com', 'foobar')
    rv = self.client.get(url_for('main.account'))
    self.assertTrue(b'用户角色: Administrator' in rv.data)

def test_del_user(self):
    self.add_user('foo@example.com', 'Administrator')
    self.add_user('bar@example.com', 'Administrator')
    self.login_user('foo@example.com', 'foo')
    # 无法直接删除管理员账户
    uid = User.query.filter_by(email='bar@example.com').first().id
    rv = self.client.get(url_for('manage.del_user', id=-1))
    self.assertEqual(rv.status_code, 404)
    rv = self.client.get(url_for('manage.del_user', id=uid), follow_redirects=True)
    self.assertTrue(b'无法删除管理员账户' in rv.data)
    # 降权后可删除
    rid = Role.query.filter_by(name='Moderator').first().id
    data={str(uid)+'-email': 'bar@example.com',
            str(uid)+'-nickname': 'bar',
            str(uid)+'-roleid': rid,
            str(uid)+'-password': None}
    rv = self.client.post(url_for('manage.index'), data=data, follow_redirects=True)
    self.assertTrue(b'成功更新用户' in rv.data)
    rv = self.client.get(url_for('manage.del_user', id=uid), follow_redirects=True)
    self.assertTrue(b'成功删除用户' in rv.data)
    self.assertFalse(b'bar@example.com' in rv.data)
Loading Disqus comments...
Table of Contents