From 8c8803874e0ef6539277fbef1308b583d766c20f Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Sun, 17 Apr 2016 12:50:31 +0200 Subject: [PATCH 1/5] First step for WTForms --- app/main.py | 85 +++++++++---------------------- app/requirements.txt | 1 + app/templates/layout.html | 1 - app/templates/newPitStopForm.html | 59 +++++++++------------ 4 files changed, 50 insertions(+), 96 deletions(-) diff --git a/app/main.py b/app/main.py index de85eb0..c4aa102 100644 --- a/app/main.py +++ b/app/main.py @@ -10,6 +10,10 @@ import hashlib import time from functools import wraps +from flask_wtf import Form +from wtforms import DateField, IntegerField, DecimalField +from wtforms.validators import DataRequired + app = Flask(__name__) DATABASE = '/data/rollerverbrauch.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE @@ -106,71 +110,32 @@ def teardown_request(exception): def index(): return redirect(url_for('get_pit_stops')) +class CreatePitstopForm(Form): + date = DateField('Date of Pitstop') + odometer = IntegerField('Odometer (km)') + litres = DecimalField('Litres (l)', places=1) -@app.route('/pitstops', methods=['POST']) + +@app.route('/pitstops/createForm', methods=['GET', 'POST']) @requires_auth -def create_pit_stop(): +def create_pit_stop_form(): + form = CreatePitstopForm() + if form.validate_on_submit(): + new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data) + sqldb.session.add(new_stop) + sqldb.session.commit() + return redirect(url_for('get_pit_stops')) + last_pitstop = Pitstop.query.order_by(Pitstop.date.desc()).first() if last_pitstop is None: last_pitstop = Pitstop(0, 0, None) - error_msg = {} - - date_of_pitstop = request.form['date'] - try: - date_of_pitstop = datetime.strptime(date_of_pitstop, '%Y-%m-%d').strftime('%Y-%m-%d') - except ValueError: - error_msg['date'] = 'invalid date, only YYYY-MM-DD is allowed' - date_of_pitstop = request.form['date'] - - odometer = request.form['odometer'] - try: - odometer = int(odometer) - except ValueError: - error_msg['odometer'] = 'Illegal Value, only Integers allowed' - odometer = None - if odometer is not None and odometer <= last_pitstop.odometer: - error_msg['odometer'] = 'Illegal Value, new Value must be bigger as given value' - odometer = request.form['odometer'] - if odometer is None: - odometer = request.form['odometer'] - - litres = request.form['litres'] - try: - litres = float(litres) - except ValueError: - error_msg['litres'] = 'Illegal Value, only floating point allowed' - litres = None - if litres is not None and litres <= 0: - error_msg['litres'] = 'Litres must not be 0' - litres = request.form['litres'] - if litres is None: - litres = request.form['litres'] - - # error checking here - if len(error_msg) > 0: - data = {'last': {'date': date_of_pitstop, 'odometer': odometer, 'litres': litres}, 'error': error_msg} - return render_template('newPitStopForm.html', data=data) - new_stop = Pitstop(odometer, litres, datetime.strptime(date_of_pitstop, '%Y-%m-%d')) - sqldb.session.add(new_stop) - sqldb.session.commit() - - return redirect(url_for('get_pit_stops')) - - -@app.route('/pitstops/createForm', methods=['GET']) -@requires_auth -def create_pit_stop_form(): - last_stop = Pitstop.query.order_by(Pitstop.date.desc()).first() - if last_stop is None: - last_stop = Pitstop(0, 0, date(1970, 1, 1)) - values = dict() - values['odometer'] = last_stop.odometer - values['litres'] = last_stop.litres - values['date'] = time.strftime("%Y-%m-%d") - g.data['last'] = values - g.data['error'] = None - return render_template('newPitStopForm.html', data=g.data) + # dynamically set values + form.odometer.default = last_pitstop.odometer + form.litres.default = last_pitstop.litres + form.date.default = datetime.now() + form.process() + return render_template('newPitStopForm.html', form=form) @app.route('/pitstops', methods=['GET']) @@ -197,7 +162,7 @@ def get_statistics(): average_distance = 0 average_litres_fuelled = 0 average_litres_used = 0 - + if count > 0: sum_litres = 0 for pitstop in pitstops: diff --git a/app/requirements.txt b/app/requirements.txt index fb675a9..ce9fbfc 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -1,2 +1,3 @@ Flask Flask-SQLAlchemy +Flask-WTF diff --git a/app/templates/layout.html b/app/templates/layout.html index 2688f76..f1ae071 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -69,4 +69,3 @@ - diff --git a/app/templates/newPitStopForm.html b/app/templates/newPitStopForm.html index 190e0eb..47ff31d 100644 --- a/app/templates/newPitStopForm.html +++ b/app/templates/newPitStopForm.html @@ -1,43 +1,32 @@ {% extends "layout.html" %} {% block body %} +
+ {{ form.hidden_tag() }} - - - -
- -
- -

{{ data.error['date'] }}

-
-
- - -
- -
- -

{{ data.error['odometer'] }}

-
-
- - -
- -
- -

{{ data.error['litres'] }}

-
-
- -
-
- - + +
+ {{ form.date(size=20) }}
+ +
+ +
+ {{ form.odometer(size=20) }} +
+
+ +
+ +
+ {{ form.litres(size=20) }} +
+
+ + - -{% endblock %} \ No newline at end of file + + +{% endblock %} From 6f218e591ff441268c5050a9dbbfb1202c940fc2 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Sun, 17 Apr 2016 15:06:54 +0200 Subject: [PATCH 2/5] add render_field macro to layout.html --- app/templates/layout.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/templates/layout.html b/app/templates/layout.html index f1ae071..b26e23f 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -4,6 +4,25 @@
  • Manual
  • {%- endmacro %} +{% macro render_field(field) %} +
    + +
    + {{ field(**kwargs)|safe }} + {% if field.errors %} +
      + {% for error in field.errors %} +
    • {{ error }}
    • + {% endfor %} +
    + {% endif %} +
    +
    +{% endmacro %} + + From 864b7822acdbc511ac2e6180ac71aff4fafbea97 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 18 Apr 2016 07:25:40 +0200 Subject: [PATCH 3/5] Changed to WTForms including validation --- app/main.py | 40 +++++++++++++++++++++++-------- app/templates/layout.html | 6 ++--- app/templates/newPitStopForm.html | 25 +++---------------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/app/main.py b/app/main.py index c4aa102..d5fc88a 100644 --- a/app/main.py +++ b/app/main.py @@ -7,12 +7,10 @@ from flask import url_for from flask_sqlalchemy import SQLAlchemy import uuid import hashlib -import time from functools import wraps - from flask_wtf import Form from wtforms import DateField, IntegerField, DecimalField -from wtforms.validators import DataRequired +from wtforms.validators import DataRequired, ValidationError app = Flask(__name__) DATABASE = '/data/rollerverbrauch.db' @@ -110,26 +108,48 @@ def teardown_request(exception): def index(): return redirect(url_for('get_pit_stops')) + +def date_check(form, field): + if field.data < form.pitstop.date: + raise ValidationError('The new date must after %s' % form.pitstop.date) + + +def odometer_check(form, field): + if field.data <= form.pitstop.odometer: + raise ValidationError('The new odometer value must be higher than %i km' % form.pitstop.odometer) + + +def litres_check(form, field): + if field.data is not None and field.data <= 0: + raise ValidationError('You must fuel at least 0.1 l') + + class CreatePitstopForm(Form): - date = DateField('Date of Pitstop') - odometer = IntegerField('Odometer (km)') - litres = DecimalField('Litres (l)', places=1) + date = DateField('Date of Pitstop', validators=[date_check]) + odometer = IntegerField('Odometer (km)', validators=[odometer_check]) + litres = DecimalField('Litres (l)', places=1, validators=[litres_check]) + pitstop = None + + def set_pitstop(self, pitstop): + self.pitstop = pitstop @app.route('/pitstops/createForm', methods=['GET', 'POST']) @requires_auth def create_pit_stop_form(): + last_pitstop = Pitstop.query.order_by(Pitstop.id.desc()).first() + if last_pitstop is None: + last_pitstop = Pitstop(0, 0, datetime.now()) + form = CreatePitstopForm() + form.set_pitstop(last_pitstop) if form.validate_on_submit(): + new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data) sqldb.session.add(new_stop) sqldb.session.commit() return redirect(url_for('get_pit_stops')) - last_pitstop = Pitstop.query.order_by(Pitstop.date.desc()).first() - if last_pitstop is None: - last_pitstop = Pitstop(0, 0, None) - # dynamically set values form.odometer.default = last_pitstop.odometer form.litres.default = last_pitstop.litres diff --git a/app/templates/layout.html b/app/templates/layout.html index b26e23f..a71985e 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -12,11 +12,11 @@
    {{ field(**kwargs)|safe }} {% if field.errors %} -
      +

      {% for error in field.errors %} -

    • {{ error }}
    • + {{ error }} {% endfor %} -
    +

    {% endif %}
    diff --git a/app/templates/newPitStopForm.html b/app/templates/newPitStopForm.html index 47ff31d..7cb043b 100644 --- a/app/templates/newPitStopForm.html +++ b/app/templates/newPitStopForm.html @@ -3,28 +3,9 @@ {% block body %}
    {{ form.hidden_tag() }} - -
    - -
    - {{ form.date(size=20) }} -
    -
    - -
    - -
    - {{ form.odometer(size=20) }} -
    -
    - -
    - -
    - {{ form.litres(size=20) }} -
    -
    - + {{ render_field(form.date) }} + {{ render_field(form.odometer) }} + {{ render_field(form.litres) }}
    From 01019c22d9314c69fb149f61a9f71c09f70bea95 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 18 Apr 2016 08:13:27 +0200 Subject: [PATCH 4/5] add switch for debugger --- README.md | 8 +++++++- app/main.py | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 65a01f0..5054800 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,13 @@ `docker build --tag=$(basename $PWD) .` ## run in development -`docker run --name rollerverbrauch -ti -v `pwd`/app:/app -p 5000:5000 rollerverbrauch` + +Include the development version of the code as volume, so the app gets +reloaded automatically. The sqlite file will be stored in *tmp* so it +can be inspected with tools like *sqlite3*. The switch *DEBUG* enables +debugging during development. + +`docker run --name rollerverbrauch -ti -v `pwd`/app:/app -v /tmp/pitstops/:/data -e DEBUG=True -p 5000:5000 rollerverbrauch` ## run in production `docker run --name pitstops -d -v /data/pitstops/:/data -p 80:5000 rollerverbrauch` \ No newline at end of file diff --git a/app/main.py b/app/main.py index de85eb0..da1ebd4 100644 --- a/app/main.py +++ b/app/main.py @@ -9,13 +9,13 @@ import uuid import hashlib import time from functools import wraps +import os app = Flask(__name__) DATABASE = '/data/rollerverbrauch.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE sqldb = SQLAlchemy(app) -DEBUG = True SECRET_KEY = 'development key' app.config.from_object(__name__) @@ -236,4 +236,5 @@ def prepare_pit_stops(pss): return pitstops if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0') + DEBUG = 'DEBUG' in os.environ and os.environ['DEBUG'] != 'False' + app.run(debug=DEBUG, host='0.0.0.0') From 4c97bd86db0c7aae7aff1f02bfae0d8a8b056d81 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 18 Apr 2016 20:58:20 +0200 Subject: [PATCH 5/5] Fixed date vs datetime --- app/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 88a435f..2ed0409 100644 --- a/app/main.py +++ b/app/main.py @@ -139,12 +139,11 @@ class CreatePitstopForm(Form): def create_pit_stop_form(): last_pitstop = Pitstop.query.order_by(Pitstop.id.desc()).first() if last_pitstop is None: - last_pitstop = Pitstop(0, 0, datetime.now()) + last_pitstop = Pitstop(0, 0, date.today()) form = CreatePitstopForm() form.set_pitstop(last_pitstop) if form.validate_on_submit(): - new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data) sqldb.session.add(new_stop) sqldb.session.commit() @@ -153,7 +152,7 @@ def create_pit_stop_form(): # dynamically set values form.odometer.default = last_pitstop.odometer form.litres.default = last_pitstop.litres - form.date.default = datetime.now() + form.date.default = date.today() form.process() return render_template('newPitStopForm.html', form=form)