def invoke(self, _clerk, server='%'): mod = Model(readable=readable) yesterday = Date("today") - 1 mod["server"] = server cur = _clerk.storage.dbc.cursor() sql =\ """ SELECT u.username, p.name, s.name, t.traffic FROM sys_user u, sys_user_traffic t, sys_server s, sys_plan p WHERE u.ID=t.userID AND p.ID=u.planID AND s.ID=u.serverID AND t.whichday = '%s' AND s.name like '%s%%' ORDER BY traffic DESC LIMIT 20 """ % (yesterday.toSQL(), mod["server"]) cur.execute(sql) mod["each"] = [{ "username": username, "plan": plan, "server": server, "traffic": traffic, } for username, plan, server, traffic in cur.fetchall()] cur.execute(""" SELECT name FROM sys_server """) mod["servers"] = [{"server": row[0]} for row in cur.fetchall()] return mod
def invoke(self, _clerk, server='%'): mod = Model(readable = readable) yesterday = Date("today")-1 mod["server"] = server cur = _clerk.storage.dbc.cursor() sql =\ """ SELECT u.username, p.name, s.name, t.traffic FROM sys_user u, sys_user_traffic t, sys_server s, sys_plan p WHERE u.ID=t.userID AND p.ID=u.planID AND s.ID=u.serverID AND t.whichday = '%s' AND s.name like '%s%%' ORDER BY traffic DESC LIMIT 20 """ % (yesterday.toSQL(), mod["server"]) cur.execute(sql) mod["each"] = [{ "username":username, "plan": plan, "server":server, "traffic":traffic, } for username, plan, server,traffic in cur.fetchall()] cur.execute( """ SELECT name FROM sys_server """) mod["servers"] = [{"server":row[0]} for row in cur.fetchall()] return mod
def test_comparisons(self): assert DateTime("06/20/1950 08:30:00") \ == "1950-6-20 8:30:00", "eq string compare" assert DateTime("01/01/2001 00:00:00") \ == Date("01/01/2001"), "eq comparison" assert Date("2001-5-1") <= DateTime("5/1/2001 0:0:0"), \ "le comparison" assert Date("5/1/2001") < DateTime("2001-5-1 00:00:01"), \ "lt comparison"
def test_postComment(self): n = self.c.store(Node(name="adsf")) self.p.input["node"] = n.ID self.p.input["content"] = "your ad here" self.assertRaises(weblib.Redirect, self.p.postComment) n = self.c.fetch(Node, ID=n.ID) assert len(n.comments) == 1 assert n.comments[0].content == "your ad here" assert Date(n.comments[0].posted.toSQL()) == Date("today")
def test_balance(self): acc = fakeAccount() nEv = duckbill.newEvent assert acc.balance() == 0 acc.events << nEv("charge", posted="1/1/2001", amount=5) assert acc.balance() == 5 assert acc.balance(Date("12/31/2000")) == 0 assert acc.balance(Date("1/1/2001")) == 0 assert acc.balance(Date("1/2/2001")) == 5
def testVestingPreconditions(self): "make sure Event requires valid dates in order" e = Event(event="charge", amount="100") e.maturity=None; e.posted = "1/1/2002" self.assertRaises(AssertionError, e.percentVested, Date("1/1/2002")) e.posted = None; e.maturity = "1/1/2002" self.assertRaises(AssertionError, e.percentVested, Date("1/1/2002")) e.posted = "1/2/2002"; e.maturity = "1/1/2002" self.assertRaises(AssertionError, e.percentVested, Date("1/1/2002"))
def test_openBalance(self): assert Statement(self.acc).openBal == 0 assert Statement(self.acc).closeBal == 0 self.acc.events << duckbill.newEvent( "charge", amount=5, posted=DateTime("now")) assert Statement(self.acc).openBal == 0, \ "Expected opening balance of 0, got %s" % Statement(self.acc).openBal assert Statement(self.acc, start=Date("today"), end=Date("today")+1).closeBal == 5, \ "Expected closing balance of 5, got %s" \ % Statement(self.acc, end=Date("today")+1).closeBal
def check_today(self): """ temporarily set time.time() to return 10/19/2001 and test for today. """ import time _time = time.time time.time = lambda: 1003539807.89 try: assert Date("today") == Date("10/19/2001"), "wrong date" finally: time.time = _time
def test_viewNode_comment(self): n = Node(name="abc") n.comments << Comment(content="a", posted=Date("today") - 1) n.comments << Comment(content="c", posted=Date("today") - 3) n.comments << Comment(content="b", posted=Date("today") - 2) self.c.store(n) self.p.input["ID"] = n.ID m = self.p.viewNode() self.assertEquals(3, len(m["comments"])) # and we want newest first: self.assertEquals(["a", "b", "c"], [c["content"] for c in m["comments"]])
def test_calcNextDue(self): """ By default, calcNextDue should return one month after nextDue. """ cyc = duckbill.Cyclic() cyc.nextDue = "1/1/2001" assert cyc.calcNextDue() == Date("2/1/2001"), \ "expected 2/1/01, got: " + repr(cyc.calcNextDue()) cyc.nextDue = "1/31/2001" assert cyc.calcNextDue() == Date("3/3/2001"), \ "expected 3/3/01, got: " + repr(cyc.calcNextDue())
def test_whenLastStatementPosted(self): """ This should find the statement with the latest 'posted' time. """ acc = fakeAccount() acc.opened = Date("1/1/2001") assert acc.whenLastStatementPosted() == Date("1/1/2001") assert len(acc.events) == 0 acc.events << duckbill.newEvent("statement", posted="2/1/2001") acc.events << duckbill.newEvent("statement", posted="5/1/2001") acc.events << duckbill.newEvent("statement", posted="3/1/2001") acc.events << duckbill.newEvent("statement", posted="1/1/2001") assert acc.whenLastStatementPosted() == Date("5/1/2001")
def showchart(REQ, RES, CLERK): ## find the user's bandwidth plan assert REQ.has_key("username") username = REQ["username"] uobj = CLERK.fetch(User, username=username) limit = uobj.bandquota assert limit!=0, "bandquota should never be zero" # divide by 0 = bad ## create a list of the past 30 days last30days = [] day = Date("today") for i in range(30): day -= 1 last30days.append(day.toSQL()) last30days.reverse() ## fetch last 30 entries ## (hopefully last 30 days, but some days could be missing) cur = CLERK.storage.dbc.cursor() #@TODO: fix this!! cur.execute( """ SELECT t.whichday, t.traffic FROM sys_user u, sys_user_traffic t WHERE u.ID=t.userID AND u.username='******' ORDER BY t.whichday DESC LIMIT 30 """ % username) hash = {} for (rawdate, traffic) in cur.fetchall(): date = Date(rawdate).toSQL() hash[date] = traffic ## now get our actual 30 days, possibly with gaps: data = [] for day in last30days: if hash.has_key(day): data.append(hash[day]) else: data.append(None) # show the image: RES.contentType="image/png" chart = pid.PILCanvas(size=(250,100)) LimitChart(250,100, limit, data).draw(chart) chart.save(file=RES, format="png")
def testVesting(self): "ensure charges are slowly 'earned' over time" e = Event(event="charge", amount="50", posted="1/1/2000", maturity="11/1/2000") assert e.percentVested(e.posted) == 0 assert e.percentVested(e.posted - 1) == 0 assert e.percentVested(e.maturity) == 100 assert e.percentVested(e.maturity + 1) == 100 #@TODO: paramaterize number of decimal places ? # In a single month, we're fully vested at the end of the month. # With a yearly charge, the vesting happens slowly over time. d = Decimal self.assertEquals(e.percentVested(Date("2/1/2000")), d('10.16')) self.assertEquals(e.percentVested(Date("5/1/2000")), d('39.67')) self.assertEquals(e.percentVested(Date("7/1/2000")), d('59.67')) self.assertEquals(e.percentVested(Date("10/1/2000")), d('89.84')) self.assertEquals(e.percentVested(Date("10/31/2000")), d('99.67')) self.assertEquals(e.percentVested(Date("11/1/2000")), d('100.00')) self.assertEquals(e.percentVested(Date("12/1/2000")), d('100.00')) # valueOn should multiply by amount e.amount = 200 self.assertEquals(e.valueOn(Date("2/1/2000")), d('20.32'))
def testReportWithTime(self): today = Date("today") yesterday = today - 1 tomorrow = today + 1 nextday = today + 2 events = [ chg(5, yesterday), pay(10, today), chg(3, tomorrow, tomorrow), chg(5, nextday, nextday) ] self.assertEquals(unearned(events, yesterday), 0) self.assertEquals(unearned(events, today), 5) self.assertEquals(unearned(events, tomorrow), 2) self.assertEquals(unearned(events, nextday), 0) c, p = chg(100, today, today + 30), pay(100, today) for x in range(-50, 0): day = today + x goal = 0 actual = unearned([c, p], day) assert actual == goal, "%s vs %s on day %s" % (actual, goal, x) for x in range(0, 50): day = today + x goal = c.amount - c.valueOn(day) actual = unearned([c, p], day) assert actual == goal, "%s vs %s on day %s" % (actual, goal, x)
def test_amountPastDue(self): acc = self.oldAccount() assert acc.amountPastDue(Date("today")) == 25 assert acc.amountPastDue("today") == 25 assert acc.amountPastDue("4/1/2002") == 15 assert acc.amountPastDue("1/15/2002") == 10 assert acc.amountPastDue("1/1/2002") == 0
def showchart(REQ, RES, CLERK): ## find the user's bandwidth plan assert REQ.has_key("username") username = REQ["username"] uobj = CLERK.fetch(User, username=username) limit = uobj.bandquota assert limit != 0, "bandquota should never be zero" # divide by 0 = bad ## create a list of the past 30 days last30days = [] day = Date("today") for i in range(30): day -= 1 last30days.append(day.toSQL()) last30days.reverse() ## fetch last 30 entries ## (hopefully last 30 days, but some days could be missing) cur = CLERK.storage.dbc.cursor() #@TODO: fix this!! cur.execute(""" SELECT t.whichday, t.traffic FROM sys_user u, sys_user_traffic t WHERE u.ID=t.userID AND u.username='******' ORDER BY t.whichday DESC LIMIT 30 """ % username) hash = {} for (rawdate, traffic) in cur.fetchall(): date = Date(rawdate).toSQL() hash[date] = traffic ## now get our actual 30 days, possibly with gaps: data = [] for day in last30days: if hash.has_key(day): data.append(hash[day]) else: data.append(None) # show the image: RES.contentType = "image/png" chart = pid.PILCanvas(size=(250, 100)) LimitChart(250, 100, limit, data).draw(chart) chart.save(file=RES, format="png")
def parseQif(infile): """ Parse a qif file and return a list of entries. infile should be open file-like object (supporting readline() ). """ inItem = False items = [] curItem = QifItem() line = infile.readline() while line != '': if line[0] == '\n': # blank line pass elif line[0] == '^': # end of item # save the item items.append(curItem) curItem = QifItem() elif line[0] == 'D': curItem.date = fmtDate( Date(line[1:-1].replace("' ", "/200").replace(" ", ""))) elif line[0] == 'T': curItem.amount = line[1:-1] elif line[0] == "U": pass # same as T elif line[0] == "N": curItem.num = line[1:-1] elif line[0] == 'C': curItem.cleared = line[1:-1] elif line[0] == 'P': curItem.payee = line[1:-1] elif line[0] == 'M': curItem.memo = line[1:-1] elif line[0] == 'A': curItem.address = line[1:-1] elif line[0] == 'L': curItem.category = line[1:-1] elif line[0] == 'S': try: curItem.categoryInSplit.append(";" + line[1:-1]) except AttributeError: curItem.categoryInSplit = line[1:-1] elif line[0] == 'E': try: curItem.memoInSplit.append(";" + line[1:-1]) except AttributeError: curItem.memoInSplit = line[1:-1] elif line[0] == '$': try: curItem.amountInSplit.append(";" + line[1:-1]) except AttributeError: curItem.amountInSplit = line[1:-1] else: # don't recognise this line; ignore it print >> sys.stderr, "Skipping unknown line:\n", line line = infile.readline() return items
def amountPastDue(self, dueDate="today"): """ Given a dueDate, return the amount past due. This may be less than the total balance. """ d = dueDate if not isinstance(d, Date): d = Date(dueDate) res = 0 for e in self.aging(): if e.posted < dueDate: res += e.value return res
def test_eventsBetween(self): acc = fakeAccount() then = Date("1/1/2001") now = Date("5/1/2001") # not really :) # start with nothing. assert len(acc.eventsBetween(then, now)) == 0 # include the front date: acc.events << duckbill.newEvent("payment", posted="1/1/2001") assert len(acc.eventsBetween(then, now)) == 1, "start" # exclude the end date: acc.events << duckbill.newEvent("note", posted="5/1/2001") assert len(acc.eventsBetween(then, now)) == 1, "end" # include the middle: acc.events << duckbill.newEvent("charge", posted="2/1/2001") acc.events << duckbill.newEvent("credit", posted="4/1/2001") l = len(acc.eventsBetween(then, now)) assert l == 3, \ "middle should have 3 events, actually has %d" % l
def toDuckbill(self): """ Returns a duckbill Account with the appropriate Subscriptions and Events. """ a = Account(fname=self.fname, lname=self.lname, email=self.email, company=self.company, phone=self.phone, address1=self.addr1, address2=self.addr2, city=self.city, state=self.state, postal=self.postal, countryCD=self.country, account=self.username, nextDue=Date("today") + 30, brand=self.brand) s = Subscription( username=self.username, service=self.plan, rate=self.calcRate(), cycLen=self.cycLen, # thirty day free trial nextDue=Date("today") + 30) e = Event(event="note", posted=Date("today"), amount=0, note="30 day free trial") a.subscriptions << s a.events << e return a
def testCycle(self): cyc = duckbill.Cyclic() assert cyc.cycLen == 'month', "should be month by default" assert repr(cyc.get_cycle()) == 'MonthlyCycle()' cyc.cycLen = "year" assert cyc.cycLen == 'year', "should be year now" assert repr(cyc.get_cycle()) == 'YearlyCycle()' cyc.nextDue = ("1/1/2001") assert cyc.get_cycle().nextDue() == Date("1/1/2002") # just to make sure: sub = duckbill.Subscription() sub.cycLen = "year" assert repr(sub.cycle) == 'YearlyCycle()'
def test_monthlyTraffic(self): arbdate = Date("2/29/2004") startdate = Date("1/29/2004") user = User() # this one is out of range: user.usage << Usage(whichday=startdate - 1, traffic=1 * GIGA) # this one is cool: user.usage << Usage(whichday=startdate, traffic=2 * GIGA) # these are good: user.usage << Usage(whichday=arbdate - 3, traffic=4 * GIGA) user.usage << Usage(whichday=arbdate - 2, traffic=8 * GIGA) user.usage << Usage(whichday=arbdate - 1, traffic=16 * GIGA) # this one is in the "future", since it # won't be recorded until "tonight": user.usage << Usage(whichday=arbdate, traffic=32 * GIGA) self.assertEquals(self.bwg.monthlyTraffic(user, arbdate), (2 + 4 + 8 + 16) * GIGA)
def makeStatement(acc): """ generate a statement for the person.... """ total = 0 stmt="" name = (acc.fname + " " + acc.lname).strip() stmt += "\n" stmt += "Account: " + acc.account + "\n" stmt += "-" * 70 + "\n" es = acc.events es.sort() for e in es: if e.posted > Date("today"): continue # allow future posting of charges (eg, lifetime account) stmt += "%s %-50s%8s\n" % (e.posted.toDate().toUS(), e.note, e.value) total += e.value stmt += ("-" * 70) + "\n" stmt += "%70s\n" % ("total: " +str(total)) return (acc.fname, name, acc.email, stmt, total)
def separate_ages(self, events, binsize=15, maxbins=4): """ separates an account's aged receivables into bins """ # generate maxbins bins, oldest first: bins = [] for x in range(0, maxbins): bins.append(Date("today") - (binsize * x)) bins.reverse() # separate values according to dates # (note that this ignores anything posted in the future) # (but then there should never be anything in the future) vals = [0] * len(bins) for evt in events: for i in range(len(bins)): islastbin = (i == len(bins) - 1) if (evt.posted <= bins[i]) or (islastbin): vals[i] += evt.value break return vals
def test_encryption(self): c = EncryptedCard(owner='fred tempy', expire='1/1/2000', number='1234') assert not c.isEncrypted() assert c.owner == 'fred tempy' assert str(c).startswith("-----BEGIN PGP MESSAGE-----"), str(c) assert not c.isEncrypted() s = str(c) del c c = EncryptedCard(s) assert c.isEncrypted() # new instance should fail since it's encrypted: self.assertRaises(AttributeError, getattr, c, "owner") # witthout the right passphrase, it should still fail self.assertRaises(Exception, c.decrypt, "not-the-pass-phrase") self.assertRaises(AttributeError, getattr, c, "owner") # but if we have the passphrase, all is well: c.decrypt("encrypted card test") assert not c.isEncrypted() assert c.owner == 'fred tempy' assert c.number == '1234' assert c.expire == Date("1/1/2000") def gpgsame(a, b): A = a.split("==")[0] B = b.split("==")[0] #@TODO: I'd like this to be the same all the time, #but it isn't... need to nail down when exactly the #cipher should be regenerated... #import pdb; pdb.set_trace() #assert gpgsame(str(c), s), str(c) + "\n##VS##\n" + s c.owner = 'wanda tempy' assert not gpgsame(str(c), s) # just to be safe, prevent decrypting twice: self.assertRaises(Exception, c.decrypt, "encrypted card test")
def check_usage(self): period = PythonicRange(Date("today")-10, Date("today")) usr = User() # zero by default: assert usr.trafficUsed(period) == 0 # sum the data: usr.usage << Usage(whichday=Date("today")-1, traffic=1*GIGA) usr.usage << Usage(whichday=Date("today")-2, traffic=1*GIGA) assert usr.trafficUsed(period) == 2 * GIGA # ignore data out of range (note: it's a PythonicRange) usr.usage << Usage(whichday=Date("today"), traffic=1*GIGA) usr.usage << Usage(whichday=Date("today")-32, traffic=1*GIGA) assert usr.trafficUsed(period) == 2 * GIGA
dbc = clerk.storage.dbc from cornerhost import User from cornerhost import config uclerk = config.makeClerk() #@TODO: get rid of this completely (use duckbill.Grace instead) # (this is just a hard-coded list) try: from graced import GRACED, gracedUntil except Exception, e: GRACED = [] gracedUntil = [] for who, until in gracedUntil: if Date("today") <= until: GRACED.append(who) try: PASTDUE_MAIL_TEMPLATE = open("pastdue.msg").read() except: print "create a file called pastdue.msg that has your email template in it." sys.exit() def makemail(account, email, fname, lname, total, pastdue, brand): cap = brand.title() return PASTDUE_MAIL_TEMPLATE % locals() if __name__ == "__main__":
def daysInLastMonth(): return daysInMonthPriorTo(Date("today"))
def unearned(events, cutoff=Date("today")): a = Account() for e in events: a.events << e return a.unearnedIncome(cutoff)
#!/usr/bin/python2.5 from duckbill import * import duckbill.config from pytypes import Date import sys CLERK = duckbill.config.makeClerk() try: cutoff = Date(sys.argv[1]) except: print "usage: unearned.py DATE|today" sys.exit() # cache the data: subs = CLERK.match(Subscription) evts = CLERK.match(Event) print "cached." sum = 0 for a in CLERK.match(Account): sum += a.unearnedIncome(cutoff) print "unearned income on %s was %s" % (cutoff.toUS(), sum)
def check_daysInYear(self): assert Date("1/1/1999").daysInYear() == 365 assert Date("1/1/2000").daysInYear() == 366 # leap year assert Date("1/1/2001").daysInYear() == 365
def check_daysInMonth(self): assert Date("1/1/2001").daysInMonth() == 31 assert Date("2/1/2001").daysInMonth() == 28 assert Date("2/1/2000").daysInMonth() == 29
def check_repr(self): d = Date('1/1/2001') assert repr(d) == "Date('01/01/2001')", "wrong __repr__"