Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save merlineus/1c63329568b5f02646d0eec75cc96265 to your computer and use it in GitHub Desktop.
Save merlineus/1c63329568b5f02646d0eec75cc96265 to your computer and use it in GitHub Desktop.
Flask-Security and Flask-Admin example by Steve Saporata
# Example of combining Flask-Security and Flask-Admin.
# by Steve Saporta
# April 15, 2014
#
# Uses Flask-Security to control access to the application, with "admin" and "end-user" roles.
# Uses Flask-Admin to provide an admin UI for the lists of users and roles.
# SQLAlchemy ORM, Flask-Mail and WTForms are used in supporting roles, as well.
from flask import Flask, render_template
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import current_user, login_required, RoleMixin, Security, \
SQLAlchemyUserDatastore, UserMixin, utils
from flask_mail import Mail
from flask.ext.admin import Admin
from flask.ext.admin.contrib import sqla
from wtforms.fields import PasswordField
# Initialize Flask and set some config values
app = Flask(__name__)
app.config['DEBUG']=True
# Replace this with your own secret key
app.config['SECRET_KEY'] = 'super-secret'
# The database must exist (although it's fine if it's empty) before you attempt to access any page of the app
# in your browser.
# I used a PostgreSQL database, but you could use another type of database, including an in-memory SQLite database.
# You'll need to connect as a user with sufficient privileges to create tables and read and write to them.
# Replace this with your own database connection string.
#xxxxx
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:xxxxxxxx@localhost/flask_example'
# Set config values for Flask-Security.
# We're using PBKDF2 with salt.
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
# Replace this with your own salt.
app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
# Flask-Security optionally sends email notification to users upon registration, password reset, etc.
# It uses Flask-Mail behind the scenes.
# Set mail-related config values.
# Replace this with your own "from" address
app.config['SECURITY_EMAIL_SENDER'] = 'no-reply@example.com'
# Replace the next five lines with your own SMTP server settings
app.config['MAIL_SERVER'] = 'email-smtp.us-west-2.amazonaws.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'xxxxxxxxxxxxxxxxxxxx'
app.config['MAIL_PASSWORD'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
# Initialize Flask-Mail and SQLAlchemy
mail = Mail(app)
db = SQLAlchemy(app)
# Create a table to support a many-to-many relationship between Users and Roles
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'))
)
# Role class
class Role(db.Model, RoleMixin):
# Our Role has three fields, ID, name and description
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
# __str__ is required by Flask-Admin, so we can have human-readable values for the Role when editing a User.
# If we were using Python 2.7, this would be __unicode__ instead.
def __str__(self):
return self.name
# __hash__ is required to avoid the exception TypeError: unhashable type: 'Role' when saving a User
def __hash__(self):
return hash(self.name)
# User class
class User(db.Model, UserMixin):
# Our User has six fields: ID, email, password, active, confirmed_at and roles. The roles field represents a
# many-to-many relationship using the roles_users table. Each user may have no role, one role, or multiple roles.
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('users', lazy='dynamic')
)
# Initialize the SQLAlchemy data store and Flask-Security.
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Executes before the first request is processed.
@app.before_first_request
def before_first_request():
# Create any database tables that don't exist yet.
db.create_all()
# Create the Roles "admin" and "end-user" -- unless they already exist
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='end-user', description='End user')
# Create two Users for testing purposes -- unless they already exists.
# In each case, use Flask-Security utility function to encrypt the password.
encrypted_password = utils.encrypt_password('password')
if not user_datastore.get_user('someone@example.com'):
user_datastore.create_user(email='someone@example.com', password=encrypted_password)
if not user_datastore.get_user('admin@example.com'):
user_datastore.create_user(email='admin@example.com', password=encrypted_password)
# Commit any database changes; the User and Roles must exist before we can add a Role to the User
db.session.commit()
# Give one User has the "end-user" role, while the other has the "admin" role. (This will have no effect if the
# Users already have these Roles.) Again, commit any database changes.
user_datastore.add_role_to_user('someone@example.com', 'end-user')
user_datastore.add_role_to_user('admin@example.com', 'admin')
db.session.commit()
# Displays the home page.
@app.route('/')
# Users must be authenticated to view the home page, but they don't have to have any particular role.
# Flask-Security will display a login form if the user isn't already authenticated.
@login_required
def index():
return render_template('index.html')
# Customized User model for SQL-Admin
class UserAdmin(sqla.ModelView):
# Don't display the password on the list of Users
column_exclude_list = list = ('password',)
# Don't include the standard password field when creating or editing a User (but see below)
form_excluded_columns = ('password',)
# Automatically display human-readable names for the current and available Roles when creating or editing a User
column_auto_select_related = True
# Prevent administration of Users unless the currently logged-in user has the "admin" role
def is_accessible(self):
return current_user.has_role('admin')
# On the form for creating or editing a User, don't display a field corresponding to the model's password field.
# There are two reasons for this. First, we want to encrypt the password before storing in the database. Second,
# we want to use a password field (with the input masked) rather than a regular text field.
def scaffold_form(self):
# Start with the standard form as provided by Flask-Admin. We've already told Flask-Admin to exclude the
# password field from this form.
form_class = super(UserAdmin, self).scaffold_form()
# Add a password field, naming it "password2" and labeling it "New Password".
form_class.password2 = PasswordField('New Password')
return form_class
# This callback executes when the user saves changes to a newly-created or edited User -- before the changes are
# committed to the database.
def on_model_change(self, form, model, is_created):
# If the password field isn't blank...
if len(model.password2):
# ... then encrypt the new password prior to storing it in the database. If the password field is blank,
# the existing password in the database will be retained.
model.password = utils.encrypt_password(model.password2)
# Customized Role model for SQL-Admin
class RoleAdmin(sqla.ModelView):
# Prevent administration of Roles unless the currently logged-in user has the "admin" role
def is_accessible(self):
return current_user.has_role('admin')
# Initialize Flask-Admin
admin = Admin(app)
# Add Flask-Admin views for Users and Roles
admin.add_view(UserAdmin(User, db.session))
admin.add_view(RoleAdmin(Role, db.session))
# If running locally, listen on all IP addresses, port 8080
if __name__ == '__main__':
app.run(
host='0.0.0.0',
port=int('8080'),
debug=app.config['DEBUG']
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment