From 118ba56584d3b254ae1fad0b4ab9df6259e0a22e Mon Sep 17 00:00:00 2001 From: Tom Naessens Date: Wed, 25 Mar 2015 18:06:03 +0100 Subject: [PATCH 01/68] Initial commit --- .gitignore | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 22 ++++++++++++++++++++++ README.md | 1 + 3 files changed, 77 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db4561e --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e887355 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Zeus WPI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cb15ae --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Foodbot From f57b636e3ef480a1295b376d43bf0999b54a4c29 Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 00:17:43 +0100 Subject: [PATCH 02/68] Initial commit --- app/FoodBot.py | 40 +++++++++++++++++ app/README.md | 7 +++ app/static/css/main.css | 90 +++++++++++++++++++++++++++++++++++++++ app/templates/about.html | 6 +++ app/templates/home.html | 7 +++ app/templates/layout.html | 30 +++++++++++++ app/templates/login.html | 24 +++++++++++ app/templates/stats.html | 6 +++ db/muhscheme | 15 +++++++ 9 files changed, 225 insertions(+) create mode 100644 app/FoodBot.py create mode 100644 app/README.md create mode 100644 app/static/css/main.css create mode 100644 app/templates/about.html create mode 100644 app/templates/home.html create mode 100644 app/templates/layout.html create mode 100644 app/templates/login.html create mode 100644 app/templates/stats.html create mode 100644 db/muhscheme diff --git a/app/FoodBot.py b/app/FoodBot.py new file mode 100644 index 0000000..6996bf7 --- /dev/null +++ b/app/FoodBot.py @@ -0,0 +1,40 @@ +# import sqlite3 +from flask import Flask, request, render_template + +# Config +DATABASE = '/db/foodbot.db' +DEBUG = True +SECRET_KEY = 'development key' +USERNAME = 'admin' +PASSWORD = 'tetten' + +app = Flask(__name__) +app.config.from_object(__name__) + + +@app.route('/') +def home(): + return render_template('home.html') + + +@app.route('/about/') +def about(): + return render_template('about.html') + + +@app.route('/stats/') +def stats(): + return render_template('stats.html') + + +@app.route('/login/', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + pass + else: + return render_template('login.html') + + +if __name__ == '__main__': + app.run(debug=True) + # app.run(host='0.0.0.0') \ No newline at end of file diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..24eb140 --- /dev/null +++ b/app/README.md @@ -0,0 +1,7 @@ +FOODBOT +======= + +FoodBot exists so lazy fucks like you and me don't need to keep tabs of who is ordering what from where. +Start an order and let people add items with a simple mouse-click! +No more calculating prices and making lists! +Be lazier today! \ No newline at end of file diff --git a/app/static/css/main.css b/app/static/css/main.css new file mode 100644 index 0000000..3c7e9c1 --- /dev/null +++ b/app/static/css/main.css @@ -0,0 +1,90 @@ +body { + margin: 0; + padding: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #444; +} + +/* + * Create dark grey header with a white logo + */ + +header { + background-color: #2B2B2B; + height: 35px; + width: 100%; + opacity: .9; + margin-bottom: 10px; +} + +header h1.logo { + margin: 0; + font-size: 1.7em; + color: #fff; + text-transform: uppercase; + float: left; +} + +header h1.logo:hover { + color: #fff; + text-decoration: none; +} + +/* + * Center the body content + */ + +.container { + width: 940px; + margin: 0 auto; +} + +div.jumbo { + padding: 10px 0 30px 0; + background-color: #eeeeee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +div.login { + padding: 10px 0 30px 0; + background-color: #eeeeee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +h2 { + font-size: 3em; + margin-top: 40px; + text-align: center; + letter-spacing: -2px; +} + +h3 { + font-size: 1.7em; + font-weight: 100; + margin-top: 30px; + text-align: center; + letter-spacing: -1px; + color: #999; +} + +.menu { + float: right; + margin-top: 8px; +} + +.menu li { + display: inline; +} + +.menu li + li { + margin-left: 35px; +} + +.menu li a { + color: #999; + text-decoration: none; +} \ No newline at end of file diff --git a/app/templates/about.html b/app/templates/about.html new file mode 100644 index 0000000..a36d7c4 --- /dev/null +++ b/app/templates/about.html @@ -0,0 +1,6 @@ +{% extends "layout.html" %} + +{% block content %} +

About

+

This is an About page for FoodBot. Don't I look good? Oh stop, you're making me blush.

+{% endblock %} \ No newline at end of file diff --git a/app/templates/home.html b/app/templates/home.html new file mode 100644 index 0000000..1e0bf08 --- /dev/null +++ b/app/templates/home.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} +{% block content %} +
+

Welcome to FoodBot

+

This is the home page for FoodBot

+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/layout.html b/app/templates/layout.html new file mode 100644 index 0000000..03685c6 --- /dev/null +++ b/app/templates/layout.html @@ -0,0 +1,30 @@ + + + + + FoodBot + + + + +
+
+

FOODBOT

+ +
+
+ +
+ {% block content %} + {% endblock %} +
+ + + \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..940493e --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,24 @@ +{% extends "layout.html" %} + +{% block content %} +
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/stats.html b/app/templates/stats.html new file mode 100644 index 0000000..553a0d1 --- /dev/null +++ b/app/templates/stats.html @@ -0,0 +1,6 @@ +{% extends "layout.html" %} + +{% block content %} +

Stats bruh

+

TOP 4

+{% endblock %} \ No newline at end of file diff --git a/db/muhscheme b/db/muhscheme new file mode 100644 index 0000000..d5be7c2 --- /dev/null +++ b/db/muhscheme @@ -0,0 +1,15 @@ +ORDERS +======= +OrderID | Courier | Locatie | Starttijdstip | Eindtijstip | Comment + +Item +===== +ItemID | OrderID | User | Food + +Locaties +====== +LocatieID | Naam | Adres | Telefoonnummer + +Food +===== +FoodID | LocatieID | Naam | Prijs \ No newline at end of file From 258a7ab9927c62c9bf383057bee2e409263a7a96 Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 00:21:25 +0100 Subject: [PATCH 03/68] LeesMij --- README.md | 8 +++++++- app/README.md | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 app/README.md diff --git a/README.md b/README.md index 6cb15ae..24eb140 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# Foodbot +FOODBOT +======= + +FoodBot exists so lazy fucks like you and me don't need to keep tabs of who is ordering what from where. +Start an order and let people add items with a simple mouse-click! +No more calculating prices and making lists! +Be lazier today! \ No newline at end of file diff --git a/app/README.md b/app/README.md deleted file mode 100644 index 24eb140..0000000 --- a/app/README.md +++ /dev/null @@ -1,7 +0,0 @@ -FOODBOT -======= - -FoodBot exists so lazy fucks like you and me don't need to keep tabs of who is ordering what from where. -Start an order and let people add items with a simple mouse-click! -No more calculating prices and making lists! -Be lazier today! \ No newline at end of file From cf9fcfbad3a7f815d03651bbd1e8bf6c7e27601d Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 21:45:27 +0100 Subject: [PATCH 04/68] Lol did not test this --- app/FoodBot.py | 40 ---------------------------------------- app/foodput.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 40 deletions(-) delete mode 100644 app/FoodBot.py create mode 100644 app/foodput.py diff --git a/app/FoodBot.py b/app/FoodBot.py deleted file mode 100644 index 6996bf7..0000000 --- a/app/FoodBot.py +++ /dev/null @@ -1,40 +0,0 @@ -# import sqlite3 -from flask import Flask, request, render_template - -# Config -DATABASE = '/db/foodbot.db' -DEBUG = True -SECRET_KEY = 'development key' -USERNAME = 'admin' -PASSWORD = 'tetten' - -app = Flask(__name__) -app.config.from_object(__name__) - - -@app.route('/') -def home(): - return render_template('home.html') - - -@app.route('/about/') -def about(): - return render_template('about.html') - - -@app.route('/stats/') -def stats(): - return render_template('stats.html') - - -@app.route('/login/', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - pass - else: - return render_template('login.html') - - -if __name__ == '__main__': - app.run(debug=True) - # app.run(host='0.0.0.0') \ No newline at end of file diff --git a/app/foodput.py b/app/foodput.py new file mode 100644 index 0000000..bc1a230 --- /dev/null +++ b/app/foodput.py @@ -0,0 +1,15 @@ +from app import app, db + +from admin import admin +from login import login_manager +from models import * +from utils import start_process +from views import * + + +if __name__ == '__main__': + app.run() + +if __name__ == '__main__': + app.run(debug=True) + # app.run(host='0.0.0.0') From eba3fc5f07b59536c41ccdeee5dea735f2856128 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 21:49:14 +0100 Subject: [PATCH 05/68] forgot to add some files --- app/admin.py | 28 +++++++++ app/app.py | 13 +++++ app/config.example.py | 13 +++++ app/config.py | 13 +++++ app/create_database.py | 18 ++++++ app/{foodput.py => foodbot.py} | 0 app/login.py | 34 +++++++++++ app/models.py | 103 +++++++++++++++++++++++++++++++++ app/requirements.txt | 16 +++++ app/views.py | 43 ++++++++++++++ app/zeus.py | 93 +++++++++++++++++++++++++++++ 11 files changed, 374 insertions(+) create mode 100644 app/admin.py create mode 100644 app/app.py create mode 100644 app/config.example.py create mode 100644 app/config.py create mode 100644 app/create_database.py rename app/{foodput.py => foodbot.py} (100%) create mode 100644 app/login.py create mode 100644 app/models.py create mode 100644 app/requirements.txt create mode 100644 app/views.py create mode 100644 app/zeus.py diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 0000000..a27c1ac --- /dev/null +++ b/app/admin.py @@ -0,0 +1,28 @@ +from flask import url_for, redirect +from flask.ext.admin import Admin, BaseView, expose +from flask.ext.admin.contrib.sqla import ModelView +from flask.ext import login + + +from app import app, db +from models import User +from utils import send_command + + +class ModelBaseView(ModelView): + + def is_accessible(self): + if login.current_user.is_anonymous(): + return False + + return login.current_user.is_admin() + + +class UserAdminModel(ModelBaseView): + column_searchable_list = ('username',) + inline_models = None + form_columns = ('username', 'admin') + +admin = Admin(app, name='FoodBot', url='/foodbot/admin', template_mode='bootstrap3') + +admin.add_view(UserAdminModel(User, db.session)) diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..dba2aa0 --- /dev/null +++ b/app/app.py @@ -0,0 +1,13 @@ +from flask import Flask +from flask.ext.sqlalchemy import SQLAlchemy +from werkzeug.contrib.cache import SimpleCache + + +from logbook import Logger + +app = Flask(__name__) +app.config.from_object('config.Configuration') + +db = SQLAlchemy(app) + +logger = Logger('FoodBot-Web') diff --git a/app/config.example.py b/app/config.example.py new file mode 100644 index 0000000..714e80a --- /dev/null +++ b/app/config.example.py @@ -0,0 +1,13 @@ +# config + + +class Configuration(object): + SQLALCHEMY_DATABASE_URI = 'sqlite:///foodbot.db' + DEBUG = True + SECRET_KEY = '' + SLACK_WEBHOOK = '' + PROCESS = 'python test.py' + LOGFILE = 'slotmachien.log' + ZEUS_KEY = '' + ZEUS_SECRET = '' + SLACK_TOKEN = '' diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..42a1713 --- /dev/null +++ b/app/config.py @@ -0,0 +1,13 @@ +# config + + +class Configuration(object): + SQLALCHEMY_DATABASE_URI = 'sqlite:///foodbot.db' + DEBUG = True + SECRET_KEY = '' + SLACK_WEBHOOK = '' + PROCESS = 'python test.py' + LOGFILE = 'slotmachien.log' + ZEUS_KEY = 'tomtest' + ZEUS_SECRET = 'blargh' + SLACK_TOKEN = 'xoxp-2484654576-2486580711-4114448516-f21087' diff --git a/app/create_database.py b/app/create_database.py new file mode 100644 index 0000000..c8b86bd --- /dev/null +++ b/app/create_database.py @@ -0,0 +1,18 @@ +from models import * +from app import db + +db.drop_all() +db.create_all() + +feli = User() +feli.configure("feliciaan", True, True) +db.session.add(feli) + +don = User() +don.configure("don", True, True) +db.session.add(don) + +# To future developers, add yourself here + +# commit all the things +db.session.commit() diff --git a/app/foodput.py b/app/foodbot.py similarity index 100% rename from app/foodput.py rename to app/foodbot.py diff --git a/app/login.py b/app/login.py new file mode 100644 index 0000000..419a942 --- /dev/null +++ b/app/login.py @@ -0,0 +1,34 @@ +from flask import redirect, request, url_for, abort, session +from flask.ext.login import LoginManager, current_user, logout_user +from flask_oauthlib.client import OAuth + +import requests + +from app import app, db, logger, cache +from models import User +from zeus import oauth, zeus_login + +login_manager = LoginManager() +login_manager.init_app(app) + + +@login_manager.user_loader +def load_user(userid): + return User.query.filter_by(id=userid).first() + +@app.route('/foodbot/login') +def login(): + return zeus_login() + + +@app.route('/foodbot/logout') +def logout(): + if 'zeus_token' in session: + session.pop('zeus_token', None) + logout_user() + return redirect(url_for('admin.index')) + + +def before_request(): + if current_user.is_anonymous() or not current_user.is_allowed(): + abort(401) diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..01cfc41 --- /dev/null +++ b/app/models.py @@ -0,0 +1,103 @@ +import uuid +from datetime import date, timedelta + + +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) + admin = db.Column(db.Boolean) + bias = db.Column(db.Integer) + courrier = db.relationship('Courrier', backref='courrier', lazy='dynamic') + logactions = db.relationship('LogAction', backref='user', lazy='dynamic') + + def configure(self, username, allowed, admin, bias): + self.username = username + self.allowed = allowed + 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)) + address = db.Column(db.String(254)) + website = db.Column(db.String(120)) + food = db.relationship('Food', backref='location', lazy='dynamic') + + def configure(self, name, address, website): + self.name = name + self.address = address + self.website = website + + def __repr__(self): + return '%s: %s' % (self.name, self.address) + +class Food(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)) + price = db.Column(db.Integer) + + def configure(self, location, name, price): + self.location = location + self.name = name + self.price = price + + def __repr__(self): + return '%s' % self.name + +class Order(db.Model): + id = db.Column(db.Integer, primary_key=True) + courrier_id = db.Column(db.Integer, db.ForeignKey('user.id')) + location_id = db.Column(db.Integer, db.ForeignKey('location.id')) + starttime = db.Column(db.DateTime) + stoptime = db.Column(db.DateTime) + orders = db.relationship('OrdreItem', 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): + return 'Order' + +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')) + food_id = db.Column(db.Integer, db.ForeignKey('food.id')) + + def configure(self, user, order, food): + self.user = user + self.order = order + self.food = food + + def __repr__(self): + return 'OrderItem' diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..45bc6f9 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,16 @@ +Flask==0.10.1 +Flask-Admin==1.0.9 +Flask-Login==0.2.11 +Flask-SQLAlchemy==2.0 +Flask-WTF==0.10.3 +Jinja2==2.7.2 +Logbook==0.8.1 +MarkupSafe==0.23 +SQLAlchemy==0.9.8 +WTForms==2.0 +Werkzeug==0.9.6 +flup==1.0.2 +itsdangerous==0.24 +requests==2.4.0 +Flask-OAuthlib==0.8.0 +oauthlib==0.7.2 diff --git a/app/views.py b/app/views.py new file mode 100644 index 0000000..03a7692 --- /dev/null +++ b/app/views.py @@ -0,0 +1,43 @@ +from flask import Blueprint, request, jsonify, redirect, url_for + + +from app import app +from login import before_request + + +@app.route('/') +def home(): + return render_template('home.html') + + +@app.route('/about/') +def about(): + return render_template('about.html') + + +@app.route('/stats/') +def stats(): + return render_template('stats.html') + + +if app.debug: # add route information + @app.route('/routes') + def list_routes(self): + import urllib + output = [] + for rule in app.url_map.iter_rules(): + options = {} + for arg in rule.arguments: + options[arg] = "[{0}]".format(arg) + + methods = ','.join(rule.methods) + url = url_for(rule.endpoint, **options) + line = urllib.unquote( + "{:50s} {:20s} {}".format(rule.endpoint, methods, url)) + output.append(line) + + string = '' + for line in sorted(output): + string += line + "
" + + return string diff --git a/app/zeus.py b/app/zeus.py new file mode 100644 index 0000000..aed492d --- /dev/null +++ b/app/zeus.py @@ -0,0 +1,93 @@ +from flask import Flask, redirect, url_for, session, request, jsonify, flash, request +from flask.ext.login import LoginManager, login_user, current_user, logout_user +from flask.ext.admin import helpers +from flask_oauthlib.client import OAuth, OAuthException +import json +import requests + + +from app import app, db +from models import User, Token + +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='http://kelder.zeus.ugent.be/oauth/api/', + access_token_method='POST', + access_token_url='https://kelder.zeus.ugent.be/oauth/oauth2/token/', + authorize_url='https://kelder.zeus.ugent.be/oauth/oauth2/authorize/' +) + + +def zeus_login(): + if app.debug: + return zeus.authorize(callback=url_for('authorized', _external=True)) + else: # temporary solution because it otherwise gives trouble on the pi because of proxies and such + return zeus.authorize(callback='http://zeus.ugent.be/foodbot/login/zeus/authorized') + + +@app.route('/slotmachien/login/zeus/authorized') +def authorized(): + resp = zeus.authorized_response() + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error'], + request.args['error_description'] + ) + if isinstance(resp, OAuthException): + return 'Access denied: %s' % resp.message + '
' + str(resp.data) + + session['zeus_token'] = (resp['access_token'], '') + me = zeus.get('current_user/') + username = me.data.get('username', '').lower() + + user = User.query.filter_by(username=username).first() + if len(username) > 0 and user: + return login_and_redirect_user(user) + elif len(username) > 0: + user = create_user(username) + return login_and_redirect_user(user) + + flash("You're not allowed to enter, please contact a system administrator") + return redirect(url_for("admin.index")) + + +@zeus.tokengetter +def get_zeus_oauth_token(): + return session.get('zeus_token') + + +def login_and_redirect_user(user): + login_user(user) + # add_token(resp['access_token'], user) + content_type = request.headers.get('Content-Type', None) + if content_type and content_type in 'application/json': + token = add_token(user) + return jsonify({'token': token.token}) + return redirect(url_for("admin.index")) + + +def add_token(user): + token = Token() + token.configure(user) + db.session.add(token) + db.session.commit() + return token + + +def create_user(username): + user = User() + user.configure(username, False) + db.session.add(user) + db.session.commit() + # EASTER EGG + text = 'Welcome ' + username + '!' + js = json.dumps({'text': text}) + url = app.config['SLACK_WEBHOOK'] + if len(url) > 0: + requests.post(url, data=js) + return user From cc62911509bf75a9a42344fbd2782fc80391c42c Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 21:54:43 +0100 Subject: [PATCH 06/68] LoL windoos --- .gitignore | 11 +++++++++-- app/templates/login.html | 38 +++++++++++++++++++------------------- db/muhscheme | 10 +++++++--- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index db4561e..8e88017 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,7 @@ var/ pip-log.txt pip-delete-this-directory.txt -# Unit test / coverage reports -htmlcov/ +# Unit test / coverage reportshtmlcov/ .tox/ .coverage .cache @@ -52,3 +51,11 @@ docs/_build/ # PyBuilder target/ + +# PyCharm +.idea/ + +# ConfigFile +app/config.py + + diff --git a/app/templates/login.html b/app/templates/login.html index 940493e..a70fb3d 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -1,24 +1,24 @@ {% extends "layout.html" %} {% block content %} -
- +
+ - -
+ +
{% endblock %} \ No newline at end of file diff --git a/db/muhscheme b/db/muhscheme index d5be7c2..e4358ee 100644 --- a/db/muhscheme +++ b/db/muhscheme @@ -1,14 +1,18 @@ ORDERS ======= -OrderID | Courier | Locatie | Starttijdstip | Eindtijstip | Comment +OrderID | User | LocatieID | Starttijdstip | Eindtijstip | Comment Item ===== -ItemID | OrderID | User | Food +ItemID | OrderID | User | FoodID Locaties ====== -LocatieID | Naam | Adres | Telefoonnummer +LocatieID | Naam | Adres | Coordinaten(2.0) | Website + +LocatieMetaData +=============== +LocatieID | Key | Value Food ===== From f9968e0654c42c4d93f5e28ab6048338858a184b Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 21:56:49 +0100 Subject: [PATCH 07/68] Removed config file --- app/config.example.py | 1 - app/config.py | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 app/config.py diff --git a/app/config.example.py b/app/config.example.py index 714e80a..3cdbb92 100644 --- a/app/config.example.py +++ b/app/config.example.py @@ -10,4 +10,3 @@ class Configuration(object): LOGFILE = 'slotmachien.log' ZEUS_KEY = '' ZEUS_SECRET = '' - SLACK_TOKEN = '' diff --git a/app/config.py b/app/config.py deleted file mode 100644 index 42a1713..0000000 --- a/app/config.py +++ /dev/null @@ -1,13 +0,0 @@ -# config - - -class Configuration(object): - SQLALCHEMY_DATABASE_URI = 'sqlite:///foodbot.db' - DEBUG = True - SECRET_KEY = '' - SLACK_WEBHOOK = '' - PROCESS = 'python test.py' - LOGFILE = 'slotmachien.log' - ZEUS_KEY = 'tomtest' - ZEUS_SECRET = 'blargh' - SLACK_TOKEN = 'xoxp-2484654576-2486580711-4114448516-f21087' From a17ffaf4a390525fc65f9831e5b5760d04c410ce Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 21:58:55 +0100 Subject: [PATCH 08/68] Remove reference to slotmachien --- app/config.example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config.example.py b/app/config.example.py index 3cdbb92..7dc6e02 100644 --- a/app/config.example.py +++ b/app/config.example.py @@ -7,6 +7,6 @@ class Configuration(object): SECRET_KEY = '' SLACK_WEBHOOK = '' PROCESS = 'python test.py' - LOGFILE = 'slotmachien.log' + LOGFILE = 'foodbot.log' ZEUS_KEY = '' ZEUS_SECRET = '' From b3b1d25bb5572556f7b4da896eb2f9a9c61908fc Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 21:59:51 +0100 Subject: [PATCH 09/68] Removed some more unnecsarry settings --- app/config.example.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/config.example.py b/app/config.example.py index 7dc6e02..07f5980 100644 --- a/app/config.example.py +++ b/app/config.example.py @@ -6,7 +6,6 @@ class Configuration(object): DEBUG = True SECRET_KEY = '' SLACK_WEBHOOK = '' - PROCESS = 'python test.py' LOGFILE = 'foodbot.log' ZEUS_KEY = '' ZEUS_SECRET = '' From 051588fba9673629546d54a56b87a232a2006ca8 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 22:11:58 +0100 Subject: [PATCH 10/68] updated thingies --- app/app.py | 2 -- app/config.example.py | 4 ++-- app/requirements.txt | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/app.py b/app/app.py index dba2aa0..09419eb 100644 --- a/app/app.py +++ b/app/app.py @@ -9,5 +9,3 @@ app = Flask(__name__) app.config.from_object('config.Configuration') db = SQLAlchemy(app) - -logger = Logger('FoodBot-Web') diff --git a/app/config.example.py b/app/config.example.py index 07f5980..7ca46e2 100644 --- a/app/config.example.py +++ b/app/config.example.py @@ -7,5 +7,5 @@ class Configuration(object): SECRET_KEY = '' SLACK_WEBHOOK = '' LOGFILE = 'foodbot.log' - ZEUS_KEY = '' - ZEUS_SECRET = '' + ZEUS_KEY = '' + ZEUS_SECRET = '' diff --git a/app/requirements.txt b/app/requirements.txt index 45bc6f9..029a996 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -4,7 +4,6 @@ Flask-Login==0.2.11 Flask-SQLAlchemy==2.0 Flask-WTF==0.10.3 Jinja2==2.7.2 -Logbook==0.8.1 MarkupSafe==0.23 SQLAlchemy==0.9.8 WTForms==2.0 From b69132b5b782f26629ae42563c59688c1d057e3e Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 22:17:50 +0100 Subject: [PATCH 11/68] Removed imports --- app/admin.py | 5 ++--- app/app.py | 3 --- app/foodbot.py | 6 ------ app/login.py | 8 +++----- app/models.py | 4 ---- app/views.py | 5 ++--- app/zeus.py | 22 ++++------------------ 7 files changed, 11 insertions(+), 42 deletions(-) diff --git a/app/admin.py b/app/admin.py index a27c1ac..335e78f 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,12 +1,11 @@ -from flask import url_for, redirect -from flask.ext.admin import Admin, BaseView, expose +from flask.ext.admin import Admin from flask.ext.admin.contrib.sqla import ModelView from flask.ext import login from app import app, db from models import User -from utils import send_command + class ModelBaseView(ModelView): diff --git a/app/app.py b/app/app.py index 09419eb..11bac36 100644 --- a/app/app.py +++ b/app/app.py @@ -1,10 +1,7 @@ from flask import Flask from flask.ext.sqlalchemy import SQLAlchemy -from werkzeug.contrib.cache import SimpleCache -from logbook import Logger - app = Flask(__name__) app.config.from_object('config.Configuration') diff --git a/app/foodbot.py b/app/foodbot.py index bc1a230..213b34a 100644 --- a/app/foodbot.py +++ b/app/foodbot.py @@ -1,9 +1,3 @@ -from app import app, db - -from admin import admin -from login import login_manager -from models import * -from utils import start_process from views import * diff --git a/app/login.py b/app/login.py index 419a942..20fb494 100644 --- a/app/login.py +++ b/app/login.py @@ -1,12 +1,10 @@ -from flask import redirect, request, url_for, abort, session +from flask import redirect, abort, session, url_for from flask.ext.login import LoginManager, current_user, logout_user -from flask_oauthlib.client import OAuth -import requests -from app import app, db, logger, cache +from app import app from models import User -from zeus import oauth, zeus_login +from zeus import zeus_login login_manager = LoginManager() login_manager.init_app(app) diff --git a/app/models.py b/app/models.py index 01cfc41..5cbbc88 100644 --- a/app/models.py +++ b/app/models.py @@ -1,7 +1,3 @@ -import uuid -from datetime import date, timedelta - - from app import db # Create database models diff --git a/app/views.py b/app/views.py index 03a7692..d9401f0 100644 --- a/app/views.py +++ b/app/views.py @@ -1,8 +1,7 @@ -from flask import Blueprint, request, jsonify, redirect, url_for - +from flask import url_for, render_template from app import app -from login import before_request + @app.route('/') diff --git a/app/zeus.py b/app/zeus.py index aed492d..b3a0c1b 100644 --- a/app/zeus.py +++ b/app/zeus.py @@ -1,13 +1,12 @@ -from flask import Flask, redirect, url_for, session, request, jsonify, flash, request -from flask.ext.login import LoginManager, login_user, current_user, logout_user -from flask.ext.admin import helpers +from flask import redirect, url_for, session, jsonify, flash, request +from flask.ext.login import login_user from flask_oauthlib.client import OAuth, OAuthException import json import requests from app import app, db -from models import User, Token +from models import User oauth = OAuth(app) @@ -30,7 +29,7 @@ def zeus_login(): return zeus.authorize(callback='http://zeus.ugent.be/foodbot/login/zeus/authorized') -@app.route('/slotmachien/login/zeus/authorized') +@app.route('/foodbot/login/zeus/authorized') def authorized(): resp = zeus.authorized_response() if resp is None: @@ -63,22 +62,9 @@ def get_zeus_oauth_token(): def login_and_redirect_user(user): login_user(user) - # add_token(resp['access_token'], user) - content_type = request.headers.get('Content-Type', None) - if content_type and content_type in 'application/json': - token = add_token(user) - return jsonify({'token': token.token}) return redirect(url_for("admin.index")) -def add_token(user): - token = Token() - token.configure(user) - db.session.add(token) - db.session.commit() - return token - - def create_user(username): user = User() user.configure(username, False) From c851ce11ec82a2779e665ba41d12be27f04e765a Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 22:23:11 +0100 Subject: [PATCH 12/68] Edited User parameters --- app/create_database.py | 7 +++---- app/models.py | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/create_database.py b/app/create_database.py index c8b86bd..6f15e18 100644 --- a/app/create_database.py +++ b/app/create_database.py @@ -5,12 +5,11 @@ db.drop_all() db.create_all() feli = User() -feli.configure("feliciaan", True, True) +feli.configure("feliciaan", True, 0) db.session.add(feli) -don = User() -don.configure("don", True, True) -db.session.add(don) +wout = User() +wout.configure('wout', True, 0) # To future developers, add yourself here diff --git a/app/models.py b/app/models.py index 5cbbc88..7318f7b 100644 --- a/app/models.py +++ b/app/models.py @@ -9,9 +9,8 @@ class User(db.Model): courrier = db.relationship('Courrier', backref='courrier', lazy='dynamic') logactions = db.relationship('LogAction', backref='user', lazy='dynamic') - def configure(self, username, allowed, admin, bias): + def configure(self, username, admin, bias): self.username = username - self.allowed = allowed self.admin = admin self.bias = bias From 4e2780d9beacaee26f3c4d50d3bdda198f0019dc Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 22:32:34 +0100 Subject: [PATCH 13/68] mode some changes to get it working --- .gitignore | 3 ++- app/foodbot.py | 10 ++++++---- app/models.py | 7 ++++--- app/templates/layout.html | 1 - 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 8e88017..0098d0a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,5 @@ target/ # ConfigFile app/config.py - +# Do not add db file +*.db diff --git a/app/foodbot.py b/app/foodbot.py index 213b34a..6d2d11f 100644 --- a/app/foodbot.py +++ b/app/foodbot.py @@ -1,9 +1,11 @@ from views import * +from app import app, db + +from admin import admin +from login import login_manager +from models import * +from views import * -if __name__ == '__main__': - app.run() - if __name__ == '__main__': app.run(debug=True) - # app.run(host='0.0.0.0') diff --git a/app/models.py b/app/models.py index 7318f7b..8d37025 100644 --- a/app/models.py +++ b/app/models.py @@ -6,8 +6,7 @@ class User(db.Model): username = db.Column(db.String(80), unique=True) admin = db.Column(db.Boolean) bias = db.Column(db.Integer) - courrier = db.relationship('Courrier', backref='courrier', lazy='dynamic') - logactions = db.relationship('LogAction', backref='user', lazy='dynamic') + orders = db.relationship('Order', backref='courrier', lazy='dynamic') def configure(self, username, admin, bias): self.username = username @@ -51,6 +50,7 @@ class Location(db.Model): def __repr__(self): return '%s: %s' % (self.name, self.address) + class Food(db.Model): id = db.Column(db.Integer, primary_key=True) location_id = db.Column(db.Integer, db.ForeignKey('location.id')) @@ -65,13 +65,14 @@ class Food(db.Model): def __repr__(self): return '%s' % self.name + class Order(db.Model): id = db.Column(db.Integer, primary_key=True) courrier_id = db.Column(db.Integer, db.ForeignKey('user.id')) location_id = db.Column(db.Integer, db.ForeignKey('location.id')) starttime = db.Column(db.DateTime) stoptime = db.Column(db.DateTime) - orders = db.relationship('OrdreItem', backref='order', lazy='dynamic') + orders = db.relationship('OrderItem', backref='order', lazy='dynamic') def configure(self, courrier, location, starttime, stoptime): diff --git a/app/templates/layout.html b/app/templates/layout.html index 03685c6..61e2bac 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -15,7 +15,6 @@
  • Home
  • Stats
  • About
  • -
  • Login
  • From 96f7542104dd8c8a61d2532cfbc180c2d11577c9 Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Thu, 26 Mar 2015 22:45:41 +0100 Subject: [PATCH 14/68] Dunno what ichanged --- app/admin.py | 1 - app/models.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/admin.py b/app/admin.py index 335e78f..008380e 100644 --- a/app/admin.py +++ b/app/admin.py @@ -7,7 +7,6 @@ from app import app, db from models import User - class ModelBaseView(ModelView): def is_accessible(self): diff --git a/app/models.py b/app/models.py index 8d37025..15e48d2 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,6 @@ from app import db + # Create database models class User(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -74,7 +75,6 @@ class Order(db.Model): stoptime = db.Column(db.DateTime) orders = db.relationship('OrderItem', backref='order', lazy='dynamic') - def configure(self, courrier, location, starttime, stoptime): self.courrier = courrier self.location = location @@ -84,6 +84,7 @@ class Order(db.Model): def __repr__(self): return 'Order' + class OrderItem(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) From e58ed018bd3f3ac06a15fb366a43b512353809e5 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 23:17:51 +0100 Subject: [PATCH 15/68] lol admin interface --- app/admin.py | 14 ++++++++++++-- app/foodbot.py | 5 +++++ app/models.py | 6 +++++- app/requirements.txt | 1 - app/templates/layout.html | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/admin.py b/app/admin.py index 335e78f..03b6152 100644 --- a/app/admin.py +++ b/app/admin.py @@ -4,7 +4,7 @@ from flask.ext import login from app import app, db -from models import User +from models import User, Location, Food, Order, OrderItem @@ -20,8 +20,18 @@ class ModelBaseView(ModelView): class UserAdminModel(ModelBaseView): column_searchable_list = ('username',) inline_models = None - form_columns = ('username', 'admin') + + +class LocationAdminModel(ModelBaseView): + column_searchable_list = ('name', 'address', 'website') + inline_models = None + form_columns = ('name', 'address', 'website') + admin = Admin(app, name='FoodBot', url='/foodbot/admin', template_mode='bootstrap3') admin.add_view(UserAdminModel(User, db.session)) +admin.add_view(LocationAdminModel(Location, db.session)) +admin.add_view(ModelBaseView(Food, db.session)) +admin.add_view(ModelBaseView(Order, db.session)) +admin.add_view(ModelBaseView(OrderItem, db.session)) diff --git a/app/foodbot.py b/app/foodbot.py index 6d2d11f..0165261 100644 --- a/app/foodbot.py +++ b/app/foodbot.py @@ -1,3 +1,8 @@ +# TEMPORARY ## SHOULD BE DELETED AFTER KELDER.ZEUS HAS THEIR CERTIFICATE +import os +os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" + + from views import * from app import app, db diff --git a/app/models.py b/app/models.py index 8d37025..1936d88 100644 --- a/app/models.py +++ b/app/models.py @@ -6,7 +6,8 @@ class User(db.Model): username = db.Column(db.String(80), unique=True) admin = db.Column(db.Boolean) bias = db.Column(db.Integer) - orders = db.relationship('Order', backref='courrier', lazy='dynamic') + runs = db.relationship('Order', backref='courrier', lazy='dynamic') + orders = db.relationship('OrderItem', backref='user', lazy='dynamic') def configure(self, username, admin, bias): self.username = username @@ -56,6 +57,8 @@ class Food(db.Model): location_id = db.Column(db.Integer, db.ForeignKey('location.id')) name = db.Column(db.String(120)) price = db.Column(db.Integer) + orders = db.relationship('OrderItem', backref='food', lazy='dynamic') + def configure(self, location, name, price): self.location = location @@ -84,6 +87,7 @@ class Order(db.Model): def __repr__(self): return 'Order' + class OrderItem(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) diff --git a/app/requirements.txt b/app/requirements.txt index 029a996..44bb7c4 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -8,7 +8,6 @@ MarkupSafe==0.23 SQLAlchemy==0.9.8 WTForms==2.0 Werkzeug==0.9.6 -flup==1.0.2 itsdangerous==0.24 requests==2.4.0 Flask-OAuthlib==0.8.0 diff --git a/app/templates/layout.html b/app/templates/layout.html index 61e2bac..03685c6 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -15,6 +15,7 @@
  • Home
  • Stats
  • About
  • +
  • Login
  • From 7a8b8b7434b637e58970d0884e5f1bd97a87baba Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Thu, 26 Mar 2015 23:31:13 +0100 Subject: [PATCH 16/68] Modified the base layout to show the logged in users name --- app/login.py | 2 +- app/templates/layout.html | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/login.py b/app/login.py index 20fb494..12aff9f 100644 --- a/app/login.py +++ b/app/login.py @@ -24,7 +24,7 @@ def logout(): if 'zeus_token' in session: session.pop('zeus_token', None) logout_user() - return redirect(url_for('admin.index')) + return redirect(url_for('home')) def before_request(): diff --git a/app/templates/layout.html b/app/templates/layout.html index 03685c6..cef4cb0 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -15,7 +15,12 @@
  • Home
  • Stats
  • About
  • -
  • Login
  • + {% if current_user.is_anonymous() %} +
  • Login
  • + {% else %} +
  • Logout
  • +
  • {{ current_user.username }}
  • + {% endif %} From 0426b71556a38d5179246782c80a07b754cbcf4f Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Fri, 27 Mar 2015 01:07:43 +0100 Subject: [PATCH 17/68] bootstrap 3 ftw --- app/static/css/main.css | 91 ++------------------------------------- app/templates/home.html | 2 +- app/templates/layout.html | 48 ++++++++++++++------- 3 files changed, 36 insertions(+), 105 deletions(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index 3c7e9c1..487c3af 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1,90 +1,5 @@ +/* Custom CSS */ body { - margin: 0; - padding: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #444; -} - -/* - * Create dark grey header with a white logo - */ - -header { - background-color: #2B2B2B; - height: 35px; - width: 100%; - opacity: .9; - margin-bottom: 10px; -} - -header h1.logo { - margin: 0; - font-size: 1.7em; - color: #fff; - text-transform: uppercase; - float: left; -} - -header h1.logo:hover { - color: #fff; - text-decoration: none; -} - -/* - * Center the body content - */ - -.container { - width: 940px; - margin: 0 auto; -} - -div.jumbo { - padding: 10px 0 30px 0; - background-color: #eeeeee; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -div.login { - padding: 10px 0 30px 0; - background-color: #eeeeee; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -h2 { - font-size: 3em; - margin-top: 40px; - text-align: center; - letter-spacing: -2px; -} - -h3 { - font-size: 1.7em; - font-weight: 100; - margin-top: 30px; - text-align: center; - letter-spacing: -1px; - color: #999; -} - -.menu { - float: right; - margin-top: 8px; -} - -.menu li { - display: inline; -} - -.menu li + li { - margin-left: 35px; -} - -.menu li a { - color: #999; - text-decoration: none; + min-height: 2000px; + padding-top: 70px; } \ No newline at end of file diff --git a/app/templates/home.html b/app/templates/home.html index 1e0bf08..91b872c 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% block content %} -
    +

    Welcome to FoodBot

    This is the home page for FoodBot

    diff --git a/app/templates/layout.html b/app/templates/layout.html index cef4cb0..772121c 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -1,35 +1,51 @@ - - FoodBot - + + + + FoodBot + + + + - -
    +
    - - +
    {% block content %} {% endblock %}
    - + + + + \ No newline at end of file From 5da909dce46dc3c235f361d5616c0e7f9fae16de Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Fri, 27 Mar 2015 01:23:07 +0100 Subject: [PATCH 18/68] Remove URL prefix, can be added during deploy, see http://flask.pocoo.org/snippets/35/ or so --- app/admin.py | 2 +- app/login.py | 4 ++-- app/zeus.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/admin.py b/app/admin.py index 96d6039..3fda49c 100644 --- a/app/admin.py +++ b/app/admin.py @@ -27,7 +27,7 @@ class LocationAdminModel(ModelBaseView): form_columns = ('name', 'address', 'website') -admin = Admin(app, name='FoodBot', url='/foodbot/admin', template_mode='bootstrap3') +admin = Admin(app, name='FoodBot', url='/admin', template_mode='bootstrap3') admin.add_view(UserAdminModel(User, db.session)) admin.add_view(LocationAdminModel(Location, db.session)) diff --git a/app/login.py b/app/login.py index 12aff9f..6375128 100644 --- a/app/login.py +++ b/app/login.py @@ -14,12 +14,12 @@ login_manager.init_app(app) def load_user(userid): return User.query.filter_by(id=userid).first() -@app.route('/foodbot/login') +@app.route('/login') def login(): return zeus_login() -@app.route('/foodbot/logout') +@app.route('/logout') def logout(): if 'zeus_token' in session: session.pop('zeus_token', None) diff --git a/app/zeus.py b/app/zeus.py index b3a0c1b..7661260 100644 --- a/app/zeus.py +++ b/app/zeus.py @@ -25,11 +25,9 @@ zeus = oauth.remote_app( def zeus_login(): if app.debug: return zeus.authorize(callback=url_for('authorized', _external=True)) - else: # temporary solution because it otherwise gives trouble on the pi because of proxies and such - return zeus.authorize(callback='http://zeus.ugent.be/foodbot/login/zeus/authorized') -@app.route('/foodbot/login/zeus/authorized') +@app.route('/login/zeus/authorized') def authorized(): resp = zeus.authorized_response() if resp is None: From 55a5c9b9278a38824310fd47c9b307f73211f965 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Fri, 27 Mar 2015 01:25:21 +0100 Subject: [PATCH 19/68] Remove some CSS --- app/static/css/main.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index 487c3af..482ef88 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1,5 +1,4 @@ /* Custom CSS */ body { - min-height: 2000px; padding-top: 70px; } \ No newline at end of file From 56c908c63fa99f1467a61d726c4f3c30c63ca30a Mon Sep 17 00:00:00 2001 From: Wout Schellaert Date: Fri, 27 Mar 2015 14:03:28 +0100 Subject: [PATCH 20/68] Removed slotmachien slack stuff --- app/admin.py | 1 + app/create_database.py | 4 ++-- app/zeus.py | 8 +------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/admin.py b/app/admin.py index 3fda49c..b6dceff 100644 --- a/app/admin.py +++ b/app/admin.py @@ -29,6 +29,7 @@ class LocationAdminModel(ModelBaseView): admin = Admin(app, name='FoodBot', url='/admin', template_mode='bootstrap3') + admin.add_view(UserAdminModel(User, db.session)) admin.add_view(LocationAdminModel(Location, db.session)) admin.add_view(ModelBaseView(Food, db.session)) diff --git a/app/create_database.py b/app/create_database.py index 6f15e18..82caa5b 100644 --- a/app/create_database.py +++ b/app/create_database.py @@ -8,8 +8,8 @@ feli = User() feli.configure("feliciaan", True, 0) db.session.add(feli) -wout = User() -wout.configure('wout', True, 0) +destro = User() +destro.configure('destro', True, 0) # To future developers, add yourself here diff --git a/app/zeus.py b/app/zeus.py index 7661260..553509d 100644 --- a/app/zeus.py +++ b/app/zeus.py @@ -65,13 +65,7 @@ def login_and_redirect_user(user): def create_user(username): user = User() - user.configure(username, False) + user.configure(username, False, 1) db.session.add(user) db.session.commit() - # EASTER EGG - text = 'Welcome ' + username + '!' - js = json.dumps({'text': text}) - url = app.config['SLACK_WEBHOOK'] - if len(url) > 0: - requests.post(url, data=js) return user From 6e660399a2f503a465f22841f4137d7626cd859f Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Fri, 27 Mar 2015 18:07:06 +0100 Subject: [PATCH 21/68] Added an order detail view, and open orders on the homepage --- app/filters.py | 7 +++++++ app/foodbot.py | 1 + app/models.py | 4 +++- app/templates/about.html | 5 +++-- app/templates/home.html | 3 ++- app/templates/home_loggedin.html | 14 ++++++++++++++ app/templates/layout.html | 17 ++++++++++++++--- app/templates/login.html | 24 ------------------------ app/templates/order.html | 16 ++++++++++++++++ app/templates/stats.html | 4 ++-- app/views.py | 17 +++++++++++++++-- app/zeus.py | 2 +- 12 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 app/filters.py create mode 100644 app/templates/home_loggedin.html delete mode 100644 app/templates/login.html create mode 100644 app/templates/order.html diff --git a/app/filters.py b/app/filters.py new file mode 100644 index 0000000..a73a454 --- /dev/null +++ b/app/filters.py @@ -0,0 +1,7 @@ +from app import app +__author__ = 'feliciaan' + +@app.template_filter('euro') +def euro(value): + result = '€' + str(value/100) + return result \ No newline at end of file diff --git a/app/foodbot.py b/app/foodbot.py index 0165261..4335503 100644 --- a/app/foodbot.py +++ b/app/foodbot.py @@ -10,6 +10,7 @@ from app import app, db from admin import admin from login import login_manager from models import * +from filters import * from views import * if __name__ == '__main__': diff --git a/app/models.py b/app/models.py index 2fc7274..86ca9e5 100644 --- a/app/models.py +++ b/app/models.py @@ -43,6 +43,8 @@ class Location(db.Model): address = db.Column(db.String(254)) website = db.Column(db.String(120)) food = db.relationship('Food', backref='location', lazy='dynamic') + orders = db.relationship('Order', backref='location', lazy='dynamic') + def configure(self, name, address, website): self.name = name @@ -85,7 +87,7 @@ class Order(db.Model): self.stoptime = stoptime def __repr__(self): - return 'Order' + return 'Order %s' % (self.location.name) class OrderItem(db.Model): diff --git a/app/templates/about.html b/app/templates/about.html index a36d7c4..b412cf7 100644 --- a/app/templates/about.html +++ b/app/templates/about.html @@ -1,5 +1,6 @@ -{% extends "layout.html" %} - +{% extends "layout.html" -%} +{% set active_page = "about" -%} + {% block content %}

    About

    This is an About page for FoodBot. Don't I look good? Oh stop, you're making me blush.

    diff --git a/app/templates/home.html b/app/templates/home.html index 91b872c..eefe505 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,4 +1,5 @@ -{% extends "layout.html" %} +{% extends "layout.html" -%} +{% set active_page = "home" -%} {% block content %}

    Welcome to FoodBot

    diff --git a/app/templates/home_loggedin.html b/app/templates/home_loggedin.html new file mode 100644 index 0000000..26bbc22 --- /dev/null +++ b/app/templates/home_loggedin.html @@ -0,0 +1,14 @@ +{% extends "home.html" %} +{% block content %} +{{ super() }} +
    +
    +

    Open orders:

    + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/layout.html b/app/templates/layout.html index 772121c..f402dd9 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -1,3 +1,12 @@ +{% set navbar = [ + ('home', 'Home'), + ('about', 'About'), + ('stats', 'Stats'), +] -%} +{% if current_user.is_admin() -%} + {% set navbar = navbar + [('admin.index', 'Admin')] -%} +{% endif -%} +{% set active_page = active_page|default('index') -%} @@ -24,9 +33,11 @@
    + {% if not current_user.is_anonymous() %}
    {{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
    + {% endif %} {% endblock %} \ No newline at end of file diff --git a/app/utils.py b/app/utils.py index 60150bf..0aff1fe 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,6 +1,8 @@ from flask import render_template from app import app +from login import login_manager + __author__ = 'feliciaan' @app.template_filter('euro') @@ -14,4 +16,22 @@ def handle404(e): @app.errorhandler(401) def handle401(e): - return render_template('errors/401.html'), 401 \ No newline at end of file + return render_template('errors/401.html'), 401 + +class AnonymouseUser: + 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 \ No newline at end of file diff --git a/app/views/__init__.py b/app/views/__init__.py index a44037b..06aa7e2 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -12,10 +12,7 @@ import views.order @app.route('/') def home(): - if not current_user.is_anonymous(): - orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() - return render_template('home_loggedin.html', orders=orders) - return render_template('home.html') + return render_template('home.html', orders=views.order.get_orders()) @app.route('/about/') diff --git a/app/views/order.py b/app/views/order.py index 8e7d0e9..a172edb 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -1,22 +1,22 @@ __author__ = 'feliciaan' -from flask import url_for, render_template, abort, redirect, Blueprint, flash +from flask import url_for, render_template, abort, redirect, Blueprint, flash, session from flask.ext.login import current_user, login_required import random from datetime import datetime from app import app, db from models import Order, OrderItem -from forms import OrderItemForm, OrderForm +from forms import OrderItemForm, OrderForm, AnonOrderItemForm order_bp = Blueprint('order_bp', 'order') @order_bp.route('/') -@login_required def orders(): - orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() - orderForm = OrderForm() - orderForm.populate() - return render_template('orders.html', orders=orders, form=orderForm) + orderForm = None + if not current_user.is_anonymous(): + orderForm = OrderForm() + orderForm.populate() + return render_template('orders.html', orders=get_orders(), form=orderForm) @order_bp.route('/create', methods=['GET', 'POST']) @@ -35,41 +35,52 @@ def order_create(): @order_bp.route('/') -@login_required def order(id): order = Order.query.filter(Order.id == id).first() - if order is not None: + if order is None: + abort(404) + form = None + if not current_user.is_anonymous(): form = OrderItemForm() - form.populate(order.location) - total_price = sum([o.product.price for o in order.items]) - total_payments = order.group_by_user_pay() - return render_template('order.html', order=order, form=form, total_price=total_price, total_payments=total_payments) - return abort(404) + else: + form = AnonOrderItemForm() + form.populate(order.location) + total_price = sum([o.product.price for o in order.items]) + total_payments = order.group_by_user_pay() + return render_template('order.html', order=order, form=form, total_price=total_price, total_payments=total_payments) @order_bp.route('//create', methods=['GET', 'POST']) -@login_required def order_item_create(id): order = Order.query.filter(Order.id == id).first() - if order is not None: + if order is None: + abort(404) + form = None + if not current_user.is_anonymous(): form = OrderItemForm() - form.populate(order.location) - if form.validate_on_submit(): - item = OrderItem() - form.populate_obj(item) - item.order_id = id + else: + form = AnonOrderItemForm() + form.populate(order.location) + if form.validate_on_submit(): + item = OrderItem() + form.populate_obj(item) + item.order_id = id + if not current_user.is_anonymous(): item.user_id = current_user.id - db.session.add(item) - db.session.commit() - return redirect(url_for('.order', id=id)) - return render_template('order_form.html', form=form, url=url_for(".order_item_create", id=id)) - return abort(404) + else: + session['anon_name'] = item.name + db.session.add(item) + db.session.commit() + return redirect(url_for('.order', id=id)) + return render_template('order_form.html', form=form, url=url_for(".order_item_create", id=id)) @order_bp.route('///delete') -@login_required def delete_item(order_id, item_id): item = OrderItem.query.filter(OrderItem.id == item_id).first() - if item.can_delete(order_id, current_user.id): + id = None + if not current_user.is_anonymous(): + id = current_user.id + if item.can_delete(order_id, id, session.get('anon_name', '')): db.session.delete(item) db.session.commit() return redirect(url_for('.order', id=order_id)) @@ -80,33 +91,32 @@ def delete_item(order_id, item_id): @login_required def volunteer(id): order = Order.query.filter(Order.id == id).first() - if order is not None: - print(order.courrier_id) - if order.courrier_id == 0: - order.courrier_id = current_user.id - db.session.commit() - flash("Thank you for volunteering!") - else: - flash("Volunteering not possible!") - return redirect(url_for('.order', id=id)) - abort(404) + if order is None: + abort(404) + if order.courrier_id is None or order.courrier_id == 0: + order.courrier_id = current_user.id + db.session.commit() + flash("Thank you for volunteering!") + else: + flash("Volunteering not possible!") + return redirect(url_for('.order', id=id)) @order_bp.route('//close') @login_required def close_order(id): order = Order.query.filter(Order.id == id).first() - if order is not None: - if (current_user.id == order.courrier_id or current_user.is_admin()) \ - and order.stoptime is None or (order.stoptime > datetime.now()): - order.stoptime = datetime.now() - if order.courrier_id == 0 or order.courrier_id is None: - courrier = select_user(order.items) - if courrier is not None: - order.courrier_id = courrier.id - db.session.commit() - return redirect(url_for('.order', id=id)) - abort(401) + if order is None: + abort(401) + if (current_user.id == order.courrier_id or current_user.is_admin()) \ + and order.stoptime is None or (order.stoptime > datetime.now()): + order.stoptime = datetime.now() + if order.courrier_id == 0 or order.courrier_id is None: + courrier = select_user(order.items) + 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') @@ -120,7 +130,16 @@ def select_user(items): while user is None: item = random.choice(items) user = item.user - if random.randint(user.bias, 100) < 80: - user = None + if user: + if random.randint(user.bias, 100) < 80: + user = None return user + +def get_orders(): + orders = [] + if not current_user.is_anonymous(): + orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() + else: + orders = Order.query.filter(((Order.stoptime > datetime.now()) | (Order.stoptime == None) & (Order.public == True))).all() + return orders From 08c09225918668faaf2639b802ad79b15676c15b Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sat, 28 Mar 2015 21:12:02 +0100 Subject: [PATCH 35/68] Removed some bugs in closing an order --- app/forms.py | 1 - app/models.py | 11 +++++++++++ app/templates/order.html | 4 +++- app/views/order.py | 11 +++++++++-- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/forms.py b/app/forms.py index 61ab915..4eae97d 100644 --- a/app/forms.py +++ b/app/forms.py @@ -35,7 +35,6 @@ class OrderItemForm(Form): def populate(self, location): self.product_id.choices = [(i.id, (i.name + ": " + euro(i.price))) for i in location.products] - class AnonOrderItemForm(OrderItemForm): name = StringField('Name', validators=[validators.required()]) diff --git a/app/models.py b/app/models.py index 14ccd8c..625ed40 100644 --- a/app/models.py +++ b/app/models.py @@ -104,6 +104,17 @@ class Order(db.Model): group[item.get_name()] += item.product.price 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')) diff --git a/app/templates/order.html b/app/templates/order.html index 136a12f..694652f 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -6,7 +6,7 @@

    Order {{ order.id }} - {% if (current_user.id == order.courrier_id and (not order.stoptime)) or current_user.is_admin() -%} + {% if order.can_close(current_user.id) -%} Close
    {%- endif %}

    Courrier: {{ order.courrier.username }} @@ -28,9 +28,11 @@ {{ key }} - {{ value|euro }}
    {% endfor %}
    + {% if form -%}

    Order:

    {{ wtf.quick_form(form, action=url_for('.order_item_create', id=order.id), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
    + {%- endif %}
    {% endblock %} \ No newline at end of file diff --git a/app/views/order.py b/app/views/order.py index a172edb..267a921 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -45,6 +45,8 @@ def order(id): else: form = AnonOrderItemForm() 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]) total_payments = order.group_by_user_pay() return render_template('order.html', order=order, form=form, total_price=total_price, total_payments=total_payments) @@ -55,6 +57,8 @@ def order_item_create(id): order = Order.query.filter(Order.id == id).first() if order is None: abort(404) + if order.stoptime and order.stoptime < datetime.now(): + abort(404) form = None if not current_user.is_anonymous(): form = OrderItemForm() @@ -107,12 +111,13 @@ def volunteer(id): def close_order(id): order = Order.query.filter(Order.id == id).first() if order is None: - abort(401) + abort(404) if (current_user.id == order.courrier_id or current_user.is_admin()) \ and order.stoptime is None or (order.stoptime > datetime.now()): order.stoptime = datetime.now() if order.courrier_id == 0 or order.courrier_id is None: courrier = select_user(order.items) + print(courrier) if courrier is not None: order.courrier_id = courrier.id db.session.commit() @@ -123,7 +128,9 @@ app.register_blueprint(order_bp, url_prefix='/order') def select_user(items): user = None - items = list(items) + # remove non users + items = [i for i in items if i.user_id] + if len(items) <= 0: return None From b4d18689884841874c44dbac76a27b2d995167c1 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sat, 28 Mar 2015 21:25:55 +0100 Subject: [PATCH 36/68] Also show recently closed orders --- app/templates/home.html | 8 ++++++++ app/views/__init__.py | 8 +++++--- app/views/order.py | 6 +++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/templates/home.html b/app/templates/home.html index 2d42adc..c280c45 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -14,5 +14,13 @@ {% endfor %} +
    +

    Recently closed orders:

    + +
    {% endblock %} \ No newline at end of file diff --git a/app/views/__init__.py b/app/views/__init__.py index 06aa7e2..0a668fd 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -2,17 +2,19 @@ __author__ = 'feliciaan' from flask import url_for, render_template, abort, redirect, request from flask.ext.login import current_user, login_required -from datetime import datetime +from datetime import datetime, timedelta from app import app, db from models import Order, OrderItem # import views -import views.order +from views.order import get_orders @app.route('/') def home(): - return render_template('home.html', orders=views.order.get_orders()) + 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('/about/') diff --git a/app/views/order.py b/app/views/order.py index 267a921..91bb2f6 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -143,10 +143,10 @@ def select_user(items): return user -def get_orders(): +def get_orders(expression=(Order.stoptime > datetime.now()) | (Order.stoptime == None)): orders = [] if not current_user.is_anonymous(): - orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() + orders = Order.query.filter(expression).all() else: - orders = Order.query.filter(((Order.stoptime > datetime.now()) | (Order.stoptime == None) & (Order.public == True))).all() + orders = Order.query.filter((expression & (Order.public == True))).all() return orders From 28cd6947a59da498ea648f29ab02f3679a57ce4e Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sat, 28 Mar 2015 23:06:36 +0100 Subject: [PATCH 37/68] Added some layouting --- app/models.py | 6 ++++++ app/static/css/main.css | 18 +++++++++++++++++- app/templates/home.html | 18 +++++++++--------- app/templates/order.html | 38 ++++++++++++++++++++++++++------------ app/templates/orders.html | 13 +++++++------ app/templates/utils.html | 12 ++++++++++++ app/utils.py | 10 ++++++++++ app/views/order.py | 7 ++++--- 8 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 app/templates/utils.html diff --git a/app/models.py b/app/models.py index 625ed40..955b359 100644 --- a/app/models.py +++ b/app/models.py @@ -104,6 +104,12 @@ class Order(db.Model): group[item.get_name()] += item.product.price return group + def group_by_product(self): + group = defaultdict(int) + for item in self.items: + group[item.product.name] += 1 + return group + def can_close(self, user_id): if self.stoptime and self.stoptime < datetime.now(): return False diff --git a/app/static/css/main.css b/app/static/css/main.css index 482ef88..34bf47a 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1,4 +1,20 @@ /* Custom CSS */ body { padding-top: 70px; -} \ No newline at end of file +} + +.darker { + background-color: #fafafa; + margin-top: 10px; + padding-bottom: 5px; +} + +@media (min-width: 992px) { + .align-bottom { + margin-top: 2.5em; + } +} + +.full-width { + width: 100%; +} diff --git a/app/templates/home.html b/app/templates/home.html index c280c45..0c0f681 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,5 +1,8 @@ {% extends "layout.html" -%} {% set active_page = "home" -%} + +{% import "utils.html" as util -%} + {% block container %}

    Welcome to FoodBot

    @@ -8,18 +11,15 @@

    Open orders:

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

    Recently closed orders:

    -
    diff --git a/app/templates/order.html b/app/templates/order.html index 694652f..4828327 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -4,35 +4,49 @@ {% import "bootstrap/wtf.html" as wtf %} {% block container %}
    -
    +

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

    Courrier: {{ order.courrier.username }} {% if order.courrier == None and not current_user.is_anonymous() %} - Volunteer + Volunteer {% endif %}
    Location: {{ order.location.name }}
    Starttime: {{ order.starttime }}
    Stoptime: {{ order.stoptime }}
    Total price: {{ total_price|euro }} -

    Orders

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

    Debts

    - {% for key, value in total_payments.items() %} - {{ key }} - {{ value|euro }}
    - {% endfor %}
    {% if form -%} -
    +

    Order:

    {{ wtf.quick_form(form, action=url_for('.order_item_create', id=order.id), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
    {%- endif %}
    +
    +
    +

    Orders

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

    Ordered products:

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

    Debts

    + {% for key, value in order.group_by_user_pay().items() %} + {{ key }} - {{ value|euro }}
    + {% endfor %} +
    +
    {% endblock %} \ No newline at end of file diff --git a/app/templates/orders.html b/app/templates/orders.html index 6cab482..2374341 100644 --- a/app/templates/orders.html +++ b/app/templates/orders.html @@ -2,19 +2,20 @@ {% set active_page = "orders" -%} {% import "bootstrap/wtf.html" as wtf %} +{% import "utils.html" as util -%} + {% block container %}

    Open orders:

    - + {% for order in orders %} + {{ util.render_order(order) }} + {% endfor %}
    {% if not current_user.is_anonymous() %}
    - {{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }} +

    Create new order:

    + {{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
    {% endif %}
    diff --git a/app/templates/utils.html b/app/templates/utils.html new file mode 100644 index 0000000..98ce6a4 --- /dev/null +++ b/app/templates/utils.html @@ -0,0 +1,12 @@ +{% macro render_order(order) -%} +
    +
    +
    {{ order.location.name }}
    +

    Status: {{ order.stoptime|countdown }}
    + Orders: {{ order.items.count() }}

    +
    +
    + Open +
    +
    +{%- endmacro %} \ No newline at end of file diff --git a/app/utils.py b/app/utils.py index 0aff1fe..afa84ff 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,3 +1,4 @@ +from datetime import datetime from flask import render_template from app import app @@ -10,6 +11,15 @@ def euro(value): result = '€' + str(value/100) return result +@app.template_filter('countdown') +def countdown(value): + delta = value - datetime.now() + if delta.total_seconds() < 0: + return "closed" + hours, remainder = divmod(delta.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return 'closes in %02d:%02d:%02d' % (hours, minutes, seconds) + @app.errorhandler(404) def handle404(e): return render_template('errors/404.html'), 404 diff --git a/app/views/order.py b/app/views/order.py index 91bb2f6..81ff2b1 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -48,8 +48,7 @@ def order(id): if order.stoptime and order.stoptime < datetime.now(): form = None total_price = sum([o.product.price for o in order.items]) - total_payments = order.group_by_user_pay() - return render_template('order.html', order=order, form=form, total_price=total_price, total_payments=total_payments) + return render_template('order.html', order=order, form=form, total_price=total_price) @order_bp.route('//create', methods=['GET', 'POST']) @@ -143,8 +142,10 @@ def select_user(items): return user -def get_orders(expression=(Order.stoptime > datetime.now()) | (Order.stoptime == None)): +def get_orders(expression=None): orders = [] + if expression is None: + expression = (Order.stoptime > datetime.now()) | (Order.stoptime == None) if not current_user.is_anonymous(): orders = Order.query.filter(expression).all() else: From 75dee4ebbdc97f71d9d71d650da6e9b534dae603 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sat, 28 Mar 2015 23:21:43 +0100 Subject: [PATCH 38/68] Some more layouting --- app/templates/order.html | 10 +++++----- app/utils.py | 9 ++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/templates/order.html b/app/templates/order.html index 4828327..e005da4 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -9,15 +9,15 @@ {% if order.can_close(current_user.id) -%} Close
    {%- endif %} - Courrier: {{ order.courrier.username }} + courrier: {{ order.courrier.username }} {% if order.courrier == None and not current_user.is_anonymous() %} Volunteer {% endif %}
    - Location: {{ order.location.name }}
    - Starttime: {{ order.starttime }}
    - Stoptime: {{ order.stoptime }}
    - Total price: {{ total_price|euro }} + location: {{ order.location.name }}
    + opened on {{ order.starttime }}
    + status: {% if order.stoptime %}{{ order.stoptime|countdown }}{% else %}open{% endif %}
    + total price: {{ total_price|euro }}
    {% if form -%}
    diff --git a/app/utils.py b/app/utils.py index afa84ff..2148b1a 100644 --- a/app/utils.py +++ b/app/utils.py @@ -12,13 +12,16 @@ def euro(value): return result @app.template_filter('countdown') -def countdown(value): +def countdown(value, only_positive=True, show_text=True): delta = value - datetime.now() - if delta.total_seconds() < 0: + if delta.total_seconds() < 0 and only_positive: return "closed" hours, remainder = divmod(delta.seconds, 3600) minutes, seconds = divmod(remainder, 60) - return 'closes in %02d:%02d:%02d' % (hours, minutes, seconds) + time = '%02d:%02d:%02d' % (hours, minutes, seconds) + if show_text: + return 'closes in ' + time + return time @app.errorhandler(404) def handle404(e): From d2621c75f52613b45cc289a89a8bdc0ec1055059 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sat, 28 Mar 2015 23:31:38 +0100 Subject: [PATCH 39/68] clearer model string representation --- app/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models.py b/app/models.py index 955b359..59ce694 100644 --- a/app/models.py +++ b/app/models.py @@ -54,7 +54,7 @@ class Location(db.Model): self.website = website def __repr__(self): - return '%s: %s' % (self.name, self.address) + return '%s' % (self.name) class Product(db.Model): @@ -71,7 +71,7 @@ class Product(db.Model): self.price = price def __repr__(self): - return '%s' % self.name + return '%s from %s' % (self.name, self.location) class Order(db.Model): @@ -90,7 +90,7 @@ class Order(db.Model): self.stoptime = stoptime def __repr__(self): - return 'Order %s' % (self.location.name) + return 'Order %d @ %s' % (self.id, self.location.name) def group_by_user(self): group = defaultdict(list) From b9a1a62a815589b448169dbc0c6390d0be26a23f Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sun, 29 Mar 2015 11:46:08 +0200 Subject: [PATCH 40/68] Fix some bugs (closes #8) --- app/templates/order.html | 2 +- app/templates/utils.html | 2 +- app/zeus.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/templates/order.html b/app/templates/order.html index e005da4..2d833d6 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -28,7 +28,7 @@
    -

    Orders

    +

    Items

    {% for item in order.items %} {{ item.get_name() }} - {{ item.product.name }} - {{ item.product.price|euro }} {% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}{%- endif %}
    diff --git a/app/templates/utils.html b/app/templates/utils.html index 98ce6a4..6d70c07 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -6,7 +6,7 @@ Orders: {{ order.items.count() }}

    - Open + Expand
    {%- endmacro %} \ No newline at end of file diff --git a/app/zeus.py b/app/zeus.py index 5fecf91..fda87b3 100644 --- a/app/zeus.py +++ b/app/zeus.py @@ -23,8 +23,7 @@ zeus = oauth.remote_app( def zeus_login(): - if app.debug: - return zeus.authorize(callback=url_for('authorized', _external=True)) + return zeus.authorize(callback=url_for('authorized', _external=True)) @app.route('/login/zeus/authorized') @@ -50,7 +49,7 @@ def authorized(): return login_and_redirect_user(user) flash("You're not allowed to enter, please contact a system administrator") - return redirect(url_for("admin.index")) + return redirect(url_for("home")) @zeus.tokengetter From 9cd05b61d24ec5e064e3c27ea0fa5f150a7ff6ec Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sun, 29 Mar 2015 13:01:42 +0200 Subject: [PATCH 41/68] flash messages --- app/templates/layout.html | 3 ++- app/views/order.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/templates/layout.html b/app/templates/layout.html index 0971737..bb72bb2 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -53,7 +53,8 @@ {% endblock %} {% block content -%} - {{ utils.flashed_messages() }} + {{ utils.flashed_messages(container=True) }} +
    {% block container -%} {%- endblock %} diff --git a/app/views/order.py b/app/views/order.py index 81ff2b1..a57027d 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -74,6 +74,7 @@ def order_item_create(id): session['anon_name'] = item.name db.session.add(item) db.session.commit() + flash('Ordered %s' % (item.product.name), 'info') return redirect(url_for('.order', id=id)) return render_template('order_form.html', form=form, url=url_for(".order_item_create", id=id)) @@ -84,8 +85,10 @@ def delete_item(order_id, item_id): if not current_user.is_anonymous(): id = current_user.id if item.can_delete(order_id, id, session.get('anon_name', '')): + product_name = item.product.name db.session.delete(item) db.session.commit() + flash('Deleted %s' % product_name, 'info') return redirect(url_for('.order', id=order_id)) abort(404) From 6c0badfd55def99cbcdcb0295828e2aa845b6f8b Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Sun, 29 Mar 2015 22:44:26 +0200 Subject: [PATCH 42/68] search items --- app/templates/order.html | 4 +--- app/templates/order_form.html | 38 +++++++++++++++++++++++++++++++--- app/templates/orders_form.html | 9 ++++++++ app/templates/utils.html | 12 ++++++++++- app/views/order.py | 4 ++-- 5 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 app/templates/orders_form.html diff --git a/app/templates/order.html b/app/templates/order.html index 2d833d6..a5ee9a7 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -1,7 +1,6 @@ {% extends "layout.html" %} {% set active_page = "orders" -%} -{% import "bootstrap/wtf.html" as wtf %} {% block container %}
    @@ -15,14 +14,13 @@ {% endif %}
    location: {{ order.location.name }}
    - opened on {{ order.starttime }}
    status: {% if order.stoptime %}{{ order.stoptime|countdown }}{% else %}open{% endif %}
    total price: {{ total_price|euro }}
    {% if form -%}

    Order:

    - {{ wtf.quick_form(form, action=url_for('.order_item_create', id=order.id), button_map={'submit_button': 'primary'}, form_type='horizontal') }} + {% include "order_form.html" with context %}
    {%- endif %}
    diff --git a/app/templates/order_form.html b/app/templates/order_form.html index a5b5068..f547e43 100644 --- a/app/templates/order_form.html +++ b/app/templates/order_form.html @@ -1,9 +1,41 @@ {% extends "layout.html" %} {% set active_page = "orders" -%} -{% import "bootstrap/wtf.html" as wtf %} -{% block container %} +{% import "utils.html" as util %} + +{% block content %}
    - {{ wtf.quick_form(form, action=url, button_map={'submit_button': 'primary'}) }} +
    +
    + {{ form.csrf_token }} +
    + {{ form.product_id.label(class='control-label') }} + {{ form.product_id(class='form-control select') }} + {{ util.render_form_field_errors(form.product_id) }} +
    + {% if current_user.is_anonymous() %} +
    + {{ form.name.label(class='control-label') }} + {{ form.name(class='form-control') }} + {{ util.render_form_field_errors(form.name) }} +
    + {% endif %} +
    + {{ form.submit_button(class='btn btn-primary') }} +
    +
    +
    +{% endblock %} + +{% block styles %} + {{ super() }} + +{% endblock %} +{% block scripts %} + {{ super() }} + + {% endblock %} \ No newline at end of file diff --git a/app/templates/orders_form.html b/app/templates/orders_form.html new file mode 100644 index 0000000..a5b5068 --- /dev/null +++ b/app/templates/orders_form.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% set active_page = "orders" -%} + +{% import "bootstrap/wtf.html" as wtf %} +{% block container %} +
    + {{ wtf.quick_form(form, action=url, button_map={'submit_button': 'primary'}) }} +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/utils.html b/app/templates/utils.html index 6d70c07..bdb0e31 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -9,4 +9,14 @@ Expand
    -{%- endmacro %} \ No newline at end of file +{%- endmacro %} + +{% macro render_form_field_errors(field) %} +{%- if field.errors %} + {%- for error in field.errors %} +

    {{error}}

    + {%- endfor %} +{%- elif field.description -%} +

    {{field.description|safe}}

    +{%- endif %} +{% endmacro %} \ No newline at end of file diff --git a/app/views/order.py b/app/views/order.py index a57027d..06d6b10 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -31,7 +31,7 @@ def order_create(): db.session.commit() return redirect(url_for('.order', id=order.id)) - return render_template('order_form.html', form=orderForm, url=url_for(".order_create")) + return render_template('orders_form.html', form=orderForm, url=url_for(".order_create")) @order_bp.route('/') @@ -76,7 +76,7 @@ def order_item_create(id): db.session.commit() flash('Ordered %s' % (item.product.name), 'info') return redirect(url_for('.order', id=id)) - return render_template('order_form.html', form=form, url=url_for(".order_item_create", id=id)) + return render_template('order_form.html', form=form, order=order) @order_bp.route('///delete') def delete_item(order_id, item_id): From c3d776a9855a783b5297a6f0315136ad413e8f1d Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Mon, 30 Mar 2015 00:01:07 +0200 Subject: [PATCH 43/68] added date pickers, only admins can set starttime --- app/forms.py | 4 +- .../css/bootstrap-datetimepicker.min.css | 366 ++++++++++++++++++ app/static/js/bootstrap-datetimepicker.min.js | 8 + app/static/js/moment.min.js | 7 + app/templates/order.html | 37 +- app/templates/order_form.html | 41 -- app/templates/orders.html | 63 ++- app/templates/orders_form.html | 9 - app/views/order.py | 47 +-- 9 files changed, 496 insertions(+), 86 deletions(-) create mode 100755 app/static/css/bootstrap-datetimepicker.min.css create mode 100755 app/static/js/bootstrap-datetimepicker.min.js create mode 100644 app/static/js/moment.min.js delete mode 100644 app/templates/order_form.html delete mode 100644 app/templates/orders_form.html diff --git a/app/forms.py b/app/forms.py index 4eae97d..a78eae6 100644 --- a/app/forms.py +++ b/app/forms.py @@ -12,8 +12,8 @@ __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) - stoptime = DateTimeField('Stoptime') + 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') def populate(self): diff --git a/app/static/css/bootstrap-datetimepicker.min.css b/app/static/css/bootstrap-datetimepicker.min.css new file mode 100755 index 0000000..c702161 --- /dev/null +++ b/app/static/css/bootstrap-datetimepicker.min.css @@ -0,0 +1,366 @@ +/*! + * Datetimepicker for Bootstrap 3 + * ! version : 4.7.14 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: 0 0 7px 7px solid transparent; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/app/static/js/bootstrap-datetimepicker.min.js b/app/static/js/bootstrap-datetimepicker.min.js new file mode 100755 index 0000000..a60af89 --- /dev/null +++ b/app/static/js/bootstrap-datetimepicker.min.js @@ -0,0 +1,8 @@ +/*! version : 4.7.14 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + Copyright (c) 2015 Jonathan Peterson + ========================================================= + */ +!function(a){"use strict";if("function"==typeof define&&define.amd)define(["jquery","moment"],a);else if("object"==typeof exports)a(require("jquery"),require("moment"));else{if("undefined"==typeof jQuery)throw"bootstrap-datetimepicker requires jQuery to be loaded first";if("undefined"==typeof moment)throw"bootstrap-datetimepicker requires Moment.js to be loaded first";a(jQuery,moment)}}(function(a,b){"use strict";if(!b)throw new Error("bootstrap-datetimepicker requires Moment.js to be loaded first");var c=function(c,d){var e,f,g,h,i,j={},k=b().startOf("d"),l=k.clone(),m=!0,n=!1,o=!1,p=0,q=[{clsName:"days",navFnc:"M",navStep:1},{clsName:"months",navFnc:"y",navStep:1},{clsName:"years",navFnc:"y",navStep:10}],r=["days","months","years"],s=["top","bottom","auto"],t=["left","right","auto"],u=["default","top","bottom"],v={up:38,38:"up",down:40,40:"down",left:37,37:"left",right:39,39:"right",tab:9,9:"tab",escape:27,27:"escape",enter:13,13:"enter",pageUp:33,33:"pageUp",pageDown:34,34:"pageDown",shift:16,16:"shift",control:17,17:"control",space:32,32:"space",t:84,84:"t","delete":46,46:"delete"},w={},x=function(a){if("string"!=typeof a||a.length>1)throw new TypeError("isEnabled expects a single character string parameter");switch(a){case"y":return-1!==g.indexOf("Y");case"M":return-1!==g.indexOf("M");case"d":return-1!==g.toLowerCase().indexOf("d");case"h":case"H":return-1!==g.toLowerCase().indexOf("h");case"m":return-1!==g.indexOf("m");case"s":return-1!==g.indexOf("s");default:return!1}},y=function(){return x("h")||x("m")||x("s")},z=function(){return x("y")||x("M")||x("d")},A=function(){var b=a("").append(a("").append(a("").addClass("prev").attr("data-action","previous").append(a("").addClass(d.icons.previous))).append(a("").addClass("picker-switch").attr("data-action","pickerSwitch").attr("colspan",d.calendarWeeks?"6":"5")).append(a("").addClass("next").attr("data-action","next").append(a("").addClass(d.icons.next)))),c=a("").append(a("").append(a("").attr("colspan",d.calendarWeeks?"8":"7")));return[a("
    ").addClass("datepicker-days").append(a("").addClass("table-condensed").append(b).append(a(""))),a("
    ").addClass("datepicker-months").append(a("
    ").addClass("table-condensed").append(b.clone()).append(c.clone())),a("
    ").addClass("datepicker-years").append(a("
    ").addClass("table-condensed").append(b.clone()).append(c.clone()))]},B=function(){var b=a(""),c=a(""),e=a("");return x("h")&&(b.append(a("
    ").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","incrementHours").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-hour").attr("data-time-component","hours").attr("data-action","showHours"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","decrementHours").append(a("").addClass(d.icons.down))))),x("m")&&(x("h")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","incrementMinutes").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-minute").attr("data-time-component","minutes").attr("data-action","showMinutes"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","decrementMinutes").append(a("").addClass(d.icons.down))))),x("s")&&(x("m")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","incrementSeconds").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-second").attr("data-time-component","seconds").attr("data-action","showSeconds"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1"}).addClass("btn").attr("data-action","decrementSeconds").append(a("").addClass(d.icons.down))))),f||(b.append(a("").addClass("separator")),c.append(a("").append(a("").addClass("separator"))),a("
    ").addClass("timepicker-picker").append(a("").addClass("table-condensed").append([b,c,e]))},C=function(){var b=a("
    ").addClass("timepicker-hours").append(a("
    ").addClass("table-condensed")),c=a("
    ").addClass("timepicker-minutes").append(a("
    ").addClass("table-condensed")),d=a("
    ").addClass("timepicker-seconds").append(a("
    ").addClass("table-condensed")),e=[B()];return x("h")&&e.push(b),x("m")&&e.push(c),x("s")&&e.push(d),e},D=function(){var b=[];return d.showTodayButton&&b.push(a("
    ").append(a("").attr("data-action","today").append(a("").addClass(d.icons.today)))),!d.sideBySide&&z()&&y()&&b.push(a("").append(a("").attr("data-action","togglePicker").append(a("").addClass(d.icons.time)))),d.showClear&&b.push(a("").append(a("").attr("data-action","clear").append(a("").addClass(d.icons.clear)))),d.showClose&&b.push(a("").append(a("").attr("data-action","close").append(a("").addClass(d.icons.close)))),a("").addClass("table-condensed").append(a("").append(a("").append(b)))},E=function(){var b=a("
    ").addClass("bootstrap-datetimepicker-widget dropdown-menu"),c=a("
    ").addClass("datepicker").append(A()),e=a("
    ").addClass("timepicker").append(C()),g=a("
      ").addClass("list-unstyled"),h=a("
    • ").addClass("picker-switch"+(d.collapse?" accordion-toggle":"")).append(D());return d.inline&&b.removeClass("dropdown-menu"),f&&b.addClass("usetwentyfour"),d.sideBySide&&z()&&y()?(b.addClass("timepicker-sbs"),b.append(a("
      ").addClass("row").append(c.addClass("col-sm-6")).append(e.addClass("col-sm-6"))),b.append(h),b):("top"===d.toolbarPlacement&&g.append(h),z()&&g.append(a("
    • ").addClass(d.collapse&&y()?"collapse in":"").append(c)),"default"===d.toolbarPlacement&&g.append(h),y()&&g.append(a("
    • ").addClass(d.collapse&&z()?"collapse":"").append(e)),"bottom"===d.toolbarPlacement&&g.append(h),b.append(g))},F=function(){var b,e={};return b=c.is("input")||d.inline?c.data():c.find("input").data(),b.dateOptions&&b.dateOptions instanceof Object&&(e=a.extend(!0,e,b.dateOptions)),a.each(d,function(a){var c="date"+a.charAt(0).toUpperCase()+a.slice(1);void 0!==b[c]&&(e[a]=b[c])}),e},G=function(){var b,e=(n||c).position(),f=(n||c).offset(),g=d.widgetPositioning.vertical,h=d.widgetPositioning.horizontal;if(d.widgetParent)b=d.widgetParent.append(o);else if(c.is("input"))b=c.parent().append(o);else{if(d.inline)return void(b=c.append(o));b=c,c.children().first().after(o)}if("auto"===g&&(g=f.top+1.5*o.height()>=a(window).height()+a(window).scrollTop()&&o.height()+c.outerHeight()a(window).width()?"right":"left"),"top"===g?o.addClass("top").removeClass("bottom"):o.addClass("bottom").removeClass("top"),"right"===h?o.addClass("pull-right"):o.removeClass("pull-right"),"relative"!==b.css("position")&&(b=b.parents().filter(function(){return"relative"===a(this).css("position")}).first()),0===b.length)throw new Error("datetimepicker component should be placed within a relative positioned container");o.css({top:"top"===g?"auto":e.top+c.outerHeight(),bottom:"top"===g?e.top+c.outerHeight():"auto",left:"left"===h?b.css("padding-left"):"auto",right:"left"===h?"auto":b.width()-c.outerWidth()})},H=function(a){"dp.change"===a.type&&(a.date&&a.date.isSame(a.oldDate)||!a.date&&!a.oldDate)||c.trigger(a)},I=function(a){o&&(a&&(i=Math.max(p,Math.min(2,i+a))),o.find(".datepicker > div").hide().filter(".datepicker-"+q[i].clsName).show())},J=function(){var b=a("
    "),c=l.clone().startOf("w");for(d.calendarWeeks===!0&&b.append(a(""),d.calendarWeeks&&e.append('"),i.push(e)),f="",c.isBefore(l,"M")&&(f+=" old"),c.isAfter(l,"M")&&(f+=" new"),c.isSame(k,"d")&&!m&&(f+=" active"),M(c,"d")||(f+=" disabled"),c.isSame(b(),"d")&&(f+=" today"),(0===c.day()||6===c.day())&&(f+=" weekend"),e.append('"),c.add(1,"d");g.find("tbody").empty().append(i),O(),P()}},R=function(){var b=o.find(".timepicker-hours table"),c=l.clone().startOf("d"),d=[],e=a("");for(l.hour()>11&&!f&&c.hour(12);c.isSame(l,"d")&&(f||l.hour()<12&&c.hour()<12||l.hour()>11);)c.hour()%4===0&&(e=a(""),d.push(e)),e.append('"),c.add(1,"h");b.empty().append(d)},S=function(){for(var b=o.find(".timepicker-minutes table"),c=l.clone().startOf("h"),e=[],f=a(""),g=1===d.stepping?5:d.stepping;l.isSame(c,"h");)c.minute()%(4*g)===0&&(f=a(""),e.push(f)),f.append('"),c.add(g,"m");b.empty().append(e)},T=function(){for(var b=o.find(".timepicker-seconds table"),c=l.clone().startOf("m"),d=[],e=a("");l.isSame(c,"m");)c.second()%20===0&&(e=a(""),d.push(e)),e.append('"),c.add(5,"s");b.empty().append(d)},U=function(){var a=o.find(".timepicker span[data-time-component]");f||o.find(".timepicker [data-action=togglePeriod]").text(k.format("A")),a.filter("[data-time-component=hours]").text(k.format(f?"HH":"hh")),a.filter("[data-time-component=minutes]").text(k.format("mm")),a.filter("[data-time-component=seconds]").text(k.format("ss")),R(),S(),T()},V=function(){o&&(Q(),U())},W=function(a){var b=m?null:k;return a?(a=a.clone().locale(d.locale),1!==d.stepping&&a.minutes(Math.round(a.minutes()/d.stepping)*d.stepping%60).seconds(0),void(M(a)?(k=a,l=k.clone(),e.val(k.format(g)),c.data("date",k.format(g)),V(),m=!1,H({type:"dp.change",date:k.clone(),oldDate:b})):(d.keepInvalid||e.val(m?"":k.format(g)),H({type:"dp.error",date:a})))):(m=!0,e.val(""),c.data("date",""),H({type:"dp.change",date:null,oldDate:b}),void V())},X=function(){var b=!1;return o?(o.find(".collapse").each(function(){var c=a(this).data("collapse");return c&&c.transitioning?(b=!0,!1):!0}),b?j:(n&&n.hasClass("btn")&&n.toggleClass("active"),o.hide(),a(window).off("resize",G),o.off("click","[data-action]"),o.off("mousedown",!1),o.remove(),o=!1,H({type:"dp.hide",date:k.clone()}),j)):j},Y=function(){W(null)},Z={next:function(){l.add(q[i].navStep,q[i].navFnc),Q()},previous:function(){l.subtract(q[i].navStep,q[i].navFnc),Q()},pickerSwitch:function(){I(1)},selectMonth:function(b){var c=a(b.target).closest("tbody").find("span").index(a(b.target));l.month(c),i===p?(W(k.clone().year(l.year()).month(l.month())),d.inline||X()):(I(-1),Q())},selectYear:function(b){var c=parseInt(a(b.target).text(),10)||0;l.year(c),i===p?(W(k.clone().year(l.year())),d.inline||X()):(I(-1),Q())},selectDay:function(b){var c=l.clone();a(b.target).is(".old")&&c.subtract(1,"M"),a(b.target).is(".new")&&c.add(1,"M"),W(c.date(parseInt(a(b.target).text(),10))),y()||d.keepOpen||d.inline||X()},incrementHours:function(){W(k.clone().add(1,"h"))},incrementMinutes:function(){W(k.clone().add(d.stepping,"m"))},incrementSeconds:function(){W(k.clone().add(1,"s"))},decrementHours:function(){W(k.clone().subtract(1,"h"))},decrementMinutes:function(){W(k.clone().subtract(d.stepping,"m"))},decrementSeconds:function(){W(k.clone().subtract(1,"s"))},togglePeriod:function(){W(k.clone().add(k.hours()>=12?-12:12,"h"))},togglePicker:function(b){var c,e=a(b.target),f=e.closest("ul"),g=f.find(".in"),h=f.find(".collapse:not(.in)");if(g&&g.length){if(c=g.data("collapse"),c&&c.transitioning)return;g.collapse?(g.collapse("hide"),h.collapse("show")):(g.removeClass("in"),h.addClass("in")),e.is("span")?e.toggleClass(d.icons.time+" "+d.icons.date):e.find("span").toggleClass(d.icons.time+" "+d.icons.date)}},showPicker:function(){o.find(".timepicker > div:not(.timepicker-picker)").hide(),o.find(".timepicker .timepicker-picker").show()},showHours:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-hours").show()},showMinutes:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-minutes").show()},showSeconds:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-seconds").show()},selectHour:function(b){var c=parseInt(a(b.target).text(),10);f||(k.hours()>=12?12!==c&&(c+=12):12===c&&(c=0)),W(k.clone().hours(c)),Z.showPicker.call(j)},selectMinute:function(b){W(k.clone().minutes(parseInt(a(b.target).text(),10))),Z.showPicker.call(j)},selectSecond:function(b){W(k.clone().seconds(parseInt(a(b.target).text(),10))),Z.showPicker.call(j)},clear:Y,today:function(){W(b())},close:X},$=function(b){return a(b.currentTarget).is(".disabled")?!1:(Z[a(b.currentTarget).data("action")].apply(j,arguments),!1)},_=function(){var c,f={year:function(a){return a.month(0).date(1).hours(0).seconds(0).minutes(0)},month:function(a){return a.date(1).hours(0).seconds(0).minutes(0)},day:function(a){return a.hours(0).seconds(0).minutes(0)},hour:function(a){return a.seconds(0).minutes(0)},minute:function(a){return a.seconds(0)}};return e.prop("disabled")||!d.ignoreReadonly&&e.prop("readonly")||o?j:(d.useCurrent&&m&&(e.is("input")&&0===e.val().trim().length||d.inline)&&(c=b(),"string"==typeof d.useCurrent&&(c=f[d.useCurrent](c)),W(c)),o=E(),J(),N(),o.find(".timepicker-hours").hide(),o.find(".timepicker-minutes").hide(),o.find(".timepicker-seconds").hide(),V(),I(),a(window).on("resize",G),o.on("click","[data-action]",$),o.on("mousedown",!1),n&&n.hasClass("btn")&&n.toggleClass("active"),o.show(),G(),e.is(":focus")||e.focus(),H({type:"dp.show"}),j)},ab=function(){return o?X():_()},bb=function(a){return a=b.isMoment(a)||a instanceof Date?b(a):b(a,h,d.useStrict),a.locale(d.locale),a},cb=function(a){var b,c,e,f,g=null,h=[],i={},k=a.which,l="p";w[k]=l;for(b in w)w.hasOwnProperty(b)&&w[b]===l&&(h.push(b),parseInt(b,10)!==k&&(i[b]=!0));for(b in d.keyBinds)if(d.keyBinds.hasOwnProperty(b)&&"function"==typeof d.keyBinds[b]&&(e=b.split(" "),e.length===h.length&&v[k]===e[e.length-1])){for(f=!0,c=e.length-2;c>=0;c--)if(!(v[e[c]]in i)){f=!1;break}if(f){g=d.keyBinds[b];break}}g&&(g.call(j,o),a.stopPropagation(),a.preventDefault())},db=function(a){w[a.which]="r",a.stopPropagation(),a.preventDefault()},eb=function(b){var c=a(b.target).val().trim(),d=c?bb(c):null;return W(d),b.stopImmediatePropagation(),!1},fb=function(){e.on({change:eb,blur:d.debug?"":X,keydown:cb,keyup:db}),c.is("input")?e.on({focus:_}):n&&(n.on("click",ab),n.on("mousedown",!1))},gb=function(){e.off({change:eb,blur:X,keydown:cb,keyup:db}),c.is("input")?e.off({focus:_}):n&&(n.off("click",ab),n.off("mousedown",!1))},hb=function(b){var c={};return a.each(b,function(){var a=bb(this);a.isValid()&&(c[a.format("YYYY-MM-DD")]=!0)}),Object.keys(c).length?c:!1},ib=function(){var a=d.format||"L LT";g=a.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){var b=k.localeData().longDateFormat(a)||a;return b.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){return k.localeData().longDateFormat(a)||a})}),h=d.extraFormats?d.extraFormats.slice():[],h.indexOf(a)<0&&h.indexOf(g)<0&&h.push(g),f=g.toLowerCase().indexOf("a")<1&&g.indexOf("h")<1,x("y")&&(p=2),x("M")&&(p=1),x("d")&&(p=0),i=Math.max(p,i),m||W(k)};if(j.destroy=function(){X(),gb(),c.removeData("DateTimePicker"),c.removeData("date")},j.toggle=ab,j.show=_,j.hide=X,j.disable=function(){return X(),n&&n.hasClass("btn")&&n.addClass("disabled"),e.prop("disabled",!0),j},j.enable=function(){return n&&n.hasClass("btn")&&n.removeClass("disabled"),e.prop("disabled",!1),j},j.ignoreReadonly=function(a){if(0===arguments.length)return d.ignoreReadonly;if("boolean"!=typeof a)throw new TypeError("ignoreReadonly () expects a boolean parameter");return d.ignoreReadonly=a,j},j.options=function(b){if(0===arguments.length)return a.extend(!0,{},d);if(!(b instanceof Object))throw new TypeError("options() options parameter should be an object");return a.extend(!0,d,b),a.each(d,function(a,b){if(void 0===j[a])throw new TypeError("option "+a+" is not recognized!");j[a](b)}),j},j.date=function(a){if(0===arguments.length)return m?null:k.clone();if(!(null===a||"string"==typeof a||b.isMoment(a)||a instanceof Date))throw new TypeError("date() parameter must be one of [null, string, moment or Date]");return W(null===a?null:bb(a)),j},j.format=function(a){if(0===arguments.length)return d.format;if("string"!=typeof a&&("boolean"!=typeof a||a!==!1))throw new TypeError("format() expects a sting or boolean:false parameter "+a);return d.format=a,g&&ib(),j},j.dayViewHeaderFormat=function(a){if(0===arguments.length)return d.dayViewHeaderFormat;if("string"!=typeof a)throw new TypeError("dayViewHeaderFormat() expects a string parameter");return d.dayViewHeaderFormat=a,j},j.extraFormats=function(a){if(0===arguments.length)return d.extraFormats;if(a!==!1&&!(a instanceof Array))throw new TypeError("extraFormats() expects an array or false parameter");return d.extraFormats=a,h&&ib(),j},j.disabledDates=function(b){if(0===arguments.length)return d.disabledDates?a.extend({},d.disabledDates):d.disabledDates;if(!b)return d.disabledDates=!1,V(),j;if(!(b instanceof Array))throw new TypeError("disabledDates() expects an array parameter");return d.disabledDates=hb(b),d.enabledDates=!1,V(),j},j.enabledDates=function(b){if(0===arguments.length)return d.enabledDates?a.extend({},d.enabledDates):d.enabledDates;if(!b)return d.enabledDates=!1,V(),j;if(!(b instanceof Array))throw new TypeError("enabledDates() expects an array parameter");return d.enabledDates=hb(b),d.disabledDates=!1,V(),j},j.daysOfWeekDisabled=function(a){if(0===arguments.length)return d.daysOfWeekDisabled.splice(0);if(!(a instanceof Array))throw new TypeError("daysOfWeekDisabled() expects an array parameter");return d.daysOfWeekDisabled=a.reduce(function(a,b){return b=parseInt(b,10),b>6||0>b||isNaN(b)?a:(-1===a.indexOf(b)&&a.push(b),a)},[]).sort(),V(),j},j.maxDate=function(a){if(0===arguments.length)return d.maxDate?d.maxDate.clone():d.maxDate;if("boolean"==typeof a&&a===!1)return d.maxDate=!1,V(),j;"string"==typeof a&&("now"===a||"moment"===a)&&(a=b());var c=bb(a);if(!c.isValid())throw new TypeError("maxDate() Could not parse date parameter: "+a);if(d.minDate&&c.isBefore(d.minDate))throw new TypeError("maxDate() date parameter is before options.minDate: "+c.format(g));return d.maxDate=c,d.maxDate.isBefore(a)&&W(d.maxDate),l.isAfter(c)&&(l=c.clone()),V(),j},j.minDate=function(a){if(0===arguments.length)return d.minDate?d.minDate.clone():d.minDate;if("boolean"==typeof a&&a===!1)return d.minDate=!1,V(),j;"string"==typeof a&&("now"===a||"moment"===a)&&(a=b());var c=bb(a);if(!c.isValid())throw new TypeError("minDate() Could not parse date parameter: "+a);if(d.maxDate&&c.isAfter(d.maxDate))throw new TypeError("minDate() date parameter is after options.maxDate: "+c.format(g));return d.minDate=c,d.minDate.isAfter(a)&&W(d.minDate),l.isBefore(c)&&(l=c.clone()),V(),j},j.defaultDate=function(a){if(0===arguments.length)return d.defaultDate?d.defaultDate.clone():d.defaultDate;if(!a)return d.defaultDate=!1,j;"string"==typeof a&&("now"===a||"moment"===a)&&(a=b());var c=bb(a);if(!c.isValid())throw new TypeError("defaultDate() Could not parse date parameter: "+a);if(!M(c))throw new TypeError("defaultDate() date passed is invalid according to component setup validations");return d.defaultDate=c,d.defaultDate&&""===e.val().trim()&&void 0===e.attr("placeholder")&&W(d.defaultDate),j},j.locale=function(a){if(0===arguments.length)return d.locale;if(!b.localeData(a))throw new TypeError("locale() locale "+a+" is not loaded from moment locales!");return d.locale=a,k.locale(d.locale),l.locale(d.locale),g&&ib(),o&&(X(),_()),j},j.stepping=function(a){return 0===arguments.length?d.stepping:(a=parseInt(a,10),(isNaN(a)||1>a)&&(a=1),d.stepping=a,j)},j.useCurrent=function(a){var b=["year","month","day","hour","minute"];if(0===arguments.length)return d.useCurrent;if("boolean"!=typeof a&&"string"!=typeof a)throw new TypeError("useCurrent() expects a boolean or string parameter");if("string"==typeof a&&-1===b.indexOf(a.toLowerCase()))throw new TypeError("useCurrent() expects a string parameter of "+b.join(", "));return d.useCurrent=a,j},j.collapse=function(a){if(0===arguments.length)return d.collapse;if("boolean"!=typeof a)throw new TypeError("collapse() expects a boolean parameter");return d.collapse===a?j:(d.collapse=a,o&&(X(),_()),j)},j.icons=function(b){if(0===arguments.length)return a.extend({},d.icons);if(!(b instanceof Object))throw new TypeError("icons() expects parameter to be an Object");return a.extend(d.icons,b),o&&(X(),_()),j},j.useStrict=function(a){if(0===arguments.length)return d.useStrict;if("boolean"!=typeof a)throw new TypeError("useStrict() expects a boolean parameter");return d.useStrict=a,j},j.sideBySide=function(a){if(0===arguments.length)return d.sideBySide;if("boolean"!=typeof a)throw new TypeError("sideBySide() expects a boolean parameter");return d.sideBySide=a,o&&(X(),_()),j},j.viewMode=function(a){if(0===arguments.length)return d.viewMode;if("string"!=typeof a)throw new TypeError("viewMode() expects a string parameter");if(-1===r.indexOf(a))throw new TypeError("viewMode() parameter must be one of ("+r.join(", ")+") value");return d.viewMode=a,i=Math.max(r.indexOf(a),p),I(),j},j.toolbarPlacement=function(a){if(0===arguments.length)return d.toolbarPlacement;if("string"!=typeof a)throw new TypeError("toolbarPlacement() expects a string parameter");if(-1===u.indexOf(a))throw new TypeError("toolbarPlacement() parameter must be one of ("+u.join(", ")+") value");return d.toolbarPlacement=a,o&&(X(),_()),j},j.widgetPositioning=function(b){if(0===arguments.length)return a.extend({},d.widgetPositioning);if("[object Object]"!=={}.toString.call(b))throw new TypeError("widgetPositioning() expects an object variable");if(b.horizontal){if("string"!=typeof b.horizontal)throw new TypeError("widgetPositioning() horizontal variable must be a string");if(b.horizontal=b.horizontal.toLowerCase(),-1===t.indexOf(b.horizontal))throw new TypeError("widgetPositioning() expects horizontal parameter to be one of ("+t.join(", ")+")");d.widgetPositioning.horizontal=b.horizontal}if(b.vertical){if("string"!=typeof b.vertical)throw new TypeError("widgetPositioning() vertical variable must be a string");if(b.vertical=b.vertical.toLowerCase(),-1===s.indexOf(b.vertical))throw new TypeError("widgetPositioning() expects vertical parameter to be one of ("+s.join(", ")+")");d.widgetPositioning.vertical=b.vertical}return V(),j},j.calendarWeeks=function(a){if(0===arguments.length)return d.calendarWeeks;if("boolean"!=typeof a)throw new TypeError("calendarWeeks() expects parameter to be a boolean value");return d.calendarWeeks=a,V(),j},j.showTodayButton=function(a){if(0===arguments.length)return d.showTodayButton;if("boolean"!=typeof a)throw new TypeError("showTodayButton() expects a boolean parameter");return d.showTodayButton=a,o&&(X(),_()),j},j.showClear=function(a){if(0===arguments.length)return d.showClear;if("boolean"!=typeof a)throw new TypeError("showClear() expects a boolean parameter");return d.showClear=a,o&&(X(),_()),j},j.widgetParent=function(b){if(0===arguments.length)return d.widgetParent;if("string"==typeof b&&(b=a(b)),null!==b&&"string"!=typeof b&&!(b instanceof a))throw new TypeError("widgetParent() expects a string or a jQuery object parameter");return d.widgetParent=b,o&&(X(),_()),j},j.keepOpen=function(a){if(0===arguments.length)return d.keepOpen;if("boolean"!=typeof a)throw new TypeError("keepOpen() expects a boolean parameter");return d.keepOpen=a,j},j.inline=function(a){if(0===arguments.length)return d.inline;if("boolean"!=typeof a)throw new TypeError("inline() expects a boolean parameter");return d.inline=a,j},j.clear=function(){return Y(),j},j.keyBinds=function(a){return d.keyBinds=a,j},j.debug=function(a){if("boolean"!=typeof a)throw new TypeError("debug() expects a boolean parameter");return d.debug=a,j},j.showClose=function(a){if(0===arguments.length)return d.showClose;if("boolean"!=typeof a)throw new TypeError("showClose() expects a boolean parameter");return d.showClose=a,j},j.keepInvalid=function(a){if(0===arguments.length)return d.keepInvalid;if("boolean"!=typeof a)throw new TypeError("keepInvalid() expects a boolean parameter");return d.keepInvalid=a,j},j.datepickerInput=function(a){if(0===arguments.length)return d.datepickerInput;if("string"!=typeof a)throw new TypeError("datepickerInput() expects a string parameter");return d.datepickerInput=a,j},c.is("input"))e=c;else if(e=c.find(d.datepickerInput),0===e.size())e=c.find("input");else if(!e.is("input"))throw new Error('CSS class "'+d.datepickerInput+'" cannot be applied to non input element');if(c.hasClass("input-group")&&(n=c.find(0===c.find(".datepickerbutton").size()?'[class^="input-group-"]':".datepickerbutton")),!d.inline&&!e.is("input"))throw new Error("Could not initialize DateTimePicker without an input element");return a.extend(!0,d,F()),j.options(d),ib(),fb(),e.prop("disabled")&&j.disable(),e.is("input")&&0!==e.val().trim().length?W(bb(e.val().trim())):d.defaultDate&&void 0===e.attr("placeholder")&&W(d.defaultDate),d.inline&&_(),j};a.fn.datetimepicker=function(b){return this.each(function(){var d=a(this);d.data("DateTimePicker")||(b=a.extend(!0,{},a.fn.datetimepicker.defaults,b),d.data("DateTimePicker",c(d,b)))})},a.fn.datetimepicker.defaults={format:!1,dayViewHeaderFormat:"MMMM YYYY",extraFormats:!1,stepping:1,minDate:!1,maxDate:!1,useCurrent:!0,collapse:!0,locale:b.locale(),defaultDate:!1,disabledDates:!1,enabledDates:!1,icons:{time:"glyphicon glyphicon-time",date:"glyphicon glyphicon-calendar",up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down",previous:"glyphicon glyphicon-chevron-left",next:"glyphicon glyphicon-chevron-right",today:"glyphicon glyphicon-screenshot",clear:"glyphicon glyphicon-trash",close:"glyphicon glyphicon-remove"},useStrict:!1,sideBySide:!1,daysOfWeekDisabled:[],calendarWeeks:!1,viewMode:"days",toolbarPlacement:"default",showTodayButton:!1,showClear:!1,showClose:!1,widgetPositioning:{horizontal:"auto",vertical:"auto"},widgetParent:null,ignoreReadonly:!1,keepOpen:!1,inline:!1,keepInvalid:!1,datepickerInput:".datepickerinput",keyBinds:{up:function(a){if(a){var c=this.date()||b();this.date(a.find(".datepicker").is(":visible")?c.clone().subtract(7,"d"):c.clone().add(1,"m"))}},down:function(a){if(!a)return void this.show();var c=this.date()||b();this.date(a.find(".datepicker").is(":visible")?c.clone().add(7,"d"):c.clone().subtract(1,"m"))},"control up":function(a){if(a){var c=this.date()||b();this.date(a.find(".datepicker").is(":visible")?c.clone().subtract(1,"y"):c.clone().add(1,"h"))}},"control down":function(a){if(a){var c=this.date()||b();this.date(a.find(".datepicker").is(":visible")?c.clone().add(1,"y"):c.clone().subtract(1,"h"))}},left:function(a){if(a){var c=this.date()||b();a.find(".datepicker").is(":visible")&&this.date(c.clone().subtract(1,"d"))}},right:function(a){if(a){var c=this.date()||b();a.find(".datepicker").is(":visible")&&this.date(c.clone().add(1,"d"))}},pageUp:function(a){if(a){var c=this.date()||b();a.find(".datepicker").is(":visible")&&this.date(c.clone().subtract(1,"M"))}},pageDown:function(a){if(a){var c=this.date()||b();a.find(".datepicker").is(":visible")&&this.date(c.clone().add(1,"M"))}},enter:function(){this.hide()},escape:function(){this.hide()},"control space":function(a){a.find(".timepicker").is(":visible")&&a.find('.btn[data-action="togglePeriod"]').click()},t:function(){this.date(b())},"delete":function(){this.clear()}},debug:!1}}); \ No newline at end of file diff --git a/app/static/js/moment.min.js b/app/static/js/moment.min.js new file mode 100644 index 0000000..024d488 --- /dev/null +++ b/app/static/js/moment.min.js @@ -0,0 +1,7 @@ +//! moment.js +//! version : 2.9.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +(function(a){function b(a,b,c){switch(arguments.length){case 2:return null!=a?a:b;case 3:return null!=a?a:null!=b?b:c;default:throw new Error("Implement me")}}function c(a,b){return Bb.call(a,b)}function d(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function e(a){vb.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+a)}function f(a,b){var c=!0;return o(function(){return c&&(e(a),c=!1),b.apply(this,arguments)},b)}function g(a,b){sc[a]||(e(b),sc[a]=!0)}function h(a,b){return function(c){return r(a.call(this,c),b)}}function i(a,b){return function(c){return this.localeData().ordinal(a.call(this,c),b)}}function j(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function k(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function l(){}function m(a,b){b!==!1&&H(a),p(this,a),this._d=new Date(+a._d),uc===!1&&(uc=!0,vb.updateOffset(this),uc=!1)}function n(a){var b=A(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=vb.localeData(),this._bubble()}function o(a,b){for(var d in b)c(b,d)&&(a[d]=b[d]);return c(b,"toString")&&(a.toString=b.toString),c(b,"valueOf")&&(a.valueOf=b.valueOf),a}function p(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Kb.length>0)for(c in Kb)d=Kb[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&C(a[d])!==C(b[d]))&&g++;return g+f}function z(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=lc[a]||mc[b]||b}return a}function A(a){var b,d,e={};for(d in a)c(a,d)&&(b=z(d),b&&(e[b]=a[d]));return e}function B(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}vb[b]=function(e,f){var g,h,i=vb._locale[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=vb().utc().set(d,a);return i.call(vb._locale,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function C(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function D(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function E(a,b,c){return jb(vb([a,11,31+b-c]),b,c).week}function F(a){return G(a)?366:365}function G(a){return a%4===0&&a%100!==0||a%400===0}function H(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[Db]<0||a._a[Db]>11?Db:a._a[Eb]<1||a._a[Eb]>D(a._a[Cb],a._a[Db])?Eb:a._a[Fb]<0||a._a[Fb]>24||24===a._a[Fb]&&(0!==a._a[Gb]||0!==a._a[Hb]||0!==a._a[Ib])?Fb:a._a[Gb]<0||a._a[Gb]>59?Gb:a._a[Hb]<0||a._a[Hb]>59?Hb:a._a[Ib]<0||a._a[Ib]>999?Ib:-1,a._pf._overflowDayOfYear&&(Cb>b||b>Eb)&&(b=Eb),a._pf.overflow=b)}function I(b){return null==b._isValid&&(b._isValid=!isNaN(b._d.getTime())&&b._pf.overflow<0&&!b._pf.empty&&!b._pf.invalidMonth&&!b._pf.nullInput&&!b._pf.invalidFormat&&!b._pf.userInvalidated,b._strict&&(b._isValid=b._isValid&&0===b._pf.charsLeftOver&&0===b._pf.unusedTokens.length&&b._pf.bigHour===a)),b._isValid}function J(a){return a?a.toLowerCase().replace("_","-"):a}function K(a){for(var b,c,d,e,f=0;f0;){if(d=L(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&y(e,c,!0)>=b-1)break;b--}f++}return null}function L(a){var b=null;if(!Jb[a]&&Lb)try{b=vb.locale(),require("./locale/"+a),vb.locale(b)}catch(c){}return Jb[a]}function M(a,b){var c,d;return b._isUTC?(c=b.clone(),d=(vb.isMoment(a)||x(a)?+a:+vb(a))-+c,c._d.setTime(+c._d+d),vb.updateOffset(c,!1),c):vb(a).local()}function N(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function O(a){var b,c,d=a.match(Pb);for(b=0,c=d.length;c>b;b++)d[b]=rc[d[b]]?rc[d[b]]:N(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function P(a,b){return a.isValid()?(b=Q(b,a.localeData()),nc[b]||(nc[b]=O(b)),nc[b](a)):a.localeData().invalidDate()}function Q(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Qb.lastIndex=0;d>=0&&Qb.test(a);)a=a.replace(Qb,c),Qb.lastIndex=0,d-=1;return a}function R(a,b){var c,d=b._strict;switch(a){case"Q":return _b;case"DDDD":return bc;case"YYYY":case"GGGG":case"gggg":return d?cc:Tb;case"Y":case"G":case"g":return ec;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?dc:Ub;case"S":if(d)return _b;case"SS":if(d)return ac;case"SSS":if(d)return bc;case"DDD":return Sb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Wb;case"a":case"A":return b._locale._meridiemParse;case"x":return Zb;case"X":return $b;case"Z":case"ZZ":return Xb;case"T":return Yb;case"SSSS":return Vb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?ac:Rb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Rb;case"Do":return d?b._locale._ordinalParse:b._locale._ordinalParseLenient;default:return c=new RegExp($(Z(a.replace("\\","")),"i"))}}function S(a){a=a||"";var b=a.match(Xb)||[],c=b[b.length-1]||[],d=(c+"").match(jc)||["-",0,0],e=+(60*d[1])+C(d[2]);return"+"===d[0]?e:-e}function T(a,b,c){var d,e=c._a;switch(a){case"Q":null!=b&&(e[Db]=3*(C(b)-1));break;case"M":case"MM":null!=b&&(e[Db]=C(b)-1);break;case"MMM":case"MMMM":d=c._locale.monthsParse(b,a,c._strict),null!=d?e[Db]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[Eb]=C(b));break;case"Do":null!=b&&(e[Eb]=C(parseInt(b.match(/\d{1,2}/)[0],10)));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=C(b));break;case"YY":e[Cb]=vb.parseTwoDigitYear(b);break;case"YYYY":case"YYYYY":case"YYYYYY":e[Cb]=C(b);break;case"a":case"A":c._meridiem=b;break;case"h":case"hh":c._pf.bigHour=!0;case"H":case"HH":e[Fb]=C(b);break;case"m":case"mm":e[Gb]=C(b);break;case"s":case"ss":e[Hb]=C(b);break;case"S":case"SS":case"SSS":case"SSSS":e[Ib]=C(1e3*("0."+b));break;case"x":c._d=new Date(C(b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=S(b);break;case"dd":case"ddd":case"dddd":d=c._locale.weekdaysParse(b),null!=d?(c._w=c._w||{},c._w.d=d):c._pf.invalidWeekday=b;break;case"w":case"ww":case"W":case"WW":case"d":case"e":case"E":a=a.substr(0,1);case"gggg":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=C(b));break;case"gg":case"GG":c._w=c._w||{},c._w[a]=vb.parseTwoDigitYear(b)}}function U(a){var c,d,e,f,g,h,i;c=a._w,null!=c.GG||null!=c.W||null!=c.E?(g=1,h=4,d=b(c.GG,a._a[Cb],jb(vb(),1,4).year),e=b(c.W,1),f=b(c.E,1)):(g=a._locale._week.dow,h=a._locale._week.doy,d=b(c.gg,a._a[Cb],jb(vb(),g,h).year),e=b(c.w,1),null!=c.d?(f=c.d,g>f&&++e):f=null!=c.e?c.e+g:g),i=kb(d,e,f,h,g),a._a[Cb]=i.year,a._dayOfYear=i.dayOfYear}function V(a){var c,d,e,f,g=[];if(!a._d){for(e=X(a),a._w&&null==a._a[Eb]&&null==a._a[Db]&&U(a),a._dayOfYear&&(f=b(a._a[Cb],e[Cb]),a._dayOfYear>F(f)&&(a._pf._overflowDayOfYear=!0),d=fb(f,0,a._dayOfYear),a._a[Db]=d.getUTCMonth(),a._a[Eb]=d.getUTCDate()),c=0;3>c&&null==a._a[c];++c)a._a[c]=g[c]=e[c];for(;7>c;c++)a._a[c]=g[c]=null==a._a[c]?2===c?1:0:a._a[c];24===a._a[Fb]&&0===a._a[Gb]&&0===a._a[Hb]&&0===a._a[Ib]&&(a._nextDay=!0,a._a[Fb]=0),a._d=(a._useUTC?fb:eb).apply(null,g),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Fb]=24)}}function W(a){var b;a._d||(b=A(a._i),a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],V(a))}function X(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function Y(b){if(b._f===vb.ISO_8601)return void ab(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Q(b._f,b._locale).match(Pb)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),rc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),T(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[Fb]<=12&&(b._pf.bigHour=a),b._a[Fb]=k(b._locale,b._a[Fb],b._meridiem),V(b),H(b)}function Z(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function $(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function _(a){var b,c,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,c=b));o(a,c||b)}function ab(a){var b,c,d=a._i,e=fc.exec(d);if(e){for(a._pf.iso=!0,b=0,c=hc.length;c>b;b++)if(hc[b][1].exec(d)){a._f=hc[b][0]+(e[6]||" ");break}for(b=0,c=ic.length;c>b;b++)if(ic[b][1].exec(d)){a._f+=ic[b][0];break}d.match(Xb)&&(a._f+="Z"),Y(a)}else a._isValid=!1}function bb(a){ab(a),a._isValid===!1&&(delete a._isValid,vb.createFromInputFallback(a))}function cb(a,b){var c,d=[];for(c=0;ca&&h.setFullYear(a),h}function fb(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function gb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function hb(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function ib(a,b,c){var d=vb.duration(a).abs(),e=Ab(d.as("s")),f=Ab(d.as("m")),g=Ab(d.as("h")),h=Ab(d.as("d")),i=Ab(d.as("M")),j=Ab(d.as("y")),k=e0,k[4]=c,hb.apply({},k)}function jb(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=vb(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function kb(a,b,c,d,e){var f,g,h=fb(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:F(a-1)+g}}function lb(b){var c,d=b._i,e=b._f;return b._locale=b._locale||vb.localeData(b._l),null===d||e===a&&""===d?vb.invalid({nullInput:!0}):("string"==typeof d&&(b._i=d=b._locale.preparse(d)),vb.isMoment(d)?new m(d,!0):(e?w(e)?_(b):Y(b):db(b),c=new m(b),c._nextDay&&(c.add(1,"d"),c._nextDay=a),c))}function mb(a,b){var c,d;if(1===b.length&&w(b[0])&&(b=b[0]),!b.length)return vb();for(c=b[0],d=1;d=0?"+":"-";return b+r(Math.abs(a),6)},gg:function(){return r(this.weekYear()%100,2)},gggg:function(){return r(this.weekYear(),4)},ggggg:function(){return r(this.weekYear(),5)},GG:function(){return r(this.isoWeekYear()%100,2)},GGGG:function(){return r(this.isoWeekYear(),4)},GGGGG:function(){return r(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return C(this.milliseconds()/100)},SS:function(){return r(C(this.milliseconds()/10),2)},SSS:function(){return r(this.milliseconds(),3)},SSSS:function(){return r(this.milliseconds(),3)},Z:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+":"+r(C(a)%60,2)},ZZ:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+r(C(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},x:function(){return this.valueOf()},X:function(){return this.unix()},Q:function(){return this.quarter()}},sc={},tc=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"],uc=!1;pc.length;)xb=pc.pop(),rc[xb+"o"]=i(rc[xb],xb);for(;qc.length;)xb=qc.pop(),rc[xb+xb]=h(rc[xb],2);rc.DDDD=h(rc.DDD,3),o(l.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=vb.utc([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=vb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.apply(b,[c]):d},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",_ordinalParse:/\d{1,2}/,preparse:function(a){return a},postformat:function(a){return a},week:function(a){return jb(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},firstDayOfWeek:function(){return this._week.dow},firstDayOfYear:function(){return this._week.doy},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),vb=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=b,g._f=c,g._l=e,g._strict=f,g._isUTC=!1,g._pf=d(),lb(g)},vb.suppressDeprecationWarnings=!1,vb.createFromInputFallback=f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),vb.min=function(){var a=[].slice.call(arguments,0);return mb("isBefore",a)},vb.max=function(){var a=[].slice.call(arguments,0);return mb("isAfter",a)},vb.utc=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=b,g._f=c,g._strict=f,g._pf=d(),lb(g).utc()},vb.unix=function(a){return vb(1e3*a)},vb.duration=function(a,b){var d,e,f,g,h=a,i=null;return vb.isDuration(a)?h={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(h={},b?h[b]=a:h.milliseconds=a):(i=Nb.exec(a))?(d="-"===i[1]?-1:1,h={y:0,d:C(i[Eb])*d,h:C(i[Fb])*d,m:C(i[Gb])*d,s:C(i[Hb])*d,ms:C(i[Ib])*d}):(i=Ob.exec(a))?(d="-"===i[1]?-1:1,f=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*d},h={y:f(i[2]),M:f(i[3]),d:f(i[4]),h:f(i[5]),m:f(i[6]),s:f(i[7]),w:f(i[8])}):null==h?h={}:"object"==typeof h&&("from"in h||"to"in h)&&(g=t(vb(h.from),vb(h.to)),h={},h.ms=g.milliseconds,h.M=g.months),e=new n(h),vb.isDuration(a)&&c(a,"_locale")&&(e._locale=a._locale),e},vb.version=yb,vb.defaultFormat=gc,vb.ISO_8601=function(){},vb.momentProperties=Kb,vb.updateOffset=function(){},vb.relativeTimeThreshold=function(b,c){return oc[b]===a?!1:c===a?oc[b]:(oc[b]=c,!0)},vb.lang=f("moment.lang is deprecated. Use moment.locale instead.",function(a,b){return vb.locale(a,b)}),vb.locale=function(a,b){var c;return a&&(c="undefined"!=typeof b?vb.defineLocale(a,b):vb.localeData(a),c&&(vb.duration._locale=vb._locale=c)),vb._locale._abbr},vb.defineLocale=function(a,b){return null!==b?(b.abbr=a,Jb[a]||(Jb[a]=new l),Jb[a].set(b),vb.locale(a),Jb[a]):(delete Jb[a],null)},vb.langData=f("moment.langData is deprecated. Use moment.localeData instead.",function(a){return vb.localeData(a)}),vb.localeData=function(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return vb._locale;if(!w(a)){if(b=L(a))return b;a=[a]}return K(a)},vb.isMoment=function(a){return a instanceof m||null!=a&&c(a,"_isAMomentObject")},vb.isDuration=function(a){return a instanceof n};for(xb=tc.length-1;xb>=0;--xb)B(tc[xb]);vb.normalizeUnits=function(a){return z(a)},vb.invalid=function(a){var b=vb.utc(0/0);return null!=a?o(b._pf,a):b._pf.userInvalidated=!0,b},vb.parseZone=function(){return vb.apply(null,arguments).parseZone()},vb.parseTwoDigitYear=function(a){return C(a)+(C(a)>68?1900:2e3)},vb.isDate=x,o(vb.fn=m.prototype,{clone:function(){return vb(this)},valueOf:function(){return+this._d-6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=vb(this).utc();return 00:!1},parsingFlags:function(){return o({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(a){return this.utcOffset(0,a)},local:function(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(this._dateUtcOffset(),"m")),this},format:function(a){var b=P(this,a||vb.defaultFormat);return this.localeData().postformat(b)},add:u(1,"add"),subtract:u(-1,"subtract"),diff:function(a,b,c){var d,e,f=M(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=j(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:q(e)},from:function(a,b){return vb.duration({to:this,from:a}).locale(this.locale()).humanize(!b)},fromNow:function(a){return this.from(vb(),a)},calendar:function(a){var b=a||vb(),c=M(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,vb(b)))},isLeapYear:function(){return G(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=gb(a,this.localeData()),this.add(a-b,"d")):b},month:qb("Month",!0),startOf:function(a){switch(a=z(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this},endOf:function(b){return b=z(b),b===a||"millisecond"===b?this:this.startOf(b).add(1,"isoWeek"===b?"week":b).subtract(1,"ms")},isAfter:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this>+a):(c=vb.isMoment(a)?+a:+vb(a),c<+this.clone().startOf(b))},isBefore:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+a>+this):(c=vb.isMoment(a)?+a:+vb(a),+this.clone().endOf(b)a?this:a}),max:f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),a>this?this:a}),zone:f("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",function(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}),utcOffset:function(a,b){var c,d=this._offset||0;return null!=a?("string"==typeof a&&(a=S(a)),Math.abs(a)<16&&(a=60*a),!this._isUTC&&b&&(c=this._dateUtcOffset()),this._offset=a,this._isUTC=!0,null!=c&&this.add(c,"m"),d!==a&&(!b||this._changeInProgress?v(this,vb.duration(a-d,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,vb.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?d:this._dateUtcOffset()},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&0===this._offset},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(S(this._i)),this},hasAlignedHourOffset:function(a){return a=a?vb(a).utcOffset():0,(this.utcOffset()-a)%60===0},daysInMonth:function(){return D(this.year(),this.month())},dayOfYear:function(a){var b=Ab((vb(this).startOf("day")-vb(this).startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")},quarter:function(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)},weekYear:function(a){var b=jb(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")},isoWeekYear:function(a){var b=jb(this,1,4).year;return null==a?b:this.add(a-b,"y")},week:function(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")},isoWeek:function(a){var b=jb(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")},weekday:function(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},isoWeeksInYear:function(){return E(this.year(),1,4)},weeksInYear:function(){var a=this.localeData()._week;return E(this.year(),a.dow,a.doy)},get:function(a){return a=z(a),this[a]()},set:function(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else a=z(a),"function"==typeof this[a]&&this[a](b);return this},locale:function(b){var c;return b===a?this._locale._abbr:(c=vb.localeData(b),null!=c&&(this._locale=c),this)},lang:f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(b){return b===a?this.localeData():this.locale(b)}),localeData:function(){return this._locale},_dateUtcOffset:function(){return 15*-Math.round(this._d.getTimezoneOffset()/15)}}),vb.fn.millisecond=vb.fn.milliseconds=qb("Milliseconds",!1),vb.fn.second=vb.fn.seconds=qb("Seconds",!1),vb.fn.minute=vb.fn.minutes=qb("Minutes",!1),vb.fn.hour=vb.fn.hours=qb("Hours",!0),vb.fn.date=qb("Date",!0),vb.fn.dates=f("dates accessor is deprecated. Use date instead.",qb("Date",!0)),vb.fn.year=qb("FullYear",!0),vb.fn.years=f("years accessor is deprecated. Use year instead.",qb("FullYear",!0)),vb.fn.days=vb.fn.day,vb.fn.months=vb.fn.month,vb.fn.weeks=vb.fn.week,vb.fn.isoWeeks=vb.fn.isoWeek,vb.fn.quarters=vb.fn.quarter,vb.fn.toJSON=vb.fn.toISOString,vb.fn.isUTC=vb.fn.isUtc,o(vb.duration.fn=n.prototype,{_bubble:function(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;g.milliseconds=d%1e3,a=q(d/1e3),g.seconds=a%60,b=q(a/60),g.minutes=b%60,c=q(b/60),g.hours=c%24,e+=q(c/24),h=q(rb(e)),e-=q(sb(h)),f+=q(e/30),e%=30,h+=q(f/12),f%=12,g.days=e,g.months=f,g.years=h},abs:function(){return this._milliseconds=Math.abs(this._milliseconds),this._days=Math.abs(this._days),this._months=Math.abs(this._months),this._data.milliseconds=Math.abs(this._data.milliseconds),this._data.seconds=Math.abs(this._data.seconds),this._data.minutes=Math.abs(this._data.minutes),this._data.hours=Math.abs(this._data.hours),this._data.months=Math.abs(this._data.months),this._data.years=Math.abs(this._data.years),this},weeks:function(){return q(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*C(this._months/12) +},humanize:function(a){var b=ib(this,!a,this.localeData());return a&&(b=this.localeData().pastFuture(+this,b)),this.localeData().postformat(b)},add:function(a,b){var c=vb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=vb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=z(a),this[a.toLowerCase()+"s"]()},as:function(a){var b,c;if(a=z(a),"month"===a||"year"===a)return b=this._days+this._milliseconds/864e5,c=this._months+12*rb(b),"month"===a?c:c/12;switch(b=this._days+Math.round(sb(this._months/12)),a){case"week":return b/7+this._milliseconds/6048e5;case"day":return b+this._milliseconds/864e5;case"hour":return 24*b+this._milliseconds/36e5;case"minute":return 24*b*60+this._milliseconds/6e4;case"second":return 24*b*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+a)}},lang:vb.fn.lang,locale:vb.fn.locale,toIsoString:f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}}),vb.duration.fn.toString=vb.duration.fn.toISOString;for(xb in kc)c(kc,xb)&&tb(xb.toLowerCase());vb.duration.fn.asMilliseconds=function(){return this.as("ms")},vb.duration.fn.asSeconds=function(){return this.as("s")},vb.duration.fn.asMinutes=function(){return this.as("m")},vb.duration.fn.asHours=function(){return this.as("h")},vb.duration.fn.asDays=function(){return this.as("d")},vb.duration.fn.asWeeks=function(){return this.as("weeks")},vb.duration.fn.asMonths=function(){return this.as("M")},vb.duration.fn.asYears=function(){return this.as("y")},vb.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===C(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),Lb?module.exports=vb:"function"==typeof define&&define.amd?(define(function(a,b,c){return c.config&&c.config()&&c.config().noGlobal===!0&&(zb.moment=wb),vb}),ub(!0)):ub()}).call(this); \ No newline at end of file diff --git a/app/templates/order.html b/app/templates/order.html index a5ee9a7..3d14aa6 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -1,9 +1,11 @@ {% extends "layout.html" %} {% set active_page = "orders" -%} +{% import "utils.html" as util %} + {% block container %}
    -
    +

    Order {{ order.id }} {% if order.can_close(current_user.id) -%} Close
    @@ -18,9 +20,26 @@ total price: {{ total_price|euro }}

    {% if form -%} -
    +

    Order:

    - {% include "order_form.html" with context %} +
    + {{ form.csrf_token }} +
    + {{ form.product_id.label(class='control-label') }} + {{ form.product_id(class='form-control select') }} + {{ util.render_form_field_errors(form.product_id) }} +
    + {% if current_user.is_anonymous() %} +
    + {{ form.name.label(class='control-label') }} + {{ form.name(class='form-control') }} + {{ util.render_form_field_errors(form.name) }} +
    + {% endif %} +
    + {{ form.submit_button(class='btn btn-primary') }} +
    +
    {%- endif %}
    @@ -47,4 +66,16 @@ {% endfor %}
    +{% endblock %} + +{% block styles %} + {{ super() }} + +{% endblock %} +{% block scripts %} + {{ super() }} + + {% endblock %} \ No newline at end of file diff --git a/app/templates/order_form.html b/app/templates/order_form.html deleted file mode 100644 index f547e43..0000000 --- a/app/templates/order_form.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "layout.html" %} -{% set active_page = "orders" -%} - -{% import "utils.html" as util %} - -{% block content %} -
    -
    -
    - {{ form.csrf_token }} -
    - {{ form.product_id.label(class='control-label') }} - {{ form.product_id(class='form-control select') }} - {{ util.render_form_field_errors(form.product_id) }} -
    - {% if current_user.is_anonymous() %} -
    - {{ form.name.label(class='control-label') }} - {{ form.name(class='form-control') }} - {{ util.render_form_field_errors(form.name) }} -
    - {% endif %} -
    - {{ form.submit_button(class='btn btn-primary') }} -
    - -
    -
    -{% endblock %} - -{% block styles %} - {{ super() }} - -{% endblock %} -{% block scripts %} - {{ super() }} - - -{% endblock %} \ No newline at end of file diff --git a/app/templates/orders.html b/app/templates/orders.html index 2374341..8e8d40c 100644 --- a/app/templates/orders.html +++ b/app/templates/orders.html @@ -13,11 +13,68 @@ {% endfor %} {% if not current_user.is_anonymous() %} -
    +

    Create new order:

    - {{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }} +
    + {{ form.csrf_token }} +
    + {{ form.courrier_id.label(class='control-label') }} + {{ form.courrier_id(class='form-control select') }} + {{ util.render_form_field_errors(form.courrier_id) }} +
    +
    + {{ form.location_id.label(class='control-label') }} + {{ form.location_id(class='form-control select') }} + {{ util.render_form_field_errors(form.location_id) }} +
    + {% if current_user.is_admin() %} +
    + {{ form.starttime.label(class='control-label') }} +
    + {{ form.starttime(class='form-control datetimepicker') }} + + + +
    + {{ util.render_form_field_errors(form.starttime) }} +
    + {% endif %} +
    + {{ form.stoptime.label(class='control-label') }} +
    + {{ form.stoptime(class='form-control datetimepicker') }} + + + +
    + {{ util.render_form_field_errors(form.stoptime) }} +
    +
    + {{ form.submit_button(class='btn btn-primary') }} +
    +
    {% endif %}
    -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block styles -%} + {{ super() }} + + +{%- endblock %} +{% block scripts -%} + {{ super() }} + + + + +{%- endblock %} \ No newline at end of file diff --git a/app/templates/orders_form.html b/app/templates/orders_form.html deleted file mode 100644 index a5b5068..0000000 --- a/app/templates/orders_form.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "layout.html" %} -{% set active_page = "orders" -%} - -{% import "bootstrap/wtf.html" as wtf %} -{% block container %} -
    - {{ wtf.quick_form(form, action=url, button_map={'submit_button': 'primary'}) }} -
    -{% endblock %} \ No newline at end of file diff --git a/app/views/order.py b/app/views/order.py index 06d6b10..e9f3b9e 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -11,15 +11,14 @@ from forms import OrderItemForm, OrderForm, AnonOrderItemForm order_bp = Blueprint('order_bp', 'order') @order_bp.route('/') -def orders(): - orderForm = None - if not current_user.is_anonymous(): - orderForm = OrderForm() - orderForm.populate() - return render_template('orders.html', orders=get_orders(), form=orderForm) +def orders(form=None): + if form is None and not current_user.is_anonymous(): + form = OrderForm() + form.populate() + return render_template('orders.html', orders=get_orders(), form=form) -@order_bp.route('/create', methods=['GET', 'POST']) +@order_bp.route('/create', methods=['POST']) @login_required def order_create(): orderForm = OrderForm() @@ -30,40 +29,32 @@ def order_create(): db.session.add(order) db.session.commit() return redirect(url_for('.order', id=order.id)) - - return render_template('orders_form.html', form=orderForm, url=url_for(".order_create")) + return orders(form=orderForm) @order_bp.route('/') -def order(id): +def order(id, form=None): order = Order.query.filter(Order.id == id).first() if order is None: abort(404) - form = None - if not current_user.is_anonymous(): - form = OrderItemForm() - else: - form = AnonOrderItemForm() - form.populate(order.location) + if form is None: + 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]) return render_template('order.html', order=order, form=form, total_price=total_price) -@order_bp.route('//create', methods=['GET', 'POST']) +@order_bp.route('//create', methods=['POST']) def order_item_create(id): - order = Order.query.filter(Order.id == id).first() - if order is None: + current_order = Order.query.filter(Order.id == id).first() + if current_order is None: abort(404) - if order.stoptime and order.stoptime < datetime.now(): + if current_order.stoptime and current_order.stoptime < datetime.now(): abort(404) - form = None - if not current_user.is_anonymous(): - form = OrderItemForm() - else: - form = AnonOrderItemForm() - form.populate(order.location) + form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm() + form.populate(current_order.location) if form.validate_on_submit(): item = OrderItem() form.populate_obj(item) @@ -76,7 +67,7 @@ def order_item_create(id): db.session.commit() flash('Ordered %s' % (item.product.name), 'info') return redirect(url_for('.order', id=id)) - return render_template('order_form.html', form=form, order=order) + return order(id, form=form) @order_bp.route('///delete') def delete_item(order_id, item_id): @@ -148,7 +139,7 @@ def select_user(items): def get_orders(expression=None): orders = [] if expression is None: - expression = (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: From 79063f8bdfd02090386cfeb6052ecb6726c13282 Mon Sep 17 00:00:00 2001 From: Feliciaan De Palmenaer Date: Mon, 30 Mar 2015 00:23:59 +0200 Subject: [PATCH 44/68] added a footer --- app/templates/home.html | 4 ++-- app/templates/layout.html | 17 ++++++++++++++++- app/utils.py | 4 ++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/templates/home.html b/app/templates/home.html index 0c0f681..21cf791 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -9,13 +9,13 @@

    This is the home page for FoodBot

    -
    +

    Open orders:

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

    Recently closed orders:

    {% for order in recently_closed %} {{ util.render_order(order) }} diff --git a/app/templates/layout.html b/app/templates/layout.html index bb72bb2..8b4ddb7 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -13,12 +13,18 @@ {% set active_page = active_page|default('index') -%} {% block title %} - FoodBot + FoodBot - {{ active_page|capitalize }} {% endblock %} + {% block styles %} {{ super() }} {% endblock %} + +{% block scripts %} + +{% endblock %} + {% block navbar %}
    ").addClass("cw").text("#"));c.isBefore(l.clone().endOf("w"));)b.append(a("").addClass("dow").text(c.format("dd"))),c.add(1,"d");o.find(".datepicker-days thead").append(b)},K=function(a){return d.disabledDates[a.format("YYYY-MM-DD")]===!0},L=function(a){return d.enabledDates[a.format("YYYY-MM-DD")]===!0},M=function(a,b){return a.isValid()?d.disabledDates&&K(a)&&"M"!==b?!1:d.enabledDates&&!L(a)&&"M"!==b?!1:d.minDate&&a.isBefore(d.minDate,b)?!1:d.maxDate&&a.isAfter(d.maxDate,b)?!1:"d"===b&&-1!==d.daysOfWeekDisabled.indexOf(a.day())?!1:!0:!1},N=function(){for(var b=[],c=l.clone().startOf("y").hour(12);c.isSame(l,"y");)b.push(a("").attr("data-action","selectMonth").addClass("month").text(c.format("MMM"))),c.add(1,"M");o.find(".datepicker-months td").empty().append(b)},O=function(){var b=o.find(".datepicker-months"),c=b.find("th"),d=b.find("tbody").find("span");b.find(".disabled").removeClass("disabled"),M(l.clone().subtract(1,"y"),"y")||c.eq(0).addClass("disabled"),c.eq(1).text(l.year()),M(l.clone().add(1,"y"),"y")||c.eq(2).addClass("disabled"),d.removeClass("active"),k.isSame(l,"y")&&d.eq(k.month()).addClass("active"),d.each(function(b){M(l.clone().month(b),"M")||a(this).addClass("disabled")})},P=function(){var a=o.find(".datepicker-years"),b=a.find("th"),c=l.clone().subtract(5,"y"),e=l.clone().add(6,"y"),f="";for(a.find(".disabled").removeClass("disabled"),d.minDate&&d.minDate.isAfter(c,"y")&&b.eq(0).addClass("disabled"),b.eq(1).text(c.year()+"-"+e.year()),d.maxDate&&d.maxDate.isBefore(e,"y")&&b.eq(2).addClass("disabled");!c.isAfter(e,"y");)f+=''+c.year()+"",c.add(1,"y");a.find("td").html(f)},Q=function(){var c,e,f,g=o.find(".datepicker-days"),h=g.find("th"),i=[];if(z()){for(g.find(".disabled").removeClass("disabled"),h.eq(1).text(l.format(d.dayViewHeaderFormat)),M(l.clone().subtract(1,"M"),"M")||h.eq(0).addClass("disabled"),M(l.clone().add(1,"M"),"M")||h.eq(2).addClass("disabled"),c=l.clone().startOf("M").startOf("week");!l.clone().endOf("M").endOf("w").isBefore(c,"d");)0===c.weekday()&&(e=a("
    '+c.week()+"'+c.date()+"
    '+c.format(f?"HH":"hh")+"
    '+c.format("mm")+"
    '+c.format("ss")+"