Browse Source

move xslt generated pages into separate file

Daniel Sheffield 1 year ago
parent
commit
31ca6482c6
6 changed files with 138 additions and 129 deletions
  1. 1 1
      app/activities/TransactionEditor.py
  2. 2 3
      app/rest/form.py
  3. 12 123
      app/rest/pyapi.py
  4. 120 0
      app/rest/query_to_xml.py
  5. 2 1
      app/rest/trend.py
  6. 1 1
      test/data/test_util.py

+ 1 - 1
app/activities/TransactionEditor.py

@@ -341,7 +341,7 @@ class TransactionEditor(FocusWidget):
         query_manager: QueryManager,
         query_manager: QueryManager,
     ):
     ):
         self.autocomplete_options = lambda name, data: self.query_manager.unique_suggestions(name.split('#', 1)[0], **data)
         self.autocomplete_options = lambda name, data: self.query_manager.unique_suggestions(name.split('#', 1)[0], **data)
-        self.activity_manager = activity_manager
+        self.activity_manager: ActivityManager = activity_manager
         self.query_manager = query_manager
         self.query_manager = query_manager
         self.buttons = {
         self.buttons = {
             'done': Button(('streak', u'Done')),
             'done': Button(('streak', u'Done')),

+ 2 - 3
app/rest/form.py

@@ -14,9 +14,8 @@ def get_option_groups(
     data: DataFrame, filter_data: Dict[str, Tuple[set, set]],
     data: DataFrame, filter_data: Dict[str, Tuple[set, set]],
     k: str, g: str, _type: str
     k: str, g: str, _type: str
 ):
 ):
-    in_chart = data['$/unit'].apply(lambda x: (x or False) and True)
     if k in data or (k == 'tag' and 'tags' in data):
     if k in data or (k == 'tag' and 'tags' in data):
-        k_data_in_chart = chain(*data[in_chart]["tags"]) if k == "tag" else data[in_chart][k]  
+        k_data_in_chart = chain(*data["tags"]) if k == "tag" else data[k]
     else:
     else:
         k_data_in_chart = []
         k_data_in_chart = []
     
     
@@ -46,7 +45,7 @@ def get_option_groups(
                     filter_data[k][0] - set(k_data_in_chart) - filter_data[k][1]
                     filter_data[k][0] - set(k_data_in_chart) - filter_data[k][1]
                 ))
                 ))
         else:
         else:
-            k_grouped_data_in_chart = set(data[in_chart & (data[g].apply(lambda x,axis=None: x == group))][k]) if k in data else set()
+            k_grouped_data_in_chart = set(data[data[g].apply(lambda x,axis=None: x == group)][k]) if k in data else set()
             if _type == "include":
             if _type == "include":
                 selected.extend(filter_data[k][0] & set(k_grouped_data_in_chart))
                 selected.extend(filter_data[k][0] & set(k_grouped_data_in_chart))
                 unselected.extend(k_grouped_data_in_chart - filter_data[k][0])
                 unselected.extend(k_grouped_data_in_chart - filter_data[k][0])

+ 12 - 123
app/rest/pyapi.py

@@ -3,7 +3,7 @@
 # All rights reserved
 # All rights reserved
 #
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from typing import Iterable, Dict
+from typing import Iterable
 import os
 import os
 from urllib.parse import urlencode
 from urllib.parse import urlencode
 from bottle import (
 from bottle import (
@@ -15,23 +15,15 @@ from bottle import (
     template,
     template,
     static_file,
     static_file,
 )
 )
-from pandas import DataFrame
 from psycopg import connect
 from psycopg import connect
-from psycopg.sql import SQL, Literal
 from threading import Thread
 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(
 from ..data.filter import(
     get_filter,
     get_filter,
     get_query_param,
     get_query_param,
 )
 )
-from .form import (
-    get_form,
-)
-from ..data.util import(
-    get_where_include_exclude
-)
 from . import trend as worker
 from . import trend as worker
 from . import PARAMS
 from . import PARAMS
 from .CachedLoadingPage import CachedLoadingPage
 from .CachedLoadingPage import CachedLoadingPage
@@ -47,31 +39,6 @@ conn = connect(f"{host} {db} {user} {password}")
 
 
 CACHE = Cache(10)
 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:
 def normalize_query(query: FormsDict, allow: Iterable[str] = None) -> str:
     param = get_filter(query, allow=allow)
     param = get_filter(query, allow=allow)
     return urlencode([
     return urlencode([
@@ -122,124 +89,46 @@ def trend():
 @route('/grocery/groups')
 @route('/grocery/groups')
 @normalize
 @normalize
 def groups():
 def groups():
-    filters = get_filter(request.query, allow=('group', 'category', 'product'))
-    _filter = get_filter(request.query, allow=PARAMS)
     try:
     try:
         with conn.cursor() as cur:
         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:
     finally:
         conn.commit()
         conn.commit()
     response.content_type = 'application/xhtml+xml; charset=utf-8'
     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')
 @route('/grocery/categories')
 @normalize
 @normalize
 def categories():
 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:
     try:
         with conn.cursor() as cur:
         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:
     finally:
         conn.commit()
         conn.commit()
     response.content_type = 'application/xhtml+xml; charset=utf-8'
     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')
 @route('/grocery/products')
 @normalize
 @normalize
 def products():
 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:
     try:
         with conn.cursor() as cur:
         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:
     finally:
         conn.commit()
         conn.commit()
     response.content_type = 'application/xhtml+xml; charset=utf-8'
     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')
 @route('/grocery/tags')
 @normalize
 @normalize
 def tags():
 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:
     try:
         with conn.cursor() as cur:
         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:
     finally:
         conn.commit()
         conn.commit()
     response.content_type = 'application/xhtml+xml; charset=utf-8'
     response.content_type = 'application/xhtml+xml; charset=utf-8'
-    return template("query-to-xml", title="Tags", xml=xml, form=form)
-
+    return page
 
 

+ 120 - 0
app/rest/query_to_xml.py

@@ -0,0 +1,120 @@
+#
+# Copyright (c) Daniel Sheffield 2023
+# All rights reserved
+#
+# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
+from bottle import request, template, FormsDict
+from pandas import DataFrame
+from psycopg import Cursor
+from psycopg.sql import SQL, Literal
+
+from ..data.filter import get_filter
+from ..data.util import get_where_include_exclude
+from ..data.QueryManager import get_data
+from .form import get_form
+from . import PARAMS
+
+
+def get_product_rollup_statement(filters) -> SQL:
+    where = [ get_where_include_exclude(
+        k[0], "name", list(include), list(exclude)
+    ) for k, (include, exclude) in filters.items() ]
+    return 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
+WHERE {where}
+GROUP BY ROLLUP (g.name, c.name, p.name)
+""").format(where=SQL("\nAND").join(where))
+
+
+def get_inner_query(query: FormsDict) -> SQL:
+    filters = get_filter(query, allow=('group', 'category', 'product'))
+    inner = get_product_rollup_statement(filters)
+    return inner
+
+
+def render_form(cur: Cursor, inner: str, query: FormsDict):
+    _filter = get_filter(query, allow=PARAMS)
+    data = DataFrame(get_data(cur, inner)).dropna()
+    return get_form(request.path.split('/')[-1], 'get', _filter, data)
+
+
+def get_xml(cur: Cursor, sql: str):
+    return cur.execute(SQL(
+        "SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)"
+    ).format(q=Literal(sql))).fetchone()[0]
+
+
+def get_products(cur: Cursor, query: FormsDict):
+    inner = get_inner_query(query)
+    form = render_form(cur, inner, query)
+    sql = 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).as_string(cur)
+    xml = get_xml(cur, sql)
+    return template("query-to-xml", title="Products", xml=xml, form=form)
+
+
+def get_categories(cur: Cursor, query: FormsDict):
+    inner = get_inner_query(query)
+    form = render_form(cur, inner, query)
+    sql = 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).as_string(cur)
+    xml = get_xml(cur, sql)
+    return template("query-to-xml", title="Categories", xml=xml, form=form)
+
+
+def get_groups(cur: Cursor, query: FormsDict):
+    inner = get_inner_query(query)
+    form = render_form(cur, inner, query)
+    sql = SQL("""
+SELECT
+    "Products",
+    "Categories",
+    COALESCE("group", "Groups"||'') "Group"
+FROM ({inner}) q
+WHERE q.category IS NULL
+""").format(inner=inner).as_string(cur)
+    xml = get_xml(cur, sql)
+    return template("query-to-xml", title="Groups", xml=xml, form=form)
+
+def get_tags(cur: Cursor, query: FormsDict):
+    form = template('form-nav', action='tags', method='get', params=[
+        {'name': k, 'value': request.params[k]} for k in request.params if k in PARAMS
+    ])
+    sql = 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 = get_xml(cur, sql)
+    return template("query-to-xml", title="Tags", xml=xml, form=form)
+

+ 2 - 1
app/rest/trend.py

@@ -56,7 +56,8 @@ def trend_internal(conn: Connection[TupleRow], path: str, query: DictProperty):
             progress.append({ "name": "Loading data", "status": ""})
             progress.append({ "name": "Loading data", "status": ""})
             yield template("loading", progress=progress)
             yield template("loading", progress=progress)
             data = get_data(query_manager, **fields)
             data = get_data(query_manager, **fields)
-
+            in_chart = data['$/unit'].apply(lambda x: (x or False) and True)
+            data = data[in_chart]
             progress[-1]["status"] = "done"
             progress[-1]["status"] = "done"
             yield template("loading", progress=progress)
             yield template("loading", progress=progress)
 
 

+ 1 - 1
test/data/test_util.py

@@ -4,7 +4,7 @@
 # All rights reserved
 # All rights reserved
 #
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from psycopg.sql import SQL, Identifier, Literal, Composable
+from psycopg.sql import SQL, Literal, Composable
 from typing import Callable, Iterable, Union
 from typing import Callable, Iterable, Union
 from pytest import mark, raises
 from pytest import mark, raises
 from app.data.util import (
 from app.data.util import (