Merge lp:~samd/wxbanker/per-account-currencies into lp:wxbanker
- per-account-currencies
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
Accounts with different currencies
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Rooney | Needs Information | ||
Review via email: mp+108499@code.launchpad.net |
Commit message
Description of the change
I'm going to propose a merge for easier discussion and change review!
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.
209 + totalCurrency = self.Store.
210 + total = 0
211 + for account in self:
212 + total = total + account.
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(
* 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.
Michael Rooney (mrooney) wrote : | # |
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://
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/
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.
> 209 + totalCurrency = self.Store.
> 210 + total = 0
> 211 + for account in self:
> 212 + total = total +
> account.
> 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(
> property converted to the ...
Preview Diff
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) |
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. 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.
* 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://