Merge lp:~samd/wxbanker/per-account-currencies into lp:wxbanker

Proposed by Michael Rooney
Status: Merged
Merged at revision: 869
Proposed branch: lp:~samd/wxbanker/per-account-currencies
Merge into: lp:wxbanker
Diff against target: 850 lines (+260/-56)
13 files modified
wxbanker/accountconfigdialog.py (+45/-0)
wxbanker/accountlistctrl.py (+17/-2)
wxbanker/bankobjects/account.py (+13/-3)
wxbanker/bankobjects/accountlist.py (+8/-2)
wxbanker/bankobjects/bankmodel.py (+23/-14)
wxbanker/bankobjects/transaction.py (+9/-1)
wxbanker/controller.py (+18/-0)
wxbanker/currencies.py (+4/-1)
wxbanker/data/exchanges.xml (+3/-3)
wxbanker/menubar.py (+19/-3)
wxbanker/persistentstore.py (+18/-6)
wxbanker/plots/cairopanel.py (+28/-3)
wxbanker/transactionolv.py (+55/-18)
To merge this branch: bzr merge lp:~samd/wxbanker/per-account-currencies
Reviewer Review Type Date Requested Status
Michael Rooney Needs Information
Review via email: mp+108499@code.launchpad.net

Description of the change

I'm going to propose a merge for easier discussion and change review!

To post a comment you must log in.
Revision history for this message
Michael Rooney (mrooney) wrote :

Thanks for this awesome code, overall it looks pretty great! A few comments:

* it would be great to remove usages of "from wxbanker.currconvert import *", for an easier to follow namespace. If you don't want to have to type the full path each time, you could use "from wxbanker import currconvert" and then use it that way.
* is it no longer using Account.Balance, but the GetCurrentBalance function? I'm just wondering if this defeats the point of caching this value and now has to add up all transactions for all accounts to display the totals. If so, maybe it could just convert the cached .Balance attribute, but if I'm misunderstanding, let me know.
* it would be great to add a few tests (there are bunch located at wxbanker/tests/ including UI tests)! I think I did a bad job at documenting how to run them so I'll attempt to get them running with nose and a simple "setup.py test" or something.
* regarding your exchanges.xml question, the idea was the ship each version with an up-to-date (at the time) version for offline use, but on startup grab the latest data if online (from http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml) and write it back to exchanges.xml. I can help implement this feature if you'd like.

review: Needs Information
849. By Sam Dieck

Reduced import scopes for easier to follow namespace.

Revision history for this message
Sam Dieck (samd) wrote :

* Namespaces are now corrected and not using * when importing.

* About the use of the .Balance attribute, i think that the code in question is the following:

            def GetBalance(self):
    208 - return sum([account.Balance for account in self])
    209 + totalCurrency = self.Store.getGlobalCurrency()
    210 + total = 0
    211 + for account in self:
    212 + total = total + account.GetCurrentBalance(totalCurrency)
    213 + return total

  Here are my thoughts on this code, and hopefully you could clarify me on this. From what I see in the function "GetCurrentBalance" the attribute ".Balance" contains even future transactions, and that's the difference between using "GetCurrentBalance and the attribute "Balance" (actually, even GetCurrentBalance() makes use of .Balance) ; do we want to include future transactions on total balances? even if those transactions have not been made? If that's the case, its just matter of creating a new function ".GetBalance(currency)" in the account class which returns the ".Balance" property converted to the desired currency without substracting future transactions (as GetCurrentBalance() does).

* I would be more than happy to run/add more tests, is there anything i need to know before going into that?

* Alright, i got ya on the exchanges.xml, ill start implementing it and tell you if i run into any problems and need help. Dont you think that it'll be better to add a "currency rates settings" where you can see current rates, click on an "update" button to update current rates, and possibly add an option for "check for currency updates automatically on startup" as i don't think that blindly pulling things from the internet without even a notification to the user is a good idea.

Revision history for this message
Michael Rooney (mrooney) wrote :
Download full text (4.0 KiB)

Thanks again Sam for all this awesome code, sorry for the delay! I've
merged this and made my first pass of changes at
http://bazaar.launchpad.net/~wxbanker-devs/wxbanker/trunk/revision/870.
Mostly minor changes, except that I made "Show currency names" non-default,
otherwise that would be a big change for everyone upgrading and it wouldn't
be useful for most people using only one currency.

As you mentioned, one of your changes does change the balances to be as of
today and not total including future as they were, and since that's kind of
an unrelated change (but something I do want to improve), I'll keep it
showing the grand total for now.

A few questions for you:

   1. Now the Currency menu drop-down just changes the global currency,
   where as before it changed them all. I'm wondering if this behavior will be
   immediately understandable, or if we should prompt when you change it
   asking if you want to change the global currency, the currency for all
   accounts (since that is what it used to do and changing them all
   individually would be a pain), or the currency for the current account.
   2. What do you think should happen for new accounts? Should they be the
   localized currency by default, or the global currency?
   3. If you are able to add some tests, it would be really helpful to make
   sure all these cases going forward. Particularly, tests that handle the
   rendering with nicknames, and making sure the total balance is handled
   correctly, and such. You can run them via "python -m
   wxbanker/tests/alltests" (I think a few translation tests will fail without
   certain language packs, you can ignore those or I can let you know which
   packs are required).

Thanks again. I'm going to do a performance review (there does seem to be a
big performance regression in startup time so I need to figure that one
out), and make a few tweaks, and figure out the best way to update the
exchanges.xml, then we can release 0.9!

On Thu, Jun 7, 2012 at 12:06 AM, Sam Dieck <email address hidden> wrote:

> * Namespaces are now corrected and not using * when importing.
>
> * About the use of the .Balance attribute, i think that the code in
> question is the following:
>
> def GetBalance(self):
> 208 - return sum([account.Balance for account in self])
> 209 + totalCurrency = self.Store.getGlobalCurrency()
> 210 + total = 0
> 211 + for account in self:
> 212 + total = total +
> account.GetCurrentBalance(totalCurrency)
> 213 + return total
>
> Here are my thoughts on this code, and hopefully you could clarify me on
> this. From what I see in the function "GetCurrentBalance" the attribute
> ".Balance" contains even future transactions, and that's the difference
> between using "GetCurrentBalance and the attribute "Balance" (actually,
> even GetCurrentBalance() makes use of .Balance) ; do we want to include
> future transactions on total balances? even if those transactions have not
> been made? If that's the case, its just matter of creating a new function
> ".GetBalance(currency)" in the account class which returns the ".Balance"
> property converted to the ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'wxbanker/accountconfigdialog.py'
2--- wxbanker/accountconfigdialog.py 2010-08-09 00:31:45 +0000
3+++ wxbanker/accountconfigdialog.py 2012-06-07 03:50:23 +0000
4@@ -19,6 +19,7 @@
5 import wx
6 from wx.lib.pubsub import Publisher
7 from wxbanker.transactionctrl import TransactionCtrl
8+from wxbanker.currencies import CurrencyStrings, GetCurrencyInt
9
10 from wxbanker.mint.api import Mint
11 try:
12@@ -238,7 +239,49 @@
13 def onTransactionChoice(self, event):
14 transaction = self.GetCurrentRecurringTransaction()
15 self.transactionCtrl.FromRecurring(transaction)
16+
17+class CurrencyConfigPanel(wx.Panel):
18+ def __init__(self, parent, account):
19+ self.Account = account
20+ wx.Panel.__init__(self, parent)
21+ self.headerText = wx.StaticText(self, -1, _("Account currency: "))
22+
23+ saveButton = wx.Button(self, label=_("Save"), id=wx.ID_SAVE)
24+ closeButton = wx.Button(self, label=_("Cancel"), id=wx.ID_CLOSE)
25+
26+ buttonSizer = wx.BoxSizer()
27+ buttonSizer.Add(saveButton)
28+ buttonSizer.AddSpacer(12)
29+ buttonSizer.Add(closeButton)
30+ buttonSizer.AddSpacer(6)
31+
32+ #base currency = global currency
33+ currencies = ["Base currency"] + CurrencyStrings
34+ self.currencyCombo = wx.Choice(self, choices=currencies)
35+ # we have to add 1 to the current indext because we added the "base currency" entry
36+ self.currencyCombo.SetSelection(GetCurrencyInt(self.Account.GetCurrency())+1)
37
38+ self.Sizer = wx.BoxSizer(wx.VERTICAL)
39+ self.Sizer.AddSpacer(6)
40+ self.Sizer.Add(self.headerText, 0, wx.LEFT, 6)
41+ self.Sizer.AddSpacer(6)
42+ self.Sizer.Add(self.currencyCombo, 0, wx.LEFT, 6)
43+ self.Sizer.AddStretchSpacer(1)
44+ self.Sizer.Add(buttonSizer, flag=wx.ALIGN_RIGHT)
45+ self.Sizer.AddSpacer(6)
46+ self.Bind(wx.EVT_BUTTON, self.onButton)
47+
48+ def onButton(self, event):
49+ """If the save button was clicked save, and close the dialog in any case (Close/Cancel/Save)."""
50+ assert event.Id in (wx.ID_CLOSE, wx.ID_SAVE)
51+
52+ if event.Id == wx.ID_SAVE:
53+ #we have to substract 1 from combo_box selection because we added the "base currency" entry
54+ selectedCurrency = self.currencyCombo.GetSelection() - 1
55+ Publisher.sendMessage("user.account_currency_changed", (self.Account, selectedCurrency))
56+
57+ self.GrandParent.Destroy()
58+
59 class AccountConfigDialog(wx.Dialog):
60 def __init__(self, parent, account, tab="default"):
61 wx.Dialog.__init__(self, parent, title=account.Name, size=(600, 400))
62@@ -248,8 +291,10 @@
63
64 self.recurringPanel = RecurringConfigPanel(self.notebook, account)
65 self.mintPanel = MintConfigPanel(self.notebook, account)
66+ self.currencyPanel = CurrencyConfigPanel(self.notebook, account)
67 self.notebook.AddPage(self.recurringPanel, _("Recurring Transactions"))
68 self.notebook.AddPage(self.mintPanel, _("Mint.com Integration"))
69+ self.notebook.AddPage(self.currencyPanel, _("Currency Settings"))
70
71 if tab == "mint":
72 # Setting the selection synchronously gets changed back somewhere in the event queue.
73
74=== modified file 'wxbanker/accountlistctrl.py'
75--- wxbanker/accountlistctrl.py 2010-08-19 04:30:23 +0000
76+++ wxbanker/accountlistctrl.py 2012-06-07 03:50:23 +0000
77@@ -116,6 +116,7 @@
78 Publisher.subscribe(self.onAccountAdded, "account.created")
79 Publisher.subscribe(self.onCurrencyChanged, "currency_changed")
80 Publisher.subscribe(self.onShowZeroToggled, "controller.showzero_toggled")
81+ Publisher.subscribe(self.onShowCurrencyNickToggled, "controller.show_currency_nick_toggled")
82 Publisher.subscribe(self.onAccountChanged, "user.account changed")
83 Publisher.subscribe(self.onSelectNextAccount, "user.next account")
84 Publisher.subscribe(self.onSelectPreviousAccount, "user.previous account")
85@@ -165,7 +166,7 @@
86 mintCtrl.SetBitmap(wx.ArtProvider.GetBitmap("wxART_%s" % bitmapName))
87 mintCtrl.SetToolTipString(tooltip)
88
89- def onCurrencyChanged(self, message):
90+ def refreshBalances(self):
91 # Update all the accounts.
92 for account, textCtrl in zip(self.accountObjects, self.totalTexts):
93 textCtrl.Label = account.float2str(account.Balance)
94@@ -173,6 +174,15 @@
95 self.updateGrandTotal()
96 self.Parent.Layout()
97
98+ def onCurrencyChanged(self, message):
99+ self.refreshBalances()
100+
101+ def onShowCurrencyNickToggled(self, message):
102+ val = message.data
103+ for account in self.Model.Accounts:
104+ account.ShowCurrencyNick = val
105+ self.refreshBalances()
106+
107 def onToggleMintIntegration(self, message):
108 enabled = message.data
109 if enabled:
110@@ -326,6 +336,10 @@
111 break
112 index += 1
113
114+ # Check if we should set account to show currency NIcks,
115+ # wouldn't it be better to have per-account show-currency-nick setting?
116+ account.ShowCurrencyNick = self.bankController.ShowCurrencyNick
117+
118 self._InsertItem(index, account)
119
120 if select:
121@@ -427,7 +441,8 @@
122 self._UpdateMintStatuses()
123
124 def updateGrandTotal(self):
125- self.totalText.Label = self.Model.float2str( self.Model.Balance )
126+ shownick = self.bankController.ShowCurrencyNick
127+ self.totalText.Label = self.Model.float2str( self.Model.Balance, withNick=shownick )
128
129 def onAddButton(self, event):
130 self.showEditCtrl()
131
132=== modified file 'wxbanker/bankobjects/account.py'
133--- wxbanker/bankobjects/account.py 2010-10-05 21:34:34 +0000
134+++ wxbanker/bankobjects/account.py 2012-06-07 03:50:23 +0000
135@@ -26,13 +26,16 @@
136 from wxbanker import currencies, bankexceptions, debug
137 from wxbanker.mint.api import Mint
138
139+from wxbanker.currencies import CurrencyList
140+from wxbanker.currconvert import CurrencyConverter
141+
142 import datetime
143
144 class Account(ORMObject):
145 ORM_TABLE = "accounts"
146 ORM_ATTRIBUTES = ["Name", "Balance", "MintId"]
147
148- def __init__(self, store, aID, name, currency=0, balance=0.0, mintId=None):
149+ def __init__(self, store, aID, name, currency=0, balance=0.0, mintId=None, currNick=False):
150 ORMObject.__init__(self)
151 self.IsFrozen = True
152 self.Store = store
153@@ -45,6 +48,7 @@
154 self.Currency = currency or 0
155 self.Balance = balance or 0.0
156 self.MintId = mintId
157+ self.ShowCurrencyNick = currNick or False
158 self.IsFrozen = False
159
160 Publisher.subscribe(self.onTransactionAmountChanged, "ormobject.updated.Transaction.Amount")
161@@ -86,7 +90,7 @@
162 def GetCurrency(self):
163 return self._Currency
164
165- def GetCurrentBalance(self):
166+ def GetCurrentBalance(self, currency=None):
167 """Returns the balance up to and including today, but not transactions in the future."""
168 currentBalance = self.Balance
169 today = datetime.date.today()
170@@ -96,6 +100,12 @@
171 while index >= 0 and transactions[index].Date > today:
172 currentBalance -= transactions[index].Amount
173 index -= 1
174+
175+ if currency:
176+ conv = CurrencyConverter()
177+ destCurrency = CurrencyList[currency]().GetCurrencyNick()
178+ srcCurrency = self.GetCurrency().GetCurrencyNick()
179+ return conv.Convert(currentBalance, srcCurrency, destCurrency)
180 return currentBalance
181
182 def GetRecurringTransactions(self):
183@@ -329,7 +339,7 @@
184 debug.debug("Ignoring transaction because I am %s: %s" % (self.Name, transaction))
185
186 def float2str(self, *args, **kwargs):
187- return self.Currency.float2str(*args, **kwargs)
188+ return self.Currency.float2str(withNick=self.ShowCurrencyNick, *args, **kwargs)
189
190 def __cmp__(self, other):
191 return cmp(self.Name, other.Name)
192
193=== modified file 'wxbanker/bankobjects/accountlist.py'
194--- wxbanker/bankobjects/accountlist.py 2010-08-09 00:31:45 +0000
195+++ wxbanker/bankobjects/accountlist.py 2012-06-07 03:50:23 +0000
196@@ -21,6 +21,7 @@
197 from wx.lib.pubsub import Publisher
198 from wxbanker import bankexceptions
199
200+
201 class AccountList(list):
202 def __init__(self, store):
203 list.__init__(self, store.GetAccounts())
204@@ -43,7 +44,11 @@
205 return allRecurrings
206
207 def GetBalance(self):
208- return sum([account.Balance for account in self])
209+ totalCurrency = self.Store.getGlobalCurrency()
210+ total = 0
211+ for account in self:
212+ total = total + account.GetCurrentBalance(totalCurrency)
213+ return total
214
215 def GetById(self, theId):
216 for account in self:
217@@ -103,4 +108,5 @@
218 def onAccountRenamed(self, message):
219 self.sort()
220
221- Balance = property(GetBalance)
222\ No newline at end of file
223+ Balance = property(GetBalance)
224+
225
226=== modified file 'wxbanker/bankobjects/bankmodel.py'
227--- wxbanker/bankobjects/bankmodel.py 2010-08-09 00:31:45 +0000
228+++ wxbanker/bankobjects/bankmodel.py 2012-06-07 03:50:23 +0000
229@@ -28,6 +28,8 @@
230 from wxbanker.bankobjects.accountlist import AccountList
231 from wxbanker.mint.api import Mint
232
233+from wxbanker.currencies import GetCurrencyInt
234+
235 class BankModel(ORMKeyValueObject):
236 ORM_TABLE = "meta"
237 ORM_ATTRIBUTES = ["LastAccountId", "MintEnabled"]
238@@ -42,7 +44,8 @@
239 if self.MintEnabled:
240 delayedresult.startWorker(lambda result: Publisher.sendMessage("mint.updated"), Mint.LoginFromKeyring, wkwargs={"notify": False})
241
242- Publisher.subscribe(self.onCurrencyChanged, "user.currency_changed")
243+ Publisher.subscribe(self.onGlobalCurrencyChanged, "user.global_currency_changed")
244+ Publisher.subscribe(self.onAccountCurrencyChanged, "user.account_currency_changed")
245 Publisher.subscribe(self.onMintToggled, "user.mint.toggled")
246 Publisher.subscribe(self.onAccountChanged, "view.account changed")
247 Publisher.subscribe(self.onTransactionTagged, "transaction.tagged")
248@@ -84,8 +87,10 @@
249 """
250 if account is None:
251 transactions = self.GetTransactions()
252+ currency = self.GlobalCurrency
253 else:
254 transactions = account.Transactions[:]
255+ currency = GetCurrencyInt(account.GetCurrency())
256 transactions.sort()
257
258 if transactions == []:
259@@ -104,7 +109,7 @@
260 if t.Date > endDate:
261 endi = i
262 break
263- total += t.Amount
264+ total += t.GetAmount(currency)
265
266 transactions = transactions[starti:endi]
267 else:
268@@ -126,7 +131,7 @@
269 balance = startingBalance
270 while currDate <= endDate:
271 while tindex < len(transactions) and transactions[tindex].Date <= currDate:
272- balance += transactions[tindex].Amount
273+ balance += transactions[tindex].GetAmount(currency)
274 tindex += 1
275 totals.append([currDate, balance])
276 currDate += onedaydelta
277@@ -167,23 +172,27 @@
278 Handle representing floats as strings for non
279 account-specific amounts, such as totals.
280 """
281- if len(self.Accounts) == 0:
282- currency = currencies.CurrencyList[0]()
283- else:
284- currency = self.Accounts[0].Currency
285-
286+ currencyID = self.Store.getGlobalCurrency()
287+ currency = currencies.CurrencyList[currencyID]()
288 return currency.float2str(*args, **kwargs)
289
290- def setCurrency(self, currencyIndex):
291+ def setGlobalCurrency(self, currencyIndex):
292 self.Store.setCurrency(currencyIndex)
293- for account in self.Accounts:
294- account.Currency = currencyIndex
295 Publisher.sendMessage("currency_changed", currencyIndex)
296
297- def onCurrencyChanged(self, message):
298+ def setAccountCurrency(self, account, currencyIndex):
299+ self.Store.setCurrency(currencyIndex, account)
300+ account.SetCurrency(currencyIndex)
301+ Publisher.sendMessage("currency_changed", currencyIndex)
302+
303+ def onGlobalCurrencyChanged(self, message):
304 currencyIndex = message.data
305- self.setCurrency(currencyIndex)
306-
307+ self.setGlobalCurrency(currencyIndex)
308+
309+ def onAccountCurrencyChanged(self, message):
310+ account, currency = message.data
311+ self.setAccountCurrency(account, currency)
312+
313 def onAccountChanged(self, message):
314 account = message.data
315 if account:
316
317=== modified file 'wxbanker/bankobjects/transaction.py'
318--- wxbanker/bankobjects/transaction.py 2010-08-10 04:47:02 +0000
319+++ wxbanker/bankobjects/transaction.py 2012-06-07 03:50:23 +0000
320@@ -26,6 +26,9 @@
321 from wxbanker.bankobjects.tag import Tag, EmptyTagException
322 from wxbanker import debug
323
324+from wxbanker.currencies import CurrencyList
325+from wxbanker.currconvert import CurrencyConverter
326+
327 class Transaction(ORMObject):
328 """
329 An object which represents a transaction.
330@@ -152,7 +155,12 @@
331 def SetTags(self, tagList):
332 self._Tags = tagList
333
334- def GetAmount(self):
335+ def GetAmount(self, currency=None):
336+ if currency:
337+ conv = CurrencyConverter()
338+ destCurrency = CurrencyList[currency]().GetCurrencyNick()
339+ srcCurrency = self.Parent.GetCurrency().GetCurrencyNick()
340+ return conv.Convert(self._Amount, srcCurrency, destCurrency)
341 return self._Amount
342
343 def SetAmount(self, amount, fromLink=False):
344
345=== modified file 'wxbanker/controller.py'
346--- wxbanker/controller.py 2010-10-09 22:39:26 +0000
347+++ wxbanker/controller.py 2012-06-07 03:50:23 +0000
348@@ -29,6 +29,7 @@
349 def __init__(self, path=None):
350 self._AutoSave = True
351 self._ShowZeroBalanceAccounts = True
352+ self._ShowCurrencyNick = True
353 self.Models = []
354
355 self.InitConfig()
356@@ -36,6 +37,7 @@
357
358 Publisher.subscribe(self.onAutoSaveToggled, "user.autosave_toggled")
359 Publisher.subscribe(self.onShowZeroToggled, "user.showzero_toggled")
360+ Publisher.subscribe(self.onShowCurrencyNickToggled, "user.show_currency_nick_toggled")
361 Publisher.subscribe(self.onSaveRequest, "user.saved")
362
363 def MigrateIfFound(self, fromPath, toPath):
364@@ -82,10 +84,13 @@
365 config.WriteBool("AUTO-SAVE", True)
366 if not config.HasEntry("HIDE_ZERO_BALANCE_ACCOUNTS"):
367 config.WriteBool("HIDE_ZERO_BALANCE_ACCOUNTS", False)
368+ if not config.HasEntry("SHOW_CURRENCY_NICK"):
369+ config.WriteBool("SHOW_CURRENCY_NICK", True)
370
371 # Set the auto-save option as appropriate.
372 self.AutoSave = config.ReadBool("AUTO-SAVE")
373 self.ShowZeroBalanceAccounts = not config.ReadBool("HIDE_ZERO_BALANCE_ACCOUNTS")
374+ self.ShowCurrencyNick = config.ReadBool("SHOW_CURRENCY_NICK")
375
376 def onAutoSaveToggled(self, message):
377 val = message.data
378@@ -95,6 +100,10 @@
379 val = message.data
380 self.ShowZeroBalanceAccounts = val
381
382+ def onShowCurrencyNickToggled(self, message):
383+ val = message.data
384+ self.ShowCurrencyNick = val
385+
386 def onSaveRequest(self, message):
387 self.Model.Save()
388
389@@ -121,6 +130,14 @@
390 wx.Config.Get().WriteBool("HIDE_ZERO_BALANCE_ACCOUNTS", not val)
391 Publisher.sendMessage("controller.showzero_toggled", val)
392
393+ def GetShowCurrencyNick(self):
394+ return self._ShowCurrencyNick
395+
396+ def SetShowCurrencyNick(self, val):
397+ self._ShowCurrencyNick = val
398+ wx.Config.Get().WriteBool("SHOW_CURRENCY_NICK", val)
399+ Publisher.sendMessage("controller.show_currency_nick_toggled", val)
400+
401 def LoadPath(self, path, use=False):
402 if path is None:
403 path = fileservice.getDataFilePath(self.DB_NAME)
404@@ -153,3 +170,4 @@
405
406 AutoSave = property(GetAutoSave, SetAutoSave)
407 ShowZeroBalanceAccounts = property(GetShowZero, SetShowZero)
408+ ShowCurrencyNick = property(GetShowCurrencyNick, SetShowCurrencyNick)
409
410=== modified file 'wxbanker/currencies.py'
411--- wxbanker/currencies.py 2010-08-09 00:31:45 +0000
412+++ wxbanker/currencies.py 2012-06-07 03:50:23 +0000
413@@ -69,7 +69,7 @@
414 def GetCurrencyNick(self):
415 return self.LOCALECONV["int_curr_symbol"].strip()
416
417- def float2str(self, val, just=0):
418+ def float2str(self, val, just=0, withNick=True):
419 """Formats float values as currency strings according to the currency settings in self.LOCALECONV"""
420 # Don't show negative zeroes!
421 if abs(val) < .001:
422@@ -89,6 +89,9 @@
423 if not isinstance(s, unicode):
424 s = unicode(s, locale.getlocale()[1])
425
426+ if withNick:
427+ s = self.GetCurrencyNick() + " " + s
428+
429 # Justify as appropriate.
430 s = s.rjust(just)
431
432
433=== modified file 'wxbanker/data/exchanges.xml'
434--- wxbanker/data/exchanges.xml 2009-12-06 23:24:24 +0000
435+++ wxbanker/data/exchanges.xml 2012-06-07 03:50:23 +0000
436@@ -6,7 +6,7 @@
437 </gesmes:Sender>
438 <Cube>
439 <Cube time='2009-02-25'>
440- <Cube currency='USD' rate='1.2795'/>
441+ <Cube currency='USD' rate='1.2489'/>
442 <Cube currency='JPY' rate='123.76'/>
443 <Cube currency='BGN' rate='1.9558'/>
444 <Cube currency='CZK' rate='28.350'/>
445@@ -32,7 +32,7 @@
446 <Cube currency='IDR' rate='15385.99'/>
447 <Cube currency='INR' rate='63.7700'/>
448 <Cube currency='KRW' rate='1938.03'/>
449- <Cube currency='MXN' rate='18.9687'/>
450+ <Cube currency='MXN' rate='17.3842235'/>
451 <Cube currency='MYR' rate='4.6951'/>
452 <Cube currency='NZD' rate='2.4847'/>
453 <Cube currency='PHP' rate='61.610'/>
454@@ -41,4 +41,4 @@
455 <Cube currency='ZAR' rate='12.7223'/>
456 </Cube>
457 </Cube>
458-</gesmes:Envelope>
459\ No newline at end of file
460+</gesmes:Envelope>
461
462=== modified file 'wxbanker/menubar.py'
463--- wxbanker/menubar.py 2010-08-19 04:27:51 +0000
464+++ wxbanker/menubar.py 2012-06-07 03:50:23 +0000
465@@ -39,6 +39,7 @@
466 ID_REQUESTFEATURE = wx.NewId()
467 ID_TRANSLATE = wx.NewId()
468 IDS_CURRENCIES = [wx.NewId() for i in range(len(CurrencyStrings))]
469+ ID_SHOWCURRENCYNICK = wx.NewId()
470 ID_MINTINTEGRATION = wx.NewId()
471 ID_REQUESTCURRENCY = wx.NewId()
472 ID_IMPORT_CSV = wx.NewId()
473@@ -49,6 +50,7 @@
474 self.bankController = bankController
475 autosave = bankController.AutoSave
476 showZero = bankController.ShowZeroBalanceAccounts
477+ showcurrnick = bankController.ShowCurrencyNick
478 self.currencyStrings = CurrencyStrings[:]
479
480 # File menu.
481@@ -76,7 +78,7 @@
482 settingsMenu = wx.Menu()
483
484 ## TRANSLATORS: Put the ampersand (&) before the letter to use as the Alt shortcut.
485- currencyMenu = wx.MenuItem(settingsMenu, -1, _("&Currency"), _("Select currency to display"))
486+ currencyMenu = wx.MenuItem(settingsMenu, -1, _("Base &Currency"), _("Select currency for the 'All accounts balance'"))
487 currencyMenu.SetBitmap(wx.ArtProvider.GetBitmap("wxART_money"))
488
489 # Add an entry for each available currency.
490@@ -97,6 +99,8 @@
491
492 settingsMenu.AppendItem(currencyMenu)
493
494+ self.showCurrencyNickItem = settingsMenu.AppendCheckItem(self.ID_SHOWCURRENCYNICK, _("Show currencies nick"), _("Show currencies nick before quantities"))
495+
496 self.mintEnabledItem = settingsMenu.AppendCheckItem(self.ID_MINTINTEGRATION, _("Integrate with Mint.com"), _("Sync account balances with an existing Mint.com account"))
497
498 # Help menu.
499@@ -142,8 +146,10 @@
500 helpMenu.Bind(wx.EVT_MENU, self.onClickAbout)
501
502 self.toggleAutoSave(autosave)
503+ self.toggleShowCurrencyNick(showcurrnick)
504 Publisher.subscribe(self.onAutoSaveToggled, "controller.autosave_toggled")
505 Publisher.subscribe(self.onShowZeroToggled, "controller.showzero_toggled")
506+ Publisher.subscribe(self.onShowCurrencyNickToggled, "controller.show_currency_nick_toggled")
507 # Subscribe to a Mint update event, which tells us the checkbox should be enabled after startup.
508 Publisher.subscribe(self.onMintUpdate, "mint.updated")
509
510@@ -170,6 +176,7 @@
511 self.ID_EXPORT_CSV: self.onClickExportCsv,
512 wx.ID_ABOUT: self.onClickAbout,
513 self.ID_REQUESTCURRENCY: self.onClickRequestCurrency,
514+ self.ID_SHOWCURRENCYNICK: self.onClickShowCurrencyNick,
515 self.ID_MINTINTEGRATION: self.onClickMintIntegration,
516 }.get(ID, lambda e: e.Skip())
517
518@@ -190,6 +197,12 @@
519
520 def toggleShowZero(self, showzero):
521 self.showZeroMenuItem.Check(showzero)
522+
523+ def onShowCurrencyNickToggled(self, message):
524+ self.toggleShowCurrencyNick(message.data)
525+
526+ def toggleShowCurrencyNick(self, showcurr):
527+ self.showCurrencyNickItem.Check(showcurr)
528
529 def toggleMintEnabled(self, enabled):
530 self.mintEnabledItem.Check(enabled)
531@@ -216,7 +229,7 @@
532 Publisher.sendMessage("quit")
533
534 def onSelectCurrency(self, currencyIndex):
535- Publisher.sendMessage("user.currency_changed", currencyIndex)
536+ Publisher.sendMessage("user.global_currency_changed", currencyIndex)
537
538 def onClickFAQs(self, event):
539 webbrowser.open("https://answers.launchpad.net/wxbanker/+faqs")
540@@ -236,6 +249,9 @@
541 def onClickRequestCurrency(self, event):
542 webbrowser.open("https://answers.launchpad.net/wxbanker/+faq/477")
543
544+ def onClickShowCurrencyNick(self, event):
545+ Publisher.sendMessage("user.show_currency_nick_toggled", event.Checked())
546+
547 def onClickMintIntegration(self, event):
548 Publisher.sendMessage("user.mint.toggled", event.Checked())
549
550@@ -278,4 +294,4 @@
551 if result == wx.ID_OK:
552 csvpath = dlg.GetPath()
553 CsvExporter.Export(self.bankController.Model, csvpath)
554-
555\ No newline at end of file
556+
557
558=== modified file 'wxbanker/persistentstore.py'
559--- wxbanker/persistentstore.py 2010-08-09 00:31:45 +0000
560+++ wxbanker/persistentstore.py 2012-06-07 03:50:23 +0000
561@@ -53,7 +53,7 @@
562 """
563 def __init__(self, path, autoSave=True):
564 self.Subscriptions = []
565- self.Version = 11
566+ self.Version = 12
567 self.Path = path
568 self.AutoSave = False
569 self.Dirty = False
570@@ -209,6 +209,10 @@
571 cursor.execute('CREATE TABLE meta (id INTEGER PRIMARY KEY, name VARCHAR(255), value VARCHAR(255))')
572 cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('VERSION', '2'))
573
574+ def getGlobalCurrency(self):
575+ results = self.dbconn.cursor().execute('SELECT value FROM meta WHERE name="GlobalCurrency"').fetchall()
576+ return int(results[0][0])
577+
578 def getMeta(self):
579 try:
580 results = self.dbconn.cursor().execute('SELECT * FROM meta').fetchall()
581@@ -288,6 +292,9 @@
582 # This is tested by testOrphanedTransactionsAreDeleted (dbupgradetests.DBUpgradeTest)
583 # Also takes care of LP #249954 without the explicit need to defensively remove on any account creation.
584 self.cleanOrphanedTransactions()
585+ elif fromVer == 11:
586+ # globalCurrency entry
587+ cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('GlobalCurrency', 0))
588 else:
589 raise Exception("Cannot upgrade database from version %i"%fromVer)
590
591@@ -442,11 +449,16 @@
592 def renameAccount(self, oldName, account):
593 self.dbconn.cursor().execute("UPDATE accounts SET name=? WHERE name=?", (account.Name, oldName))
594 self.commitIfAppropriate()
595-
596- def setCurrency(self, currencyIndex):
597- self.dbconn.cursor().execute('UPDATE accounts SET currency=?', (currencyIndex,))
598- self.commitIfAppropriate()
599-
600+
601+ def setCurrency(self, currencyIndex, account=None):
602+ if account:
603+ self.dbconn.cursor().execute('UPDATE accounts SET currency=? WHERE id=?', (currencyIndex, account.ID))
604+ else:
605+ #Since no account received, we are updating the global currency
606+ self.dbconn.cursor().execute('UPDATE meta SET value=? WHERE name="GlobalCurrency"', (currencyIndex,))
607+ self.commitIfAppropriate()
608+
609+
610 def __print__(self):
611 cursor = self.dbconn.cursor()
612 for account in cursor.execute("SELECT * FROM accounts").fetchall():
613
614=== modified file 'wxbanker/plots/cairopanel.py'
615--- wxbanker/plots/cairopanel.py 2010-09-13 07:40:38 +0000
616+++ wxbanker/plots/cairopanel.py 2012-06-07 03:50:23 +0000
617@@ -1,6 +1,7 @@
618 import wx
619 import datetime
620 from wxbanker.plots import plotfactory
621+from wx.lib.pubsub import Publisher
622
623 try:
624 try:
625@@ -17,7 +18,7 @@
626 self.Plots = [CairoPlotPanel, CairoPlotPanelMonthly]
627
628 class BaseCairoPlotPanel(wx.Panel, baseplot.BasePlot):
629- def __init__(self, bankController, parent):
630+ def __init__(self, bankController, parent, plotSettings=None):
631 wx.Panel.__init__(self, parent)
632 baseplot.BasePlot.__init__(self)
633 self.bankController = bankController
634@@ -25,6 +26,14 @@
635 self.Bind(wx.EVT_SIZE, self.OnSize)
636 self.data = None
637 self.x_labels = None
638+ self.plotSettings = plotSettings
639+
640+ # watch if there's any currency change to repaint the plot.
641+ Publisher.subscribe(self.currencyChanged, "controller.show_currency_nick_toggled")
642+ Publisher.subscribe(self.currencyChanged, "currency_changed")
643+
644+ def currencyChanged(self, message):
645+ self.Refresh()
646
647 def OnSize(self, event):
648 self.Refresh()
649@@ -33,6 +42,7 @@
650 NAME = _("monthly")
651
652 def plotBalance(self, totals, plotSettings):
653+ self.plotSettings = plotSettings
654 model = self.bankController.Model
655 transactions = model.GetTransactions()
656 earnings = baseplot.BasePlot.plotMonthly(self, transactions, plotSettings['Months'])
657@@ -56,6 +66,13 @@
658
659 cr = wx.lib.wxcairo.ContextFromDC(dc)
660 size = self.GetClientSize()
661+
662+ # try to format Y axes labels according to the account's currency.
663+ if self.plotSettings['Account']:
664+ value_formatter = lambda s: self.plotSettings['Account'].float2str(s)
665+ else:
666+ value_formatter = lambda s: self.bankController.Model.float2str(s)
667+
668 cairoplot.vertical_bar_plot(
669 cr.get_target(),
670 data = self.data,
671@@ -65,7 +82,7 @@
672 colors = ["green"],
673 #series_legend = True,
674 display_values = True,
675- value_formatter = lambda s: self.bankController.Model.float2str(s),
676+ value_formatter = value_formatter,
677 #x_title=_("Earnings"),
678 #y_title=_("Month"),
679 rounded_corners = True,
680@@ -76,6 +93,7 @@
681 NAME = _("balance")
682
683 def plotBalance(self, totals, plotSettings, xunits="Days"):
684+ self.plotSettings = plotSettings
685 amounts, dates, strdates, trendable = baseplot.BasePlot.plotBalance(self, totals, plotSettings, xunits)
686 data = [(i, total) for i, total in enumerate(amounts)]
687 self.data = {
688@@ -107,6 +125,13 @@
689
690 cr = wx.lib.wxcairo.ContextFromDC(dc)
691 size = self.GetClientSize()
692+
693+ # try to format Y axes labels according to the account's currency.
694+ if self.plotSettings['Account']:
695+ y_formatter = lambda s: self.plotSettings['Account'].float2str(s)
696+ else:
697+ y_formatter = lambda s: self.bankController.Model.float2str(s)
698+
699 cairoplot.scatter_plot(
700 cr.get_target(),
701 data = self.data,
702@@ -118,7 +143,7 @@
703 series_colors = ["green", "blue"],
704 series_legend = True,
705 x_labels=self.x_labels,
706- y_formatter=lambda s: self.bankController.Model.float2str(s),
707+ y_formatter=y_formatter,
708 x_title=_("Time"),
709 y_title=_("Balance"),
710 )
711
712=== modified file 'wxbanker/transactionolv.py'
713--- wxbanker/transactionolv.py 2010-12-25 13:45:21 +0000
714+++ wxbanker/transactionolv.py 2012-06-07 03:50:23 +0000
715@@ -33,6 +33,7 @@
716 from wxbanker.ObjectListView import GroupListView, ColumnDefn, CellEditorRegistry
717 from wxbanker import bankcontrols, tagtransactiondialog
718
719+from wxbanker.currencies import GetCurrencyInt
720
721 class TransactionOLV(GroupListView):
722 EMPTY_MSG_NORMAL = _("No transactions entered.")
723@@ -43,6 +44,7 @@
724 self.LastSearch = None
725 self.CurrentAccount = None
726 self.BankController = bankController
727+ self.GlobalCurrency = self.BankController.Model.Store.getGlobalCurrency()
728
729 self.showGroups = False
730 #WXTODO: figure out these (and the text color, or is that already?) from theme (LP: ???)
731@@ -66,7 +68,7 @@
732 self.SetColumns([
733 ColumnDefn(_("Date"), valueGetter=self.getDateAndIDOf, valueSetter=self.setDateOf, stringConverter=self.renderDateIDTuple, editFormatter=self.renderEditDate, width=dateWidth),
734 ColumnDefn(_("Description"), valueGetter="Description", isSpaceFilling=True, editFormatter=self.renderEditDescription),
735- ColumnDefn(_("Amount"), "right", valueGetter="Amount", stringConverter=self.renderFloat, editFormatter=self.renderEditFloat),
736+ ColumnDefn(_("Amount"), "right", valueGetter=self.getAmount, valueSetter=self.setAmount, stringConverter=self.renderFloat, editFormatter=self.renderEditFloat),
737 ColumnDefn(_("Balance"), "right", valueGetter=self.getTotal, stringConverter=self.renderFloat, isEditable=False),
738 ])
739 # Our custom hack in OLV.py:2017 will render amount floats appropriately as %.2f when editing.
740@@ -84,13 +86,14 @@
741 (self.onTransactionAdded, "transaction.created"),
742 (self.onTransactionsRemoved, "transactions.removed"),
743 (self.onCurrencyChanged, "currency_changed"),
744+ (self.onShowCurrencyNickToggled, "controller.show_currency_nick_toggled"),
745 (self.updateTotals, "ormobject.updated.Transaction.Amount"),
746 (self.onTransactionDateUpdated, "ormobject.updated.Transaction.Date"),
747 )
748
749 for callback, topic in self.Subscriptions:
750 Publisher.subscribe(callback, topic)
751-
752+
753 def SetObjects(self, objs, *args, **kwargs):
754 """
755 Override the default SetObjects to properly refresh the auto-size,
756@@ -127,6 +130,12 @@
757 self.Freeze()
758 self.SortBy(self.SORT_COL)
759 self.Thaw()
760+
761+ def setAmount(self, transaction, amount):
762+ transaction.Amount = amount
763+ self.Freeze()
764+ self.SortBy(self.SORT_COL)
765+ self.Thaw()
766
767 def getTotal(self, transObj):
768 if not hasattr(transObj, "_Total"):
769@@ -139,23 +148,42 @@
770 if first is None:
771 return
772
773- first._Total = first.Amount
774+ if not self.CurrentAccount:
775+ #This means we are in 'All accounts' so we need to convert each total
776+ # to the global currency
777+ balance_currency = self.GlobalCurrency
778+ else:
779+ #we are just viewing a single account
780+ # balance currency = accounts currency
781+ balance_currency = GetCurrencyInt(self.CurrentAccount.GetCurrency())
782+
783+ first._Total = first.GetAmount(balance_currency)
784
785 b = first
786 for i in range(1, len(self.GetObjects())):
787 a, b = b, self.GetObjectAt(i)
788- b._Total = a._Total + b.Amount
789+ b._Total = a._Total + b.GetAmount(balance_currency)
790
791 def renderDateIDTuple(self, pair):
792 return str(pair[0])
793-
794- def renderFloat(self, floatVal):
795- if self.CurrentAccount:
796- return self.CurrentAccount.float2str(floatVal)
797+
798+ def getAmount(self, obj):
799+ #Return the whole transaction/float since we need to use its
800+ #renderAmount method to support multiple currencies.
801+ return obj
802+
803+ def renderFloat(self, value):
804+ if isinstance(value, float):
805+ #this is a 'balance' column, its ok to use the bank model's float2str
806+ # as long as we'r not in an account.
807+ if self.CurrentAccount:
808+ return self.CurrentAccount.float2str(value)
809+ else:
810+ return self.BankController.Model.float2str(value)
811 else:
812- #WXTODO: fix me, this function should be given the object which should have a float2str method
813- # so that for multiple currencies they can be displayed differently when viewing all.
814- return self.BankController.Model.float2str(floatVal)
815+ #this is a trnasaction, so it belogns to the 'Amount' column, render
816+ # it with its appropieate currency
817+ return value.RenderAmount()
818
819 def renderEditDate(self, transaction):
820 return str(transaction.Date)
821@@ -413,13 +441,22 @@
822 self.Refresh()
823
824 def onCurrencyChanged(self, message):
825- # Refresh all the transaction objects, re-rendering the amounts.
826- self.RefreshObjects()
827- # The current likely changed the widths of the amount/total column.
828- self.sizeAmounts()
829- # Now we need to adjust the description width so we don't have a horizontal scrollbar.
830- self.AutoSizeColumns()
831-
832+ self.GlobalCurrency = message.data
833+ # Refresh all the transaction objects, re-rendering the amounts.
834+ self.RefreshObjects()
835+ # The current likely changed the widths of the amount/total column.
836+ self.sizeAmounts()
837+ # Now we need to adjust the description width so we don't have a horizontal scrollbar.
838+ self.AutoSizeColumns()
839+
840+ def onShowCurrencyNickToggled(self, message):
841+ # Refresh all the transaction objects, re-rendering the amounts.
842+ self.RefreshObjects()
843+ # The current likely changed the widths of the amount/total column.
844+ self.sizeAmounts()
845+ # Now we need to adjust the description width so we don't have a horizontal scrollbar.
846+ self.AutoSizeColumns()
847+
848 def __del__(self):
849 for callback, topic in self.Subscriptions:
850 Publisher.unsubscribe(callback)

Subscribers

People subscribed via source and target branches

to all changes: