diff --git a/data b/data index 783c380..3493907 100644 Binary files a/data and b/data differ diff --git a/db.py b/db.py index 51ec682..196dd20 100644 --- a/db.py +++ b/db.py @@ -21,6 +21,9 @@ class User(Base): def __repr__(self): return "" % self.name + def __str__(self): + return self.name + class Product(Base): __tablename__ = 'products' @@ -36,6 +39,9 @@ class Product(Base): def __repr__(self): return "" % (self.name, self.bar_code, self.price) + def __str__(self): + return self.name + class PurchaseEntry(Base): __tablename__ = 'purchase_entries' id = Column(Integer, primary_key=True) diff --git a/helpers.py b/helpers.py index 5235639..36e8c2a 100644 --- a/helpers.py +++ b/helpers.py @@ -1,6 +1,34 @@ from db import * from sqlalchemy import or_ +def search_user(string, session): + exact_match = session.query(User).filter(or_(User.name==string, User.card==string)).first() + if exact_match: + return exact_match + user_list = session.query(User).filter(or_(User.name.like('%'+string+'%'),User.card.like('%'+string+'%'))).all() + return user_list + +def search_product(string, session): + exact_match = session.query(Product)\ + .filter(or_(Product.bar_code==string, + Product.name==string)).first() + if exact_match: + return exact_match + product_list = session.query(Product)\ + .filter(or_(Product.bar_code.like('%'+string+'%'), + Product.name.like('%'+string+'%'))).all() + return product_list + +def guess_data_type(string): + if string.startswith('NTNU'): + return 'card' + if string.isdigit(): + return 'bar_code' + if string.isalpha() and string.islower(): + return 'username' + return 'product_name' + + def retrieve_user(string, session): first = session.query(User).filter(or_(User.name==string, User.card==string)).first() if first: @@ -22,9 +50,9 @@ def retrieve_user(string, session): return select_from_list(list) -def confirm(): +def confirm(prompt='Confirm? (y/n) '): while True: - input = raw_input("Confirm? (y/n)\n") + input = raw_input(prompt) if input in ["y","yes"]: return True elif input in ["n","no"]: diff --git a/text_based.py b/text_based.py index 9bc73ad..b496345 100644 --- a/text_based.py +++ b/text_based.py @@ -1,8 +1,100 @@ from helpers import * +exit_commands = ['exit', 'abort', 'quit'] + +class ExitMenu(Exception): + pass + class Menu(): - def __init__(self, name): + def __init__(self, name, items=[], prompt='> ', exit_msg=None): self.name = name + self.items = items + self.prompt = prompt + 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 + else: + return str(self.items[i]) + + def input_str(self, prompt=None): + 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 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 item_i + +class Selector(Menu): + def print_header(self): + print self.name + + def _execute(self): + result = Menu._execute(self) + if result != None: + return self.items[result] + return None class ChargeMenu(Menu): def __init__(self): @@ -44,6 +136,88 @@ class ChargeMenu(Menu): self.session.close() break +class ShowUserMenu(Menu): + def __init__(self): + Menu.__init__(self, 'Show user') + + def _execute(self): + self.print_header() + user = None + while user == None: + search_str = self.input_str('User name or card number> ') + user = search_ui(search_user, search_str, 'user', Session()) + print 'User name: %s' % user.name + print 'Card number: %s' % user.card + print 'Credit: %s' % user.credit + self.pause() + + +class BuyMenu(Menu): + def __init__(self): + Menu.__init__(self, 'Buy') + + def _execute(self): + self.print_header() + session = Session() + user = None + products = [] + while True: + self.print_partial_purchase(user, products) + print {(False,False): 'Enter user or product identification', + (False,True): 'Enter user identification or more products', + (True,False): 'Enter product identification', + (True,True): 'Enter more products, or an empty line to confirm' + }[(user != None, len(products) > 0)] + string = self.input_str() + if string == '': + if user == None or len(products) == 0: + if confirm('Not enough information entered. Abort purchase? (y/n) '): + return False + continue + break + (value_type,value) = dwim_search(string, session) + if value != None: + if value_type == 'user': + user = value + elif value_type == 'product': + products.append(value) + print 'OK purchase' + print self.format_partial_purchase(user, products) + # TODO build Purchase object and commit + self.pause() + + + def format_partial_purchase(self, user, products): + if user == None and len(products) == 0: + return + strings = [] + if user != None: + strings.append(' user: ' + user.name) + if len(products) > 0: + strings.append(' products: ' + ', '.join(map(lambda p: p.name, products))) + return '\n'.join(strings) + + def print_partial_purchase(self, user, products): + info = self.format_partial_purchase(user, products) + if info != None: + print 'Your purchase:\n' + info + + +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') + for p in product_list: + print line_format % (p.name, p.price, p.bar_code) + self.pause() + + class MainMenu(): def __init__(self): self.menu_list = [Menu("Buy"),ChargeMenu(), Menu("Add User"), Menu("Add Product")] @@ -62,7 +236,55 @@ class MainMenu(): else: print "This does not make sense" -main = MainMenu() + +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) + 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 + selector = Selector('%d %ss matching "%s":' % (len(result), thing, search_str), + items=result, + prompt='select> ') + 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(), ChargeMenu(), Menu('Add user'), Menu('Add product'), + ShowUserMenu(), ProductListMenu()], + exit_msg='happy happy joy joy') main.execute()