Rewrote views with Restful resources.

This commit is contained in:
Alexis Lahouze 2015-06-11 23:32:55 +02:00
parent c62941a704
commit be1bba3bc8
3 changed files with 283 additions and 140 deletions

View File

@ -15,36 +15,22 @@
along with Accountant. If not, see <http://www.gnu.org/licenses/>. along with Accountant. If not, see <http://www.gnu.org/licenses/>.
""" """
from flask import json, request 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 forms.accounts import AccountForm
from accountant import session_scope from accountant import session_scope, session_aware
from . import auth from . import auth
from .. import api from .. import api, api_api
from ..models.accounts import Account from ..models.accounts import Account
from ..models.entries import Entry from ..models.entries import Entry
from ..models.operations import Operation from ..models.operations import Operation
from ..fields import Object
@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()])
@api.route("/accounts/<account_id>/<year>/<month>/") @api.route("/accounts/<account_id>/<year>/<month>/")
@ -82,50 +68,114 @@ def get_months(account_id):
} for i in query.all()]) } for i in query.all()])
@api.route("/accounts", methods=["PUT"]) resource_fields = {
@auth.login_required 'id': fields.Integer,
def add_account(): 'name': fields.String,
with session_scope() as session: 'authorized_overdraft': fields.Fixed(decimals=2),
account = Account(request.json['name'], 'current': fields.Float,
request.json['authorized_overdraft']) '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) session.add(account)
return json.dumps("Account added.") return account, 201
def delete(self):
"""
Batch delete, not implemented.
"""
raise NotImplementedError()
@api.route("/accounts/<account_id>", methods=["PUT"]) class AccountResource(Resource):
@auth.login_required @session_aware
def update_account(account_id): @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
@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 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() account_form = AccountForm()
if account_form.validate(): if account_form.validate():
with session_scope() as session: try:
account = session.query(Account).get(account_id) account = Account.get(session, account_id)
except NoResultFound:
return None, 404
account.name = request.json['name'] # SQLAlchemy objects ignore __dict__.update() with merge.
account.authorized_overdraft = request.json['authorized_overdraft'] for k, v in kwargs.items():
setattr(account, k, v)
session.merge(account) session.merge(account)
return json.dumps("Account #%s updated." % account_id) return account
else: else:
return json.dumps({ return json.dumps({
'ok': False, 'ok': False,
'error_type': 'validation', 'error_type': 'validation',
'errors': account_form.errorsi 'errors': account_form.errors
}) })
@api.route("/accounts/<account_id>", methods=["DELETE"]) api_api.add_resource(AccountListResource, '/accounts')
@auth.login_required api_api.add_resource(AccountResource, '/accounts/<int:account_id>')
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.delete(account)
return json.dumps("Account #%s deleted." % account_id)

View File

@ -14,15 +14,22 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>. along with Accountant. If not, see <http://www.gnu.org/licenses/>.
""" """
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.entries import Entry
from ..models.operations import Operation from ..models.operations import Operation
from ..fields import Object
@api.route("/entries/<account_id>/<year>/<month>") @api.route("/entries/<account_id>/<year>/<month>")
def get_entries(account_id, year, month): def get_entries(account_id, year, month):
@ -47,48 +54,90 @@ def get_entries(account_id, year, month):
} for i in query.all()]) } for i in query.all()])
@api.route("/entries", methods=["PUT"]) resource_fields = {
def add_entry(): # 'id': fields.Integer,
with session_scope() as session: 'operation_date': fields.DateTime(dt_format='iso8601'),
entry = Entry( 'label': fields.String,
operation_date=request.json['operation_date'], 'value': fields.Fixed(decimals=2),
pointed=request.json['pointed'], 'pointed': fields.Boolean,
label=request.json['label'], 'category': fields.String,
value=request.json['value'], 'account_id': fields.Integer,
category=request.json['category'], 'scheduled_operation_id': fields.Integer,
account_id=request.json['account_id'], }
scheduled_operation_id=request.json['scheduled_operation_id']
) 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) session.add(entry)
return json.dumps("Entry added.") return entry
@api.route("/entries/<entry_id>", methods=["PUT"]) class EntryResource(Resource):
def update_entry(entry_id): @session_aware
with session_scope() as session: @marshal_with_field(Object(resource_fields))
entry = session.query(Entry).get(entry_id) 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 @session_aware
entry.operation_date = request.json['operation_date'] @marshal_with_field(Object(resource_fields))
entry.pointed = request.json['pointed'] def put(self, entry_id, session=None):
entry.label = request.json['label'] kwargs = parser.parse_args()
entry.value = request.json['value']
entry.category = request.json['category'] assert (id not in kwargs or kwargs.id is None
entry.account_id = request.json['account_id'] or kwargs.id == entry_id)
entry.scheduled_operation_id = request.json['scheduled_operation_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) session.merge(entry)
return json.dumps("Entry #%s updated." % entry_id) return entry
@session_aware
@api.route("/entries/<entry_id>", methods=["DELETE"]) @marshal_with_field(Object(resource_fields))
def delete_entry(entry_id): def delete(self, entry_id, session=None):
with session_scope() as session: try:
entry = session.query(Entry).filter(Entry.id == entry_id).first() entry = Entry.get(session, entry_id)
except NoResultFound:
return None, 404
session.delete(entry) 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/<int:entry_id>")

View File

@ -14,16 +14,23 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>. along with Accountant. If not, see <http://www.gnu.org/licenses/>.
""" """
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 ..models.scheduled_operations import ScheduledOperation
from .. import api from .. import api, api_api
from ..fields import Object
@api.route("/scheduled_operations/<account_id>") @api.route("/scheduled_operations/<int:account_id>")
def get_scheduled_operations(account_id): def get_scheduled_operations(account_id):
""" """
Return entries for an account, year, and month. Return entries for an account, year, and month.
@ -45,57 +52,94 @@ def get_scheduled_operations(account_id):
} for i in query.all()]) } for i in query.all()])
@api.route("/scheduled_operations", methods=["PUT"]) resource_fields = {
def add_scheduled_operation(): 'id': fields.Integer,
with session_scope() as session: 'start_date': fields.DateTime(dt_format='iso8601'),
scheduledOperation = ScheduledOperation( 'stop_date': fields.DateTime(dt_format='iso8601'),
start_date=request.json['start_date'], 'day': fields.Integer,
stop_date=request.json['stop_date'], 'frequency': fields.Integer,
day=request.json['day'], 'label': fields.String,
frequency=request.json['frequency'], 'value': fields.Fixed(decimals=2),
label=request.json['label'], 'category': fields.String,
value=request.json['value'], 'account_id': fields.Integer,
category=request.json['category'], }
account_id=request.json['account_id']
)
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) session.add(scheduledOperation)
return json.dumps("Scheduled operation added.") return scheduledOperation, 201
@api.route("/scheduled_operations/<scheduled_operation_id>", methods=["PUT"]) class ScheduledOperationResource(Resource):
def update_scheduled_operation(scheduled_operation_id): @session_aware
with session_scope() as session: @marshal_with_field(Object(resource_fields))
query = session.query(ScheduledOperation) def get(self, scheduled_operation_id, session=None):
query = query.filter(ScheduledOperation.id == scheduled_operation_id) """
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 session.delete(scheduled_operation)
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.merge(scheduledOperation) return scheduled_operation
return json.dumps( @session_aware
"Scheduled operation #%s updated." % scheduled_operation_id) @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/<scheduled_operation_id>", methods=["DELETE"]) api_api.add_resource(ScheduledOperationListResource, "/scheduled_operations")
def delete_scheduled_operation(scheduled_operation_id): api_api.add_resource(ScheduledOperationResource,
with session_scope() as session: "/scheduled_operations/<int:scheduled_operation_id>")
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)