我正在使用Flask和sqlite以及SQLAlchemy为中型组织制作简单的票务系统。对于数据的后端管理,我使用Flask Admin。
用户和票证表如下所示:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
role = db.Column(db.Integer, default=0)
vmc_kom = db.Column(db.String(20))
name = db.Column(db.String(30), nullable=False)
phone = db.Column(db.String, default="not")
email = db.Column(db.String(40), nullable=False)
password = db.Column(db.String(60), nullable=False)
tickets = db.relationship('Ticket', cascade="all,delete", backref='author', lazy=True)
def __repr__(self):
return f"('{self.name}')"
class Ticket(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
povod_vmc_kom = db.Column(db.String(20))
osoba = db.Column(db.String(20), default="XYZ")
dateVMC = db.Column(db.Date, nullable=False)
deadline = db.Column(db.Date, nullable=False)
is_finished = db.Column(db.Boolean, default = False)
images = db.relationship('Image_ticket', cascade="all,delete", backref='home_ticket', lazy=True)
solution = db.Column(db.Text)
date_solution = db.Column(db.DateTime)
zodpovedni = db.relationship("Zodpovedny", secondary="ticketutvary")
sprava = db.Column(db.String(100))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Ticket('{self.id}', '{self.title}', '{self.dateVMC}')"
我能够根据is_accesible
方法中设置的User.role设置创建、编辑或删除Tickets的权限。
class TicketModelView(ModelView):
column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']
def is_accessible(self):
if current_user.is_authenticated and current_user.role == 0:
self.can_export=True
self.can_delete = False
self.can_edit = False
self.can_create = False
return True
if current_user.is_authenticated and current_user.role == 1:
self.can_export=True
self.can_delete=True
return True
if current_user.is_authenticated and current_user.role == 2:
self.can_delete = False
self.can_export=True
return True
if current_user.is_authenticated and current_user.role == 3:
self.can_delete = False
self.can_export=True
return True
return False
但我一直在努力为特定用户设置form_edit_rules
。例如,我想允许角色为==2的用户只编辑Ticket中的两列。当我把form_edit_rules直接放在ModelView类中时,它对所有人都有效。我也试过这个:
class TicketModelView(ModelView):
column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']
def is_accessible(self):
if current_user.is_authenticated and current_user.role == 2:
self.can_export=True
self.can_delete = False
self.can_edit = False
self.can_create = False
self.form_edit_rules = ('zodpovedni','dateVMC')
return True
但没有成功。
谁能把我推向正确的方向吗?我有什么东西不见了吗?是否使用了一些非常糟糕的做法?
提前谢谢。
form_edit_rules
已在调用方法is_accessible
时缓存。如果更新规则,则刷新缓存:
class TicketModelView(ModelView):
column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']
def is_accessible(self):
if current_user.is_authenticated and current_user.role == 2:
self.can_export=True
self.can_delete = False
self.can_edit = False
self.can_create = False
self.form_edit_rules = ('zodpovedni','dateVMC')
# Refresh form rules cache
self._refresh_form_rules_cache()
return True
return False
也可以在运行时设置form_edit_rules
,而不必使规则缓存无效。我使用了这个SO Q/A烧瓶管理员:如何允许只有超级用户才能查看指定的表列?作为以下内容的基础。如果用户已登录并且具有角色'admin'
,则他们可以查看并使用'active'
字段。
class AuthorView(sqla.ModelView):
column_default_sort = ('last_name', False)
column_searchable_list = ('first_name', 'last_name')
@property
def _form_edit_rules(self):
return rules.RuleSet(self, self.form_edit_rules)
@_form_edit_rules.setter
def _form_edit_rules(self, value):
pass
@property
def form_edit_rules(self):
if not has_app_context() or current_user.has_role('admin'):
return ('first_name', 'last_name', rules.Text(f'Authenticated User has Admin role'), 'active')
return ('first_name', 'last_name', rules.Text('Not Authenticated and/or not Admin role'))
完整的单文件Python 3示例如下。
requirements.txt
Babel==2.8.0
blinker==1.4
click==7.1.2
dnspython==2.0.0
email-validator==1.1.1
Faker==4.1.1
Flask==1.1.2
Flask-Admin==1.5.6
Flask-BabelEx==0.9.4
Flask-Login==0.5.0
Flask-Mail==0.9.1
Flask-Principal==0.4.0
Flask-Security==3.0.0
Flask-SQLAlchemy==2.4.4
Flask-WTF==0.14.3
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
passlib==1.7.2
python-dateutil==2.8.1
pytz==2020.1
six==1.15.0
speaklater==1.3
SQLAlchemy==1.3.18
text-unidecode==1.3
Werkzeug==1.0.1
WTForms==2.3.3
应用程序
from datetime import datetime
from faker import Faker
import click
from flask import Flask, has_app_context, current_app
from flask_admin.form import rules
from flask_login import login_user, logout_user
from flask_security import UserMixin, RoleMixin, current_user, SQLAlchemyUserDatastore, Security
from flask_security.utils import hash_password
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib import sqla
db = SQLAlchemy()
user_to_role = db.Table('user_to_role',
db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Unicode(length=255), nullable=False)
last_name = db.Column(db.Unicode(length=255), nullable=False, index=True)
# Identification Data: email & password
email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
password = db.Column(db.Unicode(length=255), nullable=False)
active = db.Column(db.Boolean(), default=False)
roles = db.relationship('Role', secondary=user_to_role, backref=db.backref('users', lazy='select'))
class Role(db.Model, RoleMixin):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(length=64), unique=True)
description = db.Column(db.Unicode(length=255), nullable=True)
def __str__(self):
return self.name
class Author(db.Model):
__tablename__ = 'authors'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Text(length=255), nullable=False)
last_name = db.Column(db.Text(length=255), nullable=False)
active = db.Column(db.Boolean(), default=False)
def __str__(self):
return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample.sqlite'
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_PASSWORD_SALT'] = 'c1b4797ffb4783bb4aed7e14a1494a01390eacf94ee324b9'
db.init_app(app)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
@app.cli.command('create-database', short_help='Create sample database')
@click.option('--count', default=100, help='Number of authors (default 100)')
def create_database(count):
"""
Create database
"""
db.drop_all()
db.create_all()
_faker = Faker()
security = current_app.extensions.get('security')
_admin_role = security.datastore.find_or_create_role(name="admin", description='Administers the system')
_user_role = security.datastore.find_or_create_role(name="user", description='Uses the system')
users = [
{'email': 'paul@example.net', 'first_name': 'Paul', 'last_name': 'Cunningham', 'password': hash_password('pa$$word'), 'role': _user_role},
{'email': 'jane@example.net', 'first_name': 'Jane', 'last_name': 'Smith', 'password': hash_password('pa$$word'), 'role': _admin_role},
]
for user in users:
_role = user.pop('role')
_user_db = security.datastore.create_user(**user)
if _role:
security.datastore.add_role_to_user(_user_db, _role)
security.datastore.activate_user(_user_db)
_user_db.confirmed_at = datetime.utcnow()
security.datastore.commit()
for _ in range(0, count):
_author = Author(
first_name=_faker.first_name(),
last_name=_faker.last_name(),
active=_faker.boolean()
)
db.session.add(_author)
db.session.commit()
class AuthorView(sqla.ModelView):
column_default_sort = ('last_name', False)
column_searchable_list = ('first_name', 'last_name')
@property
def _form_edit_rules(self):
return rules.RuleSet(self, self.form_edit_rules)
@_form_edit_rules.setter
def _form_edit_rules(self, value):
pass
@property
def form_edit_rules(self):
if not has_app_context() or current_user.has_role('admin'):
return ('first_name', 'last_name', rules.Text(f'Authenticated User has Admin role'), 'active')
return ('first_name', 'last_name', rules.Text('Not Authenticated and/or not Admin role'))
# Flask views
@app.route('/')
def index():
_html = [
'<a href="/impersonate-paul">Click me to get to impersonate Paul (user)!</a>',
'<a href="/impersonate-jane">Click me to get to impersonate Jane (admin)!</a>'
]
return '<br>'.join(_html)
@app.route('/impersonate-paul')
def impersonate_paul():
_impersonate_user = User.query.filter(User.email == 'paul@example.net').first()
logout_user()
login_user(_impersonate_user)
return '<a href="/admin/">Click me to get to Admin logged in as Paul (user)!</a>'
@app.route('/impersonate-jane')
def impersonate_jane():
_impersonate_user = User.query.filter(User.email == 'jane@example.net').first()
logout_user()
login_user(_impersonate_user)
return '<a href="/admin/">Click me to get to Admin logged in as Jane (admin)!</a>'
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(AuthorView(Author, db.session))
if __name__ == '__main__':
app.run()
运行以下命令初始化SQLite数据库。
flask create-database --count 100