diff --git a/artifice/api/helpers.py b/artifice/api/helpers.py index 7690738..d3e05de 100644 --- a/artifice/api/helpers.py +++ b/artifice/api/helpers.py @@ -2,15 +2,17 @@ from decorator import decorator import flask import itertools import json +from artifice.models import Tenant + def _validate(data, *args, **kwargs): for key in itertools.chain(args, kwargs.keys()): if not key in data: flask.abort(400, json.dumps({'error': 'missing parameter', - 'param': key})) + 'param': key})) for key, val in kwargs.iteritems(): flask.abort(400, json.dumps({'error': 'validation failed', - 'param': key})) + 'param': key})) def must(*args, **kwargs): @@ -47,3 +49,19 @@ def json_must(*args, **kwargs): return func(*iargs) return decorator(dejson, func) return unpack + + +def validate_tenant_id(tenant_id, session): + """Tenant ID validation that check that the id you passed is valid, + and that a tenant with this ID exists. + - returns tenant query, or a tuple if validation failure.""" + if isinstance(tenant_id, unicode): + tenant_query = session.query(Tenant).\ + filter(Tenant.id == tenant_id) + if tenant_query.count() == 0: + return 400, {"errors": ["No tenant matching ID found."]} + elif tenant_id is not None: + return 400, {"error": ["tenant must be a unicode string."]} + else: + return 400, {"missing parameter": {"tenant": "Tenant id."}} + return tenant_query[0] diff --git a/artifice/api/web.py b/artifice/api/web.py index 5b5a0a9..05c701b 100644 --- a/artifice/api/web.py +++ b/artifice/api/web.py @@ -3,7 +3,7 @@ from flask import Flask, Blueprint from artifice import interface, database, config from artifice.transformers import active_transformers from artifice.rates import RatesFile -from artifice.models import SalesOrder, Tenant +from artifice.models import SalesOrder from artifice.helpers import convert_to import sqlalchemy from sqlalchemy import create_engine, func @@ -12,7 +12,7 @@ from sqlalchemy.pool import NullPool from datetime import datetime, timedelta import json -from .helpers import returns_json, json_must +from .helpers import returns_json, json_must, validate_tenant_id engine = None @@ -224,19 +224,12 @@ def add_costs_for_tenant(tenant, RatesManager): def generate_sales_order(draft, tenant_id, end): session = Session() - - if isinstance(tenant_id, unicode): - tenant_query = session.query(Tenant).\ - filter(Tenant.id == tenant_id) - if tenant_query.count() == 0: - return 400, {"errors": ["No tenant matching ID found."]} - elif tenant_id is not None: - return 400, {"error": ["tenant must be a unicode string."]} - else: - return 400, {"missing parameter": {"tenant": "Tenant id."}} - db = database.Database(session) + valid_tenant = validate_tenant_id(tenant_id, session) + if isinstance(valid_tenant, tuple): + return valid_tenant + rates = RatesFile(config.rates_config) # Get the last sales order for this tenant, to establish @@ -268,7 +261,7 @@ def generate_sales_order(draft, tenant_id, end): session.commit() # Transform the query result into a billable dict. - tenant_dict = build_tenant_dict(tenant_query[0], usage, db) + tenant_dict = build_tenant_dict(valid_tenant, usage, db) tenant_dict = add_costs_for_tenant(tenant_dict, rates) # add sales order range: @@ -285,39 +278,22 @@ def generate_sales_order(draft, tenant_id, end): def regenerate_sales_order(tenant_id, target): session = Session() - db = database.Database(session) - - if isinstance(tenant_id, unicode): - tenant_query = session.query(Tenant).\ - filter(Tenant.id == tenant_id) - if tenant_query.count() == 0: - return 400, {"errors": ["No tenant matching ID found."]} - elif tenant_id is not None: - return 400, {"error": ["tenant must be a unicode string."]} - else: - return 400, {"missing parameter": {"tenant": "Tenant id."}} - - if target is not None: - try: - target = datetime.strptime(target, iso_date) - except ValueError: - return 400, {"errors": ["date given needs to be in format: " + - "y-m-d"]} - else: - return 400, {"missing parameter": {"date": "target date in format: " + - "y-m-d"}} - rates = RatesFile(config.rates_config) + + valid_tenant = validate_tenant_id(tenant_id, session) + if isinstance(valid_tenant, tuple): + return valid_tenant + try: - sales_order = db.get_sales_order(tenant_id, target) + sales_order = db.get_sales_orders(tenant_id, target, target) except IndexError: return 400, {"errors": ["Given date not in existing sales orders."]} usage = db.usage(sales_order.start, sales_order.end, tenant_id) # Transform the query result into a billable dict. - tenant_dict = build_tenant_dict(tenant_query[0], usage, db) + tenant_dict = build_tenant_dict(valid_tenant, usage, db) tenant_dict = add_costs_for_tenant(tenant_dict, rates) # add sales order range: @@ -327,6 +303,34 @@ def regenerate_sales_order(tenant_id, target): return 200, tenant_dict +def regenerate_sales_order_range(tenant_id, start, end): + session = Session() + db = database.Database(session) + rates = RatesFile(config.rates_config) + + valid_tenant = validate_tenant_id(tenant_id, session) + if isinstance(valid_tenant, tuple): + return valid_tenant + + sales_orders = db.get_sales_orders(tenant_id, start, end) + + tenants = [] + for sales_order in sales_orders: + usage = db.usage(sales_order.start, sales_order.end, tenant_id) + + # Transform the query result into a billable dict. + tenant_dict = build_tenant_dict(valid_tenant, usage, db) + tenant_dict = add_costs_for_tenant(tenant_dict, rates) + + # add sales order range: + tenant_dict['start'] = str(sales_order.start) + tenant_dict['end'] = str(sales_order.end) + + tenants.append(tenant_dict) + + return 200, tenants + + @app.route("sales_order", methods=["POST"]) @json_must() @returns_json @@ -377,8 +381,43 @@ def run_sales_historic_generation(): tenant_id = flask.request.json.get("tenant", None) target = flask.request.json.get("date", None) + if target is not None: + try: + target = datetime.strptime(target, iso_date) + except ValueError: + return 400, {"errors": ["date given needs to be in format: " + + "y-m-d"]} + else: + return 400, {"missing parameter": {"date": "target date in format: " + + "y-m-d"}} + return regenerate_sales_order(tenant_id, target) +@app.route("sales_range", methods=["POST"]) +@json_must() +@returns_json +def run_sales_historic_range_generation(): + tenant_id = flask.request.json.get("tenant", None) + start = flask.request.json.get("start", None) + end = flask.request.json.get("end", None) + + try: + if start is not None: + start = datetime.strptime(start, iso_date) + else: + return 400, {"missing parameter": {"start": "start date in format: " + + "y-m-d"}} + if end is not None: + end = datetime.strptime(end, iso_date) + else: + end = datetime.utcnow() + except ValueError: + return 400, {"errors": ["dates given need to be in format: " + + "y-m-d"]} + + return regenerate_sales_order_range(tenant_id, start, end) + + if __name__ == '__main__': pass diff --git a/artifice/database.py b/artifice/database.py index a6980ef..33ceeda 100644 --- a/artifice/database.py +++ b/artifice/database.py @@ -90,11 +90,11 @@ class Database(object): filter(Resource.id == resource_id) return json.loads(info[0].info) - def get_sales_order(self, tenant_id, target): + def get_sales_orders(self, tenant_id, start, end): query = self.session.query(SalesOrder).\ - filter(SalesOrder.start <= target, SalesOrder.end >= target).\ + filter(SalesOrder.start <= end, SalesOrder.end >= start).\ filter(SalesOrder.tenant_id == tenant_id) - return query[0] + return query def merge_resource_metadata(self, md_dict, entry): fields = config.collection['metadata_def'].get(md_dict['type'], {}) diff --git a/client/client.py b/client/client.py index 09b4632..b5d08d4 100644 --- a/client/client.py +++ b/client/client.py @@ -65,3 +65,23 @@ class Client(object): print json.dumps(response.json(), indent=2, sort_keys=True) except ConnectionError as e: print e + + def sales_range(self, tenants, start, end): + url = self.endpoint + "sales_range" + + for tenant in tenants: + data = {"tenant": tenant, "start": start, "end": end} + try: + response = requests.post(url, + headers={"Content-Type": + "application/json", + "token": self.auth_token}, + data=json.dumps(data)) + if response.status_code != 200: + raise AttributeError("Sales order cycle failed: " + + response.text + " code: " + + str(response.status_code)) + else: + print json.dumps(response.json(), indent=2, sort_keys=True) + except ConnectionError as e: + print e diff --git a/client/shell.py b/client/shell.py index 04772dc..77a2c01 100644 --- a/client/shell.py +++ b/client/shell.py @@ -82,6 +82,22 @@ if __name__ == '__main__': "-d", "--date", dest="date", help='target search date for sales order.') + range_parser = subparsers.add_parser( + 'sales-range', + help=('regenerate historic sales orders for given tenants,' + + 'in a given range')) + range_parser.add_argument( + "-t", "--tenant", dest="tenants", + help='Tenants to create sales drafts for.', + action="append", default=[]) + range_parser.add_argument( + "-s", "--start", dest="start", + help='start of range for sales orders.') + range_parser.add_argument( + "-e", "--end", dest="end", + help='end of range for sales orders. Defaults to now.', + default=None) + args = parser.parse_args() conf = {'api': {'endpoint': 'http://0.0.0.0:8000/', @@ -107,3 +123,6 @@ if __name__ == '__main__': if args.command == 'sales-historic': client.sales_historic(args.tenants, args.date) + + if args.command == 'sales-range': + client.sales_range(args.tenants, args.start, args.end)