query_to_xml.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #
  2. # Copyright (c) Daniel Sheffield 2023
  3. # All rights reserved
  4. #
  5. # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
  6. from bottle import request, template, FormsDict
  7. from pandas import DataFrame
  8. from psycopg import Cursor
  9. from psycopg.sql import SQL, Literal
  10. from ..data.filter import get_filter
  11. from ..data.util import get_where_include_exclude
  12. from ..data.QueryManager import get_data
  13. from .form import get_form
  14. from . import BOOLEAN, PARAMS
  15. def get_product_rollup_statement(filters) -> SQL:
  16. _map = { k: k[0] for k in ('product', 'category', 'group') }
  17. where = [ get_where_include_exclude(
  18. _map[k], "name", list(include), list(exclude)
  19. ) for k, (include, exclude) in filters.items() ]
  20. return SQL("""
  21. SELECT
  22. count(DISTINCT p.id) AS "Products",
  23. count(DISTINCT c.id) AS "Categories",
  24. count(DISTINCT g.id) AS "Groups",
  25. p.name AS "product",
  26. c.name AS "category",
  27. g.name AS "group"
  28. FROM products p
  29. JOIN categories c ON p.category_id = c.id
  30. JOIN groups g ON c.group_id = g.id
  31. WHERE {where}
  32. GROUP BY ROLLUP (g.name, c.name, p.name)
  33. """).format(where=SQL("\nAND").join(where))
  34. def get_inner_query(query: FormsDict) -> SQL:
  35. filters = get_filter(query, allow=('group', 'category', 'product'))
  36. inner = get_product_rollup_statement(filters)
  37. return inner
  38. def render_form(cur: Cursor, inner: str, query: FormsDict):
  39. _filter = get_filter(query, allow=PARAMS)
  40. data = DataFrame(get_data(cur, inner)).dropna()
  41. action = request.path.split('/')[-1]
  42. organic = BOOLEAN.get(query.organic, None)
  43. return get_form(action, 'post', _filter, organic, data)
  44. def get_xml(cur: Cursor, sql: str):
  45. return cur.execute(SQL(
  46. "SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)"
  47. ).format(q=Literal(sql))).fetchone()[0]
  48. def get_products(cur: Cursor, query: FormsDict):
  49. inner = get_inner_query(query)
  50. form = render_form(cur, inner, query)
  51. sql = SQL("""
  52. SELECT
  53. --"Transactions",
  54. COALESCE("product", "Products"||'') "Product",
  55. COALESCE("category", "Categories"||'') "Category",
  56. COALESCE("group", "Groups"||'') "Group"
  57. FROM ({inner}) q
  58. WHERE q.product IS NOT NULL OR q.group IS NULL
  59. """).format(inner=inner).as_string(cur)
  60. xml = get_xml(cur, sql)
  61. return template("query-to-xml", title="Products", xml=xml, form=form)
  62. def get_categories(cur: Cursor, query: FormsDict):
  63. inner = get_inner_query(query)
  64. form = render_form(cur, inner, query)
  65. sql = SQL("""
  66. SELECT
  67. "Products" "Product",
  68. COALESCE("category", "Categories"||'') "Category",
  69. COALESCE("group", "Groups"||'') "Group"
  70. FROM ({inner}) q
  71. WHERE q.product IS NULL AND (q.category IS NOT NULL OR q.group IS NULL)
  72. """).format(inner=inner).as_string(cur)
  73. xml = get_xml(cur, sql)
  74. return template("query-to-xml", title="Categories", xml=xml, form=form)
  75. def get_groups(cur: Cursor, query: FormsDict):
  76. inner = get_inner_query(query)
  77. form = render_form(cur, inner, query)
  78. sql = SQL("""
  79. SELECT
  80. "Products",
  81. "Categories",
  82. COALESCE("group", "Groups"||'') "Group"
  83. FROM ({inner}) q
  84. WHERE q.category IS NULL
  85. """).format(inner=inner).as_string(cur)
  86. xml = get_xml(cur, sql)
  87. return template("query-to-xml", title="Groups", xml=xml, form=form)
  88. def get_tags_statement(filters) -> SQL:
  89. _map = {
  90. k: k[0] for k in ('product', 'category', 'group')
  91. }
  92. _map.update({ 'tag': 'tg' })
  93. where = [ get_where_include_exclude(
  94. _map[k], "name", list(include), list(exclude)
  95. ) for k, (include, exclude) in filters.items() ]
  96. return SQL("""
  97. SELECT * FROM (SELECT count(DISTINCT txn.id) AS "Uses", tg.name AS "Name"
  98. FROM tags tg
  99. JOIN tags_map tm ON tg.id = tm.tag_id
  100. JOIN transactions txn ON txn.id = tm.transaction_id
  101. WHERE {where}
  102. GROUP BY tg.name
  103. ORDER BY 1 DESC, 2) q
  104. UNION ALL
  105. SELECT count(DISTINCT txn.id) AS "Uses", count(DISTINCT tg.name)||'' AS "Name"
  106. FROM tags tg
  107. JOIN tags_map tm ON tg.id = tm.tag_id
  108. JOIN transactions txn ON txn.id = tm.transaction_id
  109. WHERE {where}
  110. """).format(where=SQL("\nAND").join(where))
  111. def get_inner_tags_query(query: FormsDict) -> SQL:
  112. filters = get_filter(query, allow=('tag',))
  113. inner = get_tags_statement(filters)
  114. return inner
  115. def get_tags(cur: Cursor, query: FormsDict):
  116. inner = get_inner_tags_query(query)
  117. form = render_form(cur, inner, query)
  118. sql = inner.as_string(cur)
  119. xml = get_xml(cur, sql)
  120. return template("query-to-xml", title="Tags", xml=xml, form=form)