-
Notifications
You must be signed in to change notification settings - Fork 8
Commit v0.15
kwmccabe edited this page Apr 17, 2018
·
7 revisions
v0.15 - Flask-WTF: item_create, item_edit, item_delete
- +10 -1 [M] web/app/init.py
- +41 -0 [A] web/app/item/forms.py
- +46 -0 [A] web/app/item/templates/item_create.html
- +53 -0 [A] web/app/item/templates/item_edit.html
- +6 -3 [M] web/app/item/templates/item_list.html
- +8 -2 [M] web/app/item/templates/item_view.html
- +44 -9 [M] web/app/item/views.py
- +31 -0 [A] web/app/main/templates/admin.html
- +10 -3 [M] web/app/main/templates/index.html
- +5 -5 [M] web/app/main/templates/navbar_main.html
- +22 -2 [M] web/app/main/views.py
- +5 -0 [M] web/app/static/css/flaskapp.css
- +2 -0 [M] web/config.py
- +1 -0 [M] web/requirements.txt
- Create utility function
flask_errors(form)
to display FlaskForm errors.
-from flask import Flask
+from flask import Flask, flash
...
+def flash_errors(form):
+ for field, errors in form.errors.items():
+ for error in errors:
+ flash(u"Error in the %s field - %s" % (
+ getattr(form, field).label.text,
+ error
+ ),'error')
- Create FlaskForms
CreateItemForm
andEditItemForm
. - Function
filter_keyname
converts to lowercase and removes invalid characters. - Function
validate_keyname
checks against pre-existing itemkeyname
.
+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 InputRequired, Length
+from wtforms import ValidationError
+from .models import ItemModel
+
+
+def filter_keyname(data):
+ return re.sub('[^a-z0-9_-]', '', str(data).lower())
+
+def validate_keyname(self, field):
+ if field.data != self.item.keyname and \
+ ItemModel.query.filter_by(keyname=field.data).first():
+ raise ValidationError('Keyname already in use.')
+
+
+class CreatItemForm(FlaskForm):
+ keyname = StringField('Keyname', validators=[InputRequired(),Length(2,63),validate_keyname], filters=[filter_keyname])
+ submit = SubmitField('Create Item')
+
+ def __init__(self, item, *args, **kwargs):
+ super(CreatItemForm, self).__init__(*args, **kwargs)
+ self.item = item
+
+
+class EditItemForm(FlaskForm):
+ id = HiddenField('id')
+ keyname = StringField('Keyname', validators=[InputRequired(),Length(2,63),validate_keyname], filters=[filter_keyname])
+ mod_create = DateTimeField('Item Created')
+ submit = SubmitField('Update Item')
+
+ def __init__(self, item, *args, **kwargs):
+ super(EditItemForm, self).__init__(*args, **kwargs)
+ self.item = item
+
- Display HTML form for
/admin/item/create
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Create Item{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="/admin/">Admin</a></li>
+<li><a href="{{ url_for('.item_list') }}">Items</a></li>
+<li><a href="{{ url_for('.item_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 Item</h3>
+ </div>
+ <div class="panel-body">
+
+<form class="form-horizontal" action="{{ url_for('.item_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('.item_list') }}">Cancel</a> ]
+ </span>
+</form>
+
+ </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - item_create.html{% endblock %}
+
+{# end web/app/item/templates/item_create.html #}
- Display HTML form for
/admin/item/edit/<int:id>
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}}Item Edit{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="/admin/">Admin</a></li>
+<li><a href="{{ url_for('.item_list') }}">Items</a></li>
+<li><a href="{{ url_for('.item_edit', id=form.id.data) }}">Edit {{ form.item.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('.item_view', id=form.id.data) }}">View Item</a> ]
+[ <a href="{{ url_for('.item_edit', id=form.id.data) }}">Edit Item</a> ]
+</p>
+ <h3 class="condensed">Edit : {{ form.item.keyname }}</h3>
+ </div>
+ <div class="panel-body">
+
+<form class="form-horizontal" action="{{ url_for('.item_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>
+ {{ form.submit(class_='btn btn-primary') }}
+ <span class="form-submit-buttons">
+ [ <a href="{{ url_for('.item_delete', id=form.id.data) }}">Delete Item</a> ]
+ </span>
+</form>
+
+{{ form.mod_create.label }} : {{ form.mod_create.data }}<br/>
+
+ </div> <!-- end class="panel-body" -->
+</div> <!-- end class="panel" -->
+{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - item_edit.html{% endblock %}
+
+{# end web/app/item/templates/item_edit.html #}
- Update navigation.
<!-- BLOCK: title -->
-{% block title %}{{super()}}Items{% endblock %}
+{% block title %}{{super()}} - Items{% endblock %}
<!-- BLOCK: breadcrumb -->
{% block breadcrumb %}
{{super()}}
-{# <li><a href="{{ url_for('.item_list') }}">List Items</a></li> #}
+<li><a href="/admin/">Admin</a></li>
+<li><a href="{{ url_for('.item_list') }}">Items</a></li>
{% endblock %}
...
<tr>
<td>
Showing {{ rowcnt }} Items
+ <br/><br/>[ <a href="{{ url_for('.item_create') }}">Create Item</a> ]
</td>
...
<td>
- <a href="{{ url_for('.item_edit', id=row.id) }}">edit</a>
+ [ <a href="{{ url_for('.item_edit', id=row.id) }}">edit</a> ]
+ [ <a href="{{ url_for('.item_delete', id=row.id) }}">delete</a> ]
</td>
- Update navigation.
<!-- BLOCK: title -->
-{% block title %}{{super()}}Item - {{ item.keyname }}{% endblock %}
+{% block title %}{{super()}} - Item - {{ item.keyname }}{% endblock %}
<!-- BLOCK: breadcrumb -->
{% block breadcrumb %}
{{super()}}
-<li><a href="{{ url_for('.item_view', id=item.id) }}">View Item</a></li>
+<li><a href="/admin/">Admin</a></li>
+<li><a href="{{ url_for('.item_list') }}">Items</a></li>
+<li><a href="{{ url_for('.item_view', id=item.id) }}">{{ item.keyname }}</a></li>
{% endblock %}
...
{% block content %}
<div id="item_list_panel" class="panel panel-info">
<div class="panel-heading">
+<p style="float: right;">
+[ <a href="{{ url_for('.item_view', id=item.id) }}">View Item</a> ]
+[ <a href="{{ url_for('.item_edit', id=item.id) }}">Edit Item</a> ]
+</p>
<h3 class="condensed">Item : {{ item.keyname }}</h3>
</div>
<div class="panel-body">
- Update route
/admin/item/delete/<int:id>
. - Update route
/admin/item/create
. - Update route
/admin/item/edit/<int:id>
.
import logging
-from flask import render_template
-from .. import db
+from flask import flash, redirect, render_template, request, url_for
+from .. import db, flash_errors
from . import item
from .models import ItemModel
+from .forms import CreatItemForm, EditItemForm
...
-@item.route('/admin/item/delete/<int:id>')
+@item.route('/admin/item/delete/<int:id>', methods=['GET','POST'])
def item_delete( id ):
- return 'item_delete - id:%s' % (id)
+ item = ItemModel.query.get_or_404(id)
+ db.session.delete(item)
+ db.session.commit()
+ flash('Item deleted (id=%s)' % (item.id))
+ logging.info('item_delete( id:%s )' % (item.id))
+ return redirect(url_for('.item_list'))
-@item.route('/admin/item/create')
-def item_create():
- return 'item_create'
-@item.route('/admin/item/edit/<int:id>')
+@item.route('/admin/item/create', methods=['GET','POST'])
+def item_create():
+ item = ItemModel()
+ form = CreatItemForm(item)
+ if form.validate_on_submit():
+ form.populate_obj(item)
+ db.session.add(item)
+ db.session.commit()
+ flash('Item created (id=%s)' % (item.id))
+ logging.info('item_create( id:%s )' % (item.id))
+ return redirect(url_for('.item_view', id=item.id))
+ else:
+ flash_errors(form)
+ if request.method == 'GET':
+ item.keyname = ''
+ form.process(obj=item)
+ return render_template('item_create.html', form=form)
+
+
+@item.route('/admin/item/edit/<int:id>', methods=['GET','POST'])
def item_edit( id ):
- return 'item_edit - id:%s' % (id)
+ item = ItemModel.query.get_or_404(id)
+ form = EditItemForm(item)
+ if form.validate_on_submit():
+ del form.mod_create
+ form.populate_obj(item)
+ db.session.add(item)
+ db.session.commit()
+ flash('Item updated (id=%s)' % (item.id))
+ logging.info('item_edit( id:%s )' % (item.id))
+ return redirect(url_for('.item_view', id=item.id))
+ else:
+ flash_errors(form)
+ form.process(obj=item)
+ return render_template('item_edit.html', form=form)
- Create template for route
/admin/
. - Link to route
/admin/item/list
.
+{% extends "base.html" %}
+
+
+<!-- BLOCK: title -->
+{% block title %}{{super()}} - Admin{% endblock %}
+
+
+<!-- BLOCK: breadcrumb -->
+{% block breadcrumb %}
+{{super()}}
+<li><a href="/admin/">Admin</a></li>
+{% endblock %}
+
+
+<!-- BLOCK: content -->
+{% block content %}
+
+<p>This is the landing page for the admin section.</p>
+
+<ol>
+<li><a href="{{ url_for('item.item_list') }}">Items</a></li>
+</ol>
+
+
+{% endblock content %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - admin.html{% endblock %}
+
+{# end web/app/main/templates/admin.html #}
- Simplify template for routes
/
,/index
, and/home
.
{% extends "base.html" %}
-{% block title %}{{super()}} - Home{% endblock %}
+<!-- BLOCK: content -->
{% block content %}
-<h1>{{ page_title }}</h1>
-<p>template = /web/app/main/templates/index.html</p>
+
+<p>This is the Homepage.</p>
+
{% endblock %}
+
+
+<!-- BLOCK: templates -->
+{% block templates %}{{super()}} - index.html{% endblock %}
+
+{# end web/app/main/templates/index.html #}
- Update navigation.
<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="#">ItemOne</a></li>
- <li><a href="#">ItemTwo</a></li>
+ <li><a href="#">Items</a></li>
+ <li><a href="#">Users</a></li>
<li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">ItemThree <span class="caret"></span></a>
+ <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="#">ItemFour</a></li>
+ <li><a href="{{ url_for('item.item_list') }}">Items</a></li>
+ <li><a href="#">Users</a></li>
<li role="separator" class="divider"></li>
- <li><a href="#">ItemFive</a></li>
<li><a href="#">ItemSix</a></li>
</ul>
</li>
- Simplify route
/<page>/
. - Create debugging route
/info/request
.
def main_page(page):
try:
logging.debug( "main_page( %s )" % page )
- title = "FlaskApp Page"
- return render_template('%s.html' % (page), page_title=title)
+ return render_template('%s.html' % (page))
except TemplateNotFound:
logging.info('TemplateNotFound: %s.html' % (page))
abort(404)
...
result += '<br/><a href="'+url_for('.info_date')+'">show datetime</a>'
result += '<br/><a href="'+url_for('.info_config')+'">show app.config</a>'
result += '<br/><a href="'+url_for('.info_url_map')+'">show url_map</a>'
+ result += '<br/><a href="'+url_for('.info_request')+'">show request</a>'
return result
...
+@main.route('/info/request')
+def info_request():
+ result = '<b>REQUEST</b>'
+ result += "<br/><b>request.method</b> : %s" % request.method
+ result += "<br/><b>request.args</b>"
+ for key in sorted(request.args.keys()):
+ result += "<br/>[%s] : %s" % (key,request.args[key])
+ result += "<br/><b>request.form</b>"
+ for key in sorted(request.form.keys()):
+ result += "<br/>[%s] : %s" % (key,request.form[key])
+ result += "<br/><b>request.files</b>"
+ for key in sorted(request.files.keys()):
+ result += "<br/>[%s] : %s" % (key,request.files[key])
+ result += "<br/><b>request.cookies</b>"
+ for key in sorted(request.cookies.keys()):
+ result += "<br/>[%s] : %s" % (key,request.cookies[key])
+ result += "<br/><b>request.environ</b>"
+ for key in sorted(request.environ.keys()):
+ result += "<br/>[%s] : %s" % (key,request.environ[key])
+ return result
+form { padding: 15px; }
+.form-submit-buttons { padding-left: 15px; }
+
+.table-condensed { margin-bottom: 0; }
.table-borderless td,
.table-borderless th {
border: none !important;
}
class TestingConfig(AppConfig):
TESTING = True
LOG_LEVEL = logging.INFO
+ #SQLALCHEMY_ECHO = True
+ WTF_CSRF_ENABLED = False
- Install the
Flask-WTF
package. - see https://flask-wtf.readthedocs.io/
Flask==0.12.2
Flask-Bootstrap==3.3.7.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
Commit-v0.14 | Commit-v0.15 | Commit-v0.16
- 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