From 1072142a1bc1784fe8939be38ddb1a86445c99b4 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Sun, 26 Jun 2016 08:19:28 +0200 Subject: [PATCH 01/13] adds consumables to database this adds a new consumables entity to the database and the possibility in the admin page to add these. --- app/rollerverbrauch/__init__.py | 36 +++++++++++++++--- app/rollerverbrauch/entities.py | 25 +++++++++++- app/rollerverbrauch/forms.py | 9 ++++- app/rollerverbrauch/templates/admin.html | 48 ++++++++++++++++++++---- 4 files changed, 104 insertions(+), 14 deletions(-) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index a815566..3a79605 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -31,13 +31,15 @@ from rollerverbrauch.forms import \ SelectVehicleForm, \ DeleteAccountForm, \ DeletePitStopForm, \ - EditPitstopForm + EditPitstopForm, \ + CreateConsumableForm from rollerverbrauch.entities import \ User, \ Role, \ Pitstop, \ - Vehicle + Vehicle, \ + Consumable # required to activate the filters import rollerverbrauch.filters @@ -65,6 +67,11 @@ def user_registered_sighandler(app, user, confirm_token): @app.before_first_request def before_first_request(): db.create_all() + print(""" + ========================= + TEST + ======================== + """) user_datastore.find_or_create_role(name='admin', description='Role for administrators') user_datastore.find_or_create_role(name='user', description='Role for all users.') db.session.commit() @@ -105,6 +112,7 @@ def index(): def edit_vehicle(vid): vehicle = Vehicle.query.filter(Vehicle.id == vid).first() form = EditVehicleForm() + #form.consumables.choices = [(g.id, '%s (%s)' % (g.name, g.unit)) for g in Consumable.query.all()] if form.validate_on_submit(): if not tools.check_vehicle_name_is_unique(current_user, form.name): @@ -115,8 +123,10 @@ def edit_vehicle(vid): return redirect(url_for('get_account_page')) form.name.default = vehicle.name + #form.consumables.default = [g.id for g in vehicle.consumables] + #print(form.consumables.name) form.process() - return render_template('editVehicleForm.html', form=form) + return render_template('editVehicleForm.html', form=form, vehicle=vehicle) @app.route('/account/delete_vehicle/', methods=['GET', 'POST']) @@ -287,8 +297,24 @@ def get_manual(): @app.route('/admin', methods=['GET']) @roles_required('admin') def get_admin_page(): - g.data['users'] = User.query.all() - return render_template('admin.html', data=g.data) + users = User.query.all() + consumables = Consumable.query.all() + return render_template('admin.html', users=users, consumables=consumables) + + +@app.route('/admin/create_consumable', methods=['GET', 'POST']) +@login_required +def create_consumable(): + form = CreateConsumableForm() + + if form.validate_on_submit(): + new_consumable = Consumable(form.name.data, form.unit.data) + db.session.add(new_consumable) + db.session.commit() + tools.db_log_add(new_consumable) + return redirect(url_for('get_admin_page')) + + return render_template('createConsumableForm.html', form=form) @app.route('/account', methods=['GET']) diff --git a/app/rollerverbrauch/entities.py b/app/rollerverbrauch/entities.py index 0a72a29..83e5cd7 100644 --- a/app/rollerverbrauch/entities.py +++ b/app/rollerverbrauch/entities.py @@ -5,6 +5,10 @@ roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) +vehicles_consumables = db.Table('vehicles_consumables', + db.Column('vehicle_id', db.Integer(), db.ForeignKey('vehicle.id')), + db.Column('consumable_id', db.Integer(), db.ForeignKey('consumable.id'))) + class Role(db.Model, RoleMixin): id = db.Column(db.Integer(), primary_key=True) @@ -44,6 +48,11 @@ class Vehicle(db.Model): pitstops = db.relationship( 'Pitstop' ) + consumables = db.relationship( + 'Consumable', + secondary=vehicles_consumables, + backref=db.backref('consumes', lazy='dynamic') + ) __table_args__ = (db.UniqueConstraint('owner_id', 'name', name='_owner_name_uniq'),) def __init__(self, name): @@ -68,4 +77,18 @@ class Pitstop(db.Model): self.costs = costs def __repr__(self): - return '' % (self.odometer, self.litres, self.date, self.vehicle_id) \ No newline at end of file + return '' % \ + (self.odometer, self.litres, self.date, self.vehicle_id) + + +class Consumable(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255)) + unit = db.Column(db.String(255)) + + def __init__(self, name, unit): + self.name = name + self.unit = unit + + def __repr__(self): + return '' % (self.name, self.unit) diff --git a/app/rollerverbrauch/forms.py b/app/rollerverbrauch/forms.py index c6bb7c2..60efdb9 100644 --- a/app/rollerverbrauch/forms.py +++ b/app/rollerverbrauch/forms.py @@ -1,5 +1,5 @@ from flask_wtf import Form -from wtforms import DateField, IntegerField, DecimalField, StringField, SelectField, SubmitField +from wtforms import DateField, IntegerField, DecimalField, StringField, SelectField, SubmitField, SelectMultipleField, BooleanField from wtforms.validators import ValidationError, Length from datetime import date @@ -31,6 +31,7 @@ def edit_costs_check(form, field): if costs_check_required and field.data is not None and field.data <= 0: raise ValidationError('Costs must be above 0.01 €.') + class SelectVehicleForm(Form): vehicle = SelectField('Vehicle', coerce=int) submit = SubmitField(label='Do it!') @@ -50,6 +51,7 @@ class CreatePitstopForm(Form): class EditVehicleForm(Form): name = StringField('Name', validators=[Length(1, 255)]) + #consumables = SelectMultipleField('Consumables') submit = SubmitField(label='Do it!') @@ -77,3 +79,8 @@ class EditPitstopForm(Form): self.last_pitstop = last_pitstop +class CreateConsumableForm(Form): + name = StringField('Name', validators=[Length(1, 255)]) + unit = StringField('Unit', validators=[Length(1, 255)]) + submit = SubmitField(label='Do it!') + diff --git a/app/rollerverbrauch/templates/admin.html b/app/rollerverbrauch/templates/admin.html index 8b25d30..4323d83 100644 --- a/app/rollerverbrauch/templates/admin.html +++ b/app/rollerverbrauch/templates/admin.html @@ -1,18 +1,52 @@ {% extends "layout.html" %} {% block body %} -
-
+

Admin

+
Users
-

Admin

- We have {{ data.users|length }} users so far: + We have {{ users|length }} users so far:
    - {% for user in data.users %} + {% for user in users %}
  • {{user.email}}
  • {% endfor %} -
-
+ +
+
Consumables
+ + + + + + + + + {% for consumable in consumables %} + + + + + + {% endfor %} + +
+ Name + + Unit + + Actions +
+ {{ consumable.name }} + + {{ consumable.unit }} + +
+
+ {% endblock %} From 5e24b9779eab2acc2193f30f43df5250b19a9020 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 27 Jun 2016 19:48:04 +0200 Subject: [PATCH 02/13] makes the name of a consumable unique makes the column unique and extends the create form to respect the uniqueness. --- app/rollerverbrauch/__init__.py | 16 ++++++++++++++-- app/rollerverbrauch/entities.py | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 9b7bcb6..46bd409 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -9,6 +9,7 @@ from flask.ext.security import Security, SQLAlchemyUserDatastore, \ from flask.ext.security import user_registered from flask_security.core import current_user from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.exc import IntegrityError from flask.ext.security.forms import LoginForm app = Flask(__name__) @@ -307,11 +308,22 @@ def get_admin_page(): def create_consumable(): form = CreateConsumableForm() + # preinitialize the defaults with potentially existing values from a try before + if form.name.data is not None: + form.name.default = form.name.data + if form.unit.data is not None: + form.unit.default = form.unit.data + if form.validate_on_submit(): new_consumable = Consumable(form.name.data, form.unit.data) db.session.add(new_consumable) - db.session.commit() - tools.db_log_add(new_consumable) + try: + db.session.commit() + tools.db_log_add(new_consumable) + except IntegrityError: + db.session.rollback() + form.name.errors.append('"%s" is not unique.' % (form.name.data)) + return render_template('createConsumableForm.html', form=form) return redirect(url_for('get_admin_page')) return render_template('createConsumableForm.html', form=form) diff --git a/app/rollerverbrauch/entities.py b/app/rollerverbrauch/entities.py index 83e5cd7..c0b066f 100644 --- a/app/rollerverbrauch/entities.py +++ b/app/rollerverbrauch/entities.py @@ -83,7 +83,7 @@ class Pitstop(db.Model): class Consumable(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(255)) + name = db.Column(db.String(255), unique=True) unit = db.Column(db.String(255)) def __init__(self, name, unit): From 60f2e9f4e4d9ee6dc7623e062b49bc8eb17df327 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 27 Jun 2016 20:08:49 +0200 Subject: [PATCH 03/13] adds form to create a consumable --- .../templates/createConsumableForm.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/rollerverbrauch/templates/createConsumableForm.html diff --git a/app/rollerverbrauch/templates/createConsumableForm.html b/app/rollerverbrauch/templates/createConsumableForm.html new file mode 100644 index 0000000..1634ccf --- /dev/null +++ b/app/rollerverbrauch/templates/createConsumableForm.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Create consumable

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.name) }} + {{ render_field_with_errors(form.unit) }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+
+{% endblock %} From fa4126be6681cf62035fca75581d4ff88d5e64c2 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 27 Jun 2016 21:35:00 +0200 Subject: [PATCH 04/13] adds update & delete for consumables adds functionality to * update * delete for consumables --- app/rollerverbrauch/__init__.py | 59 ++++++++++++++++++- app/rollerverbrauch/entities.py | 9 ++- app/rollerverbrauch/forms.py | 9 +++ app/rollerverbrauch/templates/admin.html | 8 +++ .../templates/deleteConsumableForm.html | 18 ++++++ .../templates/editConsumableForm.html | 19 ++++++ 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 app/rollerverbrauch/templates/deleteConsumableForm.html create mode 100644 app/rollerverbrauch/templates/editConsumableForm.html diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 46bd409..1d01bde 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -33,7 +33,9 @@ from rollerverbrauch.forms import \ DeleteAccountForm, \ DeletePitStopForm, \ EditPitstopForm, \ - CreateConsumableForm + CreateConsumableForm, \ + EditConsumableForm, \ + DeletConsumableForm from rollerverbrauch.entities import \ User, \ @@ -300,10 +302,12 @@ def get_manual(): def get_admin_page(): users = User.query.all() consumables = Consumable.query.all() + for consumable in consumables: + consumable.in_use = Pitstop.query.filter(Pitstop.consumable_id == consumable.id).count() > 0 return render_template('admin.html', users=users, consumables=consumables) -@app.route('/admin/create_consumable', methods=['GET', 'POST']) +@app.route('/admin/consumable/create', methods=['GET', 'POST']) @login_required def create_consumable(): form = CreateConsumableForm() @@ -329,6 +333,57 @@ def create_consumable(): return render_template('createConsumableForm.html', form=form) +@app.route('/admin/consumable/delete/', methods=['GET', 'POST']) +@login_required +def delete_consumable(cid): + consumable = Consumable.query.filter(Consumable.id == cid).first() + if consumable is None: + return redirect(url_for('get_admin_page')) + + form = DeletConsumableForm() + + if form.validate_on_submit(): + db.session.delete(consumable) + db.session.commit() + tools.db_log_delete(consumable) + return redirect(url_for('get_admin_page')) + + return render_template('deleteConsumableForm.html', form=form, consumable=consumable) + + +@app.route('/admin/consumable/edit/', methods=['GET', 'POST']) +@login_required +def edit_consumable(cid): + consumable = Consumable.query.filter(Consumable.id == cid).first() + if consumable is None: + return redirect(url_for('get_admin_page')) + + form = EditConsumableForm() + + form.name.default = consumable.name + form.unit.default = consumable.unit + + # preinitialize the defaults with potentially existing values from a try before + if form.name.data is not None: + form.name.default = form.name.data + if form.unit.data is not None: + form.unit.default = form.unit.data + + if form.validate_on_submit(): + consumable.name = form.name.data + consumable.unit = form.unit.data + try: + db.session.commit() + tools.db_log_update(consumable) + except IntegrityError: + db.session.rollback() + form.name.errors.append('"%s" is not unique.' % (form.name.data)) + return render_template('editConsumableForm.html', form=form) + return redirect(url_for('get_admin_page')) + + return render_template('editConsumableForm.html', form=form) + + @app.route('/account', methods=['GET']) @login_required def get_account_page(): diff --git a/app/rollerverbrauch/entities.py b/app/rollerverbrauch/entities.py index c0b066f..d63a3dc 100644 --- a/app/rollerverbrauch/entities.py +++ b/app/rollerverbrauch/entities.py @@ -66,19 +66,22 @@ class Pitstop(db.Model): id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date) odometer = db.Column(db.Integer) + consumable_id = db.Column(db.Integer, db.ForeignKey('consumable.id')) + consumable = db.relationship('Consumable') litres = db.Column(db.Numeric(5, 2)) costs = db.Column(db.Numeric(5, 2), default=0) vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id')) - def __init__(self, odometer, litres, date, costs): + def __init__(self, odometer, litres, date, costs, consumable_id): self.odometer = odometer self.litres = litres self.date = date self.costs = costs + self.consumable_id = consumable_id def __repr__(self): - return '' % \ - (self.odometer, self.litres, self.date, self.vehicle_id) + return '' % \ + (self.odometer, self.litres, self.date, self.vehicle_id, self.consumable_id) class Consumable(db.Model): diff --git a/app/rollerverbrauch/forms.py b/app/rollerverbrauch/forms.py index 60efdb9..84c85e9 100644 --- a/app/rollerverbrauch/forms.py +++ b/app/rollerverbrauch/forms.py @@ -84,3 +84,12 @@ class CreateConsumableForm(Form): unit = StringField('Unit', validators=[Length(1, 255)]) submit = SubmitField(label='Do it!') + +class EditConsumableForm(Form): + name = StringField('Name', validators=[Length(1, 255)]) + unit = StringField('Unit', validators=[Length(1, 255)]) + submit = SubmitField(label='Do it!') + + +class DeletConsumableForm(Form): + submit = SubmitField(label='Do it!') diff --git a/app/rollerverbrauch/templates/admin.html b/app/rollerverbrauch/templates/admin.html index 4323d83..e9ec71c 100644 --- a/app/rollerverbrauch/templates/admin.html +++ b/app/rollerverbrauch/templates/admin.html @@ -42,6 +42,14 @@ {{ consumable.unit }} + + edit + + {% if not consumable.in_use %} + + delete + + {% endif %} {% endfor %} diff --git a/app/rollerverbrauch/templates/deleteConsumableForm.html b/app/rollerverbrauch/templates/deleteConsumableForm.html new file mode 100644 index 0000000..40a6e88 --- /dev/null +++ b/app/rollerverbrauch/templates/deleteConsumableForm.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Delete vehicle '{{consumable.name}}'?

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.submit) }} + +
+
+
+
+
+{% endblock %} diff --git a/app/rollerverbrauch/templates/editConsumableForm.html b/app/rollerverbrauch/templates/editConsumableForm.html new file mode 100644 index 0000000..97510d5 --- /dev/null +++ b/app/rollerverbrauch/templates/editConsumableForm.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Edit consumable

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.name) }} + {{ render_field_with_errors(form.unit) }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+
+{% endblock %} From a0be1221869cfa2d78b5be0d33a2920d53d62efa Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 27 Jun 2016 22:09:42 +0200 Subject: [PATCH 05/13] reworked the crud funtions for vehicles --- app/rollerverbrauch/__init__.py | 49 +++++++++++++------ .../{selectVehice.html => selectVehicle.html} | 0 2 files changed, 33 insertions(+), 16 deletions(-) rename app/rollerverbrauch/templates/{selectVehice.html => selectVehicle.html} (100%) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 1d01bde..350e46e 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -110,29 +110,37 @@ def index(): return render_template('index.html', login_user_form=LoginForm(), data=data) -@app.route('/account/edit_vehicle/', methods=['GET', 'POST']) +@app.route('/account/vehicle/edit/', methods=['GET', 'POST']) @login_required def edit_vehicle(vid): vehicle = Vehicle.query.filter(Vehicle.id == vid).first() + + # prevent edit of foreign vehicles + if vehicle not in current_user.vehicles: + return redirect(url_for('get_account_page')) + form = EditVehicleForm() - #form.consumables.choices = [(g.id, '%s (%s)' % (g.name, g.unit)) for g in Consumable.query.all()] + + if form.name.data is not None: + form.name.default = form.name.data if form.validate_on_submit(): - if not tools.check_vehicle_name_is_unique(current_user, form.name): - return render_template('editVehicleForm.html', form=form) vehicle.name = form.name.data - db.session.commit() - tools.db_log_update(vehicle) + try: + db.session.commit() + tools.db_log_update(vehicle) + except IntegrityError: + db.session.rollback() + form.name.errors.append('"%s" is not unique.' % (form.name.data)) + return render_template('editVehicleForm.html', form=form) return redirect(url_for('get_account_page')) form.name.default = vehicle.name - #form.consumables.default = [g.id for g in vehicle.consumables] - #print(form.consumables.name) form.process() return render_template('editVehicleForm.html', form=form, vehicle=vehicle) -@app.route('/account/delete_vehicle/', methods=['GET', 'POST']) +@app.route('/account/vehicle/delete/', methods=['GET', 'POST']) @login_required def delete_vehicle(vid): vehicle = Vehicle.query.filter(Vehicle.id == vid).first() @@ -141,6 +149,9 @@ def delete_vehicle(vid): if vehicle not in current_user.vehicles: return redirect(url_for('get_account_page')) + if len(current_user.vehicles) == 1: + return redirect(url_for('get_account_page')) + form = DeleteVehicleForm() if form.validate_on_submit(): @@ -152,20 +163,26 @@ def delete_vehicle(vid): return render_template('deleteVehicleForm.html', form=form, vehicle=vehicle) -@app.route('/account/create_vehicle', methods=['GET', 'POST']) +@app.route('/account/vehicle/create', methods=['GET', 'POST']) @login_required def create_vehicle(): form = EditVehicleForm() + if form.name.data is not None: + form.name.default = form.name.data + if form.validate_on_submit(): vehicle_name = form.name.data - if not tools.check_vehicle_name_is_unique(current_user, form.name): - return render_template('createVehicleForm.html', form=form) new_vehicle = Vehicle(vehicle_name) db.session.add(new_vehicle) current_user.vehicles.append(new_vehicle) - db.session.commit() - tools.db_log_add(new_vehicle) + try: + db.session.commit() + tools.db_log_add(new_vehicle) + except IntegrityError: + db.session.rollback() + form.name.errors.append('"%s" is not unique.' % (form.name.data)) + return render_template('createVehicleForm.html', form=form) return redirect(url_for('get_account_page')) return render_template('createVehicleForm.html', form=form) @@ -180,11 +197,11 @@ def select_vehicle_for_new_pitstop(): if form.validate_on_submit(): vehicle = Vehicle.query.filter(Vehicle.id == form.vehicle.data).first() if vehicle not in current_user.vehicles: - return render_template('selectVehice.html', form=form) + return render_template('selectVehicle.html', form=form) return redirect(url_for('create_pit_stop_form', vid=form.vehicle.data)) - return render_template('selectVehice.html', form=form) + return render_template('selectVehicle.html', form=form) @app.route('/pitstops/create/', methods=['GET', 'POST']) diff --git a/app/rollerverbrauch/templates/selectVehice.html b/app/rollerverbrauch/templates/selectVehicle.html similarity index 100% rename from app/rollerverbrauch/templates/selectVehice.html rename to app/rollerverbrauch/templates/selectVehicle.html From fe4236eead5b2924720a1704c6e9259beb4b018b Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 28 Jun 2016 21:35:04 +0200 Subject: [PATCH 06/13] renamed template for create a pitstop --- app/rollerverbrauch/__init__.py | 4 ++-- .../templates/{newPitStopForm.html => createPitStopForm.html} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename app/rollerverbrauch/templates/{newPitStopForm.html => createPitStopForm.html} (100%) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 350e46e..8f30bb6 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -214,7 +214,7 @@ def create_pit_stop_form(vid): if len(vehicle.pitstops) > 0: last_pitstop = vehicle.pitstops[-1] else: - last_pitstop = Pitstop(0, 0, date(1970, 1, 1), 0) + last_pitstop = Pitstop(0, 0, date(1970, 1, 1), 0, None) form = CreatePitstopForm() form.set_pitstop(last_pitstop) @@ -237,7 +237,7 @@ def create_pit_stop_form(vid): 'odometer': 'Odometer must be greater than %s km.' % (str(last_pitstop.odometer)), 'costs': 'Costs must be higher than 0.01 €.' } - return render_template('newPitStopForm.html', form=form, vehicle=vehicle, messages = messages) + return render_template('createPitStopForm.html', form=form, vehicle=vehicle, messages=messages) @app.route('/pitstops/delete/', methods=['GET', 'POST']) diff --git a/app/rollerverbrauch/templates/newPitStopForm.html b/app/rollerverbrauch/templates/createPitStopForm.html similarity index 100% rename from app/rollerverbrauch/templates/newPitStopForm.html rename to app/rollerverbrauch/templates/createPitStopForm.html From 907d0435d1dbd0eff22490f097dd6673e48ad258 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 28 Jun 2016 23:33:24 +0200 Subject: [PATCH 07/13] consumables for vehicles can be edited --- app/rollerverbrauch/__init__.py | 26 +++++++++++++++++-- app/rollerverbrauch/entities.py | 7 +++-- app/rollerverbrauch/forms.py | 4 +-- app/rollerverbrauch/templates/account.html | 3 ++- app/rollerverbrauch/templates/admin.html | 12 ++++++--- .../templates/createVehicleForm.html | 1 + .../templates/editVehicleForm.html | 1 + 7 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 8f30bb6..57fd72a 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -120,12 +120,22 @@ def edit_vehicle(vid): return redirect(url_for('get_account_page')) form = EditVehicleForm() + form.consumables.choices = [(g.id, g.name) for g in Consumable.query.all()] + + if not form.consumables.data: + form.consumables.default = [g.id for g in vehicle.consumables] if form.name.data is not None: form.name.default = form.name.data if form.validate_on_submit(): vehicle.name = form.name.data + # we cannot delete consumables where there are pitstops for => report error + vehicle.consumables = [] + for consumable_id in form.consumables.data: + consumable = Consumable.query.get(consumable_id) + if consumable is not None: + vehicle.consumables.append(consumable) try: db.session.commit() tools.db_log_update(vehicle) @@ -167,13 +177,25 @@ def delete_vehicle(vid): @login_required def create_vehicle(): form = EditVehicleForm() + form.consumables.choices = [(g.id, g.name) for g in Consumable.query.all()] if form.name.data is not None: form.name.default = form.name.data + if form.consumables.data: + form.consumables.default = form.consumables.data + if form.validate_on_submit(): + if len(form.consumables.data) == 0: + form.consumables.errors.append('At least one consumable must be selected.') + return render_template('createVehicleForm.html', form=form) + vehicle_name = form.name.data new_vehicle = Vehicle(vehicle_name) + for consumable_id in form.consumables.data: + consumable = Consumable.query.get(consumable_id) + if consumable is not None: + new_vehicle.consumables.append(consumable) db.session.add(new_vehicle) current_user.vehicles.append(new_vehicle) try: @@ -274,7 +296,7 @@ def edit_pit_stop_form(pid): if last_pitstop_pos > 0: last_pitstop = vehicle.pitstops[last_pitstop_pos] else: - last_pitstop = Pitstop(0, 0, date(1970, 1, 1)) + last_pitstop = Pitstop(0, 0, date(1970, 1, 1), 0, 0) form = EditPitstopForm() form.set_pitstop(last_pitstop) @@ -320,7 +342,7 @@ def get_admin_page(): users = User.query.all() consumables = Consumable.query.all() for consumable in consumables: - consumable.in_use = Pitstop.query.filter(Pitstop.consumable_id == consumable.id).count() > 0 + consumable.in_use = len(consumable.vehicles) > 0 return render_template('admin.html', users=users, consumables=consumables) diff --git a/app/rollerverbrauch/entities.py b/app/rollerverbrauch/entities.py index d63a3dc..0a9c06e 100644 --- a/app/rollerverbrauch/entities.py +++ b/app/rollerverbrauch/entities.py @@ -50,8 +50,7 @@ class Vehicle(db.Model): ) consumables = db.relationship( 'Consumable', - secondary=vehicles_consumables, - backref=db.backref('consumes', lazy='dynamic') + secondary=vehicles_consumables ) __table_args__ = (db.UniqueConstraint('owner_id', 'name', name='_owner_name_uniq'),) @@ -88,6 +87,10 @@ class Consumable(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), unique=True) unit = db.Column(db.String(255)) + vehicles = db.relationship( + 'Vehicle', + secondary=vehicles_consumables + ) def __init__(self, name, unit): self.name = name diff --git a/app/rollerverbrauch/forms.py b/app/rollerverbrauch/forms.py index 84c85e9..ffc33ac 100644 --- a/app/rollerverbrauch/forms.py +++ b/app/rollerverbrauch/forms.py @@ -27,7 +27,7 @@ def costs_check(form, field): def edit_costs_check(form, field): - costs_check_required = (form.costs.default is not None and form.costs.default > 0) + costs_check_required = (form.costs.default is not None and form.costs.default > 0) if costs_check_required and field.data is not None and field.data <= 0: raise ValidationError('Costs must be above 0.01 €.') @@ -51,7 +51,7 @@ class CreatePitstopForm(Form): class EditVehicleForm(Form): name = StringField('Name', validators=[Length(1, 255)]) - #consumables = SelectMultipleField('Consumables') + consumables = SelectMultipleField('Consumables', coerce=int,validators=[]) submit = SubmitField(label='Do it!') diff --git a/app/rollerverbrauch/templates/account.html b/app/rollerverbrauch/templates/account.html index cc1ed3a..43a0d33 100644 --- a/app/rollerverbrauch/templates/account.html +++ b/app/rollerverbrauch/templates/account.html @@ -36,7 +36,8 @@ {{ vehicle.name }} - {{ vehicle.pitstops | length }} pitstops + {{ vehicle.pitstops | length }} pitstops
+ {{ vehicle.consumables | length }} consumables diff --git a/app/rollerverbrauch/templates/admin.html b/app/rollerverbrauch/templates/admin.html index e9ec71c..8bee9ef 100644 --- a/app/rollerverbrauch/templates/admin.html +++ b/app/rollerverbrauch/templates/admin.html @@ -29,6 +29,9 @@ Unit + + Used by + Actions @@ -42,14 +45,17 @@ {{ consumable.unit }} - - edit - + {{ consumable.vehicles | length }} vehicles + + {% if not consumable.in_use %} delete {% endif %} + + edit + {% endfor %} diff --git a/app/rollerverbrauch/templates/createVehicleForm.html b/app/rollerverbrauch/templates/createVehicleForm.html index 2dce35b..9e5ced5 100644 --- a/app/rollerverbrauch/templates/createVehicleForm.html +++ b/app/rollerverbrauch/templates/createVehicleForm.html @@ -9,6 +9,7 @@
{{ form.hidden_tag() }} {{ render_field_with_errors(form.name) }} + {{ render_field_with_errors(form.consumables) }} {{ render_field_with_errors(form.submit) }}
diff --git a/app/rollerverbrauch/templates/editVehicleForm.html b/app/rollerverbrauch/templates/editVehicleForm.html index 8d2827f..c5b18dd 100644 --- a/app/rollerverbrauch/templates/editVehicleForm.html +++ b/app/rollerverbrauch/templates/editVehicleForm.html @@ -9,6 +9,7 @@
{{ form.hidden_tag() }} {{ render_field_with_errors(form.name) }} + {{ render_field_with_errors(form.consumables) }} {{ render_field_with_errors(form.submit) }}
From af5d4ae6b3814dcc0aa76e1e1da4ccc0b5325a72 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Wed, 29 Jun 2016 22:50:01 +0200 Subject: [PATCH 08/13] cann add pitstops for selected vehicles / consumables --- app/rollerverbrauch/__init__.py | 54 ++++++++++++++----- app/rollerverbrauch/forms.py | 5 ++ app/rollerverbrauch/templates/pitstops.html | 14 ++++- .../templates/selectConsumableForVehicle.html | 19 +++++++ app/rollerverbrauch/templates/statistics.html | 10 ++-- 5 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 app/rollerverbrauch/templates/selectConsumableForVehicle.html diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 57fd72a..c79ce53 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -35,7 +35,8 @@ from rollerverbrauch.forms import \ EditPitstopForm, \ CreateConsumableForm, \ EditConsumableForm, \ - DeletConsumableForm + DeletConsumableForm, \ + SelectConsumableForm from rollerverbrauch.entities import \ User, \ @@ -210,29 +211,53 @@ def create_vehicle(): return render_template('createVehicleForm.html', form=form) -@app.route('/pitstops/select_vehicle', methods=['GET', 'POST']) +@app.route('/pitstops/vehicle/select', methods=['GET', 'POST']) @login_required def select_vehicle_for_new_pitstop(): + if len(current_user.vehicles) == 1: + return redirect(url_for('select_consumable_for_new_pitstop', vid=current_user.vehicles[0].id)) + form = SelectVehicleForm() form.vehicle.choices = [(g.id, g.name) for g in current_user.vehicles] if form.validate_on_submit(): - vehicle = Vehicle.query.filter(Vehicle.id == form.vehicle.data).first() - if vehicle not in current_user.vehicles: - return render_template('selectVehicle.html', form=form) - - return redirect(url_for('create_pit_stop_form', vid=form.vehicle.data)) + return redirect(url_for('select_consumable_for_new_pitstop', vid=form.vehicle.data)) return render_template('selectVehicle.html', form=form) -@app.route('/pitstops/create/', methods=['GET', 'POST']) +@app.route('/pitstops/vehicle//consumable/select', methods=['GET', 'POST']) @login_required -def create_pit_stop_form(vid): - vehicle = Vehicle.query.filter(Vehicle.id == vid).first() - if vehicle not in current_user.vehicles: +def select_consumable_for_new_pitstop(vid): + vehicle = Vehicle.query.get(vid) + if vehicle is None or vehicle not in current_user.vehicles: return redirect(url_for('select_vehicle_for_new_pitstop')) + if len(vehicle.consumables) == 1: + return redirect(url_for('create_pit_stop_form', vid=vid, cid=vehicle.consumables[0].id)) + + form = SelectConsumableForm() + form.consumable.choices = [(g.id, g.name) for g in vehicle.consumables] + + if form.validate_on_submit(): + return redirect(url_for('create_pit_stop_form', vid=vid, cid=form.consumable.data)) + + return render_template('selectConsumableForVehicle.html', vehicle=vehicle, form=form) + + +@app.route('/pitstops/vehicle//consumable//create', methods=['GET', 'POST']) +@login_required +def create_pit_stop_form(vid, cid): + vehicle = Vehicle.query.get(vid) + if vehicle is None or vehicle not in current_user.vehicles: + return redirect(url_for('select_vehicle_for_new_pitstop')) + + consumable = Consumable.query.get(cid) + if consumable not in vehicle.consumables: + return redirect(url_for('select_consumable_for_new_pitstop', vid=vid)) + + # the last pitstop is required to be able to check the monotonicy of date and odometer + if len(vehicle.pitstops) > 0: last_pitstop = vehicle.pitstops[-1] else: @@ -240,9 +265,10 @@ def create_pit_stop_form(vid): form = CreatePitstopForm() form.set_pitstop(last_pitstop) + form.litres.label = '%s (%s)' % (consumable.name, consumable.unit) if form.validate_on_submit(): - new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data, form.costs.data) + new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data, form.costs.data, cid) db.session.add(new_stop) vehicle.pitstops.append(new_stop) db.session.commit() @@ -250,9 +276,9 @@ def create_pit_stop_form(vid): return redirect(url_for('get_pit_stops', _anchor= 'v' + str(vehicle.id))) form.odometer.default = last_pitstop.odometer - form.litres.default = last_pitstop.litres +# form.litres.default = last_pitstop.litres form.date.default = date.today() - form.costs.default = last_pitstop.costs +# form.costs.default = last_pitstop.costs form.process() messages = { 'date': 'Date must be between %s and %s (including).' % (str(last_pitstop.date), str(date.today())), diff --git a/app/rollerverbrauch/forms.py b/app/rollerverbrauch/forms.py index ffc33ac..4bab1b5 100644 --- a/app/rollerverbrauch/forms.py +++ b/app/rollerverbrauch/forms.py @@ -37,6 +37,11 @@ class SelectVehicleForm(Form): submit = SubmitField(label='Do it!') +class SelectConsumableForm(Form): + consumable = SelectField('Consumable', coerce=int) + submit = SubmitField(label='Do it!') + + class CreatePitstopForm(Form): date = DateField('Date of Pitstop', validators=[date_check]) odometer = IntegerField('Odometer (km)', validators=[odometer_check]) diff --git a/app/rollerverbrauch/templates/pitstops.html b/app/rollerverbrauch/templates/pitstops.html index f45c131..7a3e773 100644 --- a/app/rollerverbrauch/templates/pitstops.html +++ b/app/rollerverbrauch/templates/pitstops.html @@ -89,13 +89,25 @@ {{ (pitstop.costs / pitstop.litres) | round(2) }} €/l + {% if loop.first %} + + + + edit + + + delete + + + + {% endif %} {% endif %} {% endfor %} {% else %} {% endif %} diff --git a/app/rollerverbrauch/templates/selectConsumableForVehicle.html b/app/rollerverbrauch/templates/selectConsumableForVehicle.html new file mode 100644 index 0000000..9676e2a --- /dev/null +++ b/app/rollerverbrauch/templates/selectConsumableForVehicle.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Select Consumable for '{{ vehicle.name }}'

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.consumable) }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+
+{% endblock %} + diff --git a/app/rollerverbrauch/templates/statistics.html b/app/rollerverbrauch/templates/statistics.html index c66622b..85cb81c 100644 --- a/app/rollerverbrauch/templates/statistics.html +++ b/app/rollerverbrauch/templates/statistics.html @@ -87,7 +87,7 @@ {% else %} {% endif %} @@ -99,7 +99,7 @@ {% else %} {% endif %} @@ -111,7 +111,7 @@ {% else %} {% endif %} @@ -123,7 +123,7 @@ {% else %} {% endif %} @@ -135,7 +135,7 @@ {% else %} {% endif %} From 43e3f9c98b14d6c871791250c446964569898c82 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 4 Jul 2016 20:19:59 +0200 Subject: [PATCH 09/13] . --- app/rollerverbrauch/__init__.py | 53 ++++---- app/rollerverbrauch/entities.py | 56 +++++++- app/rollerverbrauch/forms.py | 71 ++++++++++- .../templates/createPitStopForm.html | 1 + app/rollerverbrauch/templates/pitstops.html | 120 +++++------------- app/rollerverbrauch/tools.py | 86 ++++++++++++- 6 files changed, 267 insertions(+), 120 deletions(-) diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index c79ce53..070e3de 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -102,7 +102,7 @@ def index(): vehicle_count = len(vehicles) pitstop_count = len(Pitstop.query.all()) data = { - 'users':user_count, + 'users': user_count, 'vehicles': vehicle_count, 'pitstops': pitstop_count, 'litres': litres, @@ -256,36 +256,42 @@ def create_pit_stop_form(vid, cid): if consumable not in vehicle.consumables: return redirect(url_for('select_consumable_for_new_pitstop', vid=vid)) - # the last pitstop is required to be able to check the monotonicy of date and odometer - - if len(vehicle.pitstops) > 0: - last_pitstop = vehicle.pitstops[-1] - else: - last_pitstop = Pitstop(0, 0, date(1970, 1, 1), 0, None) - form = CreatePitstopForm() - form.set_pitstop(last_pitstop) - form.litres.label = '%s (%s)' % (consumable.name, consumable.unit) + # the last pitstop is required to be able to check the monotonicy of date and odometer + last_pitstop = tools.get_latest_pitstop_for_vehicle(vid) + last_pitstop_consumable = tools.get_latest_pitstop_for_vehicle_and_consumable(vid, cid) + + # we can enter the same odometer if the pitstops are not equal + form.same_odometer_allowed = (last_pitstop != last_pitstop_consumable) + + # set the lower limits for odometer andd date and the values for amount and costs of the last stop + form.set_pitstop(tools.compute_lower_limits_for_new_pitstop(last_pitstop, last_pitstop_consumable, cid)) + + # set the label of the litres field to make the user comfortable + form.set_consumable(consumable) + + # preinitialize the defaults with potentially existing values from a try before + form.preinit_with_data() + + # + # Validate should accept same odometer on different consumables + # if form.validate_on_submit(): new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data, form.costs.data, cid) db.session.add(new_stop) vehicle.pitstops.append(new_stop) - db.session.commit() - tools.db_log_add(new_stop) + try: + db.session.commit() + tools.db_log_add(new_stop) + except IntegrityError: + db.session.rollback() + form.odometer.errors.append('Pitstop already present for %s at odometer %s km!' % (consumable.name, form.odometer.data)) + return render_template('createPitStopForm.html', form=form, vehicle=vehicle, messages=form.get_hint_messages()) return redirect(url_for('get_pit_stops', _anchor= 'v' + str(vehicle.id))) - form.odometer.default = last_pitstop.odometer -# form.litres.default = last_pitstop.litres - form.date.default = date.today() -# form.costs.default = last_pitstop.costs form.process() - messages = { - 'date': 'Date must be between %s and %s (including).' % (str(last_pitstop.date), str(date.today())), - 'odometer': 'Odometer must be greater than %s km.' % (str(last_pitstop.odometer)), - 'costs': 'Costs must be higher than 0.01 €.' - } - return render_template('createPitStopForm.html', form=form, vehicle=vehicle, messages=messages) + return render_template('createPitStopForm.html', form=form, vehicle=vehicle, messages=form.get_hint_messages()) @app.route('/pitstops/delete/', methods=['GET', 'POST']) @@ -311,9 +317,10 @@ def delete_pit_stop_form(pid): @app.route('/pitstops/edit/', methods=['GET', 'POST']) @login_required def edit_pit_stop_form(pid): - edit_pitstop = Pitstop.query.filter(Pitstop.id == pid).first() + edit_pitstop = Pitstop.query.get(pid).first() if edit_pitstop is None: return redirect(url_for('get_pit_stops')) + vehicle = Vehicle.query.filter(Vehicle.id == edit_pitstop.vehicle_id).first() if vehicle not in current_user.vehicles: return redirect(url_for('get_pit_stops')) diff --git a/app/rollerverbrauch/entities.py b/app/rollerverbrauch/entities.py index 0a9c06e..b59a917 100644 --- a/app/rollerverbrauch/entities.py +++ b/app/rollerverbrauch/entities.py @@ -11,6 +11,9 @@ vehicles_consumables = db.Table('vehicles_consumables', class Role(db.Model, RoleMixin): + """ + Entity to handle different roles for users: Typically user and admin exist + """ id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255)) @@ -23,11 +26,15 @@ class Role(db.Model, RoleMixin): class User(db.Model, UserMixin): + """ + Entity to represent a user including login data and links to roles and vehicles. + """ id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255), unique=True) password = db.Column(db.String(255)) active = db.Column(db.Boolean()) confirmed_at = db.Column(db.DateTime()) + vehicles = db.relationship( 'Vehicle' ) @@ -42,6 +49,14 @@ class User(db.Model, UserMixin): class Vehicle(db.Model): + """ + Entity to represent a vehicle. + Attributes: + * name of the vehilce + * the id of the owner + * list of pitstops + * list of possible consumables + """ id = db.Column(db.Integer, primary_key=True) owner_id = db.Column(db.Integer, db.ForeignKey('user.id')) name = db.Column(db.String(255)) @@ -52,7 +67,10 @@ class Vehicle(db.Model): 'Consumable', secondary=vehicles_consumables ) - __table_args__ = (db.UniqueConstraint('owner_id', 'name', name='_owner_name_uniq'),) + # allow vehicle names to be duplicated between different owners but must still be uniq for each owner + __table_args__ = (db.UniqueConstraint('owner_id', + 'name', + name='_owner_name_uniq'),) def __init__(self, name): self.name = name @@ -62,31 +80,55 @@ class Vehicle(db.Model): class Pitstop(db.Model): + """ + Entity to represent a pitstop for a single consumable. + Attributes: + * the date of the pitstop + * the odometer of the pitstop + * the id of the fuelled consumable + * amount of consumable used + * the costs of the consumable + * the id of the vehicle that was refuelled + """ id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date) odometer = db.Column(db.Integer) consumable_id = db.Column(db.Integer, db.ForeignKey('consumable.id')) - consumable = db.relationship('Consumable') - litres = db.Column(db.Numeric(5, 2)) + amount = db.Column(db.Numeric(5, 2)) costs = db.Column(db.Numeric(5, 2), default=0) vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id')) + # short cut to access the fuelled consumable of the pitstop + consumable = db.relationship('Consumable') + # this uniqueness constraint makes sure that for each consumable and each vehicle only one pitstop exists at the + # same odometer + __table_args__ = (db.UniqueConstraint('odometer', + 'consumable_id', + 'vehicle_id', + name='_odometer_consumable_vehicle_uniq'),) - def __init__(self, odometer, litres, date, costs, consumable_id): + def __init__(self, odometer, amount, date, costs, consumable_id): self.odometer = odometer - self.litres = litres + self.amount = amount self.date = date self.costs = costs self.consumable_id = consumable_id def __repr__(self): - return '' % \ - (self.odometer, self.litres, self.date, self.vehicle_id, self.consumable_id) + return '' % \ + (self.odometer, self.amount, self.date, self.vehicle_id, self.consumable_id) class Consumable(db.Model): + """ + Entity to represent a material that be consumed by a vehilce. + Attributes: + * name (must be globally unique) + * unit + """ id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), unique=True) unit = db.Column(db.String(255)) + vehicles = db.relationship( 'Vehicle', secondary=vehicles_consumables diff --git a/app/rollerverbrauch/forms.py b/app/rollerverbrauch/forms.py index 4bab1b5..f884643 100644 --- a/app/rollerverbrauch/forms.py +++ b/app/rollerverbrauch/forms.py @@ -5,6 +5,13 @@ from datetime import date def date_check(form, field): + """ + Checks that the date of the pitstop is not before the date of the latest pitstop and not after today. + + :param form: the form where the field is in + :param field: the field to check + :return: Nothing or a ValidationError if the limits are not kept + """ if field.data < form.last_pitstop.date: raise ValidationError('The new date must not be before %s' % form.last_pitstop.date) if field.data > date.today(): @@ -12,7 +19,15 @@ def date_check(form, field): def odometer_check(form, field): - if field.data <= form.last_pitstop.odometer: + """ + + :param form: + :param field: + :return: + """ + if not form.same_odometer_allowed and field.data <= form.last_pitstop.odometer: + raise ValidationError('The new odometer value must be higher than %i km' % form.last_pitstop.odometer) + if form.same_odometer_allowed and field.data < form.last_pitstop.odometer: raise ValidationError('The new odometer value must be higher than %i km' % form.last_pitstop.odometer) @@ -27,6 +42,12 @@ def costs_check(form, field): def edit_costs_check(form, field): + """ + Costs must be given, if a default value was given to the form field. + :param form: + :param field: + :return: + """ costs_check_required = (form.costs.default is not None and form.costs.default > 0) if costs_check_required and field.data is not None and field.data <= 0: raise ValidationError('Costs must be above 0.01 €.') @@ -49,10 +70,44 @@ class CreatePitstopForm(Form): costs = DecimalField('Costs (€, overall)', places=2, validators=[costs_check]) submit = SubmitField(label='Do it!') last_pitstop = None + same_odometer_allowed = True def set_pitstop(self, last_pitstop): self.last_pitstop = last_pitstop + def set_consumable(self, consumable): + self.litres.label = '%s (%s)' % (consumable.name, consumable.unit) + + def preinit_with_data(self): + if self.date.data: + self.date.default = self.date.data + else: + self.date.default = self.last_pitstop.date + if self.odometer.data: + self.odometer.default = self.odometer.data + else: + self.odometer.default = self.last_pitstop.odometer + if self.litres.data: + self.litres.default = self.litres.data + else: + self.litres.default = self.last_pitstop.amount + if self.costs.data: + self.costs.default = self.costs.data + else: + self.costs.default = self.last_pitstop.costs + + def get_hint_messages(self): + if self.same_odometer_allowed: + or_equal = ' or equal to' + else: + or_equal = '' + messages = { + 'date': 'Date must be between %s and %s (including).' % (str(self.last_pitstop.date), str(date.today())), + 'odometer': 'Odometer must be greater than%s %s km.' % (or_equal, str(self.last_pitstop.odometer)), + 'costs': 'Costs must be higher than 0.01 €.' + } + return messages + class EditVehicleForm(Form): name = StringField('Name', validators=[Length(1, 255)]) @@ -79,10 +134,24 @@ class EditPitstopForm(Form): costs = DecimalField('Costs (€, overall)', places=2, validators=[edit_costs_check]) submit = SubmitField(label='Update it!') last_pitstop = None + same_odometer_allowed = True def set_pitstop(self, last_pitstop): self.last_pitstop = last_pitstop + def set_consumable(self, consumable): + self.litres.label = '%s (%s)' % (consumable.name, consumable.unit) + + def preinit_with_data(self): + if self.date.data: + self.date.default = self.date.data + if self.odometer.data: + self.odometer.default = self.odometer.data + if self.litres.data: + self.litres.default = self.litres.data + if self.costs.data: + self.costs.default = self.costs.data + class CreateConsumableForm(Form): name = StringField('Name', validators=[Length(1, 255)]) diff --git a/app/rollerverbrauch/templates/createPitStopForm.html b/app/rollerverbrauch/templates/createPitStopForm.html index feebbec..a6941e3 100644 --- a/app/rollerverbrauch/templates/createPitStopForm.html +++ b/app/rollerverbrauch/templates/createPitStopForm.html @@ -26,4 +26,5 @@ +
{% endblock %} diff --git a/app/rollerverbrauch/templates/pitstops.html b/app/rollerverbrauch/templates/pitstops.html index 7a3e773..88ca8a4 100644 --- a/app/rollerverbrauch/templates/pitstops.html +++ b/app/rollerverbrauch/templates/pitstops.html @@ -1,6 +1,7 @@ {% extends "layout.html" %} {% block body %} -
+
+
+ {% endfor %} {% else %}
+
- {% else %} - - {% endif %} - -
- {% if vehicle.pitstop_count > 0 %} -
- - {% else %} - - {% endif %} -
-
- {% if vehicle.pitstop_count > 0 %} -
- - {% else %} - - {% endif %} -
-
- {% if vehicle.pitstop_count > 0 %} -
- - {% else %} - - {% endif %} -
-
- {% if vehicle.pitstop_count > 0 %} -
- - {% else %} - - {% endif %} -
- + + +
+ {% for consumable in vehicle.consumables %} +
+ + + + + + + + + + + + + + + + + +
Average Distance:{{ consumable.average_distance | round(2) }} km
Amount fuelled:{{ consumable.amount | round(2) }} {{ consumable.unit }}
Average Amount fuelled:{{ consumable.average_amount_fuelled | round(2) }} {{ consumable.unit }}
Average Amount used:{{ consumable.average_amount_used | round(2) }} {{ consumable.unit }}/100km
+
+ {% endfor %} +
+ + +
+
+ {% if vehicle.odometers|length > 0 %} +
+ + {% else %} + + {% endif %} +
+ {% for consumable in vehicle.consumables %} +
+ {% if consumable.average_amount|length > 0 %} +
+ + {% else %} + + {% endif %} +
+ {% endfor %} +
+ {% endfor %} @@ -153,6 +118,18 @@ $('a[href="' + window.location.hash + '"]').click() } }); + jQuery(document).ready(function ($) { + $('#consumable_tabs').tab(); + if(window.location.hash != "") { + $('a[href="' + window.location.hash + '"]').click() + } + }); + jQuery(document).ready(function ($) { + $('#charts_tabs').tab(); + if(window.location.hash != "") { + $('a[href="' + window.location.hash + '"]').click() + } + }); diff --git a/app/rollerverbrauch/tools.py b/app/rollerverbrauch/tools.py index b5dfe5c..3cbaa9a 100644 --- a/app/rollerverbrauch/tools.py +++ b/app/rollerverbrauch/tools.py @@ -5,52 +5,85 @@ from dis import code_info from rollerverbrauch.entities import \ Pitstop + +class ConsumableStats: + def __init__(self, vehicle, consumable): + self.name = consumable.name + self.id = consumable.id + self.unit = consumable.unit + self.amount = 0 + self.average_distance = 0 + self.average_amount_fuelled = 0 + self.average_amount_used = 0 + self.average_amount = [] + + pitstops = [stop for stop in vehicle.pitstops if stop.consumable_id == consumable.id] + pitstop_count = len(pitstops) + + if pitstop_count > 0: + for pitstop in pitstops: + self.amount += pitstop.amount + self.average_amount_fuelled = self.amount / pitstop_count + + if pitstop_count > 1: + overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer + self.average_distance = overall_distance / (pitstop_count - 1) + self.average_amount_used = 100 * (self.amount - pitstops[0].amount) / overall_distance + for index in range(1, pitstop_count): + last_ps = pitstops[index - 1] + current_ps = pitstops[index] + self.average_amount.append( + StatsEvent( + current_ps.date, + round(100 * current_ps.amount/(current_ps.odometer - last_ps.odometer), 2))) + class VehicleStats: def __init__(self, vehicle): self.name = vehicle.name self.id = vehicle.id - self.pitstop_count = len(vehicle.pitstops) self.overall_distance = 0 - self.average_distance = 0 - self.overall_litres = 0 - self.average_litres_fuelled = 0 - self.average_litres_used = 0 - self.litres = [] - self.average_litres = [] - self.odometers = [] - self.costsPerLitre = [] - self.costs = [] self.overall_costs = 0 - self.average_costs_per_litre = 0 - cost_count = 0; + self.consumables = [] + self.odometers = [] - if self.pitstop_count > 0: - for pitstop in vehicle.pitstops: - self.overall_litres += pitstop.litres - self.litres.append(StatsEvent(pitstop.date, pitstop.litres)) - self.odometers.append(StatsEvent(pitstop.date, pitstop.odometer)) - self.costsPerLitre.append(StatsEvent(pitstop.date, pitstop.costs / pitstop.litres)) - self.costs.append(StatsEvent(pitstop.date, pitstop.costs)) - self.overall_costs += pitstop.costs - self.average_costs_per_litre += (pitstop.costs / pitstop.litres) - if pitstop.costs > 0: - cost_count += 1 - self.average_litres_fuelled = self.overall_litres / self.pitstop_count - if cost_count > 0: - self.average_costs_per_litre = self.average_costs_per_litre / cost_count - else: - self.average_costs_per_litre = 0 + for consumable in vehicle.consumables: + self.consumables.append(ConsumableStats(vehicle, consumable)) - if self.pitstop_count > 1: +# self.overall_litres = 0 +# +# self.litres = [] +# self.average_litres = [] +# self.costsPerLitre = [] +# self.costs = [] +# self.average_costs_per_litre = 0 + # cost_count = 0; + + pitstop_count = len(vehicle.pitstops) + + if pitstop_count > 0: + for pitstop in vehicle.pitstops: + # self.overall_litres += pitstop.amount + # self.litres.append(StatsEvent(pitstop.date, pitstop.amount)) + self.odometers.append(StatsEvent(pitstop.date, pitstop.odometer)) + # self.costsPerLitre.append(StatsEvent(pitstop.date, pitstop.costs / pitstop.amount)) + # self.costs.append(StatsEvent(pitstop.date, pitstop.costs)) + self.overall_costs += pitstop.costs + # self.average_costs_per_litre += (pitstop.costs / pitstop.amount) + # if pitstop.costs > 0: + # cost_count += 1 + # if cost_count > 0: + # self.average_costs_per_litre = self.average_costs_per_litre / cost_count + # else: + # self.average_costs_per_litre = 0 + + if pitstop_count > 1: self.overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer - self.average_distance = self.overall_distance / (self.pitstop_count - 1) - self.average_litres_used = 100 * (self.overall_litres - vehicle.pitstops[0].litres) / self.overall_distance - for index in range(1, self.pitstop_count): - last_ps = vehicle.pitstops[index - 1] - current_ps = vehicle.pitstops[index] - self.average_litres.append(StatsEvent( - current_ps.date, - round(100 * current_ps.litres/(current_ps.odometer - last_ps.odometer), 2))) + # for index in range(1, self.pitstop_count): + # last_ps = vehicle.pitstops[index - 1] + # current_ps = vehicle.pitstops[index] + # self.average_litres.append(StatsEvent( + # current_ps.date, + # round(100 * current_ps.litres/(current_ps.odometer - last_ps.odometer), 2))) class StatsEvent: From ca5bd9bb02fa3fc7fd583462956785cc7d5e3dad Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 11 Jul 2016 06:59:34 +0200 Subject: [PATCH 11/13] statistics 2nd step --- app/rollerverbrauch/templates/layout.html | 24 ---- app/rollerverbrauch/templates/statistics.html | 129 ++++++++++++------ app/rollerverbrauch/tools.py | 45 ++---- 3 files changed, 97 insertions(+), 101 deletions(-) diff --git a/app/rollerverbrauch/templates/layout.html b/app/rollerverbrauch/templates/layout.html index 60e5ebe..8920020 100644 --- a/app/rollerverbrauch/templates/layout.html +++ b/app/rollerverbrauch/templates/layout.html @@ -85,30 +85,6 @@ {% endmacro %} -{% macro chartScript(divId, data, unit)%} - {% set hash = divId | md5 %} - - data_{{ hash }} = [{% for stop in data %}{ - "date": "{{stop.date}}", - "value": {{stop.value}} - }{% if not loop.last %},{%endif%} - {% endfor%} - ] - var chart_{{ hash }} = createChart('{{divId}}', data_{{ hash }}, '{{unit}}'); - - function zoom_chart_{{ hash }}() { - chart_{{ hash }}.zoomToIndexes( - chart_{{ hash }}.dataProvider.length - 40, - chart_{{ hash }}.dataProvider.length - 1 - ); - } - - chart_{{ hash }}.addListener("rendered", zoom_chart_{{ hash }}); - - zoom_chart_{{ hash }}() - -{% endmacro %} - diff --git a/app/rollerverbrauch/templates/statistics.html b/app/rollerverbrauch/templates/statistics.html index b6a3aaf..0b3ceb4 100644 --- a/app/rollerverbrauch/templates/statistics.html +++ b/app/rollerverbrauch/templates/statistics.html @@ -1,5 +1,44 @@ {% extends "layout.html" %} +{% macro chartScript(divId, data, unit)%} + {% set hash = divId | md5 %} + + data_{{ hash }} = [{% for stop in data %}{ + "date": "{{stop.date}}", + "value": {{stop.value}} + }{% if not loop.last %},{%endif%} + {% endfor%} + ] + var chart_{{ hash }} = createChart('{{divId}}', data_{{ hash }}, '{{unit}}'); + + function zoom_chart_{{ hash }}() { + chart_{{ hash }}.zoomToIndexes( + chart_{{ hash }}.dataProvider.length - 40, + chart_{{ hash }}.dataProvider.length - 1 + ); + } + + chart_{{ hash }}.addListener("rendered", zoom_chart_{{ hash }}); + + zoom_chart_{{ hash }}() +{% endmacro %} + +{% macro chart(data, baseId, unit, link, active)%} + {% set chartID = 'chart_' + baseId %} +
+ {% if data|length > 0 %} +
+ + {% else %} + + {% endif %} +
+{% endmacro %} + {% block body %}
{% endfor %} - -
-
- {% if vehicle.odometers|length > 0 %} -
- - {% else %} - - {% endif %} -
- {% for consumable in vehicle.consumables %} -
- {% if consumable.average_amount|length > 0 %} -
- - {% else %} - - {% endif %} -
- {% endfor %} -
{% endfor %} diff --git a/app/rollerverbrauch/tools.py b/app/rollerverbrauch/tools.py index 3cbaa9a..aef46d5 100644 --- a/app/rollerverbrauch/tools.py +++ b/app/rollerverbrauch/tools.py @@ -11,24 +11,26 @@ class ConsumableStats: self.name = consumable.name self.id = consumable.id self.unit = consumable.unit - self.amount = 0 + self.overall_amount = 0 self.average_distance = 0 self.average_amount_fuelled = 0 self.average_amount_used = 0 self.average_amount = [] + self.amounts = [] pitstops = [stop for stop in vehicle.pitstops if stop.consumable_id == consumable.id] pitstop_count = len(pitstops) if pitstop_count > 0: for pitstop in pitstops: - self.amount += pitstop.amount - self.average_amount_fuelled = self.amount / pitstop_count - + self.overall_amount += pitstop.amount + self.amounts.append(StatsEvent(pitstop.date, pitstop.amount)) + self.average_amount_fuelled = self.overall_amount / pitstop_count + print(self.amounts) if pitstop_count > 1: overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer self.average_distance = overall_distance / (pitstop_count - 1) - self.average_amount_used = 100 * (self.amount - pitstops[0].amount) / overall_distance + self.average_amount_used = 100 * (self.overall_amount - pitstops[0].amount) / overall_distance for index in range(1, pitstop_count): last_ps = pitstops[index - 1] current_ps = pitstops[index] @@ -37,6 +39,7 @@ class ConsumableStats: current_ps.date, round(100 * current_ps.amount/(current_ps.odometer - last_ps.odometer), 2))) + class VehicleStats: def __init__(self, vehicle): self.name = vehicle.name @@ -49,41 +52,15 @@ class VehicleStats: for consumable in vehicle.consumables: self.consumables.append(ConsumableStats(vehicle, consumable)) -# self.overall_litres = 0 -# -# self.litres = [] -# self.average_litres = [] -# self.costsPerLitre = [] -# self.costs = [] -# self.average_costs_per_litre = 0 - # cost_count = 0; - pitstop_count = len(vehicle.pitstops) if pitstop_count > 0: for pitstop in vehicle.pitstops: - # self.overall_litres += pitstop.amount - # self.litres.append(StatsEvent(pitstop.date, pitstop.amount)) self.odometers.append(StatsEvent(pitstop.date, pitstop.odometer)) - # self.costsPerLitre.append(StatsEvent(pitstop.date, pitstop.costs / pitstop.amount)) - # self.costs.append(StatsEvent(pitstop.date, pitstop.costs)) self.overall_costs += pitstop.costs - # self.average_costs_per_litre += (pitstop.costs / pitstop.amount) - # if pitstop.costs > 0: - # cost_count += 1 - # if cost_count > 0: - # self.average_costs_per_litre = self.average_costs_per_litre / cost_count - # else: - # self.average_costs_per_litre = 0 if pitstop_count > 1: self.overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer - # for index in range(1, self.pitstop_count): - # last_ps = vehicle.pitstops[index - 1] - # current_ps = vehicle.pitstops[index] - # self.average_litres.append(StatsEvent( - # current_ps.date, - # round(100 * current_ps.litres/(current_ps.odometer - last_ps.odometer), 2))) class StatsEvent: @@ -178,13 +155,13 @@ def compute_lower_limits_for_new_pitstop(latest_pitstop, last_pitstop_consumable # if last_pitstop_consumable is not None and last_pitstop_consumable != latest_pitstop: # if latest_pitstop.id > last_pitstop_consumable.id: # return Pitstop(latest_pitstop.odometer, - # last_pitstop_consumable.amount, + # last_pitstop_consumable.overall_amount, # latest_pitstop.date, # last_pitstop_consumable.costs, # consumable_id) # else: # return Pitstop(last_pitstop_consumable.odometer, - # last_pitstop_consumable.amount, + # last_pitstop_consumable.overall_amount, # last_pitstop_consumable.date, # last_pitstop_consumable.costs, # consumable_id) @@ -193,7 +170,7 @@ def compute_lower_limits_for_new_pitstop(latest_pitstop, last_pitstop_consumable # litres = 0 # costs = 0 # if latest_pitstop.consumable_id == last_pitstop_consumable.consumable_id: - # litres = latest_pitstop.amount + # litres = latest_pitstop.overall_amount # costs = latest_pitstop.costs # return Pitstop(latest_pitstop.odometer, litres, latest_pitstop.date, costs, consumable_id) # else: From aa9fda93c0eb8f506167cfc6307282366013ad04 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Mon, 11 Jul 2016 22:15:23 +0200 Subject: [PATCH 12/13] statistics 3rd --- app/rollerverbrauch/templates/statistics.html | 274 +++++++++--------- 1 file changed, 134 insertions(+), 140 deletions(-) diff --git a/app/rollerverbrauch/templates/statistics.html b/app/rollerverbrauch/templates/statistics.html index 0b3ceb4..f559fec 100644 --- a/app/rollerverbrauch/templates/statistics.html +++ b/app/rollerverbrauch/templates/statistics.html @@ -23,159 +23,153 @@ zoom_chart_{{ hash }}() {% endmacro %} -{% macro chart(data, baseId, unit, link, active)%} - {% set chartID = 'chart_' + baseId %} -
- {% if data|length > 0 %} -
- - {% else %} - - {% endif %} +{% macro nav_tab(id, text, active) %} + {# + + #} +
  • + + {{ text }} + +
  • +{% endmacro %} + +{% macro tab_pane(id, content, active) %} +
    + {{ content }} +
    +{% endmacro %} + +{% macro chart(data, baseId, unit, link) %} + {% if data|length > 0 %} + {% set chartID = 'chart_' + baseId %} +
    + + {% else %} + + {% endif %} +{% endmacro %} + +{% macro print_consumable_table(consumable) %} + + + + + + + + + + + + + + + + + +
    Average Distance:{{ consumable.average_distance | round(2) }} km
    Amount fuelled:{{ consumable.overall_amount | round(2) }} {{ consumable.unit }}
    Average Amount fuelled:{{ consumable.average_amount_fuelled | round(2) }} {{ consumable.unit }}
    Average Amount used:{{ consumable.average_amount_used | round(2) }} {{ consumable.unit }}/100km
    +{% endmacro %} + +{% macro print_vehicle_table(vehicle) %} +

    {{vehicle.name}}

    +
    + + + + + + + + + +
    Logged Distance:{{ vehicle.overall_distance | round(2) }} km
    Logged Costs:{{ vehicle.overall_costs | round(2) }} €
    {% endmacro %} +{% macro tab_script(id) %} + +{% endmacro %} + {% block body %}
    {% for vehicle in data %} -
    -

    {{vehicle.name}}

    -
    - - - - - - - - - -
    Logged Distance:{{ vehicle.overall_distance | round(2) }} km
    Logged Costs:{{ vehicle.overall_costs | round(2) }} €
    -
    - - -
    - {% for consumable in vehicle.consumables %} -
    - - - - - - - - - - - - - - - - - -
    Average Distance:{{ consumable.average_distance | round(2) }} km
    Amount fuelled:{{ consumable.overall_amount | round(2) }} {{ consumable.unit }}
    Average Amount fuelled:{{ consumable.average_amount_fuelled | round(2) }} {{ consumable.unit }}
    Average Amount used:{{ consumable.average_amount_used | round(2) }} {{ consumable.unit }}/100km
    - -{% set ID = vehicle.id|string + '_c' + consumable.id|string %} -{% set odometerID = ID + '_odometer' %} -{% set consumptionID = ID + '_consumption' %} -{% set amountID = ID + '_amount' %} - -
    - {% set baseId = 'v' + odometerID %} - {% set link = url_for('select_consumable_for_new_pitstop', vid=vehicle.id) %} - {{ chart(vehicle.odometers, baseId, 'km', link, true) }} - - - {% set baseId = 'v' + consumptionID %} - {% set link = url_for('create_pit_stop_form', vid=vehicle.id, cid=consumable.id) %} - {{ chart(consumable.average_amount, baseId, consumable.unit + '/100km', link, false) }} - - - {% set baseId = 'v' + amountID %} - {% set link = url_for('create_pit_stop_form', vid=vehicle.id, cid=consumable.id) %} - {{ chart(consumable.amounts, baseId, consumable.unit, link, false) }} -
    - - -
    - {% endfor %} -
    - - +
    + {{ print_vehicle_table(vehicle) }} + +
    + {{ tab_pane( + vehicle.id|string + '_odometer', + chart( + vehicle.odometers, + 'ref_' + vehicle.id|string + '_odometer', + 'km', + url_for('select_consumable_for_new_pitstop', vid=vehicle.id) + ), + true + ) + }} + {% for consumable in vehicle.consumables %} +
    + {{ print_consumable_table(consumable) }} + +
    + {{ tab_pane( + vehicle.id|string + '_' + consumable.id|string + '_consumption', + chart( + consumable.average_amount, + 'ref_' + vehicle.id|string + '_' + consumable.id|string + '_consumption', + consumable.unit + '/100km', + url_for('create_pit_stop_form', vid=vehicle.id, cid=consumable.id) + ), + true + ) + }} + {{ tab_pane( + vehicle.id|string + '_' + consumable.id|string + '_amount', + chart( + consumable.amounts, + 'ref_' + vehicle.id|string + '_' + consumable.id|string + '_amount', + consumable.unit, + url_for('create_pit_stop_form', vid=vehicle.id, cid=consumable.id) + ), + false + ) + }} +
    + {{ tab_script('vehicle_' + vehicle.id + '_' + consumable.id + '_tabs') }} +
    + {% endfor %} +
    + {{ tab_script('vehicle_' + vehicle.id + '_tabs') }}
    {% endfor %}
    + {{ tab_script('vehicle_tabs') }}
    - - - - - - {% endblock %} From 1516ac2c5147848e08658a8f3225d8a8fea0e4f5 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Sat, 16 Jul 2016 12:07:01 +0200 Subject: [PATCH 13/13] db update script and some fixes --- app/rollerverbrauch/__init__.py | 5 ---- app/rollerverbrauch/templates/pitstops.html | 8 +++++- app/rollerverbrauch/templates/statistics.html | 4 +-- app/rollerverbrauch/tools.py | 5 ++-- .../upgrade_cathrine_to_danielle.sql | 28 +++++++++++++++++++ 5 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 database_upgrades/upgrade_cathrine_to_danielle.sql diff --git a/app/rollerverbrauch/__init__.py b/app/rollerverbrauch/__init__.py index 070e3de..85b7afb 100644 --- a/app/rollerverbrauch/__init__.py +++ b/app/rollerverbrauch/__init__.py @@ -71,11 +71,6 @@ def user_registered_sighandler(app, user, confirm_token): @app.before_first_request def before_first_request(): db.create_all() - print(""" - ========================= - TEST - ======================== - """) user_datastore.find_or_create_role(name='admin', description='Role for administrators') user_datastore.find_or_create_role(name='user', description='Role for all users.') db.session.commit() diff --git a/app/rollerverbrauch/templates/pitstops.html b/app/rollerverbrauch/templates/pitstops.html index 88ca8a4..25c7283 100644 --- a/app/rollerverbrauch/templates/pitstops.html +++ b/app/rollerverbrauch/templates/pitstops.html @@ -34,7 +34,13 @@ Costs - {{pitstop.costs}} € + + {% if pitstop.costs %} + {{pitstop.costs}} € + {% else %} + -- € + {% endif %} + {% if loop.first %} diff --git a/app/rollerverbrauch/templates/statistics.html b/app/rollerverbrauch/templates/statistics.html index f559fec..0f1ecd3 100644 --- a/app/rollerverbrauch/templates/statistics.html +++ b/app/rollerverbrauch/templates/statistics.html @@ -162,11 +162,11 @@ ) }}
    - {{ tab_script('vehicle_' + vehicle.id + '_' + consumable.id + '_tabs') }} + {{ tab_script('vehicle_' + vehicle.id|string + '_' + consumable.id|string + '_tabs') }}
    {% endfor %} - {{ tab_script('vehicle_' + vehicle.id + '_tabs') }} + {{ tab_script('vehicle_' + vehicle.id|string + '_tabs') }} {% endfor %} diff --git a/app/rollerverbrauch/tools.py b/app/rollerverbrauch/tools.py index aef46d5..00d2862 100644 --- a/app/rollerverbrauch/tools.py +++ b/app/rollerverbrauch/tools.py @@ -1,6 +1,5 @@ import logging from datetime import date -from dis import code_info from rollerverbrauch.entities import \ Pitstop @@ -26,7 +25,6 @@ class ConsumableStats: self.overall_amount += pitstop.amount self.amounts.append(StatsEvent(pitstop.date, pitstop.amount)) self.average_amount_fuelled = self.overall_amount / pitstop_count - print(self.amounts) if pitstop_count > 1: overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer self.average_distance = overall_distance / (pitstop_count - 1) @@ -57,7 +55,8 @@ class VehicleStats: if pitstop_count > 0: for pitstop in vehicle.pitstops: self.odometers.append(StatsEvent(pitstop.date, pitstop.odometer)) - self.overall_costs += pitstop.costs + if pitstop.costs is not None: + self.overall_costs += pitstop.costs if pitstop_count > 1: self.overall_distance = vehicle.pitstops[-1].odometer - vehicle.pitstops[0].odometer diff --git a/database_upgrades/upgrade_cathrine_to_danielle.sql b/database_upgrades/upgrade_cathrine_to_danielle.sql new file mode 100644 index 0000000..c221633 --- /dev/null +++ b/database_upgrades/upgrade_cathrine_to_danielle.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS `consumable` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `unit` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=latin1; + +INSERT INTO `consumable`(`id`, `name`, `unit`) VALUES (1, 'Super E5', 'l'); + +CREATE TABLE IF NOT EXISTS `vehicles_consumables` ( + `vehicle_id` int(11) DEFAULT NULL, + `consumable_id` int(11) DEFAULT NULL, + KEY `vehicle_id` (`vehicle_id`), + KEY `consumable_id` (`consumable_id`), + CONSTRAINT `vehicles_consumables_ibfk_1` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicle` (`id`), + CONSTRAINT `vehicles_consumables_ibfk_2` FOREIGN KEY (`consumable_id`) REFERENCES `consumable` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +ALTER TABLE `pitstop` ADD COLUMN `costs` decimal(5,2) DEFAULT NULL; + +ALTER TABLE `pitstop` CHANGE `litres` `amount`; + +ALTER TABLE `pitstop` ADD COLUMN `consumable_id` int(11); +ALTER TABLE `pitstop` ADD FOREIGN KEY (`consumable_id`) REFERENCES `consumable` (`id`); +UPDATE `pitstop` set `consumable_id`=1; + +INSERT INTO `vehicles_consumables`(`vehicle_id`, `consumable_id`) SELECT `id`, 1 FROM `vehicle`; \ No newline at end of file