Refactoring.

This commit is contained in:
Alexis Lahouze 2015-06-06 14:04:48 +02:00
parent 6c24fb27be
commit 069fbe8f8e
12 changed files with 285 additions and 168 deletions

View File

@ -14,27 +14,40 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from contextlib import contextmanager
from .api import api
#from api.controller import login_manager
from .api.model import db
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from .frontend import frontend
from sqlalchemy.orm import sessionmaker
from . import config
# The app
app = Flask(__name__, static_folder = None)
app = Flask(__name__, static_folder=None)
app.config['SQLALCHEMY_DATABASE_URI'] = config.db_uri
app.config['SQLALCHEMY_RECORD_QUERIES'] = config.debug
app.config['WTF_CSRF_ENABLED'] = False
app.config['SECRET_KEY'] = 'my_secret_key'
db.init_app(app)
#login_manager.init_app(app)
db = SQLAlchemy(app)
@contextmanager
def session_scope():
from accountant import db
session = db.session
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
# Must be after db declaration because the blueprints may need it.
from .api import api
from .frontend import frontend
app.register_blueprint(frontend, url_prefix='')
app.register_blueprint(api, url_prefix='/api')

View File

@ -1,6 +1,22 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from flask import Blueprint
api = Blueprint('api', __name__)
# Load all views.
from .controller import *

View File

@ -1,3 +1,19 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
import pkgutil
from flask.ext.httpauth import HTTPBasicAuth
@ -7,6 +23,3 @@ __all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
__all__.append(module_name)
from . import *

View File

@ -14,15 +14,21 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from flask import json, request
from sqlalchemy import func, case, cast, extract, distinct
from forms.accounts import AccountForm
from accountant import db, session_scope
from . import auth
from .. import api
from ..model import db, session_scope
from ..model.accounts import Account
from ..model.entries import Entry
from ..model.operations import Operation
from flask import json, request
from sqlalchemy import func, case, cast, extract, distinct
from forms.accounts import AccountIdForm, AccountForm
@api.route("/accounts", methods=["GET"])
@auth.login_required
@ -32,12 +38,14 @@ def get_accounts():
"""
with session_scope() as session:
query = session.query(
Account.id.label("id"),
Account.name.label("name"),
Account.authorized_overdraft.label("authorized_overdraft"),
func.sum(Entry.value).label("future"),
func.sum(case([(Entry.pointed, Entry.value,)], else_=cast(0, db.Numeric(15, 2)))).label("pointed"),
func.sum(case([(Entry.operation_date < func.now(), Entry.value,)], else_=cast(0, db.Numeric(15, 2)))).label("current")
Account.id.label("id"),
Account.name.label("name"),
Account.authorized_overdraft.label("authorized_overdraft"),
func.sum(Entry.value).label("future"),
func.sum(case([(Entry.pointed, Entry.value,)],
else_=cast(0, db.Numeric(15, 2)))).label("pointed"),
func.sum(case([(Entry.operation_date < func.now(), Entry.value,)],
else_=cast(0, db.Numeric(15, 2)))).label("current")
).outerjoin(Entry).group_by(Account.id).order_by(Account.id)
return json.dumps([{
@ -49,18 +57,23 @@ def get_accounts():
"future": str(i.future)
} for i in query.all()])
@api.route("/accounts/<account_id>/<year>/<month>/")
@auth.login_required
def get_account_status(account_id, year, month):
with session_scope() as session:
query = session.query(
func.sum(case([(func.sign(Operation.value) == -1, Operation.value)], else_=0)).label("expenses"),
func.sum(case([(func.sign(Operation.value) == 1, Operation.value)], else_=0)).label("revenues"),
func.sum(case([(func.sign(Operation.value) == -1, Operation.value)],
else_=0)).label("expenses"),
func.sum(case([(func.sign(Operation.value) == 1, Operation.value)],
else_=0)).label("revenues"),
func.sum(Operation.value).label("balance")
).filter(
Operation.account_id == account_id
).filter(
func.date_trunc('month', Operation.operation_date) == "%s-%s-01" % (year, month)
func.date_trunc('month',
Operation.operation_date) == "%s-%s-01" % (year,
month)
).group_by(Operation.account_id)
if query.count() == 1:
@ -79,13 +92,16 @@ def get_account_status(account_id, year, month):
"balance": str(balance)
})
@api.route("/accounts/<account_id>/months")
@auth.login_required
def get_months(account_id):
with session_scope() as session:
query = session.query(
distinct(func.lpad(cast(extract("year", Entry.operation_date), db.String), 4, '0')).label("year"),
func.lpad(cast(extract("month", Entry.operation_date), db.String), 2, '0').label("month")
distinct(func.lpad(cast(extract("year", Entry.operation_date),
db.String), 4, '0')).label("year"),
func.lpad(cast(extract("month", Entry.operation_date),
db.String), 2, '0').label("month")
).filter(Entry.account_id == account_id).order_by("year", "month")
return json.dumps([{
@ -93,11 +109,13 @@ def get_months(account_id):
"month": i.month.rjust(2, '0')
} 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'])
account = Account(request.json['name'],
request.json['authorized_overdraft'])
session.add(account)
@ -111,7 +129,10 @@ def update_account(account_id):
if account_form.validate():
with session_scope() as session:
account = session.query(Account).filter(Account.id == account_id).first()
query = session.query(Account)
query = query.filter(Account.id == account_id)
account = query.first()
account.name = request.json['name']
account.authorized_overdraft = request.json['authorized_overdraft']
@ -120,15 +141,22 @@ def update_account(account_id):
return json.dumps("Account #%s updated." % account_id)
else:
return json.dumps({'ok': False, 'error_type': 'validation', 'errors': account_form.errors})
return json.dumps({
'ok': False,
'error_type': 'validation',
'errors': account_form.errorsi
})
@api.route("/accounts/<account_id>", methods=["DELETE"])
@auth.login_required
def delete_account(account_id):
with session_scope() as session:
account = session.query(Account).filter(Account.id == account_id).first()
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,16 +14,17 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from flask import json, request
from sqlalchemy import func, case, desc
from accountant import session_scope
from .. import api
from ..model import db, session_scope
from ..model.entries import Entry
from ..model.operations import Operation
from ..model.scheduled_operations import ScheduledOperation
from flask import json, request
from sqlalchemy import func, desc
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.orm import sessionmaker, column_property, aliased
from sqlalchemy.sql import func, select, case
@api.route("/entries/<account_id>/<year>/<month>")
def get_entries(account_id, year, month):
@ -33,45 +34,54 @@ def get_entries(account_id, year, month):
with session_scope() as session:
base_query = session.query(
Operation,
case(whens={Operation.canceled: None}, else_=func.sum(Operation.value).over(partition_by="canceled", order_by="operation_date, value desc, label desc")).label("sold")
case(
whens={Operation.canceled: None},
else_=func.sum(Operation.value).over(
partition_by="canceled",
order_by="operation_date, value desc, label desc")
).label("sold")
).filter(Operation.account_id == account_id).order_by(
desc(Operation.operation_date),
Operation.value,
Operation.label,
).subquery()
query = session.query(base_query).select_from(base_query).filter(func.date_trunc('month', base_query.c.operation_date) == "%s-%s-01" % (year, month))
query = session.query(base_query).select_from(base_query)
query = query.filter(func.date_trunc(
'month', base_query.c.operation_date) == "%s-%s-01" % (year, month))
return json.dumps([{
"id": i.id,
"pointed": i.pointed,
"operation_date": i.operation_date.strftime("%Y-%m-%d"),
"label": i.label,
"value": str(i.value),
"category": i.category,
"sold": str(i.sold) if not i.canceled else None,
"account_id": i.account_id,
"canceled": i.canceled,
"scheduled_operation_id": i.scheduled_operation_id
"id": i.id,
"pointed": i.pointed,
"operation_date": i.operation_date.strftime("%Y-%m-%d"),
"label": i.label,
"value": str(i.value),
"category": i.category,
"sold": str(i.sold) if not i.canceled else None,
"account_id": i.account_id,
"canceled": i.canceled,
"scheduled_operation_id": i.scheduled_operation_id
} 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']
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']
)
session.add(entry)
return json.dumps("Entry added.")
@api.route("/entries/<entry_id>", methods=["PUT"])
def update_entry(entry_id):
with session_scope() as session:
@ -90,6 +100,7 @@ def update_entry(entry_id):
return json.dumps("Entry #%s updated." % entry_id)
@api.route("/entries/<entry_id>", methods=["DELETE"])
def delete_entry(entry_id):
with session_scope() as session:
@ -98,4 +109,3 @@ def delete_entry(entry_id):
session.delete(entry)
return json.dumps("Entry #%s deleted." % entry_id)

View File

@ -1,11 +1,28 @@
from .. import api
from ..model import db, session_scope
from ..model.scheduled_operations import ScheduledOperation
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from flask import json, request
from sqlalchemy import func, desc
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.orm import sessionmaker, column_property
from sqlalchemy.sql import func, select
from sqlalchemy import desc
from accountant import session_scope
from .. import api
from ..model.scheduled_operations import ScheduledOperation
@api.route("/scheduled_operations/<account_id>")
def get_scheduled_operations(account_id):
@ -14,51 +31,56 @@ def get_scheduled_operations(account_id):
"""
with session_scope() as session:
query = session.query(
ScheduledOperation
ScheduledOperation
).select_from(
session.query(ScheduledOperation)
.filter(ScheduledOperation.account_id == account_id)
.order_by(
desc(ScheduledOperation.day),
ScheduledOperation.value,
ScheduledOperation.label,
).subquery()
session.query(ScheduledOperation)
.filter(ScheduledOperation.account_id == account_id)
.order_by(
desc(ScheduledOperation.day),
ScheduledOperation.value,
ScheduledOperation.label,
).subquery()
)
return json.dumps([{
"id": i.id,
"start_date": i.start_date.strftime("%Y-%m-%d"),
"stop_date": i.stop_date.strftime("%Y-%m-%d"),
"day": str(i.day),
"frequency": str(i.frequency),
"label": i.label,
"value": str(i.value),
"category": i.category,
"account_id": i.account_id
"id": i.id,
"start_date": i.start_date.strftime("%Y-%m-%d"),
"stop_date": i.stop_date.strftime("%Y-%m-%d"),
"day": str(i.day),
"frequency": str(i.frequency),
"label": i.label,
"value": str(i.value),
"category": i.category,
"account_id": i.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']
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']
)
session.add(scheduledOperation)
return json.dumps("Scheduled operation added.")
@api.route("/scheduled_operations/<scheduled_operation_id>", methods=["PUT"])
def update_scheduled_operation(scheduled_operation_id):
with session_scope() as session:
scheduledOperation = session.query(ScheduledOperation).filter(ScheduledOperation.id == scheduled_operation_id).first()
query = session.query(ScheduledOperation)
query = query.filter(ScheduledOperation.id == scheduled_operation_id)
scheduledOperation = query.first()
scheduledOperation.id = scheduled_operation_id
scheduledOperation.start_date = request.json['start_date'],
@ -72,14 +94,18 @@ def update_scheduled_operation(scheduled_operation_id):
session.merge(scheduledOperation)
return json.dumps("Scheduled operation #%s updated." % scheduled_operation_id)
return json.dumps(
"Scheduled operation #%s updated." % scheduled_operation_id)
@api.route("/scheduled_operations/<scheduled_operation_id>", methods=["DELETE"])
def delete_scheduled_operation(scheduled_operation_id):
with session_scope() as session:
scheduledOperation = session.query(ScheduledOperation).filter(ScheduledOperation.id == scheduled_operation_id).first()
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)
return json.dumps(
"Scheduled operation #%s deleted." % scheduled_operation_id)

View File

@ -14,10 +14,11 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
#from accountant import session_scope
from . import auth
from .. import api
from ..model import db, session_scope
#from .. import api
@auth.verify_password
def verify_password(username, password):

View File

@ -1,25 +1,19 @@
from contextlib import contextmanager
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
"""
This file is part of Accountant.
db = SQLAlchemy()
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
@contextmanager
def session_scope():
#session = scoped_session(sessionmaker(autocommit = False, autoflush = False, bind = engine))
session = db.session
#Base.query = session.query_property()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
import pkgutil
__all__ = []
@ -27,5 +21,4 @@ __all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
__all__.append(module_name)
from . import *
#from . import *

View File

@ -14,14 +14,14 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
from accountant import db
class Account(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(200), nullable = False)
authorized_overdraft = db.Column(db.Integer, nullable = True, default = 0)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False)
authorized_overdraft = db.Column(db.Integer, nullable=True, default=0)
def __init__(self, name, authorized_overdraft):
self.name = name
self.authorized_overdraft = authorized_overdraft

View File

@ -14,28 +14,32 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
from accountant import db
from .accounts import Account
from .scheduled_operations import ScheduledOperation
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class Entry(db.Model):
id = db.Column(db.Integer, primary_key=True)
pointed = db.Column(db.Boolean, nullable = False, default = False)
operation_date = db.Column(db.Date, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
pointed = db.Column(db.Boolean, nullable=False, default=False)
operation_date = db.Column(db.Date, nullable=False)
label = db.Column(db.String(500), nullable=False)
value = db.Column(db.Numeric(15, 2), nullable=False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
scheduled_operation_id = db.Column(db.Integer, db.ForeignKey('scheduled_operation.id'))
scheduled_operation_id = db.Column(db.Integer,
db.ForeignKey('scheduled_operation.id'))
account = db.relationship(Account, backref = db.backref('entry', lazy="dynamic"))
scheduled_operation = db.relationship(ScheduledOperation, backref = db.backref('entry', lazy="dynamic"))
account = db.relationship(Account, backref=db.backref('entry',
lazy="dynamic"))
scheduled_operation = db.relationship(ScheduledOperation,
backref=db.backref('entry',
lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
category = db.Column(db.String(100), nullable=True)
def __init__(self, pointed, label, value, account_id, operation_date = None, category = None, scheduled_operation_id = None):
def __init__(self, pointed, label, value, account_id, operation_date=None,
category=None, scheduled_operation_id=None):
self.pointed = pointed
self.operation_date = operation_date
self.label = label
@ -43,4 +47,3 @@ class Entry(db.Model):
self.account_id = account_id
self.category = category
self.scheduled_operation_id = scheduled_operation_id

View File

@ -14,35 +14,33 @@
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
from accountant import db
from .accounts import Account
from .scheduled_operations import ScheduledOperation
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class Operation(db.Model):
id = db.Column(db.Integer, primary_key=True)
pointed = db.Column(db.Boolean, nullable = False, default = False)
operation_date = db.Column(db.Date, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
pointed = db.Column(db.Boolean, nullable=False, default=False)
operation_date = db.Column(db.Date, nullable=False)
label = db.Column(db.String(500), nullable=False)
value = db.Column(db.Numeric(15, 2), nullable=False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
scheduled_operation_id = db.Column(db.Integer, db.ForeignKey('scheduled_operation.id'))
scheduled_operation_id = db.Column(db.Integer,
db.ForeignKey('scheduled_operation.id'))
account = db.relationship(Account, backref = db.backref('operation', lazy="dynamic"))
account = db.relationship(Account, backref=db.backref('operation',
lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
category = db.Column(db.String(100), nullable=True)
canceled = db.Column(db.Boolean, nullable = False)
canceled = db.Column(db.Boolean, nullable=False)
def __init__(self, pointed, label, value, account_id, operation_date = None, category = None):
def __init__(self, pointed, label, value, account_id, operation_date=None,
category=None):
self.pointed = pointed
self.operation_date = operation_date
self.label = label
self.value = value
self.account_id = account_id
self.category = category

View File

@ -1,24 +1,41 @@
from . import db
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from accountant import db
from .accounts import Account
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class ScheduledOperation(db.Model):
id = db.Column(db.Integer, primary_key=True)
start_date = db.Column(db.Date, nullable = False)
stop_date = db.Column(db.Date, nullable = False)
day = db.Column(db.Integer, nullable = False)
frequency = db.Column(db.Integer, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
start_date = db.Column(db.Date, nullable=False)
stop_date = db.Column(db.Date, nullable=False)
day = db.Column(db.Integer, nullable=False)
frequency = db.Column(db.Integer, nullable=False)
label = db.Column(db.String(500), nullable=False)
value = db.Column(db.Numeric(15, 2), nullable=False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
account = db.relationship(Account, backref = db.backref('scheduled_operation', lazy="dynamic"))
account = db.relationship(Account, backref=db.backref('scheduled_operation',
lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
category = db.Column(db.String(100), nullable=True)
def __init__(self, start_date, stop_date, day, frequency, label, value, account_id, category = None):
def __init__(self, start_date, stop_date, day, frequency, label, value,
account_id, category=None):
self.start_date = start_date
self.stop_date = stop_date
self.day = day
@ -27,4 +44,3 @@ class ScheduledOperation(db.Model):
self.value = value
self.account_id = account_id
self.category = category