Merge pull request #7 from ZeusWPI/unauthenticated_user

With unregistered user (fixes #1 )
This commit is contained in:
Wout Schellaert 2015-03-29 00:57:30 +01:00
commit 7f8c18dbbe
11 changed files with 267 additions and 112 deletions

View file

@ -1,7 +1,8 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import session
from flask.ext.login import current_user from flask.ext.login import current_user
from flask_wtf import Form from flask_wtf import Form
from wtforms import SelectField, DateTimeField, validators, SubmitField, HiddenField from wtforms import SelectField, DateTimeField, validators, SubmitField, StringField
from models import User, Location from models import User, Location
from utils import euro from utils import euro
@ -32,4 +33,24 @@ class OrderItemForm(Form):
submit_button = SubmitField('Submit') submit_button = SubmitField('Submit')
def populate(self, location): def populate(self, location):
self.product_id.choices = [(i.id, (i.name + ": " + euro(i.price))) for i in location.products] self.product_id.choices = [(i.id, (i.name + ": " + euro(i.price))) for i in location.products]
class AnonOrderItemForm(OrderItemForm):
name = StringField('Name', validators=[validators.required()])
def populate(self, location):
OrderItemForm.populate(self, location)
if self.name.data is None:
self.name.data = session.get('anon_name', None)
def validate(self):
rv = OrderForm.validate(self)
if not rv:
return False
# check if we have a user with this name
user = User.query.filter_by(username=self.name.data).first()
if user is not None:
self.name.errors.append('Name already in use')
return False
return True

View file

@ -54,7 +54,7 @@ class Location(db.Model):
self.website = website self.website = website
def __repr__(self): def __repr__(self):
return '%s: %s' % (self.name, self.address) return '%s' % (self.name)
class Product(db.Model): class Product(db.Model):
@ -71,7 +71,7 @@ class Product(db.Model):
self.price = price self.price = price
def __repr__(self): def __repr__(self):
return '%s' % self.name return '%s from %s' % (self.name, self.location)
class Order(db.Model): class Order(db.Model):
@ -80,6 +80,7 @@ class Order(db.Model):
location_id = db.Column(db.Integer, db.ForeignKey('location.id')) location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
starttime = db.Column(db.DateTime) starttime = db.Column(db.DateTime)
stoptime = db.Column(db.DateTime) stoptime = db.Column(db.DateTime)
public = db.Column(db.Boolean, default=True)
items = db.relationship('OrderItem', backref='order', lazy='dynamic') items = db.relationship('OrderItem', backref='order', lazy='dynamic')
def configure(self, courrier, location, starttime, stoptime): def configure(self, courrier, location, starttime, stoptime):
@ -89,39 +90,62 @@ class Order(db.Model):
self.stoptime = stoptime self.stoptime = stoptime
def __repr__(self): def __repr__(self):
return 'Order %s' % (self.location.name) return 'Order %d @ %s' % (self.id, self.location.name)
def group_by_user(self): def group_by_user(self):
group = defaultdict(list) group = defaultdict(list)
for item in self.items: for item in self.items:
group[item.user_id] += [item.product] group[item.get_name()] += [item.product]
return group return group
def group_by_user_pay(self): def group_by_user_pay(self):
group = defaultdict(int) group = defaultdict(int)
for item in self.items: for item in self.items:
group[item.user] += item.product.price group[item.get_name()] += item.product.price
return group 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
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): class OrderItem(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
order_id = db.Column(db.Integer, db.ForeignKey('order.id')) order_id = db.Column(db.Integer, db.ForeignKey('order.id'))
product_id = db.Column(db.Integer, db.ForeignKey('product.id')) product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
name = db.Column(db.String(120))
def configure(self, user, order, product): def configure(self, user, order, product):
self.user = user self.user = user
self.order = order self.order = order
self.product = product self.product = product
def __repr__(self): def get_name(self):
return 'OrderItem' if self.user_id is not None and self.user_id > 0:
return self.user.username
return self.name
def can_delete(self, order_id, user_id): def __repr__(self):
if self.user_id != user_id: return 'Order %d: %s wants %s' % (self.order_id, self.get_name(), self.product.name)
return False
def can_delete(self, order_id, user_id, name):
if int(self.order_id) != int(order_id): if int(self.order_id) != int(order_id):
return False return False
if self.order.stoptime and self.order.stoptime < datetime.now(): if self.order.stoptime and self.order.stoptime < datetime.now():
return False return False
return True if self.user_id == user_id or self.name == name:
return True
return False

View file

@ -1,4 +1,20 @@
/* Custom CSS */ /* Custom CSS */
body { body {
padding-top: 70px; padding-top: 70px;
} }
.darker {
background-color: #fafafa;
margin-top: 10px;
padding-bottom: 5px;
}
@media (min-width: 992px) {
.align-bottom {
margin-top: 2.5em;
}
}
.full-width {
width: 100%;
}

View file

@ -1,8 +1,26 @@
{% extends "layout.html" -%} {% extends "layout.html" -%}
{% set active_page = "home" -%} {% set active_page = "home" -%}
{% import "utils.html" as util -%}
{% block container %} {% block container %}
<div class="jumbotron"> <div class="jumbotron">
<h2>Welcome to FoodBot</h2> <h2>Welcome to FoodBot</h2>
<h3>This is the home page for FoodBot</h3> <h3>This is the home page for FoodBot</h3>
</div> </div>
<div class="row">
<div class="col-md-4">
<h3>Open orders:</h3>
{% for order in orders %}
{{ util.render_order(order) }}
{% endfor %}
</div>
<div class="col-md-4 col-md-push-4">
<h3>Recently closed orders:</h3>
{% for order in recently_closed %}
{{ util.render_order(order) }}
{% endfor %}
</ul>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -1,14 +0,0 @@
{% extends "home.html" %}
{% block container %}
{{ super() }}
<div class="row">
<div class="col-md-4">
<h3>Open orders:</h3>
<ul>
{% for order in orders %}
<li><a href="{{ url_for('order_bp.order', id=order.id) }}">{{ order.location.name }}-{{ order.stoptime }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View file

@ -4,33 +4,49 @@
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% block container %} {% block container %}
<div class="row"> <div class="row">
<div class="col-md-7"><!-- Shitty html--> <div class="col-md-push-1 col-md-4 darker"><!-- Shitty html-->
<h3>Order {{ order.id }} <h3>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) -%}
<a class="btn btn-danger" href="{{ url_for('.close_order', id=order.id) }}">Close</a><br/> <a class="btn btn-danger" href="{{ url_for('.close_order', id=order.id) }}">Close</a><br/>
{%- endif %}</h3> {%- endif %}</h3>
Courrier: {{ order.courrier.username }} courrier: {{ order.courrier.username }}
{% if order.courrier == None %} {% if order.courrier == None and not current_user.is_anonymous() %}
<a href="{{ url_for('.volunteer', id=order.id) }}" class="btn btn-primary">Volunteer</a> <a href="{{ url_for('.volunteer', id=order.id) }}" class="btn btn-primary btn-sm">Volunteer</a>
{% endif %} {% endif %}
<br/> <br/>
Location: <a href="{{ order.location.website }}">{{ order.location.name }}</a><br/> location: <a href="{{ order.location.website }}">{{ order.location.name }}</a><br/>
Starttime: {{ order.starttime }}<br/> opened on {{ order.starttime }}<br/>
Stoptime: {{ order.stoptime }}<br/> <b>status:</b> {% if order.stoptime %}{{ order.stoptime|countdown }}{% else %}open{% endif %}<br/>
Total price: {{ total_price|euro }} total price: {{ total_price|euro }}
<h3>Orders</h3>
{% for item in order.items %}
{{ item.user.username }} - {{ item.product.name }} - {{ item.product.price|euro }}
{% if item.can_delete(order.id, current_user.id) -%}<a href="{{ url_for('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/>
{% endfor %}
<h3>Debts</h3>
{% for key, value in total_payments.items() %}
{{ key.username }} - {{ value|euro }}<br/>
{% endfor %}
</div> </div>
<div class="col-md-push-1 col-md-4"> {% if form -%}
<div class="col-md-push-3 col-md-4 darker">
<h4>Order:</h4> <h4>Order:</h4>
{{ wtf.quick_form(form, action=url_for('.order_item_create', id=order.id), button_map={'submit_button': 'primary'}, form_type='horizontal') }} {{ wtf.quick_form(form, action=url_for('.order_item_create', id=order.id), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
</div> </div>
{%- endif %}
</div>
<div class="row">
<div class="col-md-push-1 col-md-4 darker">
<h3>Orders</h3>
{% 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', '')) -%}<a href="{{ url_for('.delete_item', order_id=order.id, item_id=item.id) }}"><span class="glyphicon glyphicon-remove"></span></a>{%- endif %}<br/>
{% endfor %}
</div>
<div class="col-md-push-3 col-md-4 darker">
<h3>Ordered products:</h3>
{% for key, value in order.group_by_product().items() %}
{{ key }} - {{ value }}<br/>
{% endfor %}
</div>
</div>
<div class="row">
<div class="col-md-push-1 col-md-4 darker">
<h3>Debts</h3>
{% for key, value in order.group_by_user_pay().items() %}
{{ key }} - {{ value|euro }}<br/>
{% endfor %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -2,19 +2,22 @@
{% set active_page = "orders" -%} {% set active_page = "orders" -%}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% import "utils.html" as util -%}
{% block container %} {% block container %}
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
<h3>Open orders:</h3> <h3>Open orders:</h3>
<ul> {% for order in orders %}
{% for order in orders %} {{ util.render_order(order) }}
<li><a href="{{ url_for('.order', id=order.id) }}">{{ order.location.name }}-{{ order.stoptime }}</a></li> {% endfor %}
{% endfor %}
</ul>
</div> </div>
{% if not current_user.is_anonymous() %}
<div class="col-md-push-1 col-md-6"> <div class="col-md-push-1 col-md-6">
{{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }} <h3>Create new order:</h3>
{{ wtf.quick_form(form, action=url_for('.order_create'), button_map={'submit_button': 'primary'}, form_type='horizontal') }}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

12
app/templates/utils.html Normal file
View file

@ -0,0 +1,12 @@
{% macro render_order(order) -%}
<div class="row darker">
<div class="col-md-9">
<h5>{{ order.location.name }}</h5>
<p><b>Status:</b> {{ order.stoptime|countdown }}<br/>
<b>Orders:</b> {{ order.items.count() }}</p>
</div>
<div class="col-md-3">
<a class="btn btn-primary align-bottom full-width" href="{{ url_for('order_bp.order', id=order.id) }}">Open</a>
</div>
</div>
{%- endmacro %}

View file

@ -1,6 +1,9 @@
from datetime import datetime
from flask import render_template from flask import render_template
from app import app from app import app
from login import login_manager
__author__ = 'feliciaan' __author__ = 'feliciaan'
@app.template_filter('euro') @app.template_filter('euro')
@ -8,10 +11,40 @@ def euro(value):
result = '' + str(value/100) result = '' + str(value/100)
return result return result
@app.template_filter('countdown')
def countdown(value, only_positive=True, show_text=True):
delta = value - datetime.now()
if delta.total_seconds() < 0 and only_positive:
return "closed"
hours, remainder = divmod(delta.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
time = '%02d:%02d:%02d' % (hours, minutes, seconds)
if show_text:
return 'closes in ' + time
return time
@app.errorhandler(404) @app.errorhandler(404)
def handle404(e): def handle404(e):
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
@app.errorhandler(401) @app.errorhandler(401)
def handle401(e): def handle401(e):
return render_template('errors/401.html'), 401 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

View file

@ -2,20 +2,19 @@ __author__ = 'feliciaan'
from flask import url_for, render_template, abort, redirect, request from flask import url_for, render_template, abort, redirect, request
from flask.ext.login import current_user, login_required from flask.ext.login import current_user, login_required
from datetime import datetime from datetime import datetime, timedelta
from app import app, db from app import app, db
from models import Order, OrderItem from models import Order, OrderItem
# import views # import views
import views.order from views.order import get_orders
@app.route('/') @app.route('/')
def home(): def home():
if not current_user.is_anonymous(): prev_day = datetime.now() - timedelta(days=1)
orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() recently_closed = get_orders(((Order.stoptime > prev_day) & (Order.stoptime < datetime.now())))
return render_template('home_loggedin.html', orders=orders) return render_template('home.html', orders=get_orders(), recently_closed=recently_closed)
return render_template('home.html')
@app.route('/about/') @app.route('/about/')

View file

@ -1,22 +1,22 @@
__author__ = 'feliciaan' __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 from flask.ext.login import current_user, login_required
import random import random
from datetime import datetime from datetime import datetime
from app import app, db from app import app, db
from models import Order, OrderItem from models import Order, OrderItem
from forms import OrderItemForm, OrderForm from forms import OrderItemForm, OrderForm, AnonOrderItemForm
order_bp = Blueprint('order_bp', 'order') order_bp = Blueprint('order_bp', 'order')
@order_bp.route('/') @order_bp.route('/')
@login_required
def orders(): def orders():
orders = Order.query.filter((Order.stoptime > datetime.now()) | (Order.stoptime == None)).all() orderForm = None
orderForm = OrderForm() if not current_user.is_anonymous():
orderForm.populate() orderForm = OrderForm()
return render_template('orders.html', orders=orders, form=orderForm) orderForm.populate()
return render_template('orders.html', orders=get_orders(), form=orderForm)
@order_bp.route('/create', methods=['GET', 'POST']) @order_bp.route('/create', methods=['GET', 'POST'])
@ -35,41 +35,55 @@ def order_create():
@order_bp.route('/<id>') @order_bp.route('/<id>')
@login_required
def order(id): def order(id):
order = Order.query.filter(Order.id == id).first() 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 = OrderItemForm()
form.populate(order.location) else:
total_price = sum([o.product.price for o in order.items]) form = AnonOrderItemForm()
total_payments = order.group_by_user_pay() form.populate(order.location)
return render_template('order.html', order=order, form=form, total_price=total_price, total_payments=total_payments) if order.stoptime and order.stoptime < datetime.now():
return abort(404) 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('/<id>/create', methods=['GET', 'POST']) @order_bp.route('/<id>/create', methods=['GET', 'POST'])
@login_required
def order_item_create(id): def order_item_create(id):
order = Order.query.filter(Order.id == id).first() order = Order.query.filter(Order.id == id).first()
if order is not None: 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() form = OrderItemForm()
form.populate(order.location) else:
if form.validate_on_submit(): form = AnonOrderItemForm()
item = OrderItem() form.populate(order.location)
form.populate_obj(item) if form.validate_on_submit():
item.order_id = id item = OrderItem()
form.populate_obj(item)
item.order_id = id
if not current_user.is_anonymous():
item.user_id = current_user.id item.user_id = current_user.id
db.session.add(item) else:
db.session.commit() session['anon_name'] = item.name
return redirect(url_for('.order', id=id)) db.session.add(item)
return render_template('order_form.html', form=form, url=url_for(".order_item_create", id=id)) db.session.commit()
return abort(404) 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('/<order_id>/<item_id>/delete') @order_bp.route('/<order_id>/<item_id>/delete')
@login_required
def delete_item(order_id, item_id): def delete_item(order_id, item_id):
item = OrderItem.query.filter(OrderItem.id == item_id).first() 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.delete(item)
db.session.commit() db.session.commit()
return redirect(url_for('.order', id=order_id)) return redirect(url_for('.order', id=order_id))
@ -80,47 +94,60 @@ def delete_item(order_id, item_id):
@login_required @login_required
def volunteer(id): def volunteer(id):
order = Order.query.filter(Order.id == id).first() order = Order.query.filter(Order.id == id).first()
if order is not None: if order is None:
print(order.courrier_id) abort(404)
if order.courrier_id == 0: if order.courrier_id is None or order.courrier_id == 0:
order.courrier_id = current_user.id order.courrier_id = current_user.id
db.session.commit() db.session.commit()
flash("Thank you for volunteering!") flash("Thank you for volunteering!")
else: else:
flash("Volunteering not possible!") flash("Volunteering not possible!")
return redirect(url_for('.order', id=id)) return redirect(url_for('.order', id=id))
abort(404)
@order_bp.route('/<id>/close') @order_bp.route('/<id>/close')
@login_required @login_required
def close_order(id): def close_order(id):
order = Order.query.filter(Order.id == id).first() order = Order.query.filter(Order.id == id).first()
if order is not None: if order is None:
if (current_user.id == order.courrier_id or current_user.is_admin()) \ abort(404)
and order.stoptime is None or (order.stoptime > datetime.now()): if (current_user.id == order.courrier_id or current_user.is_admin()) \
order.stoptime = datetime.now() and order.stoptime is None or (order.stoptime > datetime.now()):
if order.courrier_id == 0 or order.courrier_id is None: order.stoptime = datetime.now()
courrier = select_user(order.items) if order.courrier_id == 0 or order.courrier_id is None:
if courrier is not None: courrier = select_user(order.items)
order.courrier_id = courrier.id print(courrier)
db.session.commit() if courrier is not None:
return redirect(url_for('.order', id=id)) order.courrier_id = courrier.id
abort(401) db.session.commit()
return redirect(url_for('.order', id=id))
app.register_blueprint(order_bp, url_prefix='/order') app.register_blueprint(order_bp, url_prefix='/order')
def select_user(items): def select_user(items):
user = None user = None
items = list(items) # remove non users
items = [i for i in items if i.user_id]
if len(items) <= 0: if len(items) <= 0:
return None return None
while user is None: while user is None:
item = random.choice(items) item = random.choice(items)
user = item.user user = item.user
if random.randint(user.bias, 100) < 80: if user:
user = None if random.randint(user.bias, 100) < 80:
user = None
return user return user
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:
orders = Order.query.filter((expression & (Order.public == True))).all()
return orders