diff --git a/accountant/api/views/accounts.py b/accountant/api/views/accounts.py
index da53f6a..21df50f 100644
--- a/accountant/api/views/accounts.py
+++ b/accountant/api/views/accounts.py
@@ -15,36 +15,22 @@
along with Accountant. If not, see .
"""
from flask import json, request
+from flask.ext.restful import Resource, fields, reqparse, marshal_with_field
+
+from sqlalchemy.orm.exc import NoResultFound
from forms.accounts import AccountForm
-from accountant import session_scope
+from accountant import session_scope, session_aware
from . import auth
-from .. import api
+from .. import api, api_api
from ..models.accounts import Account
from ..models.entries import Entry
from ..models.operations import Operation
-
-@api.route("/accounts", methods=["GET"])
-@auth.login_required
-def get_accounts():
- """
- Returns accounts with their solds.
- """
- with session_scope() as session:
- query = Account.get_accounts(session)
-
- return json.dumps([{
- "id": i.id,
- "name": i.name,
- "authorized_overdraft": i.authorized_overdraft,
- "current": str(i.current),
- "pointed": str(i.pointed),
- "future": str(i.future)
- } for i in query.all()])
+from ..fields import Object
@api.route("/accounts////")
@@ -82,50 +68,114 @@ def get_months(account_id):
} for i in query.all()])
-@api.route("/accounts", methods=["PUT"])
-@auth.login_required
-def add_account():
- with session_scope() as session:
- account = Account(request.json['name'],
- request.json['authorized_overdraft'])
+resource_fields = {
+ 'id': fields.Integer,
+ 'name': fields.String,
+ 'authorized_overdraft': fields.Fixed(decimals=2),
+ 'current': fields.Float,
+ 'pointed': fields.Float,
+ 'future': fields.Float,
+}
+
+
+parser = reqparse.RequestParser()
+parser.add_argument('name', type=str, required=True)
+parser.add_argument('authorized_overdraft', type=float, required=True)
+
+
+class AccountListResource(Resource):
+ @session_aware
+ @marshal_with_field(fields.List(Object(resource_fields)))
+ def get(self, session=None):
+ """
+ Returns accounts with their balances.
+ """
+ return Account.get_accounts(session).all(), 200
+
+ def put(self):
+ """
+ Batch update, not implemented.
+ """
+ raise NotImplementedError()
+
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def post(self, session=None):
+ """
+ Create a new account.
+ """
+ kwargs = parser.parse_args()
+
+ account = Account(**kwargs)
session.add(account)
- return json.dumps("Account added.")
+ return account, 201
+
+ def delete(self):
+ """
+ Batch delete, not implemented.
+ """
+ raise NotImplementedError()
-@api.route("/accounts/", methods=["PUT"])
-@auth.login_required
-def update_account(account_id):
- account_form = AccountForm()
+class AccountResource(Resource):
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def get(self, account_id, session=None):
+ """
+ Get account.
+ """
+ try:
+ return Account.get(session, account_id)
+ except NoResultFound:
+ return None, 404
- if account_form.validate():
- with session_scope() as session:
- account = session.query(Account).get(account_id)
-
- account.name = request.json['name']
- account.authorized_overdraft = request.json['authorized_overdraft']
-
- session.merge(account)
-
- return json.dumps("Account #%s updated." % account_id)
- else:
- return json.dumps({
- 'ok': False,
- 'error_type': 'validation',
- 'errors': account_form.errorsi
- })
-
-
-@api.route("/accounts/", methods=["DELETE"])
-@auth.login_required
-def delete_account(account_id):
- with session_scope() as session:
- query = session.query(Account)
- query = query.filter(Account.id == account_id)
-
- account = query.first()
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def delete(self, account_id, session=None):
+ try:
+ account = Account.get(session, account_id)
+ except NoResultFound:
+ return None, 404
session.delete(account)
- return json.dumps("Account #%s deleted." % account_id)
+ return account
+
+ def patch(self, id):
+ pass
+
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def put(self, account_id, session=None):
+ kwargs = parser.parse_args()
+
+ assert (id not in kwargs or kwargs.id is None
+ or kwargs.id == account_id)
+
+ account_form = AccountForm()
+
+ if account_form.validate():
+ try:
+ account = Account.get(session, account_id)
+ except NoResultFound:
+ return None, 404
+
+ # SQLAlchemy objects ignore __dict__.update() with merge.
+ for k, v in kwargs.items():
+ setattr(account, k, v)
+
+ session.merge(account)
+
+ return account
+ else:
+ return json.dumps({
+ 'ok': False,
+ 'error_type': 'validation',
+ 'errors': account_form.errors
+ })
+
+
+api_api.add_resource(AccountListResource, '/accounts')
+api_api.add_resource(AccountResource, '/accounts/')
diff --git a/accountant/api/views/entries.py b/accountant/api/views/entries.py
index f70a7da..a8162be 100644
--- a/accountant/api/views/entries.py
+++ b/accountant/api/views/entries.py
@@ -14,15 +14,22 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see .
"""
-from flask import json, request
+import dateutil.parser
-from accountant import session_scope
+from flask import json
+from flask.ext.restful import Resource, fields, reqparse, marshal_with_field
-from .. import api
+from sqlalchemy.orm.exc import NoResultFound
+
+from accountant import session_scope, session_aware
+
+from .. import api, api_api
from ..models.entries import Entry
from ..models.operations import Operation
+from ..fields import Object
+
@api.route("/entries///")
def get_entries(account_id, year, month):
@@ -47,48 +54,90 @@ def get_entries(account_id, year, month):
} for i in query.all()])
-@api.route("/entries", methods=["PUT"])
-def add_entry():
- with session_scope() as session:
- entry = Entry(
- operation_date=request.json['operation_date'],
- pointed=request.json['pointed'],
- label=request.json['label'],
- value=request.json['value'],
- category=request.json['category'],
- account_id=request.json['account_id'],
- scheduled_operation_id=request.json['scheduled_operation_id']
- )
+resource_fields = {
+ # 'id': fields.Integer,
+ 'operation_date': fields.DateTime(dt_format='iso8601'),
+ 'label': fields.String,
+ 'value': fields.Fixed(decimals=2),
+ 'pointed': fields.Boolean,
+ 'category': fields.String,
+ 'account_id': fields.Integer,
+ 'scheduled_operation_id': fields.Integer,
+}
+
+parser = reqparse.RequestParser()
+# Must use lambda because the parser passes other parameters badly interpreted
+# by dateutil.parser.parse
+parser.add_argument('operation_date', type=lambda a: dateutil.parser.parse(a))
+parser.add_argument('label', type=str)
+parser.add_argument('value', type=float)
+parser.add_argument('pointed', type=bool)
+parser.add_argument('category', type=str)
+parser.add_argument('account_id', type=int)
+parser.add_argument('scheduled_operation_id', type=int)
+
+
+class EntryListResource(Resource):
+ def put(self, *args):
+ return self.post()
+
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def post(self, session=None):
+ kwargs = parser.parse_args()
+
+ entry = Entry(**kwargs)
session.add(entry)
- return json.dumps("Entry added.")
+ return entry
-@api.route("/entries/", methods=["PUT"])
-def update_entry(entry_id):
- with session_scope() as session:
- entry = session.query(Entry).get(entry_id)
+class EntryResource(Resource):
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def get(self, entry_id, session=None):
+ """
+ Get entry.
+ """
+ try:
+ return Entry.get(session, entry_id)
+ except NoResultFound:
+ return None, 404
- entry.id = entry_id
- entry.operation_date = request.json['operation_date']
- entry.pointed = request.json['pointed']
- entry.label = request.json['label']
- entry.value = request.json['value']
- entry.category = request.json['category']
- entry.account_id = request.json['account_id']
- entry.scheduled_operation_id = request.json['scheduled_operation_id']
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def put(self, entry_id, session=None):
+ kwargs = parser.parse_args()
+
+ assert (id not in kwargs or kwargs.id is None
+ or kwargs.id == entry_id)
+
+ try:
+ entry = Entry.get(session, entry_id)
+ except NoResultFound:
+ return None, 404
+
+ # SQLAlchemy objects ignore __dict__.update() with merge.
+ for k, v in kwargs.items():
+ setattr(entry, k, v)
session.merge(entry)
- return json.dumps("Entry #%s updated." % entry_id)
+ return entry
-
-@api.route("/entries/", methods=["DELETE"])
-def delete_entry(entry_id):
- with session_scope() as session:
- entry = session.query(Entry).filter(Entry.id == entry_id).first()
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def delete(self, entry_id, session=None):
+ try:
+ entry = Entry.get(session, entry_id)
+ except NoResultFound:
+ return None, 404
session.delete(entry)
- return json.dumps("Entry #%s deleted." % entry_id)
+ return entry
+
+
+api_api.add_resource(EntryListResource, "/entries")
+api_api.add_resource(EntryResource, "/entries/")
diff --git a/accountant/api/views/scheduled_operations.py b/accountant/api/views/scheduled_operations.py
index 4538648..efc0654 100644
--- a/accountant/api/views/scheduled_operations.py
+++ b/accountant/api/views/scheduled_operations.py
@@ -14,16 +14,23 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see .
"""
-from flask import json, request
+import dateutil.parser
-from accountant import session_scope
+from flask import json, request
+from flask.ext.restful import Resource, fields, reqparse, marshal_with_field
+
+from sqlalchemy.orm.exc import NoResultFound
+
+from accountant import session_scope, session_aware
from ..models.scheduled_operations import ScheduledOperation
-from .. import api
+from .. import api, api_api
+
+from ..fields import Object
-@api.route("/scheduled_operations/")
+@api.route("/scheduled_operations/")
def get_scheduled_operations(account_id):
"""
Return entries for an account, year, and month.
@@ -45,57 +52,94 @@ def get_scheduled_operations(account_id):
} for i in query.all()])
-@api.route("/scheduled_operations", methods=["PUT"])
-def add_scheduled_operation():
- with session_scope() as session:
- scheduledOperation = ScheduledOperation(
- start_date=request.json['start_date'],
- stop_date=request.json['stop_date'],
- day=request.json['day'],
- frequency=request.json['frequency'],
- label=request.json['label'],
- value=request.json['value'],
- category=request.json['category'],
- account_id=request.json['account_id']
- )
+resource_fields = {
+ 'id': fields.Integer,
+ 'start_date': fields.DateTime(dt_format='iso8601'),
+ 'stop_date': fields.DateTime(dt_format='iso8601'),
+ 'day': fields.Integer,
+ 'frequency': fields.Integer,
+ 'label': fields.String,
+ 'value': fields.Fixed(decimals=2),
+ 'category': fields.String,
+ 'account_id': fields.Integer,
+}
+
+
+parser = reqparse.RequestParser()
+parser.add_argument('start_date', type=lambda a: dateutil.parser.parse(a))
+parser.add_argument('stop_date', type=lambda a: dateutil.parser.parse(a))
+parser.add_argument('day', type=int)
+parser.add_argument('frequency', type=int)
+parser.add_argument('label', type=str)
+parser.add_argument('value', type=float)
+parser.add_argument('category', type=str)
+parser.add_argument('account_id', type=int)
+
+
+class ScheduledOperationListResource(Resource):
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def put(self, session=None):
+ """
+ Add a new scheduled operation.
+ """
+ kwargs = parser.parse_args()
+
+ scheduledOperation = ScheduledOperation(**kwargs)
session.add(scheduledOperation)
- return json.dumps("Scheduled operation added.")
+ return scheduledOperation, 201
-@api.route("/scheduled_operations/", methods=["PUT"])
-def update_scheduled_operation(scheduled_operation_id):
- with session_scope() as session:
- query = session.query(ScheduledOperation)
- query = query.filter(ScheduledOperation.id == scheduled_operation_id)
+class ScheduledOperationResource(Resource):
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def get(self, scheduled_operation_id, session=None):
+ """
+ Get scheduled operation.
+ """
+ try:
+ return ScheduledOperation.get(session, scheduled_operation_id)
+ except NoResultFound:
+ return None, 404
- scheduledOperation = query.first()
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def delete(self, scheduled_operation_id, session=None):
+ try:
+ scheduled_operation = ScheduledOperation.get(
+ session, scheduled_operation_id)
+ except NoResultFound:
+ return None, 404
- scheduledOperation.id = scheduled_operation_id
- scheduledOperation.start_date = request.json['start_date'],
- scheduledOperation.stop_date = request.json['stop_date'],
- scheduledOperation.day = request.json['day'],
- scheduledOperation.frequency = request.json['frequency'],
- scheduledOperation.label = request.json['label']
- scheduledOperation.value = request.json['value']
- scheduledOperation.category = request.json['category']
- scheduledOperation.account_id = request.json['account_id']
+ session.delete(scheduled_operation)
- session.merge(scheduledOperation)
+ return scheduled_operation
- return json.dumps(
- "Scheduled operation #%s updated." % scheduled_operation_id)
+ @session_aware
+ @marshal_with_field(Object(resource_fields))
+ def put(self, scheduled_operation_id, session=None):
+ kwargs = parser.parse_args()
+
+ assert (id not in kwargs or kwargs.id is None
+ or kwargs.id == scheduled_operation_id)
+
+ try:
+ scheduled_operation = ScheduledOperation.get(
+ session, scheduled_operation_id)
+ except NoResultFound:
+ return None, 404
+
+ # SQLAlchemy objects ignore __dict__.update() with merge.
+ for k, v in kwargs.items():
+ setattr(scheduled_operation, k, v)
+
+ session.merge(scheduled_operation)
+
+ return scheduled_operation
-@api.route("/scheduled_operations/", methods=["DELETE"])
-def delete_scheduled_operation(scheduled_operation_id):
- with session_scope() as session:
- query = session.query(ScheduledOperation)
- query = query.filter(ScheduledOperation.id == scheduled_operation_id)
- scheduledOperation = query.first()
-
- session.delete(scheduledOperation)
-
- return json.dumps(
- "Scheduled operation #%s deleted." % scheduled_operation_id)
+api_api.add_resource(ScheduledOperationListResource, "/scheduled_operations")
+api_api.add_resource(ScheduledOperationResource,
+ "/scheduled_operations/")