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 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)))
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])
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)
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")
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", )
def titleHint(self, record): return H.join(G(record, N.explanation) or [])
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)
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}"))
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))