Changeset - f2b9decf2752
[Not reviewed]
0 6 1
Joar Wandborg - 10 years ago 2013-12-16 06:33:56
joar@wandborg.se
SQL, GTK

- Made the storage model slightly more flexible
- Made a small P-o-C GUI application in GTK
- Polished accounting.client
- models.Transaction.id is now a str
- Fixed transaction.id marshalling for storage.ledgercli
7 files changed with 180 insertions and 74 deletions:
0 comments (0 inline, 0 general)
accounting/client.py
Show inline comments
...
 
@@ -4,2 +4,3 @@ import json
 
import logging
 
import locale
 

	
...
 
@@ -13,2 +14,4 @@ from accounting.transport import AccountingDecoder, AccountingEncoder
 

	
 
locale.setlocale(locale.LC_ALL, '')
 

	
 
_log = logging.getLogger(__name__)
...
 
@@ -45,11 +48,16 @@ class Client:
 

	
 
    def simple_transaction(self, from_acc, to_acc, amount):
 
    def simple_transaction(self, from_acc, to_acc, amount, symbol=None,
 
                           payee=None):
 
        if symbol is None:
 
            # Get the currency from the environment locale
 
            symbol = locale.localeconv()['int_curr_symbol'].strip()
 

	
 
        t = Transaction(
 
            date=datetime.today(),
 
            payee='PayPal donation',
 
            payee=payee,
 
            postings=[
 
                Posting(account=from_acc,
 
                        amount=Amount(symbol='$', amount=-amount)),
 
                        amount=Amount(symbol=symbol, amount=-amount)),
 
                Posting(account=to_acc,
 
                        amount=Amount(symbol='$', amount=amount))
 
                        amount=Amount(symbol=symbol, amount=amount))
 
            ]
...
 
@@ -88,3 +96,3 @@ def print_balance_accounts(accounts, level=0):
 

	
 
        print_balance_accounts(account.accounts, level+1)
 
        print_balance_accounts(account.accounts, level + 1)
 

	
...
 
@@ -102,5 +110,12 @@ def main(argv=None, prog=None):
 
                                aliases=['in'])
 
    insert.add_argument('payee',
 
                        help='The payee line of the transaction')
 
    insert.add_argument('from_account')
 
    insert.add_argument('to_account')
 
    insert.add_argument('amount', type=Decimal)
 
    insert.add_argument('amount', type=Decimal,
 
                        help='The amount deducted from from_account and added'
 
                             ' to to_account')
 
    insert.add_argument('-s', '--symbol',
 
                        help='The symbol for the amount, e.g. $ or USD for'
 
                             ' USD. Defaults to your locale\'s setting.')
 

	
...
 
@@ -124,3 +139,4 @@ def main(argv=None, prog=None):
 
        print(client.simple_transaction(args.from_account, args.to_account,
 
                                        args.amount))
 
                                        args.amount, payee=args.payee,
 
                                        symbol=args.symbol))
 
    elif args.action in ['balance', 'bal']:
accounting/gtkclient.py
Show inline comments
 
new file 100644
 
import sys
 
import logging
 
import threading
 

	
 
from datetime import datetime
 

	
 
from gi.repository import Gtk
 
from gi.repository import GLib
 
from gi.repository import GObject
 

	
 
from accounting.client import Client
 

	
 
_log = logging.getLogger(__name__)
 

	
 

	
 
class Accounting(Gtk.Window):
 
    def __init__(self):
 
        Gtk.Window.__init__(self, title='Accounting Client')
 

	
 
        self.client = Client()
 

	
 
        self.set_border_width(3)
 

	
 
        # Table
 

	
 
        self.table = Gtk.Table(3, 2, True)
 
        self.add(self.table)
 

	
 
        # Button
 

	
 
        self.btn_load_transactions = Gtk.Button(label='Load transactions')
 
        self.btn_load_transactions.connect('clicked', self.on_button_clicked)
 

	
 
        self.spinner = Gtk.Spinner()
 

	
 
        # Transaction stuff
 

	
 
        self.transaction_store = Gtk.ListStore(str, str)
 
        self.transaction_view = Gtk.TreeView(self.transaction_store)
 

	
 
        renderer = Gtk.CellRendererText()
 
        date_column = Gtk.TreeViewColumn('Date', renderer, text=0)
 
        payee_column = Gtk.TreeViewColumn('Payee', renderer, text=1)
 

	
 
        self.transaction_view.append_column(date_column)
 
        self.transaction_view.append_column(payee_column)
 

	
 
        # Layout
 
        self.table.attach(self.btn_load_transactions, 0, 1, 0, 1)
 
        self.table.attach(self.spinner, 1, 2, 0, 1)
 
        self.table.attach(self.transaction_view, 0, 2, 1, 3)
 

	
 
        # Show
 
        self.show_all()
 
        self.spinner.hide()
 

	
 

	
 
    def on_button_clicked(self, widget):
 
        def load_transactions():
 
            transactions = self.client.get_register()
 
            GLib.idle_add(self.on_transactions_loaded, transactions)
 

	
 
        self.spinner.show()
 
        self.spinner.start()
 

	
 
        threading.Thread(target=load_transactions).start()
 

	
 
    def on_transactions_loaded(self, transactions):
 
        self.spinner.stop()
 
        self.spinner.hide()
 
        _log.debug('transactions: %s', transactions)
 

	
 
        self.transaction_store.clear()
 

	
 
        for transaction in transactions:
 
            self.transaction_store.append([
 
                transaction.date.strftime('%Y-%m-%d'),
 
                transaction.payee
 
            ])
 

	
 

	
 
def main(argv=None):
 
    logging.basicConfig(level=logging.DEBUG)
 

	
 
    GObject.threads_init()
 

	
 
    accounting_win = Accounting()
 
    accounting_win.connect('delete-event', Gtk.main_quit)
 

	
 
    Gtk.main()
 

	
 
if __name__ == '__main__':
 
    sys.exit(main())
accounting/models.py
Show inline comments
...
 
@@ -17,6 +17,6 @@ class Transaction:
 
    def generate_id(self):
 
        self.id = uuid.uuid4()
 
        self.id = str(uuid.uuid4())
 

	
 
    def __repr__(self):
 
        return ('<{self.__class__.__name__} {date}' +
 
        return ('<{self.__class__.__name__} {self.id} {date}' +
 
                ' {self.payee} {self.postings}').format(
accounting/storage/ledgercli.py
Show inline comments
...
 
@@ -16,3 +16,6 @@ _log = logging.getLogger(__name__)
 
class Ledger(Storage):
 
    def __init__(self, ledger_file=None, ledger_bin=None):
 
    def __init__(self, app=None, ledger_file=None, ledger_bin=None):
 
        if app:
 
            ledger_file = app.config['LEDGER_FILE']
 

	
 
        if ledger_file is None:
...
 
@@ -160,7 +163,10 @@ class Ledger(Storage):
 
        '''
 
        if not transaction.metadata.get('Id'):
 
        if transaction.id is None:
 
            _log.debug('No ID found. Generating an ID.')
 
            transaction.generate_id()
 

	
 
        transaction.metadata.update({'Id': transaction.id})
 

	
 
        transaction_template = ('\n{date} {t.payee}\n'
 
                                '{tags}'
 
                                '{metadata}'
 
                                '{postings}')
...
 
@@ -180,3 +186,3 @@ class Ledger(Storage):
 
            t=transaction,
 
            tags=''.join([
 
            metadata=''.join([
 
                metadata_template.format(k, v)
...
 
@@ -241,2 +247,5 @@ class Ledger(Storage):
 

	
 
    def get_transactions(self):
 
        return self.reg()
 

	
 
    def reg(self):
...
 
@@ -303,2 +312,5 @@ class Ledger(Storage):
 

	
 
    def update_transaction(self, transaction):
 
        _log.debug('DUMMY: Updated transaction: %s', transaction)
 

	
 

	
accounting/storage/sql/__init__.py
Show inline comments
...
 
@@ -5,2 +5,3 @@ from flask.ext.sqlalchemy import SQLAlchemy
 

	
 
from accounting.exceptions import AccountingException
 
from accounting.storage import Storage
...
 
@@ -13,4 +14,8 @@ db = None
 
class SQLStorage(Storage):
 
    def __init__(self, app):
 
    def __init__(self, app=None):
 
        global db
 

	
 
        if not app:
 
            raise Exception('Missing app keyword argument')
 

	
 
        self.app = app
...
 
@@ -49,2 +54,9 @@ class SQLStorage(Storage):
 

	
 
    def update_transaction(self, transaction):
 
        if transaction.id is None:
 
            raise AccountingException('The transaction id must be set for'
 
                                      ' update_transaction calls')
 

	
 
        _log.debug('DUMMY: Update transaction: %s', transaction)
 

	
 
    def add_transaction(self, transaction):
...
 
@@ -54,3 +66,3 @@ class SQLStorage(Storage):
 
        _t = self.Transaction()
 
        _t.uuid = str(transaction.id)
 
        _t.uuid = transaction.id
 
        _t.date = transaction.date
...
 
@@ -63,3 +75,3 @@ class SQLStorage(Storage):
 
            _p = self.Posting()
 
            _p.transaction_uuid = str(transaction.id)
 
            _p.transaction_uuid = transaction.id
 
            _p.account = posting.account
accounting/transport.py
Show inline comments
...
 
@@ -6,2 +6,3 @@ from accounting.models import Amount, Transaction, Posting, Account
 

	
 

	
 
class AccountingEncoder(json.JSONEncoder):
...
 
@@ -18,2 +19,3 @@ class AccountingEncoder(json.JSONEncoder):
 
                __type__=o.__class__.__name__,
 
                id=o.id,
 
                date=o.date.strftime('%Y-%m-%d'),
...
 
@@ -44,2 +46,3 @@ class AccountingEncoder(json.JSONEncoder):
 

	
 

	
 
class AccountingDecoder(json.JSONDecoder):
...
 
@@ -52,4 +55,4 @@ class AccountingDecoder(json.JSONDecoder):
 

	
 
        types = {c.__name__ : c for c in [Amount, Transaction, Posting,
 
                                          Account]}
 
        types = {c.__name__: c for c in [Amount, Transaction, Posting,
 
                                         Account]}
 

	
accounting/web.py
Show inline comments
...
 
@@ -23,10 +23,11 @@ app.config.from_pyfile('config.py')
 

	
 
storage = SQLStorage(app)
 
storage = Ledger(app=app)
 

	
 
# TODO: Move migration stuff into SQLStorage
 
db = storage.db
 
migrate = Migrate(app, db)
 
if isinstance(storage, SQLStorage):
 
    # TODO: Move migration stuff into SQLStorage
 
    db = storage.db
 
    migrate = Migrate(app, db)
 

	
 
manager = Manager(app)
 
manager.add_command('db', MigrateCommand)
 
    manager = Manager(app)
 
    manager.add_command('db', MigrateCommand)
 

	
...
 
@@ -61,2 +62,20 @@ def transaction_get():
 

	
 
@app.route('/transaction/<string:transaction_id>', methods=['POST'])
 
@jsonify_exceptions
 
def transaction_update(transaction_id=None):
 
    if transaction_id is None:
 
        raise AccountingException('The transaction ID cannot be None.')
 

	
 
    transaction = request.json['transaction']
 

	
 
    if transaction.id is not None and not transaction.id == transaction_id:
 
        raise AccountingException('The transaction data has an ID attribute and'
 
                                  ' it is not the same ID as in the path')
 
    elif transaction.id is None:
 
        transaction.id = transaction_id
 

	
 
    storage.update_transaction(transaction)
 

	
 
    return jsonify(status='OK')
 

	
 

	
...
 
@@ -120,52 +139,3 @@ def transaction_post():
 

	
 
    return jsonify(foo='bar')
 

	
 

	
 
@app.route('/parse-json', methods=['POST'])
 
def parse_json():
 
    r'''
 
    Parses a __type__-annotated JSON payload and debug-logs the decoded version
 
    of it.
 

	
 
    Example:
 

	
 
    .. code-block:: bash
 

	
 
        wget http://127.0.0.1:5000/balance -O balance.json
 
        curl -X POST -H 'Content-Type: application/json' -d @balance.json \
 
            http://127.0.0.1/parse-json
 
        # Logging output (linebreaks added for clarity)
 
        # DEBUG:accounting:json data: {'balance_report':
 
        #    [<Account "None" [
 
        #        <Amount $ 0>, <Amount KARMA 0>]
 
        #        [<Account "Assets" [
 
        #            <Amount $ 50>, <Amount KARMA 10>]
 
        #            [<Account "Assets:Checking" [
 
        #                <Amount $ 50>] []>,
 
        #             <Account "Assets:Karma Account" [
 
        #                <Amount KARMA 10>] []>]>,
 
        #         <Account "Expenses" [
 
        #            <Amount $ 500>]
 
        #            [<Account "Expenses:Blah" [
 
        #                <Amount $ 250>]
 
        #                [<Account "Expenses:Blah:Hosting" [
 
        #                    <Amount $ 250>] []>]>,
 
        #             <Account "Expenses:Foo" [
 
        #                <Amount $ 250>] [
 
        #                <Account "Expenses:Foo:Hosting" [
 
        #                    <Amount $ 250>] []>]>]>,
 
        #         <Account "Income" [
 
        #            <Amount $ -550>,
 
        #            <Amount KARMA -10>]
 
        #            [<Account "Income:Donation" [
 
        #                <Amount $ -50>] []>,
 
        #             <Account "Income:Foo" [
 
        #                <Amount $ -500>]
 
        #                [<Account "Income:Foo:Donation" [
 
        #                    <Amount $ -500>] []>]>,
 
        #             <Account "Income:Karma" [
 
        #             <Amount KARMA -10>] []>]>]>]}
 
    '''
 
    app.logger.debug('json data: %s', request.json)
 
    return jsonify(foo='bar')
 
    return jsonify(status='OK')
 

	
0 comments (0 inline, 0 general)