import sqlalchemy import re from helpers import * exit_commands = ['exit', 'abort', 'quit'] class ExitMenu(Exception): pass class Menu(): def __init__(self, name, items=[], prompt='> ', return_index=True, exit_msg=None): self.name = name self.items = items self.prompt = prompt self.return_index = return_index self.exit_msg = exit_msg def at_exit(self): if self.exit_msg: print self.exit_msg def item_is_submenu(self, i): return isinstance(self.items[i], Menu) def item_name(self, i): if self.item_is_submenu(i): return self.items[i].name elif isinstance(self.items[i], tuple): return self.items[i][1] else: return str(self.items[i]) def item_value(self, i): if isinstance(self.items[i], tuple): return self.items[i][0] if self.return_index: return i return self.items[i] def input_str(self, prompt=None, regex=None): if regex != None: while True: result = self.input_str(prompt) if re.match(regex+'$', result): return result else: print 'Value must match regular expression "%s"' % regex if prompt == None: prompt = self.prompt try: result = raw_input(prompt) except EOFError: print 'quit' raise ExitMenu() if result in exit_commands: raise ExitMenu() return result def input_int(self, prompt=None, allowed_range=(None,None)): if prompt == None: prompt = self.prompt while True: result = self.input_str(prompt) try: value = int(result) if ((allowed_range[0] and value < allowed_range[0]) or (allowed_range[1] and value > allowed_range[1])): if allowed_range[0] and allowed_range[1]: print 'Value must be in range [%d,%d]' % allowed_range elif allowed_range[0]: print 'Value must be at least %d' % allowed_range[0] else: print 'Value must be at most %d' % allowed_range[1] else: return value except ValueError: print 'Please enter an integer' def input_user(self, prompt=None): user = None while user == None: user = retrieve_user(self.input_str(prompt), self.session) return user def input_product(self, prompt=None): product = None while product == None: product = retrieve_product(self.input_str(prompt), self.session) return product def input_thing(self, prompt=None, permitted_things=('user','product'), empty_input_permitted=False): result = None while result == None: search_str = self.input_str(prompt) if search_str == '' and empty_input_permitted: return None result = self.search_for_thing(search_str, permitted_things) return result def search_for_thing(self, search_str, permitted_things=('user','product')): search_fun = {'user': search_user, 'product': search_product} results = {} result_values = {} for thing in permitted_things: results[thing] = search_fun[thing](search_str, self.session) result_values[thing] = self.search_result_value(results[thing]) selected_thing = argmax(result_values) return search_ui2(search_str, results[selected_thing], selected_thing, self.session) def search_result_value(self, result): if result == None: return 0 if not isinstance(result, list): return 3 if len(result) == 0: return 0 if len(result) == 1: return 2 return 1 def print_header(self): print print '[%s]' % self.name def pause(self): self.input_str('.') def execute(self): try: return self._execute() except ExitMenu: self.at_exit() return None def _execute(self): while True: self.print_header() if len(self.items)==0: print '(empty menu)' self.pause() return None for i in range(len(self.items)): print '%d ) %s' % (i+1, self.item_name(i)) item_i = self.input_int(self.prompt, (1,len(self.items)))-1 if self.item_is_submenu(item_i): self.items[item_i].execute() else: return self.item_value(item_i) class Selector(Menu): def __init__(self, name, items=[], prompt='select> ', return_index=True, exit_msg=None): Menu.__init__(self, name, items, prompt, return_index, exit_msg) def print_header(self): print self.name class ChargeMenu(Menu): def __init__(self): self.name = "Add credits to a user account" def execute(self): self.session = Session() amount = self.input_int('Amount to be added> ') user = self.input_user('To user>') t = Transaction(user, -amount, 'Add '+str(amount)+' to user '+user.name) t.perform_transaction() self.session.add(t) self.session.commit() print 'Added %d kr to user %s\'s account' % (amount, user.name) print 'User %s\'s credit is now %d kr' % (user,user.credit) self.session.close() self.pause() class TransferMenu(Menu): def __init__(self): Menu.__init__(self, 'Transfer credit between users') def _execute(self): self.print_header() self.session = Session() amount = self.input_int('Transfer amount> ') user1 = self.input_user('From user> ') user2 = self.input_user('To user> ') t1 = Transaction(user1, amount, 'transfer to '+user2.name) t2 = Transaction(user2, -amount, 'transfer from '+user1.name) t1.perform_transaction() t2.perform_transaction() self.session.add(t1) self.session.add(t2) self.session.commit() print 'Transfered %d kr from %s to %s' % (amount, user1, user2) print 'User %s\'s credit is now %d kr' % (user1, user1.credit) print 'User %s\'s credit is now %d kr' % (user2, user2.credit) self.session.close() self.pause() class AddUserMenu(Menu): def __init__(self): Menu.__init__(self, 'Add user') def _execute(self): self.print_header() self.session = Session() username = self.input_str('User name> ', regex=User.name_re) cardnum = self.input_str('Card number> ', regex=User.card_re) user = User(username, cardnum) self.session.add(user) try: self.session.commit() print 'User %s stored' % username except sqlalchemy.exc.IntegrityError, e: print 'Could not store user %s: %s' % (username,e) self.session.close() self.pause() class EditUserMenu(Menu): def __init__(self): Menu.__init__(self, 'Edit user') def _execute(self): self.print_header() self.session = Session() user = self.input_user('User> ') user.card = self.input_str('Card number (currently "%s")> ' % user.card, regex=User.card_re) self.session.commit() print 'User %s stored' % user.name self.session.close() self.pause() class AddProductMenu(Menu): def __init__(self): Menu.__init__(self, 'Add product') def _execute(self): self.session = Session() self.print_header() bar_code = self.input_int('Bar code> ') name = self.input_str('Name> ', regex=r".+") price = self.input_int('Price> ', (1,None)) product = Product(bar_code, name, price) self.session.add(product) try: self.session.commit() print 'Product %s stored' % name except sqlalchemy.exc.IntegrityError, e: print 'Could not store product %s: %s' % (name,e) self.session.close() self.pause() class EditProductMenu(Menu): def __init__(self): Menu.__init__(self, 'Edit product') def _execute(self): self.print_header() self.session = Session() product = self.input_product('Product> ') while True: selector = Selector('Do what with %s?' % product.name, items=[('name', 'Edit name'), ('price', 'Edit price (currently %d)' % product.price), ('store', 'Store')]) what = selector.execute() if what == 'name': product.name = self.input_str('Name> ') elif what == 'price': product.price = self.input_int('Price> ') elif what == 'store': self.session.commit() print 'Product %s stored' % product.name self.session.close() self.pause() return class ShowUserMenu(Menu): def __init__(self): Menu.__init__(self, 'Show user') def _execute(self): self.session = Session() self.print_header() user = self.input_user('User name or card number> ') print 'User name: %s' % user.name print 'Card number: %s' % user.card print 'Credit: %s kr' % user.credit self.print_transactions(user) self.pause() def print_transactions(self, user): limit = 20 if len(user.transactions) == 0: print 'No transactions' return if len(user.transactions) <= limit: print 'Transactions:' else: print 'Transactions (last %d):' % limit for t in user.transactions[-limit:]: string = ' * %s: %s %d kr, ' % \ (t.time.strftime('%Y-%m-%d %H:%M'), {True:'in', False:'out'}[t.amount<0], abs(t.amount)) if t.purchase: string += 'purchase (' string += ', '.join(map(lambda e: e.product.name, t.purchase.entries)) string += ')' else: string += t.description print string class BuyMenu(Menu): def __init__(self): Menu.__init__(self, 'Buy') def _execute(self): self.print_header() self.session = Session() self.purchase = Purchase() while True: self.print_purchase() print {(False,False): 'Enter user or product identification', (False,True): 'Enter user identification or more products', (True,False): 'Enter product identification or more users', (True,True): 'Enter more products or users, or an empty line to confirm' }[(len(self.purchase.transactions) > 0, len(self.purchase.entries) > 0)] thing = self.input_thing(empty_input_permitted=True) if thing == None: if not self.complete_input(): if confirm('Not enough information entered. Abort purchase? (y/n) '): return False continue break if isinstance(thing, User): Transaction(thing, purchase=self.purchase) elif isinstance(thing, Product): PurchaseEntry(self.purchase, thing, 1) self.purchase.perform_purchase() self.session.add(self.purchase) self.session.commit() print 'Purchase stored.' self.print_purchase() for t in self.purchase.transactions: print 'User %s\'s credit is now %d kr' % (t.user.name, t.user.credit) self.session.close() self.pause() return True def complete_input(self): return self.purchase.is_complete() def format_purchase(self): self.purchase.set_price() transactions = self.purchase.transactions entries = self.purchase.entries if len(transactions) == 0 and len(entries) == 0: return None string = 'Purchase:' string += '\n buyers: ' if len(transactions) == 0: string += '(empty)' else: string += ', '.join(map(lambda t: t.user.name, transactions)) string += '\n products: ' if len(entries) == 0: string += '(empty)' else: string += ', '.join(map(lambda e: '%s (%d kr)'%(e.product.name, e.product.price), entries)) if len(transactions) > 1: string += '\n price per person: %d kr' % self.purchase.price_per_transaction() string += '\n total price: %d kr' % self.purchase.price return string def print_purchase(self): info = self.format_purchase() if info != None: print info class AdjustCreditMenu(Menu): # reimplements ChargeMenu; these should be combined to one def __init__(self): Menu.__init__(self, 'Adjust credit') def _execute(self): self.print_header() self.session = Session() user = self.input_user('User> ') print 'User %s\'s credit is %d kr' % (user.name, user.credit) amount = self.input_int('Add amount> ') description = self.input_str('Log message> ') if description == '': description = 'manually adjusted credit' transaction = Transaction(user, -amount, description) transaction.perform_transaction() self.session.add(transaction) self.session.commit() print 'User %s\'s credit is now %d kr' % (user.name, user.credit) self.session.close() self.pause() class ProductListMenu(Menu): def __init__(self): Menu.__init__(self, 'Product list') def _execute(self): self.print_header() session = Session() product_list = session.query(Product).all() line_format = '%-20s %6s %-15s' print line_format % ('name', 'price', 'bar code') print '-------------------------------------------' for p in product_list: print line_format % (p.name, p.price, p.bar_code) self.pause() class AddProductMenu(Menu): def __init__(self): Menu.__init__(self, 'Add product') def _execute(self): name = self.input_str('Product name> ') bar_code = self.input_int('Bar code> ') price = self.input_int('Price> ') product = Product(bar_code,name,price) session = Session() session.add(product) session.commit() session.close() print 'Added product %s, price %d, bar code %d' % (name,price,bar_code) self.pause() #class MainMenu(): # def __init__(self): # self.menu_list = [Menu("Buy"),ChargeMenu(), Menu("Add User"), Menu("Add Product")] # def execute(self): # while 1: # print "Main Menu: \nWhat do you want to do? \n" # for i in range(len(self.menu_list)): # print i+1," ) ",self.menu_list[i].name # result = raw_input('\nEnter a number corresponding to your action, or "exit" to exit \n') # if result in ["1","2","3","4"]: # self.menu_list[int(result)-1].execute() # elif result in ["quit", "exit", "abort"]: # print "OK, quitting" # break # else: # print "This does not make sense" def dwim_search(string, session): typ = guess_data_type(string) if typ == None: print 'This does not make sense' return retriever = {'card': retrieve_user, 'username': retrieve_user, 'bar_code': retrieve_product, 'product_name': retrieve_product} value_type = {'card': 'user', 'username': 'user', 'bar_code': 'product', 'product_name': 'product'} value = retriever[typ](string, session) # if value == None: # print 'Input "%s" interpreted as %s; no matching %s found.' \ # % (string, typ, value_type[typ]) return (value_type[typ], value) def search_ui(search_fun, search_str, thing, session): result = search_fun(search_str, session) return search_ui2(search_str, result, thing, session) def search_ui2(search_str, result, thing, session): if not isinstance(result, list): return result if len(result) == 0: print 'No %ss matching "%s"' % (thing, search_str) return None if len(result) == 1: msg = 'One %s matching "%s": %s. Use this? (y/n) ' %\ (thing, search_str, result[0]) if confirm(msg): return result[0] return None limit = 9 if len(result) > limit: select_header = '%d %ss matching "%s"; showing first %d' % \ (len(result), thing, search_str, limit) select_items = result[:limit] else: select_header = '%d %ss matching "%s"' % \ (len(result), thing, search_str) select_items = result selector = Selector(select_header, items=select_items, return_index=False) return selector.execute() def retrieve_user(search_str, session): return search_ui(search_user, search_str, 'user', session) def retrieve_product(search_str, session): return search_ui(search_product, search_str, 'product', session) #main = MainMenu() main = Menu('Dibbler main menu', items=[BuyMenu(), ProductListMenu(), ShowUserMenu(), AdjustCreditMenu(), ChargeMenu(), TransferMenu(), Menu('Add/edit', items=[AddUserMenu(), EditUserMenu(), AddProductMenu(), EditProductMenu()]) ], exit_msg='happy happy joy joy') main.execute()