Skip to content

Commit v0.15

kwmccabe edited this page Apr 17, 2018 · 7 revisions

v0.15 - Flask-WTF: item_create, item_edit, item_delete


Files changed (14)

File web/app/init.py MODIFIED

  • 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')

File web/app/item/forms.py ADDED

  • Create FlaskForms CreateItemForm and EditItemForm.
  • Function filter_keyname converts to lowercase and removes invalid characters.
  • Function validate_keyname checks against pre-existing item keyname.
+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
+

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

  • 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 #}

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

  • 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 #}

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

  • 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>

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

  • 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">

File web/app/item/views.py MODIFIED

  • 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)

File web/app/main/templates/admin.html ADDED

  • 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 #}

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

  • 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 #}

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

  • 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>

File web/app/main/views.py MODIFIED

  • 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

File web/app/static/css/flaskapp.css MODIFIED

+form { padding: 15px; }
+.form-submit-buttons { padding-left: 15px; }
+
+.table-condensed { margin-bottom: 0; }
 .table-borderless td,
 .table-borderless th {
     border: none !important;
 }

File web/config.py MODIFIED

 class TestingConfig(AppConfig):
     TESTING = True
     LOG_LEVEL = logging.INFO
+    #SQLALCHEMY_ECHO = True
+    WTF_CSRF_ENABLED = False

File web/requirements.txt MODIFIED

 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

Clone this wiki locally