Restructuring the codebase!
But for real, it's a real shitstorm in there. - Added context by making the init go through a function and not implicitly happen via imports - Fixup all context and contextless function mixups - splitup the models in sensible different files - give the dump of view functions in views/__init__.py their own file - add all routes via blueprints, not half of them - move the slack notifications function and class to its own file, no idea what it was doing in a views file in the first place.
This commit is contained in:
parent
acee8c6b43
commit
d0863325a5
30 changed files with 647 additions and 531 deletions
|
@ -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.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))
|
||||
|
|
166
app/app.py
166
app/app.py
|
@ -1,48 +1,148 @@
|
|||
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
|
||||
|
||||
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 app
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# 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 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='/')
|
||||
|
||||
|
||||
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__':
|
||||
app = create_app()
|
||||
app.run(threaded=True, host='0.0.0.0')
|
||||
|
|
25
app/forms.py
25
app/forms.py
|
@ -1,20 +1,25 @@
|
|||
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
|
||||
from models import Location, User
|
||||
from utils import euro_string
|
||||
|
||||
__author__ = 'feliciaan'
|
||||
|
||||
|
||||
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 +28,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 +43,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):
|
||||
|
|
|
@ -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()
|
23
app/login.py
23
app/login.py
|
@ -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():
|
||||
|
|
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')
|
39
app/models/user.py
Normal file
39
app/models/user.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
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):
|
||||
try:
|
||||
return unicode(self.id) # python 2
|
||||
except NameError:
|
||||
return str(self.id) # python 3
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
61
app/utils.py
61
app/utils.py
|
@ -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
|
|
@ -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
|
90
app/views/general.py
Normal file
90
app/views/general.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
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')
|
||||
|
||||
|
||||
@general_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
|
|
@ -1,18 +1,19 @@
|
|||
__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 +36,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 +49,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 +73,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 +88,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 +102,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 +116,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 +128,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 +140,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 +152,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 +170,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 +186,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 +204,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 +228,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
|
||||
|
|
|
@ -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': {
|
||||
|
|
61
app/zeus.py
61
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_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):
|
||||
|
|
Loading…
Reference in a new issue