def bodyCompact(self, **kwargs):
        critId = self.critId
        critRecord = self.critRecord
        perm = self.perm

        critData = critRecord.record
        actual = G(critData, N.actual, default=False)
        msg = E if actual else G(MESSAGES, N.legacyCriterion)

        critKey = f"""{N.criteria}/{critId}/help"""
        (infoShow, infoHide, infoBody) = H.detailx(
            (N.info, N.dismiss),
            critRecord.wrapHelp(),
            critKey,
            openAtts=dict(cls="button small",
                          title="Explanation and scoring guide"),
            closeAtts=dict(cls="button small",
                           title="Hide criteria explanation"),
        )

        score = H.div(self.field(N.score).wrap(asEdit=G(perm, N.isEdit)))
        evidence = H.div(self.field(N.evidence).wrap(asEdit=G(perm, N.isEdit)))
        entry = H.div([
            H.div(H.he(msg), cls="heavy") if msg else E,
            infoShow,
            infoHide,
            infoBody,
            score,
            evidence,
        ], )

        return entry
Exemple #2
0
    def wrapTestUsers(self):
        """Present a widget to select a test user for login.

        !!! caution
            In production this will do nothing.
            Only in development mode one can select a test user.
        """
        if not self.isDevel:
            return E

        db = self.db

        testUsers = {
            record[N.eppn]: record
            for record in db.user.values()
            if N.eppn in record and G(record, N.authority) == N.local
        }
        return H.join(
            [
                H.div(H.a(u, href=f"/login?eppn={u}", cls="button small"))
                for u in testUsers
            ]
            + [
                H.div(
                    H.input(
                        E,
                        placeholder="email",
                        onchange="window.location.href=`/login?email=${this.value}`",
                    )
                )
            ]
        )
    def bodyCompact(self, **kwargs):
        perm = self.perm

        theTitle = self.title()
        comments = ("""<!-- begin review comment -->""" + H.div(
            self.field(N.comments).wrap(withLabel=False,
                                        asEdit=G(perm, N.isEdit)), ) +
                    """<!-- end review comment -->""")

        return H.div([theTitle, comments], cls=f"reviewentry")
Exemple #4
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")
Exemple #5
0
    def makeCaption(self, label, entries, rule=False):
        """Produce the caption for a section of navigation items.

        Parameters
        ----------
        label: string
            Points to a key in web.yaml, under `captions`,
            where the full text of the caption can be founnd.
        entries: iterable of (path, string(html))
            The path is used to determine whether this entry is active;
            the string is the formatted html of the entry.
        rule: boolean
            Whether there should be a rule before the first entry.

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

        if not entries:
            return E

        refPath = self.path
        paths = {path for (path, rep) in entries}
        reps = [rep for (path, rep) in entries]
        active = any(refPath.startswith(f"""/{p}/""") for p in paths)
        navClass = " active" if active else E
        atts = dict(cls=f"nav {navClass}")
        if rule:
            atts[N.addClass] = " ruleabove"

        entriesRep = H.div(reps, cls="sidebarsec")
        return H.details(label, entriesRep, label, **atts)
Exemple #6
0
    def status(self, table, kind=None):
        """Present all workflow info and controls relevant to the record.

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

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

        eid = list(self.info(table, N._id, kind=kind))[0]
        itemKey = f"""{table}/{eid}"""
        rButton = H.iconr(itemKey, "#workflow", msg=N.status)

        return H.div(
            [
                rButton,
                self.statusOverview(table, kind=kind),
                self.tasks(table, kind=kind),
            ],
            cls="workflow",
        )
    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",
        )
Exemple #8
0
    def wrap(self, *args, **kwargs):
        wfitem = self.wfitem
        if not wfitem:
            return super().wrap(*args, **kwargs)

        kind = self.kind

        statusRep = wfitem.status(N.review, kind=kind)

        return H.div(statusRep)
Exemple #9
0
 def wrapHelp(self):
     term = self.title()
     definition = H.div(
         [
             self.field(field).wrap(
                 empty=True,
                 action=N.view,
                 cls="scoredesc" if field == N.description else E,
             )
             for field in [N.description, N.remarks]
         ]
     )
     return (term, definition)
Exemple #10
0
    def widget(self, val):
        values = G(BOOLEAN_TYPES, self.name)
        noneValue = False if len(values) == 2 else None
        refV = G(values, val, default=G(values, noneValue))

        return H.div(
            [
                H.iconx(
                    values[w],
                    bool=str(w).lower(),
                    cls=(("active" if values[w] is refV else E) + " medium"),
                )
                for w in values
            ],
            cls="wvalue",
        )
    def wrap(self, *args, **kwargs):
        wfitem = self.wfitem
        if not wfitem:
            return super().wrap(*args, **kwargs)

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

        statusRep = wfitem.status(N.contrib)
        showEid = self.mustShow(N.assessment, kwargs)

        return H.div(
            [statusRep,
             self.wrapDetail(N.assessment, showEid=showEid)])
Exemple #12
0
    def widget(self, val, multiple, extensible, constrain):
        context = self.context
        db = context.db
        table = self.name

        valueRecords = db.getValueRecords(table, constrain=constrain)

        filterControl = (
            [
                H.input(
                    E,
                    type=N.text,
                    placeholder=G(MESSAGES, N.filter, default=E),
                    cls="wfilter",
                ),
                H.iconx(N.add, cls="small wfilter add", title="add value")
                if extensible
                else E,
                H.iconx(N.clear, cls="small wfilter clear", title="clear filter"),
            ]
            if len(valueRecords) > FILTER_THRESHOLD
            else []
        )
        atts = dict(
            markup=True,
            clickable=True,
            multiple=multiple,
            active=val,
            hideInActual=True,
            hideBlockedUsers=True,
        )
        return H.div(
            filterControl
            + [
                formatted
                for (text, formatted) in (
                    ([] if multiple else [self.title(record={}, **atts)])
                    + sorted(
                        (self.title(record=record, **atts) for record in valueRecords),
                        key=lambda x: x[0].lower(),
                    )
                )
            ],
            cls="wvalue",
        )
    def wrapHelp(self):
        info = H.join(
            self.field(field, readonly=True).wrap(action=N.view)
            for field in [N.typeContribution, N.remarks]
            if field != N.typeContribution
        )

        detailsObj = self.DetailsClass(self)
        detailsObj.fetchDetails(N.score)
        details = detailsObj.wrapDetail(
            N.score,
            expanded=True,
            readonly=True,
            wrapMethod=N.wrapHelp,
            combineMethod=lambda x: [H.dl(x)],
        )

        return H.div(info + details, cls="criteriahelp")
    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],
        )
Exemple #15
0
    def wrap(
        self,
        inner=True,
        wrapMethod=None,
        expanded=1,
        withProv=True,
        hideMasters=False,
        showTable=None,
        showEid=None,
        extraCls=E,
    ):
        """Wrap the record into HTML.

        A record can be displayed in several states:

        expanded | effect
        --- | ---
        `-1` | only a title with a control to get the full details
        `0` | full details, no control to collapse/expand
        `1` | full details, with a control to collapse to the title

        !!! note
            When a record in state `1` or `-1` is sent to the client,
            only the material that is displayed is sent. When the user clicks on the
            expand/collapse control, the other part is fetched from the server
            *at that very moment*.
            So collapsing/expanding can be used to refresh the view on a record
            if things have happened.

        !!! caution
            The triggering of the fetch actions for expanded/collapsed material
            is done by the Javascript in `index.js`.
            A single function does it all, and it is sensitive to the exact attributes
            of the summary and the detail.
            If you tamper with this code, you might end up with an infinite loop of
            expanding and collapsing.

        !!! hint
            Pay extra attension to the attribute `fat`!
            When it is present, it is an indication that the expanded material
            is already on the client, and that it does not have to be fetched.

        !!! note
            There are several ways to customise the effect of `wrap` for specific
            tables. Start with writing a derived class for that table with
            `Record` as base class.

            *   write an alternative for `Record.body`,
                e.g. `control.cust.review_record.ReviewR.bodyCompact`, and
                initialize the `Record` with `(bodyMethod='compact')`.
                Just specify the part of the name after `body` as string starting
                with a lower case.
            *   override `Record.body`. This app does not do this currently.
            *   use a custom `wrap` function, by defining it in your derived class,
                e.g. `control.cust.score_record.ScoreR.wrapHelp`.
                Use it by calling `Record.wrap(wrapMethod=scoreObj.wrapHelp)`.

        Parameters
        ----------
        inner: boolean, optional `True`
            Whether to add the CSS class `inner` to the outer `<div>` of the result.
        wrapMethod: function, optional `None`
            The method to compose the result out of all its components.
            Typically defined in a derived class.
            If passed, this function will be called to deliver the result.
            Otherwise, `wrap` does the composition itself.
        expanded: {-1, 0, 1}
            Whether to expand the record.
            See the table above.
        withProv: boolean, optional `True`
            Include a display of the provenance fields.
        hideMasters: boolean, optional `False`
            Whether to hide the master fields.
            If they are not hidden, they will be presented as hyperlinks to the
            master record.
        extraCls: string, optional `''`
            An extra class to add to the outer `<div>`.

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

        table = self.table
        eid = self.eid
        record = self.record
        provSpecs = self.prov
        valid = self.valid
        withDetails = self.withDetails

        withRefresh = table in REFRESH_TABLES

        func = getattr(self, wrapMethod, None) if wrapMethod else None
        if func:
            return func()

        bodyMethod = self.bodyMethod
        urlExtra = f"""?method={bodyMethod}""" if bodyMethod else E
        fetchUrl = f"""/api/{table}/{N.item}/{eid}"""

        itemKey = f"""{table}/{G(record, N._id)}"""
        theTitle = self.title()

        if expanded == -1:
            return H.details(
                theTitle,
                H.div(ELLIPS),
                itemKey,
                fetchurl=fetchUrl,
                urlextra=urlExtra,
                urltitle=E,
            )

        bodyFunc = (getattr(self, f"""{N.body}{cap1(bodyMethod)}""", self.body)
                    if bodyMethod else self.body)
        myMasters = G(MASTERS, table, default=[])

        deleteButton = self.deleteButton()

        innerCls = " inner" if inner else E
        warningCls = E if valid else " warning "

        provenance = (H.div(
            H.detailx(
                (N.prov, N.dismiss),
                H.div([self.field(field).wrap() for field in provSpecs],
                      cls="prov"),
                f"""{table}/{G(record, N._id)}/{N.prov}""",
                openAtts=dict(
                    cls="button small",
                    title="Provenance and editors of this record",
                ),
                closeAtts=dict(cls="button small", title="Hide provenance"),
                cls="prov",
            ),
            cls="provx",
        ) if withProv else E)

        main = H.div(
            [
                deleteButton,
                H.div(
                    H.join(
                        bodyFunc(myMasters=myMasters,
                                 hideMasters=hideMasters)),
                    cls=f"{table.lower()}",
                ),
                *provenance,
            ],
            cls=f"record{innerCls} {extraCls} {warningCls}",
        )

        rButton = H.iconr(itemKey, "#main", msg=table) if withRefresh else E
        details = (self.DetailsClass(self).wrap(
            showTable=showTable, showEid=showEid) if withDetails else E)

        return (H.details(
            rButton + theTitle,
            H.div(main + details),
            itemKey,
            fetchurl=fetchUrl,
            urlextra=urlExtra,
            urltitle="""/title""",
            fat=ONE,
            forceopen=ONE,
            open=True,
        ) if expanded == 1 else H.div(main + details))
Exemple #16
0
    def wrapValue(self, editable, cls=E):
        """Wraps the value of a field.

        !!! hint "field=value in comments"
            In the result we include a string

            ``` html
            <!-- title=My contribution -->
            ```

            but then with the field name instead of `title`
            and the unmarked-up value  instead of `My contribution`.
            This makes it easier for the test suite
            to spot the fields and their values.

        Parameters
        ----------
        editable: boolean
            Whether the field should be presented in editable form
        cls
            See `Field.wrap()`

        Returns
        -------
        string(html)
        """
        context = self.context
        types = context.types
        fieldTypeObj = self.fieldTypeObj
        field = self.field
        value = self.value
        tp = self.tp
        multiple = self.multiple
        extensible = self.extensible
        widgetType = self.widgetType

        baseCls = "tags" if widgetType == N.related else "values"
        isSelectWidget = widgetType == N.related

        args = []
        if isSelectWidget and editable:
            record = self.record
            constrain = None
            constrainField = G(CONSTRAINED, field)
            if constrainField:
                constrainValue = G(record, constrainField)
                if constrainValue:
                    constrain = (constrainField, constrainValue)
            args.append(multiple)
            args.append(extensible)
            args.append(constrain)
        atts = dict(wtype=widgetType)

        if editable:
            typeObj = getattr(types, tp, None)
            method = typeObj.toOrig
            origStr = [method(v)
                       for v in value or []] if multiple else method(value)
            atts[N.orig] = bencode(origStr)
        if multiple:
            atts[N.multiple] = ONE
        if extensible:
            atts[N.extensible] = ONE

        method = fieldTypeObj.widget if editable else fieldTypeObj.toDisplay
        extraCls = E if editable else cls
        valueBare = ((COMMA.join(
            val for val in value or []) if multiple else value)
                     if tp == N.markdown else
                     (COMMA.join(
                         fieldTypeObj.toDisplay(val, markup=False)
                         for val in value or []) if multiple else
                      fieldTypeObj.toDisplay(value, markup=False)))

        return f"<!-- {field}={valueBare} -->" + (H.div(
            [
                method(val, *args)
                for val in (value or []) + ([E] if editable else [])
            ],
            **atts,
            cls=baseCls,
        ) if multiple and not (editable and isSelectWidget) else H.div(
            method(value, *args), **atts, cls=f"value {extraCls}"))
Exemple #17
0
    def wrap(self,
             action=None,
             asEdit=False,
             empty=False,
             withLabel=True,
             cls=E):
        """Wrap the field into HTML.

        If there is an `action`, data from the request is picked up and
        `Field.save` is called to save that data to the MongoDB.
        Depending on the `action`, the field is then rendered as follows:

        action | effect
        --- | ---
        `save` | no rendering
        `view`| a read only rendering
        `edit` | an edit widget

        Whether a field is presented as an editable field depends on a number of factors:

        factor | story
        --- | ---
        is master | a field pointing to a master will not be edited
        attribute `mayEdit` | does the current user have permission to edit the field?
        action `edit` or parameter `asEdit` | do we want to present an editable widget?

        Parameters
        ----------
        action: {save, edit, view}, optional `None`
            If present, data will be saved to the database first.
        asEdit: boolean, optional `False`
            No data will be saved.
            The field will rendered editable, if permitted.
        empty: boolean, optional `False`
            Only relevant for readonly views: if the value is empty, just present the
            empty string and nothing else.
        withLabel: boolean, optional `True`
            Whether to precede the value with a field label.
            The label is specified in the field specs which are in the table's .yaml
            file in `control/tables`.
        cls: string, optional `''`
            A CSS class to append to the outer `<div>` of the result.

        Returns
        -------
        string(html)
            If there was an `action`, the bare representation of the field value is returned.
            Otherwise, and if `withLabel`, a label is added.
        """

        mayRead = self.mayRead

        if mayRead is False:
            return E

        asMaster = self.asMaster
        mayEdit = self.mayEdit

        if action is not None and not asMaster:
            contentLength = request.content_length
            if contentLength is not None and contentLength > LIMIT_JSON:
                abort(400)
            data = request.get_json()
            if data is not None and N.save in data:
                if mayEdit:
                    good = self.save(data[N.save])
                else:
                    good = False
                if not good:
                    abort(400)

        if action == N.save:
            return E

        editable = mayEdit and (action == N.edit or asEdit) and not asMaster
        widget = self.wrapWidget(editable, cls=cls)

        if action is not None:
            return H.join(widget)

        if empty and self.isEmpty():
            return E

        label = self.label
        editClass = " edit" if editable else E

        return (H.div(
            [
                H.div(f"""{label}:""", cls="record-label"),
                H.div(widget, cls=f"record-value{editClass}"),
            ],
            cls="record-row",
        ) if withLabel else H.div(widget, cls=f"record-value{editClass}"))
Exemple #18
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",
        )
Exemple #19
0
    def wrapDetail(
        self,
        dtable,
        withDetails=False,
        readonly=False,
        bodyMethod=None,
        inner=True,
        wrapMethod=None,
        expanded=False,
        withProv=True,
        extraCls=None,
        filterFunc=None,
        combineMethod=None,
        withN=True,
        showEid=None,
    ):
        """Wrap the details of a specific table for this record into HTML.

        Some of the parameters above will be passed to the initializers
        of the detail record objects, others to their `wrap` method.

        Parameters
        ----------
        dtable: string
            The name of the detail table
        withDetails, readonly, bodyMethod: mixed
            See `control.record.Record`
        inner, wrapMethod, withProv, extraCls: mixed
            See `control.record.Record.wrap`.
        expanded: boolean
            Whether to expand all details in this detail table.
            If True, the expanded details will not get a collapse control.
        filterFunc: function, optional `None`
            You can optionally filter the detail records.
        combineMethod: function, optional `None`
            After getting the HTML for individual records, you can
            instruct to reorder/restructure those representations.
        withN: boolean, optional `True`
            Whether to present the number of detail records
        showEid: ObjectId
            Id of the detail record that should be initially opened.

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

        details = self.details

        (dtableObj, drecordsAll) = G(details, dtable, default=(None, []))
        if not dtableObj:
            return E

        drecords = [
            drecord for drecord in drecordsAll
            if filterFunc is None or filterFunc(drecord)
        ]

        nRecords = len(drecords)
        if nRecords == 0:
            return E

        (itemSingular, itemPlural) = dtableObj.itemLabels
        itemLabel = itemSingular if nRecords == 1 else itemPlural

        nRep = H.div(f"""{nRecords} {itemLabel}""", cls="stats")

        drecordReps = []
        for drecord in drecords:
            show = showEid == str(G(drecord, N._id))
            drecordReps.append(
                dtableObj.record(
                    record=drecord,
                    readonly=readonly,
                    bodyMethod=bodyMethod,
                    withDetails=withDetails or show,
                ).wrap(
                    inner=inner,
                    wrapMethod=wrapMethod,
                    withProv=withProv,
                    expanded=0 if expanded else 1 if show else -1,
                ))
        if combineMethod:
            drecordReps = combineMethod(drecordReps)

        innerCls = " inner" if inner else E
        return H.div(
            [nRep if withN else E] + drecordReps,
            cls=f"record-details{innerCls} {extraCls}",
        )
Exemple #20
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)
Exemple #21
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}",
        )