def set(self, instance, value, **kw): """ Set Analyses to an AR :param instance: Analysis Request :param value: Single AS UID or a list of dictionaries containing AS UIDs :param kw: Additional keyword parameters passed to the field """ if not isinstance(value, (list, tuple)): value = [value] uids = [] for item in value: uid = None if isinstance(item, dict): uid = item.get("uid") if api.is_uid(value): uid = item if uid is None: logger.warn("Could extract UID of value") continue uids.append(uid) analyses = map(api.get_object_by_uid, uids) self._set(instance, analyses, **kw)
def get_object_by_record(record): """Find an object by a given record Inspects request the record to locate an object :param record: A dictionary representation of an object :type record: dict :returns: Found Object or None :rtype: object """ # nothing to do here if not record: return None if record.get("uid"): return get_object_by_uid(record["uid"]) if record.get("path"): return get_object_by_path(record["path"]) if record.get("parent_path") and record.get("id"): path = "/".join([record["parent_path"], record["id"]]) return get_object_by_path(path) logger.warn("get_object_by_record::No object found! record='%r'" % record) return None
def _set(self, instance, value, **kw): """Set the value of the field """ logger.debug("DexterityFieldManager::set: value=%r" % value) # Check if the field is read only if self.field.readonly: raise Unauthorized("Field is read only") fieldname = self.get_field_name() # id fields take only strings if fieldname == "id": value = str(value) try: # Validate self.field.validate(value) # TODO: Check security on the field level return self.field.set(instance, value) except WrongType: logger.warn("WrongType: Field={} Value={}".format( self.field, value)) except: # noqa logger.warn("Unknown Exception: Field={} Value={}".format( self.field, value))
def get_keyword_query(self, **kw): """Generates a query from the given keywords. Only known indexes make it into the generated query. :returns: Catalog query :rtype: dict """ query = dict() # Only known indexes get observed indexes = self.catalog.get_indexes() # Handle additional keyword parameters for k, v in kw.iteritems(): # handle uid in keywords if k.lower() == "uid": k = "UID" # handle portal_type in keywords if k.lower() == "portal_type": if v: v = _.to_list(v) if k not in indexes: logger.warn("Skipping unknown keyword parameter '%s=%s'" % (k, v)) continue if v is None: logger.warn("Skip None value in kw parameter '%s=%s'" % (k, v)) continue logger.debug("Adding '%s=%s' to query" % (k, v)) query[k] = v return query
def get_sort_on(allowed_indexes=None): """ returns the 'sort_on' from the request """ sort_on = get("sort_on") if allowed_indexes and sort_on not in allowed_indexes: logger.warn("Index '{}' is not in allowed_indexes".format(sort_on)) return None return sort_on
def disable_csrf_protection(): """ disables the CSRF protection https://pypi.python.org/pypi/plone.protect """ if not HAS_PLONE_PROTECT: logger.warn( "Can not disable CSRF protection – please install plone.protect") return False request = get_request() interface.alsoProvides(request, IDisableCSRFProtection) return True
def set(self, instance, value, **kw): """Converts the value into a DateTime object before setting. """ try: value = DateTime(value) except SyntaxError: logger.warn( "Value '{}' is not a valid DateTime string".format(value)) return False self._set(instance, value, **kw)
def json_data(self, instance, default=None): """Get a JSON compatible value """ value = self.get(instance) out = [] for rel in value: if rel.isBroken(): logger.warn("Skipping broken relation {}".format(repr(rel))) continue obj = rel.to_object out.append(api.get_url_info(obj)) return out
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) # Look for an update-specific adapter for this object adapter = queryAdapter(content, IUpdate) if adapter: # Use the adapter to update the object logger.info("Delegating 'update' operation of '{}'".format( api.get_path(content))) adapter.update_object(**record) else: # Fall-back to default update machinery # get the proper data manager dm = IDataManager(content) if dm is None: fail(400, "Update for this object is not allowed") # Bail-out non-update-able fields purged_records = copy.deepcopy(record) map(lambda key: purged_records.pop(key, None), SKIP_UPDATE_FIELDS) # Iterate through record items for k, v in purged_records.items(): try: success = dm.set(k, v, **record) 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_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 senaite LIMS generates a proper one id = data.pop("id") logger.warn("Passed in ID '{}' omitted! Senaite LIMS " "generates a proper ID for you".format(id)) try: # Is there any adapter registered to handle the creation of this type? adapter = queryAdapter(container, ICreate, name=portal_type) if adapter and adapter.is_creation_delegated(): logger.info("Delegating 'create' operation of '{}' in '{}'".format( portal_type, api.get_path(container))) return adapter.create_object(**data) # Special case for ARs # => return immediately w/o update if portal_type == "AnalysisRequest": obj = create_analysisrequest(container, **data) # Omit values which are already set through the helper data = u.omit(data, "SampleType", "Analyses") # Set the container as the client, as the AR lives in it data["Client"] = container return obj # Standard content creation else: # we want just a minimun viable object and set the data later obj = api.create(container, portal_type) # obj = api.create(container, portal_type, **data) except Unauthorized: fail(401, "You are not allowed to create this content") # Update the object with the given data, but omit the id try: update_object_with_data(obj, data) except APIError: # Failure in creation process, delete the invalid object # NOTE: We bypass the permission checks container._delObject(obj.id) # reraise the error raise return obj
def url_for(endpoint, default=DEFAULT_ENDPOINT, **values): """Looks up the API URL for the given endpoint :param endpoint: The name of the registered route (aka endpoint) :type endpoint: string :returns: External URL for this endpoint :rtype: string/None """ try: return router.url_for(endpoint, force_external=True, values=values) except Exception: logger.warn("Could not build API URL for endpoint '%s'. " "No route provider registered?" % endpoint) # build generic API URL return router.url_for(default, force_external=True, values=values)
def resource_to_portal_type(resource): """Converts a resource to a portal type :param resource: Resource name as it is used in the content route :type name: string :returns: Portal type name :rtype: string """ if resource is None: return None resource_mapping = get_resource_mapping() portal_type = resource_mapping.get(resource.lower()) if portal_type is None: logger.warn("Could not map the resource '{}' " "to any known portal type".format(resource)) return portal_type
def get_user_info(user): """Get the user information """ user = api.get_user(user) current = api.get_current_user() if api.is_anonymous(): return { "username": current.getUserName(), "authenticated": False, "roles": current.getRoles(), "api_url": api.url_for("senaite.jsonapi.v1.users", username="******"), } # nothing to do if user is None: logger.warn("No user found for {}".format(user)) return None # plone user pu = user.getUser() info = { "username": user.getUserName(), "roles": user.getRoles(), "groups": pu.getGroups(), "authenticated": current == user, "api_url": api.url_for("senaite.jsonapi.v1.users", username=user.getId()), } for k, v in api.get_user_properties(user).items(): if api.is_date(v): v = api.to_iso_date(v) if not api.is_json_serializable(v): logger.warn( "User property '{}' is not JSON serializable".format(k)) continue info[k] = v return info
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 senaite LIMS generates a proper one id = data.pop("id") logger.warn("Passed in ID '{}' omitted! Senaite LIMS " "generates a proper ID for you".format(id)) try: # Special case for ARs # => return immediately w/o update if portal_type == "AnalysisRequest": obj = create_analysisrequest(container, **data) # Omit values which are already set through the helper data = u.omit(data, "SampleType", "Analyses") # Set the container as the client, as the AR lives in it data["Client"] = container # Standard content creation else: # we want just a minimun viable object and set the data later obj = api.create(container, portal_type) # obj = api.create(container, portal_type, **data) except Unauthorized: fail(401, "You are not allowed to create this content") # Update the object with the given data, but omit the id try: update_object_with_data(obj, data) except APIError: # Failure in creation process, delete the invalid object container.manage_delObjects(obj.id) # reraise the error raise return obj
def to_json_value(obj, fieldname, value=_marker, default=None): """JSON save value encoding :param obj: Content object :type obj: ATContentType/DexterityContentType :param fieldname: Schema name of the field :type fieldname: str/unicode :param value: The field value :type value: depends on the field type :returns: JSON encoded field value :rtype: field dependent """ # This function bridges the value of the field to a probably more complex # JSON structure to return to the client. # extract the value from the object if omitted if value is _marker: value = IDataManager(obj).json_data(fieldname) # convert objects if isinstance(value, ImplicitAcquisitionWrapper): return get_url_info(value) # check if the value is callable if callable(value): value = value() # convert dates if is_date(value): return to_iso_date(value) # check if the value is JSON serializable if not is_json_serializable(value): logger.warn("Output {} is not JSON serializable".format(repr(value))) return default return value
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") # Iterate through record items for k, v in record.items(): try: success = dm.set(k, v, **record) 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 get_workflow_info(brain_or_object, endpoint=None): """Generate workflow information of the assigned workflows :param brain_or_object: A single catalog brain or content object :type brain_or_object: ATContentType/DexterityContentType/CatalogBrain :param endpoint: The named URL endpoint for the root of the items :type endpoint: str/unicode :returns: Workflows info :rtype: dict """ # ensure we have a full content object obj = get_object(brain_or_object) # get the portal workflow tool wf_tool = get_tool("portal_workflow") # the assigned workflows of this object workflows = wf_tool.getWorkflowsFor(obj) # no worfkflows assigned -> return if not workflows: return [] def to_transition_info(transition): """ return the transition information """ return { "title": transition["title"], "value": transition["id"], "display": transition["description"], "url": transition["url"], } def to_review_history_info(review_history): """ return the transition information """ converted = DT2dt(review_history.get('time')).\ strftime("%Y-%m-%d %H:%M:%S") review_history['time'] = converted return review_history out = [] for workflow in workflows: # get the status info of the current state (dictionary) info = wf_tool.getStatusOf(workflow.getId(), obj) if info is None: continue # get the current review_status review_state = info.get("review_state", None) inactive_state = info.get("inactive_state", None) cancellation_state = info.get("cancellation_state", None) worksheetanalysis_review_state = info.get( "worksheetanalysis_review_state", None) state = review_state or \ inactive_state or \ cancellation_state or \ worksheetanalysis_review_state if state is None: logger.warn("No state variable found for {} -> {}".format( repr(obj), info)) continue # get the wf status object status_info = workflow.states[state] # get the title of the current status status = status_info.title # get the transition informations transitions = map(to_transition_info, wf_tool.getTransitionsFor(obj)) # get the review history rh = map(to_review_history_info, workflow.getInfoFor(obj, 'review_history', '')) out.append({ "workflow": workflow.getId(), "status": status, "review_state": state, "transitions": transitions, "review_history": rh, }) return {"workflow_info": out}
def get_info(brain_or_object, endpoint=None, complete=False): """Extract the data from the catalog brain or object :param brain_or_object: A single catalog brain or content object :type brain_or_object: ATContentType/DexterityContentType/CatalogBrain :param endpoint: The named URL endpoint for the root of the items :type endpoint: str/unicode :param complete: Flag to wake up the object and fetch all data :type complete: bool :returns: Data mapping for the object/catalog brain :rtype: dict """ # also extract the brain data for objects if not is_brain(brain_or_object): brain_or_object = get_brain(brain_or_object) if brain_or_object is None: logger.warn( "Couldn't find/fetch brain of {}".format(brain_or_object)) return {} complete = True # When querying uid catalog we have to be sure that we skip the objects # used to relate two or more objects if is_relationship_object(brain_or_object): logger.warn("Skipping relationship object {}".format( repr(brain_or_object))) return {} # extract the data from the initial object with the proper adapter info = IInfo(brain_or_object).to_dict() # update with url info (always included) url_info = get_url_info(brain_or_object, endpoint) info.update(url_info) # include the parent url info parent = get_parent_info(brain_or_object) info.update(parent) # add the complete data of the object if requested # -> requires to wake up the object if it is a catalog brain if complete: # ensure we have a full content object obj = api.get_object(brain_or_object) # get the compatible adapter adapter = IInfo(obj) # update the data set with the complete information info.update(adapter.to_dict()) # update the data set with the workflow information # -> only possible if `?complete=yes&workflow=yes` if req.get_workflow(False): info.update(get_workflow_info(obj)) # # add sharing data if the user requested it # # -> only possible if `?complete=yes` # if req.get_sharing(False): # sharing = get_sharing_info(obj) # info.update({"sharing": sharing}) return info
def set(self, name, value, **kw): """Setter is not used for catalog brains """ logger.warn("Setting is not allowed on catalog brains")
def set(self, instance, value, **kw): """Not applicable for Computed Fields """ logger.warn("Setting is not allowed for computed fields")