Enda mer mystisk brukergrensesnitt.

Brukergrensesnittet er forsøkt forbedret.  Forandringene er basert på
observasjoner av reelle brukeres handlinger.

* Buy-menyen har fått litt ekstra magi for tilfellet der inputstrengen
  ikke gir noe treff i databasen.  Hvis strengen ser ut som et
  brukernavn (dvs finnes i /etc/passwd) eller kortnummer får man
  spørsmål om man vil lage brukeren.  For kortnummer får man også
  muligheten til å assosiere kortnummeret med en eksisterende bruker.

* AddUserMenu presiserer litt tydeligere hva det forventes at man skal
  skrive: at brukernavnet skal være PVV-brukernavn og at kortnummeret
  kan utelates.

* Menu.confirm er gjort case-insensitiv, så den godtar strengene 'y',
  'n', 'yes', 'no' i alle kombinasjoner av små og store bokstaver.
This commit is contained in:
Øystein Ingmar Skartsæterhagen 2010-05-13 18:11:31 +00:00
parent 5c9d73fbb4
commit 9199da6c13
2 changed files with 128 additions and 72 deletions

View File

@ -1,5 +1,6 @@
from db import *
from sqlalchemy import or_
import pwd
def search_user(string, session):
exact_match = session.query(User).filter(or_(User.name==string, User.card==string)).first()
@ -20,14 +21,25 @@ def search_product(string, session):
Product.name.ilike('%'+string+'%'))).all()
return product_list
# def guess_data_type(string):
# if string.startswith('NTNU'):
def system_user_exists(username):
try:
pwd.getpwnam(username)
except KeyError:
return False
else:
return True
def guess_data_type(string):
if string.startswith('ntnu') and string[4:].isdigit():
return 'card'
if string.isdigit() and len(string) in [8,13]:
return 'bar_code'
# if string.isdigit() and len(string) > 5:
# return 'card'
# if string.isdigit():
# return 'bar_code'
# if string.isalpha() and string.islower():
# return 'username'
# return 'product_name'
if string.isalpha() and string.islower() and system_user_exists(string):
return 'username'
return None
# def retrieve_user(string, session):

View File

@ -163,28 +163,33 @@ class Menu():
def input_user(self, prompt=None):
user = None
while user == None:
user = retrieve_user(self.input_str(prompt),
self.session)
user = self.retrieve_user(self.input_str(prompt))
return user
def retrieve_user(self, search_str):
return self.search_ui(search_user, search_str, 'user')
def input_product(self, prompt=None):
product = None
while product == None:
product = retrieve_product(self.input_str(prompt),
self.session)
product = self.retrieve_product(self.input_str(prompt))
return product
def retrieve_product(self, search_str):
return self.search_ui(search_product, search_str, 'product')
def input_thing(self, prompt=None, permitted_things=('user','product'),
empty_input_permitted=False):
add_nonexisting=(), 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)
result = self.search_for_thing(search_str, permitted_things, add_nonexisting)
return result
def search_for_thing(self, search_str, permitted_things=('user','product')):
def search_for_thing(self, search_str, permitted_things=('user','product'),
add_nonexisting=()):
search_fun = {'user': search_user,
'product': search_product}
results = {}
@ -193,8 +198,15 @@ class Menu():
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)
if results[selected_thing] == []:
thing_for_type = {'card': 'user', 'username': 'user',
'bar_code': 'product'}
type_guess = guess_data_type(search_str)
if type_guess != None and thing_for_type[type_guess] in add_nonexisting:
return self.search_add(search_str)
print 'No match found for "%s".' % search_str
return None
return self.search_ui2(search_str, results[selected_thing], selected_thing)
def search_result_value(self, result):
if result == None:
@ -207,6 +219,69 @@ class Menu():
return 2
return 1
def search_add(self, string):
type_guess = guess_data_type(string)
if type_guess == 'username':
print '"%s" looks like a username, but no such user exists.' % string
if self.confirm('Create user %s?' % string):
user = User(string, None)
self.session.add(user)
return user
return None
if type_guess == 'card':
selector = Selector('"%s" looks like a card number, but no user with that card number exists.' % string,
[('create', 'Create user with card number %s' % string),
('set', 'Set card number of an existing user to %s' % string)])
selection = selector.execute()
if selection == 'create':
username = self.input_str('Username for new user (should be same as PVV username)> ',
User.name_re, (1,10))
user = User(username, string)
self.session.add(user)
return user
if selection == 'set':
user = self.input_user('User to set card number for> ')
old_card = user.card
user.card = string
print 'Card number of %s set to %s (was %s)' % (user.name, string, old_card)
return user
return None
if type_guess == 'bar_code':
print '"%s" looks like the bar code for a product, but no such product exists.' % string
return None
def search_ui(self, search_fun, search_str, thing):
result = search_fun(search_str, self.session)
return self.search_ui2(search_str, result, thing)
def search_ui2(self, search_str, result, thing):
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?' %\
(thing, search_str, unicode(result[0]))
if self.confirm(msg, default=True):
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 confirm(self, prompt, default=None):
return ConfirmMenu(prompt, default).execute()
@ -297,6 +372,7 @@ class ConfirmMenu(Menu):
options = {True: 'Y/n', False: 'y/N', None: 'y/n'}[self.default]
while True:
result = self.input_str('%s (%s) ' % (self.prompt, options))
result = result.lower()
if result in ['y','yes']:
return True
if result in ['n','no']:
@ -364,8 +440,8 @@ class AddUserMenu(Menu):
def _execute(self):
self.print_header()
self.session = Session()
username = self.input_str('User name> ', User.name_re, (1,10))
cardnum = self.input_str('Card number> ', User.card_re, (0,10))
username = self.input_str('Username (should be same as PVV username)> ', User.name_re, (1,10))
cardnum = self.input_str('Card number (optional)> ', User.card_re, (0,10))
user = User(username, cardnum)
self.session.add(user)
try:
@ -528,7 +604,8 @@ When finished, write an empty line to confirm the purchase.
(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)
thing = self.input_thing(add_nonexisting=('user',),
empty_input_permitted=True)
if thing == None:
if not self.complete_input():
if self.confirm('Not enough information entered. Abort purchase?',
@ -575,7 +652,8 @@ When finished, write an empty line to confirm the purchase.
if len(transactions) == 0:
string += '(empty)'
else:
string += ', '.join(map(lambda t: t.user.name, transactions))
string += ', '.join(map(lambda t: t.user.name,
transactions))
string += '\n products: '
if len(entries) == 0:
string += '(empty)'
@ -635,58 +713,24 @@ class ProductListMenu(Menu):
self.pause()
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?' %\
(thing, search_str, unicode(result[0]))
if ConfirmMenu(msg, default=True).execute():
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)
# 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 restart():