def create_object(container, portal_type, **data): """Creates an object slug :returns: The new created content object :rtype: object """ if "id" in data: # always omit the id as Bika LIMS generates a proper one id = data.pop("id") logger.warn("Passed in ID '{}' omitted! Bika LIMS " "generates a proper ID for you".format(id)) try: if portal_type == "Sample": obj = create_sample(container, **data) return obj elif portal_type == "SampleType": obj = create_sample_type(container, portal_type, **data) return obj elif portal_type == "Project": obj = create_project(container, portal_type, **data) return obj elif portal_type == "StorageUnit" or \ portal_type == "ManagedStorage" or \ portal_type == "UnmanagedStorage": obj = create_storage(container, portal_type, **data) return obj except Unauthorized: fail(401, "You are not allowed to create this content") obj = bika_create_object(container, portal_type, **data) return obj
def action(context, request, action=None, resource=None, uid=None): """Various HTTP POST actions """ # Fetch and call the action function of the API func_name = "{}_items".format(action) action_func = getattr(api, func_name, None) if action_func is None: api.fail(500, "API has no member named '{}'".format(func_name)) portal_type = api.resource_to_portal_type(resource) items = action_func(portal_type=portal_type, uid=uid) return { "count": len(items), "items": items, "url": api.url_for("bika.lims.jsonapi.v2.action", action=action), }
def login(context, request): """ Login Route Login route to authenticate a user against Plone. """ # extract the data __ac_name = request.get("__ac_name", None) __ac_password = request.get("__ac_password", None) logger.info("*** LOGIN %s ***" % __ac_name) if __ac_name is None: api.fail(400, "__ac_name is missing") if __ac_password is None: api.fail(400, "__ac_password is missing") acl_users = api.get_tool("acl_users") # XXX hard coded acl_users.credentials_cookie_auth.login() # XXX amin user won't be logged in if I use this approach # acl_users.login() # response = request.response # acl_users.updateCredentials(request, response, __ac_name, __ac_password) if api.is_anonymous(): api.fail(401, "Invalid Credentials") # return the JSON in the same format like the user route return get(context, request, username=__ac_name)
def login(context, request): """ Login Route Login route to authenticate a user against Plone. """ # extract the data __ac_name = request.get("__ac_name", None) __ac_password = request.get("__ac_password", None) logger.info("*** LOGIN %s ***" % __ac_name) if __ac_name is None: api.fail(400, "__ac_name is missing") if __ac_password is None: api.fail(400, "__ac_password is missing") acl_users = api.get_tool("acl_users") # XXX hard coded acl_users.credentials_cookie_auth.login() # XXX amin user won't be logged in if I use this approach # acl_users.login() # response = request.response # acl_users.updateCredentials(request, response, __ac_name, __ac_password) if api.is_anonymous(): api.fail(401, "Invalid Credentials") # return the JSON in the same format like the user route return get(context, request, username=__ac_name)
def action(context, request, action=None, resource=None, uid=None): """Various HTTP POST actions Case 1: /<uid> -> Return the full object immediately in the root of the JSON API response <Bika-Site>/@@API/v2/<uid> Case 2: /<action>/<uid> -> The actions (update, delete) will performed on the object identified by <uid> -> The actions (create) will use the <uid> as the parent folder <Bika-Site>/@@API/v2/<action>/<uid> Case 3: <resource>/<action> -> The "target" object will be located by a location given in the request body (uid, path, parent_path + id) -> The actions (cut, copy, update, delete) will performed on the target object -> The actions (create) will use the target object as the container <Bika-Site>/@@API/v2/<resource>/<action> Case 4: <resource>/<action>/<uid> -> The actions (cut, copy, update, delete) will performed on the object identified by <uid> -> The actions (create) will use the <uid> as the parent folder <Bika-Site>/@@API/v2/<resource>/<action> """ # Fetch and call the action function of the API func_name = "{}_items".format(action) action_func = getattr(api, func_name, None) if action_func is None: api.fail(500, "API has no member named '{}'".format(func_name)) portal_type = api.resource_to_portal_type(resource) items = action_func(portal_type=portal_type, uid=uid) return { "count": len(items), "items": items, "url": api.url_for("bika.lims.jsonapi.v2.action", action=action), }
def action(context, request, action=None, resource=None, uid=None): """Various HTTP POST actions Case 1: /<uid> -> Return the full object immediately in the root of the JSON API response <Bika-Site>/@@API/v2/<uid> Case 2: /<action>/<uid> -> The actions (update, delete) will performed on the object identified by <uid> -> The actions (create) will use the <uid> as the parent folder <Bika-Site>/@@API/v2/<action>/<uid> Case 3: <resource>/<action> -> The "target" object will be located by a location given in the request body (uid, path, parent_path + id) -> The actions (cut, copy, update, delete) will performed on the target object -> The actions (create) will use the target object as the container <Bika-Site>/@@API/v2/<resource>/<action> Case 4: <resource>/<action>/<uid> -> The actions (cut, copy, update, delete) will performed on the object identified by <uid> -> The actions (create) will use the <uid> as the parent folder <Bika-Site>/@@API/v2/<resource>/<action> """ # Fetch and call the action function of the API func_name = "{}_items".format(action) action_func = getattr(api, func_name, None) if action_func is None: api.fail(500, "API has no member named '{}'".format(func_name)) portal_type = api.resource_to_portal_type(resource) items = action_func(portal_type=portal_type, uid=uid) return { "count": len(items), "items": items, "url": api.url_for("bika.lims.jsonapi.v2.action", action=action), }
def get_object_by_id(id, default=None): """Find an object by a given ID :param id: The ID of the object to find :type id: string :returns: Found Object or None """ # nothing to do here if not id: if default is not _marker: return default fail("get_object_by_id requires ID as first argument; got {} instead". format(id)) # we defined the portal object UID to be '0':: if id == '0': return api.get_portal() # we try to find the object with both catalogs pc = api.get_portal_catalog() # uc = api.get_tool("uid_catalog") # try to find the object with the reference catalog first brains = pc(id=id) if brains: return brains[0].getObject() # try to find the object with the portal catalog res = pc(ID=id) if not res: if default is not _marker: return default fail("No object found for UID {}".format(id)) return api.get_object(res[0])
def create_project(container, portal_type, **data): """ Create a project via API """ container = get_object(container) title = data.get("title", "") if not title: fail(404, "Title is required.") obj = bika_create_object(container, portal_type, **data) st_titles = data.get("SampleType", "") st_uids = [] if st_titles and type(st_titles) is list: for st_title in st_titles: brains = search(portal_type="SampleType", title=st_title) if brains: st_uids.append(brains[0].UID) if st_uids: obj.setSampleType(st_uids) return obj
def create_items(portal_type=None, uid=None, endpoint=None, **kw): """ create items 1. If the uid is given, get the object and create the content in there (assumed that it is folderish) 2. If the uid is 0, the target folder is assumed the portal. 3. If there is no uid given, the payload is checked for either a key - `parent_uid` specifies the *uid* of the target folder - `parent_path` specifies the *physical path* of the target folder """ # import pdb;pdb.set_trace() # disable CSRF req.disable_csrf_protection() # destination where to create the content container = uid and api.get_object_by_uid(uid) or None # extract the data from the request records = req.get_request_data() results = [] for record in records: # get the portal_type if portal_type is None: # try to fetch the portal type out of the request data portal_type = record.pop("portal_type", None) # check if it is allowed to create the portal_type if not is_creation_allowed(portal_type): fail(401, "Creation of '{}' is not allowed".format(portal_type)) if container is None: # find the container for content creation container = find_target_container(portal_type, record) # Check if we have a container and a portal_type if not all([container, portal_type]): fail(400, "Please provide a container path/uid and portal_type") # create the object and pass in the record data obj = create_object(container, portal_type, **record) if type(obj) is list: results.extend(obj) else: results.append(obj) if not results: fail(400, "No Objects could be created") return make_items_for(results, endpoint=endpoint)
def update_object_with_data(content, record): """Update the content with the record data :param content: A single folderish catalog brain or content object :type content: ATContentType/DexterityContentType/CatalogBrain :param record: The data to update :type record: dict :returns: The updated content object :rtype: object :raises: APIError, :class:`~plone.jsonapi.routes.exceptions.APIError` """ # ensure we have a full content object content = get_object(content) # get the proper data manager dm = IDataManager(content) if dm is None: fail(400, "Update for this object is not allowed") if content.portal_type == 'Sample': content = update_sample(content, record) record = u.omit(record, "SampleType", "StorageLocation") # Iterate through record items for k, v in record.items(): try: success = dm.set(k, v, **record) #starting point except Unauthorized: fail(401, "Not allowed to set the field '%s'" % k) except ValueError, exc: fail(400, str(exc)) if not success: logger.warn("update_object_with_data::skipping key=%r", k) continue logger.debug("update_object_with_data::field %r updated", k)
def create_sample(container, **data): """ create a sample from here that doesnt go via api create :param container: :param data: :return: Sample object """ container = get_object(container) request = req.get_request() # we need to resolve the SampleType to a full object sample_type = data.get("SampleType", None) if sample_type is None: fail(400, "Please provide a SampleType") # TODO We should handle the same values as in the DataManager for this field # (UID, path, objects, dictionaries ...) sample_type_results = search(portal_type="SampleType", title=sample_type) if not sample_type_results: sample_type_results = search(portal_type="SampleType", uid=sample_type) # StorageLocation storage_location = data.get("StorageLocation", None) if storage_location is None: fail(400, "Please provide a StorageLocation") linked_sample_list = search(portal_type="Sample", title=data.get('LinkedSample', '')) linked_sample = linked_sample_list and linked_sample_list[0].getObject( ) or None try: volume = str(data.get('Volume')) float_volume = float(volume) if not float_volume: fail(400, "Please provide a correct Volume") except: raise # TODO We should handle the same values as in the DataManager for this field # (UID, path, objects, dictionaries ...) storage_location_results = search(portal_type='StoragePosition', Title=storage_location) if not storage_location_results: storage_location_results = search(portal_type='StoragePosition', uid=storage_location) # set the values and call the create function values = { "title": data.get('title', ''), "description": data.get("description", ""), "Project": container, #because the container is in fact the project this sample belongs to. "AllowSharing": data.get('AllowSharing', 0), "StorageLocation": storage_location_results and get_object(storage_location_results[0]) or None, "SampleType": sample_type_results and get_object(sample_type_results[0]) or None, "SubjectID": data.get("SubjectID", ""), "Barcode": data.get("Barcode", ""), "Volume": volume, "Unit": data.get("Unit", ""), "LinkedSample": linked_sample, "DateCreated": data.get("DateCreated", ""), } api_source = data.get('APISource', None) if api_source: values['APISource'] = api_source return create_smp(container, request, values)
def create_storage(container, portal_type, **data): """ Create a storage unit and (un-)managed storage via API """ def set_inputs_into_schema(instance, temperature, department, unit_type): # Set field values across each object if possible schema = instance.Schema() if temperature and 'Temperature' in schema: instance.Schema()['Temperature'].set(instance, temperature) if department and 'Department' in schema: instance.Schema()['Department'].set(instance, department) if unit_type and 'UnitType' in schema: instance.Schema()['UnitType'].set(instance, unit_type) def set_storage_types(instance, storage_interfaces): schema = instance.Schema() if storage_interfaces and 'StorageTypes' in schema: instance.Schema()['StorageTypes'].set(instance, storage_interfaces) for storage_interface in storage_interfaces: inter = resolve(storage_interface) alsoProvides(instance, inter) container = get_object(container) # variables for storage unit department_title = data.get("department", "") temperature = data.get("temperature", "") unit_type = data.get("unit_type", "") department = None if container.portal_type == "StorageUnit": department = container.getDepartment() temperature = container.getTemperature() else: brains = search(portal_type="Department", title=department_title) if not brains: department = brains[0].getObject() # variables for managed storage if portal_type == "ManagedStorage": number_positions = data.get("number_positions", "") x_axis = data.get("x_axis", "") y_axis = data.get("y_axis", "") try: x_axis = x_axis and int(x_axis) or '' y_axis = y_axis and int(y_axis) or '' number_positions = number_positions and int(number_positions) or '' except ValueError: fail(401, "Number positions, X axis and Y axis must be integers.") if not number_positions or not x_axis or not y_axis: fail( 400, "Number positions, X axis and Y axis are required to create storage positions." ) # common variables prefix = data.get("prefix", "") leading_zeros = data.get("leading_zeros", "") if not prefix or not leading_zeros: fail( 400, "Prefix and leading_zeros are required to construct storage unit title and Id." ) number_items = data.get("number_items", "") try: number_items = number_items and int(number_items) or 1 except ValueError: fail(401, "Number items must be integer.") seq_start = data.get("seq_start", "") try: seq_start = seq_start and int(seq_start) or 1 except ValueError: fail(401, "Id sequence start must be integer.") sequence = range(seq_start, seq_start + number_items) units = [] for x in sequence: id_obj = prefix + '-' + str(x).zfill(len(leading_zeros) + 1) title_obj = prefix + ' ' + str(x).zfill(len(leading_zeros) + 1) instance = api.content.create(container=container, type=portal_type, id=id_obj, title=title_obj) if instance.portal_type == "StorageUnit": set_inputs_into_schema(instance, temperature, department, unit_type) elif instance.portal_type == "UnmanagedStorage": set_storage_types(instance, ["baobab.lims.interfaces.IStockItemStorage"]) elif instance.portal_type == "ManagedStorage": instance.setXAxis(x_axis) instance.setYAxis(y_axis) set_storage_types( instance, ["baobab.lims.interfaces.ISampleStorageLocation"]) positions = storage_positions(instance, number_positions) for position in positions: set_storage_types( position, ["baobab.lims.interfaces.ISampleStorageLocation"]) position.reindexObject() instance.reindexObject() units.append(instance) return units
success = dm.set(k, v, **record) #starting point except Unauthorized: fail(401, "Not allowed to set the field '%s'" % k) except ValueError, exc: fail(400, str(exc)) if not success: logger.warn("update_object_with_data::skipping key=%r", k) continue logger.debug("update_object_with_data::field %r updated", k) # Validate the entire content object invalid = validate_object(content, record) if invalid: fail(400, u.to_json(invalid)) # do a wf transition if record.get("transition", None): t = record.get("transition") logger.debug(">>> Do Transition '%s' for Object %s", t, content.getId()) do_transition_for(content, t) # reindex the object content.reindexObject() return content # TODO: MOVE TO BAOBAB (QUINTON) def update_sample(content, record):