Merge branch 'add_vehicles' into 'master'

Add vehicles



See merge request !5
This commit is contained in:
Joachim Lusiardi 2016-04-27 07:24:59 +02:00
commit a7df0aebee
47 changed files with 652 additions and 352 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
**.DS_Store
**.swp
**.pyc

View File

@ -1,236 +1,5 @@
from datetime import date
from flask import Flask
from flask import render_template, make_response
from flask import request, redirect, g
from flask import url_for
from flask_sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required, roles_required, utils
from flask.ext.security import user_registered
from flask.ext.mail import Mail, Message
from flask_security.core import current_user
from flask_wtf import Form
from wtforms import DateField, IntegerField, DecimalField
from wtforms.validators import DataRequired, ValidationError
import os
app = Flask(__name__)
DATABASE = '/data/rollerverbrauch.db'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE
db = SQLAlchemy(app)
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_CHANGEABLE'] = True
app.config['SECURITY_RECOVERABLE'] = True
app.config.from_envvar('config')
app.config.from_object(__name__)
mail = Mail(app)
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')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return self.name
def __hash__(self):
return hash(self.name)
class User(db.Model, UserMixin):
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())
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('users', lazy='dynamic')
)
def __repr__(self):
return '<User %r>' % self.email
class Pitstop(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
odometer = db.Column(db.Integer)
litres = db.Column(db.Numeric(5,2))
def __init__(self, odometer, litres, date):
self.odometer = odometer
self.litres = litres
self.date = date
def __repr__(self):
return '<Pitstop %r km, %r l>' % (self.odometer, self.litres)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
@user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
"""
Called after a user was created
"""
role = user_datastore.find_role('user')
user_datastore.add_role_to_user(user, role)
@app.before_first_request
def before_first_request():
db.create_all()
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()
@app.before_request
def before_request():
g.data = {}
@app.route('/')
@login_required
def index():
return redirect(url_for('get_pit_stops'))
def date_check(form, field):
if field.data < form.pitstop.date:
raise ValidationError('The new date must after %s' % form.pitstop.date)
def odometer_check(form, field):
if field.data <= form.pitstop.odometer:
raise ValidationError('The new odometer value must be higher than %i km' % form.pitstop.odometer)
def litres_check(form, field):
if field.data is not None and field.data <= 0:
raise ValidationError('You must fuel at least 0.1 l')
class CreatePitstopForm(Form):
date = DateField('Date of Pitstop', validators=[date_check])
odometer = IntegerField('Odometer (km)', validators=[odometer_check])
litres = DecimalField('Litres (l)', places=1, validators=[litres_check])
pitstop = None
def set_pitstop(self, pitstop):
self.pitstop = pitstop
@app.route('/pitstops/createForm', methods=['GET', 'POST'])
@login_required
def create_pit_stop_form():
last_pitstop = Pitstop.query.order_by(Pitstop.id.desc()).first()
if last_pitstop is None:
last_pitstop = Pitstop(0, 0, date.today())
form = CreatePitstopForm()
form.set_pitstop(last_pitstop)
if form.validate_on_submit():
new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data)
db.session.add(new_stop)
db.session.commit()
return redirect(url_for('get_pit_stops'))
# dynamically set values
form.odometer.default = last_pitstop.odometer
form.litres.default = last_pitstop.litres
form.date.default = date.today()
form.process()
return render_template('newPitStopForm.html', form=form)
@app.route('/pitstops', methods=['GET'])
@login_required
def get_pit_stops():
data = prepare_pit_stops(Pitstop.query.all())
g.data['pitstops'] = data
return render_template('pitstops.html', data=g.data)
@app.route('/manual', methods=['GET'])
@login_required
def get_manual():
return render_template('manual.html', data=g.data)
@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)
@app.route('/account', methods=['GET'])
@login_required
def get_account_page():
print(current_user)
return render_template('account.html', data=g.data)
@app.route('/statistics', methods=['GET'])
@login_required
def get_statistics():
pitstops = Pitstop.query.all()
count = len(pitstops)
distance = 0
sum_litres = 0
average_distance = 0
average_litres_fuelled = 0
average_litres_used = 0
if count > 0:
sum_litres = 0
for pitstop in pitstops:
sum_litres += pitstop.litres
average_litres_fuelled = sum_litres/count
if count > 1:
distance = pitstops[-1].odometer - pitstops[0].odometer
average_distance = distance/(count - 1)
average_litres_used = 100 * (sum_litres-pitstops[0].litres)/distance
g.data['distance'] = distance
g.data['count'] = count
g.data['litres'] = sum_litres
g.data['averageDistance'] = average_distance
g.data['averageListresFuelled'] = average_litres_fuelled
g.data['averageListresUsed'] = average_litres_used
return render_template('statistics.html', data=g.data)
def prepare_pit_stops(pss):
pitstops = []
for pitstop_index in range(0, len(pss)):
p = dict()
p['odometer'] = pss[pitstop_index].odometer
p['litres'] = pss[pitstop_index].litres
p['date'] = pss[pitstop_index].date
pitstops.append(p)
for pitstop_index in range(1, len(pitstops)):
last = pitstops[pitstop_index - 1]
curr = pitstops[pitstop_index]
curr['distance'] = curr['odometer'] - last['odometer']
curr['average'] = 100 * curr['litres']/curr['distance']
last_date = last['date']
curr_date = curr['date']
curr['days'] = (curr_date - last_date).days
pitstops.reverse()
return pitstops
from rollerverbrauch import app
if __name__ == '__main__':
DEBUG = 'DEBUG' in os.environ and os.environ['DEBUG'] != 'False'

View File

@ -0,0 +1,209 @@
from datetime import date
from flask import Flask
from flask import redirect, g
from flask import render_template
from flask import url_for
from flask.ext.mail import Mail
from flask.ext.security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required, roles_required
from flask.ext.security import user_registered
from flask_security.core import current_user
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////data/rollerverbrauch.db'
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_CHANGEABLE'] = True
app.config['SECURITY_RECOVERABLE'] = True
app.config.from_envvar('config')
app.config.from_object(__name__)
db = SQLAlchemy(app)
mail = Mail(app)
from rollerverbrauch.tools import \
VehicleStats
from rollerverbrauch.forms import \
CreatePitstopForm, \
EditVehicleForm, \
DeleteVehicleForm, \
SelectVehicleForm
from rollerverbrauch.entities import \
User, \
Role, \
Pitstop, \
Vehicle
# required to activate the filters
import rollerverbrauch.filters
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
@user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
"""
Called after a user was created
"""
role = user_datastore.find_role('user')
user_datastore.add_role_to_user(user, role)
v = Vehicle('default vehicle')
db.session.add(v)
user.vehicles.append(v)
db.session.commit()
@app.before_first_request
def before_first_request():
db.create_all()
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()
@app.before_request
def before_request():
g.data = {}
@app.route('/')
@login_required
def index():
return redirect(url_for('get_pit_stops'))
@app.route('/account/edit_vehicle/<int:vid>', methods=['GET', 'POST'])
@login_required
def edit_vehicle(vid):
vehicle = Vehicle.query.filter(Vehicle.id == vid).first()
form = EditVehicleForm()
if form.validate_on_submit():
vehicle.name = form.name.data
db.session.commit()
return redirect(url_for('get_account_page'))
form.name.default = vehicle.name
form.process()
return render_template('editVehicleForm.html', form=form)
@app.route('/account/delete_vehicle/<int:vid>', methods=['GET', 'POST'])
@login_required
def delete_vehicle(vid):
vehicle = Vehicle.query.filter(Vehicle.id == vid).first()
# prevent deletion of foreign vehicles
if vehicle not in current_user.vehicles:
return redirect(url_for('get_account_page'))
form = DeleteVehicleForm()
if form.validate_on_submit():
db.session.delete(vehicle)
db.session.commit()
return redirect(url_for('get_account_page'))
return render_template('deleteVehicleForm.html', form=form, vehicle=vehicle)
@app.route('/account/create_vehicle', methods=['GET', 'POST'])
@login_required
def create_vehicle():
form = EditVehicleForm()
if form.validate_on_submit():
new_vehicle = Vehicle(form.name.data)
db.session.add(new_vehicle)
current_user.vehicles.append(new_vehicle)
db.session.commit()
return redirect(url_for('get_account_page'))
return render_template('createVehicleForm.html', form=form)
@app.route('/pitstops/select_vehicle', methods=['GET', 'POST'])
@login_required
def select_vehicle_for_new_pitstop():
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('selectVehice.html', form=form)
return redirect(url_for('create_pit_stop_form', vid=form.vehicle.data))
return render_template('selectVehice.html', form=form)
@app.route('/pitstops/create/<int:vid>', 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:
return redirect(url_for('select_vehicle_for_new_pitstop'))
if len(vehicle.pitstops) > 0:
last_pitstop = vehicle.pitstops[-1]
else:
last_pitstop = Pitstop(0, 0, date.today())
form = CreatePitstopForm()
form.set_pitstop(last_pitstop)
if form.validate_on_submit():
new_stop = Pitstop(form.odometer.data, form.litres.data, form.date.data)
db.session.add(new_stop)
vehicle.pitstops.append(new_stop)
db.session.commit()
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.process()
return render_template('newPitStopForm.html', form=form, vehicle = vehicle)
@app.route('/pitstops', methods=['GET'])
@login_required
def get_pit_stops():
return render_template('pitstops.html', user=current_user)
@app.route('/manual', methods=['GET'])
@login_required
def get_manual():
return render_template('manual.html', data=g.data)
@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)
@app.route('/account', methods=['GET'])
@login_required
def get_account_page():
print(current_user)
return render_template('account.html', data=g.data)
@app.route('/statistics', methods=['GET'])
@login_required
def get_statistics():
stats = []
for vehicle in current_user.vehicles:
stats.append(VehicleStats(vehicle))
return render_template('statistics.html', data=stats)

View File

@ -0,0 +1,68 @@
from rollerverbrauch import db
from flask.ext.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')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return self.name
def __hash__(self):
return hash(self.name)
class User(db.Model, UserMixin):
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'
)
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('users', lazy='dynamic')
)
def __repr__(self):
return '<User %r>' % self.email
class Vehicle(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
name = db.Column(db.String(255), unique=True)
pitstops = db.relationship(
'Pitstop'
)
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Vehicle %r>' % self.name
class Pitstop(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
odometer = db.Column(db.Integer)
litres = db.Column(db.Numeric(5, 2))
vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id'))
def __init__(self, odometer, litres, date):
self.odometer = odometer
self.litres = litres
self.date = date
def __repr__(self):
return '<Pitstop %r km, %r l>' % (self.odometer, self.litres)

View File

@ -0,0 +1,9 @@
from rollerverbrauch import app
@app.template_filter('none_filter')
def none_filter(value):
if value is None:
return ''
else:
return value

View File

@ -0,0 +1,43 @@
from flask_wtf import Form
from wtforms import DateField, IntegerField, DecimalField, StringField, SelectField, SubmitField
from wtforms.validators import ValidationError, Length
def date_check(form, field):
if field.data < form.pitstop.date:
raise ValidationError('The new date must after %s' % form.pitstop.date)
def odometer_check(form, field):
if field.data <= form.pitstop.odometer:
raise ValidationError('The new odometer value must be higher than %i km' % form.pitstop.odometer)
def litres_check(form, field):
if field.data is not None and field.data <= 0:
raise ValidationError('You must fuel at least 0.1 l')
class SelectVehicleForm(Form):
vehicle = SelectField('Vehicle', 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])
litres = DecimalField('Litres (l)', places=2, validators=[litres_check])
submit = SubmitField(label='Do it!')
pitstop = None
def set_pitstop(self, pitstop):
self.pitstop = pitstop
class EditVehicleForm(Form):
name = StringField('Name', validators=[Length(1, 255)])
submit = SubmitField(label='Do it!')
class DeleteVehicleForm(Form):
submit = SubmitField(label='Do it!')

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 980 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,56 @@
{% extends "layout.html" %}
{% block body %}
<h3>Account management for {{current_user.email}}</h3>
<div class="panel panel-default">
<div class="panel-heading">Password</div>
<div class="panel-body">
<a href='{{ url_for('security.change_password') }}'>
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Change
</a>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Vehicles</div>
<div class="panel-body">
<a href="{{ url_for('create_vehicle') }}">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> create
</a>
</div>
<table class="table table-striped table-bordered">
<tr>
<th>
Vehicle
</th>
<th>
Info
</th>
<th>
Actions
</th>
</tr>
{% for vehicle in current_user.vehicles %}
<tr>
<td style="text-align:center">
{{ vehicle.name }}
</td>
<td style="text-align:center">
{{ vehicle.pitstops | length }} pitstops
</td>
<td style="text-align:center">
<a href="{{ url_for('edit_vehicle', vid=vehicle.id) }}">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> edit
</a>
{% if current_user.vehicles | length > 1 %}
<a href="{{ url_for('delete_vehicle', vid=vehicle.id) }}">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> delete
</a>
{% else %}
&nbsp;
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block body %}
<h1>Admin</h1>
<h3>Admin</h3>
We have {{ data.users|length }} users so far:
<ul>
{% for user in data.users %}

View File

@ -0,0 +1,12 @@
{% extends "layout.html" %}
{% block body %}
<h3>Create vehicle</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.name) }}
{{ render_field_with_errors(form.submit) }}
</form>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block body %}
<h3>Delete vehicle '{{vehicle.name}}'</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.submit) }}
</form>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "layout.html" %}
{% block body %}
<h3>Edit vehicle</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.name) }}
{{ render_field_with_errors(form.submit) }}
</form>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% macro navigation() -%}
{% if current_user.email %}
<li><a href='{{ url_for('create_pit_stop_form') }}'>Create Pitstop</a></li>
<li><a href='{{ url_for('select_vehicle_for_new_pitstop') }}'>Create Pitstop</a></li>
<li><a href='{{ url_for('get_statistics') }}'>Statistics</a></li>
<li><a href='{{ url_for('get_account_page') }}'>Account</a></li>
{% if current_user.has_role('admin') %}
@ -15,11 +15,36 @@
{% macro render_field_with_errors(field) %}
<div class="form-group">
{% if field.type == 'SubmitField' %}
<div class="col-sm-12" style="align:center">
<input id="{{ field.id }}" name="{{ field.id }}" class="btn btn-default" type="submit" value="{{ field.label.text }}">
</div>
{% else %}
<label class="col-sm-6 control-label">
{{ field.label }}
</label>
<div class="col-sm-2">
{% if field.type == 'SelectField' %}
<select id="{{ field.id }}" name="{{ field.id }}" class="form-control">
{% for choice in field.choices %}
<option value="{{ choice[0] }}">{{ choice[1] }}</option>
{% endfor %}
</select>
{% elif field.type == 'BooleanField' %}
<input class="form-control" type="checkbox" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" />
{% elif field.type == 'StringField' %}
<input class="form-control" type="text" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" />
{% elif field.type == 'PasswordField' %}
<input class="form-control" type="password" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" />
{% elif field.type == 'DateField' %}
<input class="form-control" type="date" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" />
{% elif field.type == 'IntegerField' %}
<input class="form-control" type="number" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" step="1" />
{% elif field.type == 'DecimalField' %}
<input class="form-control" type="number" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.default|none_filter }}" step="{{ 1 / 10 ** field.places}}" />
{% else %}
{{ field(**kwargs)|safe }}
{% endif %}
{% if field.errors %}
<p class='error'>
{% for error in field.errors %}
@ -28,6 +53,8 @@
</p>
{% endif %}
</div>
{% endif %}
</div>
{% endmacro %}

View File

@ -1,12 +1,13 @@
{% extends "layout.html" %}
{% block body %}
<h3>New Pitstop for '{{ vehicle.name }}'</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.date) }}
{{ render_field_with_errors(form.odometer) }}
{{ render_field_with_errors(form.litres) }}
<input type="submit" value="Go">
{{ render_field_with_errors(form.submit) }}
</form>

View File

@ -0,0 +1,86 @@
{% extends "layout.html" %}
{% block body %}
<div id="content">
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
{% for vehicle in current_user.vehicles %}
<li {% if loop.first %}class="active" {%endif %}>
<a href="#v{{vehicle.id}}" id="i{{vehicle.id}}" data-toggle="tab">
{{ vehicle.name }}
</a>
</li>
{% endfor %}
</ul>
<div id="my-tab-content" class="tab-content">
{% for vehicle in current_user.vehicles %}
<div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}">
<h3>{{vehicle.name}}</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<tr>
<th>
Date<br/>
Days
</th>
<th>
Odometer<br/>
Distance
</th>
<th>
Litres<br/>
Average
</th>
</tr>
{% for pitstop in vehicle.pitstops|reverse %}
{% if not loop.last %}
{% set days = (pitstop.date - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].date).days %}
{% set distance = pitstop.odometer - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].odometer %}
{% set average = (pitstop.litres / distance) * 100 %}
<tr class='pitstop'>
<td>
{{pitstop.date}}<br/>
{{ days }} days
</td>
<td>
{{pitstop.odometer}} km<br/>
{{distance}} km
</td>
<td>
{{pitstop.litres}} l<br/>
{{average | round(2)}} l/100km
</td>
</tr>
{% else %}
<tr class='pitstop'>
<td>
{{pitstop.date}}<br/>
-- days
</td>
<td>
{{pitstop.odometer}} km<br/>
-- km
</td>
<td>
{{pitstop.litres}} l<br/>
-- l/100km
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
</div>
{% endfor %}
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function ($) {
$('#tabs').tab();
if(window.location.hash != "") {
$('a[href="' + window.location.hash + '"]').click()
}
});
</script>
{% endblock %}

View File

@ -8,6 +8,6 @@
{{ render_field_with_errors(change_password_form.password) }}
{{ render_field_with_errors(change_password_form.new_password) }}
{{ render_field_with_errors(change_password_form.new_password_confirm) }}
{{ render_field(change_password_form.submit) }}
{{ render_field_with_errors(change_password_form.submit) }}
</form>
{% endblock %}

View File

@ -6,6 +6,6 @@
<form class='form-horizontal' action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form">
{{ forgot_password_form.hidden_tag() }}
{{ render_field_with_errors(forgot_password_form.email) }}
{{ render_field(forgot_password_form.submit) }}
{{ render_field_with_errors(forgot_password_form.submit) }}
</form>
{% endblock %}

View File

@ -9,7 +9,7 @@
{{ render_field_with_errors(login_user_form.password) }}
{{ render_field_with_errors(login_user_form.remember) }}
{{ render_field(login_user_form.next) }}
{{ render_field(login_user_form.submit) }}
{{ render_field_with_errors(login_user_form.submit) }}
{% if security.recoverable %}
<a href="{{ url_for_security('forgot_password') }}">Forgot password</a>
{% endif %}

View File

@ -10,6 +10,6 @@
{% if register_user_form.password_confirm %}
{{ render_field_with_errors(register_user_form.password_confirm) }}
{% endif %}
{{ render_field(register_user_form.submit) }}
{{ render_field_with_errors(register_user_form.submit) }}
</form>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block body %}
<h3>Select Vehicle</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.vehicle) }}
{{ render_field_with_errors(form.submit) }}
</form>
{% endblock %}

View File

@ -0,0 +1,62 @@
{% extends "layout.html" %}
{% block body %}
<div id="content">
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
{% for vehicle in data %}
<li {% if loop.first %}class="active" {%endif %}>
<a href="#v{{vehicle.id}}" id="i{{vehicle.id}}" data-toggle="tab">
{{ vehicle.name }}
</a>
</li>
{% endfor %}
</ul>
<div id="my-tab-content" class="tab-content">
{% for vehicle in data %}
<div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}">
<h3>{{vehicle.name}}</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<tr>
<th>Number of Pitstops:</th>
<td>{{ vehicle.pitstop_count }}</td>
</tr>
<tr>
<th>Logged Distance:</th>
<td>{{ vehicle.overall_distance | round(2) }} km</td>
</tr>
<tr>
<th>Average Distance:</th>
<td>{{ vehicle.average_distance | round(2) }} km</td>
</tr>
<tr>
<th>Litres fuelled:</th>
<td>{{ vehicle.overall_litres | round(2) }} l</td>
</tr>
<tr>
<th>Average Litres fuelled:</th>
<td>{{ vehicle.average_litres_fuelled | round(2) }} l</td>
</tr>
<tr>
<th>Average Litres used:</th>
<td>{{ vehicle.average_litres_used | round(2) }} l/100km</td>
</tr>
</table>
</div>
</div>
{% endfor %}
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function ($) {
$('#tabs').tab();
if(window.location.hash != "") {
$('a[href="' + window.location.hash + '"]').click()
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,18 @@
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
if self.pitstop_count > 0:
for pitstop in vehicle.pitstops:
self.overall_litres += pitstop.litres
self.average_litres_fuelled = self.overall_litres / self.pitstop_count
if self.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

View File

@ -1,16 +0,0 @@
drop table if exists pitstops;
create table pitstops (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`date` TEXT NOT NULL,
`odometer` INTEGER NOT NULL,
`litres` REAL NOT NULL
);
drop table if exists users;
create table `users` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`salt` TEXT NOT NULL,
`password` TEXT NOT NULL
);
insert into users (name, salt, password) values ('shing19m', 'pL85Kl2U', '207357fdbf6f379c53bb5ab7fa0bc8c0072ae743973a510f551db7b5c90049b7');

View File

@ -1,7 +0,0 @@
{% extends "layout.html" %}
{% block body %}
<h1>Account management for {{current_user.email}}</h1>
<a href='{{ url_for('security.change_password') }}'>Change password</a>
{% endblock %}

View File

@ -1,39 +0,0 @@
{% extends "layout.html" %}
{% block body %}
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<tr>
<th>
Date<br />
Days
</th>
<th>
Odometer<br />
Distance
</th>
<th>
Litres<br />
Average
</th>
</tr>
{% for pitstop in data['pitstops'] %}
<tr class='pitstop'>
<td>
{{pitstop.date}}<br />
{% if pitstop.days %}{{pitstop.days}}{% else %} --{% endif %} days
</td>
<td>
{{pitstop.odometer}} km<br />
{% if pitstop.distance %}{{pitstop.distance}}{% else %} --{% endif %} km
</td>
<td>
{{pitstop.litres}} l<br />
{% if pitstop.average %}{{pitstop.average | round(2)}}{% else %} --{% endif %} l/100km
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@ -1,33 +0,0 @@
{% extends "layout.html" %}
{% block body %}
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<tr>
<th>Number of Pitstops:</th>
<td>{{ data.count }}</td>
</tr>
<tr>
<th>Logged Distance:</th>
<td>{{ data.distance | round(2) }} km</td>
</tr>
<tr>
<th>Average Distance:</th>
<td>{{ data.averageDistance | round(2) }} km</td>
</tr>
<tr>
<th>Litres fuelled:</th>
<td>{{ data.litres | round(2) }} l</td>
</tr>
<tr>
<th>Average Litres fuelled:</th>
<td>{{ data.averageListresFuelled | round(2) }} l</td>
</tr>
<tr>
<th>Average Litres used:</th>
<td>{{ data.averageListresUsed | round(2) }} l/100km</td>
</tr>
</table>
</div>
{% endblock %}

View File

@ -4,14 +4,14 @@ SIZES="57 60 72 76 114 120 144 152 180"
for SIZE in $SIZES
do
convert ./icon_orig.png -resize ${SIZE}x${SIZE} app/static/apple-touch-icon-${SIZE}.png
convert ./icon_orig.png -resize ${SIZE}x${SIZE} app/rollerverbrauch/static/apple-touch-icon-${SIZE}.png
done
convert ./icon_orig.png -resize 192x192 app/static/android-icon-192x192.png
convert ./icon_orig.png -resize 192x192 app/rollerverbrauch/static/android-icon-192x192.png
SIZES="16 32 96"
for SIZE in $SIZES
do
convert ./icon_orig.png -resize ${SIZE}x${SIZE} app/static/favicon-${SIZE}.png
convert ./icon_orig.png -resize ${SIZE}x${SIZE} app/rollerverbrauch/static/favicon-${SIZE}.png
done