From 2d6640b8b9ee22e0b4def14e723cfb34d9d207bf Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 17:29:37 +0200 Subject: [PATCH 1/7] Added alembic and flask-migrate to haldis --- app/haldis.py | 8 ++- app/migrations/README | 6 ++ app/migrations/alembic.ini | 45 +++++++++++++++ app/migrations/env.py | 73 ++++++++++++++++++++++++ app/migrations/script.py.mako | 22 ++++++++ app/migrations/versions/2d696203e56_.py | 54 ++++++++++++++++++ app/migrations/versions/354676f60be_.py | 74 +++++++++++++++++++++++++ requirements.txt | 1 + 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 app/migrations/README create mode 100644 app/migrations/alembic.ini create mode 100644 app/migrations/env.py create mode 100644 app/migrations/script.py.mako create mode 100644 app/migrations/versions/2d696203e56_.py create mode 100644 app/migrations/versions/354676f60be_.py diff --git a/app/haldis.py b/app/haldis.py index f8002bc..fd0f6cc 100644 --- a/app/haldis.py +++ b/app/haldis.py @@ -8,6 +8,12 @@ from models import * from forms import * from utils import * from views import * +from flask.ext.migrate import Migrate, MigrateCommand +from flask.ext.script import Manager 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() diff --git a/app/migrations/README b/app/migrations/README new file mode 100644 index 0000000..ac616e5 --- /dev/null +++ b/app/migrations/README @@ -0,0 +1,6 @@ +Generic single-database configuration. + ++On the existing databases, initialize flask-migrate and alembic with: ++``` ++python haldis.py db stamp "354676f60be" ++``` \ No newline at end of file diff --git a/app/migrations/alembic.ini b/app/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/app/migrations/alembic.ini @@ -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 diff --git a/app/migrations/env.py b/app/migrations/env.py new file mode 100644 index 0000000..0a038e6 --- /dev/null +++ b/app/migrations/env.py @@ -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() + diff --git a/app/migrations/script.py.mako b/app/migrations/script.py.mako new file mode 100644 index 0000000..9570201 --- /dev/null +++ b/app/migrations/script.py.mako @@ -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"} diff --git a/app/migrations/versions/2d696203e56_.py b/app/migrations/versions/2d696203e56_.py new file mode 100644 index 0000000..2067c81 --- /dev/null +++ b/app/migrations/versions/2d696203e56_.py @@ -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 ### diff --git a/app/migrations/versions/354676f60be_.py b/app/migrations/versions/354676f60be_.py new file mode 100644 index 0000000..34d8e13 --- /dev/null +++ b/app/migrations/versions/354676f60be_.py @@ -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 ### diff --git a/requirements.txt b/requirements.txt index 3ac6713..060e0c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ Flask-Login==0.2.11 Flask-OAuthlib==0.8.0 Flask-SQLAlchemy==2.0 Flask-WTF==0.10.3 +Flask-Migrate==1.4.0 itsdangerous==0.24 Jinja2==2.7.2 MarkupSafe==0.23 From 24d1b7c094af9e173493363b30edf55130ba9f28 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 18:45:59 +0200 Subject: [PATCH 2/7] Added phone number --- app/admin.py | 2 +- app/migrations/versions/3243c3538fc_.py | 26 +++++++++++++++++++++++++ app/models.py | 1 + app/templates/order.html | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 app/migrations/versions/3243c3538fc_.py diff --git a/app/admin.py b/app/admin.py index 9dfb62a..685934b 100644 --- a/app/admin.py +++ b/app/admin.py @@ -24,7 +24,7 @@ class UserAdminModel(ModelBaseView): class LocationAdminModel(ModelBaseView): column_searchable_list = ('name', 'address', 'website') inline_models = None - form_columns = ('name', 'address', 'website') + form_columns = ('name', 'address', 'website', 'telephone') admin = Admin(app, name='Haldis', url='/admin', template_mode='bootstrap3') diff --git a/app/migrations/versions/3243c3538fc_.py b/app/migrations/versions/3243c3538fc_.py new file mode 100644 index 0000000..3d8f9c2 --- /dev/null +++ b/app/migrations/versions/3243c3538fc_.py @@ -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 ### diff --git a/app/models.py b/app/models.py index 7ae015b..bc42d26 100644 --- a/app/models.py +++ b/app/models.py @@ -45,6 +45,7 @@ class Location(db.Model): name = db.Column(db.String(120), nullable=False) address = db.Column(db.String(254)) website = db.Column(db.String(120)) + telephone = db.Column(db.String(20), nullable=True) products = db.relationship('Product', backref='location', lazy='dynamic') orders = db.relationship('Order', backref='location', lazy='dynamic') diff --git a/app/templates/order.html b/app/templates/order.html index c918635..52dbbf5 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -16,6 +16,9 @@ {% endif %}
location: {{ order.location.name }}
+ {% if order.location.telephone != None %} + telephone: {{ order.location.telephone }}
+ {% endif %} status: {% if order.stoptime %}{{ order.stoptime|countdown }}{% else %}open{% endif %}
total price: {{ total_price|euro }} From 50a8714c5177608169b2d2ff8b94f27ef67cc2c3 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 19:11:08 +0200 Subject: [PATCH 3/7] Edit orders --- app/templates/order.html | 1 + app/templates/order_edit.html | 79 +++++++++++++++++++++++++++++++++++ app/views/order.py | 18 +++++++- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 app/templates/order_edit.html diff --git a/app/templates/order.html b/app/templates/order.html index c918635..7e7cbbf 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -9,6 +9,7 @@

Order {{ order.id }} {% if order.can_close(current_user.id) -%} Close
+ Edit {%- endif %}

courier: {{ order.courrier.username }} {% if order.courrier == None and not current_user.is_anonymous() %} diff --git a/app/templates/order_edit.html b/app/templates/order_edit.html new file mode 100644 index 0000000..84a68d6 --- /dev/null +++ b/app/templates/order_edit.html @@ -0,0 +1,79 @@ +{% extends 'layout.html' %} +{% set active_page = "orders" -%} + +{% import "bootstrap/wtf.html" as wtf %} +{% import "utils.html" as util -%} + +{% block container %} +
+ {% if not current_user.is_anonymous() %} +
+

Create new order:

+
+
+
+ {{ form.csrf_token }} +
+ {{ form.courrier_id.label(class='control-label') }}
+ {{ form.courrier_id(class='form-control select') }} + {{ util.render_form_field_errors(form.courrier_id) }} +
+
+ {{ form.location_id.label(class='control-label') }} + {{ form.location_id(class='form-control select') }} + {{ util.render_form_field_errors(form.location_id) }} +
+ {% if current_user.is_admin() %} +
+ {{ form.starttime.label(class='control-label') }} +
+ {{ form.starttime(class='form-control datetimepicker') }} + + + +
+ {{ util.render_form_field_errors(form.starttime) }} +
+ {% endif %} +
+ {{ form.stoptime.label(class='control-label') }} +
+ {{ form.stoptime(class='form-control datetimepicker') }} + + + +
+ {{ util.render_form_field_errors(form.stoptime) }} +
+
+ {{ form.submit_button(class='btn btn-primary') }} +
+
+
+
+
+ {% endif %} +
+ +{% endblock %} + +{% block styles -%} + {{ super() }} + + + +{%- endblock %} +{% block scripts -%} + {{ super() }} + + + + +{%- endblock %} \ No newline at end of file diff --git a/app/views/order.py b/app/views/order.py index b8089d0..de92b92 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -1,5 +1,5 @@ __author__ = 'feliciaan' -from flask import url_for, render_template, abort, redirect, Blueprint, flash, session +from flask import url_for, render_template, abort, redirect, Blueprint, flash, session, request from flask.ext.login import current_user, login_required import random from datetime import datetime @@ -49,6 +49,20 @@ def order(id, form=None): return render_template('order.html', order=order, form=form, total_price=total_price) +@order_bp.route('//edit', methods=['GET', 'POST']) +@login_required +def order_edit(id): + order = Order.query.filter(Order.id == id).first() + 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('//create', methods=['POST']) def order_item_create(id): current_order = Order.query.filter(Order.id == id).first() @@ -75,6 +89,8 @@ def order_item_create(id): return redirect(url_for('.order', id=id)) return order(id, form=form) + + @order_bp.route('///delete') def delete_item(order_id, item_id): item = OrderItem.query.filter(OrderItem.id == item_id).first() From 333a7ec5ec8543dc7f14ff72a609b5eab2002ff9 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 19:13:09 +0200 Subject: [PATCH 4/7] spacing --- app/views/order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/order.py b/app/views/order.py index de92b92..301cbb5 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -63,6 +63,7 @@ def order_edit(id): return redirect(url_for('.order', id=order.id)) return render_template('order_edit.html', form=orderForm, order_id=id) + @order_bp.route('//create', methods=['POST']) def order_item_create(id): current_order = Order.query.filter(Order.id == id).first() @@ -90,7 +91,6 @@ def order_item_create(id): return order(id, form=form) - @order_bp.route('///delete') def delete_item(order_id, item_id): item = OrderItem.query.filter(OrderItem.id == item_id).first() From 69eb22c50625e9b12f61dffccacb2303e3ffa6d3 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 21:20:38 +0200 Subject: [PATCH 5/7] Add paid indicator, fixes #19 --- app/migrations/versions/57a00d0b7bc_.py | 26 ++++++++++++ app/models.py | 18 ++++---- app/templates/home.html | 28 +++++++++---- app/templates/order.html | 56 ++++++++++++++++++------- app/templates/orders.html | 9 ++++ app/views/order.py | 43 +++++++++++++++++-- 6 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 app/migrations/versions/57a00d0b7bc_.py diff --git a/app/migrations/versions/57a00d0b7bc_.py b/app/migrations/versions/57a00d0b7bc_.py new file mode 100644 index 0000000..464d9c9 --- /dev/null +++ b/app/migrations/versions/57a00d0b7bc_.py @@ -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 ### diff --git a/app/models.py b/app/models.py index bc42d26..0145db4 100644 --- a/app/models.py +++ b/app/models.py @@ -65,7 +65,6 @@ class Product(db.Model): price = db.Column(db.Integer, nullable=False) orderItems = db.relationship('OrderItem', backref='product', lazy='dynamic') - def configure(self, location, name, price): self.location = location self.name = name @@ -94,15 +93,14 @@ class Order(db.Model): return 'Order %d @ %s' % (self.id, self.location.name or 'None') def group_by_user(self): - group = defaultdict(list) + group = dict() for item in self.items: - group[item.get_name()] += [item.product] - return group - - def group_by_user_pay(self): - group = defaultdict(int) - for item in self.items: - group[item.get_name()] += item.product.price + user = group.get(item.get_name(), dict()) + user["total"] = user.get("totalm", 0) + item.product.price + user["to_pay"] = user.get("to_pay", 0) + item.product.price if not item.paid else 0 + user["paid"] = user.get("paid", True) and item.paid + user["products"] = user.get("products", []) + [item.product] + group[item.get_name()] = user return group def group_by_product(self): @@ -122,11 +120,13 @@ class Order(db.Model): return True return False + class OrderItem(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False) product_id = db.Column(db.Integer, db.ForeignKey('product.id')) + paid = db.Column(db.Boolean, default=False) name = db.Column(db.String(120)) def configure(self, user, order, product): diff --git a/app/templates/home.html b/app/templates/home.html index 05f84cb..1e035bd 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -12,17 +12,27 @@
-

Open orders

- {% for order in orders %} - {{ util.render_order(order) }} - {% endfor %} + {% if orders|count > 0 -%} +

Open orders:

+ {% for order in orders %} + {{ util.render_order(order) }} + {% endfor %} + {% else %} +

No orders available.

+ {% 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 %}
-

Recently closed orders

- {% for order in recently_closed %} - {{ util.render_order(order) }} - {% endfor %} - + {% if orders|count > 0 -%} +

Recently closed orders:

+ {% for order in recently_closed %} + {{ util.render_order(order) }} + {% endfor %} + {%- endif %}
{% endblock %} diff --git a/app/templates/order.html b/app/templates/order.html index 52dbbf5..852687c 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -1,5 +1,7 @@ {% extends "layout.html" %} {% 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 %} @@ -7,7 +9,7 @@

Order {{ order.id }} - {% if order.can_close(current_user.id) -%} + {% if courier_or_admin -%} Close
{%- endif %}

courier: {{ order.courrier.username }} @@ -20,7 +22,7 @@ telephone: {{ order.location.telephone }}
{% endif %} status: {% if order.stoptime %}{{ order.stoptime|countdown }}{% else %}open{% endif %}
- total price: {{ total_price|euro }} + total price: {{ total_price|euro }} {% if courier_or_admin %}- remaining debts: {{ debts|euro }}{% endif %}
{% if form -%}
@@ -47,26 +49,50 @@ {%- endif %}
-
-

Items

- {% for item in order.items %} - {{ item.get_name() }} - {{ item.product.name }} - {{ item.product.price|euro }} - {% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}{%- endif %}
- {% endfor %} +
+

Items

+ + + {% if courier_or_admin %}{% endif %} + + + {% for item in order.items -%} + + + + + {% if courier_or_admin %}{% endif %} + + + {%- endfor %} + +
NameItemPricePaid?Delete
{{ item.get_name() }}{{ item.product.name }}{{ item.product.price|euro }}{% if not item.paid %} Pay {% else %} {% endif %}{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) or courier_or_admin -%}{%- endif %}
-
+

Ordered products:

- {% for key, value in order.group_by_product().items() %} + {% for key, value in order.group_by_product().items() -%} {{ key }} - {{ value }}
- {% endfor %} + {%- endfor %}
-
+

Debts

- {% for key, value in order.group_by_user_pay().items() %} - {{ key }} - {{ value|euro }}
- {% endfor %} + + + {% if courier_or_admin %}{% endif %} + + + {% for key, value in order_items.items() -%} + + + + + {% if courier_or_admin %}{% endif %} + + {%- endfor %} + +
NameTotalTo payPaid?
{{ key }}{{ value["total"]|euro }}{{ value["to_pay"]|euro }}{% if not value["to_pay"] == 0 %} Pay {% else %} {% endif %}
{% endblock %} diff --git a/app/templates/orders.html b/app/templates/orders.html index 112684e..9757ca0 100644 --- a/app/templates/orders.html +++ b/app/templates/orders.html @@ -7,10 +7,19 @@ {% block container %}
+ {% if orders|count > 0 -%}

Open orders:

{% for order in orders %} {{ util.render_order(order) }} {% endfor %} + {% else %} +

No orders available.

+ {% 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 %}
{% if not current_user.is_anonymous() %}
diff --git a/app/views/order.py b/app/views/order.py index b8089d0..2e9a07b 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -1,3 +1,4 @@ + __author__ = 'feliciaan' from flask import url_for, render_template, abort, redirect, Blueprint, flash, session from flask.ext.login import current_user, login_required @@ -5,7 +6,7 @@ import random from datetime import datetime from app import app, db -from models import Order, OrderItem +from models import Order, OrderItem, User from forms import OrderItemForm, OrderForm, AnonOrderItemForm order_bp = Blueprint('order_bp', 'order') @@ -46,7 +47,8 @@ def order(id, form=None): if order.stoptime and order.stoptime < datetime.now(): form = None 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('//create', methods=['POST']) @@ -75,6 +77,41 @@ def order_item_create(id): return redirect(url_for('.order', id=id)) return order(id, form=form) + +@order_bp.route('///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('///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('///delete') def delete_item(order_id, item_id): item = OrderItem.query.filter(OrderItem.id == item_id).first() @@ -85,7 +122,7 @@ def delete_item(order_id, item_id): product_name = item.product.name db.session.delete(item) db.session.commit() - flash('Deleted %s' % product_name, 'info') + flash('Deleted %s' % product_name, 'success') return redirect(url_for('.order', id=order_id)) abort(404) From 3d5f6ef1ae27a4662c07b2977065fbcd34189804 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 21:36:57 +0200 Subject: [PATCH 6/7] Check if user is allowed to edit --- app/views/order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/order.py b/app/views/order.py index 301cbb5..e6f056b 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -53,6 +53,8 @@ def order(id, form=None): @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) From d6c29b97034409b92d2171c26e7dcf2f31d1aa83 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 4 Jun 2015 22:37:00 +0200 Subject: [PATCH 7/7] Fixed bug for volunteering closes #44 --- app/migrations/versions/42709384216_.py | 26 +++++++++++++++++++++++++ app/models.py | 5 +++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 app/migrations/versions/42709384216_.py diff --git a/app/migrations/versions/42709384216_.py b/app/migrations/versions/42709384216_.py new file mode 100644 index 0000000..4fa051d --- /dev/null +++ b/app/migrations/versions/42709384216_.py @@ -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 ### diff --git a/app/models.py b/app/models.py index 9b2c79a..b4b8d85 100644 --- a/app/models.py +++ b/app/models.py @@ -10,7 +10,8 @@ class User(db.Model): username = db.Column(db.String(80), unique=True, nullable=False) admin = db.Column(db.Boolean) 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') def configure(self, username, admin, bias): @@ -76,7 +77,7 @@ class Product(db.Model): class Order(db.Model): 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')) starttime = db.Column(db.DateTime) stoptime = db.Column(db.DateTime)