-
Notifications
You must be signed in to change notification settings - Fork 8
Commit v0.22
kwmccabe edited this page Apr 17, 2018
·
8 revisions
v0.22 - User module: tables.sql, models, forms, views, templates
- +4 -0 [M] mysql/scripts/seeddata.sql
- +16 -0 [M] mysql/scripts/tables.sql
- +3 -0 [M] web/app/init.py
- +1 -0 [M] web/app/main/templates/admin.html
- +2 -2 [M] web/app/main/templates/navbar_main.html
- +5 -0 [A] web/app/user/init.py
- +42 -0 [A] web/app/user/forms.py
- +21 -0 [A] web/app/user/models.py
- +46 -0 [A] web/app/user/templates/user_create.html
- +55 -0 [A] web/app/user/templates/user_edit.html
- +23 -0 [A] web/app/user/templates/user_index.html
- +136 -0 [A] web/app/user/templates/user_list.html
- +54 -0 [A] web/app/user/templates/user_view.html
- +146 -0 [A] web/app/user/views.py
- +3 -2 [M] web/flaskapp.py
- Add test row for table
flaskapp.user
.
+DELETE FROM `user`;
+INSERT INTO `user` (keyname) VALUES ("admin");
+OPTIMIZE TABLE `user`;
- Add minimal database table
user
.
+-- -------------------------------------------- --
+-- user
+-- -------------------------------------------- --
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `keyname` varchar(63) NOT NULL,
+ `active` tinyint(1) NOT NULL DEFAULT '1',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_keyname` (`keyname`),
+ KEY `user_active` (`active`)
+) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8;
+DESCRIBE `user`;
+SELECT "table `user` created" AS MSG;
- Add the
user
module viaapp.register_blueprint()
.
+ from .user import user as user_blueprint
+ app.register_blueprint(user_blueprint)
- Add link from
/admin
to the Admin Users area.
<ol>
<li><a href="{{ url_for('item.item_list') }}">Items</a></li>
+<li><a href="{{ url_for('user.user_list') }}">Users</a></li>
</ol>
- Add main navbar link for public page
/user/
. - Add main navbar link for admin page
/admin/user/list
.
<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="#">Users</a></li>
+ <li><a href="{{ url_for('user.user_page') }}">Users</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('main.main_page', page='admin') }}">Dashboard</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{ url_for('item.item_list') }}">Items</a></li>
- <li><a href="#">Users</a></li>
+ <li><a href="{{ url_for('user.user_list') }}">Users</a></li>
</ul>
</li>
</ul>
- Init the
user
module. - Create a Blueprint object
user
for use bycreate_app()
inweb/app/__init__.py
.
+from flask import Blueprint
+
+user = Blueprint('user', __name__, template_folder='templates')
+
+from . import views
- Create minimal
CreateUserForm
withkeyname
field. - Create minimal
EditUserForm
withid
,keyname
, andactive
fields.
+import re
+
+from flask_wtf import FlaskForm
+from wtforms import BooleanField, DecimalField, FloatField, IntegerField, \
+ DateTimeField, DateField, \
+ FileField, PasswordField, StringField, TextAreaField, \
+ RadioField, SelectField, SelectMultipleField, \
+ HiddenField, SubmitField
+from wtforms.validators import Email, InputRequired, Length
+from wtforms import ValidationError
+#from flask_pagedown.fields import PageDownField
+from .models import UserModel
+
+
+def filter_username(data):
+ return re.sub('[^a-z0-9_-]', '', str(data).lower())
+
+def validate_username(self, field):
+ if field.data != self.user.keyname and \
+ UserModel.query.filter_by(keyname=field.data).first():
+ raise ValidationError('Username already in use.')
+
+
+class CreatUserForm(FlaskForm):
+ keyname = StringField('Username', validators=[InputRequired(),Length(2,63),validate_username], filters=[filter_username])
+ submit = SubmitField('Create User')
+
+ def __init__(self, user, *args, **kwargs):
+ super(CreatUserForm, self).__init__(*args, **kwargs)
+ #self.role.choices = [(role.id, role.name) for role in Role.query.order_by(Role.name).all()]
+ self.user = user
+
+
+class EditUserForm(FlaskForm):
+ id = HiddenField('id')
+ keyname = StringField('Username', validators=[InputRequired(),Length(2,63),validate_username], filters=[filter_username])
+ active = BooleanField('Active')
+ submit = SubmitField('Update User')
+
+ def __init__(self, user, *args, **kwargs):
+ super(EditUserForm, self).__init__(*args, **kwargs)
+ self.user = user
- Create minimal
UserModel
.
+from flask import url_for
+from .. import db
+
+
+class UserModel(db.Model):
+ __tablename__ = 'user'
+ id = db.Column(db.BigInteger, autoincrement=True, primary_key=True)
+ keyname = db.Column(db.String(63), nullable=False, unique=True, index=True, default='')
+ active = db.Column(db.Boolean, nullable=False, index=True, default=1)
+
+ def to_json(self):
+ json_user = {
+ #'url': url_for('api.get_user', id=self.id),
+ 'id' : self.id,
+ 'keyname' : self.keyname,
+ 'active' : self.active,
+ }
+ return json_user
+
+ def __repr__(self):
+ return '<UserModel %r>' % (self.id)
- Template for route
/admin/user/create
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Admin - Create User{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('main.main_page', page='admin') }}">Admin</a></li>
+<li><a href="{{ url_for('.user_list') }}">Users</a></li>
+<li><a href="{{ url_for('.user_create') }}">Create</a></li>
+{% endblock %}
+
+
+<!-- BLOCK: content -->
+{% block content %}
+<div id="item_create_panel" class="panel panel-info">
+ <div class="panel-heading">
+ <h3 class="condensed">Create User</h3>
+ </div>
+ <div class="panel-body">
+
+<form class="form-horizontal" action="{{ url_for('.user_create') }}" method="post">
+ {{ form.csrf_token }}
+ <div class="form-group">
+ *{{ form.keyname.label }}
+ {{ form.keyname(class_='form-control',placeholder='keyname') }}
+ <p class="help-block">unique textual identifier - letters, numbers, dashes and underscores allowed, no spaces</p>
+ </div>
+ {{ form.submit(class_='btn btn-primary') }}
+ <span class="form-submit-buttons">
+ <a class="btn btn-default" href="{{ url_for('.user_list') }}">Cancel</a>
+ </span>
+</form>
+
+ </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_create.html{% endblock %}
+
+{# end web/app/user/templates/user_create.html #}
- Template for route
/admin/user/edit/<int:id>
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Admin - Edit '{{ form.user.keyname }}'{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('main.main_page', page='admin') }}">Admin</a></li>
+<li><a href="{{ url_for('.user_list') }}">Users</a></li>
+<li><a href="{{ url_for('.user_edit', id=form.id.data) }}">Edit '{{ form.user.keyname }}'</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('.user_view', id=form.id.data) }}">View User</a> ]
+[ <a href="{{ url_for('.user_edit', id=form.id.data) }}">Edit User</a> ]
+</p>
+ <h3 class="condensed">Edit User</h3>
+ </div>
+ <div class="panel-body">
+
+<form class="form-horizontal" action="{{ url_for('.user_edit', id=form.id.data) }}" method="post">
+ {{ form.csrf_token }}
+ {{ form.id }}
+ <div class="form-group">
+ *{{ form.keyname.label }}
+ {{ form.keyname(class_='form-control',placeholder='keyname') }}
+ <p class="help-block">unique textual identifier - letters, numbers, dashes and underscores allowed - no spaces</p>
+ </div>
+ <div class="form-group">
+ {{ form.active.label }}
+ {{ form.active(class_='form-control') }}
+ </div>
+ {{ form.submit(class_='btn btn-primary') }}
+ <span class="form-submit-buttons">
+ <a data-confirm-link="Delete User? This action cannot be undone." href="{{ url_for('.user_delete', id=form.id.data) }}"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete User</a>
+ </span>
+</form>
+
+ </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_edit.html{% endblock %}
+
+{# end web/app/user/templates/user_edit.html #}
- Template for route
/user/
.
+{% extends "base.html" %}
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Users{% endblock %}
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('.user_page') }}">Users</a></li>
+{% endblock %}
+
+<!-- BLOCK: content -->
+{% block content %}
+
+<p>This is public landing page for the User Module.</p>
+
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_index.html{% endblock %}
+
+{# end web/app/user/templates/user_index.html #}
- Template for route
/admin/user/list
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Admin - Users{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('main.main_page', page='admin') }}">Admin</a></li>
+<li><a href="{{ url_for('.user_list') }}">Users</a></li>
+{% endblock %}
+
+
+<!-- BLOCK: content -->
+{% block content %}
+<form class="form-inline" action="{{ url_for('.user_action') }}" method="post">
+
+<div id="item_list_panel" class="panel panel-info">
+ <div class="panel-heading">
+
+<div class="table-responsive">
+<table class="table table-condensed table-borderless">
+<tr>
+<td>
+ Showing {{ rowcnt }} of {{ session[opts_key]['itemcnt'] }} Users
+</td>
+<td class="text-right">
+ {% if session[opts_key]['page'] > 1 %}
+ <a href="{{ url_for('.user_list') }}?page=1"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></a>
+ <a href="{{ url_for('.user_list') }}?page={{ session[opts_key]['page'] - 1 }}"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span></a>
+ {% elif session[opts_key]['pagecnt'] > 1 %}
+ <span class="glyphicon glyphicon-backward" aria-hidden="true"></span>
+ <span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>
+ {% endif %}
+ <span>Page {{ session[opts_key]['page'] }} of {{ session[opts_key]['pagecnt'] }}</span>
+ {% if session[opts_key]['page'] < session[opts_key]['pagecnt'] %}
+ <a href="{{ url_for('.user_list') }}?page={{ session[opts_key]['page'] + 1 }}"><span class="glyphicon glyphicon-arrow-right" aria-hidden="true"></span></a>
+ <a href="{{ url_for('.user_list') }}?page={{ session[opts_key]['pagecnt'] }}"><span class="glyphicon glyphicon-forward" aria-hidden="true"></span></a>
+ {% elif session[opts_key]['pagecnt'] > 1 %}
+ <span class="glyphicon glyphicon-arrow-right" aria-hidden="true"></span>
+ <span class="glyphicon glyphicon-forward" aria-hidden="true"></span>
+ {% endif %}
+</td>
+</tr>
+<tr>
+<td>
+ [ <a href="{{ url_for('.user_create') }}">Create User</a> ]
+</td>
+<td class="text-right">
+ {% if session[opts_key]['status'] == 'active' %}
+ [<a href="{{ url_for('.user_list') }}?status=all">All</a>]
+ [Active]
+ [<a href="{{ url_for('.user_list') }}?status=inactive">Inactive</a>]
+ {% elif session[opts_key]['status'] == 'inactive' %}
+ [<a href="{{ url_for('.user_list') }}?status=all">All</a>]
+ [<a href="{{ url_for('.user_list') }}?status=active">Active</a>]
+ [Inactive]
+ {% else %}
+ [All]
+ [<a href="{{ url_for('.user_list') }}?status=active">Active</a>]
+ [<a href="{{ url_for('.user_list') }}?status=inactive">Inactive</a>]
+ {% endif %}
+</td>
+</tr>
+</table>
+</div>
+
+ </div> <!-- end class="panel-heading" -->
+ <div class="panel-body">
+
+
+<div class="table-responsive">
+<table class="table table-condensed table-hover">
+<tr>
+<th>
+<input type="checkbox" class="checkall" onclick="checkAll('user_id',this.checked);" />
+</th>
+{% for col in cols %}
+ <th>
+ {% if col == session[opts_key]['sort'] and session[opts_key]['order'] == 'asc' %}
+ <a href="{{ url_for('.user_list') }}?sort={{ col }}&order=desc"><span class="glyphicon glyphicon-sort-by-attributes" aria-hidden="true"></span></a>
+ {% elif col == session[opts_key]['sort'] %}
+ <a href="{{ url_for('.user_list') }}?sort={{ col }}&order=asc"><span class="glyphicon glyphicon-sort-by-attributes-alt" aria-hidden="true"></span></a>
+ {% else %}
+ <a href="{{ url_for('.user_list') }}?sort={{ col }}"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a>
+ {% endif %}
+ {{ col }}
+ </th>
+{% endfor %}
+<th></th>
+</tr>
+
+{% for row in rows %}
+ <tr>
+ <td>
+ <input type="checkbox" name="user_id" value="{{row.id}}">
+ </td>
+ {% for col in cols %}
+ <td onclick="window.location='{{ url_for('.user_view', id=row.id) }}';">{{ row[col] }}</td>
+ {% endfor %}
+ <td>
+ <a href="{{ url_for('.user_edit', id=row.id) }}"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
+ </td>
+ </tr>
+{% endfor %}
+
+</table>
+</div>
+
+ </div> <!-- end class="panel-body" -->
+ <div class="panel-footer">
+<div id="list-actions">
+ <div class="form-group">
+ <label for="action" class="sr-only">Action</label>
+ <select id="action" name="action" class="form-control">
+ <option value="">Selected Users...</option>
+ <option value="active">Set Active</option>
+ <option value="inactive">Set Inactive</option>
+ <option value="delete">Delete</option>
+ </select>
+ </div>
+ <button class="btn btn-default" type="submit">Apply</button>
+</div>
+ </div> <!-- end class="panel-footer" -->
+</div> <!-- end class="panel" -->
+
+</form>
+{% endblock content %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - user_list.html{% endblock %}
+
+{# end web/app/user/templates/user_list.html #}
- Template for route
/admin/user/view/<int:id>
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Admin - View '{{ user.keyname }}'{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="{{ url_for('main.main_page', page='admin') }}">Admin</a></li>
+<li><a href="{{ url_for('.user_list') }}">Users</a></li>
+<li><a href="{{ url_for('.user_view', id=user.id) }}">View '{{ user.keyname }}'</a></li>
+{% 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_view', id=user.id) }}">View User</a> ]
+[ <a href="{{ url_for('.user_edit', id=user.id) }}">Edit User</a> ]
+</p>
+ <h3 class="condensed">View User</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_view.html{% endblock %}
+
+{# end web/app/user/templates/user_view.html #}
- Create general user page route
/user/<page>/
. - Create route
/admin/user/action
for use by/admin/user/list
. - Create route
/admin/user/delete/<int:id>
. - Create route
/admin/user/create/
. - Create route
/admin/user/edit/<int:id>
. - Create route
/admin/user/view/<int:id>
. - Create route
/admin/user/list
.
+import logging
+import math
+
+from flask import flash, redirect, render_template, request, session, url_for
+from jinja2 import TemplateNotFound
+from .. import db, flash_errors
+from . import user
+from .models import UserModel
+from .forms import CreatUserForm, EditUserForm
+from ..decorators import get_list_opts
+
+
+@user.route('/user/', defaults={'page': 'index'})
+@user.route('/user/<page>/')
+def user_page(page):
+ try:
+ logging.debug( 'user_page( page:%s )' % (page) )
+ return render_template('user_%s.html' % (page))
+ except TemplateNotFound:
+ logging.info('TemplateNotFound: user_%s.html' % (page))
+ abort(404)
+
+
+@user.route('/admin/user/action', methods=['POST'])
+def user_action():
+ action = request.values.get('action', '')
+ user_ids = request.form.getlist('user_id')
+ id_str = "["+",".join([str(id) for id in user_ids])+"]"
+
+ if action and user_ids:
+ if action == 'delete':
+ for id in user_ids:
+ user = UserModel.query.get_or_404(id)
+ db.session.delete(user)
+ db.session.commit()
+ flash('Users Deleted (id='+id_str+')')
+ if action == 'active':
+ for id in user_ids:
+ user = UserModel.query.get_or_404(id)
+ user.active = True
+ db.session.add(user)
+ db.session.commit()
+ flash('Users Activated (id='+id_str+')')
+ if action == 'inactive':
+ for id in user_ids:
+ user = UserModel.query.get_or_404(id)
+ user.active = False
+ db.session.add(user)
+ db.session.commit()
+ flash('Users Deactivated (id='+id_str+')')
+ logging.info('user_action - action:%s, user_ids:%s' % (action, id_str))
+ return redirect(url_for('.user_list'))
+
+
+@user.route('/admin/user/delete/<int:id>', methods=['GET','POST'])
+def user_delete( id ):
+ user = UserModel.query.get_or_404(id)
+ db.session.delete(user)
+ db.session.commit()
+ flash('User deleted (id=%s)' % (user.id))
+ logging.info('user_delete( id:%s )' % (user.id))
+ return redirect(url_for('.user_list'))
+
+
+@user.route('/admin/user/create', methods=['GET','POST'])
+def user_create():
+ user = UserModel()
+ form = CreatUserForm(user)
+ if form.validate_on_submit():
+ form.populate_obj(user)
+ db.session.add(user)
+ db.session.commit()
+ flash('User created (id=%s)' % (user.id))
+ logging.info('user_create( id:%s )' % (user.id))
+ return redirect(url_for('.user_view', id=user.id))
+ else:
+ flash_errors(form)
+ if request.method == 'GET':
+ user.keyname = ''
+ form.process(obj=user)
+ return render_template('user_create.html', form=form)
+
+
+@user.route('/admin/user/edit/<int:id>', methods=['GET','POST'])
+def user_edit( id ):
+ user = UserModel.query.get_or_404(id)
+ form = EditUserForm(user)
+ if form.validate_on_submit():
+ form.populate_obj(user)
+ db.session.add(user)
+ db.session.commit()
+ flash('User updated (id=%s)' % (user.id))
+ logging.info('user_edit( id:%s )' % (user.id))
+ return redirect(url_for('.user_view', id=user.id))
+ else:
+ flash_errors(form)
+ form.process(obj=user)
+ return render_template('user_edit.html', form=form)
+
+
+@user.route('/admin/user/view/<int:id>')
+def user_view( id ):
+ user = UserModel.query.get_or_404(id)
+ cols = UserModel.__table__.columns.keys()
+ return render_template('user_view.html', cols=cols, user=user)
+
+
+@user.route('/admin/user/list', methods=['GET','POST'])
+@get_list_opts('user_list_opts')
+def user_list():
+ cols = UserModel.__table__.columns.keys()
+ rows = db.session.query(UserModel)
+
+ opts_key = 'user_list_opts'
+ S = session[opts_key]
+
+ if S['status'] in ['active', 'inactive']:
+ rows = rows.filter(UserModel.active == (S['status'] == 'active'))
+
+ S['itemcnt'] = rows.count()
+ S['pagecnt'] = int(math.ceil( float(S['itemcnt'])/float(S['limit']) ))
+
+ if S['page'] > S['pagecnt']:
+ S['page'] = S['pagecnt']
+ S['offset'] = 0
+ if ((S['page'] - 1) * S['limit']) < S['itemcnt']:
+ S['offset'] = (S['page'] - 1) * S['limit']
+ session[opts_key] = S
+
+ if S['sort'] in cols:
+ if S['order'] == 'desc':
+ rows = rows.order_by(getattr( UserModel, S['sort'] ).desc())
+ else:
+ rows = rows.order_by(getattr( UserModel, S['sort'] ).asc())
+ if S['offset'] > 0:
+ rows = rows.offset(S['offset'])
+ if S['limit'] > 0:
+ rows = rows.limit(S['limit'])
+
+ rows = rows.all()
+ rowcnt = len(rows)
+
+ logging.debug('user_list - %s' % (rowcnt))
+ return render_template('user_list.html', cols=cols,rows=rows,rowcnt=rowcnt,opts_key=opts_key)
- Update test route
/hello_db
.
#stmt = "SELECT CONNECTION_ID()" # Return the connection ID (thread ID) for the connection
#stmt = "SELECT CURRENT_USER()" # The authenticated user name and host name
#stmt = "SELECT DATABASE()" # Return the default (current) database name
- #stmt = "SHOW TABLES" # Return list of non-temporary tables in current database
+ stmt = "SHOW TABLES" # Return list of non-temporary tables in current database
#stmt = "show create database %s;" % current_app.config['MYSQL_DB']
#stmt = "show grants for %s;" % current_app.config['MYSQL_USER']
- #stmt = "select * from item" # Return list of non-temporary tables in current database
+ #stmt = "select * from item"
+ #stmt = "select * from user"
Commit-v0.21 | Commit-v0.22 | Commit-v0.23
- FlaskApp Tutorial
- Table of Contents
- About
- Application Setup
- Modules, Templates, and Layouts
- Database Items, Forms, and CRUD
- List Filter, Sort, and Paginate
- Users and Login
- Database Relationships
- API Module, HTTPAuth and JSON
- Refactoring User Roles and Item Status
- AJAX and Public Pages