· 6 years ago · Aug 16, 2019, 03:48 AM
1# Example of combining Flask-Security and Flask-Admin.
2# by Steve Saporta
3# April 15, 2014
4#
5# Uses Flask-Security to control access to the application, with "admin" and "end-user" roles.
6# Uses Flask-Admin to provide an admin UI for the lists of users and roles.
7# SQLAlchemy ORM, Flask-Mail and WTForms are used in supporting roles, as well.
8
9from flask import Flask, render_template
10from flask.ext.sqlalchemy import SQLAlchemy
11from flask.ext.security import current_user, login_required, RoleMixin, Security, \
12 SQLAlchemyUserDatastore, UserMixin, utils
13from flask_mail import Mail
14from flask.ext.admin import Admin
15from flask.ext.admin.contrib import sqla
16
17from wtforms.fields import PasswordField
18
19# Initialize Flask and set some config values
20app = Flask(__name__)
21app.config['DEBUG']=True
22# Replace this with your own secret key
23app.config['SECRET_KEY'] = 'super-secret'
24# The database must exist (although it's fine if it's empty) before you attempt to access any page of the app
25# in your browser.
26# I used a PostgreSQL database, but you could use another type of database, including an in-memory SQLite database.
27# You'll need to connect as a user with sufficient privileges to create tables and read and write to them.
28# Replace this with your own database connection string.
29#xxxxx
30app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:xxxxxxxx@localhost/flask_example'
31
32# Set config values for Flask-Security.
33# We're using PBKDF2 with salt.
34app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
35# Replace this with your own salt.
36app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
37
38# Flask-Security optionally sends email notification to users upon registration, password reset, etc.
39# It uses Flask-Mail behind the scenes.
40# Set mail-related config values.
41# Replace this with your own "from" address
42app.config['SECURITY_EMAIL_SENDER'] = 'no-reply@example.com'
43# Replace the next five lines with your own SMTP server settings
44app.config['MAIL_SERVER'] = 'email-smtp.us-west-2.amazonaws.com'
45app.config['MAIL_PORT'] = 465
46app.config['MAIL_USE_SSL'] = True
47app.config['MAIL_USERNAME'] = 'xxxxxxxxxxxxxxxxxxxx'
48app.config['MAIL_PASSWORD'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
49
50# Initialize Flask-Mail and SQLAlchemy
51mail = Mail(app)
52db = SQLAlchemy(app)
53
54# Create a table to support a many-to-many relationship between Users and Roles
55roles_users = db.Table(
56 'roles_users',
57 db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
58 db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
59)
60
61
62# Role class
63class Role(db.Model, RoleMixin):
64
65 # Our Role has three fields, ID, name and description
66 id = db.Column(db.Integer(), primary_key=True)
67 name = db.Column(db.String(80), unique=True)
68 description = db.Column(db.String(255))
69
70 # __str__ is required by Flask-Admin, so we can have human-readable values for the Role when editing a User.
71 # If we were using Python 2.7, this would be __unicode__ instead.
72 def __str__(self):
73 return self.name
74
75 # __hash__ is required to avoid the exception TypeError: unhashable type: 'Role' when saving a User
76 def __hash__(self):
77 return hash(self.name)
78
79
80# User class
81class User(db.Model, UserMixin):
82
83 # Our User has six fields: ID, email, password, active, confirmed_at and roles. The roles field represents a
84 # many-to-many relationship using the roles_users table. Each user may have no role, one role, or multiple roles.
85 id = db.Column(db.Integer, primary_key=True)
86 email = db.Column(db.String(255), unique=True)
87 password = db.Column(db.String(255))
88 active = db.Column(db.Boolean())
89 confirmed_at = db.Column(db.DateTime())
90 roles = db.relationship(
91 'Role',
92 secondary=roles_users,
93 backref=db.backref('users', lazy='dynamic')
94 )
95
96
97# Initialize the SQLAlchemy data store and Flask-Security.
98user_datastore = SQLAlchemyUserDatastore(db, User, Role)
99security = Security(app, user_datastore)
100
101
102# Executes before the first request is processed.
103@app.before_first_request
104def before_first_request():
105
106 # Create any database tables that don't exist yet.
107 db.create_all()
108
109 # Create the Roles "admin" and "end-user" -- unless they already exist
110 user_datastore.find_or_create_role(name='admin', description='Administrator')
111 user_datastore.find_or_create_role(name='end-user', description='End user')
112
113 # Create two Users for testing purposes -- unless they already exists.
114 # In each case, use Flask-Security utility function to encrypt the password.
115 encrypted_password = utils.encrypt_password('password')
116 if not user_datastore.get_user('someone@example.com'):
117 user_datastore.create_user(email='someone@example.com', password=encrypted_password)
118 if not user_datastore.get_user('admin@example.com'):
119 user_datastore.create_user(email='admin@example.com', password=encrypted_password)
120
121 # Commit any database changes; the User and Roles must exist before we can add a Role to the User
122 db.session.commit()
123
124 # Give one User has the "end-user" role, while the other has the "admin" role. (This will have no effect if the
125 # Users already have these Roles.) Again, commit any database changes.
126 user_datastore.add_role_to_user('someone@example.com', 'end-user')
127 user_datastore.add_role_to_user('admin@example.com', 'admin')
128 db.session.commit()
129
130
131# Displays the home page.
132@app.route('/')
133# Users must be authenticated to view the home page, but they don't have to have any particular role.
134# Flask-Security will display a login form if the user isn't already authenticated.
135@login_required
136def index():
137 return render_template('index.html')
138
139
140# Customized User model for SQL-Admin
141class UserAdmin(sqla.ModelView):
142
143 # Don't display the password on the list of Users
144 column_exclude_list = list = ('password',)
145
146 # Don't include the standard password field when creating or editing a User (but see below)
147 form_excluded_columns = ('password',)
148
149 # Automatically display human-readable names for the current and available Roles when creating or editing a User
150 column_auto_select_related = True
151
152 # Prevent administration of Users unless the currently logged-in user has the "admin" role
153 def is_accessible(self):
154 return current_user.has_role('admin')
155
156 # On the form for creating or editing a User, don't display a field corresponding to the model's password field.
157 # There are two reasons for this. First, we want to encrypt the password before storing in the database. Second,
158 # we want to use a password field (with the input masked) rather than a regular text field.
159 def scaffold_form(self):
160
161 # Start with the standard form as provided by Flask-Admin. We've already told Flask-Admin to exclude the
162 # password field from this form.
163 form_class = super(UserAdmin, self).scaffold_form()
164
165 # Add a password field, naming it "password2" and labeling it "New Password".
166 form_class.password2 = PasswordField('New Password')
167 return form_class
168
169 # This callback executes when the user saves changes to a newly-created or edited User -- before the changes are
170 # committed to the database.
171 def on_model_change(self, form, model, is_created):
172
173 # If the password field isn't blank...
174 if len(model.password2):
175
176 # ... then encrypt the new password prior to storing it in the database. If the password field is blank,
177 # the existing password in the database will be retained.
178 model.password = utils.encrypt_password(model.password2)
179
180
181# Customized Role model for SQL-Admin
182class RoleAdmin(sqla.ModelView):
183
184 # Prevent administration of Roles unless the currently logged-in user has the "admin" role
185 def is_accessible(self):
186 return current_user.has_role('admin')
187
188# Initialize Flask-Admin
189admin = Admin(app)
190
191# Add Flask-Admin views for Users and Roles
192admin.add_view(UserAdmin(User, db.session))
193admin.add_view(RoleAdmin(Role, db.session))
194
195
196# If running locally, listen on all IP addresses, port 8080
197if __name__ == '__main__':
198 app.run(
199 host='0.0.0.0',
200 port=int('8080'),
201 debug=app.config['DEBUG']
202 )