diff --git a/README.md b/README.md index f1d2876..31087c6 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,20 @@ ## general configuration -Look at *app/config/email.py.example** for the configuration of the +Look at *app/config/email.py.example* for the configuration of the parameters required for sending emails. Copy the file as *email.py* to a folder that will serve as configuration directory and fill in the information. The directory will be used as volume during container operation. ## start database + `docker run --name pitstops_db -e MYSQL_ROOT_PASSWORD=$SOMESECUREPASSWORD$ -e MYSQL_DATABASE=pitstops -d mysql:latest` +or + +`docker run --name pitstops_db -e MYSQL_ROOT_PASSWORD=$SOMESECUREPASSWORD$ -e MYSQL_DATABASE=pitstops -d mariadb:latest` + ## Database migrations ### From *Cathrine* to *Master*: @@ -26,6 +31,6 @@ 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 --rm --name rollerverbrauch -ti -v $PWD/app:/app --link pitstops_db:database -p 5000:5000 -e SECURITY_PASSWORD_SALT=XXX -e SECRET_KEY=XXX -e MAIL_SERVER=XXX -e MAIL_USERNAME=XXX -e MAIL_PASSWORD=XXX rollerverbrauch` +`docker run --rm --name rollerverbrauch -ti -v $PWD/app:/app -v $PWD/data:/data -p 5000:5000 -e SECURITY_PASSWORD_SALT=XXX -e SECRET_KEY=XXX -e MAIL_SERVER=XXX -e MAIL_USERNAME=XXX -e MAIL_PASSWORD=XXX rollerverbrauch` ## run in production `docker run --name pitstops -tid -e PROXY_DATA=server_names:ps.lusiardi.de,port:5000 --link pitstops_db:database -e SECURITY_PASSWORD_SALT=XXX -e SECRET_KEY=XXX -e MAIL_SERVER=XXX -e MAIL_USERNAME=XXX -e MAIL_PASSWORD=XXX rollerverbrauch:catherine` diff --git a/app/entities.py b/app/entities.py index d21fc0b..b7d04ee 100644 --- a/app/entities.py +++ b/app/entities.py @@ -1,24 +1,33 @@ from app import db from flask_security import UserMixin, RoleMixin -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'))) +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'))) +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")), +) -users_fillingstations = db.Table('users_fillingstations', - db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), - db.Column('fillingstation_id', db.Integer(), db.ForeignKey('filling_station.int_id'))) +users_fillingstations = db.Table( + "users_fillingstations", + db.Column("user_id", db.Integer(), db.ForeignKey("user.id")), + db.Column( + "fillingstation_id", db.Integer(), db.ForeignKey("filling_station.int_id") + ), +) 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)) @@ -34,6 +43,7 @@ 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)) @@ -43,17 +53,12 @@ class User(db.Model, UserMixin): home_long = db.Column(db.Numeric(8, 5), default=0) home_zoom = db.Column(db.Integer(), default=0) - vehicles = db.relationship( - 'Vehicle' - ) + vehicles = db.relationship("Vehicle") roles = db.relationship( - 'Role', - secondary=roles_users, - backref=db.backref('users', lazy='dynamic') + "Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic") ) favourite_filling_stations = db.relationship( - 'FillingStation', - secondary=users_fillingstations + "FillingStation", secondary=users_fillingstations ) def __repr__(self): @@ -69,31 +74,26 @@ class Vehicle(db.Model): * 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')) + owner_id = db.Column(db.Integer, db.ForeignKey("user.id")) name = db.Column(db.String(255)) - pitstops = db.relationship( - 'Pitstop', - order_by="asc(Pitstop.odometer)" - ) - services = db.relationship( - 'Service', - order_by = "asc(Service.odometer)" - ) - consumables = db.relationship( - 'Consumable', - secondary=vehicles_consumables - ) + pitstops = db.relationship("Pitstop", order_by="asc(Pitstop.odometer)") + services = db.relationship("Service", order_by="asc(Service.odometer)") + regulars = db.relationship("RegularCost") + consumables = db.relationship("Consumable", secondary=vehicles_consumables) # 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'),) + __table_args__ = (db.UniqueConstraint("owner_id", "name", name="_owner_name_uniq"),) def __init__(self, name): self.name = name def __repr__(self): - return '' % (self.id, self.owner_id, self.name) + return '' % ( + self.id, + self.owner_id, + self.name, + ) class Pitstop(db.Model): @@ -107,21 +107,26 @@ class Pitstop(db.Model): * 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_id = db.Column(db.Integer, db.ForeignKey("consumable.id")) 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')) + 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'),) + 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, amount, date, costs, consumable_id): self.odometer = odometer @@ -131,8 +136,31 @@ class Pitstop(db.Model): self.consumable_id = consumable_id def __repr__(self): - return '' % \ - (self.odometer, self.amount, self.date, self.vehicle_id, self.consumable_id) + return '' % ( + self.odometer, + self.amount, + self.date, + self.vehicle_id, + self.consumable_id, + ) + + +class RegularCost(db.Model): + id = db.Column(db.Integer, primary_key=True) + vehicle_id = db.Column(db.Integer, db.ForeignKey("vehicle.id")) + description = db.Column(db.String(4096), unique=True) + costs = db.Column(db.Numeric(10, 2), default=0) + days = db.Column(db.String(1024)) + start_at = db.Column(db.Date) + ends_at = db.Column(db.Date) + + def __init__(self, vehicle_id, description, costs, days, start_at, ends_at): + self.vehicle_id = vehicle_id + self.description = description + self.costs = costs + self.days = days + self.start_at = start_at + self.ends_at = ends_at class Consumable(db.Model): @@ -142,15 +170,13 @@ class Consumable(db.Model): * name (must be globally unique) * unit """ + id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), unique=True) ext_id = db.Column(db.String(255)) unit = db.Column(db.String(255)) - vehicles = db.relationship( - 'Vehicle', - secondary=vehicles_consumables - ) + vehicles = db.relationship("Vehicle", secondary=vehicles_consumables) def __init__(self, name, ext_id, unit): self.name = name @@ -165,7 +191,7 @@ class Service(db.Model): id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date) odometer = db.Column(db.Integer) - vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id')) + vehicle_id = db.Column(db.Integer, db.ForeignKey("vehicle.id")) costs = db.Column(db.Numeric(10, 2), default=0) description = db.Column(db.String(4096)) @@ -177,8 +203,10 @@ class Service(db.Model): self.vehicle_id = vehicle_id def __repr__(self): - return '' % \ - (self.odometer, self.date, self.vehicle_id, self.costs, self.description) + return ( + '' + % (self.odometer, self.date, self.vehicle_id, self.costs, self.description) + ) class FillingStation(db.Model): @@ -203,6 +231,7 @@ class FillingStation(db.Model): for c in self.__table__.columns: val = getattr(self, c.name) import decimal + if isinstance(val, decimal.Decimal): val = float(val) val = str(val) diff --git a/app/forms/__init__.py b/app/forms/__init__.py index e4f65bc..6a83e2c 100644 --- a/app/forms/__init__.py +++ b/app/forms/__init__.py @@ -4,3 +4,4 @@ from .checks import * from .consumable import * from .service import * from .vehicle import * +from .regular_cost import * diff --git a/app/forms/checks.py b/app/forms/checks.py index a7b827f..8efe182 100644 --- a/app/forms/checks.py +++ b/app/forms/checks.py @@ -2,10 +2,34 @@ from wtforms.validators import ValidationError from datetime import date +def regular_costs_days_check(form, field): + """ + Checks the input field to enter multiple days in the following format: + `01-15,07-15` for Jan 15th and July 15 + """ + days = form.days.data + for day in days.split(","): + day = day.strip() + if not day: + raise ValidationError("Missing Date after ','") + try: + m, d = day.split("-") + m_i = int(m) + d_i = int(d) + except Exception: + raise ValidationError("Malformed Date, must be 'Month-Day'") + try: + d = date(2021, m_i, d_i) + except Exception: + raise ValidationError("{}-{} is not a valid date".format(m, d)) + + def odometer_date_check(form, field): """ - Checks that the entered date and odometer of the pit stop is conformant to the existing pit stops. That means, if a - pitstops date is between two other pit stops, the odometer should be as well. + Checks that the entered date and odometer of the pit stop is conformant to + the existing pit stops. That means, if a pitstops date is between two + other pit stops, the odometer should be as well. + :param form: :param field: :return: @@ -16,23 +40,37 @@ def odometer_date_check(form, field): if len(pitstops) > 0: if date < pitstops[0].date and odometer >= pitstops[0].odometer: - raise ValidationError('The new odometer value must be less than %i km' % pitstops[0].odometer) + raise ValidationError( + "The new odometer value must be less than %i km" + % pitstops[0].odometer + ) if date >= pitstops[-1].date and odometer <= pitstops[-1].odometer: - raise ValidationError('The new odometer value must be greater than %i km' % pitstops[-1].odometer) + raise ValidationError( + "The new odometer value must be greater than %i km" + % pitstops[-1].odometer + ) if len(pitstops) > 1: - for index in range(0, len(pitstops)-1): + for index in range(0, len(pitstops) - 1): if pitstops[index].date <= date < pitstops[index + 1].date: - if odometer <= pitstops[index].odometer or odometer >= pitstops[index+1].odometer: - raise ValidationError('The new odometer value must be greater than %i km and less than %i km' - % (pitstops[index].odometer,pitstops[index+1].odometer)) + if ( + odometer <= pitstops[index].odometer + or odometer >= pitstops[index + 1].odometer + ): + raise ValidationError( + "The new odometer value must be greater than %i km and less than %i km" + % ( + pitstops[index].odometer, + pitstops[index + 1].odometer, + ) + ) def edit_odometer_date_check(form, field): """ - This makes exactly the same checks as 'odometer_date_check' but the odometers may be the same (to change only amount - and price). + This makes exactly the same checks as 'odometer_date_check' but the + odometers may be the same (to change only amount and price). :param form: :param field: @@ -44,31 +82,50 @@ def edit_odometer_date_check(form, field): if len(pitstops) > 0: if date < pitstops[0].date and odometer > pitstops[0].odometer: - raise ValidationError('The new odometer value must be less than %i km' % pitstops[0].odometer) + raise ValidationError( + "The new odometer value must be less than %i km" + % pitstops[0].odometer + ) if date >= pitstops[-1].date and odometer < pitstops[-1].odometer: - raise ValidationError('The new odometer value must be greater than %i km' % pitstops[-1].odometer) + raise ValidationError( + "The new odometer value must be greater than %i km" + % pitstops[-1].odometer + ) if len(pitstops) > 1: - for index in range(0, len(pitstops)-1): + for index in range(0, len(pitstops) - 1): if pitstops[index].date <= date < pitstops[index + 1].date: - if odometer < pitstops[index].odometer or odometer > pitstops[index+1].odometer: - raise ValidationError('The new odometer value must be greater than %i km and less than %i km' - % (pitstops[index].odometer,pitstops[index+1].odometer)) + if ( + odometer < pitstops[index].odometer + or odometer > pitstops[index + 1].odometer + ): + raise ValidationError( + "The new odometer value must be greater than %i km and less than %i km" + % ( + pitstops[index].odometer, + pitstops[index + 1].odometer, + ) + ) 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. + 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) + raise ValidationError( + "The new date must not be before %s" % form.last_pitstop.date + ) if field.data > date.today(): - raise ValidationError('The new date must not be after %s' % date.today()) + raise ValidationError( + "The new date must not be after %s" % date.today() + ) def odometer_check(form, field): @@ -78,20 +135,29 @@ def odometer_check(form, field): :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 ( + 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) + raise ValidationError( + "The new odometer value must be higher than %i km" + % form.last_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') + raise ValidationError("You must fuel at least 0.1 l") def costs_check(form, field): if field.data is not None and field.data <= 0: - raise ValidationError('Costs must be above 0.01 €.') + raise ValidationError("Costs must be above 0.01 €.") def edit_costs_check(form, field): @@ -101,8 +167,8 @@ def edit_costs_check(form, field): :param field: :return: """ - 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 €.') - - + raise ValidationError("Costs must be above 0.01 €.") diff --git a/app/forms/regular_cost.py b/app/forms/regular_cost.py new file mode 100644 index 0000000..c8009e9 --- /dev/null +++ b/app/forms/regular_cost.py @@ -0,0 +1,86 @@ +from flask_wtf import FlaskForm +from wtforms import ( + DateField, + IntegerField, + DecimalField, + SubmitField, + TextAreaField, + StringField, +) +from wtforms.validators import Length + +from .checks import * + +from wtforms.validators import Optional + + +class DeleteRegularCostForm(FlaskForm): + submit = SubmitField(label="Really delete this regular cost!") + + +class EndRegularCostForm(FlaskForm): + submit = SubmitField(label="Really end this regular cost!") + + +class EditRegularCostForm(FlaskForm): + start_at = DateField("Date of first instance") + ends_at = DateField( + "Date of last instance", validators=[Optional(strip_whitespace=True)] + ) + + days = StringField("Days for instance", validators=[regular_costs_days_check]) + + costs = DecimalField("Costs (€, per instance)", places=2, validators=[costs_check]) + description = TextAreaField("Description", validators=[Length(1, 4096)]) + submit = SubmitField(label="Update it!") + + def preinit_with_data(self): + if self.costs.data: + self.costs.default = self.costs.data + if self.start_at.data: + self.start_at.default = self.start_at.data + if self.ends_at.data: + self.ends_at.default = self.ends_at.data + if self.days.data: + self.days.default = self.days.data + if self.description.data: + self.description.default = self.description.data + + def get_hint_messages(self): + messages = {"costs": "Costs must be higher than 0.01 €."} + return messages + + +class CreateRegularCostForm(FlaskForm): + start_at = DateField("Date of first instance") + ends_at = DateField( + "Date of last instance", validators=[Optional(strip_whitespace=True)] + ) + + days = StringField("Days for instance", validators=[regular_costs_days_check]) + + costs = DecimalField("Costs (€, per instance)", places=2, validators=[costs_check]) + description = TextAreaField("Description", validators=[Length(1, 4096)]) + submit = SubmitField(label="Do it!") + + def preinit_with_data(self): + if self.start_at.data: + self.start_at.default = self.start_at.data + else: + self.start_at.default = date.today() + + if self.ends_at.data: + self.ends_at.default = self.ends_at.data + else: + self.ends_at.default = None + + if self.days.data: + self.days.default = self.days.data + + if self.costs.data: + self.costs.default = self.costs.data + else: + self.costs.default = 0 + + if self.description.data: + self.description.default = self.description.data diff --git a/app/routes/__init__.py b/app/routes/__init__.py index c169592..00e44b6 100644 --- a/app/routes/__init__.py +++ b/app/routes/__init__.py @@ -3,4 +3,5 @@ from .admin import * from .misc import * from .pitstop import * from .service import * -from .filling_stations import * \ No newline at end of file +from .filling_stations import * +from .regular_cost import * diff --git a/app/routes/pitstop.py b/app/routes/pitstop.py index 4781660..c9b8446 100644 --- a/app/routes/pitstop.py +++ b/app/routes/pitstop.py @@ -3,11 +3,16 @@ from flask_security import login_required from flask_security.core import current_user from sqlalchemy.exc import IntegrityError from datetime import date +import types + from ..entities import Vehicle, Consumable, Pitstop -from ..forms import SelectVehicleForm, SelectConsumableForm, CreatePitstopForm, EditPitstopForm, DeletePitStopForm -from ..tools import db_log_update, db_log_delete, db_log_add, pitstop_service_key, \ - get_event_line_for_vehicle, update_filling_station_prices +from ..forms import SelectVehicleForm, SelectConsumableForm, \ + CreatePitstopForm, EditPitstopForm, DeletePitStopForm +from ..tools import db_log_update, db_log_delete, db_log_add, \ + pitstop_service_key, get_event_line_for_vehicle, \ + update_filling_station_prices, RegularCostInstance, \ + calculate_regular_cost_instances from .. import app, db @@ -162,11 +167,15 @@ def get_pit_stops(): data.append(pitstop) for service in vehicle.services: data.append(service) + for regular_instance in calculate_regular_cost_instances(vehicle): + data.append(regular_instance) + data.sort(key=pitstop_service_key) v = { 'id': vehicle.id, 'name': vehicle.name, - 'data': data + 'data': data, + "regulars": vehicle.regulars, } user['vehicles'].append(v) diff --git a/app/routes/regular_cost.py b/app/routes/regular_cost.py new file mode 100644 index 0000000..e0d1b24 --- /dev/null +++ b/app/routes/regular_cost.py @@ -0,0 +1,161 @@ +from flask import url_for, redirect, render_template, flash +from flask_security import login_required +from flask_security.core import current_user +from sqlalchemy.exc import IntegrityError +from datetime import date + +from ..entities import Vehicle, Consumable, Pitstop, RegularCost +from ..forms import ( + SelectVehicleForm, + CreateRegularCostForm, + DeleteRegularCostForm, + EditRegularCostForm, + EndRegularCostForm, +) +from ..tools import ( + db_log_update, + db_log_delete, + db_log_add, + pitstop_service_key, + get_event_line_for_vehicle, + update_filling_station_prices, +) +from .. import app, db + + +@app.route("/regular_costs/delete/", methods=["GET", "POST"]) +@login_required +def delete_regular_form(pid): + regular_cost = RegularCost.query.filter(RegularCost.id == pid).first() + if regular_cost is None: + return redirect(url_for("get_pit_stops")) + vehicle = Vehicle.query.filter(Vehicle.id == regular_cost.vehicle_id).first() + if vehicle not in current_user.vehicles: + return redirect(url_for("get_pit_stops")) + + form = DeleteRegularCostForm() + if form.validate_on_submit(): + db.session.delete(regular_cost) + db.session.commit() + db_log_delete(regular_cost) + return redirect(url_for("get_pit_stops", _anchor="v" + str(vehicle.id))) + + return render_template( + "deleteRegularCostForm.html", form=form, regular_cost=regular_cost + ) + + +@app.route("/regular_costs/vehicle/select", methods=["GET", "POST"]) +@login_required +def select_vehicle_for_new_regular_cost(): + if len(current_user.vehicles) == 1: + return redirect( + url_for("create_regular_cost_for_vehicle", 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(): + return redirect( + url_for("create_regular_cost_for_vehicle", vid=form.vehicle.data) + ) + + return render_template("selectVehicle.html", form=form) + + +@app.route("/regular_costs/vehicle//create", methods=["GET", "POST"]) +@login_required +def create_regular_cost_for_vehicle(vid): + vehicle = Vehicle.query.get(vid) + if vehicle is None or vehicle not in current_user.vehicles: + return redirect(url_for("get_account_page")) + + form = CreateRegularCostForm() + + form.preinit_with_data() + + if form.validate_on_submit(): + regular_cost = RegularCost( + vid, + form.description.data, + form.costs.data, + form.days.data, + form.start_at.data, + form.ends_at.data, + ) + db.session.add(regular_cost) + vehicle.regulars.append(regular_cost) + db.session.commit() + return redirect(url_for("get_pit_stops", _anchor="v" + str(vehicle.id))) + + form.process() + return render_template( + "createRegularCostForm.html", form=form, vehicle=vehicle, messages=[] + ) + + +@app.route("/regular_costs/edit/", methods=["GET", "POST"]) +@login_required +def edit_regular_form(pid): + edit_regular = RegularCost.query.get(pid) + if edit_regular is None: + return redirect(url_for("get_pit_stops")) + + vehicle = Vehicle.query.filter(Vehicle.id == edit_regular.vehicle_id).first() + if vehicle not in current_user.vehicles: + return redirect(url_for("get_pit_stops")) + + form = EditRegularCostForm() + form.preinit_with_data() + + if not form.is_submitted(): + form.costs.default = edit_regular.costs + form.start_at.default = edit_regular.start_at + form.ends_at.default = edit_regular.ends_at + form.days.default = edit_regular.days + form.description.default = edit_regular.description + + if form.validate_on_submit(): + edit_regular.start_at = form.start_at.data + edit_regular.ends_at = form.ends_at.data + edit_regular.costs = form.costs.data + edit_regular.days = form.days.data + edit_regular.description = form.description.data + db.session.commit() + db_log_update(edit_regular) + return redirect(url_for("get_pit_stops", _anchor="v" + str(vehicle.id))) + + form.preinit_with_data() + form.process() + return render_template( + "editRegularCostForm.html", + form=form, + vehicle=vehicle, + messages=form.get_hint_messages(), + ) + + +@app.route("/regular_costs/end/", methods=["GET", "POST"]) +@login_required +def end_regular_form(pid): + edit_regular = RegularCost.query.get(pid) + if edit_regular is None: + return redirect(url_for("get_pit_stops")) + + vehicle = Vehicle.query.filter(Vehicle.id == edit_regular.vehicle_id).first() + if vehicle not in current_user.vehicles: + return redirect(url_for("get_pit_stops")) + + form = EndRegularCostForm() + if form.validate_on_submit(): + edit_regular.ends_at = date.today() + db.session.commit() + return redirect(url_for("get_pit_stops", _anchor="v" + str(vehicle.id))) + + return render_template( + "endRegularCostForm.html", + form=form, + vehicle=vehicle, + ) + diff --git a/app/static/js/main.js b/app/static/js/main.js index 4f1b963..1d594a4 100644 --- a/app/static/js/main.js +++ b/app/static/js/main.js @@ -12,7 +12,7 @@ function createChart(id, data, unit) { "axisAlpha": 0, "position": "left", "ignoreAxisWidth":true, -// "title": unit + "title": unit }], "balloon": { "borderThickness": 1, diff --git a/app/templates/createRegularCostForm.html b/app/templates/createRegularCostForm.html new file mode 100644 index 0000000..3870fcd --- /dev/null +++ b/app/templates/createRegularCostForm.html @@ -0,0 +1,39 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

New Regular Cost for '{{ vehicle.name }}'

+
+ {{ form.hidden_tag() }} + + {{ render_field_with_errors(form.start_at) }} + + {{messages['start_at']}} + + + {{ render_field_with_errors(form.ends_at) }} + + {{messages['ends_at']}} + + + {{ render_field_with_errors(form.days) }} + Format as 'Month-Day' (e.g. 05-25) and separate with ','. + + {{messages['days']}} + + + {{ render_field_with_errors(form.costs) }} + + {{messages['costs']}} + + {{ render_field_with_errors(form.description) }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+
+{% endblock %} diff --git a/app/templates/deleteRegularCostForm.html b/app/templates/deleteRegularCostForm.html new file mode 100644 index 0000000..9ccf944 --- /dev/null +++ b/app/templates/deleteRegularCostForm.html @@ -0,0 +1,46 @@ +{% extends 'layout.html' %} + +{% block body %} +
+
+
+
+

Delete Regular Cost?

+ + + + + + + + + + + + + + + + + + + + + +
Description of regular cost{{ regular_cost.description }}
Costs (per instance) + {% if regular_cost.costs %} + {{ regular_cost.costs }} + {% else %} + -- + {% endif %} + € +
Days for regular costs{{ regular_cost.days }}
regular costs starting from{{ regular_cost.start_at }}
regular costs ending at{{ regular_cost.ends_at }}
+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+
+{% endblock %} diff --git a/app/templates/deleteVehicleForm.html b/app/templates/deleteVehicleForm.html index c058771..d8694b3 100644 --- a/app/templates/deleteVehicleForm.html +++ b/app/templates/deleteVehicleForm.html @@ -8,7 +8,7 @@

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

{{ form.hidden_tag() }} - {{ render_field_with_errors(form.submit) }} + {{ render_field_with_errors(form.submit, include_cancel=True) }}
diff --git a/app/templates/editRegularCostForm.html b/app/templates/editRegularCostForm.html new file mode 100644 index 0000000..599e570 --- /dev/null +++ b/app/templates/editRegularCostForm.html @@ -0,0 +1,39 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Edit Regular Cost for '{{ vehicle.name }}'

+
+ {{ form.hidden_tag() }} + + {{ render_field_with_errors(form.start_at) }} + + {{messages['start_at']}} + + + {{ render_field_with_errors(form.ends_at) }} + + {{messages['ends_at']}} + + + {{ render_field_with_errors(form.days) }} + Format as 'Month-Day' (e.g. 05-25) and separate with ','. + + {{messages['days']}} + + + {{ render_field_with_errors(form.costs) }} + + {{messages['costs']}} + + {{ render_field_with_errors(form.description) }} + + {{ render_field_with_errors(form.submit) }} +
+
+
+
+{% endblock %} diff --git a/app/templates/endRegularCostForm.html b/app/templates/endRegularCostForm.html new file mode 100644 index 0000000..483fb58 --- /dev/null +++ b/app/templates/endRegularCostForm.html @@ -0,0 +1,16 @@ +{% extends "layout.html" %} + +{% block body %} +
+
+
+
+

Edit Regular Cost for '{{ vehicle.name }}'

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.submit) }} +
+
+
+
+{% endblock %} diff --git a/app/templates/layout.html b/app/templates/layout.html index 4295654..dc6582e 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -3,6 +3,7 @@
  • Plan Pitstop
  • Create Pitstop
  • Create Service
  • +
  • Create Regular Cost
  • Statistics
  • Account
  • {% if current_user.has_role('admin') %} @@ -15,20 +16,17 @@ {% endif %} {%- endmacro %} -{% macro render_field_with_errors(field) %} +{% macro render_field_with_errors(field, include_cancel=True) %}
    {% if field.type == 'SubmitField' %} -
    - -
    +
    +
    + {% if include_cancel %} + + {% endif %}
    - -
    +
    {% else %}
    {% endmacro %} @@ -115,6 +130,7 @@ {{ print_vehicle_table(vehicle) }}