Merge pull request #134 from ZeusWPI/refactoring

Refactoring
This commit is contained in:
Maxime 2019-09-05 16:13:21 +02:00 committed by GitHub
commit b90b8d571b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 661 additions and 767 deletions

1
.python-version Normal file
View file

@ -0,0 +1 @@
3.5.3

View file

@ -58,3 +58,7 @@ Finally run the webserver with
Run `pip-compile --upgrade`
For more information about managing the dependencies see [jazzband/pip-tools: A set of tools to keep your pinned Python dependencies fresh.](https://github.com/jazzband/pip-tools)
## Authors
* **Feliciaan De Palmenaer** - *Initial work* - [Github](https://github.com/feliciaan)

View file

@ -1,2 +0,0 @@
# Bind app.db to db
from app.app import db as db

View file

@ -1,14 +1,11 @@
import flask_login as login
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
import flask_login as login
from app import app, db
from models import User, Location, Product, Order, OrderItem
from models import Location, Order, OrderItem, Product, User
class ModelBaseView(ModelView):
def is_accessible(self):
if login.current_user.is_anonymous():
return False
@ -17,12 +14,12 @@ class ModelBaseView(ModelView):
class UserAdminModel(ModelBaseView):
column_searchable_list = ('username',)
column_searchable_list = ('username', )
inline_models = None
class ProductAdminModel(ModelBaseView):
column_searchable_list = ('name',)
column_searchable_list = ('name', )
inline_models = None
@ -32,11 +29,11 @@ class LocationAdminModel(ModelBaseView):
form_columns = ('name', 'address', 'website', 'telephone')
admin = Admin(app, name='Haldis', url='/admin', template_mode='bootstrap3')
def init_admin(app, db):
admin = Admin(app, name='Haldis', url='/admin', template_mode='bootstrap3')
admin.add_view(UserAdminModel(User, db.session))
admin.add_view(LocationAdminModel(Location, db.session))
admin.add_view(ProductAdminModel(Product, db.session))
admin.add_view(ModelBaseView(Order, db.session))
admin.add_view(ModelBaseView(OrderItem, db.session))
admin.add_view(UserAdminModel(User, db.session))
admin.add_view(LocationAdminModel(Location, db.session))
admin.add_view(ProductAdminModel(Product, db.session))
admin.add_view(ModelBaseView(Order, db.session))
admin.add_view(ModelBaseView(OrderItem, db.session))

View file

@ -1,48 +1,153 @@
import logging
from datetime import datetime
from logging.handlers import TimedRotatingFileHandler
from flask import Flask
from flask_bootstrap import Bootstrap, StaticCDN
from flask_sqlalchemy import SQLAlchemy
from flask_debugtoolbar import DebugToolbarExtension
from airbrake import Airbrake, AirbrakeHandler
from flask import Flask, render_template
from flask_bootstrap import Bootstrap, StaticCDN
from flask_debugtoolbar import DebugToolbarExtension
from flask_login import LoginManager
from flask_migrate import Migrate, MigrateCommand
from flask_oauthlib.client import OAuth, OAuthException
from flask_script import Manager, Server
from admin import init_admin
from login import init_login
from models import db
from models.anonymous_user import AnonymouseUser
from utils import euro_string
from zeus import init_oauth
app = Flask(__name__)
app.config.from_object('config.Configuration')
Bootstrap(app)
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
def create_app():
app = Flask(__name__)
# use our own bootstrap theme
app.extensions['bootstrap']['cdns']['bootstrap'] = StaticCDN()
# Load the config file
app.config.from_object('config.Configuration')
db = SQLAlchemy(app)
manager = register_plugins(app, debug=app.debug)
add_handlers(app)
add_routes(app)
add_template_filters(app)
toolbar = DebugToolbarExtension(app)
# TODO do we need to return and then run the manager?
return manager
if not app.debug:
timedFileHandler = TimedRotatingFileHandler(app.config['LOGFILE'], when='midnight', backupCount=100)
timedFileHandler.setLevel(logging.DEBUG)
def register_plugins(app, debug: bool):
# Register Airbrake and enable the logrotation
if not app.debug:
timedFileHandler = TimedRotatingFileHandler(app.config['LOGFILE'],
when='midnight',
backupCount=100)
timedFileHandler.setLevel(logging.DEBUG)
loglogger = logging.getLogger('werkzeug')
loglogger.setLevel(logging.DEBUG)
loglogger.addHandler(timedFileHandler)
app.logger.addHandler(timedFileHandler)
loglogger = logging.getLogger('werkzeug')
loglogger.setLevel(logging.DEBUG)
loglogger.addHandler(timedFileHandler)
app.logger.addHandler(timedFileHandler)
airbrakelogger = logging.getLogger('airbrake')
airbrakelogger = logging.getLogger('airbrake')
# Airbrake
airbrake = Airbrake(
project_id=app.config['AIRBRAKE_ID'],
api_key=app.config['AIRBRAKE_KEY']
)
# ugly hack to make this work for out errbit
airbrake._api_url = "http://errbit.awesomepeople.tv/api/v3/projects/{}/notices".format(airbrake.project_id)
# Airbrake
airbrake = Airbrake(project_id=app.config['AIRBRAKE_ID'],
api_key=app.config['AIRBRAKE_KEY'])
# ugly hack to make this work for out errbit
airbrake._api_url = "http://errbit.awesomepeople.tv/api/v3/projects/{}/notices".format(
airbrake.project_id)
airbrakelogger.addHandler(
AirbrakeHandler(airbrake=airbrake)
)
app.logger.addHandler(
AirbrakeHandler(airbrake=airbrake)
)
airbrakelogger.addHandler(AirbrakeHandler(airbrake=airbrake))
app.logger.addHandler(AirbrakeHandler(airbrake=airbrake))
# Initialize SQLAlchemy
db.init_app(app)
# Initialize Flask-Migrate
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
manager.add_command('runserver', Server(port=8000))
# Add admin interface
init_admin(app, db)
# Init login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = AnonymouseUser
init_login(app)
# Add oauth
zeus = init_oauth(app)
app.zeus = zeus
# Load the bootstrap local cdn
Bootstrap(app)
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
# use our own bootstrap theme
app.extensions['bootstrap']['cdns']['bootstrap'] = StaticCDN()
# Load the flask debug toolbar
toolbar = DebugToolbarExtension(app)
return manager
def add_handlers(app):
@app.errorhandler(404)
def handle404(e):
return render_template('errors/404.html'), 404
@app.errorhandler(401)
def handle401(e):
return render_template('errors/401.html'), 401
def add_routes(application):
# import views # TODO convert to blueprint
# import views.stats # TODO convert to blueprint
from views.order import order_bp
from views.general import general_bp
from views.stats import stats_blueprint
from views.debug import debug_bp
from login import auth_bp
from zeus import oauth_bp
application.register_blueprint(general_bp, url_prefix='/')
application.register_blueprint(order_bp, url_prefix='/order')
application.register_blueprint(stats_blueprint, url_prefix='/stats')
application.register_blueprint(auth_bp, url_prefix='/')
application.register_blueprint(oauth_bp, url_prefix='/')
if application.debug:
application.register_blueprint(debug_bp, url_prefix='/debug')
def add_template_filters(app):
@app.template_filter('countdown')
def countdown(value, only_positive=True, show_text=True):
delta = value - datetime.now()
if delta.total_seconds() < 0 and only_positive:
return "closed"
hours, remainder = divmod(delta.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
time = '%02d:%02d:%02d' % (hours, minutes, seconds)
if show_text:
return 'closes in ' + time
return time
@app.template_filter('year')
def current_year(value):
return str(datetime.now().year)
@app.template_filter('euro')
def euro(value):
euro_string(value)
# For usage when you directly call the script with python
if __name__ == '__main__':
manager = create_app()
manager.run()

View file

@ -1,20 +1,23 @@
from datetime import datetime, timedelta
from flask import session
from flask_login import current_user
from flask_wtf import FlaskForm as Form
from wtforms import SelectField, DateTimeField, validators, SubmitField, StringField
from wtforms import (DateTimeField, SelectField, StringField, SubmitField,
validators)
from models import User, Location
from utils import euro
__author__ = 'feliciaan'
from models import Location, User
from utils import euro_string
class OrderForm(Form):
courrier_id = SelectField('Courrier', coerce=int)
location_id = SelectField('Location', coerce=int, validators=[validators.required()])
starttime = DateTimeField('Starttime', default=datetime.now, format='%d-%m-%Y %H:%M')
location_id = SelectField('Location',
coerce=int,
validators=[validators.required()])
starttime = DateTimeField('Starttime',
default=datetime.now,
format='%d-%m-%Y %H:%M')
stoptime = DateTimeField('Stoptime', format='%d-%m-%Y %H:%M')
submit_button = SubmitField('Submit')
@ -23,7 +26,9 @@ class OrderForm(Form):
self.courrier_id.choices = [(0, None)] + \
[(u.id, u.username) for u in User.query.order_by('username')]
else:
self.courrier_id.choices = [(0, None), (current_user.id, current_user.username)]
self.courrier_id.choices = [(0, None),
(current_user.id,
current_user.username)]
self.location_id.choices = [(l.id, l.name)
for l in Location.query.order_by('name')]
if self.stoptime.data is None:
@ -36,7 +41,9 @@ class OrderItemForm(Form):
submit_button = SubmitField('Submit')
def populate(self, location):
self.product_id.choices = [(i.id, (i.name + ": " + euro(i.price))) for i in location.products]
self.product_id.choices = [(i.id,
(i.name + ": " + euro_string(i.price)))
for i in location.products]
class AnonOrderItemForm(OrderItemForm):

View file

@ -1,19 +0,0 @@
from views import *
from app import app, db
from admin import admin
from login import login_manager
from models import *
from forms import *
from utils import *
from views import *
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
if __name__ == '__main__':
# 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()

View file

@ -1,31 +1,30 @@
from flask import redirect, abort, session, url_for
from flask_login import LoginManager, current_user, logout_user
from flask import abort, Blueprint
from flask import redirect, session, url_for
from flask_login import current_user, logout_user
from app import app
from models import User
from zeus import zeus_login
login_manager = LoginManager()
login_manager.init_app(app)
auth_bp = Blueprint('auth_bp', __name__)
@login_manager.user_loader
def load_user(userid):
return User.query.filter_by(id=userid).first()
def init_login(app):
@app.login_manager.user_loader
def load_user(userid):
return User.query.filter_by(id=userid).first()
@app.route('/login')
@auth_bp.route('/login')
def login():
return zeus_login()
@app.route('/logout')
@auth_bp.route('/logout')
def logout():
if 'zeus_token' in session:
session.pop('zeus_token', None)
logout_user()
return redirect(url_for('home'))
return redirect(url_for('general_bp.home'))
def before_request():

View file

@ -1,54 +0,0 @@
"""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

@ -1,26 +0,0 @@
"""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

@ -1,74 +0,0 @@
"""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

@ -1,26 +0,0 @@
"""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

@ -1,26 +0,0 @@
"""Add extra message
Revision ID: 4e94c0b08ed
Revises: 42709384216
Create Date: 2015-06-24 21:42:41.466973
"""
# revision identifiers, used by Alembic.
revision = '4e94c0b08ed'
down_revision = '42709384216'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('order_item', sa.Column('extra', sa.String(length=254), nullable=True))
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('order_item', 'extra')
### end Alembic commands ###

View file

@ -1,26 +0,0 @@
"""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

@ -1,173 +0,0 @@
from datetime import datetime
from collections import defaultdict
from app import db
# Create database models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
admin = db.Column(db.Boolean)
bias = db.Column(db.Integer)
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):
self.username = username
self.admin = admin
self.bias = bias
def is_authenticated(self):
return True
def is_active(self):
return True
def is_admin(self):
return self.admin
def is_anonymous(self):
return False
def get_id(self):
try:
return unicode(self.id) # python 2
except NameError:
return str(self.id) # python 3
def __repr__(self):
return '%s' % self.username
class Location(db.Model):
id = db.Column(db.Integer, primary_key=True)
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')
def configure(self, name, address, telephone, website):
self.name = name
self.address = address
self.website = website
self.telephone = telephone
def __repr__(self):
return '%s' % (self.name)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
name = db.Column(db.String(120), nullable=False)
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
self.price = price
def __repr__(self):
return '%s (€%d)from %s' % (self.name, self.price/100, self.location or 'None')
class Order(db.Model):
id = db.Column(db.Integer, primary_key=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)
public = db.Column(db.Boolean, default=True)
items = db.relationship('OrderItem', backref='order', lazy='dynamic')
def configure(self, courrier, location, starttime, stoptime):
self.courrier = courrier
self.location = location
self.starttime = starttime
self.stoptime = stoptime
def __repr__(self):
if self.location:
return 'Order %d @ %s' % (self.id, self.location.name or 'None')
else:
return 'Order %d' % (self.id)
def group_by_user(self):
group = dict()
for item in self.items:
user = group.get(item.get_name(), dict())
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
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):
group = dict()
for item in self.items:
product = group.get(item.product.name, dict())
product['count'] = product.get("count", 0) + 1
if item.extra:
product["extras"] = product.get("extras", []) + [item.extra]
group[item.product.name] = product
return group
def can_close(self, user_id):
if self.stoptime and self.stoptime < datetime.now():
return False
user = None
if user_id:
user = User.query.filter_by(id=user_id).first()
print(user)
if self.courrier_id == user_id or (user and user.is_admin()):
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'),
nullable=True) # TODO make false after init migration
paid = db.Column(db.Boolean, default=False, nullable=True) # TODO make false after init migration
extra = db.Column(db.String(254), nullable=True)
name = db.Column(db.String(120))
def configure(self, user, order, product):
self.user = user
self.order = order
self.product = product
def get_name(self):
if self.user_id is not None and self.user_id > 0:
return self.user.username
return self.name
def __repr__(self):
product_name = None
if self.product:
product_name = self.product.name
return 'Order %d: %s wants %s' % (self.order_id or 0, self.get_name(), product_name or 'None')
def can_delete(self, order_id, user_id, name):
if int(self.order_id) != int(order_id):
return False
if self.order.stoptime and self.order.stoptime < datetime.now():
return False
if self.user is not None and self.user_id == user_id:
return True
if user_id is None:
return False
user = User.query.filter(User.id == user_id).first()
if user and user.is_admin():
return True
return False

16
app/models/__init__.py Normal file
View file

@ -0,0 +1,16 @@
# This file will expose what we want from the models module
# This will probably be everything. But putting the imports here makes it possible to import all models in one line like this:
#
# from models import User, Item, ...
#
# Instead of this:
# from models.user import User
# from models.item import Item
# ...
from .database import db
from .location import Location
from .order import Order
from .orderitem import OrderItem
from .product import Product
from .user import User

View file

@ -0,0 +1,17 @@
class AnonymouseUser:
id = None
def is_active(self):
return False
def is_authenticated(self):
return False
def is_anonymous(self):
return True
def is_admin(self):
return False
def get_id(self):
return None

3
app/models/database.py Normal file
View file

@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

20
app/models/location.py Normal file
View file

@ -0,0 +1,20 @@
from models import db
class Location(db.Model):
id = db.Column(db.Integer, primary_key=True)
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')
def configure(self, name, address, telephone, website):
self.name = name
self.address = address
self.website = website
self.telephone = telephone
def __repr__(self):
return '%s' % (self.name)

61
app/models/order.py Normal file
View file

@ -0,0 +1,61 @@
from datetime import datetime
from .database import db
from .user import User
class Order(db.Model):
id = db.Column(db.Integer, primary_key=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)
public = db.Column(db.Boolean, default=True)
items = db.relationship('OrderItem', backref='order', lazy='dynamic')
def configure(self, courrier, location, starttime, stoptime):
self.courrier = courrier
self.location = location
self.starttime = starttime
self.stoptime = stoptime
def __repr__(self):
if self.location:
return 'Order %d @ %s' % (self.id, self.location.name or 'None')
else:
return 'Order %d' % (self.id)
def group_by_user(self):
group = dict()
for item in self.items:
user = group.get(item.get_name(), dict())
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
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):
group = dict()
for item in self.items:
product = group.get(item.product.name, dict())
product['count'] = product.get("count", 0) + 1
if item.extra:
product["extras"] = product.get("extras", []) + [item.extra]
group[item.product.name] = product
return group
def can_close(self, user_id):
if self.stoptime and self.stoptime < datetime.now():
return False
user = None
if user_id:
user = User.query.filter_by(id=user_id).first()
print(user)
if self.courrier_id == user_id or (user and user.is_admin()):
return True
return False

48
app/models/orderitem.py Normal file
View file

@ -0,0 +1,48 @@
from datetime import datetime
from .database import db
from .user import User
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'),
nullable=True) # TODO make false after init migration
paid = db.Column(db.Boolean, default=False,
nullable=True) # TODO make false after init migration
extra = db.Column(db.String(254), nullable=True)
name = db.Column(db.String(120))
def configure(self, user, order, product):
self.user = user
self.order = order
self.product = product
def get_name(self):
if self.user_id is not None and self.user_id > 0:
return self.user.username
return self.name
def __repr__(self):
product_name = None
if self.product:
product_name = self.product.name
return 'Order %d: %s wants %s' % (self.order_id or 0, self.get_name(),
product_name or 'None')
def can_delete(self, order_id, user_id, name):
if int(self.order_id) != int(order_id):
return False
if self.order.stoptime and self.order.stoptime < datetime.now():
return False
if self.user is not None and self.user_id == user_id:
return True
if user_id is None:
return False
user = User.query.filter(User.id == user_id).first()
if user and user.is_admin():
return True
return False

20
app/models/product.py Normal file
View file

@ -0,0 +1,20 @@
from models import db
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
name = db.Column(db.String(120), nullable=False)
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
self.price = price
def __repr__(self):
return '%s (€%d)from %s' % (self.name, self.price / 100, self.location
or 'None')

36
app/models/user.py Normal file
View file

@ -0,0 +1,36 @@
from models import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
admin = db.Column(db.Boolean)
bias = db.Column(db.Integer)
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):
self.username = username
self.admin = admin
self.bias = bias
def is_authenticated(self):
return True
def is_active(self):
return True
def is_admin(self):
return self.admin
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
def __repr__(self):
return '%s' % self.username

47
app/notification.py Normal file
View file

@ -0,0 +1,47 @@
import json
from datetime import datetime
from threading import Thread
import requests
from flask import current_app as app
from flask import url_for
def post_order_to_webhook(order_item):
message = ''
if order_item.courrier is not None:
message = '<!channel|@channel> {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!'.format(
url_for('order_bp.order', id=order_item.id, _external=True),
order_item.location.name, remaining_minutes(order_item.stoptime),
order_item.courrier.username.title())
else:
message = '<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>'.format(
order_item.location.name, remaining_minutes(order_item.stoptime),
url_for('order_bp.order', id=order_item.id, _external=True))
webhookthread = WebhookSenderThread(message)
webhookthread.start()
class WebhookSenderThread(Thread):
def __init__(self, message):
super(WebhookSenderThread, self).__init__()
self.message = message
def run(self):
self.slack_webhook()
def slack_webhook(self):
js = json.dumps({'text': self.message})
url = app.config['SLACK_WEBHOOK']
if len(url) > 0:
requests.post(url, data=js)
else:
app.logger.info(str(js))
def remaining_minutes(value):
delta = value - datetime.now()
if delta.total_seconds() < 0:
return "0"
minutes, _ = divmod(delta.total_seconds(), 60)
return "%02d" % minutes

View file

@ -1,7 +1,9 @@
#!/usr/bin/env python
import sys
import os
import sys
from app import create_app
INTERP = os.path.expanduser("~/env/bin/python")
if sys.executable != INTERP:
@ -9,4 +11,8 @@ if sys.executable != INTERP:
sys.path.append(os.getcwd())
from haldis import app as application
application = create_app()
# For running on the server with passenger etc
if __name__ == "__main__":
application.run(port=8000)

View file

@ -4,6 +4,6 @@
<div class="jumbotron">
<h1>Unauthorized</h1>
<p>You're not authorized to look to this page!</p>
<p><a href="{{ url_for('home') }}">Go somewhere nice</a></p>
<p><a href="{{ url_for('general_bp.home') }}">Go somewhere nice</a></p>
</div>
{% endblock %}

View file

@ -4,6 +4,6 @@
<div class="jumbotron">
<h1>Page Not Found</h1>
<p>What you were looking for is just not there.</p>
<p><a href="{{ url_for('home') }}">Go somewhere nice</a></p>
<p><a href="{{ url_for('general_bp.home') }}">Go somewhere nice</a></p>
</div>
{% endblock %}

View file

@ -2,12 +2,12 @@
{% import "bootstrap/utils.html" as utils %}
{% set navbar = [
('home', 'Home'),
('general_bp.home', 'Home'),
('order_bp.orders', 'Orders'),
('locations', 'Locations'),
('map', 'Map'),
('about', 'About'),
('stats', 'Stats'),
('general_bp.locations', 'Locations'),
('general_bp.map', 'Map'),
('general_bp.about', 'About'),
('stats_blueprint.stats', 'Stats'),
] -%}
{% if not current_user.is_anonymous() and current_user.is_admin() -%}
{% set navbar = navbar + [('admin.index', 'Admin')] -%}
@ -43,7 +43,7 @@ Haldis - {{ active_page|capitalize }}
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ url_for('home') }}">HALDIS</a>
<a class="navbar-brand" href="{{ url_for('general_bp.home') }}">HALDIS</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
@ -55,10 +55,10 @@ Haldis - {{ active_page|capitalize }}
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_anonymous() %}
<li><a href="{{ url_for('login') }}">Login</a></li>
<li><a href="{{ url_for('auth_bp.login') }}">Login</a></li>
{% else %}
<li><a href="{{ url_for('profile') }}">{{ current_user.username }}</a></li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
<li><a href="{{ url_for('general_bp.profile') }}">{{ current_user.username }}</a></li>
<li><a href="{{ url_for('auth_bp.logout') }}">Logout</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->

View file

@ -14,7 +14,7 @@
<tbody>
{% for loc in locations -%}
<tr>
<td><a href="{{ url_for('location', id=loc.id) }}">{{ loc.name }}</a></td>
<td><a href="{{ url_for('general_bp.location', id=loc.id) }}">{{ loc.name }}</a></td>
<td>{{ loc.address }}<td>
<td><a href="{{ loc.website}}"><span class="glyphicon glyphicon-link"></span></a></td>
<td>

View file

@ -28,7 +28,7 @@
loc = {
"address": "{{loc.address}}",
"name": "{{loc.name}}",
"url": "{{ url_for('location', id=loc.id) }}"
"url": "{{ url_for('general_bp.location', id=loc.id) }}"
};
locations.push(loc);

View file

@ -11,14 +11,14 @@
<h3 id="order-title">Order {{ order.id }}
<div class="pull-right">
{% if order.can_close(current_user.id) -%}
<a class="btn btn-danger" href="{{ url_for('.close_order', id=order.id) }}">Close</a>
<a class="btn btn-danger" href="{{ url_for('order_bp.close_order', id=order.id) }}">Close</a>
{% endif %}{% if courier_or_admin %}
<a class="btn btn-warning" href="{{ url_for('.order_edit', id=order.id) }}">Edit</a>
<a class="btn btn-warning" href="{{ url_for('order_bp.order_edit', id=order.id) }}">Edit</a>
{%- endif %}
</div></h3>
courier: {{ order.courrier.username }}
{% if order.courrier == None and not current_user.is_anonymous() %}
<a href="{{ url_for('.volunteer', id=order.id) }}" class="btn btn-primary btn-sm">Volunteer</a>
<a href="{{ url_for('order_bp.volunteer', id=order.id) }}" class="btn btn-primary btn-sm">Volunteer</a>
{% endif %}
<br/>
location: <a href="{{ order.location.website }}">{{ order.location.name }}</a><br/>
@ -34,7 +34,7 @@
{% if form -%}
<div class="col-md-push-1 col-md-10 darker" id="form">
<h4>Order:</h4>
<form method="post" action="{{ url_for('.order_item_create', id=order.id) }}">
<form method="post" action="{{ url_for('order_bp.order_item_create', id=order.id) }}">
<span class="pull-right">
<a class="btn btn-primary" onclick="chooseRandom()">Choose for me</a>
</span>
@ -76,8 +76,8 @@
<td>{{ item.get_name() }}</td>
<td><span title="{{ item.extra if item.extra }}">{{ item.product.name }}{{ "*" if item.extra }}</span></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', '')) -%}<a href="{{ url_for('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/></td>
{% if courier_or_admin %}<td>{% if not item.paid %} <a class="btn btn-xs btn-primary" href="{{ url_for('order_bp.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', '')) -%}<a href="{{ url_for('order_bp.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/></td>
</tr>
{%- endfor %}
</tbody>
@ -85,7 +85,7 @@
</div>
<div class="col-md-push-2 col-md-4 darker box" id="items-ordered">
<h3>Ordered products: {{ order.items.count() }}</h3>
<a class="divLink" href="{{ url_for('.items_showcase', id=order.id) }}"></a>
<a class="divLink" href="{{ url_for('order_bp.items_showcase', id=order.id) }}"></a>
{% for key, value in order.group_by_product().items() -%}
<div class="product">
{{ key }}: {{ value["count"] }}
@ -113,7 +113,7 @@
<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 %}
{% if courier_or_admin %}<td>{% if not value["to_pay"] == 0 %} <a class="btn btn-xs btn-primary" href="{{ url_for('order_bp.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>

View file

@ -26,7 +26,7 @@
<h3>Create new order:</h3>
<div class="row darker">
<div class="col-sm-12">
<form method="post" action="{{ url_for('.order_create') }}">
<form method="post" action="{{ url_for('order_bp.order_create') }}">
{{ form.csrf_token }}
<div class="form-group select2 {{ 'has-errors' if form.courrier_id.errors else ''}}">
{{ form.courrier_id.label(class='control-label') }}<br>

View file

@ -1,57 +1,6 @@
from datetime import datetime
from flask import render_template
from app import app
from login import login_manager
__author__ = 'feliciaan'
@app.template_filter('euro')
def euro(value):
result = '{:.2f}'.format(round(value/100,2))
def euro_string(value):
"""
Convert cents to string formatted euro
"""
result = '{:.2f}'.format(round(value / 100, 2))
return result
@app.template_filter('countdown')
def countdown(value, only_positive=True, show_text=True):
delta = value - datetime.now()
if delta.total_seconds() < 0 and only_positive:
return "closed"
hours, remainder = divmod(delta.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
time = '%02d:%02d:%02d' % (hours, minutes, seconds)
if show_text:
return 'closes in ' + time
return time
@app.template_filter('year')
def current_year(value):
return str(datetime.now().year)
@app.errorhandler(404)
def handle404(e):
return render_template('errors/404.html'), 404
@app.errorhandler(401)
def handle401(e):
return render_template('errors/401.html'), 401
class AnonymouseUser:
id = None
def is_active(self):
return False
def is_authenticated(self):
return False
def is_anonymous(self):
return True
def is_admin(self):
return False
def get_id(self):
return None
login_manager.anonymous_user = AnonymouseUser

View file

@ -1,85 +0,0 @@
from datetime import datetime, timedelta
from flask import url_for, render_template, abort, send_from_directory
from flask_login import login_required
import os
from app import app
from models import Order, Location
# import views
from views.order import get_orders
from views import stats
@app.route('/')
def home():
prev_day = datetime.now() - timedelta(days=1)
recently_closed = get_orders(
((Order.stoptime > prev_day) & (Order.stoptime < datetime.now())))
return render_template('home.html', orders=get_orders(),
recently_closed=recently_closed)
@app.route('/map', defaults= {'id': None})
@app.route('/map/<int:id>')
def map(id):
locs = Location.query.order_by('name')
return render_template('maps.html', locations= locs)
@app.route('/location')
def locations():
locs = Location.query.order_by('name')
return render_template('locations.html', locations=locs)
@app.route('/location/<int:id>')
def location(id):
loc = Location.query.filter(Location.id == id).first()
if loc is None:
abort(404)
return render_template('location.html', location=loc, title=loc.name)
@app.route('/about/')
def about():
return render_template('about.html')
@app.route('/profile/')
@login_required
def profile():
return render_template('profile.html')
@app.route('/favicon.ico')
def favicon():
if len(get_orders((Order.stoptime > datetime.now()))) == 0:
return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/x-icon')
else:
return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon_orange.ico', mimetype='image/x-icon')
if app.debug: # add route information
@app.route('/routes')
@login_required
def list_routes():
import urllib
output = []
for rule in app.url_map.iter_rules():
options = {}
for arg in rule.arguments:
options[arg] = "[{0}]".format(arg)
print(rule.endpoint)
methods = ','.join(rule.methods)
url = url_for(rule.endpoint, **options)
line = urllib.parse.unquote(
"{:50s} {:20s} {}".format(rule.endpoint, methods, url))
output.append(line)
string = ''
for line in sorted(output):
string += line + "<br/>"
return string

29
app/views/debug.py Normal file
View file

@ -0,0 +1,29 @@
from flask import Blueprint
from flask import current_app as app
from flask import url_for
from flask_login import login_required
debug_bp = Blueprint('debug_bp', __name__)
@debug_bp.route('/routes')
@login_required
def list_routes():
import urllib
output = []
for rule in app.url_map.iter_rules():
options = {}
for arg in rule.arguments:
options[arg] = "[{0}]".format(arg)
print(rule.endpoint)
methods = ','.join(rule.methods)
url = url_for(rule.endpoint, **options)
line = urllib.parse.unquote("{:50s} {:20s} {}".format(
rule.endpoint, methods, url))
output.append(line)
string = ''
for line in sorted(output):
string += line + "<br/>"
return string

67
app/views/general.py Normal file
View file

@ -0,0 +1,67 @@
import os
from datetime import datetime, timedelta
from flask import Blueprint, abort
from flask import current_app as app
from flask import render_template, send_from_directory, url_for
from flask_login import login_required
from models import Location, Order
# import views
from views.order import get_orders
general_bp = Blueprint('general_bp', __name__)
@general_bp.route('/')
def home():
prev_day = datetime.now() - timedelta(days=1)
recently_closed = get_orders(
((Order.stoptime > prev_day) & (Order.stoptime < datetime.now())))
return render_template('home.html',
orders=get_orders(),
recently_closed=recently_closed)
@general_bp.route('/map', defaults={'id': None})
@general_bp.route('/map/<int:id>')
def map(id):
locs = Location.query.order_by('name')
return render_template('maps.html', locations=locs)
@general_bp.route('/location')
def locations():
locs = Location.query.order_by('name')
return render_template('locations.html', locations=locs)
@general_bp.route('/location/<int:id>')
def location(id):
loc = Location.query.filter(Location.id == id).first()
if loc is None:
abort(404)
return render_template('location.html', location=loc, title=loc.name)
@general_bp.route('/about/')
def about():
return render_template('about.html')
@general_bp.route('/profile/')
@login_required
def profile():
return render_template('profile.html')
@general_bp.route('/favicon.ico')
def favicon():
if len(get_orders((Order.stoptime > datetime.now()))) == 0:
return send_from_directory(os.path.join(app.root_path, 'static'),
'favicon.ico',
mimetype='image/x-icon')
else:
return send_from_directory(os.path.join(app.root_path, 'static'),
'favicon_orange.ico',
mimetype='image/x-icon')

View file

@ -1,18 +1,18 @@
__author__ = 'feliciaan'
import json
from threading import Thread
import requests
from flask import url_for, render_template, abort, redirect, Blueprint, flash, session, request
from flask_login import current_user, login_required
import random
from datetime import datetime
from app import app, db
from models import Order, OrderItem, User
from forms import OrderItemForm, OrderForm, AnonOrderItemForm
# from flask import current_app as app
from flask import (Blueprint, abort, flash, redirect, render_template, request,
session, url_for)
from flask_login import current_user, login_required
from forms import AnonOrderItemForm, OrderForm, OrderItemForm
from models import Order, OrderItem, User, db
from notification import post_order_to_webhook
order_bp = Blueprint('order_bp', 'order')
@order_bp.route('/')
def orders(form=None):
if form is None and not current_user.is_anonymous():
@ -35,7 +35,7 @@ def order_create():
db.session.add(order)
db.session.commit()
post_order_to_webhook(order)
return redirect(url_for('.order', id=order.id))
return redirect(url_for('order_bp.order', id=order.id))
return orders(form=orderForm)
@ -48,13 +48,19 @@ def order(id, form=None):
flash('Please login to see this order.', 'info')
abort(401)
if form is None:
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
form = AnonOrderItemForm() if current_user.is_anonymous(
) else OrderItemForm()
form.populate(order.location)
if order.stoptime and order.stoptime < datetime.now():
form = None
total_price = sum([o.product.price for o in order.items])
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)
return render_template('order.html',
order=order,
form=form,
total_price=total_price,
debts=debts)
@order_bp.route('/<id>/items')
def items_showcase(id, form=None):
@ -66,11 +72,13 @@ def items_showcase(id, form=None):
abort(401)
return render_template('order_items.html', order=order)
@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():
if current_user.id is not order.courrier_id and not current_user.is_admin(
):
abort(401)
if order is None:
abort(404)
@ -79,7 +87,7 @@ def order_edit(id):
if orderForm.validate_on_submit():
orderForm.populate_obj(order)
db.session.commit()
return redirect(url_for('.order', id=order.id))
return redirect(url_for('order_bp.order', id=order.id))
return render_template('order_edit.html', form=orderForm, order_id=id)
@ -93,7 +101,8 @@ def order_item_create(id):
if current_user.is_anonymous() and not current_order.public:
flash('Please login to see this order.', 'info')
abort(401)
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
form = AnonOrderItemForm() if current_user.is_anonymous(
) else OrderItemForm()
form.populate(current_order.location)
if form.validate_on_submit():
item = OrderItem()
@ -106,7 +115,7 @@ def order_item_create(id):
db.session.add(item)
db.session.commit()
flash('Ordered %s' % (item.product.name), 'success')
return redirect(url_for('.order', id=id))
return redirect(url_for('order_bp.order', id=id))
return order(id, form=form)
@ -118,8 +127,9 @@ def item_paid(order_id, item_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.get_name()), 'success')
return redirect(url_for('.order', id=order_id))
flash('Paid %s by %s' % (item.product.name, item.get_name()),
'success')
return redirect(url_for('order_bp.order', id=order_id))
abort(404)
@ -129,9 +139,11 @@ 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))
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))
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)
@ -139,8 +151,9 @@ def items_user_paid(order_id, user_name):
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))
flash('Paid %d items for %s' % (items.count(), item.get_name()),
'success')
return redirect(url_for('order_bp.order', id=order_id))
abort(404)
@ -156,7 +169,7 @@ def delete_item(order_id, item_id):
db.session.delete(item)
db.session.commit()
flash('Deleted %s' % (product_name), 'success')
return redirect(url_for('.order', id=order_id))
return redirect(url_for('order_bp.order', id=order_id))
abort(404)
@ -172,7 +185,7 @@ def volunteer(id):
flash("Thank you for volunteering!")
else:
flash("Volunteering not possible!")
return redirect(url_for('.order', id=id))
return redirect(url_for('order_bp.order', id=id))
@order_bp.route('/<id>/close')
@ -190,9 +203,7 @@ def close_order(id):
if courrier is not None:
order.courrier_id = courrier.id
db.session.commit()
return redirect(url_for('.order', id=id))
app.register_blueprint(order_bp, url_prefix='/order')
return redirect(url_for('order_bp.order', id=id))
def select_user(items):
@ -216,52 +227,12 @@ def select_user(items):
def get_orders(expression=None):
orders = []
if expression is None:
expression = ((datetime.now() > Order.starttime) & (Order.stoptime > datetime.now()) | (Order.stoptime == None))
expression = ((datetime.now() > Order.starttime) &
(Order.stoptime > datetime.now()) |
(Order.stoptime == None))
if not current_user.is_anonymous():
orders = Order.query.filter(expression).all()
else:
orders = Order.query.filter((expression & (Order.public == True))).all()
orders = Order.query.filter(
(expression & (Order.public == True))).all()
return orders
def post_order_to_webhook(order_item):
message = ''
if order_item.courrier is not None:
message = '<!channel|@channel> {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!'.format(
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 = '<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>'.format(
order_item.location.name,
remaining_minutes(order_item.stoptime),
url_for('.order', id=order_item.id, _external=True))
webhookthread = WebhookSenderThread(message)
webhookthread.start()
class WebhookSenderThread(Thread):
def __init__(self, message):
super(WebhookSenderThread, self).__init__()
self.message = message
def run(self):
self.slack_webhook()
def slack_webhook(self):
js = json.dumps({'text': self.message})
url = app.config['SLACK_WEBHOOK']
if len(url) > 0:
requests.post(url, data=js)
else:
app.logger.info(str(js))
def remaining_minutes(value):
delta = value - datetime.now()
if delta.total_seconds() < 0:
return "0"
minutes, _ = divmod(delta.total_seconds(), 60)
return "%02d" % minutes

View file

@ -1,10 +1,13 @@
from flask import Blueprint
from flask import current_app as app
from flask import render_template
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser, FatProduct
from app import app
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatProduct, FatUser
stats_blueprint = Blueprint('stats_blueprint', __name__)
@app.route('/stats/')
@stats_blueprint.route('/')
def stats():
data = {
'amount': {

View file

@ -1,44 +1,29 @@
from flask import redirect, url_for, session, jsonify, flash, request
from flask import (current_app, flash, redirect, request, session,
url_for, Blueprint)
from flask_login import login_user
from flask_oauthlib.client import OAuth, OAuthException
import json
import requests
from flask_oauthlib.client import OAuthException, OAuth
from models import User, db
from app import app, db
from models import User
oauth = OAuth(app)
zeus = oauth.remote_app(
'zeus',
consumer_key=app.config['ZEUS_KEY'],
consumer_secret=app.config['ZEUS_SECRET'],
request_token_params={},
base_url='https://adams.ugent.be/oauth/api/',
access_token_method='POST',
access_token_url='https://adams.ugent.be/oauth/oauth2/token/',
authorize_url='https://adams.ugent.be/oauth/oauth2/authorize/'
)
oauth_bp = Blueprint("oauth_bp", __name__)
def zeus_login():
return zeus.authorize(callback=url_for('authorized', _external=True))
return current_app.zeus.authorize(
callback=url_for('oauth_bp.authorized', _external=True))
@app.route('/login/zeus/authorized')
@oauth_bp.route('/login/zeus/authorized')
def authorized():
resp = zeus.authorized_response()
resp = current_app.zeus.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
request.args['error'], request.args['error_description'])
if isinstance(resp, OAuthException):
return 'Access denied: %s' % resp.message + '<br>' + str(resp.data)
session['zeus_token'] = (resp['access_token'], '')
me = zeus.get('current_user/')
me = current_app.zeus.get('current_user/')
username = me.data.get('username', '').lower()
user = User.query.filter_by(username=username).first()
@ -49,17 +34,31 @@ def authorized():
return login_and_redirect_user(user)
flash("You're not allowed to enter, please contact a system administrator")
return redirect(url_for("home"))
return redirect(url_for("general_bp.home"))
def init_oauth(app):
oauth = OAuth(app)
@zeus.tokengetter
def get_zeus_oauth_token():
return session.get('zeus_token')
zeus = oauth.remote_app(
'zeus',
consumer_key=app.config['ZEUS_KEY'],
consumer_secret=app.config['ZEUS_SECRET'],
request_token_params={},
base_url='https://adams.ugent.be/oauth/api/',
access_token_method='POST',
access_token_url='https://adams.ugent.be/oauth/oauth2/token/',
authorize_url='https://adams.ugent.be/oauth/oauth2/authorize/')
@zeus.tokengetter
def get_zeus_oauth_token():
return session.get('zeus_token')
return zeus
def login_and_redirect_user(user):
login_user(user)
return redirect(url_for("home"))
return redirect(url_for("general_bp.home"))
def create_user(username):

View file

@ -29,4 +29,4 @@ cd ..
echo -e "${B} Seeding database ${E}"
./populate-db.sh
echo -e "${B} Activate your venv using 'source venv/bin/activate'.\nThen run the server with 'python app/haldis.py runserver' ${E}"
echo -e "${B} Activate your venv using 'source venv/bin/activate'.\nThen run the server with 'python app/app.py runserver' ${E}"