grocery_transactions.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #!/usr/bin/python3
  2. #
  3. # Copyright (c) Daniel Sheffield 2021 - 2023
  4. #
  5. # All rights reserved
  6. #
  7. # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
  8. import sys
  9. from psycopg import Cursor
  10. from urwid import raw_display, WidgetPlaceholder, SolidFill, MainLoop
  11. from app.activities.ActivityManager import ActivityManager, show_or_exit
  12. from app.activities.TransactionEditor import TransactionEditor
  13. from app.data.QueryManager import QueryManager, display_mapper
  14. from app.palette import iter_palettes, solarized
  15. try:
  16. from db_credentials import HOST, PASSWORD
  17. host = f'host={HOST}'
  18. password = f'password={PASSWORD}'
  19. except:
  20. host = ''
  21. password = ''
  22. try:
  23. import os
  24. import psycopg
  25. user = os.getenv('USER')
  26. conn: Cursor = psycopg.connect(f"{host} dbname=grocery user={user} {password}")
  27. cur = conn.cursor()
  28. except:
  29. print('Failed to set up db connection. Entering Mock mode')
  30. from mock import *
  31. args = sys.argv
  32. log = args[1]
  33. class GroceryTransactionEditor(WidgetPlaceholder):
  34. def __init__(self, activity_manager, cur, log):
  35. super().__init__(SolidFill(u'/'))
  36. self.activity_manager = activity_manager
  37. self.cur = cur
  38. txn: TransactionEditor = self.activity_manager.get('transaction')
  39. with open(log, 'r') as f:
  40. date = None
  41. store = None
  42. for line in f.readlines():
  43. if date is None and store is None:
  44. if '$store$' in line:
  45. date, store, _= line.split('$store$')
  46. date = date.split("'")[1]
  47. else:
  48. assert None not in (date, store,), \
  49. "Both date and store should be set or neither should be set"
  50. if '$store$' in line:
  51. assert date in line and f'$store${store}$store$' in line, \
  52. "Date ({date}) and store ({store}) not found in {line}."\
  53. " Mixing transactions from different dates and stores is not supported"
  54. self.cur.execute(line)
  55. if None not in (date, store):
  56. txn.apply_choice('ts', date)
  57. txn.apply_choice('store', store)
  58. else:
  59. txn.focus_on(txn.edit_fields['ts'])
  60. self.activity_manager.show(self)
  61. self.activity_manager.show(txn.update())
  62. self.log = self.open(log)
  63. def _open(self, log):
  64. with open(log, 'a') as f:
  65. yield f
  66. def open(self, log):
  67. self._to_close = self._open(log)
  68. return next(self._to_close)
  69. def close(self):
  70. if self._to_close is not None:
  71. self._to_close.close()
  72. self._to_close = None
  73. def save(self, data):
  74. ts = data['ts']
  75. store = data['store']
  76. description = data['description']
  77. quantity = data['quantity']
  78. unit = data['unit']
  79. price = data['price']
  80. product = data['product']
  81. organic = 'true' if data['organic'] is True else 'false'
  82. tags = ', '.join([
  83. f'$quot${v}$quot$' for t,v in data.items() if 'tags' in t and v
  84. ])
  85. tags = f", ARRAY[{tags}]" if tags else ''
  86. statement = \
  87. f"CALL insert_transaction('{ts}', $store${store}$store$, " \
  88. f"$descr${description}$descr$, {quantity}, $unit${unit}$unit$, " \
  89. f"{price}, $produ${product}$produ$, {organic}{tags});\n"
  90. self.log.write(statement)
  91. self.log.flush()
  92. self.cur.execute(statement)
  93. cur.execute("BEGIN")
  94. activity_manager = ActivityManager()
  95. query_manager = QueryManager(cur, display_mapper)
  96. activity_manager.create(TransactionEditor, 'transaction',
  97. activity_manager, query_manager,
  98. )
  99. app = None
  100. palettes = iter_palettes(solarized.theme)
  101. try:
  102. app = GroceryTransactionEditor(activity_manager, cur, log)
  103. screen = raw_display.Screen()
  104. loop = MainLoop(app, next(palettes), screen=screen,
  105. unhandled_input=lambda k: show_or_exit(k, screen=screen, palettes=palettes),
  106. pop_ups=True
  107. )
  108. loop.run()
  109. finally:
  110. if app is not None:
  111. app.close()
  112. cur.close()
  113. conn.close()