query_to_xml.py 4.6 KB

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