This commit is contained in:
parent
e53b680dd2
commit
c8a6f6c209
13
db.py
13
db.py
|
@ -92,15 +92,16 @@ class Transaction(Base):
|
|||
|
||||
user = relationship(User, backref=backref('transactions', order_by=time))
|
||||
|
||||
def __init__(self, user, amount=0, description=None, purchase=None):
|
||||
def __init__(self, user, amount=0, description=None, purchase=None, penalty_ratio=1):
|
||||
self.user = user
|
||||
self.amount = amount
|
||||
self.description = description
|
||||
self.purchase = purchase
|
||||
self.penalty_ratio = penalty_ratio
|
||||
|
||||
def perform_transaction(self):
|
||||
self.time = datetime.datetime.now()
|
||||
self.user.credit -= self.amount
|
||||
self.user.credit -= self.amount * self.penalty_ratio
|
||||
if self.purchase:
|
||||
for entry in self.purchase.entries:
|
||||
entry.product.stock -= entry.amount
|
||||
|
@ -111,12 +112,12 @@ class Purchase(Base):
|
|||
|
||||
id = Column(Integer, primary_key=True)
|
||||
time = Column(DateTime)
|
||||
# user_name = Column(Integer, ForeignKey('users.name'))
|
||||
# user_name = Column(Integer, ForeignKey('users.name'))
|
||||
price = Column(Integer)
|
||||
# performed = Column(Boolean)
|
||||
# performed = Column(Boolean)
|
||||
|
||||
# user = relationship(User, backref=backref('purchases', order_by=id))
|
||||
# users = relationship(User, secondary=purchase_user, backref='purhcases'
|
||||
# user = relationship(User, backref=backref('purchases', order_by=id))
|
||||
# users = relationship(User, secondary=purchase_user, backref='purhcases'
|
||||
transactions = relationship(Transaction, order_by=Transaction.user_name, backref='purchase')
|
||||
entries = relationship(PurchaseEntry, backref=backref("purchase"))
|
||||
|
||||
|
|
229
text_based.py
229
text_based.py
|
@ -5,9 +5,11 @@ import sqlalchemy
|
|||
from sqlalchemy.sql import func
|
||||
from sqlalchemy import desc
|
||||
import re, sys, os, traceback, signal, readline
|
||||
from select import select
|
||||
from helpers import *
|
||||
import random
|
||||
from statistikkHelpers import statisticsTextOnly
|
||||
from math import ceil
|
||||
|
||||
random.seed()
|
||||
exit_commands = ['exit', 'abort', 'quit', 'bye', 'eat flaming death', 'q']
|
||||
|
@ -93,7 +95,9 @@ class Menu():
|
|||
return self.items[i]
|
||||
|
||||
def input_str(self, prompt=None, regex=None, length_range=(None,None),
|
||||
empty_string_is_none=False):
|
||||
empty_string_is_none=False, timeout=0):
|
||||
if prompt == None:
|
||||
prompt = self.prompt
|
||||
if regex != None:
|
||||
while True:
|
||||
result = self.input_str(prompt, length_range=length_range,
|
||||
|
@ -119,8 +123,14 @@ class Menu():
|
|||
print 'Value must have length at most %d' % length_range[1]
|
||||
else:
|
||||
return result
|
||||
if prompt == None:
|
||||
prompt = self.prompt
|
||||
if timeout != 0:
|
||||
print prompt,
|
||||
rlist, _, _ = select([sys.stdin], [], [], timeout)
|
||||
if rlist:
|
||||
s = sys.stdin.readline()
|
||||
return s
|
||||
else:
|
||||
return ''
|
||||
while True:
|
||||
try:
|
||||
result = unicode(raw_input(safe_str(prompt)),
|
||||
|
@ -274,7 +284,6 @@ class Menu():
|
|||
return (result,num)
|
||||
|
||||
|
||||
|
||||
def search_for_thing(self, search_str, permitted_things=('user','product'),
|
||||
add_nonexisting=()):
|
||||
search_fun = {'user': search_user,
|
||||
|
@ -349,7 +358,7 @@ class Menu():
|
|||
print 'No %ss matching "%s"' % (thing, search_str)
|
||||
return None
|
||||
if len(result) == 1:
|
||||
msg = 'One %s matching "%s": %s. Use this?' %\
|
||||
msg = 'One %s matching "%s": %s. Use this?' % \
|
||||
(thing, search_str, unicode(result[0]))
|
||||
if self.confirm(msg, default=True):
|
||||
return result[0]
|
||||
|
@ -369,8 +378,8 @@ class Menu():
|
|||
|
||||
|
||||
|
||||
def confirm(self, prompt, default=None):
|
||||
return ConfirmMenu(prompt, default).execute()
|
||||
def confirm(self, prompt, default=None, timeout=0):
|
||||
return ConfirmMenu(prompt, default, timeout).execute()
|
||||
|
||||
def print_header(self):
|
||||
print
|
||||
|
@ -465,15 +474,16 @@ class Selector(Menu):
|
|||
|
||||
|
||||
class ConfirmMenu(Menu):
|
||||
def __init__(self, prompt='confirm?', default=None):
|
||||
def __init__(self, prompt='confirm?', default=None, timeout=0):
|
||||
Menu.__init__(self, 'question', prompt=prompt,
|
||||
exit_disallowed_msg='Please answer yes or no')
|
||||
self.default=default
|
||||
self.timeout=0
|
||||
|
||||
def _execute(self):
|
||||
options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default]
|
||||
while True:
|
||||
result = self.input_str('%s (%s) ' % (self.prompt, options))
|
||||
result = self.input_str('%s (%s) ' % (self.prompt, options), timeout=self.timeout)
|
||||
result = result.lower()
|
||||
if result in ['y','yes']:
|
||||
return True
|
||||
|
@ -503,39 +513,39 @@ class FAQMenu(Menu):
|
|||
Menu.__init__(self, 'Frequently Asked Questions')
|
||||
self.items = [MessageMenu('What is the meaning with this program?',
|
||||
'''
|
||||
We want to avoid keeping lots of cash in PVVVV\'s money box and to
|
||||
make it easy to pay for stuff without using money. (Without using
|
||||
money each time, that is. You do of course have to pay for the things
|
||||
you buy eventually).
|
||||
We want to avoid keeping lots of cash in PVVVV\'s money box and to
|
||||
make it easy to pay for stuff without using money. (Without using
|
||||
money each time, that is. You do of course have to pay for the things
|
||||
you buy eventually).
|
||||
|
||||
Dibbler stores a "credit" amount for each user. When you register a
|
||||
purchase in Dibbler, this amount is decreased. To increase your
|
||||
credit, add money to the money box and use "Adjust credit" to tell
|
||||
Dibbler about it.
|
||||
'''),
|
||||
Dibbler stores a "credit" amount for each user. When you register a
|
||||
purchase in Dibbler, this amount is decreased. To increase your
|
||||
credit, add money to the money box and use "Adjust credit" to tell
|
||||
Dibbler about it.
|
||||
'''),
|
||||
MessageMenu('Can I still pay for stuff using cash?',
|
||||
'Yes. You can safely ignore this program completely.'),
|
||||
MessageMenu('How do I exit from a submenu/dialog/thing?',
|
||||
'Type "exit" or C-d.'),
|
||||
MessageMenu('What does "." mean?',
|
||||
'''
|
||||
The "." character, known as "full stop" or "period", is most often
|
||||
used to indicate the end of a sentence.
|
||||
The "." character, known as "full stop" or "period", is most often
|
||||
used to indicate the end of a sentence.
|
||||
|
||||
It is also used by Dibbler to indicate that the program wants you to
|
||||
read some text before continuing. Whenever some output ends with a
|
||||
line containing only a period, you should read the lines above and
|
||||
then press enter to continue.
|
||||
It is also used by Dibbler to indicate that the program wants you to
|
||||
read some text before continuing. Whenever some output ends with a
|
||||
line containing only a period, you should read the lines above and
|
||||
then press enter to continue.
|
||||
'''),
|
||||
MessageMenu('Why is the user interface so terribly unintuitive?',
|
||||
'''
|
||||
Answer #1: It is not.
|
||||
Answer #1: It is not.
|
||||
|
||||
Answer #2: We are trying to compete with PVV\'s microwave oven in
|
||||
userfriendliness.
|
||||
Answer #2: We are trying to compete with PVV\'s microwave oven in
|
||||
userfriendliness.
|
||||
|
||||
Answer #3: YOU are unintuitive.
|
||||
'''),
|
||||
Answer #3: YOU are unintuitive.
|
||||
'''),
|
||||
MessageMenu('Why is there no help command?',
|
||||
'There is. Have you tried typing "help"?'),
|
||||
MessageMenu('Where are the easter eggs? I tried saying "moo", but nothing happened.',
|
||||
|
@ -544,54 +554,54 @@ Answer #3: YOU are unintuitive.
|
|||
u'Godt spørsmål. Det virket sikkert som en god idé der og da.'),
|
||||
MessageMenu('I found a bug; is there a reward?',
|
||||
'''
|
||||
No.
|
||||
No.
|
||||
|
||||
But if you are certain that it is a bug, not a feature, then you
|
||||
should fix it (or better: force someone else to do it).
|
||||
But if you are certain that it is a bug, not a feature, then you
|
||||
should fix it (or better: force someone else to do it).
|
||||
|
||||
Follow this procedure:
|
||||
Follow this procedure:
|
||||
|
||||
1. Check out the Dibbler code from https://dev.pvv.ntnu.no/svn/dibbler
|
||||
1. Check out the Dibbler code from https://dev.pvv.ntnu.no/svn/dibbler
|
||||
|
||||
2. Fix the bug.
|
||||
2. Fix the bug.
|
||||
|
||||
3. Check that the program still runs (and, preferably, that the bug is
|
||||
3. Check that the program still runs (and, preferably, that the bug is
|
||||
in fact fixed).
|
||||
|
||||
4. Commit.
|
||||
4. Commit.
|
||||
|
||||
5. Update the running copy from svn:
|
||||
5. Update the running copy from svn:
|
||||
|
||||
$ su -
|
||||
# su -l -s /bin/bash pvvvv
|
||||
$ cd dibbler
|
||||
$ svn up
|
||||
|
||||
6. Type "restart" in Dibbler to replace the running process by a new
|
||||
6. Type "restart" in Dibbler to replace the running process by a new
|
||||
one using the updated files.
|
||||
'''),
|
||||
'''),
|
||||
MessageMenu('My question isn\'t listed here; what do I do?',
|
||||
'''
|
||||
DON\'T PANIC.
|
||||
DON\'T PANIC.
|
||||
|
||||
Follow this procedure:
|
||||
Follow this procedure:
|
||||
|
||||
1. Ask someone (or read the source code) and get an answer.
|
||||
1. Ask someone (or read the source code) and get an answer.
|
||||
|
||||
2. Check out the Dibbler code from https://dev.pvv.ntnu.no/svn/dibbler
|
||||
2. Check out the Dibbler code from https://dev.pvv.ntnu.no/svn/dibbler
|
||||
|
||||
3. Add your question (with answer) to the FAQ and commit.
|
||||
3. Add your question (with answer) to the FAQ and commit.
|
||||
|
||||
4. Update the running copy from svn:
|
||||
4. Update the running copy from svn:
|
||||
|
||||
$ su -
|
||||
# su -l -s /bin/bash pvvvv
|
||||
$ cd dibbler
|
||||
$ svn up
|
||||
|
||||
5. Type "restart" in Dibbler to replace the running process by a new
|
||||
5. Type "restart" in Dibbler to replace the running process by a new
|
||||
one using the updated files.
|
||||
''')]
|
||||
''')]
|
||||
|
||||
|
||||
|
||||
|
@ -857,6 +867,47 @@ addition, and you can type 'what' at any time to redisplay it.
|
|||
|
||||
When finished, write an empty line to confirm the purchase.
|
||||
'''
|
||||
def credit_check(self, user):
|
||||
"""
|
||||
|
||||
:param user:
|
||||
:type user: User
|
||||
:rtype: boolean
|
||||
"""
|
||||
assert isinstance(user, User)
|
||||
|
||||
return user.credit > low_credit_warning_limit
|
||||
|
||||
def low_credit_warning(self, user, timeout=False):
|
||||
assert isinstance(user, User)
|
||||
|
||||
print "***********************************************************************"
|
||||
print "***********************************************************************"
|
||||
print
|
||||
print "$$\ $$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\\"
|
||||
print "$$ | $\ $$ |$$ __$$\ $$ __$$\ $$$\ $$ |\_$$ _|$$$\ $$ |$$ __$$\\"
|
||||
print "$$ |$$$\ $$ |$$ / $$ |$$ | $$ |$$$$\ $$ | $$ | $$$$\ $$ |$$ / \__|"
|
||||
print "$$ $$ $$\$$ |$$$$$$$$ |$$$$$$$ |$$ $$\$$ | $$ | $$ $$\$$ |$$ |$$$$\\"
|
||||
print "$$$$ _$$$$ |$$ __$$ |$$ __$$< $$ \$$$$ | $$ | $$ \$$$$ |$$ |\_$$ |"
|
||||
print "$$$ / \$$$ |$$ | $$ |$$ | $$ |$$ |\$$$ | $$ | $$ |\$$$ |$$ | $$ |"
|
||||
print "$$ / \$$ |$$ | $$ |$$ | $$ |$$ | \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |"
|
||||
print "\__/ \__|\__| \__|\__| \__|\__| \__|\______|\__| \__| \______/"
|
||||
print
|
||||
print "***********************************************************************"
|
||||
print "***********************************************************************"
|
||||
print
|
||||
print "USER %s HAS LOWER CREDIT THAN %d." % (user.name, low_credit_warning_limit)
|
||||
print "THIS PURCHASE WILL CHARGE YOUR CREDIT TWICE AS MUCH."
|
||||
print "CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS."
|
||||
print
|
||||
print "Do you want to continue with this purchase?"
|
||||
|
||||
if timeout:
|
||||
print "THIS PURCHASE WILL AUTOMATICALLY BE PERFORMED IN 3 MINUTES!"
|
||||
return self.confirm(prompt=">", default=True, timeout=180)
|
||||
else:
|
||||
return self.confirm(prompt=">")
|
||||
|
||||
|
||||
def add_thing_to_purchase(self, thing):
|
||||
if isinstance(thing, User):
|
||||
|
@ -865,6 +916,13 @@ When finished, write an empty line to confirm the purchase.
|
|||
print 'You are now purchasing as the user anonym.'
|
||||
print 'You have to put money in the anonym-jar.'
|
||||
print '--------------------------------------------'
|
||||
|
||||
if not self.credit_check(thing):
|
||||
if self.low_credit_warning(thing, self.superfast_mode ):
|
||||
Transaction(thing, purchase=self.purchase, penalty_ratio=2)
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
Transaction(thing, purchase=self.purchase)
|
||||
elif isinstance(thing, Product):
|
||||
PurchaseEntry(self.purchase, thing, 1)
|
||||
|
@ -1006,6 +1064,46 @@ class AdjustStockMenu(Menu):
|
|||
print 'The stock is now %d' % (product.stock)
|
||||
|
||||
|
||||
class CleanupStockMenu(Menu):
|
||||
def __init__(self):
|
||||
Menu.__init__(self,'Stock Cleanup', uses_db=True)
|
||||
|
||||
def _execute(self):
|
||||
self.print_header()
|
||||
|
||||
products = self.session.query(Product).filter(Product.stock != 0).all()
|
||||
|
||||
print "Every product in stock will be printed."
|
||||
print "Entering no value will keep current stock or set it to 0 if it is negative."
|
||||
print "Entering a value will set current stock to that value."
|
||||
print "Press enter to begin."
|
||||
|
||||
self.pause()
|
||||
|
||||
changed_products = []
|
||||
|
||||
for product in products:
|
||||
oldstock = product.stock
|
||||
product.stock = self.input_int(product.name, (0,10000), default=max(0, oldstock))
|
||||
self.session.add(product)
|
||||
if oldstock != product.stock:
|
||||
changed_products.append((product, oldstock))
|
||||
|
||||
|
||||
try:
|
||||
self.session.commit()
|
||||
print 'New stocks are now stored.'
|
||||
self.pause()
|
||||
except sqlalchemy.exc.SQLAlchemyError, e:
|
||||
print 'Could not store stock: %s' % (e)
|
||||
self.pause()
|
||||
return
|
||||
|
||||
for p in changed_products:
|
||||
print p[0].name, ".", p[1], "->", p[0].stock
|
||||
|
||||
|
||||
|
||||
class AdjustCreditMenu(Menu): # reimplements ChargeMenu; these should be combined to one
|
||||
def __init__(self):
|
||||
Menu.__init__(self, 'Adjust credit', uses_db=True)
|
||||
|
@ -1078,14 +1176,14 @@ class ProductPopularityMenu(Menu):
|
|||
text = ''
|
||||
sub = \
|
||||
self.session.query(PurchaseEntry.product_bar_code,
|
||||
func.count('*').label('purchase_count'))\
|
||||
.group_by(PurchaseEntry.product_bar_code)\
|
||||
func.count('*').label('purchase_count')) \
|
||||
.group_by(PurchaseEntry.product_bar_code) \
|
||||
.subquery()
|
||||
product_list = \
|
||||
self.session.query(Product, sub.c.purchase_count)\
|
||||
.outerjoin((sub, Product.bar_code==sub.c.product_bar_code))\
|
||||
.order_by(desc(sub.c.purchase_count))\
|
||||
.filter(sub.c.purchase_count != None)\
|
||||
self.session.query(Product, sub.c.purchase_count) \
|
||||
.outerjoin((sub, Product.bar_code==sub.c.product_bar_code)) \
|
||||
.order_by(desc(sub.c.purchase_count)) \
|
||||
.filter(sub.c.purchase_count != None) \
|
||||
.all()
|
||||
line_format = '%10s | %-'+str(Product.name_length)+'s\n'
|
||||
text += line_format % ('items sold', 'product')
|
||||
|
@ -1103,14 +1201,14 @@ class ProductRevenueMenu(Menu):
|
|||
text = ''
|
||||
sub = \
|
||||
self.session.query(PurchaseEntry.product_bar_code,
|
||||
func.count('*').label('purchase_count'))\
|
||||
.group_by(PurchaseEntry.product_bar_code)\
|
||||
func.count('*').label('purchase_count')) \
|
||||
.group_by(PurchaseEntry.product_bar_code) \
|
||||
.subquery()
|
||||
product_list = \
|
||||
self.session.query(Product, sub.c.purchase_count)\
|
||||
.outerjoin((sub, Product.bar_code==sub.c.product_bar_code))\
|
||||
.order_by(desc(sub.c.purchase_count*Product.price))\
|
||||
.filter(sub.c.purchase_count != None)\
|
||||
self.session.query(Product, sub.c.purchase_count) \
|
||||
.outerjoin((sub, Product.bar_code==sub.c.product_bar_code)) \
|
||||
.order_by(desc(sub.c.purchase_count*Product.price)) \
|
||||
.filter(sub.c.purchase_count != None) \
|
||||
.all()
|
||||
line_format = '%7s | %10s | %5s | %-'+str(Product.name_length)+'s\n'
|
||||
text += line_format % ('revenue', 'items sold', 'price', 'product')
|
||||
|
@ -1244,8 +1342,8 @@ much money you're due in credits for the purchase when prompted.
|
|||
print ('\n%-'+str(Product.name_length-1)+'s Amount') % ("Product")
|
||||
print (6+Product.name_length)*'-'
|
||||
if len(self.products):
|
||||
# print "Products added:"
|
||||
# print (6+Product.name_length)*'-'
|
||||
# print "Products added:"
|
||||
# print (6+Product.name_length)*'-'
|
||||
for product in self.products.keys():
|
||||
print ('%'+str(-Product.name_length)+'s %5i') % (product.name, self.products[product])
|
||||
print (6+Product.name_length)*'-'
|
||||
|
@ -1265,7 +1363,7 @@ much money you're due in credits for the purchase when prompted.
|
|||
self.products[thing] = amount
|
||||
|
||||
def perform_transaction(self):
|
||||
# self.user.credit += self.price
|
||||
# self.user.credit += self.price
|
||||
description = self.input_str('Log message> ', length_range=(0,50))
|
||||
if description == '':
|
||||
description = 'Purchased products for PVVVV, adjusted credit '+str(self.price)
|
||||
|
@ -1277,7 +1375,7 @@ much money you're due in credits for the purchase when prompted.
|
|||
try:
|
||||
self.session.commit()
|
||||
print "Success! Transaction performed:"
|
||||
# self.print_info()
|
||||
# self.print_info()
|
||||
print "User %s's credit is now %i" % (self.user.name, self.user.credit)
|
||||
except sqlalchemy.exc.SQLAlchemyError, e:
|
||||
print 'Could not perform transaction: %s' % e
|
||||
|
@ -1298,7 +1396,8 @@ main = MainMenu('Dibbler main menu',
|
|||
EditUserMenu(),
|
||||
AddProductMenu(),
|
||||
EditProductMenu(),
|
||||
AdjustStockMenu(),]),
|
||||
AdjustStockMenu(),
|
||||
CleanupStockMenu(),]),
|
||||
ProductSearchMenu(),
|
||||
Menu('Statistics',
|
||||
items=[ProductPopularityMenu(),
|
||||
|
|
Loading…
Reference in New Issue