Example #1
0
    def titleRaw(obj, record, cls=E):
        """Generate a title for a different record.

        This is fast title generation.
        No record object will be created.

        The title will be based on the fields in the record,
        and its formatting is assisted by the appropriate
        type class in `control.typ.types`.

        !!! hint
            If the record is  not "actual", its title will get a warning
            background color.
            See `control.db.Db.collect`.

        Parameters
        ----------
        obj: object
            Any object that has a table and context attribute, e.g. a table object
            or a record object
        record: dict
        cls: string, optional, `''`
            A CSS class to add to the outer element of the result
        """

        table = obj.table
        context = obj.context

        types = context.types
        typesObj = getattr(types, table, None)

        inActualCls = Record.inActualCls(obj, record)
        atts = dict(cls=f"{cls} {inActualCls}")

        return H.span(typesObj.title(record=record), **atts)
Example #2
0
    def widget(self, val):
        """Constructs and edit widget around for this type.

        Parameters
        ----------
        val: string
            The initial value for the widget.

        Returns
        -------
        string(html)
            Dependent on a batch of Javascript in `index.js`, look for `const widgets`.
        """

        atts = {}
        if self.pattern:
            atts[N.pattern] = self.pattern
        validationMsg = TypeBase.validationMsg(self.name)

        widgetElem = H.input(self.toEdit(val),
                             type=N.text,
                             cls="wvalue",
                             **atts)
        validationElem = H.span(E,
                                valmsg=validationMsg) if validationMsg else E
        return H.join([widgetElem, validationElem])
Example #3
0
    def tasks(self, table, kind=None):
        """Present the currently available tasks as buttons on the interface.

        !!! hint "easy comments"
            We also include a comment `<!-- task~!taskName:eid -->
            for the ease of testing.

        Parameters
        ----------
        table: string
            We must specify the table for which we want to present the
            tasks: contrib, assessment, or review.
        kind: string {`expert`, `final`}, optional `None`
            Only if we want review attributes

        Returns
        -------
        string(html)
        """

        uid = self.uid

        if not uid or table not in USER_TABLES:
            return E

        eid = list(self.info(table, N._id, kind=kind))[0]
        taskParts = []

        allowedTasks = sorted((task, taskInfo)
                              for (task, taskInfo) in TASKS.items()
                              if G(taskInfo, N.table) == table)
        justNow = now()

        for (task, taskInfo) in allowedTasks:
            permitted = self.permission(task, kind=kind)
            if not permitted:
                continue

            remaining = type(permitted) is timedelta and permitted
            taskUntil = E
            if remaining:
                remainingRep = datetime.toDisplay(justNow + remaining)
                taskUntil = H.span(f""" before {remainingRep}""", cls="datex")
            taskMsg = G(taskInfo, N.msg)
            taskCls = G(taskInfo, N.cls)

            taskPart = (H.a(
                [taskMsg, taskUntil],
                f"""/api/task/{task}/{eid}""",
                cls=f"large task {taskCls}",
            ) + f"""<!-- task!{task}:{eid} -->""")
            taskParts.append(taskPart)

        return H.join(taskParts)
    def title(self, *args, **kwargs):
        record = self.record
        critRecord = self.critRecord

        withEvidence = H.icon(
            N.missing if self.field(N.evidence).isBlank() else N.check)
        status = H.span(f"""evidence{NBSP}{withEvidence}""", cls="right small")
        seq = G(record, N.seq, default=Q)
        scoreRep = self.field(N.score).wrapBare()

        return H.span(
            [
                H.span([f"""{seq}{DOT}{NBSP}""",
                        critRecord.title()],
                       cls="col1"),
                H.span(scoreRep, cls="col2"),
                status,
            ],
            cls=f"centrytitle criteria",
        )
    def title(self, *args, **kwargs):
        uid = self.uid
        record = self.record

        creatorId = G(record, N.creator)

        youRep = f""" ({N.you})""" if creatorId == uid else E
        lastModified = G(record, N.modified)
        lastModifiedRep = (self.field(N.modified).value[-1].rsplit(
            DOT, maxsplit=1)[0] if lastModified else Qu)

        return H.span(f"""{lastModifiedRep}{youRep}""", cls=f"rentrytitle")
Example #6
0
    def statusOverview(self, table, kind=None):
        """Present the current status of a record on the interface.

        Parameters
        ----------
        table: string
            We must specify the kind of record for which we want to present the stage:
            contrib, assessment, or review.
        kind: string {`expert`, `final`}, optional `None`
            Only if we want review attributes

        Returns
        -------
        string(html)
        """

        (stage, stageDate, locked, done, frozen, score, eid) = self.info(
            table,
            N.stage,
            N.stageDate,
            N.locked,
            N.done,
            N.frozen,
            N.score,
            N._id,
            kind=kind,
        )
        stageInfo = G(STAGE_ATTS, stage)
        statusCls = G(stageInfo, N.cls)
        stageOn = (H.span(f""" on {datetime.toDisplay(stageDate)}""",
                          cls="date") if stageDate else E)
        statusMsg = H.span([G(stageInfo, N.msg) or E, stageOn],
                           cls=f"large status {statusCls}")
        lockedCls = N.locked if locked else E
        lockedMsg = (H.span(G(STATUS_REP, N.locked),
                            cls=f"large status {lockedCls}") if locked else E)
        doneCls = N.done if done else E
        doneMsg = (H.span(G(STATUS_REP, N.done), cls=f"large status {doneCls}")
                   if done else E)
        frozenCls = N.frozen if frozen else E
        frozenMsg = (H.span(G(STATUS_REP, N.frozen), cls="large status info")
                     if frozen else E)

        statusRep = f"<!-- stage:{stage} -->" + H.div(
            [statusMsg, lockedMsg, doneMsg, frozenMsg], cls=frozenCls)

        scorePart = E
        if table == N.assessment:
            scoreParts = presentScore(score, eid)
            scorePart = (H.span(scoreParts) if table == N.assessment else
                         (scoreParts[0] if scoreParts else E)
                         if table == N.contrib else E)

        return H.div([statusRep, scorePart], cls="workflow-line")
Example #7
0
    def toDisplay(self, val, markup=True):
        """Turns a real value into a HTML code for readonly display.

        Parameters
        ----------
        val: mixed
            A value of this type.

        Returns
        -------
        string(html)
            Possibly with nice formatting depending on the nature of the value.
        """

        if val is None:
            return QQ if markup else Qq
        valBare = H.he(self.normalize(str(val)))
        return H.span(valBare) if markup else valBare
    def wrap(self, *args, **kwargs):
        wfitem = self.wfitem
        if not wfitem:
            return super().wrap(*args, **kwargs)

        details = self.details

        (reviewer, ) = wfitem.info(N.assessment, N.reviewer)

        self.fetchDetails(
            N.reviewEntry,
            sortKey=lambda r: G(r, N.dateCreated, default=0),
        )
        (tableObj, records) = G(details, N.reviewEntry, default=(None, []))
        if not tableObj:
            return E

        byReviewer = {N.expert: E, N.final: E}

        for dest in (N.expert, N.final):
            byReviewer[dest] = self.wrapDetail(
                N.reviewEntry,
                filterFunc=lambda r: G(r, N.creator) == G(reviewer, dest),
                bodyMethod=N.compact,
                expanded=True,
                withProv=False,
                withN=False,
                inner=False,
            )

            if not byReviewer[dest]:
                byReviewer[dest] = H.span("""No review comment yet""",
                                          cls="info small")

        return H.div(
            [
                f"""<!-- begin reviewer {dest} -->""" + H.div(
                    [H.div(cap1(dest), cls="head"),
                     G(byReviewer, dest)],
                    cls=f"reviewentries {dest}",
                ) + f"""<!-- end reviewer {dest} -->""" for dest in reviewer
            ],
            cls="reviewers",
        )
Example #9
0
    def makeOptions(self):
        """Produce an options widget.

        The options are defined in web.yaml, under the key `options`.
        """

        options = self.options

        filterRep = [
            H.input(E, type=N.text, id="cfilter", placeholder="match title"),
        ]
        optionsRep = [
            H.span(
                [H.checkbox(name, trival=value),
                 G(G(OPTIONS, name), N.label)],
                cls=N.option,
            ) for (name, value) in options.items()
        ]

        return [("XXX", rep) for rep in filterRep + optionsRep]
Example #10
0
    def wrap(self):
        """Wrap it all up."""

        context = self.context
        auth = context.auth
        eppn = G(auth.user, N.eppn) if auth.authenticated() else N.public
        eppnRep = H.span(eppn, cls="mono")

        (identityRep, accessRep) = auth.credentials()
        login = (E if auth.authenticated() else (
            auth.wrapTestUsers() if auth.isDevel else H.
            a(G(LOGIN, N.text), G(LOGIN, N.url), cls="button small loginout")))
        logout = (H.join([
            H.a(G(LOGOUT, N.text),
                G(LOGOUT, N.url),
                cls="button small loginout"),
            H.a(
                G(SLOGOUT, N.text),
                G(SLOGOUT, N.url),
                cls="button small loginout",
                title=G(SLOGOUT, N.title),
            ),
        ]) if auth.authenticated() else E)
        techdoc = (H.a(
            G(TECH, N.text),
            G(TECH, N.url),
            target=N._blank,
            cls="button medium help",
            title=G(TECH, N.title),
        ))
        userhelp = (H.a(
            G(HELP, N.text),
            G(HELP, N.url),
            target=N._blank,
            cls="button medium help",
            title=G(HELP, N.title),
        ))
        return H.div(
            [
                H.div(
                    [
                        H.icon(N.devel) if auth.isDevel else E,
                        H.details(identityRep, eppnRep, "usereppn",
                                  cls="user"),
                        H.div(accessRep, cls="access"),
                        login,
                        logout,
                    ],
                    cls="headlinestart",
                ),
                H.div(
                    [
                        techdoc,
                        userhelp,
                    ],
                    cls="headlineend",
                ),
                H.img(
                    G(LOGO, N.src),
                    href=G(LOGO, N.url),
                    target=N._blank,
                    title=G(LOGO, N.text),
                    imgAtts=dict(height=G(LOGO, N.height)),
                    id="logo",
                ),
            ],
            cls="headline",
        )
Example #11
0
    def title(
        self,
        eid=None,
        record=None,
        markup=False,
        clickable=False,
        multiple=False,
        active=None,
        hideInActual=False,
        hideBlockedUsers=False,
    ):
        if record is None and eid is None:
            return (QQ, QQ) if markup else Qq

        table = self.name

        if record is None:
            context = self.context
            record = context.getItem(table, eid)

        titleStr = self.titleStr(record)
        titleHint = self.titleHint(record)

        if markup:
            if eid is None:
                eid = G(record, N._id)

            isActive = eid in (active or []) if multiple else eid == active
            baseCls = (
                ("button " if multiple or not isActive else "label ")
                if clickable
                else "tag "
            )
            activeCls = "active " if isActive else E
            inActualCls = self.inActualCls(record=record)
            hidden = (
                hideInActual
                and inActualCls
                and not isActive
                or hideBlockedUsers
                and table == N.user
                and not G(record, N.mayLogin)
            )
            if hidden:
                return (E, E)
            atts = dict(cls=f"{baseCls}{activeCls}medium {inActualCls}")
            if clickable and eid is not None:
                atts[N.eid] = str(eid)

            if titleHint:
                atts[N.title] = titleHint

            titleIcon = (
                (NBSP + H.icon(N.cross if isActive else N.add, cls="small"))
                if multiple
                else E
            )

            titleFormatted = H.span(
                [titleStr, titleIcon], lab=titleStr.lower(), **atts,
            )
            return (titleStr, titleFormatted)
        else:
            return titleStr
    def wrap(self, *args, **kwargs):
        wfitem = self.wfitem
        if not wfitem:
            return super().wrap(*args, **kwargs)

        (reviewer, reviewers) = wfitem.info(N.assessment, N.reviewer, N.reviewers)

        self.fetchDetails(N.criteriaEntry, sortKey=self.cEntrySort)

        criteriaPart = self.wrapDetail(N.criteriaEntry, bodyMethod=N.compact)

        self.fetchDetails(
            N.review, sortKey=lambda r: G(r, N.dateCreated, default=0),
        )

        byReviewer = {N.expert: E, N.final: E}

        for dest in (N.expert, N.final):
            byReviewer[dest] = self.wrapDetail(
                N.review,
                filterFunc=lambda r: G(r, N.creator) == G(reviewer, dest),
                bodyMethod=N.compact,
                withDetails=True,
                expanded=True,
                withProv=True,
                withN=False,
                inner=False,
            )

            if not byReviewer[dest]:
                byReviewer[dest] = H.span(
                    """No review decision yet""", cls="info small"
                )

        showEid = self.mustShow(N.review, kwargs)

        orphanedReviews = self.wrapDetail(
            N.review,
            filterFunc=lambda r: G(r, N.creator) not in reviewers,
            withProv=True,
            showEid=showEid,
        )

        reviewPart = H.div(
            [
                H.div(
                    [H.div(cap1(dest), cls="head"), G(byReviewer, dest)],
                    cls=f"reviews {dest}",
                )
                for dest in reviewer
            ],
            cls="reviewers",
        ) + (
            H.div(
                [
                    H.div(cap1(N.orphaned) + " " + N.reviews, cls="head"),
                    orphanedReviews,
                ],
            )
            if orphanedReviews
            else E
        )

        statusRep = wfitem.status(N.assessment)

        return H.div(
            [criteriaPart, statusRep, H.div(REVIEW_DECISION, cls="head"), reviewPart],
        )
Example #13
0
    def title(
        self,
        eid=None,
        record=None,
        markup=False,
        clickable=False,
        active=None,
        **kwargs,
    ):
        """Generate a custom title.

        Parameters
        ----------
        record: dict, optional, `None`
        eid: ObjectId, optional, `None`
        markup: boolean, optional, `False`
        clickable: boolean, optional, `False`
            If `True`, the title will be represented as a workflow task,
            otherwise as a workflow stage.
        active: string, optional, `None`
        **kwargs: dict
            Possible remaining parameters that might have been passed but are not
            relevant for this class.

        Returns
        -------
        string(html?)
            The title, possibly wrapped in HTML
        """

        if record is None and eid is None:
            return (QQ, QQ) if markup else Qq

        if record is None:
            context = self.context
            table = self.name
            record = context.getItem(table, eid)

        titleStr = self.titleStr(record)
        titleHint = self.titleHint(record)

        if markup:
            if eid is None:
                eid = G(record, N._id)

            isActive = eid == active
            baseCls = "task" if clickable else "status"
            activeCls = "active " if isActive else E
            extraCls = G(record, N.acro)
            inActualCls = self.inActualCls(record)
            atts = dict(
                cls=f"{baseCls} {extraCls} {activeCls} large {inActualCls}")
            if clickable and eid is not None:
                atts[N.eid] = str(eid)

            if titleHint:
                atts[N.title] = titleHint

            titleFormatted = H.span(titleStr, lab=titleStr.lower(), **atts)
            return (titleStr, titleFormatted)
        else:
            return titleStr
Example #14
0
    def deleteButton(self):
        """Show the delete button and/or the number of dependencies.

        Check the permissions in order to not show a delete button if the user
        cannot delete the record.

        Returns
        -------
        string(html)
        """

        mayDelete = self.mayDelete

        if not mayDelete:
            return E

        record = self.record
        table = self.table
        itemSingle = self.itemLabels[0]

        dependencies = self.getDependencies()

        nCas = G(dependencies, N.cascade, default=0)
        cascadeMsg = (H.span(
            f"""{nCas} detail record{E if nCas == 1 else S}""",
            title="""Detail records will be deleted with the master record""",
            cls="label small warning-o right",
        ) if nCas else E)
        cascadeMsgShort = (
            f""" and {nCas} dependent record{E if nCas == 1 else S}"""
            if nCas else E)

        nRef = G(dependencies, N.reference, default=0)

        if nRef:
            plural = E if nRef == 1 else S
            return H.span([
                H.icon(
                    N.chain,
                    cls="medium right",
                    title=
                    f"""Cannot delete because of {nRef} dependent record{plural}""",
                ),
                H.span(
                    f"""{nRef} dependent record{plural}""",
                    cls="label small warning-o right",
                ),
            ])

        if table in TO_MASTER:
            masterTable = G(TO_MASTER, table)
            masterId = G(record, masterTable)
        else:
            masterTable = None
            masterId = None

        url = (
            f"""/api/{table}/{N.delete}/{G(record, N._id)}"""
            if masterTable is None or masterId is None else
            f"""/api/{masterTable}/{masterId}/{table}/{N.delete}/{G(record, N._id)}"""
        )
        return H.span([
            cascadeMsg,
            H.iconx(
                N.delete,
                cls="medium right warning",
                deleteurl=url,
                title=f"""delete this {itemSingle}{cascadeMsgShort}""",
            ),
        ])
Example #15
0
 def toDisplay(self, val, markup=True):
     if val is None:
         return QQ if markup else Qq
     valBare = self.normalize(val.isoformat())
     return H.span(valBare) if markup else valBare
Example #16
0
    def wrap(self, openEid, action=None):
        """Wrap the list of records into HTML.

        action | selection
        --- | ---
        `my` | records that the current user has created or is an editor of
        `our` | records that the current user can edit, assess, review, or select
        `assess` | records that the current user is assessing
        `assign` | records that the current office user must assign to reviewers
        `reviewer` | records that the current user is reviewing
        `reviewdone` | records that the current user has reviewed
        `select` | records that the current national coordinator user can select

        Permissions will be checked before executing one of these list actions.
        See `control.table.Table.mayList`.

        !!! caution "Workflow restrictions"
            There might be additional restrictions on individual records
            due to workflow. Some records may not be readable.
            They will be filtered out.

        !!! note
            Whether records are presented  in an opened or closed state
            depends onn how the user has last left them.
            This information is  stored in `localStorage` inn the browser.
            However, if the last action was the creation of a nnew record,
            we want to open the list with the new record open and scrolled to,
            so that the usercan start filling in the blank record straightaway.

        Parameters
        ----------
        openEid: ObjectId
            The id of a record that must forcibly be opened.
        action: string, optional, `None`
            If present, a specific record selection will be presented,
            otherwise all records go to the interface.

        Returns
        -------
        string(html)
        """

        if not self.mayList(action=action):
            return None

        context = self.context
        db = context.db
        table = self.table
        uid = self.uid
        countryId = self.countryId
        titleSortkey = self.titleSortkey
        (itemSingular, itemPlural) = self.itemLabels

        params = (dict(my=uid) if action == N.my else dict(
            our=countryId) if action == N.our else dict(
                my=uid) if action == N.assess else dict(
                    assign=True) if action == N.
                  assign else dict(review=uid) if action == N.review else dict(
                      review=uid) if action == N.reviewdone else dict(
                          selectable=countryId) if action == N.select else {})
        if request.args:
            params.update(request.args)

        records = db.getList(table,
                             titleSortkey,
                             select=self.isMainTable,
                             **params)
        insertButton = self.insertButton() if self.withInsert(action) else E
        sep = NBSP if insertButton else E

        if action == N.assess:
            records = [
                record for record in records
                if self.stage(record, N.assessment) in ASSESSMENT_STAGES
                and self.stage(record, N.review, kind=N.final) not in
                {N.reviewAccept, N.reviewReject}
                and uid in self.creators(record, N.assessment)
            ]
        if action == N.review:
            records = [
                record for record in records
                if not self.myFinished(uid, record)
            ]
        if action == N.reviewdone:
            records = [
                record for record in records if self.myFinished(uid, record)
            ]

        recordsHtml = []
        sensitive = table in SENSITIVE_TABLES
        for record in records:
            if not sensitive or self.readable(record) is not False:
                recordsHtml.append(
                    H.details(
                        self.title(record),
                        H.div(ELLIPS),
                        f"""{table}/{G(record, N._id)}""",
                        fetchurl=
                        f"""/api/{table}/{N.item}/{G(record, N._id)}""",
                        urltitle=E,
                        urlextra=E,
                        **self.forceOpen(G(record, N._id), openEid),
                    ))

        nRecords = len(recordsHtml)
        itemLabel = itemSingular if nRecords == 1 else itemPlural
        nRepCmt = f"""<!-- mainN~{nRecords}~{itemLabel} -->"""
        nRep = nRepCmt + H.span(f"""{nRecords} {itemLabel}""", cls="stats")

        return H.div(
            [H.span([insertButton, sep, nRep])] + recordsHtml,
            cls=f"table {table}",
        )
Example #17
0
def presentScore(score, eid, derivation=True):
    """Presents the result of a score computation.

    It shows the overall score, but it can also expand a derivation of the
    overall score.

    Parameters
    ----------
    score: dict
        Quantities relevant to the score and its derivation
    eid: ObjectId
        Id of the assessment of which the score has been taken.
        Only used to give the score presentation a unique identifier on the
        interface, so that the derivation can be collapsed and expanded
        in the presence of other score presentations.
    derivation: boolean, optional `True`
        If `False`, the derivation will be suppressed.
    """

    overall = G(score, N.overall, default=0)
    relevantScore = G(score, N.relevantScore, default=0)
    relevantMax = G(score, N.relevantMax, default=0)
    relevantN = G(score, N.relevantN, default=0)
    allMax = G(score, N.allMax, default=0)
    allN = G(score, N.allN, default=0)
    irrelevantN = allN - relevantN

    fullScore = H.span(
        f"Score {overall}%",
        title="overall score of this assessment",
        cls="ass-score",
    )
    if not derivation:
        return fullScore

    scoreMaterial = H.div(
        [
            H.div(
                [
                    H.p(f"""This assessment scores {relevantScore} points."""),
                    H.p(f"""For this type of contribution there is a total of
                          {allMax} points, divided over {allN} criteria.
                      """),
                    (H.p(f"""However,
                              {irrelevantN}
                              rule{" is " if irrelevantN == 1 else "s are"}
                              not applicable to this contribution,
                              which leaves the total amount to
                              {relevantMax} points,
                              divided over {relevantN} criteria.
                          """) if irrelevantN else E),
                    H.p(f"""The total score is expressed as a percentage:
                          the fraction of {relevantScore} scored points
                          with respect to {relevantMax} scorable points:
                          {overall}%.
                      """),
                ],
                cls="ass-score-deriv",
            ),
        ],
        cls="ass-score-box",
    )

    scoreWidget = H.detailx(
        (N.calc, N.dismiss),
        scoreMaterial,
        f"""{N.assessment}/{eid}/scorebox""",
        openAtts=dict(cls="button small", title="Show derivation"),
        closeAtts=dict(cls="button small", title="Hide derivation"),
    )
    return (fullScore, *scoreWidget)
Example #18
0
 def toDisplay(self, val, markup=True):
     if val is None:
         return QQ if markup else Qq
     valBare = f"""{EURO} {self.normalize(str(val))}"""
     return H.span(valBare) if markup else valBare