My problem arises in the fact that I want to make a panel like with the help of flask-admin, but for a user who logs into his personal account and there at least a panel with links is displayed with filling forms. So that when the user fills them, they are sent to the admin in the database! Is it possible? Can you advise something?

At the moment the code is like this:

import os from flask import Flask, render_template, url_for, redirect, request, session, flash, abort from flask_sqlalchemy import SQLAlchemy from flask_admin import helpers, expose from flask_admin import helpers as admin_helpers import flask_admin as admin import flask_login as login from datetime import datetime from flask_admin.contrib import sqla from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_user, \ login_required, logout_user, current_user from wtforms import form, StringField, PasswordField, BooleanField, validators, fields from flask_security.utils import encrypt_password from werkzeug.security import generate_password_hash, check_password_hash app = Flask(__name__) app.config['DATABASE_FILE'] = 'sample_db.sqlite' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE'] app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SECRET_KEY'] = 'thisissecret' db = SQLAlchemy(app) # Define models roles_users = db.Table( 'roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('role_id', db.Integer(), db.ForeignKey('role.id')) ) class Role(RoleMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255)) def __str__(self): return self.name class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) login = db.Column(db.String(255)) email = db.Column(db.String(50), unique=True) password = db.Column(db.String(100)) active = db.Column(db.Boolean()) registered_on = db.Column('registered_on', db.DateTime) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) # Flask-Login integration def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return self.id # Required for administrative interface def __unicode__(self): return self.login def __repr__(self): return '<User %r>' % self.login def __str__(self): return self.email # Setup Flask-Security user_datastore = SQLAlchemyUserDatastore(db, User, Role) security = Security(app, user_datastore) # Define login and registration forms (for flask-login) class LoginForm(form.Form): user_message = 'Mailing address should be no more than 60 characters' user_required = 'Please enter your login.' pass_message = 'Password should be no more than 50 characters' pass_required = 'Please enter your password.' login = fields.StringField('Username', validators=[validators.Length(message=user_message, max=60), validators.required(user_required)]) password = fields.PasswordField('Password', validators=[validators.Length(message=pass_message, max=50), validators.required(pass_required)]) def get_user(self): return db.session.query(User).filter_by(login=self.login.data).first() def validate_login(self, field): user = self.get_user() if user is None: raise validators.ValidationError('Invalid user') # we're comparing the plaintext pw with the the hash from the db if not check_password_hash(user.password, self.password.data): # to compare plain text passwords use # if user.password != self.password.data: raise validators.ValidationError('Invalid password') class RegistrationForm(form.Form): username_message = 'The name must be at least 4 letters and no more than 25 characters' username_required = 'Please enter your login.' email_address_message = 'Mailing address should be no more than 60 characters' email_address_required = 'Please enter your email address.' password_message = 'Passwords must match' login = fields.StringField('Username', validators=[validators.Length(message=username_message, min=4, max=25), validators.required(username_required)]) email = fields.StringField('Email', validators=[validators.Length(message=email_address_message, max=60), validators.required(email_address_required)]) password = fields.PasswordField('Password', validators=[validators.required(password_message), validators.EqualTo('confirm', message='Passwords must match')]) confirm = fields.PasswordField('Repeat Password') def validate_login(self, field): if db.session.query(User).filter_by(login=self.login.data).count() > 0: raise validators.ValidationError('Duplicate username') if db.session.query(User).filter_by(email=self.email.data).count() > 0: raise validators.ValidationError('Duplicate email') # Initialize flask-login def init_login(): login_manager = login.LoginManager() login_manager.init_app(app) # Create user loader function @login_manager.user_loader def load_user(user_id): return db.session.query(User).get(user_id) # Create customized model view class class MyModelView(sqla.ModelView): def is_accessible(self): if not current_user.is_active or not current_user.is_authenticated: return False if current_user.has_role('superuser'): return True return False def _handle_view(self, name, **kwargs): """ Override builtin _handle_view in order to redirect users when a view is not accessible. """ if not self.is_accessible(): if current_user.is_authenticated: # permission denied abort(403) else: # login return redirect(url_for('admin.login_view', next=request.url)) # Create customized index view class that handles login & registration class MyAdminIndexView(admin.AdminIndexView): @expose('/') def index(self): if not login.current_user.is_authenticated: return redirect(url_for('.login_view')) return super(MyAdminIndexView, self).index() @expose('/login/', methods=['GET', 'POST']) def login_view(self): # handle user login form = LoginForm(request.form) if helpers.validate_form_on_submit(form): user = form.get_user() login.login_user(user) if login.current_user.is_authenticated: return redirect(url_for('.index')) link = '<p>Don\'t have an account? <a href="' + url_for('.register_view') + '">Click here to register.</a></p>' self._template_args['form'] = form self._template_args['link'] = link return super(MyAdminIndexView, self).index() @expose('/register/', methods=['GET', 'POST']) def register_view(self): form = RegistrationForm(request.form) if helpers.validate_form_on_submit(form): user = User() form.populate_obj(user) # we hash the users password to avoid saving it as plaintext in the db, # remove to use plain text: user.password = generate_password_hash(form.password.data) db.session.add(user) db.session.commit() login.login_user(user) return redirect(url_for('.index')) link = '<p>Already have an account? <a href="' + url_for('.login_view') + '">Click here to log in.</a></p>' self._template_args['form'] = form self._template_args['link'] = link return super(MyAdminIndexView, self).index() @expose('/logout/') def logout_view(self): login.logout_user() return redirect(url_for('.index')) # Flask views @app.route('/') def index(): return render_template('index.html') # Initialize flask-login init_login() # Create admin admin = admin.Admin(app, index_view=MyAdminIndexView(), base_template='my_master.html') # Add view admin.add_view(MyModelView(User, db.session)) admin.add_view(MyModelView(Role, db.session)) # define a context processor for merging flask-admin's template context into the # flask-security views. @security.context_processor def security_context_processor(): return dict( admin_base_template=admin.base_template, admin_view=admin.index_view, h=admin_helpers, get_url=url_for ) def build_sample_db(): """ Populate a small db with some example entries. """ db.create_all() with app.app_context(): user_role = Role(name='user') super_user_role = Role(name='superuser') db.session.add(user_role) db.session.add(super_user_role) db.session.commit() test_user = user_datastore.create_user( login='Admin', email='admin', password=generate_password_hash('admin'), roles=[user_role, super_user_role] ) db.session.add(test_user) db.session.commit() return if __name__ == '__main__': # Build a sample db on the fly, if one does not exist yet. app_dir = os.path.realpath(os.path.dirname(__file__)) database_path = os.path.join(app_dir, app.config['DATABASE_FILE']) if not os.path.exists(database_path): build_sample_db() # Start app app.run(debug=True) 
  • About sending forms, saving in the database and templates - all this is in the relevant documentation, what exactly is the problem? - andreymal
  • prntscr.com/efh0sz here is the admin panel and I want the panel to be in the user prntscr.com/efh1n8 but those tables that are in the admin panel were not used by the user panel (then write a form that will pop up in the database will see who has sent this form to fill out) Something you can advise, some interesting information! Sorry, maybe I'm not explaining something wrong - Dmitriy

1 answer 1

First you need to differentiate access to the system.

And you already have a part of the code responsible for this, namely:

 class MyModelView(sqla.ModelView): def is_accessible(self): if not current_user.is_active or not current_user.is_authenticated: return False if current_user.has_role('superuser'): return True return False def _handle_view(self, name, **kwargs): """ Override builtin _handle_view in order to redirect users when a view is not accessible. """ if not self.is_accessible(): if current_user.is_authenticated: # permission denied abort(403) else: # login return redirect(url_for('admin.login_view', next=request.url)) 

Users who have the superuser role will access the views you have registered that are inherited from MyAdminIndexView. Create, conditionally, MyMasterIndexView, where you will check for another role.

But in this case, for each role you will have your blueprint and your routes, which is not always good.

There is an implementation that I spied on Quokka CMS

Create a Roled mixin this kind:

 class Roled(object): def is_accessible(self): roles_accepted = getattr(self, 'roles_accepted', None) return is_accessible(roles_accepted=roles_accepted, user=current_user) def _handle_view(self, *args, **kwargs): if not current_user.is_authenticated: return redirect(url_for_security('login', next="/admin")) if not self.is_accessible(): return self.render("admin/denied.html") 

After that, create a class similar to MyModelView proposed above, inheriting sqla ModelView and Roled mixin :

 class AdminView(Roled, ModelView): def __init__(self, *args, **kwargs): self.roles_accepted = kwargs.pop('roles_accepted', list()) super(AdminView, self).__init__(*args, **kwargs) 

Then, inherit your models from this class:

 class UserView(AdminView): form_excluded_columns = ('password') class RoleView(AdminView): pass class PostView(AdminView): pass 

And at the very end, register your views like this:

 admin.add_view(UserView(model=User, session=db.session, category='Аккаунт', name='Пользователи', roles_accepted=['admin'])) admin.add_view(RoleView(model=Role, session=db.session, category='Аккаунт', name='Роли', roles_accepted=['admin'])) admin.add_view(PostView(model=Post, session=db.session, category='Блог', name='Посты (Только для авторов)', roles_accepted=['editor'])) admin.add_view(PostView(model=Post, session=db.session, category='Блог', name='Посты (Админы и Авторы)', endpoint="post_special", roles_accepted=['editor', 'admin'])) 

Easy to scale, checking multiple roles, endpoint added only where there is more than one view per model.

  • def is_accessible (self): roles_accepted = getattr (self, 'roles_accepted', None) return is_accessible (roles_accepted = roles_accepted, user = current_user) Here's this code I can not understand why? - Dmitriy
  • In fact, it supports the verification of several roles, in contrast to the standard method proposed in the first example. When registering a new view, it is possible to transfer roles_accepted = ['editor', 'admin'] - Narnik Gamarnik
  • github.com/quokkaproject/quokka/blob ... ... Here is the implementation of a similar method. - Narnik Gamarnik