Merge branch 'development' into 'master'

Merge preparation for Cathrine



See merge request !13
This commit is contained in:
Joachim Lusiardi 2016-05-28 20:47:26 +02:00
commit f82cf425fd
24 changed files with 527 additions and 222 deletions

View File

@ -11,6 +11,9 @@ a folder that will serve as configuration directory and fill in the
information. The directory will be used as volume during container information. The directory will be used as volume during container
operation. operation.
## start database
`docker run --name pitstops_db -e MYSQL_ROOT_PASSWORD=$SOMESECUREPASSWORD$ -e MYSQL_DATABASE=pitstops -d mysql:latest`
## run in development ## run in development
Include the development version of the code as volume, so the app gets Include the development version of the code as volume, so the app gets
@ -22,3 +25,4 @@ debugging during development.
## run in production ## run in production
`docker run --name pitstops -d -v /data/pitstops/:/data -v /configs/pitstops/:/app/config -p 80:5000 rollerverbrauch` `docker run --name pitstops -d -v /data/pitstops/:/data -v /configs/pitstops/:/app/config -p 80:5000 rollerverbrauch`

View File

@ -9,6 +9,7 @@ from flask.ext.security import Security, SQLAlchemyUserDatastore, \
from flask.ext.security import user_registered from flask.ext.security import user_registered
from flask_security.core import current_user from flask_security.core import current_user
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask.ext.security.forms import LoginForm
app = Flask(__name__) app = Flask(__name__)
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
@ -21,17 +22,14 @@ app.config.from_object(__name__)
db = SQLAlchemy(app) db = SQLAlchemy(app)
mail = Mail(app) mail = Mail(app)
from rollerverbrauch.tools import \ import rollerverbrauch.tools as tools
VehicleStats, \
db_log_add, \
db_log_delete, \
db_log_update
from rollerverbrauch.forms import \ from rollerverbrauch.forms import \
CreatePitstopForm, \ CreatePitstopForm, \
EditVehicleForm, \ EditVehicleForm, \
DeleteVehicleForm, \ DeleteVehicleForm, \
SelectVehicleForm SelectVehicleForm, \
DeleteAccountForm
from rollerverbrauch.entities import \ from rollerverbrauch.entities import \
User, \ User, \
@ -58,8 +56,8 @@ def user_registered_sighandler(app, user, confirm_token):
db.session.add(new_vehicle) db.session.add(new_vehicle)
user.vehicles.append(new_vehicle) user.vehicles.append(new_vehicle)
db.session.commit() db.session.commit()
db_log_add(user) tools.db_log_add(user)
db_log_add(new_vehicle) tools.db_log_add(new_vehicle)
@app.before_first_request @app.before_first_request
@ -76,9 +74,28 @@ def before_request():
@app.route('/') @app.route('/')
@login_required
def index(): def index():
return redirect(url_for('get_pit_stops')) if current_user.is_authenticated:
return redirect(url_for('get_pit_stops'))
else:
user_count = len(User.query.all())
vehicles = Vehicle.query.all()
litres = 0
kilometers = 0
for vehicle in vehicles:
stats = tools.VehicleStats(vehicle)
litres += stats.overall_litres
kilometers += stats.overall_distance
vehicle_count = len(vehicles)
pitstop_count = len(Pitstop.query.all())
data = {
'users':user_count,
'vehicles': vehicle_count,
'pitstops': pitstop_count,
'litres': litres,
'kilometers': kilometers
}
return render_template('index.html', login_user_form=LoginForm(), data=data)
@app.route('/account/edit_vehicle/<int:vid>', methods=['GET', 'POST']) @app.route('/account/edit_vehicle/<int:vid>', methods=['GET', 'POST'])
@ -88,9 +105,11 @@ def edit_vehicle(vid):
form = EditVehicleForm() form = EditVehicleForm()
if form.validate_on_submit(): 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 vehicle.name = form.name.data
db.session.commit() db.session.commit()
db_log_update(vehicle) tools.db_log_update(vehicle)
return redirect(url_for('get_account_page')) return redirect(url_for('get_account_page'))
form.name.default = vehicle.name form.name.default = vehicle.name
@ -112,7 +131,7 @@ def delete_vehicle(vid):
if form.validate_on_submit(): if form.validate_on_submit():
db.session.delete(vehicle) db.session.delete(vehicle)
db.session.commit() db.session.commit()
db_log_delete(vehicle) tools.db_log_delete(vehicle)
return redirect(url_for('get_account_page')) return redirect(url_for('get_account_page'))
return render_template('deleteVehicleForm.html', form=form, vehicle=vehicle) return render_template('deleteVehicleForm.html', form=form, vehicle=vehicle)
@ -124,11 +143,14 @@ def create_vehicle():
form = EditVehicleForm() form = EditVehicleForm()
if form.validate_on_submit(): if form.validate_on_submit():
new_vehicle = Vehicle(form.name.data) 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) db.session.add(new_vehicle)
current_user.vehicles.append(new_vehicle) current_user.vehicles.append(new_vehicle)
db.session.commit() db.session.commit()
db_log_add(new_vehicle) tools.db_log_add(new_vehicle)
return redirect(url_for('get_account_page')) return redirect(url_for('get_account_page'))
return render_template('createVehicleForm.html', form=form) return render_template('createVehicleForm.html', form=form)
@ -170,7 +192,7 @@ def create_pit_stop_form(vid):
db.session.add(new_stop) db.session.add(new_stop)
vehicle.pitstops.append(new_stop) vehicle.pitstops.append(new_stop)
db.session.commit() db.session.commit()
db_log_add(new_stop) tools.db_log_add(new_stop)
return redirect(url_for('get_pit_stops', _anchor= 'v' + str(vehicle.id))) return redirect(url_for('get_pit_stops', _anchor= 'v' + str(vehicle.id)))
form.odometer.default = last_pitstop.odometer form.odometer.default = last_pitstop.odometer
@ -214,5 +236,18 @@ def get_account_page():
def get_statistics(): def get_statistics():
stats = [] stats = []
for vehicle in current_user.vehicles: for vehicle in current_user.vehicles:
stats.append(VehicleStats(vehicle)) stats.append(tools.VehicleStats(vehicle))
return render_template('statistics.html', data=stats) return render_template('statistics.html', data=stats)
@app.route('/account/delete', methods=['GET', 'POST'])
@login_required
def delete_account():
form = DeleteAccountForm()
if form.validate_on_submit():
user_datastore.delete_user(current_user)
db.session.commit()
return redirect(url_for('index'))
return render_template('deleteAccountForm.html', form=form)

View File

@ -40,10 +40,11 @@ class User(db.Model, UserMixin):
class Vehicle(db.Model): class Vehicle(db.Model):
id = db.Column(db.Integer, primary_key=True) 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), unique=True) name = db.Column(db.String(255))
pitstops = db.relationship( pitstops = db.relationship(
'Pitstop' 'Pitstop'
) )
__table_args__ = (db.UniqueConstraint('owner_id', 'name', name='_owner_name_uniq'),)
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name

View File

@ -44,3 +44,7 @@ class EditVehicleForm(Form):
class DeleteVehicleForm(Form): class DeleteVehicleForm(Form):
submit = SubmitField(label='Do it!') submit = SubmitField(label='Do it!')
class DeleteAccountForm(Form):
submit = SubmitField(label='Really delete my account!')

View File

@ -1,8 +1,10 @@
body { body {
padding-top: 50px; padding-top: 50px;
} }
.starter-template { .starter-template {
padding: 40px 15px; padding-top: 30px;
padding-bottom: 60px;
text-align: center; text-align: center;
} }
@ -22,9 +24,55 @@ td {
color: #a94442; color: #a94442;
} }
.pitstop {
}
.nav-pills > li > a { .nav-pills > li > a {
border: 1px solid; border: 1px solid;
} }
h1 {
margin-top: 0px;
}
h2 {
margin-top: 0px;
}
h3 {
margin-top: 0px;
text-align: center;
}
h3:before{
content:"― ";
}
h3:after{
content:" ―";
}
.tab-content > .active {
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 1px;
padding-top: 15px;
}
.panel-body > p, .panel-body > ul {
text-align: left;
}
// for small devices
@media only screen
and (min-device-width : 320px)
and (max-device-width : 568px) {
h3:before{
content:"";
}
h3:after{
content:"";
}
#charts_tabs {
display:none;
}
#charts_tabs-content {
display:none;
}
}

View File

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

View File

@ -1,12 +1,18 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<h3>Admin</h3> <div class="col-md-2" ></div>
We have {{ data.users|length }} users so far: <div class="col-md-8">
<ul> <div class="panel panel-default">
{% for user in data.users %} <div class="panel-body">
<li>{{user.email}}</li> <h3>Admin</h3>
{% endfor %} We have {{ data.users|length }} users so far:
</ul> <ul>
<a href='{{ url_for('security.login', _external=True) }}'>Login</a> {% for user in data.users %}
<li>{{user.email}}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

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

View File

@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block body %}
<div class="col-md-2" ></div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<h3>Delete account for '{{current_user.email}}'?</h3>
This cannot be undone!
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.submit) }}
</form>
</div>
</div>
</div>
<div class="col-md-2" ></div>
{% endblock %}

View File

@ -1,11 +1,18 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<h3>Delete vehicle '{{vehicle.name}}'</h3> <div class="col-md-2" ></div>
<form class='form-horizontal' method="POST"> <div class="col-md-8">
{{ form.hidden_tag() }} <div class="panel panel-default">
{{ render_field_with_errors(form.submit) }} <div class="panel-body">
</form> <h3>Delete vehicle '{{vehicle.name}}'?</h3>
<form class='form-horizontal' method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.submit) }}
</form>
</div>
</div>
</div>
<div class="col-md-2" ></div>
{% endblock %} {% endblock %}

View File

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

View File

@ -0,0 +1,28 @@
{% extends "layout.html" %}
{% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %}
<div class="row">
<div class="col-md-4">
{{ render_login_form() }}
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body" >
<h1>Join the pitstop community!</h1>
<p>There are already {{ data.users}} members with {{ data.vehicles }} vehicles who have logged {{ data.pitstops }} pitstops fuelling {{ data.litres }}l for {{ data.kilometers }}km.</p>
<p>With pitstop community you can:</p>
<ul>
<li>manage multiple vehicles</li>
<li>track each pitstop</li>
<li>get statistics about the fuel consumption</li>
</ul>
<p><a href='{{ url_for('security.register') }}'>Register your account now</a> or <a href='{{ url_for('security.login') }}'>log into your account</a>.</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -16,14 +16,22 @@
{% macro render_field_with_errors(field) %} {% macro render_field_with_errors(field) %}
<div class="form-group"> <div class="form-group">
{% if field.type == 'SubmitField' %} {% if field.type == 'SubmitField' %}
<div class="col-sm-12" style="align:center"> <div class="col-md-4" ></div>
<div class="col-sm-4" style="align:center">
<input id="{{ field.id }}" name="{{ field.id }}" class="btn btn-default" type="submit" value="{{ field.label.text }}"> <input id="{{ field.id }}" name="{{ field.id }}" class="btn btn-default" type="submit" value="{{ field.label.text }}">
</div> </div>
<!--
<div class="col-sm-3" style="align:center">
<a class="btn btn-default" href="{{ g.data['back'] }}" role="button">Cancel</a>
</div>
-->
<div class="col-md-4" ></div>
{% else %} {% else %}
<label class="col-sm-6 control-label"> <label class="col-sm-6 control-label">
{{ field.label }} {{ field.label }}
</label> </label>
<div class="col-sm-2"> <div class="col-sm-6">
{% if field.type == 'SelectField' %} {% if field.type == 'SelectField' %}
<select id="{{ field.id }}" name="{{ field.id }}" class="form-control"> <select id="{{ field.id }}" name="{{ field.id }}" class="form-control">
{% for choice in field.choices %} {% for choice in field.choices %}
@ -54,7 +62,25 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
</div>
{% endmacro %}
{% macro render_login_form() %}
<div class="panel panel-default">
<div class="panel-body">
<h3>Login</h3>
<form class='form-horizontal' action="{{ url_for_security('login') }}" method="POST" name="login_user_form">
{{ login_user_form.hidden_tag() }}
{{ render_field_with_errors(login_user_form.email) }}
{{ render_field_with_errors(login_user_form.password) }}
{{ render_field_with_errors(login_user_form.remember) }}
{{ render_field(login_user_form.next) }}
{{ render_field_with_errors(login_user_form.submit) }}
{% if security.recoverable %}
<a href="{{ url_for_security('forgot_password') }}">Forgot password</a>
{% endif %}
</form>
</div>
</div> </div>
{% endmacro %} {% endmacro %}
@ -134,7 +160,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="{{ url_for('get_pit_stops') }}">refuel journal</a> <a class="navbar-brand" href="{{ url_for('index') }}">refuel journal</a>
</div> </div>
<div id="navbar" class="collapse navbar-collapse"> <div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
@ -150,6 +176,14 @@
{% endblock %} {% endblock %}
</div> </div>
</div> </div>
{#
<nav class="navbar navbar-inverse navbar-fixed-bottom">
<div class="container">
<div class="navbar-footer">
<a class="navbar-brand" href="">Imprint</a>
</div>
</div>
</nav>
#}
</body> </body>
</html> </html>

View File

@ -1,20 +1,25 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<h3>New Pitstop for '{{ vehicle.name }}'</h3> <div class="col-md-2" ></div>
<form class='form-horizontal' method="POST"> <div class="col-md-8">
{{ form.hidden_tag() }} <div class="panel panel-default">
{{ render_field_with_errors(form.date) }} <div class="panel-body">
<span id="{{form.date.id}}_help" class="help-block"> <h3>New Pitstop for '{{ vehicle.name }}'</h3>
{{messages['date']}} <form class='form-horizontal' method="POST">
</span> {{ form.hidden_tag() }}
{{ render_field_with_errors(form.odometer) }} {{ render_field_with_errors(form.date) }}
<span id="{{form.odometer.id}}_help" class="help-block"> <span id="{{form.date.id}}_help" class="help-block">
{{messages['odometer']}} {{messages['date']}}
</span> </span>
{{ render_field_with_errors(form.litres) }} {{ render_field_with_errors(form.odometer) }}
{{ render_field_with_errors(form.submit) }} <span id="{{form.odometer.id}}_help" class="help-block">
</form> {{messages['odometer']}}
</span>
{{ render_field_with_errors(form.litres) }}
{{ render_field_with_errors(form.submit) }}
</form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<div id="content"> <div id="content">
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs"> <ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
@ -14,60 +14,66 @@
{% for vehicle in current_user.vehicles %} {% for vehicle in current_user.vehicles %}
<div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}"> <div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}">
<h3>{{vehicle.name}}</h3> <h3>{{vehicle.name}}</h3>
<div class="table-responsive"> {% if vehicle.pitstops %}
<table class="table table-striped table-bordered table-condensed"> <div class="table-responsive">
<tr> <table class="table table-striped table-bordered table-condensed">
<th> <tr>
Date<br/> <th>
Days Date<br/>
</th> Days
<th> </th>
Odometer<br/> <th>
Distance Odometer<br/>
</th> Distance
<th> </th>
Litres<br/> <th>
Average Litres<br/>
</th> Average
</tr> </th>
{% for pitstop in vehicle.pitstops|reverse %} </tr>
{% if not loop.last %} {% for pitstop in vehicle.pitstops|reverse %}
{% set days = (pitstop.date - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].date).days %} {% if not loop.last %}
{% set distance = pitstop.odometer - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].odometer %} {% set days = (pitstop.date - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].date).days %}
{% set average = (pitstop.litres / distance) * 100 %} {% set distance = pitstop.odometer - vehicle.pitstops[vehicle.pitstops|length - loop.index - 1].odometer %}
<tr class='pitstop'> {% set average = (pitstop.litres / distance) * 100 %}
<td> <tr class='pitstop'>
{{pitstop.date}}<br/> <td>
{{ days }} days {{pitstop.date}}<br/>
</td> {{ days }} days
<td> </td>
{{pitstop.odometer}} km<br/> <td>
{{distance}} km {{pitstop.odometer}} km<br/>
</td> {{distance}} km
<td> </td>
{{pitstop.litres}} l<br/> <td>
{{average | round(2)}} l/100km {{pitstop.litres}} l<br/>
</td> {{average | round(2)}} l/100km
</tr> </td>
{% else %} </tr>
<tr class='pitstop'> {% else %}
<td> <tr class='pitstop'>
{{pitstop.date}}<br/> <td>
-- days {{pitstop.date}}<br/>
</td> -- days
<td> </td>
{{pitstop.odometer}} km<br/> <td>
-- km {{pitstop.odometer}} km<br/>
</td> -- km
<td> </td>
{{pitstop.litres}} l<br/> <td>
-- l/100km {{pitstop.litres}} l<br/>
</td> -- l/100km
</tr> </td>
{% endif %} </tr>
{% endfor %} {% endif %}
</table> {% endfor %}
</div> </table>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
not enough data: <a href="{{ url_for('create_pit_stop_form', vid=vehicle.id) }}">log a pitstop</a>?
</div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -2,12 +2,19 @@
{% from "security/_macros.html" import render_field_with_errors, render_field %} {% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %} {% block body %}
<h1>Change password</h1> <div class="col-md-2" ></div>
<form class='form-horizontal' action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form"> <div class="col-md-8">
{{ change_password_form.hidden_tag() }} <div class="panel panel-default">
{{ render_field_with_errors(change_password_form.password) }} <div class="panel-body">
{{ render_field_with_errors(change_password_form.new_password) }} <h3>Change password</h3>
{{ render_field_with_errors(change_password_form.new_password_confirm) }} <form class='form-horizontal' action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form">
{{ render_field_with_errors(change_password_form.submit) }} {{ change_password_form.hidden_tag() }}
</form> {{ 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_with_errors(change_password_form.submit) }}
</form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -2,10 +2,18 @@
{% from "security/_macros.html" import render_field_with_errors, render_field %} {% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %} {% block body %}
<h1>Send password reset instructions</h1> <div class="col-md-2" ></div>
<form class='form-horizontal' action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form"> <div class="col-md-8">
{{ forgot_password_form.hidden_tag() }} <div class="panel panel-default">
{{ render_field_with_errors(forgot_password_form.email) }} <div class="panel-body">
{{ render_field_with_errors(forgot_password_form.submit) }} <h3>Reset password</h3>
</form> <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_with_errors(forgot_password_form.submit) }}
</form>
</div>
</div>
<div class="col-md-2" ></div>
</div>
{% endblock %} {% endblock %}

View File

@ -2,16 +2,12 @@
{% from "security/_macros.html" import render_field_with_errors, render_field %} {% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %} {% block body %}
<h1>Login</h1> <div class="row">
<form class='form-horizontal' action="{{ url_for_security('login') }}" method="POST" name="login_user_form"> <div class="col-md-2" ></div>
{{ login_user_form.hidden_tag() }} <div class="col-md-8">
{{ render_field_with_errors(login_user_form.email) }} {{ render_login_form() }}
{{ render_field_with_errors(login_user_form.password) }} </div>
{{ render_field_with_errors(login_user_form.remember) }} <div class="col-md-2" ></div>
{{ render_field(login_user_form.next) }} </div>
{{ render_field_with_errors(login_user_form.submit) }}
{% if security.recoverable %}
<a href="{{ url_for_security('forgot_password') }}">Forgot password</a>
{% endif %}
</form>
{% endblock %} {% endblock %}

View File

@ -2,14 +2,22 @@
{% from "security/_macros.html" import render_field_with_errors, render_field %} {% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %} {% block body %}
<h1>Register User</h1> <div class="col-md-2" ></div>
<form class='form-horizontal' action="{{ url_for_security('register') }}" method="POST" name="register_user_form"> <div class="col-md-8">
{{ register_user_form.hidden_tag() }} <div class="panel panel-default">
{{ render_field_with_errors(register_user_form.email) }} <div class="panel-body">
{{ render_field_with_errors(register_user_form.password) }} <h3>Register</h3>
{% if register_user_form.password_confirm %} <form class='form-horizontal' action="{{ url_for_security('register') }}" method="POST" name="register_user_form">
{{ render_field_with_errors(register_user_form.password_confirm) }} {{ register_user_form.hidden_tag() }}
{% endif %} {{ render_field_with_errors(register_user_form.email) }}
{{ render_field_with_errors(register_user_form.submit) }} {{ render_field_with_errors(register_user_form.password) }}
</form> {% if register_user_form.password_confirm %}
{{ render_field_with_errors(register_user_form.password_confirm) }}
{% endif %}
{{ render_field_with_errors(register_user_form.submit) }}
</form>
</div>
</div>
</div>
<div class="col-md-2" ></div>
{% endblock %} {% endblock %}

View File

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

View File

@ -11,7 +11,7 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div id="vehicle_contetn" class="tab-content"> <div id="vehicle_content" class="tab-content ">
{% for vehicle in data %} {% for vehicle in data %}
<div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}"> <div class="tab-pane {% if loop.first %}active{% endif %}" id="v{{vehicle.id}}">
<h3>{{vehicle.name}}</h3> <h3>{{vehicle.name}}</h3>
@ -45,6 +45,11 @@
</div> </div>
<ul id="charts_tabs" class="nav nav-tabs" data-tabs="tabs"> <ul id="charts_tabs" class="nav nav-tabs" data-tabs="tabs">
<li class="active"> <li class="active">
<a href="#v{{vehicle.id}}_c3" id="i{{vehicle.id}}_c3" data-toggle="tab">
Consumption
</a>
</li>
<li>
<a href="#v{{vehicle.id}}_c1" id="i{{vehicle.id}}_c1" data-toggle="tab"> <a href="#v{{vehicle.id}}_c1" id="i{{vehicle.id}}_c1" data-toggle="tab">
Fuelled litres Fuelled litres
</a> </a>
@ -54,21 +59,30 @@
Odometer Odometer
</a> </a>
</li> </li>
<li>
<a href="#v{{vehicle.id}}_c3" id="i{{vehicle.id}}_c3" data-toggle="tab">
Consumption
</a>
</li>
</ul> </ul>
<div id="my-tab-content" class="tab-content"> <div id="charts_tabs-content" class="tab-content">
<div class="tab-pane active" id="v{{vehicle.id}}_c1"> <div class="tab-pane active" id="v{{vehicle.id}}_c3">
{% if vehicle.pitstop_count > 1 %}
<div id="averageUsageDiv{{vehicle.id}}" style="width:100%; height:500px;"></div>
<script type="text/javascript">
{{ chartScript('averageUsageDiv'+vehicle.id|str, vehicle.average_litres, 'l/100 km') }}
</script>
{% else %}
<div class="alert alert-warning" role="alert">
not enough data: <a href="{{ url_for('create_pit_stop_form', vid=vehicle.id) }}">log a pitstop</a>?
</div>
{% endif %}
</div>
<div class="tab-pane " id="v{{vehicle.id}}_c1">
{% if vehicle.pitstop_count > 0 %} {% if vehicle.pitstop_count > 0 %}
<div id="fuelledChartDiv{{vehicle.id}}" style="width:100%; height:500px;"></div> <div id="fuelledChartDiv{{vehicle.id}}" style="width:100%; height:500px;"></div>
<script type="text/javascript"> <script type="text/javascript">
{{ chartScript('fuelledChartDiv'+vehicle.id|str, vehicle.litres, 'l') }} {{ chartScript('fuelledChartDiv'+vehicle.id|str, vehicle.litres, 'l') }}
</script> </script>
{% else %} {% else %}
not enough data. <div class="alert alert-warning" role="alert">
not enough data: <a href="{{ url_for('create_pit_stop_form', vid=vehicle.id) }}">log a pitstop</a>?
</div>
{% endif %} {% endif %}
</div> </div>
<div class="tab-pane " id="v{{vehicle.id}}_c2"> <div class="tab-pane " id="v{{vehicle.id}}_c2">
@ -78,17 +92,9 @@
{{ chartScript('odometerChartDiv'+vehicle.id|str, vehicle.odometers, 'km') }} {{ chartScript('odometerChartDiv'+vehicle.id|str, vehicle.odometers, 'km') }}
</script> </script>
{% else %} {% else %}
not enough data. <div class="alert alert-warning" role="alert">
{% endif %} not enough data: <a href="{{ url_for('create_pit_stop_form', vid=vehicle.id) }}">log a pitstop</a>?
</div> </div>
<div class="tab-pane " id="v{{vehicle.id}}_c3">
{% if vehicle.pitstop_count > 1 %}
<div id="averageUsageDiv{{vehicle.id}}" style="width:100%; height:500px;"></div>
<script type="text/javascript">
{{ chartScript('averageUsageDiv'+vehicle.id|str, vehicle.average_litres, 'l/100 km') }}
</script>
{% else %}
not enough data.
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -47,3 +47,21 @@ def db_log_delete(entity):
def db_log_update(entity): def db_log_update(entity):
logging.info('db_update: %s' % str(entity)) logging.info('db_update: %s' % str(entity))
def check_vehicle_name_is_unique(current_user, name_field):
"""
Checks if the vehicle name given in the name_field is unique for the vehicles of the current user. An error is added
to the field it the name is not unique.
:param current_user: the user currently logged in
:param name_field: the form field to enter the name to
:return: True if the name is unique, False otherwise.
"""
vehicle_name = name_field.data
for vehicle in current_user.vehicles:
if vehicle.name == vehicle_name:
name_field.default = vehicle_name
name_field.errors.append('Vehicle "%s" already exists.' % vehicle_name)
return False
return True

16
compose_config/config.py Normal file
View File

@ -0,0 +1,16 @@
import os
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:%s@database/pitstops' % (os.environ['DATABASE_ENV_MYSQL_ROOT_PASSWORD'])
#SQLALCHEMY_DATABASE_URI = 'sqlite:////data/rollerverbrauch.db'
MAIL_SERVER = ''
MAIL_PORT= 25
MAIL_USE_TLS = True
MAIL_USE_SSL = False
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
SECURITY_EMAIL_SENDER = ''
SECURITY_PASSWORD_SALT = 'SecretSalt'
SECRET_KEY = 'SecretKey'
SECURITY_SEND_REGISTER_EMAIL = False

20
docker-compose.yml Normal file
View File

@ -0,0 +1,20 @@
version: '2'
services:
rollerverbrauch:
build: .
depends_on:
- database
volumes:
- ./compose_config/:/config
environment:
- config=/config/config.py
- DATABASE_ENV_MYSQL_ROOT_PASSWORD=foobar123
ports:
- 5000
database:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=foobar123
- MYSQL_DATABASE=pitstops
ports:
- 3306