Skip to content

Instantly share code, notes, and snippets.

@skyuplam
Created October 13, 2014 07:50
Show Gist options
  • Save skyuplam/ffb1b5f12d7ad787f6e4 to your computer and use it in GitHub Desktop.
Save skyuplam/ffb1b5f12d7ad787f6e4 to your computer and use it in GitHub Desktop.

Revisions

  1. Terrence Lam created this gist Oct 13, 2014.
    202 changes: 202 additions & 0 deletions gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,202 @@
    # 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'] = '[email protected]'
    # 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('[email protected]'):
    user_datastore.create_user(email='[email protected]', password=encrypted_password)
    if not user_datastore.get_user('[email protected]'):
    user_datastore.create_user(email='[email protected]', 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('[email protected]', 'end-user')
    user_datastore.add_role_to_user('[email protected]', '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']
    )