Exemple #1
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}`",
                    )
                )
            ]
        )
Exemple #2
0
    def body(self, myMasters=None, hideMasters=False):
        """Wrap the body of the record in HTML.

        This is the part without the provenance information and without
        the detail records.

        This method can be overridden by `body` methods in derived classes.

        Parameters
        ----------
        myMasters: iterable of string, optional `None`
            A declaration of which fields must be treated as master fields.
        hideMaster: boolean, optional `False`
            If `True`, all master fields as declared in `myMasters` will be left out.

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

        fieldSpecs = self.fields
        provSpecs = self.prov

        return H.join(
            self.field(field, asMaster=field in myMasters).wrap()
            for field in fieldSpecs
            if (field not in provSpecs
                and not (hideMasters and field in myMasters)))
Exemple #3
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])
Exemple #4
0
    def wrap(self, readonly=False, showTable=None, showEid=None):
        """Wrap the details of all tables for this record into HTML.

        Parameters
        ----------
        readonly: boolean, optional `False`
            Whether the records should be presented in readonly form.
        showTable: string
            Name of the detail table of which record `showEid` should be opened.
        showEid: ObjectId
            Id of the detail record that should be initially opened.

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

        table = self.table

        for detailTable in G(DETAILS, table, default=[]):
            self.fetchDetails(detailTable)

        details = self.details

        return H.join(
            self.wrapDetail(
                detailTable,
                readonly=readonly,
                showEid=showEid if detailTable == showTable else None,
            ) for detailTable in details)
Exemple #5
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 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")
Exemple #7
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 #8
0
 def titleHint(self, record):
     return H.join(G(record, N.explanation) or [])
Exemple #9
0
    def wrap(self):
        """Wrap it all up.

        Produce a list geared to the current user, with actions that make sense
        to him/her.

        Take care that only permitted actions are presented.

        Actions that belong to workflow are presented as conspicuous workflow tasks.

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

        context = self.context
        auth = context.auth
        isAuth = auth.authenticated()
        isOffice = auth.officeuser()
        isSuperUser = auth.superuser()
        isSysAdmin = auth.sysadmin()
        isCoord = auth.coordinator()
        country = auth.country()

        entries = []

        # home
        entries.append(self.makeEntry(G(HOME, N.text), G(HOME, N.url))[1])

        # options

        entries.append(
            self.makeCaption(G(CAPTIONS, N.options),
                             self.makeOptions(),
                             rule=True))

        # DARIAH contributions

        subEntries = []

        subEntries.append(
            self.makeEntry(cap1(N.overview), path=OVERVIEW, asTask=True))

        subEntries.append(
            self.tableEntry(N.contrib, prefix="All", withOptions=True))

        if isAuth:

            # my country

            if country:
                countryType = Country(context)

                countryRep = countryType.titleStr(country)
                iso = G(country, N.iso)
                if iso:
                    subEntries.append(
                        self.tableEntry(
                            N.contrib,
                            action=N.our,
                            prefix=f"{countryRep}",
                            withOptions=True,
                        ))

            # - my contributions and assessments

            subEntries.extend([
                self.tableEntry(N.contrib,
                                action=N.my,
                                prefix="My",
                                withOptions=True),
                self.tableEntry(N.assessment, action=N.my, prefix="My"),
                self.tableEntry(N.review, action=N.my, prefix="My"),
            ])

        # - reviewed by me (all)

        entries.append(self.makeCaption(G(CAPTIONS, N.contrib), subEntries))

        # tasks

        if not isAuth:
            return H.join(entries)

        subEntries = []

        if isCoord:

            # - select contributions

            subEntries.append(
                self.tableEntry(
                    N.contrib,
                    action=N.select,
                    item="Contributions",
                    postfix="to be selected",
                    asTask=True,
                ))
        # - my unfinished assessments

        subEntries.append(
            self.tableEntry(
                N.contrib,
                action=N.assess,
                item="Contributions",
                postfix="I am assessing",
                asTask=True,
            ))

        if isOffice:

            # - assign reviewers

            subEntries.append(
                self.tableEntry(
                    N.assessment,
                    action=N.assign,
                    item="Assessments",
                    postfix="needing reviewers",
                    asTask=True,
                ))

        # - in review by me (unfinished)

        subEntries.append(
            self.tableEntry(
                N.assessment,
                action=N.review,
                item="Assessments",
                postfix="in review by me",
                asTask=True,
            ))

        # - reviewed by me (finished)

        subEntries.append(
            self.tableEntry(
                N.assessment,
                action=N.reviewdone,
                item="Assessments",
                postfix="reviewed by me",
                asTask=True,
            ))

        entries.append(
            self.makeCaption(G(CAPTIONS, N.tasks), subEntries, rule=True))

        # user content

        subEntries = []
        if isSuperUser:
            for table in USER_TABLES_LIST[1:]:
                if isSysAdmin or table not in USER_ENTRY_TABLES:
                    subEntries.append(self.tableEntry(table, prefix="All"))
            entries.append(
                self.makeCaption(G(CAPTIONS, N.user), subEntries, rule=True))

        # office content

        subEntries = []
        if isSuperUser:
            for table in OFFICE_TABLES:
                subEntries.append(
                    self.tableEntry(table, asTask=table == N.user))
            entries.append(
                self.makeCaption(G(CAPTIONS, N.office), subEntries, rule=True))

        # system content

        subEntries = []
        if isSysAdmin:
            subEntries.append(
                self.makeEntry(REFRESH_TEXT, path=REFRESH, asTask=True))
            subEntries.append(self.tableEntry(N.collect))
            subEntries.append(
                self.makeEntry(WORKFLOW_TEXT, path=WORKFLOW, asTask=True))
            for table in SYSTEM_TABLES:
                if table != N.collect:
                    subEntries.append(self.tableEntry(table))
            entries.append(
                self.makeCaption(G(CAPTIONS, N.system), subEntries, rule=True))

        return H.join(entries)
Exemple #10
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 #11
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))