· 8 years ago · Sep 12, 2017, 03:12 AM
1# THIS PROJECT IS AN EXAMPLE APP. SOME CODE MAY NOT BE ACTUALLY USEFUL
2# FOR DEMONSTRATION PURPOSES ONLY
3# YOUR MILEAGE MAY VARY
4# Requirements are Flask, Flask-WTF, Flask-SQLAlchemy, bcrypt
5
6import os
7
8from flask import (Flask,
9 Blueprint,
10 redirect,
11 render_template_string,
12 request,
13 url_for)
14from flask_login import UserMixin
15from flask_principal import Permission, RoleNeed
16
17from flask_security import (
18 login_required,
19 RoleMixin,
20 Security,
21 SQLAlchemyUserDatastore,
22 utils,
23 )
24from flask_sqlalchemy import SQLAlchemy
25from flask_wtf import FlaskForm
26from wtforms import (
27 HiddenField,
28 StringField,
29 SubmitField,
30 TextAreaField)
31from wtforms.validators import DataRequired
32
33BASE_PATH = os.path.abspath(os.path.dirname(__file__))
34
35# Instantiate the extensions but do not initialize them yet.
36db = SQLAlchemy()
37security = Security()
38
39# The following are template strings which are utilized by jinja2. Jinja2
40# is a template language which provides specific tags that allow the ability to
41# execute python code during the parsing process.
42
43# The index template string for the main page
44index_template = '''
45<div class="nav">
46 {% if current_user.is_authenticated %}
47 Welcome, {{ current_user.email }}
48 <a href="{{ url_for('security.logout') }}">Logout</a>
49 <a href="{{ url_for('blog.blog_index') }}">View All</a>
50 {% else %}
51 <a href="{{ url_for('security.login') }}">Login</a>
52 {% endif %}
53</div>
54
55{% if posts %}
56 {% for post in posts %}
57 <div>
58 <h1>{{ post.title }}</h1>
59 <p> {{ post.text }} </p>
60 {% endfor %}
61 </div>
62 {% else %}
63 <p>No posts yet. Try again later</p>
64{% endif %}
65'''
66
67blog_index_template = '''
68{% if posts %}
69 <table class="posts-table">
70 <thead>
71 <tr>
72 <th>post id</th>
73 <th>title</th>
74 <th>text</th>
75 </tr>
76 </thead>
77 <tbody>
78 {% for post in posts %}
79 <tr>
80 <td>{{ post.id }}</td>
81 <td><a href="{{ url_for('blog.edit_post', post_id=post.id) }}">{{ post.title }}</a></td>
82 <td>{{ post.text }}</td>
83 </tr>
84 {% endfor %}
85 </tbody>
86 </table>
87{% else %}
88<p> You don't have any posts </p>
89{% endif %}
90<a href = "{{ url_for('blog.create_post') }}">Create Post</a>
91'''
92
93# The template string for creating/editing a blog post
94create_post_template = '''
95<div class="post-form">
96 <form action="{% if form.id %} {{url_for('blog.edit_post', post_id=form.id.data) }} {% else %} {{ url_for('blog.create_post') }} {% endif %}" method="POST" name="post_form">
97 {% if form.id %}
98 {{ form.id }}
99 {% endif %}
100 {{ form.csrf_token }}
101 {{ form.title.label }}
102 {{ form.title }}
103 {{ form.text.label }}
104 {{ form.text }}
105 {{ form.submit() }}
106 </form>
107</div>
108'''
109
110# Customize your own error pages
111error_template = '''
112<div id="error">
113 <h1> Sorry, there was an error</h1>
114 <p>Error:# {{ status_code }}</p>
115</div>
116'''
117
118
119# Blog post creation form
120class CreatePostForm(FlaskForm):
121 """
122 The form used to create a blog post
123 """
124 title = StringField('Title', validators=[DataRequired()])
125 text = TextAreaField('Text', validators=[DataRequired()])
126 submit = SubmitField('Submit')
127
128
129class EditPostForm(CreatePostForm):
130 id = HiddenField()
131
132
133# Create the Models
134class Post(db.Model):
135 __tablename__ = 'posts'
136
137 id = db.Column(db.Integer(), primary_key=True)
138 title = db.Column(db.String(100))
139 text = db.Column(db.Text())
140
141 @classmethod
142 def all(cls):
143 """
144 Returns all researcher items from the database
145 """
146 return db.session.query(cls).all()
147
148
149roles_users = db.Table(
150 'roles_users',
151 db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
152 db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))
153
154
155class Role(RoleMixin, db.Model):
156 __tablename__ = 'roles'
157
158 id = db.Column(db.Integer(), primary_key=True)
159 name = db.Column(db.String(80), unique=True)
160 description = db.Column(db.String(255))
161
162 def __eq__(self, other):
163 return (self.name == other or
164 self.name == getattr(other, 'name', None))
165
166 def __ne__(self, other):
167 return (self.name != other and
168 self.name != getattr(other, 'name', None))
169
170
171class User(UserMixin, db.Model):
172 __tablename__ = 'users'
173
174 id = db.Column(db.Integer, primary_key=True)
175 email = db.Column(db.String(255), unique=True)
176 password = db.Column(db.String(120))
177 active = db.Column(db.Boolean())
178 confirmed_at = db.Column(db.DateTime())
179 last_login_at = db.Column(db.DateTime())
180 current_login_at = db.Column(db.DateTime())
181 last_login_ip = db.Column(db.String(100))
182 current_login_ip = db.Column(db.String(100))
183 login_count = db.Column(db.Integer)
184 registered_at = db.Column(db.DateTime())
185
186 roles = db.relationship('Role', secondary=roles_users,
187 backref=db.backref('users', lazy='dynamic'))
188
189
190# Create settings object
191settings = {
192 'SECRET_KEY': 'super not secure development key',
193 'DEBUG': True,
194 'SQLALCHEMY_TRACK_MODIFICATIONS': False,
195 'SQLALCHEMY_DATABASE_URI': 'sqlite:///' + os.path.join(BASE_PATH,
196 'posts.db'),
197 'SQLALCHEMY_ECHO': True,
198 'SECURITY_PASSWORD_HASH': 'bcrypt',
199 'SECURITY_PASSWORD_SALT': os.environ.get('SECURITY_SALT', 'dev secret'),
200 'SECUIRTY_CONFIRMABLE': False,
201 'SECURITY_REGISTERABLE': False,
202 'SECURITY_TRACKABLE': True,
203 'SECURITY_CHANGEABLE': True
204}
205
206# Create the main Flask application
207app = Flask(__name__)
208app.config.update(settings)
209
210# Initialize extensions
211db.init_app(app)
212security.init_app(app,
213 SQLAlchemyUserDatastore(db, User, Role),
214 register_blueprint=True)
215
216
217# Public Endpoints
218@app.route('/', methods=['GET'])
219def index():
220 posts = Post.all()
221 return render_template_string(index_template, posts=posts)
222
223
224# Register errorhandlers
225@app.errorhandler(404)
226def page_not_found(e):
227 return render_template_string(error_template, status_code=404), 404
228
229
230@app.errorhandler(500)
231def server_error(e):
232 return render_template_string(error_template, status_code=500), 500
233
234
235# Create blog blueprint
236blog = Blueprint('blog', __name__, url_prefix='/blog')
237
238# Create admin role for blog endpoints
239admin_permission = Permission(RoleNeed('admin'))
240
241
242# Create the blog routes
243@blog.route('/', methods=['GET'])
244@login_required
245@admin_permission.require()
246def blog_index():
247 posts = Post.all()
248 return render_template_string(blog_index_template, posts=posts)
249
250
251@blog.route('/create', methods=['GET', 'POST'])
252@login_required
253@admin_permission.require()
254def create_post():
255 form = CreatePostForm(request.form)
256 if form.validate_on_submit():
257 post = Post(title=form.title.data,
258 text=form.text.data)
259 db.session.add(post)
260 db.session.commit()
261 return redirect(url_for('blog.blog_index'))
262 return render_template_string(create_post_template, form=form)
263
264
265@blog.route('/edit/<int:post_id>', methods=['GET', 'POST'])
266@login_required
267@admin_permission.require()
268def edit_post(post_id):
269 post = Post.query.get(post_id)
270 form = EditPostForm(request.form, obj=post)
271 if form.validate_on_submit():
272 form.populate_obj(post)
273 db.session.merge(post)
274 db.session.commit()
275 return redirect(url_for('blog.blog_index'))
276 return render_template_string(create_post_template, form=form)
277
278
279# Register bluprints
280app.register_blueprint(blog)
281
282
283# Utils
284def create_and_seed_db():
285 # Hack for running db commands w/o context
286 ctx = app.test_request_context()
287 ctx.push()
288 db.create_all()
289 # Create the administrator role
290 user_datastore = app.extensions['security'].datastore
291 user_datastore.find_or_create_role(name='admin',
292 description='Administrator')
293 # Create the demo user
294 encrypted_password = utils.hash_password('demo')
295 user = User(email='demo', password=encrypted_password)
296 db.session.add(user)
297 db.session.commit()
298 user_datastore.add_role_to_user('demo', 'admin')
299 db.session.commit()
300
301 return
302
303
304if __name__ == '__main__':
305 # This will create the database if it doesn't already exist.
306 if not os.path.exists(
307 app.config['SQLALCHEMY_DATABASE_URI'].split('///')[1]):
308 create_and_seed_db()
309 app.run()