Improve query for operations.

This commit is contained in:
Alexis Lahouze 2015-07-18 11:58:32 +02:00
parent c4e262e255
commit ee17f7a12f
2 changed files with 56 additions and 42 deletions

View File

@ -16,7 +16,8 @@
""" """
import arrow import arrow
from sqlalchemy import func, case, desc, true, false from sqlalchemy import func, case, desc, true, false, text
from sqlalchemy.orm import column_property
from accountant import db from accountant import db
@ -66,6 +67,18 @@ class Operation(db.Model):
server_default=false() server_default=false()
) )
sold = column_property(
func.sum(
case(
whens={canceled: text("0")},
else_=value
)
).over(
partition_by=[account_id],
order_by=["operation_date", desc("value"), desc("label"), id]
).label("sold")
)
def __init__(self, label, value, account_id, operation_date=None, def __init__(self, label, value, account_id, operation_date=None,
category=None, pointed=False, confirmed=True, canceled=False, category=None, pointed=False, confirmed=True, canceled=False,
scheduled_operation_id=None): scheduled_operation_id=None):
@ -80,39 +93,37 @@ class Operation(db.Model):
self.canceled = canceled self.canceled = canceled
@classmethod @classmethod
def get_for_account_and_range(cls, session, account, begin, end): def query(cls, session, begin=None, end=None):
if isinstance(account, int) or isinstance(account, str):
account_id = account
else:
account_id = account.id
end = arrow.now().ceil('month').date() if not end else end end = arrow.now().ceil('month').date() if not end else end
# We have to use a join because the sold is not computed from the
# begining.
base_query = session.query( base_query = session.query(
cls, cls.id,
case( cls.sold
whens={cls.canceled: None},
else_=func.sum(
cls.value
).over(
partition_by=[cls.account_id, cls.canceled],
order_by=["operation_date", desc("value"), desc("label")]
)
).label("sold")
).subquery() ).subquery()
query = session.query( query = session.query(
base_query cls.id,
).select_from( cls.operation_date,
base_query cls.label,
cls.value,
base_query.c.sold,
cls.category,
cls.scheduled_operation_id,
cls.account_id,
cls.pointed,
cls.confirmed,
cls.canceled
).join(
base_query, base_query.c.id == cls.id
).filter( ).filter(
base_query.c.account_id == account_id, cls.operation_date >= str(begin),
base_query.c.operation_date >= str(begin), cls.operation_date <= str(end)
base_query.c.operation_date <= str(end)
).order_by( ).order_by(
desc(base_query.c.operation_date), desc(cls.operation_date),
base_query.c.value, cls.value,
base_query.c.label, cls.label,
) )
return query return query

View File

@ -18,8 +18,6 @@ import dateutil.parser
from flask.ext.restful import Resource, fields, reqparse, marshal_with_field from flask.ext.restful import Resource, fields, reqparse, marshal_with_field
from sqlalchemy.orm.exc import NoResultFound
from accountant import session_aware from accountant import session_aware
from .. import api_api from .. import api_api
@ -69,10 +67,13 @@ class OperationListResource(Resource):
def get(self, session): def get(self, session):
kwargs = range_parser.parse_args() kwargs = range_parser.parse_args()
return Operation.get_for_account_and_range(session, **kwargs).all() return Operation.query(
session,
def put(self, *args): begin=kwargs['begin'],
return self.post() end=kwargs['end'],
).filter(
Operation.account_id == kwargs['account']
).all()
@session_aware @session_aware
@marshal_with_field(Object(resource_fields)) @marshal_with_field(Object(resource_fields))
@ -83,7 +84,7 @@ class OperationListResource(Resource):
session.add(operation) session.add(operation)
return operation return Operation.query(session).get(operation.id)
class OperationResource(Resource): class OperationResource(Resource):
@ -93,11 +94,13 @@ class OperationResource(Resource):
""" """
Get operation. Get operation.
""" """
try: operation = Operation.query(session).get(operation_id)
return Operation.get(session, operation_id)
except NoResultFound: if not operation:
return None, 404 return None, 404
return operation
@session_aware @session_aware
@marshal_with_field(Object(resource_fields)) @marshal_with_field(Object(resource_fields))
def post(self, operation_id, session): def post(self, operation_id, session):
@ -106,9 +109,9 @@ class OperationResource(Resource):
assert (id not in kwargs or kwargs.id is None assert (id not in kwargs or kwargs.id is None
or kwargs.id == operation_id) or kwargs.id == operation_id)
try: operation = Operation.query(session).get(operation_id)
operation = Operation.get(session, operation_id)
except NoResultFound: if not operation:
return None, 404 return None, 404
# SQLAlchemy objects ignore __dict__.update() with merge. # SQLAlchemy objects ignore __dict__.update() with merge.
@ -122,9 +125,9 @@ class OperationResource(Resource):
@session_aware @session_aware
@marshal_with_field(Object(resource_fields)) @marshal_with_field(Object(resource_fields))
def delete(self, operation_id, session): def delete(self, operation_id, session):
try: operation = Operation.query(session).get(operation_id)
operation = Operation.get(session, operation_id)
except NoResultFound: if not operation:
return None, 404 return None, 404
session.delete(operation) session.delete(operation)