|
@@ -3,7 +3,7 @@
|
|
|
# All rights reserved
|
|
|
#
|
|
|
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
|
|
|
-from typing import Iterable, Dict
|
|
|
+from typing import Iterable
|
|
|
import os
|
|
|
from urllib.parse import urlencode
|
|
|
from bottle import (
|
|
@@ -15,23 +15,15 @@ from bottle import (
|
|
|
template,
|
|
|
static_file,
|
|
|
)
|
|
|
-from pandas import DataFrame
|
|
|
from psycopg import connect
|
|
|
-from psycopg.sql import SQL, Literal
|
|
|
from threading import Thread
|
|
|
|
|
|
-from app.data.QueryManager import QueryManager, get_data
|
|
|
+from .query_to_xml import get_categories, get_groups, get_products, get_tags
|
|
|
|
|
|
from ..data.filter import(
|
|
|
get_filter,
|
|
|
get_query_param,
|
|
|
)
|
|
|
-from .form import (
|
|
|
- get_form,
|
|
|
-)
|
|
|
-from ..data.util import(
|
|
|
- get_where_include_exclude
|
|
|
-)
|
|
|
from . import trend as worker
|
|
|
from . import PARAMS
|
|
|
from .CachedLoadingPage import CachedLoadingPage
|
|
@@ -47,31 +39,6 @@ conn = connect(f"{host} {db} {user} {password}")
|
|
|
|
|
|
CACHE = Cache(10)
|
|
|
|
|
|
-def get_product_rollup_statement(filters):
|
|
|
- where = [ get_where_include_exclude(
|
|
|
- k[0], "name", list(include), list(exclude)
|
|
|
- ) for k, (include, exclude) in filters.items() ]
|
|
|
- return SQL('\n').join([
|
|
|
- SQL("""
|
|
|
-SELECT
|
|
|
- count(DISTINCT p.id) AS "Products",
|
|
|
- count(DISTINCT c.id) AS "Categories",
|
|
|
- count(DISTINCT g.id) AS "Groups",
|
|
|
- p.name AS "product",
|
|
|
- c.name AS "category",
|
|
|
- g.name AS "group"
|
|
|
-FROM products p
|
|
|
-JOIN categories c ON p.category_id = c.id
|
|
|
-JOIN groups g ON c.group_id = g.id
|
|
|
-"""),
|
|
|
- SQL("""
|
|
|
-WHERE {where}
|
|
|
-""").format(where=SQL("\nAND").join(where)) if where else SQL(''),
|
|
|
- SQL("""
|
|
|
-GROUP BY ROLLUP (g.name, c.name, p.name)
|
|
|
-"""),
|
|
|
- ])
|
|
|
-
|
|
|
def normalize_query(query: FormsDict, allow: Iterable[str] = None) -> str:
|
|
|
param = get_filter(query, allow=allow)
|
|
|
return urlencode([
|
|
@@ -122,124 +89,46 @@ def trend():
|
|
|
@route('/grocery/groups')
|
|
|
@normalize
|
|
|
def groups():
|
|
|
- filters = get_filter(request.query, allow=('group', 'category', 'product'))
|
|
|
- _filter = get_filter(request.query, allow=PARAMS)
|
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
|
- inner = get_product_rollup_statement(filters)
|
|
|
- query = SQL("""
|
|
|
-SELECT
|
|
|
- "Products",
|
|
|
- "Categories",
|
|
|
- COALESCE("group", "Groups"||'') "Group"
|
|
|
-FROM (
|
|
|
-{inner}
|
|
|
-) q
|
|
|
-WHERE q.category IS NULL
|
|
|
-""").format(inner=inner)
|
|
|
- data = DataFrame(get_data(cur, inner)).dropna()
|
|
|
- data['$/unit'] = 1
|
|
|
- form = get_form(request.path.split('/')[-1], 'get', _filter, data)
|
|
|
- xml = cur.execute(SQL("""
|
|
|
-SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)
|
|
|
-""").format(q=Literal(query.as_string(cur)))).fetchone()[0]
|
|
|
+ page = get_groups(cur, request.query)
|
|
|
finally:
|
|
|
conn.commit()
|
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
|
- return template("query-to-xml", title="Groups", xml=xml, form=form)
|
|
|
+ return page
|
|
|
+
|
|
|
|
|
|
@route('/grocery/categories')
|
|
|
@normalize
|
|
|
def categories():
|
|
|
- filters = get_filter(request.query, allow=('group', 'category', 'product'))
|
|
|
- _filter = get_filter(request.query, allow=PARAMS)
|
|
|
- form = template('form-nav', action='categories', method='get', params=[
|
|
|
- {'name': k, 'value': request.params[k]} for k in request.params if k in PARAMS
|
|
|
- ])
|
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
|
- inner = get_product_rollup_statement(filters)
|
|
|
- query = SQL("""
|
|
|
-SELECT
|
|
|
- "Products" "Product",
|
|
|
- COALESCE("category", "Categories"||'') "Category",
|
|
|
- COALESCE("group", "Groups"||'') "Group"
|
|
|
-FROM (
|
|
|
-{inner}
|
|
|
-) q
|
|
|
-WHERE q.product IS NULL AND (q.category IS NOT NULL OR q.group IS NULL)
|
|
|
-""").format(inner=inner)
|
|
|
- data = DataFrame(get_data(cur, inner)).dropna()
|
|
|
- data['$/unit'] = 1
|
|
|
- form = get_form(request.path.split('/')[-1], 'get', _filter, data)
|
|
|
- xml = cur.execute(SQL("""
|
|
|
-SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)
|
|
|
-""").format(q=Literal(query.as_string(cur)))).fetchone()[0]
|
|
|
+ page = get_categories(cur, request.query)
|
|
|
finally:
|
|
|
conn.commit()
|
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
|
- return template("query-to-xml", title="Categories", xml=xml, form=form)
|
|
|
+ return page
|
|
|
|
|
|
@route('/grocery/products')
|
|
|
@normalize
|
|
|
def products():
|
|
|
- filters = get_filter(request.query, allow=('group', 'category', 'product'))
|
|
|
- _filter = get_filter(request.query, allow=PARAMS)
|
|
|
- form = template('form-nav', action='products', method='get', params=[
|
|
|
- {'name': k, 'value': request.params[k]} for k in request.params if k in PARAMS
|
|
|
- ])
|
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
|
- inner = get_product_rollup_statement(filters)
|
|
|
- query = SQL("""
|
|
|
-SELECT
|
|
|
- --"Transactions",
|
|
|
- COALESCE("product", "Products"||'') "Product",
|
|
|
- COALESCE("category", "Categories"||'') "Category",
|
|
|
- COALESCE("group", "Groups"||'') "Group"
|
|
|
-FROM (
|
|
|
-{inner}
|
|
|
-) q
|
|
|
-WHERE q.product IS NOT NULL OR q.group IS NULL
|
|
|
-""").format(inner=inner)
|
|
|
- data = DataFrame(get_data(cur, inner)).dropna()
|
|
|
- data['$/unit'] = 1
|
|
|
- form = get_form(request.path.split('/')[-1], 'get', _filter, data)
|
|
|
- xml = cur.execute(SQL("""
|
|
|
-SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)
|
|
|
-""").format(q=Literal(query.as_string(cur)))).fetchone()[0]
|
|
|
+ page = get_products(cur, request.query)
|
|
|
finally:
|
|
|
conn.commit()
|
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
|
- return template("query-to-xml", title="Products", xml=xml, form=form)
|
|
|
+ return page
|
|
|
+
|
|
|
|
|
|
@route('/grocery/tags')
|
|
|
@normalize
|
|
|
def tags():
|
|
|
- form = template('form-nav', action='tags', method='get', params=[
|
|
|
- {'name': k, 'value': request.params[k]} for k in request.params if k in PARAMS
|
|
|
- ])
|
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
|
- inner = SQL('\n').join([SQL("""
|
|
|
-SELECT * FROM (SELECT count(DISTINCT txn.id) AS "Uses", tg.name AS "Name"
|
|
|
-FROM tags tg
|
|
|
-JOIN tags_map tm ON tg.id = tm.tag_id
|
|
|
-JOIN transactions txn ON txn.id = tm.transaction_id
|
|
|
-GROUP BY tg.name
|
|
|
-ORDER BY 1 DESC, 2) q
|
|
|
-UNION ALL
|
|
|
-SELECT count(DISTINCT txn.id) AS "Uses", count(DISTINCT tg.name)||'' AS "Name"
|
|
|
-FROM tags tg
|
|
|
-JOIN tags_map tm ON tg.id = tm.tag_id
|
|
|
-JOIN transactions txn ON txn.id = tm.transaction_id
|
|
|
-""")]).as_string(cur)
|
|
|
- xml = cur.execute(SQL("""
|
|
|
-SELECT query_to_xml_and_xmlschema({inner}, false, false, ''::text)
|
|
|
-""").format(inner=Literal(inner))).fetchone()[0]
|
|
|
+ page = get_tags(cur, request.query)
|
|
|
finally:
|
|
|
conn.commit()
|
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
|
- return template("query-to-xml", title="Tags", xml=xml, form=form)
|
|
|
-
|
|
|
+ return page
|
|
|
|