Coverage for control/sidebar.py : 96%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Sidebar.
3* Navigation controls
4"""
6from flask import request
8from config import Config as C, Names as N
9from control.html import HtmlElements as H
10from control.utils import pick as G, cap1, E, Q, AMP, ZERO
11from control.typ.types import Country
12from control.perm import checkTable
14from control.cust.factory_table import make as mkTable
16CT = C.tables
17CW = C.web
20SORTED_TABLES = CT.sorted
22URLS = CW.urls
23HOME = URLS[N.home]
24OVERVIEW = URLS[N.info][N.url]
25REFRESH = URLS[N.refresh][N.url]
26REFRESH_TEXT = URLS[N.refresh][N.text]
27WORKFLOW = URLS[N.workflow][N.url]
28WORKFLOW_TEXT = URLS[N.workflow][N.text]
29OPTIONS = CW.options
30CAPTIONS = CW.captions
31USER_TABLES_LIST = CT.userTables
32USER_ENTRY_TABLES = set(CT.userEntryTables)
33VALUE_TABLES = CT.valueTables
34SYSTEM_TABLES = CT.systemTables
35OFFICE_TABLES = [t for t in VALUE_TABLES if t not in SYSTEM_TABLES]
38class Sidebar:
39 """Show a sidebar with navigation links and buttons on the interface."""
41 def __init__(self, context, path):
42 """## Initialization
44 Store the incoming information and set up attributes for collecting items.
46 !!! hint
47 The `path` is needed to determine which items in the sidebar are the active
48 items, so that they can be highlighted by means of a navigation CSS class.
50 Parameters
51 ----------
52 context: object
53 See below.
54 path: url
55 See below.
56 """
58 self.context = context
59 """*object* A `control.context.Context` singleton.
60 """
62 self.path = path
63 """*url* The current url.
64 """
66 self.options = {
67 option: G(request.args, option, default=ZERO) for option in OPTIONS.keys()
68 }
69 """*dict* The current setting of the options.
70 """
72 def tablePerm(self, table):
73 context = self.context
74 auth = context.auth
76 return checkTable(auth, table)
78 def makeCaption(self, label, entries, rule=False):
79 """Produce the caption for a section of navigation items.
81 Parameters
82 ----------
83 label: string
84 Points to a key in web.yaml, under `captions`,
85 where the full text of the caption can be founnd.
86 entries: iterable of (path, string(html))
87 The path is used to determine whether this entry is active;
88 the string is the formatted html of the entry.
89 rule: boolean
90 Whether there should be a rule before the first entry.
92 Returns
93 -------
94 string(html)
95 """
97 if not entries: 97 ↛ 98line 97 didn't jump to line 98, because the condition on line 97 was never true
98 return E
100 refPath = self.path
101 paths = {path for (path, rep) in entries}
102 reps = [rep for (path, rep) in entries]
103 active = any(refPath.startswith(f"""/{p}/""") for p in paths)
104 navClass = " active" if active else E
105 atts = dict(cls=f"nav {navClass}")
106 if rule:
107 atts[N.addClass] = " ruleabove"
109 entriesRep = H.div(reps, cls="sidebarsec")
110 return H.details(label, entriesRep, label, **atts)
112 def makeOptions(self):
113 """Produce an options widget.
115 The options are defined in web.yaml, under the key `options`.
116 """
118 options = self.options
120 filterRep = [
121 H.input(E, type=N.text, id="cfilter", placeholder="match title"),
122 ]
123 optionsRep = [
124 H.span(
125 [H.checkbox(name, trival=value), G(G(OPTIONS, name), N.label)],
126 cls=N.option,
127 )
128 for (name, value) in options.items()
129 ]
131 return [("XXX", rep) for rep in filterRep + optionsRep]
133 def makeEntry(self, label, path, withOptions=False, asTask=False):
134 """Produce an entry.
136 !!! hint "easy comments"
137 We also include a comment `<!-- caption^label -->
138 for the ease of testing.
140 Parameters
141 ----------
142 label: string
143 The text of the entry
144 path: url
145 The destination after the entry is clicked.
146 withOptions: boolean, optional `False`
147 Whether to include the options widget.
148 asTask: boolean, optional `False`
149 Display the entry as a big workflow task button or as a modest
150 hyperlink.
152 Returns
153 -------
154 path: url
155 The url that corresponds to this entry
156 string(html)
157 The wrapped entry
158 """
160 options = self.options
161 active = path == self.path
163 task = "task info" if asTask else "button"
164 navClass = f"{task} small nav" + (" active" if active else E)
166 optionsRep = (
167 AMP.join(f"""{name}={value}""" for (name, value) in options.items())
168 if withOptions
169 else E
170 )
171 if optionsRep:
172 optionSep = AMP if Q in path else Q
173 optionsRep = optionSep + optionsRep
175 atts = dict(cls=navClass,)
176 if withOptions:
177 atts[N.hrefbase] = path
179 comment = f"""<!-- caption^{label} -->"""
180 return (
181 path,
182 comment + H.a(label, path + optionsRep, **atts),
183 )
185 def tableEntry(
186 self,
187 table,
188 prefix=None,
189 item=None,
190 postfix=None,
191 action=None,
192 withOptions=False,
193 asTask=False,
194 ):
195 """Produce a table entry.
197 A table entry is a link or button to show a table.
199 Parameters
200 ----------
201 table: string
202 prefix, item, postfix: string, optional `None`
203 These make up the text of the link in that order.
204 If `item` is left out, the tables.yaml file has a suitable
205 string under the key `items`
206 action: string {`my`, `our`, ...}, optional, `None`
207 If left out, all items will be retrieved. Otherwise, a selection is made.
208 See web.yaml under `listActions` for all possible values.
209 See also `control.table.Table.wrap`.
211 !!! caution
212 The table entry will only be made if the user has permissions
213 to list the detail table!
215 Returns
216 -------
217 path: url
218 The url that corresponds to this entry
219 string(html)
220 The wrapped entry
221 """
223 if not self.tablePerm(table): 223 ↛ 224line 223 didn't jump to line 224, because the condition on line 223 was never true
224 return (E, E)
226 context = self.context
228 tableObj = mkTable(context, table)
229 item = tableObj.itemLabels[1] if item is None else item
230 actionRep = f"?action={action}" if action else E
231 prefixRep = f"{prefix} " if prefix else E
232 postfixRep = f" {postfix}" if postfix else E
234 return self.makeEntry(
235 f"""{prefixRep}{item}{postfixRep}""",
236 f"""/{table}/{N.list}{actionRep}""",
237 withOptions=True,
238 asTask=asTask,
239 )
241 def wrap(self):
242 """Wrap it all up.
244 Produce a list geared to the current user, with actions that make sense
245 to him/her.
247 Take care that only permitted actions are presented.
249 Actions that belong to workflow are presented as conspicuous workflow tasks.
251 Returns
252 -------
253 string(html)
254 """
256 context = self.context
257 auth = context.auth
258 isAuth = auth.authenticated()
259 isOffice = auth.officeuser()
260 isSuperUser = auth.superuser()
261 isSysAdmin = auth.sysadmin()
262 isCoord = auth.coordinator()
263 country = auth.country()
265 entries = []
267 # home
268 entries.append(self.makeEntry(G(HOME, N.text), G(HOME, N.url))[1])
270 # options
272 entries.append(
273 self.makeCaption(G(CAPTIONS, N.options), self.makeOptions(), rule=True)
274 )
276 # DARIAH contributions
278 subEntries = []
280 subEntries.append(self.makeEntry(cap1(N.overview), path=OVERVIEW, asTask=True))
282 subEntries.append(self.tableEntry(N.contrib, prefix="All", withOptions=True))
284 if isAuth:
286 # my country
288 if country: 288 ↛ 305line 288 didn't jump to line 305, because the condition on line 288 was never false
289 countryType = Country(context)
291 countryRep = countryType.titleStr(country)
292 iso = G(country, N.iso)
293 if iso: 293 ↛ 305line 293 didn't jump to line 305, because the condition on line 293 was never false
294 subEntries.append(
295 self.tableEntry(
296 N.contrib,
297 action=N.our,
298 prefix=f"{countryRep}",
299 withOptions=True,
300 )
301 )
303 # - my contributions and assessments
305 subEntries.extend(
306 [
307 self.tableEntry(
308 N.contrib, action=N.my, prefix="My", withOptions=True
309 ),
310 self.tableEntry(N.assessment, action=N.my, prefix="My"),
311 self.tableEntry(N.review, action=N.my, prefix="My"),
312 ]
313 )
315 # - reviewed by me (all)
317 entries.append(self.makeCaption(G(CAPTIONS, N.contrib), subEntries))
319 # tasks
321 if not isAuth:
322 return H.join(entries)
324 subEntries = []
326 if isCoord:
328 # - select contributions
330 subEntries.append(
331 self.tableEntry(
332 N.contrib,
333 action=N.select,
334 item="Contributions",
335 postfix="to be selected",
336 asTask=True,
337 )
338 )
339 # - my unfinished assessments
341 subEntries.append(
342 self.tableEntry(
343 N.contrib,
344 action=N.assess,
345 item="Contributions",
346 postfix="I am assessing",
347 asTask=True,
348 )
349 )
351 if isOffice:
353 # - assign reviewers
355 subEntries.append(
356 self.tableEntry(
357 N.assessment,
358 action=N.assign,
359 item="Assessments",
360 postfix="needing reviewers",
361 asTask=True,
362 )
363 )
365 # - in review by me (unfinished)
367 subEntries.append(
368 self.tableEntry(
369 N.assessment,
370 action=N.review,
371 item="Assessments",
372 postfix="in review by me",
373 asTask=True,
374 )
375 )
377 # - reviewed by me (finished)
379 subEntries.append(
380 self.tableEntry(
381 N.assessment,
382 action=N.reviewdone,
383 item="Assessments",
384 postfix="reviewed by me",
385 asTask=True,
386 )
387 )
389 entries.append(self.makeCaption(G(CAPTIONS, N.tasks), subEntries, rule=True))
391 # user content
393 subEntries = []
394 if isSuperUser:
395 for table in USER_TABLES_LIST[1:]:
396 if isSysAdmin or table not in USER_ENTRY_TABLES: 396 ↛ 395line 396 didn't jump to line 395, because the condition on line 396 was never false
397 subEntries.append(self.tableEntry(table, prefix="All"))
398 entries.append(self.makeCaption(G(CAPTIONS, N.user), subEntries, rule=True))
400 # office content
402 subEntries = []
403 if isSuperUser:
404 for table in OFFICE_TABLES:
405 subEntries.append(self.tableEntry(table, asTask=table == N.user))
406 entries.append(
407 self.makeCaption(G(CAPTIONS, N.office), subEntries, rule=True)
408 )
410 # system content
412 subEntries = []
413 if isSysAdmin:
414 subEntries.append(
415 self.makeEntry(REFRESH_TEXT, path=REFRESH, asTask=True)
416 )
417 subEntries.append(self.tableEntry(N.collect))
418 subEntries.append(
419 self.makeEntry(WORKFLOW_TEXT, path=WORKFLOW, asTask=True)
420 )
421 for table in SYSTEM_TABLES:
422 if table != N.collect:
423 subEntries.append(self.tableEntry(table))
424 entries.append(
425 self.makeCaption(G(CAPTIONS, N.system), subEntries, rule=True)
426 )
428 return H.join(entries)