commit
b90b8d571b
40 changed files with 661 additions and 767 deletions
1
.python-version
Normal file
1
.python-version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
3.5.3
|
|
@ -58,3 +58,7 @@ Finally run the webserver with
|
||||||
Run `pip-compile --upgrade`
|
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)
|
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)
|
|
@ -1,2 +0,0 @@
|
||||||
# Bind app.db to db
|
|
||||||
from app.app import db as db
|
|
25
app/admin.py
25
app/admin.py
|
@ -1,14 +1,11 @@
|
||||||
|
import flask_login as login
|
||||||
from flask_admin import Admin
|
from flask_admin import Admin
|
||||||
from flask_admin.contrib.sqla import ModelView
|
from flask_admin.contrib.sqla import ModelView
|
||||||
import flask_login as login
|
|
||||||
|
|
||||||
|
from models import Location, Order, OrderItem, Product, User
|
||||||
from app import app, db
|
|
||||||
from models import User, Location, Product, Order, OrderItem
|
|
||||||
|
|
||||||
|
|
||||||
class ModelBaseView(ModelView):
|
class ModelBaseView(ModelView):
|
||||||
|
|
||||||
def is_accessible(self):
|
def is_accessible(self):
|
||||||
if login.current_user.is_anonymous():
|
if login.current_user.is_anonymous():
|
||||||
return False
|
return False
|
||||||
|
@ -17,12 +14,12 @@ class ModelBaseView(ModelView):
|
||||||
|
|
||||||
|
|
||||||
class UserAdminModel(ModelBaseView):
|
class UserAdminModel(ModelBaseView):
|
||||||
column_searchable_list = ('username',)
|
column_searchable_list = ('username', )
|
||||||
inline_models = None
|
inline_models = None
|
||||||
|
|
||||||
|
|
||||||
class ProductAdminModel(ModelBaseView):
|
class ProductAdminModel(ModelBaseView):
|
||||||
column_searchable_list = ('name',)
|
column_searchable_list = ('name', )
|
||||||
inline_models = None
|
inline_models = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,11 +29,11 @@ class LocationAdminModel(ModelBaseView):
|
||||||
form_columns = ('name', 'address', 'website', 'telephone')
|
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(UserAdminModel(User, db.session))
|
admin.add_view(LocationAdminModel(Location, db.session))
|
||||||
admin.add_view(LocationAdminModel(Location, db.session))
|
admin.add_view(ProductAdminModel(Product, db.session))
|
||||||
admin.add_view(ProductAdminModel(Product, db.session))
|
admin.add_view(ModelBaseView(Order, db.session))
|
||||||
admin.add_view(ModelBaseView(Order, db.session))
|
admin.add_view(ModelBaseView(OrderItem, db.session))
|
||||||
admin.add_view(ModelBaseView(OrderItem, db.session))
|
|
||||||
|
|
155
app/app.py
155
app/app.py
|
@ -1,28 +1,45 @@
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
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 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__)
|
def create_app():
|
||||||
app.config.from_object('config.Configuration')
|
app = Flask(__name__)
|
||||||
Bootstrap(app)
|
|
||||||
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
|
|
||||||
|
|
||||||
# use our own bootstrap theme
|
# Load the config file
|
||||||
app.extensions['bootstrap']['cdns']['bootstrap'] = StaticCDN()
|
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:
|
def register_plugins(app, debug: bool):
|
||||||
timedFileHandler = TimedRotatingFileHandler(app.config['LOGFILE'], when='midnight', backupCount=100)
|
# Register Airbrake and enable the logrotation
|
||||||
|
if not app.debug:
|
||||||
|
timedFileHandler = TimedRotatingFileHandler(app.config['LOGFILE'],
|
||||||
|
when='midnight',
|
||||||
|
backupCount=100)
|
||||||
timedFileHandler.setLevel(logging.DEBUG)
|
timedFileHandler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
loglogger = logging.getLogger('werkzeug')
|
loglogger = logging.getLogger('werkzeug')
|
||||||
|
@ -33,16 +50,104 @@ if not app.debug:
|
||||||
airbrakelogger = logging.getLogger('airbrake')
|
airbrakelogger = logging.getLogger('airbrake')
|
||||||
|
|
||||||
# Airbrake
|
# Airbrake
|
||||||
airbrake = Airbrake(
|
airbrake = Airbrake(project_id=app.config['AIRBRAKE_ID'],
|
||||||
project_id=app.config['AIRBRAKE_ID'],
|
api_key=app.config['AIRBRAKE_KEY'])
|
||||||
api_key=app.config['AIRBRAKE_KEY']
|
|
||||||
)
|
|
||||||
# ugly hack to make this work for out errbit
|
# 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._api_url = "http://errbit.awesomepeople.tv/api/v3/projects/{}/notices".format(
|
||||||
|
airbrake.project_id)
|
||||||
|
|
||||||
airbrakelogger.addHandler(
|
airbrakelogger.addHandler(AirbrakeHandler(airbrake=airbrake))
|
||||||
AirbrakeHandler(airbrake=airbrake)
|
app.logger.addHandler(AirbrakeHandler(airbrake=airbrake))
|
||||||
)
|
|
||||||
app.logger.addHandler(
|
# Initialize SQLAlchemy
|
||||||
AirbrakeHandler(airbrake=airbrake)
|
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()
|
||||||
|
|
27
app/forms.py
27
app/forms.py
|
@ -1,20 +1,23 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from flask import session
|
from flask import session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_wtf import FlaskForm as Form
|
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 Location, User
|
||||||
from models import User, Location
|
from utils import euro_string
|
||||||
from utils import euro
|
|
||||||
|
|
||||||
__author__ = 'feliciaan'
|
|
||||||
|
|
||||||
|
|
||||||
class OrderForm(Form):
|
class OrderForm(Form):
|
||||||
courrier_id = SelectField('Courrier', coerce=int)
|
courrier_id = SelectField('Courrier', coerce=int)
|
||||||
location_id = SelectField('Location', coerce=int, validators=[validators.required()])
|
location_id = SelectField('Location',
|
||||||
starttime = DateTimeField('Starttime', default=datetime.now, format='%d-%m-%Y %H:%M')
|
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')
|
stoptime = DateTimeField('Stoptime', format='%d-%m-%Y %H:%M')
|
||||||
submit_button = SubmitField('Submit')
|
submit_button = SubmitField('Submit')
|
||||||
|
|
||||||
|
@ -23,7 +26,9 @@ class OrderForm(Form):
|
||||||
self.courrier_id.choices = [(0, None)] + \
|
self.courrier_id.choices = [(0, None)] + \
|
||||||
[(u.id, u.username) for u in User.query.order_by('username')]
|
[(u.id, u.username) for u in User.query.order_by('username')]
|
||||||
else:
|
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)
|
self.location_id.choices = [(l.id, l.name)
|
||||||
for l in Location.query.order_by('name')]
|
for l in Location.query.order_by('name')]
|
||||||
if self.stoptime.data is None:
|
if self.stoptime.data is None:
|
||||||
|
@ -36,7 +41,9 @@ class OrderItemForm(Form):
|
||||||
submit_button = SubmitField('Submit')
|
submit_button = SubmitField('Submit')
|
||||||
|
|
||||||
def populate(self, location):
|
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):
|
class AnonOrderItemForm(OrderItemForm):
|
||||||
|
|
|
@ -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()
|
|
21
app/login.py
21
app/login.py
|
@ -1,31 +1,30 @@
|
||||||
from flask import redirect, abort, session, url_for
|
from flask import abort, Blueprint
|
||||||
from flask_login import LoginManager, current_user, logout_user
|
from flask import redirect, session, url_for
|
||||||
|
from flask_login import current_user, logout_user
|
||||||
|
|
||||||
|
|
||||||
from app import app
|
|
||||||
from models import User
|
from models import User
|
||||||
from zeus import zeus_login
|
from zeus import zeus_login
|
||||||
|
|
||||||
login_manager = LoginManager()
|
auth_bp = Blueprint('auth_bp', __name__)
|
||||||
login_manager.init_app(app)
|
|
||||||
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
def init_login(app):
|
||||||
def load_user(userid):
|
@app.login_manager.user_loader
|
||||||
|
def load_user(userid):
|
||||||
return User.query.filter_by(id=userid).first()
|
return User.query.filter_by(id=userid).first()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login')
|
@auth_bp.route('/login')
|
||||||
def login():
|
def login():
|
||||||
return zeus_login()
|
return zeus_login()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logout')
|
@auth_bp.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
if 'zeus_token' in session:
|
if 'zeus_token' in session:
|
||||||
session.pop('zeus_token', None)
|
session.pop('zeus_token', None)
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(url_for('home'))
|
return redirect(url_for('general_bp.home'))
|
||||||
|
|
||||||
|
|
||||||
def before_request():
|
def before_request():
|
||||||
|
|
|
@ -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 ###
|
|
|
@ -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 ###
|
|
|
@ -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 ###
|
|
|
@ -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 ###
|
|
|
@ -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 ###
|
|
|
@ -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 ###
|
|
173
app/models.py
173
app/models.py
|
@ -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
16
app/models/__init__.py
Normal 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
|
17
app/models/anonymous_user.py
Normal file
17
app/models/anonymous_user.py
Normal 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
3
app/models/database.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
20
app/models/location.py
Normal file
20
app/models/location.py
Normal 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
61
app/models/order.py
Normal 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
48
app/models/orderitem.py
Normal 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
20
app/models/product.py
Normal 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
36
app/models/user.py
Normal 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
47
app/notification.py
Normal 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
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from app import create_app
|
||||||
|
|
||||||
INTERP = os.path.expanduser("~/env/bin/python")
|
INTERP = os.path.expanduser("~/env/bin/python")
|
||||||
if sys.executable != INTERP:
|
if sys.executable != INTERP:
|
||||||
|
@ -9,4 +11,8 @@ if sys.executable != INTERP:
|
||||||
|
|
||||||
sys.path.append(os.getcwd())
|
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)
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<h1>Unauthorized</h1>
|
<h1>Unauthorized</h1>
|
||||||
<p>You're not authorized to look to this page!</p>
|
<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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -4,6 +4,6 @@
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<h1>Page Not Found</h1>
|
<h1>Page Not Found</h1>
|
||||||
<p>What you were looking for is just not there.</p>
|
<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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -2,12 +2,12 @@
|
||||||
{% import "bootstrap/utils.html" as utils %}
|
{% import "bootstrap/utils.html" as utils %}
|
||||||
|
|
||||||
{% set navbar = [
|
{% set navbar = [
|
||||||
('home', 'Home'),
|
('general_bp.home', 'Home'),
|
||||||
('order_bp.orders', 'Orders'),
|
('order_bp.orders', 'Orders'),
|
||||||
('locations', 'Locations'),
|
('general_bp.locations', 'Locations'),
|
||||||
('map', 'Map'),
|
('general_bp.map', 'Map'),
|
||||||
('about', 'About'),
|
('general_bp.about', 'About'),
|
||||||
('stats', 'Stats'),
|
('stats_blueprint.stats', 'Stats'),
|
||||||
] -%}
|
] -%}
|
||||||
{% if not current_user.is_anonymous() and current_user.is_admin() -%}
|
{% if not current_user.is_anonymous() and current_user.is_admin() -%}
|
||||||
{% set navbar = navbar + [('admin.index', '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>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</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>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
|
@ -55,10 +55,10 @@ Haldis - {{ active_page|capitalize }}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{% if current_user.is_anonymous() %}
|
{% 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 %}
|
{% else %}
|
||||||
<li><a href="{{ url_for('profile') }}">{{ current_user.username }}</a></li>
|
<li><a href="{{ url_for('general_bp.profile') }}">{{ current_user.username }}</a></li>
|
||||||
<li><a href="{{ url_for('logout') }}">Logout</a></li>
|
<li><a href="{{ url_for('auth_bp.logout') }}">Logout</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for loc in locations -%}
|
{% for loc in locations -%}
|
||||||
<tr>
|
<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>{{ loc.address }}<td>
|
||||||
<td><a href="{{ loc.website}}"><span class="glyphicon glyphicon-link"></span></a></td>
|
<td><a href="{{ loc.website}}"><span class="glyphicon glyphicon-link"></span></a></td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
loc = {
|
loc = {
|
||||||
"address": "{{loc.address}}",
|
"address": "{{loc.address}}",
|
||||||
"name": "{{loc.name}}",
|
"name": "{{loc.name}}",
|
||||||
"url": "{{ url_for('location', id=loc.id) }}"
|
"url": "{{ url_for('general_bp.location', id=loc.id) }}"
|
||||||
};
|
};
|
||||||
locations.push(loc);
|
locations.push(loc);
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,14 @@
|
||||||
<h3 id="order-title">Order {{ order.id }}
|
<h3 id="order-title">Order {{ order.id }}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if order.can_close(current_user.id) -%}
|
{% 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 %}
|
{% 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 %}
|
{%- endif %}
|
||||||
</div></h3>
|
</div></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() %}
|
||||||
<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 %}
|
{% 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/>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
{% if form -%}
|
{% if form -%}
|
||||||
<div class="col-md-push-1 col-md-10 darker" id="form">
|
<div class="col-md-push-1 col-md-10 darker" id="form">
|
||||||
<h4>Order:</h4>
|
<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">
|
<span class="pull-right">
|
||||||
<a class="btn btn-primary" onclick="chooseRandom()">Choose for me</a>
|
<a class="btn btn-primary" onclick="chooseRandom()">Choose for me</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -76,8 +76,8 @@
|
||||||
<td>{{ item.get_name() }}</td>
|
<td>{{ item.get_name() }}</td>
|
||||||
<td><span title="{{ item.extra if item.extra }}">{{ item.product.name }}{{ "*" if item.extra }}</span></td>
|
<td><span title="{{ item.extra if item.extra }}">{{ item.product.name }}{{ "*" if item.extra }}</span></td>
|
||||||
<td>{{ item.product.price|euro }}</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 %}
|
{% 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('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/></td>
|
<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>
|
</tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-push-2 col-md-4 darker box" id="items-ordered">
|
<div class="col-md-push-2 col-md-4 darker box" id="items-ordered">
|
||||||
<h3>Ordered products: {{ order.items.count() }}</h3>
|
<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() -%}
|
{% for key, value in order.group_by_product().items() -%}
|
||||||
<div class="product">
|
<div class="product">
|
||||||
{{ key }}: {{ value["count"] }}
|
{{ key }}: {{ value["count"] }}
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
<td>{{ key }}</td>
|
<td>{{ key }}</td>
|
||||||
<td>{{ value["total"]|euro }}</td>
|
<td>{{ value["total"]|euro }}</td>
|
||||||
<td>{{ value["to_pay"]|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>
|
</tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<h3>Create new order:</h3>
|
<h3>Create new order:</h3>
|
||||||
<div class="row darker">
|
<div class="row darker">
|
||||||
<div class="col-sm-12">
|
<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 }}
|
{{ form.csrf_token }}
|
||||||
<div class="form-group select2 {{ 'has-errors' if form.courrier_id.errors else ''}}">
|
<div class="form-group select2 {{ 'has-errors' if form.courrier_id.errors else ''}}">
|
||||||
{{ form.courrier_id.label(class='control-label') }}<br>
|
{{ form.courrier_id.label(class='control-label') }}<br>
|
||||||
|
|
61
app/utils.py
61
app/utils.py
|
@ -1,57 +1,6 @@
|
||||||
from datetime import datetime
|
def euro_string(value):
|
||||||
from flask import render_template
|
"""
|
||||||
|
Convert cents to string formatted euro
|
||||||
from app import app
|
"""
|
||||||
from login import login_manager
|
result = '€ {:.2f}'.format(round(value / 100, 2))
|
||||||
|
|
||||||
__author__ = 'feliciaan'
|
|
||||||
|
|
||||||
@app.template_filter('euro')
|
|
||||||
def euro(value):
|
|
||||||
result = '€ {:.2f}'.format(round(value/100,2))
|
|
||||||
return result
|
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
|
|
|
@ -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
29
app/views/debug.py
Normal 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
67
app/views/general.py
Normal 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')
|
|
@ -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
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from app import app, db
|
# from flask import current_app as app
|
||||||
from models import Order, OrderItem, User
|
from flask import (Blueprint, abort, flash, redirect, render_template, request,
|
||||||
from forms import OrderItemForm, OrderForm, AnonOrderItemForm
|
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 = Blueprint('order_bp', 'order')
|
||||||
|
|
||||||
|
|
||||||
@order_bp.route('/')
|
@order_bp.route('/')
|
||||||
def orders(form=None):
|
def orders(form=None):
|
||||||
if form is None and not current_user.is_anonymous():
|
if form is None and not current_user.is_anonymous():
|
||||||
|
@ -35,7 +35,7 @@ def order_create():
|
||||||
db.session.add(order)
|
db.session.add(order)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
post_order_to_webhook(order)
|
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)
|
return orders(form=orderForm)
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,13 +48,19 @@ def order(id, form=None):
|
||||||
flash('Please login to see this order.', 'info')
|
flash('Please login to see this order.', 'info')
|
||||||
abort(401)
|
abort(401)
|
||||||
if form is None:
|
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)
|
form.populate(order.location)
|
||||||
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])
|
||||||
debts = sum([o.product.price for o in order.items if not o.paid])
|
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')
|
@order_bp.route('/<id>/items')
|
||||||
def items_showcase(id, form=None):
|
def items_showcase(id, form=None):
|
||||||
|
@ -66,11 +72,13 @@ def items_showcase(id, form=None):
|
||||||
abort(401)
|
abort(401)
|
||||||
return render_template('order_items.html', order=order)
|
return render_template('order_items.html', order=order)
|
||||||
|
|
||||||
|
|
||||||
@order_bp.route('/<id>/edit', methods=['GET', 'POST'])
|
@order_bp.route('/<id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def order_edit(id):
|
def order_edit(id):
|
||||||
order = Order.query.filter(Order.id == id).first()
|
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)
|
abort(401)
|
||||||
if order is None:
|
if order is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
@ -79,7 +87,7 @@ def order_edit(id):
|
||||||
if orderForm.validate_on_submit():
|
if orderForm.validate_on_submit():
|
||||||
orderForm.populate_obj(order)
|
orderForm.populate_obj(order)
|
||||||
db.session.commit()
|
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)
|
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:
|
if current_user.is_anonymous() and not current_order.public:
|
||||||
flash('Please login to see this order.', 'info')
|
flash('Please login to see this order.', 'info')
|
||||||
abort(401)
|
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)
|
form.populate(current_order.location)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
item = OrderItem()
|
item = OrderItem()
|
||||||
|
@ -106,7 +115,7 @@ def order_item_create(id):
|
||||||
db.session.add(item)
|
db.session.add(item)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Ordered %s' % (item.product.name), 'success')
|
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)
|
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:
|
if item.order.courrier_id == id or current_user.admin:
|
||||||
item.paid = True
|
item.paid = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Paid %s by %s' % (item.product.name, item.get_name()), 'success')
|
flash('Paid %s by %s' % (item.product.name, item.get_name()),
|
||||||
return redirect(url_for('.order', id=order_id))
|
'success')
|
||||||
|
return redirect(url_for('order_bp.order', id=order_id))
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,9 +139,11 @@ def items_user_paid(order_id, user_name):
|
||||||
user = User.query.filter(User.username == user_name).first()
|
user = User.query.filter(User.username == user_name).first()
|
||||||
items = []
|
items = []
|
||||||
if user:
|
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:
|
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()
|
current_order = Order.query.filter(Order.id == order_id).first()
|
||||||
for item in items:
|
for item in items:
|
||||||
print(item)
|
print(item)
|
||||||
|
@ -139,8 +151,9 @@ def items_user_paid(order_id, user_name):
|
||||||
for item in items:
|
for item in items:
|
||||||
item.paid = True
|
item.paid = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Paid %d items for %s' % (items.count(), item.get_name()), 'success')
|
flash('Paid %d items for %s' % (items.count(), item.get_name()),
|
||||||
return redirect(url_for('.order', id=order_id))
|
'success')
|
||||||
|
return redirect(url_for('order_bp.order', id=order_id))
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,7 +169,7 @@ def delete_item(order_id, item_id):
|
||||||
db.session.delete(item)
|
db.session.delete(item)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Deleted %s' % (product_name), 'success')
|
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)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,7 +185,7 @@ def volunteer(id):
|
||||||
flash("Thank you for volunteering!")
|
flash("Thank you for volunteering!")
|
||||||
else:
|
else:
|
||||||
flash("Volunteering not possible!")
|
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')
|
@order_bp.route('/<id>/close')
|
||||||
|
@ -190,9 +203,7 @@ def close_order(id):
|
||||||
if courrier is not None:
|
if courrier is not None:
|
||||||
order.courrier_id = courrier.id
|
order.courrier_id = courrier.id
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('.order', id=id))
|
return redirect(url_for('order_bp.order', id=id))
|
||||||
|
|
||||||
app.register_blueprint(order_bp, url_prefix='/order')
|
|
||||||
|
|
||||||
|
|
||||||
def select_user(items):
|
def select_user(items):
|
||||||
|
@ -216,52 +227,12 @@ def select_user(items):
|
||||||
def get_orders(expression=None):
|
def get_orders(expression=None):
|
||||||
orders = []
|
orders = []
|
||||||
if expression is None:
|
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():
|
if not current_user.is_anonymous():
|
||||||
orders = Order.query.filter(expression).all()
|
orders = Order.query.filter(expression).all()
|
||||||
else:
|
else:
|
||||||
orders = Order.query.filter((expression & (Order.public == True))).all()
|
orders = Order.query.filter(
|
||||||
|
(expression & (Order.public == True))).all()
|
||||||
return orders
|
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
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
from flask import current_app as app
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser, FatProduct
|
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatProduct, FatUser
|
||||||
from app import app
|
|
||||||
|
stats_blueprint = Blueprint('stats_blueprint', __name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/stats/')
|
@stats_blueprint.route('/')
|
||||||
def stats():
|
def stats():
|
||||||
data = {
|
data = {
|
||||||
'amount': {
|
'amount': {
|
||||||
|
|
59
app/zeus.py
59
app/zeus.py
|
@ -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_login import login_user
|
||||||
from flask_oauthlib.client import OAuth, OAuthException
|
from flask_oauthlib.client import OAuthException, OAuth
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
from models import User, db
|
||||||
|
|
||||||
from app import app, db
|
oauth_bp = Blueprint("oauth_bp", __name__)
|
||||||
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/'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def zeus_login():
|
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():
|
def authorized():
|
||||||
resp = zeus.authorized_response()
|
resp = current_app.zeus.authorized_response()
|
||||||
if resp is None:
|
if resp is None:
|
||||||
return 'Access denied: reason=%s error=%s' % (
|
return 'Access denied: reason=%s error=%s' % (
|
||||||
request.args['error'],
|
request.args['error'], request.args['error_description'])
|
||||||
request.args['error_description']
|
|
||||||
)
|
|
||||||
if isinstance(resp, OAuthException):
|
if isinstance(resp, OAuthException):
|
||||||
return 'Access denied: %s' % resp.message + '<br>' + str(resp.data)
|
return 'Access denied: %s' % resp.message + '<br>' + str(resp.data)
|
||||||
|
|
||||||
session['zeus_token'] = (resp['access_token'], '')
|
session['zeus_token'] = (resp['access_token'], '')
|
||||||
me = zeus.get('current_user/')
|
me = current_app.zeus.get('current_user/')
|
||||||
username = me.data.get('username', '').lower()
|
username = me.data.get('username', '').lower()
|
||||||
|
|
||||||
user = User.query.filter_by(username=username).first()
|
user = User.query.filter_by(username=username).first()
|
||||||
|
@ -49,17 +34,31 @@ def authorized():
|
||||||
return login_and_redirect_user(user)
|
return login_and_redirect_user(user)
|
||||||
|
|
||||||
flash("You're not allowed to enter, please contact a system administrator")
|
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
|
zeus = oauth.remote_app(
|
||||||
def get_zeus_oauth_token():
|
'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 session.get('zeus_token')
|
||||||
|
|
||||||
|
return zeus
|
||||||
|
|
||||||
|
|
||||||
def login_and_redirect_user(user):
|
def login_and_redirect_user(user):
|
||||||
login_user(user)
|
login_user(user)
|
||||||
return redirect(url_for("home"))
|
return redirect(url_for("general_bp.home"))
|
||||||
|
|
||||||
|
|
||||||
def create_user(username):
|
def create_user(username):
|
||||||
|
|
|
@ -29,4 +29,4 @@ cd ..
|
||||||
echo -e "${B} Seeding database ${E}"
|
echo -e "${B} Seeding database ${E}"
|
||||||
./populate-db.sh
|
./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}"
|
Loading…
Reference in a new issue