Skip to content

Commit v0.24

kwmccabe edited this page Apr 17, 2018 · 9 revisions

v0.24 - Flask-Login: update UserModel, add /login and /profile, decorate routes with @login_required


Files changed (17)

File web/app/init.py MODIFIED

  • Import and init LoginManager.
  • Create function init_login_manager() to collect configuration options.
+from flask_login import LoginManager

...

+login_manager = LoginManager()
 
...

+    init_login_manager(app)

...

+def init_login_manager(app):
+    login_manager.init_app(app)
+    login_manager.session_protection = 'strong'
+    login_manager.login_view = 'user.user_login'
+    login_manager.login_message = u"Please log in to access this page."
+    login_manager.login_message_category = "warning"
+    login_manager.refresh_view = 'user.user_login'
+    login_manager.needs_refresh_message = (u"Please confirm your credentials to access this page.")
+    login_manager.needs_refresh_message_category = "warning"

File web/app/item/templates/item_create.html MODIFIED

-        <h3 class="condensed">Create Item</h3>
+        <h3 class="condensed">Item</h3>

File web/app/item/templates/item_edit.html MODIFIED

 <!-- BLOCK: title -->
-{% block title %}{{super()}} - Admin - Edit '{{ form.item.keyname }}'{% endblock %}
+{% block title %}{{super()}} - Admin - Edit Item '{{ form.item.keyname }}'{% endblock %}
 
...
 
-        <h3 class="condensed">Edit Item</h3>
+        <h3 class="condensed">Item '{{ form.item.keyname }}'</h3>

File web/app/item/templates/item_view.html MODIFIED

 <!-- BLOCK: title -->
-{% block title %}{{super()}} - Admin - View '{{ item.keyname }}'{% endblock %}
+{% block title %}{{super()}} - Admin - Item '{{ item.keyname }}'{% endblock %}

...

-        <h3 class="condensed">View Item</h3>
+        <h3 class="condensed">Item '{{ item.keyname }}'</h3>

File web/app/item/views.py MODIFIED

  • Restrict access to admin routes with login decorator @login_required.
+from flask_login import login_required

...

 @item.route('/admin/item/action', methods=['POST'])
+@login_required
 def item_action():

...

 @item.route('/admin/item/delete/<int:id>', methods=['GET','POST'])
+@login_required
 def item_delete( id ):

...

 @item.route('/admin/item/create', methods=['GET','POST'])
+@login_required
 def item_create():

...

 @item.route('/admin/item/edit/<int:id>', methods=['GET','POST'])
+@login_required
 def item_edit( id ):

...

 @item.route('/admin/item/view/<int:id>')
+@login_required
 def item_view( id ):

...

 @item.route('/admin/item/list')
 @get_list_opts('item_list_opts')
+@login_required
 def item_list():

File web/app/main/templates/base.html MODIFIED

  • Display current_user.user_email and Profile link if logged in.
  • Display Login link if not logged in.
             <div id="header_right" class="col-md-4 text-right">
                 {% block header_right %}{% endblock %}
+                {% if current_user.user_email %}
+                    <a href="{{ url_for('user.user_profile') }}">{{ current_user.user_email }}</a>
+                {% else %}
+                    <a href="{{ url_for('user.user_login') }}">Login</a>
+                {% endif %}
             </div>

...

 <div id="debuginfo"><pre>
 DEBUG:
+{% if current_user.user_email %}LOGGED IN: {{ current_user.user_email }}{% else %}LOGGED OUT{% endif %}
 TEMPLATES: {% block templates %}base.html{% endblock %}
 REQUEST.ARGS: {{ request.args }}
+REQUEST.COOKIES: {{ request.cookies }}
 SESSION: {{ session }}
 </pre></div>

File web/app/main/templates/navbar_main.html MODIFIED

     <div class="collapse navbar-collapse" id="navbar-main-collapse">
       <ul class="nav navbar-nav">
         <!-- li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li -->
-        <li><a href="{{ url_for('item.item_page') }}">Items</a></li>
-        <li><a href="{{ url_for('user.user_page') }}">Users</a></li>
+        <li><a href="{{ url_for('item.item_page', page='index') }}">Items</a></li>
+        <li><a href="{{ url_for('user.user_page', page='index') }}">Users</a></li>
         <li class="dropdown">

File web/app/user/forms.py MODIFIED

  • Add LoginForm for use by route /login.
+class LoginForm(FlaskForm):
+    next       = HiddenField('next')
+    user_email = StringField('Email', validators=[InputRequired(),Length(1,255),Email()])
+    password   = PasswordField('Password', validators=[InputRequired()])
+    remember   = BooleanField('Keep me logged in')
+    submit     = SubmitField('Login')
+
+    def __init__(self, user, *args, **kwargs):
+        super(LoginForm, self).__init__(*args, **kwargs)
+        self.user = user

File web/app/user/models.py MODIFIED

  • Import login_manager.
  • Create function for @login_manager.user_loader.
  • Create class methods is_authenticated(), is_anonymous(), is_active(), and get_id().
-from .. import db
+from .. import db, login_manager
 
+#see https://flask-login.readthedocs.io/
+#@login_manager.request_loader
+#def load_user_from_request(request):
+
+@login_manager.user_loader
+def load_user(user_id):
+    logging.info( "load_user(%s)" %  user_id)
+    return UserModel.query.filter_by(user_email=user_id).first()
 
 class UserModel(db.Model):

...

+    def is_authenticated(self):
+        return self.authenticated
+
+    def is_anonymous(self):
+        return self.anonymous
+
+    def is_active(self):
+        return self.active
+
+    def get_id(self):
+        return self.user_email
+
     def to_json(self):

File web/app/user/templates/user_create.html MODIFIED

-        <h3 class="condensed">Create User</h3>
+        <h3 class="condensed">User</h3>

File web/app/user/templates/user_edit.html MODIFIED

 <!-- BLOCK: title -->
-{% block title %}{{super()}} - Admin - Edit '{{ form.user.keyname }}'{% endblock %}
+{% block title %}{{super()}} - Admin - Edit User '{{ form.user.keyname }}'{% endblock %}

...

-        <h3 class="condensed">Edit User</h3>
+        <h3 class="condensed">User - '{{ form.user.keyname }}'</h3>

File web/app/user/templates/user_index.html MODIFIED

-<p>This is public landing page for the User Module.</p>
+<p>This is the public landing page for the User Module.</p>

File web/app/user/templates/user_login.html ADDED

+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Login{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('.user_login') }}">Login</a></li>
+{% endblock %}
+
+
+<!-- BLOCK: content -->
+{% block content %}
+<div id="item_edit_panel" class="panel panel-info">
+    <div class="panel-heading">
+<p style="float: right;">
+[ <a href="{{ url_for('main.main_home') }}">Cancel</a> ]
+</p>
+        <h3 class="condensed">User Login</h3>
+    </div>
+    <div class="panel-body">
+
+<form class="form-horizontal" action="{{ url_for('.user_login') }}" method="post">
+    {{ form.csrf_token }}
+    {{ form.next }}
+    <div class="form-group">
+        *{{ form.user_email.label }}
+        {{ form.user_email(class_='form-control',placeholder='[email protected]') }}
+    </div>
+    <div class="form-group">
+        *{{ form.password.label }}
+        {{ form.password(class_='form-control') }}
+    </div>
+    <div class="form-group">
+        {{ form.remember.label }}
+        {{ form.remember(class_='form-control') }}
+    </div>
+    {{ form.submit(class_='btn btn-primary') }}
+    <span class="form-submit-buttons">
+        <a class="btn btn-default" href="{{ url_for('main.main_home') }}">Cancel</a>
+    </span>
+</form>
+
+    </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_login.html{% endblock %}
+
+{# end web/app/user/templates/user_login.html #}

File web/app/user/templates/user_profile.html ADDED

+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Profile{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('.user_profile') }}">User Profile</a></li>
+{% endblock %}
+
+
+<!-- BLOCK: content_left -->
+{% block content_left %}
+{{super()}} <b>user_view</b>
+{% endblock %}
+
+
+<!-- BLOCK: content -->
+{% block content %}
+<div id="item_list_panel" class="panel panel-info">
+    <div class="panel-heading">
+<p style="float: right;">
+{# [ <a href="{{ url_for('.user_edit', id=user.id) }}">Update Profile</a> ] #}
+[ <a href="{{ url_for('.user_logout') }}">Logout</a> ]
+</p>
+        <h3 class="condensed">Profile '{{ user.keyname }}'</h3>
+    </div>
+    <div class="panel-body">
+
+<div class="table-responsive">
+<table class="table table-condensed table-hover">
+<tr>
+    <th>column</th>
+    <th>value</th>
+</tr>
+
+{% for col in cols %}
+    <tr>
+    <td>{{ col }}</td>
+    <td>{{ user[col] }}</td>
+    </tr>
+{% endfor %}
+
+</table>
+</div>
+
+    </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_profile.html{% endblock %}
+
+{# end web/app/user/templates/user_profile.html #}

File web/app/user/templates/user_view.html MODIFIED

 <!-- BLOCK: title -->
-{% block title %}{{super()}} - Admin - View '{{ user.keyname }}'{% endblock %}
+{% block title %}{{super()}} - Admin - User '{{ user.keyname }}'{% endblock %}

...

-        <h3 class="condensed">View User</h3>
+        <h3 class="condensed">User '{{ user.keyname }}'</h3>

File web/app/user/views.py MODIFIED

  • Import flask_login functions.
  • Create routes /login, /logout, and /profile.
  • Restrict access to admin routes with login decorator @login_required.
-from flask import flash, redirect, render_template, request, session, url_for
+from flask import abort, flash, redirect, render_template, request, session, url_for
+from flask_login import current_user, login_required, login_user, logout_user

...

-from .forms import CreatUserForm, EditUserForm
+from .forms import CreatUserForm, EditUserForm, LoginForm

...

+@user.route('/login', methods=['GET','POST'])
+def user_login():
+    user = UserModel()
+    form = LoginForm(user)
+    if form.validate_on_submit():
+        user = UserModel.query.filter_by(user_email=form.user_email.data).first()
+        if user is not None and user.verify_password(form.password.data):
+            login_user(user, form.remember.data)
+            return redirect(form.next.data or url_for('main.main_home'))
+        flash('Invalid username or password')
+    else:
+        flash_errors(form)
+    form.next.data = request.args.get('next') or url_for('main.main_home')
+    return render_template('user_login.html', form=form)
+
+
+@user.route('/logout')
+#@login_required
+def user_logout():
+    logout_user()
+    flash('You have been logged out.')
+    return redirect(url_for('main.main_page', page='index'))
+
+
+@user.route('/profile')
+@login_required
+def user_profile():
+    cols = UserModel.__table__.columns.keys()
+    cols_filtered = list(filter(lambda x: x not in ['id','user_pass'], cols))
+    user = UserModel.query.get_or_404( current_user.id )
+    return render_template('user_profile.html', cols=cols_filtered, user=user)

...

 @user.route('/admin/user/action', methods=['POST'])
+@login_required
 def user_action():

...

 @user.route('/admin/user/delete/<int:id>', methods=['GET','POST'])
+@login_required
 def user_delete( id ):

...

 @user.route('/admin/user/create', methods=['GET','POST'])
+@login_required
 def user_create():

...

 @user.route('/admin/user/edit/<int:id>', methods=['GET','POST'])
+@login_required
 def user_edit( id ):

...

 @user.route('/admin/user/view/<int:id>')
+@login_required
 def user_view( id ):

...

 @user.route('/admin/user/list', methods=['GET','POST'])
 @get_list_opts('user_list_opts')
+@login_required
 def user_list():

File web/requirements.txt MODIFIED

  • Add Flask-Login package.
  • Provide explicit version numbers to some implicit packages.
 Flask==0.12.2
 Flask-Bootstrap==3.3.7.1
+Flask-Login==0.4.1
 Flask-SQLAlchemy==2.2.0
 Flask-WTF==0.14.2
 gunicorn==19.7.1
 Jinja2==2.9.6
 MySQL-Connector-Python==8.0.6
+SQLAlchemy==1.2.5
+Werkzeug==0.14.1
+WTForms==2.1

Commit-v0.23 | Commit-v0.24 | Commit-v0.25

Clone this wiki locally