Merge branch 'master' of github.com:ZeusWPI/Foodbot into feat/38/post_to_slack

* 'master' of github.com:ZeusWPI/Foodbot:
  Fixed bug for volunteering closes #44
  Check if user is allowed to edit
  Add paid indicator, fixes #19
  spacing
  Edit orders
  Added phone number
  Added alembic and flask-migrate to haldis
This commit is contained in:
Feliciaan De Palmenaer 2015-06-04 22:57:42 +02:00
commit 46bf2a0cbb
18 changed files with 596 additions and 47 deletions

View file

@ -24,7 +24,7 @@ class UserAdminModel(ModelBaseView):
class LocationAdminModel(ModelBaseView): class LocationAdminModel(ModelBaseView):
column_searchable_list = ('name', 'address', 'website') column_searchable_list = ('name', 'address', 'website')
inline_models = None inline_models = None
form_columns = ('name', 'address', 'website') form_columns = ('name', 'address', 'website', 'telephone')
admin = Admin(app, name='Haldis', url='/admin', template_mode='bootstrap3') admin = Admin(app, name='Haldis', url='/admin', template_mode='bootstrap3')

View file

@ -8,6 +8,12 @@ from models import *
from forms import * from forms import *
from utils import * from utils import *
from views import * from views import *
from flask.ext.migrate import Migrate, MigrateCommand
from flask.ext.script import Manager
if __name__ == '__main__': if __name__ == '__main__':
app.run() # do it here, because make accessing db changes only possible when executing the program directly
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
manager.run()

6
app/migrations/README Normal file
View file

@ -0,0 +1,6 @@
Generic single-database configuration.
+On the existing databases, initialize flask-migrate and alembic with:
+```
+python haldis.py db stamp "354676f60be"
+```

View file

@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

73
app/migrations/env.py Normal file
View file

@ -0,0 +1,73 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = engine_from_config(config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(connection=connection,
target_metadata=target_metadata,
**current_app.extensions['migrate'].configure_args)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View file

@ -0,0 +1,22 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,54 @@
"""Make it possible for some columns to be null
Revision ID: 2d696203e56
Revises: 354676f60be
Create Date: 2015-06-04 17:25:39.119678
"""
# revision identifiers, used by Alembic.
revision = '2d696203e56'
down_revision = '354676f60be'
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.alter_column('location', 'name',
existing_type=sa.VARCHAR(length=120),
nullable=False)
op.alter_column('order_item', 'order_id',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('product', 'name',
existing_type=sa.VARCHAR(length=120),
nullable=False)
op.alter_column('product', 'price',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('user', 'username',
existing_type=sa.VARCHAR(length=80),
nullable=False)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.alter_column('user', 'username',
existing_type=sa.VARCHAR(length=80),
nullable=True)
op.alter_column('product', 'price',
existing_type=sa.INTEGER(),
nullable=True)
op.alter_column('product', 'name',
existing_type=sa.VARCHAR(length=120),
nullable=True)
op.alter_column('order_item', 'order_id',
existing_type=sa.INTEGER(),
nullable=True)
op.alter_column('location', 'name',
existing_type=sa.VARCHAR(length=120),
nullable=True)
### end Alembic commands ###

View file

@ -0,0 +1,26 @@
"""Add telephone number
Revision ID: 3243c3538fc
Revises: 2d696203e56
Create Date: 2015-06-04 18:39:54.895177
"""
# revision identifiers, used by Alembic.
revision = '3243c3538fc'
down_revision = '2d696203e56'
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('location', sa.Column('telephone', sa.String(length=20), nullable=True))
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('location', 'telephone')
### end Alembic commands ###

View file

@ -0,0 +1,74 @@
"""Initial configuration
Revision ID: 354676f60be
Revises: None
Create Date: 2015-06-04 17:17:28.933840
"""
# revision identifiers, used by Alembic.
revision = '354676f60be'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('location',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=120), nullable=True),
sa.Column('address', sa.String(length=254), nullable=True),
sa.Column('website', sa.String(length=120), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=80), nullable=True),
sa.Column('admin', sa.Boolean(), nullable=True),
sa.Column('bias', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('username')
)
op.create_table('product',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('location_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(length=120), nullable=True),
sa.Column('price', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['location_id'], ['location.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('order',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('courrier_id', sa.Integer(), nullable=True),
sa.Column('location_id', sa.Integer(), nullable=True),
sa.Column('starttime', sa.DateTime(), nullable=True),
sa.Column('stoptime', sa.DateTime(), nullable=True),
sa.Column('public', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['courrier_id'], ['user.id'], ),
sa.ForeignKeyConstraint(['location_id'], ['location.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('order_item',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('order_id', sa.Integer(), nullable=True),
sa.Column('product_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(length=120), nullable=True),
sa.ForeignKeyConstraint(['order_id'], ['order.id'], ),
sa.ForeignKeyConstraint(['product_id'], ['product.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('order_item')
op.drop_table('order')
op.drop_table('product')
op.drop_table('user')
op.drop_table('location')
### end Alembic commands ###

View file

@ -0,0 +1,26 @@
"""Removed foreign key constraint
Revision ID: 42709384216
Revises: 57a00d0b7bc
Create Date: 2015-06-04 22:34:05.237943
"""
# revision identifiers, used by Alembic.
revision = '42709384216'
down_revision = '57a00d0b7bc'
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('order_ibfk_1', 'order', type_='foreignkey')
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_foreign_key('order_ibfk_1', 'order', 'user', ['courrier_id'], ['id'])
### end Alembic commands ###

View file

@ -0,0 +1,26 @@
"""Added order paid column
Revision ID: 57a00d0b7bc
Revises: 3243c3538fc
Create Date: 2015-06-04 19:16:47.888372
"""
# revision identifiers, used by Alembic.
revision = '57a00d0b7bc'
down_revision = '3243c3538fc'
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('order_item', sa.Column('paid', sa.Boolean(), nullable=True))
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('order_item', 'paid')
### end Alembic commands ###

View file

@ -10,7 +10,8 @@ class User(db.Model):
username = db.Column(db.String(80), unique=True, nullable=False) username = db.Column(db.String(80), unique=True, nullable=False)
admin = db.Column(db.Boolean) admin = db.Column(db.Boolean)
bias = db.Column(db.Integer) bias = db.Column(db.Integer)
runs = db.relationship('Order', backref='courrier', lazy='dynamic') runs = db.relation('Order', backref='courrier', primaryjoin='Order.courrier_id==User.id',
foreign_keys='Order.courrier_id')
orderItems = db.relationship('OrderItem', backref='user', lazy='dynamic') orderItems = db.relationship('OrderItem', backref='user', lazy='dynamic')
def configure(self, username, admin, bias): def configure(self, username, admin, bias):
@ -45,6 +46,7 @@ class Location(db.Model):
name = db.Column(db.String(120), nullable=False) name = db.Column(db.String(120), nullable=False)
address = db.Column(db.String(254)) address = db.Column(db.String(254))
website = db.Column(db.String(120)) website = db.Column(db.String(120))
telephone = db.Column(db.String(20), nullable=True)
products = db.relationship('Product', backref='location', lazy='dynamic') products = db.relationship('Product', backref='location', lazy='dynamic')
orders = db.relationship('Order', backref='location', lazy='dynamic') orders = db.relationship('Order', backref='location', lazy='dynamic')
@ -64,7 +66,6 @@ class Product(db.Model):
price = db.Column(db.Integer, nullable=False) price = db.Column(db.Integer, nullable=False)
orderItems = db.relationship('OrderItem', backref='product', lazy='dynamic') orderItems = db.relationship('OrderItem', backref='product', lazy='dynamic')
def configure(self, location, name, price): def configure(self, location, name, price):
self.location = location self.location = location
self.name = name self.name = name
@ -76,7 +77,7 @@ class Product(db.Model):
class Order(db.Model): class Order(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
courrier_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) courrier_id = db.Column(db.Integer, nullable=True)
location_id = db.Column(db.Integer, db.ForeignKey('location.id')) location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
starttime = db.Column(db.DateTime) starttime = db.Column(db.DateTime)
stoptime = db.Column(db.DateTime) stoptime = db.Column(db.DateTime)
@ -93,15 +94,14 @@ class Order(db.Model):
return 'Order %d @ %s' % (self.id, self.location.name or 'None') return 'Order %d @ %s' % (self.id, self.location.name or 'None')
def group_by_user(self): def group_by_user(self):
group = defaultdict(list) group = dict()
for item in self.items: for item in self.items:
group[item.get_name()] += [item.product] user = group.get(item.get_name(), dict())
return group user["total"] = user.get("total", 0) + item.product.price
user["to_pay"] = user.get("to_pay", 0) + item.product.price if not item.paid else 0
def group_by_user_pay(self): user["paid"] = user.get("paid", True) and item.paid
group = defaultdict(int) user["products"] = user.get("products", []) + [item.product]
for item in self.items: group[item.get_name()] = user
group[item.get_name()] += item.product.price
return group return group
def group_by_product(self): def group_by_product(self):
@ -121,11 +121,13 @@ class Order(db.Model):
return True return True
return False return False
class OrderItem(db.Model): class OrderItem(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False) order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('product.id')) product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
paid = db.Column(db.Boolean, default=False)
name = db.Column(db.String(120)) name = db.Column(db.String(120))
def configure(self, user, order, product): def configure(self, user, order, product):

View file

@ -12,17 +12,27 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-5"> <div class="col-sm-5">
<h3>Open orders</h3> {% if orders|count > 0 -%}
{% for order in orders %} <h3>Open orders:</h3>
{{ util.render_order(order) }} {% for order in orders %}
{% endfor %} {{ util.render_order(order) }}
{% endfor %}
{% else %}
<h4>No orders available.</h4>
{% if not current_user.is_anonymous() %}
To create an order, fill in the form on the right.
{% else %}
Login to create an order, or ask someone else.
{% endif %}
{%- endif %}
</div> </div>
<div class="col-sm-5 col-sm-push-2"> <div class="col-sm-5 col-sm-push-2">
<h3>Recently closed orders</h3> {% if orders|count > 0 -%}
{% for order in recently_closed %} <h3>Recently closed orders:</h3>
{{ util.render_order(order) }} {% for order in recently_closed %}
{% endfor %} {{ util.render_order(order) }}
</ul> {% endfor %}
{%- endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -1,5 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% set active_page = "orders" -%} {% set active_page = "orders" -%}
{% set order_items = order.group_by_user() -%}
{% set courier_or_admin = order.can_close(current_user.id) -%}
{% import "utils.html" as util %} {% import "utils.html" as util %}
@ -7,8 +9,9 @@
<div class="row"> <div class="row">
<div class="col-md-push-1 col-md-10 darker"><!-- Shitty html--> <div class="col-md-push-1 col-md-10 darker"><!-- Shitty html-->
<h3>Order {{ order.id }} <h3>Order {{ order.id }}
{% if order.can_close(current_user.id) -%} {% if courier_or_admin -%}
<a class="btn btn-danger pull-right" href="{{ url_for('.close_order', id=order.id) }}">Close</a><br/> <a class="btn btn-danger pull-right" href="{{ url_for('.close_order', id=order.id) }}">Close</a><br/>
<a class="btn btn-warning pull-right" href="{{ url_for('.order_edit', id=order.id) }}">Edit</a>
{%- endif %}</h3> {%- endif %}</h3>
courier: {{ order.courrier.username }} courier: {{ order.courrier.username }}
{% if order.courrier == None and not current_user.is_anonymous() %} {% if order.courrier == None and not current_user.is_anonymous() %}
@ -16,8 +19,11 @@
{% endif %} {% endif %}
<br/> <br/>
location: <a href="{{ order.location.website }}">{{ order.location.name }}</a><br/> location: <a href="{{ order.location.website }}">{{ order.location.name }}</a><br/>
{% if order.location.telephone != None %}
telephone: <a href="tel://{{ order.location.telephone }}">{{ order.location.telephone }}</a><br/>
{% endif %}
<b>status:</b> {% if order.stoptime %}<span class="time">{{ order.stoptime|countdown }}</span>{% else %}open{% endif %}<br/> <b>status:</b> {% if order.stoptime %}<span class="time">{{ order.stoptime|countdown }}</span>{% else %}open{% endif %}<br/>
total price: {{ total_price|euro }} total price: {{ total_price|euro }} {% if courier_or_admin %}- remaining debts: {{ debts|euro }}{% endif %}
</div> </div>
{% if form -%} {% if form -%}
<div class="col-md-push-1 col-md-10 darker"> <div class="col-md-push-1 col-md-10 darker">
@ -44,26 +50,50 @@
{%- endif %} {%- endif %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-push-1 col-md-4 darker"> <div class="col-md-push-1 col-md-5 darker">
<h3>Items</h3> <h3>Items</h3>
{% for item in order.items %} <table class="table table-hover table-condensed">
{{ item.get_name() }} - {{ item.product.name }} - {{ item.product.price|euro }} <thead>
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}<a href="{{ url_for('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/> <tr><th>Name</th><th>Item</th><th>Price</th>{% if courier_or_admin %}<th>Paid?</th>{% endif %}<th>Delete</th></tr>
{% endfor %} </thead>
<tbody>
{% for item in order.items -%}
<tr>
<td>{{ item.get_name() }}</td>
<td>{{ item.product.name }}</td>
<td>{{ item.product.price|euro }}</td>
{% if courier_or_admin %}<td>{% if not item.paid %} <a class="btn btn-xs btn-primary" href="{{ url_for('.item_paid', order_id=order.id, item_id=item.id) }}">Pay</a> {% else %} <span class="glyphicon glyphicon-chevron-down"></span> {% endif %}</td>{% endif %}
<td>{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) or courier_or_admin -%}<a href="{{ url_for('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/></td>
</tr>
{%- endfor %}
</tbody>
</table>
</div> </div>
<div class="col-md-push-3 col-md-4 darker"> <div class="col-md-push-2 col-md-4 darker">
<h3>Ordered products:</h3> <h3>Ordered products:</h3>
{% for key, value in order.group_by_product().items() %} {% for key, value in order.group_by_product().items() -%}
{{ key }} - {{ value }}<br/> {{ key }} - {{ value }}<br/>
{% endfor %} {%- endfor %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-push-1 col-md-4 darker"> <div class="col-md-push-1 col-md-5 darker">
<h3>Debts</h3> <h3>Debts</h3>
{% for key, value in order.group_by_user_pay().items() %} <table class="table table-hover table-condensed">
{{ key }} - {{ value|euro }}<br/> <thead>
{% endfor %} <tr><th>Name</th><th>Total</th><th>To pay</th>{% if courier_or_admin %}<th>Paid?</th>{% endif %}</tr>
</thead>
<tbody>
{% for key, value in order_items.items() -%}
<tr>
<td>{{ key }}</td>
<td>{{ value["total"]|euro }}</td>
<td>{{ value["to_pay"]|euro }}</td>
{% if courier_or_admin %}<td>{% if not value["to_pay"] == 0 %} <a class="btn btn-xs btn-primary" href="{{ url_for('.items_user_paid', order_id=order.id, user_name=key) }}">Pay</a> {% else %} <span class="glyphicon glyphicon-chevron-down"></span> {% endif %}</td>{% endif %}
</tr>
{%- endfor %}
</tbody>
</table>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,79 @@
{% extends 'layout.html' %}
{% set active_page = "orders" -%}
{% import "bootstrap/wtf.html" as wtf %}
{% import "utils.html" as util -%}
{% block container %}
<div class="row">
{% if not current_user.is_anonymous() %}
<div class="col-md-push-4 col-md-6">
<h3>Create new order:</h3>
<div class="row darker">
<div class="col-sm-12">
<form method="post" action="{{ url_for('.order_edit', id=order_id) }}">
{{ form.csrf_token }}
<div class="form-group select2 {{ 'has-errors' if form.courrier_id.errors else ''}}">
{{ form.courrier_id.label(class='control-label') }}<br>
{{ form.courrier_id(class='form-control select') }}
{{ util.render_form_field_errors(form.courrier_id) }}
</div>
<div class="form-group select2 {{ 'has-errors' if form.location_id.errors else ''}}{{ ' required' if form.location_id.flags.required }}">
{{ form.location_id.label(class='control-label') }}
{{ form.location_id(class='form-control select') }}
{{ util.render_form_field_errors(form.location_id) }}
</div>
{% if current_user.is_admin() %}
<div class="form-group{{ ' has-error' if form.starttime.errors }}{{ ' required' if form.starttime.flags.required }}{{ ' hidden' if not current_user.is_admin() }}">
{{ form.starttime.label(class='control-label') }}
<div class='input-group date' class='datetimepicker'>
{{ form.starttime(class='form-control datetimepicker') }}
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
{{ util.render_form_field_errors(form.starttime) }}
</div>
{% endif %}
<div class="form-group{{ ' has-error' if form.stoptime.errors }}{{ ' required' if form.stoptime.flags.required }}{{ ' hidden' if not current_user.is_admin() }}">
{{ form.stoptime.label(class='control-label') }}
<div class='input-group date' class='datetimepicker'>
{{ form.stoptime(class='form-control datetimepicker') }}
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
{{ util.render_form_field_errors(form.stoptime) }}
</div>
<div class="form-group">
{{ form.submit_button(class='btn btn-primary') }}
</div>
</form>
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block styles -%}
{{ super() }}
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0-rc.2/css/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-datetimepicker.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2-bootstrap.min.css') }}">
{%- endblock %}
{% block scripts -%}
{{ super() }}
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0-rc.2/js/select2.min.js"></script>
<script src="{{ url_for('static', filename='js/moment.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bootstrap-datetimepicker.min.js') }}"></script>
<script type="text/javascript">
$.ready(function(){
$('.select').select2();
$('.datetimepicker').datetimepicker({
format: 'DD-MM-YYYY HH:mm'
});
}());
</script>
{%- endblock %}

View file

@ -7,10 +7,19 @@
{% block container %} {% block container %}
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
{% if orders|count > 0 -%}
<h3>Open orders:</h3> <h3>Open orders:</h3>
{% for order in orders %} {% for order in orders %}
{{ util.render_order(order) }} {{ util.render_order(order) }}
{% endfor %} {% endfor %}
{% else %}
<h4>No orders available.</h4>
{% if not current_user.is_anonymous() %}
To create an order, fill in the form on the right.
{% else %}
Login to create an order, or ask someone else.
{% endif %}
{%- endif %}
</div> </div>
{% if not current_user.is_anonymous() %} {% if not current_user.is_anonymous() %}
<div class="col-md-push-1 col-md-6"> <div class="col-md-push-1 col-md-6">

View file

@ -1,15 +1,14 @@
__author__ = 'feliciaan'
import json import json
from threading import Thread from threading import Thread
import requests import requests
from flask import url_for, render_template, abort, redirect, Blueprint, flash, session, request
__author__ = 'feliciaan'
from flask import url_for, render_template, abort, redirect, Blueprint, flash, session
from flask.ext.login import current_user, login_required from flask.ext.login import current_user, login_required
import random import random
from datetime import datetime from datetime import datetime
from app import app, db from app import app, db
from models import Order, OrderItem from models import Order, OrderItem, User
from forms import OrderItemForm, OrderForm, AnonOrderItemForm from forms import OrderItemForm, OrderForm, AnonOrderItemForm
order_bp = Blueprint('order_bp', 'order') order_bp = Blueprint('order_bp', 'order')
@ -51,7 +50,25 @@ def order(id, form=None):
if order.stoptime and order.stoptime < datetime.now(): if order.stoptime and order.stoptime < datetime.now():
form = None form = None
total_price = sum([o.product.price for o in order.items]) total_price = sum([o.product.price for o in order.items])
return render_template('order.html', order=order, form=form, total_price=total_price) debts = sum([o.product.price for o in order.items if not o.paid])
return render_template('order.html', order=order, form=form, total_price=total_price, debts=debts)
@order_bp.route('/<id>/edit', methods=['GET', 'POST'])
@login_required
def order_edit(id):
order = Order.query.filter(Order.id == id).first()
if current_user.id is not order.courrier_id and not current_user.is_admin():
abort(401)
if order is None:
abort(404)
orderForm = OrderForm(obj=order)
orderForm.populate()
if orderForm.validate_on_submit():
orderForm.populate_obj(order)
db.session.commit()
return redirect(url_for('.order', id=order.id))
return render_template('order_edit.html', form=orderForm, order_id=id)
@order_bp.route('/<id>/create', methods=['POST']) @order_bp.route('/<id>/create', methods=['POST'])
@ -80,6 +97,41 @@ def order_item_create(id):
return redirect(url_for('.order', id=id)) return redirect(url_for('.order', id=id))
return order(id, form=form) return order(id, form=form)
@order_bp.route('/<order_id>/<item_id>/paid')
@login_required
def item_paid(order_id, item_id):
item = OrderItem.query.filter(OrderItem.id == item_id).first()
id = current_user.id
if item.order.courrier_id == id or current_user.admin:
item.paid = True
db.session.commit()
flash('Paid %s by %s' % (item.product.name, item.user.username), 'success')
return redirect(url_for('.order', id=order_id))
abort(404)
@order_bp.route('/<order_id>/<user_name>/user_paid')
@login_required
def items_user_paid(order_id, user_name):
user = User.query.filter(User.username == user_name).first()
items = []
if user:
items = OrderItem.query.filter((OrderItem.user_id == user.id) & (OrderItem.order_id == order_id))
else:
items = OrderItem.query.filter((OrderItem.name == user_name) & (OrderItem.order_id == order_id))
current_order = Order.query.filter(Order.id == order_id).first()
for item in items:
print(item)
if current_order.courrier_id == current_user.id or current_user.admin:
for item in items:
item.paid = True
db.session.commit()
flash('Paid %d items for %s' % (items.count(), item.get_name()), 'success')
return redirect(url_for('.order', id=order_id))
abort(404)
@order_bp.route('/<order_id>/<item_id>/delete') @order_bp.route('/<order_id>/<item_id>/delete')
def delete_item(order_id, item_id): def delete_item(order_id, item_id):
item = OrderItem.query.filter(OrderItem.id == item_id).first() item = OrderItem.query.filter(OrderItem.id == item_id).first()
@ -90,7 +142,7 @@ def delete_item(order_id, item_id):
product_name = item.product.name product_name = item.product.name
db.session.delete(item) db.session.delete(item)
db.session.commit() db.session.commit()
flash('Deleted %s' % product_name, 'info') flash('Deleted %s' % product_name, 'success')
return redirect(url_for('.order', id=order_id)) return redirect(url_for('.order', id=order_id))
abort(404) abort(404)
@ -160,10 +212,18 @@ def get_orders(expression=None):
def post_order_to_webhook(order_item): def post_order_to_webhook(order_item):
message = '<{}|Open here>New order for {}. Deadline in {} minutes.'.format( message = ''
url_for('.order', id=order_item.id, _external=True), if order_item.courrier is not None:
order_item.location.name, message = '{3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!'.format(
remaining_minutes(order_item.stoptime)) url_for('.order', id=order_item.id, _external=True),
order_item.location.name,
remaining_minutes(order_item.stoptime),
order_item.courrier.username.title())
else:
message = '<{}|Open here.> New order for {}. Deadline in {} minutes.'.format(
url_for('.order', id=order_item.id, _external=True),
order_item.location.name,
remaining_minutes(order_item.stoptime))
webhookthread = WebhookSenderThread(message) webhookthread = WebhookSenderThread(message)
webhookthread.start() webhookthread.start()

View file

@ -6,6 +6,7 @@ Flask-Login==0.2.11
Flask-OAuthlib==0.8.0 Flask-OAuthlib==0.8.0
Flask-SQLAlchemy==2.0 Flask-SQLAlchemy==2.0
Flask-WTF==0.10.3 Flask-WTF==0.10.3
Flask-Migrate==1.4.0
itsdangerous==0.24 itsdangerous==0.24
Jinja2==2.7.2 Jinja2==2.7.2
MarkupSafe==0.23 MarkupSafe==0.23