コード例 #1
0
def post_install(portal_setup):
    """Runs after the last import step of the *default* profile
    This handler is registered as a *post_handler* in the generic setup profile
    :param portal_setup: SetupTool
    """
    logger.info("{} install handler [BEGIN]".format(PRODUCT_NAME.upper()))
    context = portal_setup._getImportContext(PROFILE_ID)
    portal = context.getSite()  # noqa

    # Setup catalogs
    setup_catalogs(portal)

    # Reindex new content types
    reindex_new_content_types(portal)

    # Setup ID Formatting for Storage content types
    setup_id_formatting(portal)

    # Hide actions
    hide_actions(portal)

    # Migrate "classic" storage locations
    migrate_storage_locations(portal)

    # Injects "store" and "recover" transitions into senaite's workflow
    setup_workflows(portal)

    logger.info("{} install handler [DONE]".format(PRODUCT_NAME.upper()))
コード例 #2
0
def hide_action(folder, action_id):
    logger.info("Hiding {} from {} ...".format(action_id, folder.id))
    if action_id not in folder:
        logger.info("{} not found in {} [SKIP]".format(action_id, folder.id))
        return

    item = folder[action_id]
    logger.info("Hide {} ({}) from nav bar".format(action_id, item.Title()))
    item.setExcludeFromNav(True)

    def get_action_index(action_id):
        for n, action in enumerate(cp.listActions()):
            if action.getId() == action_id:
                return n
        return -1

    logger.info("Hide {} from control_panel".format(action_id, item.Title()))
    cp = api.get_tool("portal_controlpanel")
    action_index = get_action_index(action_id)
    if (action_index == -1):
        logger.info("{}  not found in control_panel [SKIP]".format(cp.id))
        return

    actions = cp._cloneActions()
    del actions[action_index]
    cp._actions = tuple(actions)
    cp._p_changed = 1
コード例 #3
0
def hide_actions(portal):
    """Excludes actions from both navigation portlet and from control_panel
    """
    logger.info("Hiding actions ...")
    for action_id, folder_id in ACTIONS_TO_HIDE:
        if folder_id and folder_id not in portal:
            logger.info("{} not found in portal [SKIP]".format(folder_id))
            continue
        folder = folder_id and portal[folder_id] or portal
        hide_action(folder, action_id)
コード例 #4
0
def create_test_data(portal):
    """Populates with storage-like test data
    """
    if not CREATE_TEST_DATA:
        return
    logger.info("Creating test data ...")
    facilities = portal.senaite_storage
    if len(facilities.objectValues()) > 0:
        logger.info("There are facilities created already [SKIP]")
        return

    def get_random(min, max):
        if not CREATE_TEST_DATA_RANDOM:
            return min
        return int(round(random.uniform(min, max)))

    # Facilities
    for x in range(get_random(3, 8)):
        facility = api.create(
            facilities,
            "StorageFacility",
            title="Storage facility {:02d}".format(x + 1),
            Phone="123456789",
            EmailAddress="storage{:02d}@example.com".format(x + 1),
            PhysicalAddress={
                "address": "Av. Via Augusta 15 - 25",
                "city": "Sant Cugat del Valles",
                "zip": "08174",
                "state": "",
                "country": "Spain",
            })

        # Fridges
        for i in range(get_random(2, 5)):
            container = api.create(facility,
                                   "StorageContainer",
                                   title="Fridge {:02d}".format(i + 1),
                                   Rows=get_random(4, 8),
                                   Columns=get_random(4, 6))

            # Racks
            for j in range(get_random(4, container.get_capacity())):
                rack = api.create(container,
                                  "StorageContainer",
                                  title="Rack {:02d}".format(j + 1),
                                  Rows=get_random(3, 4),
                                  Columns=get_random(2, 3))

                # Boxes
                for k in range(get_random(2, rack.get_capacity())):
                    box = api.create(rack,
                                     "StorageSamplesContainer",
                                     title="Sample box {:02d}".format(k + 1),
                                     Rows=get_random(5, 10),
                                     Columns=get_random(5, 10))
コード例 #5
0
def reindex_new_content_types(portal):
    """Setup new content types"""
    logger.info("*** Reindex new content types ***")

    # Index objects - Importing through GenericSetup doesn't
    for obj_id, folder_id in NEW_CONTENT_TYPES:
        folder = folder_id and portal[folder_id] or portal
        logger.info("Reindexing {}".format(obj_id))
        obj = folder[obj_id]
        obj.unmarkCreationFlag()
        obj.reindexObject()
コード例 #6
0
    def __call__(self):
        form = self.request.form

        # Form submit toggle
        form_submitted = form.get("submitted", False)
        form_store = form.get("button_store", False)
        form_cancel = form.get("button_cancel", False)

        # Get the container
        container = self.get_container()
        if not container:
            return self.redirect(message=_s("No items selected"),
                                 level="warning")
        if not IStorageSamplesContainer.providedBy(container):
            logger.warn("Not a samples container: {}").format(repr(container))
            self.redirect(redirect_url=self.get_next_url())

        # If container is full, there is no way to add more samples there
        if container.is_full():
            message = _("Cannot store samples. Samples container {} is full")
            return self.redirect(message=message.format(api.get_id(container)),
                                 level="warning")

        # Handle store
        if form_submitted and form_store:
            alpha_position = form.get("position")
            sample_uid = form.get("sample_uid")
            if not alpha_position or not api.is_uid(sample_uid):
                message = _("No position or not valid sample selected")
                return self.redirect(message=message)

            sample = api.get_object(sample_uid)
            logger.info("Storing sample {} in {} at {}".format(
                api.get_id(sample), api.get_id(container), alpha_position))

            # Store
            position = container.alpha_to_position(alpha_position)
            if container.add_object_at(sample, position[0], position[1]):
                message = _("Stored sample {} at position {}").format(
                    api.get_id(sample), alpha_position)
                if container.is_full():
                    return self.redirect(redirect_url=self.get_next_url())
                return self.redirect(redirect_url=self.get_fallback_url(),
                                     message=message)

        # Handle cancel
        if form_submitted and form_cancel:
            return self.redirect(message=_("Sample storing canceled"))

        return self.template()
コード例 #7
0
def setup_id_formatting(portal, format=None):
    """Setup default ID Formatting for storage content types
    """
    if not format:
        logger.info("Setting up ID formatting ...")
        for formatting in ID_FORMATTING:
            setup_id_formatting(portal, format=formatting)
        return

    bs = portal.bika_setup
    p_type = format.get("portal_type", None)
    if not p_type:
        return
    id_map = bs.getIDFormatting()
    id_format = filter(lambda id: id.get("portal_type", "") == p_type, id_map)
    if id_format:
        logger.info("ID Format for {} already set: '{}' [SKIP]".format(
            p_type, id_format[0]["form"]))
        return

    form = format.get("form", "")
    if not form:
        logger.info("Param 'form' for portal type {} not set [SKIP")
        return

    logger.info("Applying format '{}' for {}".format(form, p_type))
    ids = list()
    for record in id_map:
        if record.get('portal_type', '') == p_type:
            continue
        ids.append(record)
    ids.append(format)
    bs.setIDFormatting(ids)
コード例 #8
0
def pre_install(portal_setup):
    """Runs before the first import step of the *default* profile
    This handler is registered as a *pre_handler* in the generic setup profile
    :param portal_setup: SetupTool
    """
    logger.info("{} pre-install handler [BEGIN]".format(PRODUCT_NAME.upper()))
    context = portal_setup._getImportContext(PROFILE_ID)
    portal = context.getSite()  # noqa

    # Only install senaite.lims once!
    qi = portal.portal_quickinstaller
    if not qi.isProductInstalled("senaite.lims"):
        portal_setup.runAllImportStepsFromProfile("profile-senaite.lims:default")

    logger.info("{} pre-install handler [DONE]".format(PRODUCT_NAME.upper()))
コード例 #9
0
def update_workflow(portal, workflow_id, settings):
    """Injects 'store' and 'recover' transitions into workflow
    """
    logger.info("Updating workflow '{}' ...".format(workflow_id))
    wf_tool = api.get_tool("portal_workflow")
    workflow = wf_tool.getWorkflowById(workflow_id)
    if not workflow:
        logger.warn("Workflow '{}' not found [SKIP]".format(workflow_id))
    states = settings.get("states", {})
    for state_id, values in states.items():
        update_workflow_state(workflow, state_id, values)

    transitions = settings.get("transitions", {})
    for transition_id, values in transitions.items():
        update_workflow_transition(workflow, transition_id, values)
コード例 #10
0
ファイル: api.py プロジェクト: mstroehle/senaite.storage
def create_partition_for_storage(sample_obj_brain_or_uid):
    """Creates an empty partition suitable for storage from the given sample
    If the sample passed in is a partition, generates a copy of the same
    partition without analyses set, but keeping the same parent.
    If the sample passed in is a primary sample, generates a new partition, but
    without analyses
    """
    sample = get_object(sample_obj_brain_or_uid)
    logger.info("Creating partition for storage: {}".format(get_id(sample)))

    PARTITION_SKIP_FIELDS = [
        "Analyses",
        "Attachment",
        "Client",
        "Profile",
        "Profiles",
        "RejectionReasons",
        "Remarks",
        "ResultsInterpretation",
        "ResultsInterpretationDepts",
        "Sample",
        "Template",
        "creation_date",
        "id",
        "modification_date",
        "ParentAnalysisRequest",
    ]
    primary = sample
    if sample.isPartition():
        primary = sample.getParentAnalysisRequest()

    # Set the basic fields for the Partition
    record = {
        "ParentAnalysisRequest": get_uid(primary),
    }

    # Copy all fields
    for fieldname, field in get_fields(sample).items():
        if field.type == "computed":
            logger.info("Skipping computed field {}".format(fieldname))
            continue
        if fieldname in PARTITION_SKIP_FIELDS:
            logger.info("Skipping field {}".format(fieldname))
            continue
        fieldvalue = field.get(sample)
        record[fieldname] = fieldvalue
        logger.info("Update record '{}': {}".format(fieldname,
                                                    repr(fieldvalue)))

    client = sample.getClient()
    partition = crar(client, request={}, values=record)

    # Force status to "stored"
    wf.changeWorkflowState(partition, "bika_ar_workflow", "stored")

    # Reindex the primary AR
    primary.reindexObject(idxs=["isRootAncestor"])
    return partition
コード例 #11
0
    def __call__(self):
        form = self.request.form

        # Form submit toggle
        form_submitted = form.get("submitted", False)
        form_store = form.get("button_store", False)
        form_cancel = form.get("button_cancel", False)

        objs = self.get_objects_from_request()

        # No items selected
        if not objs:
            return self.redirect(message=_("No items selected"),
                                 level="warning")

        # Handle store
        if form_submitted and form_store:
            samples = []
            for sample in form.get("samples", []):
                sample_uid = sample.get("uid")
                container_uid = sample.get("container_uid")
                alpha_position = sample.get("container_position")
                if not sample_uid or not container_uid or not alpha_position:
                    continue

                sample_obj = self.get_object_by_uid(sample_uid)
                container = self.get_object_by_uid(container_uid)
                logger.info("Storing sample {} in {}".format(
                    sample_obj.getId(), container.getId()))
                # Store
                position = container.alpha_to_position(alpha_position)
                stored = container.add_object_at(sample_obj, position[0],
                                                 position[1])
                if stored:
                    stored = container.get_object_at(position[0], position[1])
                    samples.append(stored)

            message = _s("Stored {} samples: {}".format(
                len(samples), ", ".join(map(api.get_title, samples))))
            return self.redirect(message=message)

        # Handle cancel
        if form_submitted and form_cancel:
            return self.redirect(message=_s("Sample storing canceled"))

        return self.template()
コード例 #12
0
def update_workflow_transition(workflow, transition_id, settings):
    logger.info("Updating workflow '{}', transition: '{}'"
                .format(workflow.id, transition_id))
    if transition_id not in workflow.transitions:
        workflow.transitions.addTransition(transition_id)
    transition = workflow.transitions.get(transition_id)
    transition.setProperties(
        title=settings.get("title"),
        new_state_id=settings.get("new_state"),
        after_script_name=settings.get("after_script", ""),
        actbox_name=settings.get("action", settings.get("title"))
    )
    guard = transition.guard or Guard()
    guard_props = {"guard_permissions": "",
                   "guard_roles": "",
                   "guard_expr": ""}
    guard_props = settings.get("guard", guard_props)
    guard.changeFromProperties(guard_props)
    transition.guard = guard
コード例 #13
0
ファイル: v01_00_001.py プロジェクト: senaite/senaite.storage
def upgrade(tool):
    portal = tool.aq_inner.aq_parent
    setup = portal.portal_setup
    ut = UpgradeUtils(portal)
    ver_from = ut.getInstalledVersion(PRODUCT_NAME)

    if ut.isOlderVersion(PRODUCT_NAME, version):
        logger.info("Skipping upgrade of {0}: {1} > {2}".format(
            PRODUCT_NAME, ver_from, version))
        return True

    logger.info("Upgrading {0}: {1} -> {2}".format(PRODUCT_NAME, ver_from,
                                                   version))

    # -------- ADD YOUR STUFF BELOW --------

    # Compatibility with "Fix error when creating a partition as analyst user",
    # that reimports the worklflow tool and do an updateRoleMappings for Samples
    # that are in "received" status. Since senaite.storage "overrides" the
    # definition the sample_workflow, there is the need to setup workflow again
    # https://github.com/senaite/senaite.core/pull/1525
    setup_workflows(portal)
    update_wf_received_samples(portal)

    logger.info("{0} upgraded to version {1}".format(PRODUCT_NAME, version))
    return True
コード例 #14
0
def update_workflow_state(workflow, status_id, settings):
    logger.info("Updating workflow '{}', status: '{}' ..."
                .format(workflow.id, status_id))

    # Create the status (if does not exist yet)
    new_status = workflow.states.get(status_id)
    if not new_status:
        workflow.states.addState(status_id)
        new_status = workflow.states.get(status_id)

    # Set basic info (title, description, etc.)
    new_status.title = settings.get("title", new_status.title)
    new_status.description = settings.get("description", new_status.description)

    # Set transitions
    trans = settings.get("transitions", ())
    if settings.get("preserve_transitions", False):
        trans = tuple(set(new_status.transitions+trans))
    new_status.transitions = trans

    # Set permissions
    update_workflow_state_permissions(workflow, new_status, settings)
コード例 #15
0
def migrate_storage_locations(portal):
    """Migrates classic StorageLocation objects to StorageSamplesContainer
    """
    logger.info("Migrating classic Storage Locations ...")
    query = dict(portal_type="StorageLocation")
    brains = api.search(query, "portal_catalog")
    if not brains:
        logger.info("No Storage Locations found [SKIP]")
        return

    total = len(brains)
    for num, brain in enumerate(brains):
        if num % 100 == 0:
            logger.info("Migrating Storage Locations: {}/{}".format(num, total))
        object = api.get_object(brain)
コード例 #16
0
def update_workflow_state_permissions(workflow, status, settings):
    # Copy permissions from another state?
    permissions_copy_from = settings.get("permissions_copy_from", None)
    if permissions_copy_from:
        logger.info("Copying permissions from '{}' to '{}' ...".format(
            permissions_copy_from, status.id))
        copy_from_state = workflow.states.get(permissions_copy_from)
        if not copy_from_state:
            logger.info("State '{}' not found [SKIP]".format(copy_from_state))
        else:
            for perm_id in copy_from_state.permissions:
                perm_info = copy_from_state.getPermissionInfo(perm_id)
                acquired = perm_info.get("acquired", 1)
                roles = perm_info.get("roles", acquired and [] or ())
                logger.info(
                    "Setting permission '{}' (acquired={}): '{}'".format(
                        perm_id, repr(acquired), ', '.join(roles)))
                status.setPermission(perm_id, acquired, roles)

    # Override permissions
    logger.info("Overriding permissions for '{}' ...".format(status.id))
    state_permissions = settings.get('permissions', {})
    if not state_permissions:
        logger.info("No permissions set for '{}' [SKIP]".format(status.id))
        return
    for permission_id, roles in state_permissions.items():
        state_roles = roles and roles or ()
        if isinstance(state_roles, tuple):
            acq = 0
        else:
            acq = 1
        logger.info("Setting permission '{}' (acquired={}): '{}'".format(
            permission_id, repr(acq), ', '.join(state_roles)))
        status.setPermission(permission_id, acq, state_roles)
コード例 #17
0
def setup_workflows(portal):
    """Injects 'store' and 'recover' transitions into workflow
    """
    logger.info("Setup storage workflow ...")
    for wf_id, settings in WORKFLOWS_TO_UPDATE.items():
        update_workflow(portal, wf_id, settings)
コード例 #18
0
def setup_catalogs(portal):
    """Setup Plone catalogs
    """
    logger.info("Setup Catalogs ...")

    # Setup catalogs by type
    for type_name, catalogs in CATALOGS_BY_TYPE:
        at = api.get_tool("archetype_tool")
        # get the current registered catalogs
        current_catalogs = at.getCatalogsByType(type_name)
        # get the desired catalogs this type should be in
        desired_catalogs = map(api.get_tool, catalogs)
        # check if the catalogs changed for this portal_type
        if set(desired_catalogs).difference(current_catalogs):
            # fetch the brains to reindex
            brains = api.search({"portal_type": type_name})
            # updated the catalogs
            at.setCatalogsByType(type_name, catalogs)
            logger.info("Assign '%s' type to Catalogs %s" %
                        (type_name, catalogs))
            for brain in brains:
                obj = api.get_object(brain)
                logger.info("Reindexing '%s'" % repr(obj))
                obj.reindexObject()

    # Setup catalog indexes
    to_index = []
    for catalog, name, meta_type in INDEXES:
        c = api.get_tool(catalog)
        indexes = c.indexes()
        if name in indexes:
            logger.info("Index '%s' already in Catalog [SKIP]" % name)
            continue

        logger.info("Adding Index '%s' for field '%s' to catalog '%s" %
                    (meta_type, name, catalog))
        if meta_type == "ZCTextIndex":
            addZCTextIndex(c, name)
        else:
            c.addIndex(name, meta_type)
        to_index.append((c, name))
        logger.info("Added Index '%s' for field '%s' to catalog [DONE]" %
                    (meta_type, name))

    for catalog, name in to_index:
        logger.info("Indexing new index '%s' ..." % name)
        catalog.manage_reindexIndex(name)
        logger.info("Indexing new index '%s' [DONE]" % name)

    # Setup catalog metadata columns
    for catalog, name in COLUMNS:
        c = api.get_tool(catalog)
        if name not in c.schema():
            logger.info("Adding Column '%s' to catalog '%s' ..." %
                        (name, catalog))
            c.addColumn(name)
            logger.info("Added Column '%s' to catalog '%s' [DONE]" %
                        (name, catalog))
        else:
            logger.info("Column '%s' already in catalog '%s'  [SKIP]" %
                        (name, catalog))
            continue
コード例 #19
0
 def reindex_content_type(obj_id, folder):
     logger.info("Reindexing {}".format(obj_id))
     obj = folder[obj_id]
     obj.unmarkCreationFlag()
     obj.reindexObject()
コード例 #20
0
def update_workflow_state_permissions(workflow, status, settings):
    # Copy permissions from another state?
    permissions_copy_from = settings.get("permissions_copy_from", None)
    if permissions_copy_from:
        logger.info("Copying permissions from '{}' to '{}' ...".format(
            permissions_copy_from, status.id))
        copy_from_state = workflow.states.get(permissions_copy_from)
        if not copy_from_state:
            logger.info("State '{}' not found [SKIP]".format(copy_from_state))
        else:
            source_permissions = copy_from_state.permission_roles
            for perm_id, roles in source_permissions.items():
                logger.info("Setting permission '{}': '{}'".format(
                    perm_id, ', '.join(roles)))
                status.setPermission(perm_id, False, roles)

    # Override permissions
    logger.info("Overriding permissions for '{}' ...".format(status.id))
    state_permissions = settings.get('permissions', {})
    if not state_permissions:
        logger.info("No permissions set for '{}' [SKIP]".format(status.id))
        return
    for permission_id, roles in state_permissions.items():
        state_roles = roles and roles or ()
        logger.info("Setting permission '{}': '{}'".format(
            permission_id, ', '.join(state_roles)))
        status.setPermission(permission_id, False, state_roles)