This commit is contained in:
crudo 2017-03-21 11:26:25 +01:00
commit e7874a10ee
17 changed files with 1127 additions and 0 deletions

98
.gitignore vendored Normal file
View File

@ -0,0 +1,98 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.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
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# macao-pos files
pos.db
pos.log

101
README.md Normal file
View File

@ -0,0 +1,101 @@
# macao-pos
## Installation
The only requirements are Python 3 and virtualenv as you're going to install all
the modules through pip.
If you're using MySQL or PostgreSQL create now a new user and database. You can
also use SQLite for testing purposes.
```
cd /var/www
git clone ...
cd macao-pos
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
```
Now you need to configure this software. Inside `doc/` you'll find some
examples. The default path for configurations in `conf/` but you can also use
`~/.config/pos`, `/usr/local/etc/pos` and `/etc/pos` that will be checked in
this order.
For a testing environment you can just do:
```
mkdir conf
cp docs/config_core/core_debug_sqlite.ini conf/core.ini
cp docs/config_logging/logging_debug.yaml conf/logging.yaml
```
and you're ready to go.
For a production environment:
```
cd /var/www/macao-pos
mkdir conf
cp docs/config_core/core_production_mysql.ini conf/core.ini
cp docs/config_logging/logging_production.yaml conf/logging.yaml
```
and then edit the conf/core.ini file to adjust the database params. Don't forget
to change the SECRET_KEY value (`openssl rand -hex 32` will help you).
If you want to change the log file path open your `conf/logging.yaml` and
change the `filename` field of the `file` entry inside the `handlers` category.
## Building the database
You also need to add some entries to the database.
First of all add a new user. Get inside the virtualenv and then just do:
```
python3 cli.py user add username password
```
Add some products with:
```
python3 cli.py product add "Birra media" 300
python3 cli.py product add "Birra grande" 400
python3 cli.py product add "Cocktail" 500
python3 cli.py product add "Vino" 400
python3 cli.py product add "Amaro" 200
python3 cli.py product add "Acqua" 100
```
And finally add and event you can play with:
```
python3 cli.py event add "My party" --start "2017-03-19 22:00" --end "2017-03-22 07:00"
```
## Running
You can run this software with from the virtualenv with:
```
python3 web.py
```
If you want to use a read httpd you can setup uwsgi as follows:
```
[uwsgi]
socket = 127.0.0.1:9000
chdir = /var/www/macao-pos
wsgi-file = web.py
virtualenv = env
master
workers = 1
max-requests = 200
harakiri = 30
die-on-term
```
## Contributing
Fork → commit → pull request

165
cli.py Normal file
View File

@ -0,0 +1,165 @@
import click
from tabulate import tabulate
from datetime import datetime
from pos.config import Config
from pos.logging import init_logging, get_logger
from pos.database import Database, User, Event, Product, Order
config = Config()
conf_db = config.core['DATABASE']
init_logging(config.logging)
log = get_logger('cli')
db = Database(**conf_db)
@click.group()
def cli():
pass
@cli.group('user')
def user():
pass
def tabulate_users(users):
tab = [["UID", "Username", "Enabled", "Created at"]]
for u in users:
tab.append([u.uid, u.username, u.is_active, u.created_at])
return tabulate(tab, headers='firstrow')
@user.command('add')
@click.argument('username')
@click.argument('password')
def user_add(username, password):
user = User(username=username, password=password)
with db.get_session() as session:
session.add(user)
print("User succesfully added.")
@user.command('list')
def user_list():
users = None
with db.get_session() as session:
users = session.query(User).all()
if len(users) == 0:
print("No users found.")
return
print(tabulate_users(users))
@cli.group('event')
def event():
pass
def tabulate_events(events):
tab = [["UID", "Name", "Starts at", "Ends at", "Income", "Created at"]]
for e in events:
sum = 0
for order in e.orders:
for entry in order.entries:
sum += entry.product.price * entry.quantity
tab.append([e.uid, e.name, e.starts_at, e.ends_at, sum, e.created_at])
return tabulate(tab, headers='firstrow')
@event.command('add')
@click.argument('name')
@click.option('--start')
@click.option('--end')
def event_add(name, start, end):
start = datetime.strptime(start, "%Y-%m-%d %H:%M")
end = datetime.strptime(end, "%Y-%m-%d %H:%M")
event = Event(name=name, starts_at=start, ends_at=end)
with db.get_session() as session:
session.add(event)
print("Event succesfully added.")
@event.command('list')
def event_list():
events = None
with db.get_session() as session:
events = session.query(Event).all()
if len(events) == 0:
print("No events found.")
return
print(tabulate_events(events))
@cli.group('product')
def product():
pass
def tabulate_products(products):
tab = [["UID", "Name", "Price", "Currency", "Enabled", "Created at"]]
for p in products:
tab.append([p.uid, p.name, p.price, p.currency,
p.is_active, p.created_at])
return tabulate(tab, headers='firstrow')
@product.command('add')
@click.argument('name')
@click.argument('price')
@click.option('--currency')
def product_add(name, price, currency):
product = Product(name=name, currency=currency, price=price)
with db.get_session() as session:
session.add(product)
print("Product succesfully added.")
@product.command('list')
def product_list():
products = None
with db.get_session() as session:
products = session.query(Product).all()
if len(products) == 0:
print("No products found.")
return
print(tabulate_products(products))
@cli.group('order')
def order():
pass
def tabulate_orders(orders):
text = []
for o in orders:
text.append("Listing order #{} ({}):".format(o.uid, o.created_at))
tab = [["Product", "Quantity", "Total"]]
total = 0
for e in o.entries:
if e.quantity != 0:
tab.append([e.product.name, e.quantity,
e.product.price * e.quantity])
total += e.product.price * e.quantity
text.append(tabulate(tab, headers='firstrow'))
text.append("Total: {}".format(total))
text.append('\n')
return '\n'.join(text)
@order.command('list')
def order_list():
orders = None
with db.get_session() as session:
orders = session.query(Order).all()
if len(orders) == 0:
print("No orders found.")
return
print(tabulate_orders(orders))
if __name__ == '__main__':
cli()

View File

@ -0,0 +1,9 @@
[GENERAL]
Debug = True
[DATABASE]
Engine = sqlite
Path = pos.db
[FLASK]
SECRET_KEY = CHANGE_ME_NOW!

View File

@ -0,0 +1,13 @@
[GENERAL]
Debug = False
[DATABASE]
Engine = mysql+pymysql
Host = 127.0.0.1
Port = 3306
Database = pos
User = pos
Password = secret
[FLASK]
SECRET_KEY = CHANGE_ME_NOW!

View File

@ -0,0 +1,23 @@
version: 1
formatters:
default:
format: '[%(name)s %(levelname)s] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: default
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: default
filename: pos.log
loggers:
pos:
level: DEBUG
handlers: [console,file]
sqlalchemy:
level: INFO
handlers: [console,file]

View File

@ -0,0 +1,25 @@
version: 1
formatters:
default:
format: '%(asctime)s %(levelname)-8s %(name)-30s %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: default
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
formatter: default
filename: pos.log
maxBytes: 1024
backupCount: 3
loggers:
pos:
level: WARNING
handlers: [console,file]
sqlalchemy:
level: CRITICAL
handlers: [console,file]

39
pos/config.py Normal file
View File

@ -0,0 +1,39 @@
import os.path
from configparser import ConfigParser
import yaml
APP_NAME = 'pos'
CONFIG_PATHS = ['conf/', '~/.config/pos', '/usr/local/etc/pos', '/etc/pos']
CONFIG_FILES = {
'core': 'core.ini',
'logging': 'logging.yaml'
}
def get_default_path():
for p in CONFIG_PATHS:
if all([
os.path.isfile(os.path.join(p, f))
for f in CONFIG_FILES.values()
]):
return p
else:
raise Exception('Unable to find a configuration folder.')
class Config:
def __init__(self):
self.basedir = get_default_path()
self.path_core = os.path.join(self.basedir, CONFIG_FILES['core'])
self.path_logging = os.path.join(self.basedir, CONFIG_FILES['logging'])
self.core = ConfigParser()
self.logging = None
self.read()
def read(self):
self.core.read(self.path_core)
self.logging = yaml.load(open(self.path_logging, 'r'))

152
pos/database.py Normal file
View File

@ -0,0 +1,152 @@
from datetime import datetime
from contextlib import contextmanager
import sqlalchemy as sa
import sqlalchemy.ext.declarative
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import Table, Column, ForeignKey
from sqlalchemy import Integer, String, Boolean, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy_utils import force_auto_coercion
from sqlalchemy_utils.types.password import PasswordType
from sqlalchemy_utils.types.currency import CurrencyType
from pos.logging import get_logger
# The database URL must follow RFC 1738 in the form
# dialect+driver://username:password@host:port/database
ENGINE_GENERIC = "{engine}://{user}:{password}@{host}:{port}/{database}"\
"?charset=utf8"
ENGINE_SQLITE = "sqlite:///{path}"
ENGINE_SQLITE_MEMORY = "sqlite://"
PASSWORD_SCHEMES = ['pbkdf2_sha512']
DEFAULT_CURRENCY = 'EUR'
Base = sqlalchemy.ext.declarative.declarative_base()
log = get_logger('database')
force_auto_coercion()
class Database:
"""
Handle database operations."
"""
Session = None
engine = None
def __init__(self, **kwargs):
"""
Initialize database connection.
:param engine: The SQLAlchemy database backend in the form
dialect+driver where dialect is the name of a SQLAlchemy
dialect (sqlite, mysql, postgresql, oracle or mssql) and
driver is the name of the DBAPI in all lowercase
letters. If driver is not specified the default DBAPI
will be imported if available.
:param path: Only for SQLite. Path to database. If not specified the
database will be kept in memory (should be used only for
testing).
:param host:
:param port:
:param database:
:param user:
:param password:
"""
if kwargs['engine'] == 'sqlite':
if 'path' in kwargs:
url = ENGINE_SQLITE.format(path=kwargs['path'])
else:
url = ENGINE_SQLITE_MEMORY
else:
url = ENGINE_GENERIC.format(**kwargs)
self.engine = sa.create_engine(url)
self.Session = sa.orm.sessionmaker(
bind=self.engine,
expire_on_commit=False
)
Base.metadata.create_all(self.engine)
@contextmanager
def get_session(self):
session = self.Session()
try:
yield session
except SQLAlchemyError as e:
log.critical("Error performing transaction:")
log.critical(e)
session.rollback()
else:
session.commit()
finally:
session.close()
class User(Base):
__tablename__ = 'users'
uid = Column(Integer, primary_key=True)
username = Column(String, nullable=False, unique=True)
password = Column(PasswordType(schemes=PASSWORD_SCHEMES), nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.now)
is_active = Column(Boolean, nullable=False, server_default='1')
is_authenticated = Column(Boolean, nullable=False, server_default='0')
def get_id(self):
return u'{}'.format(self.uid)
class Event(Base):
__tablename__ = 'events'
uid = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.now)
starts_at = Column(DateTime, nullable=False, default=datetime.now)
ends_at = Column(DateTime)
orders = relationship('Order', lazy='joined')
class Product(Base):
__tablename__ = 'products'
uid = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
currency = Column(CurrencyType, nullable=False, default=DEFAULT_CURRENCY)
price = Column(Integer, nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.now)
is_active = Column(Boolean, nullable=False, server_default='1')
order_entry_association = Table(
'order_entry_associations', Base.metadata,
Column('order_uid', Integer, ForeignKey('orders.uid')),
Column('order_entry_uid', Integer, ForeignKey('order_entries.uid'))
)
class Order(Base):
__tablename__ = 'orders'
uid = Column(Integer, primary_key=True)
created_at = Column(DateTime, nullable=False, default=datetime.now)
event_uid = Column(Integer, ForeignKey('events.uid'), nullable=False)
event = relationship('Event')
entries = relationship('OrderEntry', lazy='joined',
secondary=order_entry_association)
class OrderEntry(Base):
__tablename__ = 'order_entries'
uid = Column(Integer, primary_key=True)
product_uid = Column(Integer, ForeignKey('products.uid'), nullable=False)
quantity = Column(Integer, nullable=False)
product = relationship('Product', lazy='joined')

15
pos/logging.py Normal file
View File

@ -0,0 +1,15 @@
import logging
import logging.config
from pos.config import APP_NAME
root = logging.getLogger(APP_NAME)
def init_logging(config):
logging.config.dictConfig(config)
def get_logger(name):
return root.getChild(name)

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
click>=6.0
SQLAlchemy>=1.1.0
sqlalchemy_utils>=0.32.00
pymysql
babel
passlib
tabulate
PyYAML
flask>=0.12.0
flask_login>=0.4.0

55
static/style/reset.css Normal file
View File

@ -0,0 +1,55 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

170
static/style/screen.css Normal file
View File

@ -0,0 +1,170 @@
html {
width: 100%;
height: 100%;
font-size: 16px;
}
body {
font-family: sans-serif;
color: black;
}
main {
margin: 2rem;
}
.alert {
max-width: 20rem;
margin: 1rem auto;
border: 1px solid;
border-radius: 0.2rem;
padding: 0.7rem 1.5rem;
}
.alert.success {
background-color: #60B044;
border-color: #5CA941;
}
.alert.error {
background-color: #E74C3C;
border-color: #C0392B;
}
h1 {
margin-bottom: 1rem;
font-size: 2.5em;
text-align: center;
}
h2 {
margin-bottom: 0.5rem;
font-size: 2rem;
text-align: center;
}
h3 {
margin-bottom: 0.2rem;
font-size: 1.5rem;
text-align: center;
}
form#login {
max-width: 16rem;
margin: 5rem auto;
border-radius: 0.2rem;
border: 1px solid #CCC;
padding: 2rem 2.5rem 1.5rem 2.5rem;
}
form#login > label {
display: block;
margin: 1rem 0 0.25rem 0;
color: #666;
font-size: 0.9rem;
font-weight: bold;
}
form#login > input {
display: block;
width: 100%;
border: 1px solid #CCC;
border-radius: 0.2rem;
padding: 0.4rem 0.6rem;
vertical-align: middle;
background-color: #FAFAFA;
box-shadow: inset 0 1px 3px #DDD;
box-sizing: border-box;
font-size: 1rem;
}
form#login > input:focus {
border-color: #51A7E8;
background-color: #FFF;
outline: 0;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset,
0 0 5px rgba(81, 167, 232, 0.5);
}
form#login > button {
display: block;
margin: 2rem 0 0.5rem 0;
border: 1px solid #5CA941;
border-radius: 0.2rem;
padding: 0.5rem 0.7rem;
vertical-align: middle;
background-color: #60B044;
background-image: linear-gradient(#8ADD6D, #60B044);
box-sizing: border-box;
cursor: pointer;
user-select: none;
white-space: nowrap;
color: #fff;
font-size: 0.9rem;
font-weight: bold;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
}
form#login > button:focus,
form#login > button:hover {
border-color: #4A993E;
background-color: #569E3D;
background-image: linear-gradient(#79D858, #569E3D);
}
#products {
display: inline-block;
margin: 4rem auto;
max-width: 50%;
}
#products > button {
display: inline-block;
width: 15rem;
height: 7rem;
margin: 0 1rem 1rem 0;
padding: 0.5rem 0.7rem;
white-space: nowrap;
font-size: 1.5rem;
}
#products > button > span {
display: block;
}
#basket {
display: inline-block;
float: right;
margin: 4rem auto;
min-width: 30%;
}
#basket > button {
display: block;
width: 15rem;
height: 3rem;
margin-bottom: 0.3rem;
padding: 0.2rem 0.5rem;
font-size: 1.3rem;
}
#sell {
display: inline-block;
float: right;
margin: auto;
min-width: 30%;
}
#sell > button {
display: block;
width: 15rem;
height: 3rem;
padding: 0.2rem 0.5rem;
font-size: 1.3rem;
}

21
templates/base.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="{{ url_for('static', filename='style/reset.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='style/screen.css') }}">
<title>{{ title }}</title>
</head>
<body>
<main>{% block main %}{% endblock %}</main>
{% with alerts = get_flashed_messages(with_categories=True) %}
{% if alerts %}{% for category, message in alerts %}
<div class="alert {{ category }}">{{ message }}</div>
{% endfor %}{% endif %}
{% endwith %}
</body>
</html>

11
templates/login.html Normal file
View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block main %}
<h1>{{ title }}</h1>
<form id="login" action="/login" method="post">
<label for="username">Username</label>
<input id="username" name="username" type="text" required autofocus>
<label for="password">Password</label>
<input id="password" name="password" type="password" required>
<button type="submit">Login</button>
</form>
{% endblock %}

76
templates/sell.html Normal file
View File

@ -0,0 +1,76 @@
{% extends 'base.html' %}
{% block main %}
<h1>{{ title }}</h1>
{% if event %}
<h2>{{ event.name }}</h2>
<h3>{{ event.starts_at }} → {{ event.ends_at }}</h3>
{% else %}
<h2>No ongoing event!</h2>
{% endif %}
<div id="products">
{% for product in products %}
<button type="button"
onclick="addProduct({{ product.uid }})">
<span class="name">{{ product.name }}</span>
<span class="price">{{ product.price }}</span>
</button>
{% endfor %}
</div>
<div id="basket">
</div>
<form id="sell" action="/sell" method="POST">
{% for product in products %}
<input type="hidden"
id="prod_{{ product.uid }}"
name="{{ product.uid }}"
data-name="{{ product.name }}"
data-price="{{ product.price }}"
value="0">
{% endfor %}
<button type="submit">Print</input>
</form>
<script type="text/javascript">
function addProduct(uid) {
// This is the hidden input element inside the form. We'll use this DOM
// element to keep informations about the product, such as the name,
// the price and the value inside the 'data-' tag.
form_el = document.getElementById('prod_' + uid)
basket = document.getElementById('basket')
basket_el = document.getElementById('basketitem_' + uid)
form_el.value = parseInt(form_el.value) + 1
// If there is not a button for the given product inside the basket
// div we'll create it.
if (basket_el === null) {
new_basket_el =
'<button id="basketitem_' + uid + '"' +
'onclick="delProduct(' + uid + ')">' +
'1 x ' + form_el.dataset.name +
'</button>'
basket.innerHTML += new_basket_el
} else {
// Otherwise we'll just update it.
console.log(form_el.value)
new_text = form_el.value + ' x ' + form_el.dataset.name
basket_el.innerText = new_text
}
}
function delProduct(uid) {
form_el = document.getElementById('prod_' + uid)
basket = document.getElementById('basket')
basket_el = document.getElementById('basketitem_' + uid)
form_el.value = parseInt(form_el.value) - 1
if (form_el.value == 0) {
basket.removeChild(basket_el)
} else {
new_text = form_el.value + ' x ' + form_el.dataset.name
basket_el.innerText = new_text
}
}
</script>
{% endblock %}

144
web.py Normal file
View File

@ -0,0 +1,144 @@
from datetime import datetime
from flask import Flask, redirect, request, render_template, flash
from flask_login import LoginManager, login_user, logout_user, login_required
from pos.config import Config
from pos.logging import init_logging, get_logger
from pos.database import Database, User, Product, Event, Order, OrderEntry
config = Config()
debug = config.core['GENERAL'].getboolean('Debug', False)
conf_db = config.core['DATABASE']
conf_flask = config.core['FLASK']
init_logging(config.logging)
log = get_logger('web')
app = Flask(__name__)
app.config.update(
debug=debug,
SECRET_KEY=conf_flask['SECRET_KEY']
)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'error'
db = Database(**conf_db)
@login_manager.user_loader
def load_user(uid):
user = None
with db.get_session() as session:
user = session.query(User).get(uid)
return user
@app.route('/')
def home():
return redirect('/sell')
@app.route('/login', methods=['GET'])
def login_page():
return render_template('login.html', title='Login')
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if not all([username, password]):
flash("Fill all fields.", 'error')
return login_page(), 400
with db.get_session() as session:
user = session.query(User)\
.filter(User.username == username)\
.filter(User.is_active == 1)\
.one_or_none()
if user is None:
flash("User not found.", 'error')
return login_page(), 400
if user.password == password:
login_user(user)
session.add(user)
user.is_authenticated = True
flash("Succesfully logged in.", 'success')
return redirect('/')
else:
flash("Wrong password.", 'error')
return login_page(), 400
@app.route('/logout')
def logout():
logout_user()
return redirect('/')
def get_products(session):
return session.query(Product)\
.filter(Product.is_active == 1)\
.all()
def get_event(session):
return session.query(Event)\
.filter(Event.starts_at < datetime.now())\
.filter((Event.ends_at > datetime.now()) |
(Event.ends_at is None))\
.one_or_none()
@app.route('/sell', methods=['GET'])
def sell_page():
with db.get_session() as session:
products = get_products(session)
event = get_event(session)
return render_template('sell.html',
title='Sell', event=event, products=products)
@app.route('/sell', methods=['POST'])
@login_required
def sell():
app.logger.info(str(request.form))
with db.get_session() as session:
event = get_event(session)
if event is None:
flash("Can't perform order without an ongoing event!", 'error')
return sell_page()
quantities = request.form.values()
if not any(int(qty) > 0 for qty in quantities):
flash("Can't perform empty order!", 'error')
return sell_page()
with db.get_session() as session:
items = request.form.items()
entries = [
OrderEntry(product_uid=uid, quantity=qty)
for uid, qty in items
if int(qty) > 0
]
order = Order(event_uid=event.uid, entries=entries)
session.add(order)
flash("Success!", 'success')
return sell_page()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=debug)