Source code for order

"""
Functions and request handlers related to orders.

Special permissions are required to access orders:

* If you have permission ``DATA_EDIT`` you have CRUD permissions to your own orders.
* If you have permission ``DATA_MANAGEMENT`` you have CRUD permissions to any orders.
"""
import flask

import structure
import utils

blueprint = flask.Blueprint("order", __name__)  # pylint: disable=invalid-name


[docs]@blueprint.before_request def prepare(): """ All order request require ``DATA_EDIT``. Make sure that the user is logged in and has the required permission. """ perm_status = utils.req_check_permissions(["DATA_EDIT"]) if perm_status != 200: flask.abort(status=perm_status)
[docs]@blueprint.route("", methods=["GET"]) def list_orders(): """ List all orders visible to the current user. Returns: flask.Response: JSON structure with a list of orders. """ projection = {"_id": 1, "title": 1, "tags": 1, "properties": 1} if utils.req_has_permission("DATA_MANAGEMENT"): orders = list(flask.g.db["orders"].find(projection=projection)) else: orders = list( flask.g.db["orders"].find( {"editors": flask.g.current_user["_id"]}, projection=projection ) ) return utils.response_json({"orders": orders})
[docs]@blueprint.route("/<identifier>", methods=["GET"]) def get_order(identifier): """ Retrieve the order with the provided uuid. ``order['datasets']`` is returned as ``[{_id, title}, ...]``. Args: identifier (str): Uuid for the wanted order. Returns: flask.Response: JSON structure for the order. """ entry = utils.req_get_entry("orders", identifier) if not entry: flask.abort(status=404) if not ( utils.req_has_permission("DATA_MANAGEMENT") or flask.g.current_user["_id"] in entry["editors"] ): flask.abort(status=403) prepare_order_response(entry, flask.g.db) return utils.response_json({"order": entry})
[docs]@blueprint.route("/<identifier>/log", methods=["GET"]) def get_order_logs(identifier): """ List changes to the dataset. Logs will be sorted chronologically. The ``data`` in each log will be trimmed to only show the changed fields. Args: identifier (str): Uuid for the wanted order. Returns: flask.Response: Json structure for the logs. """ entry = utils.req_get_entry("orders", identifier) if not entry: flask.abort(status=404) if ( not utils.req_has_permission("DATA_MANAGEMENT") and flask.g.current_user["_id"] not in entry["editors"] ): flask.abort(status=403) order_logs = list(flask.g.db["logs"].find({"data_type": "order", "data._id": entry["_id"]})) if not order_logs: flask.abort(status=404) for log in order_logs: del log["data_type"] utils.incremental_logs(order_logs) return utils.response_json({"entry_id": entry["_id"], "data_type": "order", "logs": order_logs})
[docs]@blueprint.route("", methods=["POST"]) def add_order(): """ Add an order. Returns: flask.Response: Json structure with ``_id`` of the added order. """ # create new order new_order = structure.order() jsondata = flask.request.json if not jsondata or "order" not in jsondata or not isinstance(jsondata["order"], dict): flask.abort(status=400) indata = jsondata["order"] validation = utils.basic_check_indata(indata, new_order, ["_id", "datasets"]) if not validation.result: flask.abort(status=validation.status) # add current user to editors if no editors are defined if not indata.get("editors"): indata["editors"] = [flask.g.current_user["_id"]] # add current user if missing and only DATA_EDIT elif ( not utils.req_has_permission("DATA_MANAGEMENT") and str(flask.g.current_user["_id"]) not in indata["editors"] ): indata["editors"].append(flask.g.current_user["_id"]) # convert all incoming uuids to uuid.UUID indata = utils.prepare_for_db(indata) new_order.update(indata) result = utils.req_commit_to_db("orders", "add", new_order) if not result.log or not result.data: flask.abort(status=500) return utils.response_json({"_id": result.ins_id})
[docs]@blueprint.route("/<identifier>", methods=["DELETE"]) def delete_order(identifier: str): """ Delete the order with the given identifier. Returns: flask.Response: Status code """ entry = utils.req_get_entry("orders", identifier) if not entry: flask.abort(status=404) # permission check if ( not utils.req_has_permission("DATA_MANAGEMENT") and flask.g.current_user["_id"] not in entry["editors"] ): flask.abort(status=403) for dataset_uuid in entry["datasets"]: result = utils.req_commit_to_db("datasets", "delete", {"_id": dataset_uuid}) if not result.log or not result.data: flask.abort(status=500) # delete dataset references in all collections collections = list(flask.g.db["collections"].find({"datasets": {"$in": entry["datasets"]}})) flask.g.db["collections"].update_many({}, {"$pull": {"datasets": {"$in": entry["datasets"]}}}) for collection in collections: collection["datasets"] = [ ds for ds in collection["datasets"] if ds not in entry["datasets"] ] utils.req_make_log_new( data_type="collection", action="edit", comment="Order deleted", data=collection, ) result = utils.req_commit_to_db("orders", "delete", {"_id": entry["_id"]}) if not result.log or not result.data: flask.abort(status=500) return flask.Response(status=200)
[docs]@blueprint.route("/<identifier>", methods=["PATCH"]) def update_order(identifier: str): # pylint: disable=too-many-branches """ Update an existing order. Args: identifier (str): Order uuid. Returns: flask.Response: Status code of the request. """ order = utils.req_get_entry("orders", identifier) if not order: flask.abort(status=404) # permission check if ( not utils.req_has_permission("DATA_MANAGEMENT") and flask.g.current_user["_id"] not in order["editors"] ): flask.abort(status=403) jsondata = flask.request.json if not jsondata or "order" not in jsondata or not isinstance(jsondata["order"], dict): flask.abort(status=400) indata = jsondata["order"] validation = utils.basic_check_indata(indata, order, ["_id", "datasets"]) if not validation.result: flask.abort(status=validation.status) # DATA_EDIT may not delete itself from editors if ( not utils.req_has_permission("DATA_MANAGEMENT") and indata.get("editors") and str(flask.g.current_user["_id"]) not in indata["editors"] ): flask.abort(status=400) # convert all incoming uuids to uuid.UUID indata = utils.prepare_for_db(indata) is_different = False for field in indata: if indata[field] != order[field]: is_different = True break order.update(indata) if indata and is_different: result = utils.req_commit_to_db("orders", "edit", order) if not result.log or not result.data: flask.abort(status=500) return flask.Response(status=200)
[docs]@blueprint.route("/<identifier>/dataset", methods=["POST"]) def add_dataset(identifier: str): # pylint: disable=too-many-branches """ Add a dataset to the given order. Args: identifier (str): The order to add the dataset to. """ order = utils.req_get_entry("orders", identifier) if not order: flask.abort(status=404) if ( not utils.req_has_permission("DATA_MANAGEMENT") and flask.g.current_user["_id"] not in order["editors"] ): flask.abort(status=403) new_dataset = structure.dataset() jsondata = flask.request.json if not jsondata or "dataset" not in jsondata or not isinstance(jsondata["dataset"], dict): flask.abort(status=400) indata = jsondata["dataset"] validation = utils.basic_check_indata(indata, new_dataset, ["_id"]) if not validation.result: flask.abort(status=validation.status) indata = utils.prepare_for_db(indata) new_dataset.update(indata) ds_result = utils.req_commit_to_db("datasets", "add", new_dataset) if not ds_result.log or not ds_result.data: flask.abort(status=500) order_result = flask.g.db["orders"].update_one( {"_id": order["_id"]}, {"$push": {"datasets": new_dataset["_id"]}} ) if not order_result.acknowledged: flask.current_app.logger.error( "Failed to add dataset %s to order %s", new_dataset["_id"], order["_id"] ) flask.abort(status=500) order["datasets"].append(new_dataset["_id"]) utils.req_make_log_new( data_type="order", action="edit", comment="Dataset added", data=order, ) return utils.response_json({"_id": ds_result.ins_id})
[docs]def prepare_order_response(order_data: dict, mongodb): """ Prepare an order by e.g. converting user uuids to names etc. Changes are done in-place. Args: order_data (dict): The order entry from the db. mongodb: The mongo database to use. """ order_data["authors"] = utils.user_uuid_data(order_data["authors"], mongodb) order_data["generators"] = utils.user_uuid_data(order_data["generators"], mongodb) order_data["editors"] = utils.user_uuid_data(order_data["editors"], mongodb) if order_data["organisation"]: if org_entry := utils.user_uuid_data(order_data["organisation"], mongodb): order_data["organisation"] = org_entry[0] else: flask.current_app.logger.error( "Reference to non-existing organisation: %s", order_data["organisation"] ) else: order_data["organisation"] = {} # convert dataset list into {title, _id} order_data["datasets"] = list( mongodb["datasets"].find({"_id": {"$in": order_data["datasets"]}}, {"_id": 1, "title": 1}) )