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 27cfc57..4a75aa5 100644 --- a/app/main.py +++ b/app/main.py @@ -9,15 +9,17 @@ from flask.ext.security import Security, SQLAlchemyUserDatastore, \ UserMixin, RoleMixin, login_required, utils 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, ValidationError +import os app = Flask(__name__) DATABASE = '/data/rollerverbrauch.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE db = SQLAlchemy(app) -app.config['DEBUG'] = True app.config['SECRET_KEY'] = 'development key' app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxx' @@ -109,70 +111,52 @@ def index(): return redirect(url_for('get_pit_stops')) -@app.route('/pitstops', methods=['POST']) -@login_required -def create_pit_stop(): - 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')) - db.session.add(new_stop) - db.session.commit() - - 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) -@app.route('/pitstops/createForm', methods=['GET']) +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', 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']) @login_required 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) + last_pitstop = Pitstop.query.order_by(Pitstop.id.desc()).first() + if last_pitstop is None: + 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) + db.session.add(new_stop) + db.session.commit() + return redirect(url_for('get_pit_stops')) + + # dynamically set values + form.odometer.default = last_pitstop.odometer + form.litres.default = last_pitstop.litres + form.date.default = date.today() + form.process() + return render_template('newPitStopForm.html', form=form) @app.route('/pitstops', methods=['GET']) @@ -238,4 +222,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') diff --git a/app/requirements.txt b/app/requirements.txt index 53a2faa..4887658 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -1,3 +1,4 @@ Flask Flask-SQLAlchemy Flask-Security +Flask-WTF diff --git a/app/templates/layout.html b/app/templates/layout.html index 7b09223..f6ada95 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -7,6 +7,25 @@ {% endif %} {%- endmacro %} +{% macro render_field_with_errors(field) %} +
+ {% for error in field.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +