Hide keyboard shortcuts

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. 

2 

3* Navigation controls 

4""" 

5 

6from flask import request 

7 

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 

13 

14from control.cust.factory_table import make as mkTable 

15 

16CT = C.tables 

17CW = C.web 

18 

19 

20SORTED_TABLES = CT.sorted 

21 

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] 

36 

37 

38class Sidebar: 

39 """Show a sidebar with navigation links and buttons on the interface.""" 

40 

41 def __init__(self, context, path): 

42 """## Initialization 

43 

44 Store the incoming information and set up attributes for collecting items. 

45 

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. 

49 

50 Parameters 

51 ---------- 

52 context: object 

53 See below. 

54 path: url 

55 See below. 

56 """ 

57 

58 self.context = context 

59 """*object* A `control.context.Context` singleton. 

60 """ 

61 

62 self.path = path 

63 """*url* The current url. 

64 """ 

65 

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 """ 

71 

72 def tablePerm(self, table): 

73 context = self.context 

74 auth = context.auth 

75 

76 return checkTable(auth, table) 

77 

78 def makeCaption(self, label, entries, rule=False): 

79 """Produce the caption for a section of navigation items. 

80 

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. 

91 

92 Returns 

93 ------- 

94 string(html) 

95 """ 

96 

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 

99 

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" 

108 

109 entriesRep = H.div(reps, cls="sidebarsec") 

110 return H.details(label, entriesRep, label, **atts) 

111 

112 def makeOptions(self): 

113 """Produce an options widget. 

114 

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

116 """ 

117 

118 options = self.options 

119 

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 ] 

130 

131 return [("XXX", rep) for rep in filterRep + optionsRep] 

132 

133 def makeEntry(self, label, path, withOptions=False, asTask=False): 

134 """Produce an entry. 

135 

136 !!! hint "easy comments" 

137 We also include a comment `<!-- caption^label --> 

138 for the ease of testing. 

139 

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. 

151 

152 Returns 

153 ------- 

154 path: url 

155 The url that corresponds to this entry 

156 string(html) 

157 The wrapped entry 

158 """ 

159 

160 options = self.options 

161 active = path == self.path 

162 

163 task = "task info" if asTask else "button" 

164 navClass = f"{task} small nav" + (" active" if active else E) 

165 

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 

174 

175 atts = dict(cls=navClass,) 

176 if withOptions: 

177 atts[N.hrefbase] = path 

178 

179 comment = f"""<!-- caption^{label} -->""" 

180 return ( 

181 path, 

182 comment + H.a(label, path + optionsRep, **atts), 

183 ) 

184 

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. 

196 

197 A table entry is a link or button to show a table. 

198 

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`. 

210 

211 !!! caution 

212 The table entry will only be made if the user has permissions 

213 to list the detail table! 

214 

215 Returns 

216 ------- 

217 path: url 

218 The url that corresponds to this entry 

219 string(html) 

220 The wrapped entry 

221 """ 

222 

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) 

225 

226 context = self.context 

227 

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 

233 

234 return self.makeEntry( 

235 f"""{prefixRep}{item}{postfixRep}""", 

236 f"""/{table}/{N.list}{actionRep}""", 

237 withOptions=True, 

238 asTask=asTask, 

239 ) 

240 

241 def wrap(self): 

242 """Wrap it all up. 

243 

244 Produce a list geared to the current user, with actions that make sense 

245 to him/her. 

246 

247 Take care that only permitted actions are presented. 

248 

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

250 

251 Returns 

252 ------- 

253 string(html) 

254 """ 

255 

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() 

264 

265 entries = [] 

266 

267 # home 

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

269 

270 # options 

271 

272 entries.append( 

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

274 ) 

275 

276 # DARIAH contributions 

277 

278 subEntries = [] 

279 

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

281 

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

283 

284 if isAuth: 

285 

286 # my country 

287 

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) 

290 

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 ) 

302 

303 # - my contributions and assessments 

304 

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 ) 

314 

315 # - reviewed by me (all) 

316 

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

318 

319 # tasks 

320 

321 if not isAuth: 

322 return H.join(entries) 

323 

324 subEntries = [] 

325 

326 if isCoord: 

327 

328 # - select contributions 

329 

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 

340 

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 ) 

350 

351 if isOffice: 

352 

353 # - assign reviewers 

354 

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 ) 

364 

365 # - in review by me (unfinished) 

366 

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 ) 

376 

377 # - reviewed by me (finished) 

378 

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 ) 

388 

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

390 

391 # user content 

392 

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)) 

399 

400 # office content 

401 

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 ) 

409 

410 # system content 

411 

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 ) 

427 

428 return H.join(entries)