Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/render/html/env/viur.py: 0%

348 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-03 12:27 +0000

1from collections import OrderedDict 

2import logging 

3import os 

4import typing as t 

5import urllib 

6import urllib.parse 

7from datetime import timedelta 

8from hashlib import sha512 

9import jinja2 

10from deprecated.sphinx import deprecated 

11from qrcode import make as qrcode_make 

12from qrcode.image import svg as qrcode_svg 

13 

14import string 

15from viur.core import Method, current, db, errors, prototypes, securitykey, utils 

16from viur.core.config import conf 

17from viur.core.i18n import LanguageWrapper 

18from viur.core.i18n import translate as translate_class 

19from viur.core.render.html.utils import jinjaGlobalFilter, jinjaGlobalFunction 

20from viur.core.request import TEMPLATE_STYLE_KEY 

21from viur.core.skeleton import RelSkel, SkeletonInstance 

22from viur.core.modules import file 

23from ..default import Render 

24 

25 

26@jinjaGlobalFunction 

27def translate( 

28 render: Render, 

29 key: str, 

30 default_text: str = None, 

31 hint: str = None, 

32 force_lang: str | None = None, 

33 **kwargs 

34) -> str: 

35 """Jinja function for translations 

36 

37 See also :class:`core.i18n.TranslationExtension`. 

38 """ 

39 return translate_class(key, default_text, hint, force_lang, caller_is_jinja=True)(**kwargs) 

40 

41 

42@jinjaGlobalFunction 

43def execRequest(render: Render, path: str, *args, **kwargs) -> t.Any: 

44 """ 

45 Jinja2 global: Perform an internal Request. 

46 

47 This function allows to embed the result of another request inside the current template. 

48 All optional parameters are passed to the requested resource. 

49 

50 :param path: Local part of the url, e.g. user/list. Must not start with an /. 

51 Must not include an protocol or hostname. 

52 

53 :returns: Whatever the requested resource returns. This is *not* limited to strings! 

54 """ 

55 request = current.request.get() 

56 cachetime = kwargs.pop("cachetime", 0) 

57 if conf.debug.disable_cache or request.disableCache: # Caching disabled by config 

58 cachetime = 0 

59 cacheEnvKey = None 

60 if conf.cache_environment_key: 

61 try: 

62 cacheEnvKey = conf.cache_environment_key() 

63 except RuntimeError: 

64 cachetime = 0 

65 if cachetime: 

66 # Calculate the cache key that entry would be stored under 

67 tmpList = [f"{k}:{v}" for k, v in kwargs.items()] 

68 tmpList.sort() 

69 tmpList.extend(list(args)) 

70 tmpList.append(path) 

71 if cacheEnvKey is not None: 

72 tmpList.append(cacheEnvKey) 

73 try: 

74 appVersion = request.request.environ["CURRENT_VERSION_ID"].split('.')[0] 

75 except: 

76 appVersion = "" 

77 logging.error("Could not determine the current application id! Caching might produce unexpected results!") 

78 tmpList.append(appVersion) 

79 mysha512 = sha512() 

80 mysha512.update(str(tmpList).encode("UTF8")) 

81 # TODO: Add hook for optional memcache or remove these remnants 

82 cacheKey = f"jinja2_cache_{mysha512.hexdigest()}" 

83 res = None # memcache.get(cacheKey) 

84 if res: 

85 return res 

86 # Pop this key after building the cache string with it 

87 template_style = kwargs.pop(TEMPLATE_STYLE_KEY, None) 

88 tmp_params = request.kwargs.copy() 

89 request.kwargs = {"__args": args, "__outer": tmp_params} 

90 request.kwargs.update(kwargs) 

91 lastRequestState = request.internalRequest 

92 request.internalRequest = True 

93 last_template_style = request.template_style 

94 request.template_style = template_style 

95 caller = conf.main_app 

96 pathlist = path.split("/") 

97 for currpath in pathlist: 

98 if currpath in dir(caller): 

99 caller = getattr(caller, currpath) 

100 elif "index" in dir(caller) and hasattr(getattr(caller, "index"), '__call__'): 

101 caller = getattr(caller, "index") 

102 else: 

103 request.kwargs = tmp_params # Reset RequestParams 

104 request.internalRequest = lastRequestState 

105 request.template_style = last_template_style 

106 return f"{path=} not found (failed at {currpath!r})" 

107 

108 if not (isinstance(caller, Method) and caller.exposed is not None): 

109 request.kwargs = tmp_params # Reset RequestParams 

110 request.internalRequest = lastRequestState 

111 request.template_style = last_template_style 

112 return f"{caller!r} not callable or not exposed" 

113 try: 

114 resstr = caller(*args, **kwargs) 

115 except Exception as e: 

116 logging.error(f"Caught exception in execRequest while calling {path}") 

117 logging.exception(e) 

118 raise 

119 request.kwargs = tmp_params 

120 request.internalRequest = lastRequestState 

121 request.template_style = last_template_style 

122 if cachetime: 

123 pass 

124 # memcache.set(cacheKey, resstr, cachetime) 

125 return resstr 

126 

127 

128@jinjaGlobalFunction 

129def getCurrentUser(render: Render) -> t.Optional[SkeletonInstance]: 

130 """ 

131 Jinja2 global: Returns the current user from the session, or None if not logged in. 

132 

133 :return: A dict containing user data. Returns None if no user data is available. 

134 """ 

135 currentUser = current.user.get() 

136 if currentUser: 

137 currentUser = currentUser.clone() # need to clone, as renderPreparation is changed 

138 currentUser.renderPreparation = render.renderBoneValue 

139 return currentUser 

140 

141 

142@jinjaGlobalFunction 

143def getSkel( 

144 render: Render, 

145 module: str, 

146 key: str = None, 

147 skel: str = "viewSkel", 

148 skel_args: tuple[t.Any] = (), 

149) -> dict | bool | None: 

150 """ 

151 Jinja2 global: Fetch an entry from a given module, and return the data as a dict, 

152 prepared for direct use in the output. 

153 

154 It is possible to specify a different data-model as the one used for rendering 

155 (e.g. an editSkel). 

156 

157 :param module: Name of the module, from which the data should be fetched. 

158 :param key: Requested entity-key in an urlsafe-format. If the module is a Singleton 

159 application, the parameter can be omitted. 

160 :param skel: Specifies and optionally different data-model 

161 :param skel_arg: Optional skeleton arguments to be passed to the skel-function (e.g. for Tree-Modules) 

162 

163 :returns: dict on success, False on error. 

164 """ 

165 obj = conf.main_app 

166 for mod in module.split("."): 

167 if not (obj := getattr(obj, mod, None)): 

168 raise ValueError(f"getSkel: Can't read a skeleton from unknown module {module!r}") 

169 

170 if not getattr(obj, "html", False): 

171 raise PermissionError(f"getSkel: module {module!r} is not allowed to be accessed") 

172 

173 # Retrieve a skeleton 

174 skel = getattr(obj, skel)(*skel_args) 

175 if not isinstance(skel, SkeletonInstance): 

176 raise RuntimeError("getSkel: Invalid skel name provided") 

177 

178 if isinstance(obj, prototypes.singleton.Singleton) and not key: 

179 # We fetching the entry from a singleton - No key needed 

180 key = db.Key(skel.kindName, obj.getKey()) 

181 

182 elif not key: 

183 raise ValueError(f"getSkel has to be called with a valid key! Got {key!r}") 

184 

185 if hasattr(obj, "canView"): 

186 if not skel.read(key): 

187 logging.info(f"getSkel: Entry {key!r} not found") 

188 return None 

189 

190 if isinstance(obj, prototypes.singleton.Singleton): 

191 is_allowed = obj.canView() 

192 

193 elif isinstance(obj, prototypes.tree.Tree): 

194 if skel["key"].kind == obj.nodeSkelCls.kindName: 

195 is_allowed = obj.canView("node", skel) 

196 else: 

197 is_allowed = obj.canView("leaf", skel) 

198 

199 else: 

200 is_allowed = obj.canView(skel) 

201 

202 if not is_allowed: 

203 logging.error(f"getSkel: Access to {key} denied from canView") 

204 return None 

205 

206 elif hasattr(obj, "listFilter"): 

207 qry = skel.all().mergeExternalFilter({"key": str(key)}) 

208 qry = obj.listFilter(qry) 

209 if not qry: 

210 logging.info("listFilter permits getting entry, returning None") 

211 return None 

212 

213 if not (skel := qry.getSkel()): 

214 return None 

215 

216 else: # No Access-Test for this module 

217 if not skel.read(key): 

218 return None 

219 

220 skel.renderPreparation = render.renderBoneValue 

221 return skel 

222 

223 

224@jinjaGlobalFunction 

225def getHostUrl(render: Render, forceSSL=False, *args, **kwargs): 

226 """ 

227 Jinja2 global: Retrieve hostname with protocol. 

228 

229 :returns: Returns the hostname, including the currently used protocol, e.g: http://www.example.com 

230 :rtype: str 

231 """ 

232 url = current.request.get().request.url 

233 url = url[:url.find("/", url.find("://") + 5)] 

234 if forceSSL and url.startswith("http://"): 

235 url = "https://" + url[7:] 

236 return url 

237 

238 

239@jinjaGlobalFunction 

240def getVersionHash(render: Render) -> str: 

241 """ 

242 Jinja2 global: Return the application hash for the current version. This can be used for cache-busting in 

243 resource links (eg. /static/css/style.css?v={{ getVersionHash() }}. This hash is stable for each version 

244 deployed (identical across all instances), but will change whenever a new version is deployed. 

245 :return: The current version hash 

246 """ 

247 return conf.instance.version_hash 

248 

249 

250@jinjaGlobalFunction 

251def getAppVersion(render: Render) -> str: 

252 """ 

253 Jinja2 global: Return the application version for the current version as set on deployment. 

254 :return: The current version 

255 """ 

256 return conf.instance.app_version 

257 

258 

259@jinjaGlobalFunction 

260def redirect(render: Render, url: str) -> t.NoReturn: 

261 """ 

262 Jinja2 global: Redirect to another URL. 

263 

264 :param url: URL to redirect to. 

265 

266 :raises: :exc:`viur.core.errors.Redirect` 

267 """ 

268 raise errors.Redirect(url) 

269 

270 

271@jinjaGlobalFunction 

272def getLanguage(render: Render, resolveAlias: bool = False) -> str: 

273 """ 

274 Jinja2 global: Returns the language used for this request. 

275 

276 :param resolveAlias: If True, the function tries to resolve the current language 

277 using conf.i18n.language_alias_map. 

278 """ 

279 lang = current.language.get() 

280 if resolveAlias and lang in conf.i18n.language_alias_map: 

281 lang = conf.i18n.language_alias_map[lang] 

282 return lang 

283 

284 

285@jinjaGlobalFunction 

286def moduleName(render: Render) -> str: 

287 """ 

288 Jinja2 global: Retrieve name of current module where this renderer is used within. 

289 

290 :return: Returns the name of the current module, or empty string if there is no module set. 

291 """ 

292 if render.parent and "moduleName" in dir(render.parent): 

293 return render.parent.moduleName 

294 return "" 

295 

296 

297@jinjaGlobalFunction 

298def modulePath(render: Render) -> str: 

299 """ 

300 Jinja2 global: Retrieve path of current module the renderer is used within. 

301 

302 :return: Returns the path of the current module, or empty string if there is no module set. 

303 """ 

304 if render.parent and "modulePath" in dir(render.parent): 

305 return render.parent.modulePath 

306 return "" 

307 

308 

309@jinjaGlobalFunction 

310def getList( 

311 render: Render, 

312 module: str, 

313 skel: str = "viewSkel", 

314 *, 

315 skel_args: tuple[t.Any] = (), 

316 _noEmptyFilter: bool = False, 

317 **kwargs 

318) -> bool | None | list[SkeletonInstance]: 

319 """ 

320 Jinja2 global: Fetches a list of entries which match the given filter criteria. 

321 

322 :param render: The html-renderer instance. 

323 :param module: Name of the module from which list should be fetched. 

324 :param skel: Name of the skeleton that is used to fetching the list. 

325 :param skel_arg: Optional skeleton arguments to be passed to the skel-function (e.g. for Tree-Modules) 

326 :param _noEmptyFilter: If True, this function will not return any results if at least one 

327 parameter is an empty list. This is useful to prevent filtering (e.g. by key) not being 

328 performed because the list is empty. 

329 

330 :returns: Returns a dict that contains the "skellist" and "cursor" information, 

331 or None on error case. 

332 """ 

333 if not (caller := getattr(conf.main_app, module, None)): 

334 raise ValueError(f"getList: Can't fetch a list from unknown module {module!r}") 

335 

336 if not getattr(caller, "html", False): 

337 raise PermissionError(f"getList: module {module!r} is not allowed to be accessed from html") 

338 

339 if not hasattr(caller, "listFilter"): 

340 raise NotImplementedError(f"getList: The module {module!r} is not designed for a list retrieval") 

341 

342 # Retrieve a skeleton 

343 skel = getattr(caller, skel)(*skel_args) 

344 if not isinstance(skel, SkeletonInstance): 

345 raise RuntimeError("getList: Invalid skel name provided") 

346 

347 # Test if any value of kwargs is an empty list 

348 if _noEmptyFilter and any(isinstance(value, list) and not value for value in kwargs.values()): 

349 return [] 

350 

351 # Create initial query 

352 query = skel.all().mergeExternalFilter(kwargs) 

353 

354 if query := caller.listFilter(query): 

355 caller._apply_default_order(query) 

356 

357 if query is None: 

358 return None 

359 

360 mylist = query.fetch() 

361 

362 if mylist: 

363 for skel in mylist: 

364 skel.renderPreparation = render.renderBoneValue 

365 

366 return mylist 

367 

368 

369@jinjaGlobalFunction 

370def getSecurityKey(render: Render, **kwargs) -> str: 

371 """ 

372 Jinja2 global: Creates a new ViUR security key. 

373 """ 

374 return securitykey.create(**kwargs) 

375 

376 

377@jinjaGlobalFunction 

378def getStructure(render: Render, 

379 module: str, 

380 skel: str = "viewSkel", 

381 subSkel: t.Optional[str] = None) -> dict | bool: 

382 """ 

383 Jinja2 global: Returns the skeleton structure instead of data for a module. 

384 

385 :param render: The html-renderer instance. 

386 :param module: Module from which the skeleton is retrieved. 

387 :param skel: Name of the skeleton. 

388 :param subSkel: If set, return just that subskel instead of the whole skeleton 

389 """ 

390 if module not in dir(conf.main_app): 

391 return False 

392 

393 obj = getattr(conf.main_app, module) 

394 

395 if skel in dir(obj): 

396 skel = getattr(obj, skel)() 

397 

398 if isinstance(skel, SkeletonInstance) or isinstance(skel, RelSkel): 

399 if subSkel is not None: 

400 try: 

401 skel = skel.subSkel(subSkel) 

402 

403 except Exception as e: 

404 logging.exception(e) 

405 return False 

406 

407 return skel.structure() 

408 

409 return False 

410 

411 

412@jinjaGlobalFunction 

413def requestParams(render: Render) -> dict[str, str]: 

414 """ 

415 Jinja2 global: Allows for accessing the request-parameters from the template. 

416 

417 These returned values are escaped, as users tend to use these in an unsafe manner. 

418 

419 :returns: dict of parameter and values. 

420 """ 

421 res = {} 

422 for k, v in current.request.get().kwargs.items(): 

423 res[utils.string.escape(k)] = utils.string.escape(v) 

424 return res 

425 

426 

427@jinjaGlobalFunction 

428def updateURL(render: Render, **kwargs) -> str: 

429 """ 

430 Jinja2 global: Constructs a new URL based on the current requests url. 

431 

432 Given parameters are replaced if they exists in the current requests url, otherwise there appended. 

433 

434 :returns: Returns a well-formed URL. 

435 """ 

436 tmpparams = {} 

437 tmpparams.update(current.request.get().kwargs) 

438 

439 for key in list(tmpparams.keys()): 

440 if not key or key[0] == "_": 

441 del tmpparams[key] 

442 

443 for key, value in list(kwargs.items()): 

444 if value is None: 

445 if key in tmpparams: 

446 del tmpparams[key] 

447 else: 

448 tmpparams[key] = value 

449 

450 return "?" + urllib.parse.urlencode(tmpparams).replace("&", "&") 

451 

452 

453@jinjaGlobalFilter 

454@deprecated(version="3.7.0", reason="Use Jinja filter filesizeformat instead", action="always") 

455def fileSize(render: Render, value: int | float, binary: bool = False) -> str: 

456 """ 

457 Jinja2 filter: Format the value in an 'human-readable' file size (i.e. 13 kB, 4.1 MB, 102 Bytes, etc). 

458 Per default, decimal prefixes are used (Mega, Giga, etc.). When the second parameter is set to True, 

459 the binary prefixes are used (Mebi, Gibi). 

460 

461 :param render: The html-renderer instance. 

462 :param value: Value to be calculated. 

463 :param binary: Decimal prefixes behavior 

464 

465 :returns: The formatted file size string in human readable format. 

466 """ 

467 return jinja2.filters.do_filesizeformat(value, binary) 

468 

469 

470# TODO 

471''' 

472This has been disabled until we are sure 

473 a) what use-cases it has 

474 b) how it's best implemented 

475 c) doesn't introduce any XSS vulnerability 

476 - TS 13.03.2016 

477@jinjaGlobalFilter 

478def className(render: Render, s: str) -> str: 

479 """ 

480 Jinja2 filter: Converts a URL or name into a CSS-class name, by replacing slashes by underscores. 

481 Example call could be```{{self|string|toClassName}}```. 

482 

483 :param s: The string to be converted, probably ``self|string`` in the Jinja2 template. 

484 

485 :return: CSS class name. 

486 """ 

487 l = re.findall('\'([^\']*)\'', str(s)) 

488 if l: 

489 l = set(re.split(r'/|_', l[0].replace(".html", ""))) 

490 return " ".join(l) 

491 

492 return "" 

493''' 

494 

495 

496@jinjaGlobalFilter 

497def shortKey(render: Render, val: str) -> t.Optional[str]: 

498 """ 

499 Jinja2 filter: Make a shortkey from an entity-key. 

500 

501 :param render: The html-renderer instance. 

502 :param val: Entity-key as string. 

503 

504 :returns: Shortkey on success, None on error. 

505 """ 

506 try: 

507 k = db.Key.from_legacy_urlsafe(str(val)) 

508 return k.id_or_name 

509 except: 

510 return None 

511 

512 

513@jinjaGlobalFunction 

514def renderEditBone(render: Render, skel, boneName, boneErrors=None, prefix=None): 

515 if not isinstance(skel, dict) or not all([x in skel for x in ["errors", "structure", "value"]]): 

516 raise ValueError("This does not look like an editable Skeleton!") 

517 

518 boneParams = skel["structure"].get(boneName) 

519 

520 if not boneParams: 

521 raise ValueError(f"Bone {boneName} is not part of that skeleton") 

522 

523 if not boneParams["visible"]: 

524 fileName = "editform_bone_hidden" 

525 else: 

526 fileName = "editform_bone_" + boneParams["type"] 

527 

528 while fileName: 

529 try: 

530 fn = render.getTemplateFileName(fileName) 

531 break 

532 

533 except errors.NotFound: 

534 if "." in fileName: 

535 fileName, unused = fileName.rsplit(".", 1) 

536 else: 

537 fn = render.getTemplateFileName("editform_bone_bone") 

538 break 

539 

540 tpl = render.getEnv().get_template(fn) 

541 

542 return tpl.render( 

543 boneName=((prefix + ".") if prefix else "") + boneName, 

544 boneParams=boneParams, 

545 boneValue=skel["value"][boneName] if boneName in skel["value"] else None, 

546 boneErrors=boneErrors 

547 ) 

548 

549 

550@jinjaGlobalFunction 

551def renderEditForm(render: Render, 

552 skel: dict, 

553 ignore: list[str] = None, 

554 hide: list[str] = None, 

555 prefix=None, 

556 bones: list[str] = None, 

557 ) -> str: 

558 """Render an edit-form based on a skeleton. 

559 

560 Render an HTML-form with lables and inputs from the skeleton structure 

561 using templates for each bone-types. 

562 

563 :param render: The html-renderer instance. 

564 :param skel: The skelton which provides the structure. 

565 :param ignore: Don't render fields for these bones (name of the bone). 

566 :param hide: Render these fields as hidden fields (name of the bone). 

567 :param prefix: Prefix added to the bone names. 

568 :param bones: If set only the bone with a name in the list would be rendered. 

569 

570 :return: A string containing the HTML-form. 

571 """ 

572 if not isinstance(skel, dict) or not all([x in skel.keys() for x in ["errors", "structure", "value"]]): 

573 raise ValueError("This does not look like an editable Skeleton!") 

574 

575 res = "" 

576 

577 sectionTpl = render.getEnv().get_template(render.getTemplateFileName("editform_category")) 

578 rowTpl = render.getEnv().get_template(render.getTemplateFileName("editform_row")) 

579 sections = OrderedDict() 

580 

581 if ignore and bones and (both := set(ignore).intersection(bones)): 

582 raise ValueError(f"You have specified the same bones {', '.join(both)} in *ignore* AND *bones*!") 

583 for boneName, boneParams in skel["structure"].items(): 

584 category = str("server.render.html.default_category") 

585 if "params" in boneParams and isinstance(boneParams["params"], dict): 

586 category = boneParams["params"].get("category", category) 

587 if not category in sections: 

588 sections[category] = [] 

589 

590 sections[category].append((boneName, boneParams)) 

591 

592 for category, boneList in sections.items(): 

593 allReadOnly = True 

594 allHidden = True 

595 categoryContent = "" 

596 

597 for boneName, boneParams in boneList: 

598 if ignore and boneName in ignore: 

599 continue 

600 if bones and boneName not in bones: 

601 continue 

602 # print("--- skel[\"errors\"] ---") 

603 # print(skel["errors"]) 

604 

605 pathToBone = ((prefix + ".") if prefix else "") + boneName 

606 boneErrors = [entry for entry in skel["errors"] if ".".join(entry.fieldPath).startswith(pathToBone)] 

607 

608 if hide and boneName in hide: 

609 boneParams["visible"] = False 

610 

611 if not boneParams["readonly"]: 

612 allReadOnly = False 

613 

614 if boneParams["visible"]: 

615 allHidden = False 

616 

617 editWidget = renderEditBone(render, skel, boneName, boneErrors, prefix=prefix) 

618 categoryContent += rowTpl.render( 

619 boneName=pathToBone, 

620 boneParams=boneParams, 

621 boneErrors=boneErrors, 

622 editWidget=editWidget 

623 ) 

624 

625 res += sectionTpl.render( 

626 categoryName=category, 

627 categoryClassName="".join(ch for ch in str(category) if ch in string.ascii_letters), 

628 categoryContent=categoryContent, 

629 allReadOnly=allReadOnly, 

630 allHidden=allHidden 

631 ) 

632 

633 return res 

634 

635 

636@jinjaGlobalFunction 

637def embedSvg(render: Render, name: str, classes: list[str] | None = None, **kwargs: dict[str, str]) -> str: 

638 """ 

639 jinja2 function to get an <img/>-tag for a SVG. 

640 This method will not check the existence of a SVG! 

641 

642 :param render: The jinja renderer instance 

643 :param name: Name of the icon (basename of file) 

644 :param classes: A list of css-classes for the <img/>-tag 

645 :param kwargs: Further html-attributes for this tag (e.g. "alt" or "title") 

646 :return: A <img/>-tag 

647 """ 

648 if any([x in name for x in ["..", "~", "/"]]): 

649 return "" 

650 

651 if classes is None: 

652 classes = ["js-svg", name.split("-", 1)[0]] 

653 else: 

654 assert isinstance(classes, list), "*classes* must be a list" 

655 classes.extend(["js-svg", name.split("-", 1)[0]]) 

656 

657 attributes = { 

658 "src": os.path.join(conf.static_embed_svg_path, f"{name}.svg"), 

659 "class": " ".join(classes), 

660 **kwargs 

661 } 

662 return f"""<img {" ".join(f'{k}="{v}"' for k, v in attributes.items())}>""" 

663 

664 

665@jinjaGlobalFunction 

666def downloadUrlFor( 

667 render: Render, 

668 fileObj: dict, 

669 expires: t.Optional[int] = conf.render_html_download_url_expiration, 

670 derived: t.Optional[str] = None, 

671 downloadFileName: t.Optional[str] = None, 

672 language: t.Optional[str] = None, 

673) -> str: 

674 """ 

675 Constructs a signed download-url for the given file-bone. Mostly a wrapper around 

676 :meth:`file.File.create_download_url`. 

677 

678 :param render: The jinja renderer instance 

679 :param fileObj: The file-bone (eg. skel["file"]) 

680 :param expires: 

681 None if the file is supposed to be public 

682 (which causes it to be cached on the google ede caches), otherwise it's lifetime in seconds. 

683 :param derived: 

684 Optional the filename of a derived file, 

685 otherwise the download-link will point to the originally uploaded file. 

686 :param downloadFileName: The filename to use when saving the response payload locally. 

687 :param language: Language overwrite if fileObj has multiple languages and we want to explicitly specify one 

688 :return: THe signed download-url relative to the current domain (eg /download/...) 

689 """ 

690 

691 if isinstance(fileObj, LanguageWrapper): 

692 language = language or current.language.get() 

693 if not language or not (fileObj := fileObj.get(language)): 

694 return "" 

695 

696 if "dlkey" not in fileObj and "dest" in fileObj: 

697 fileObj = fileObj["dest"] 

698 

699 if expires: 

700 expires = timedelta(minutes=expires) 

701 

702 if not isinstance(fileObj, (SkeletonInstance, dict)) or "dlkey" not in fileObj or "name" not in fileObj: 

703 logging.error("Invalid fileObj supplied") 

704 return "" 

705 

706 if derived and ("derived" not in fileObj or not isinstance(fileObj["derived"], dict)): 

707 logging.error("No derivation for this fileObj") 

708 return "" 

709 

710 if derived: 

711 return file.File.create_download_url( 

712 fileObj["dlkey"], 

713 filename=derived, 

714 derived=True, 

715 expires=expires, 

716 download_filename=downloadFileName, 

717 ) 

718 

719 return file.File.create_download_url( 

720 fileObj["dlkey"], 

721 filename=fileObj["name"], 

722 expires=expires, 

723 download_filename=downloadFileName 

724 ) 

725 

726 

727@jinjaGlobalFunction 

728def srcSetFor( 

729 render: Render, 

730 fileObj: dict, 

731 expires: t.Optional[int] = conf.render_html_download_url_expiration, 

732 width: t.Optional[int] = None, 

733 height: t.Optional[int] = None, 

734 language: t.Optional[str] = None, 

735) -> str: 

736 """ 

737 Generates a string suitable for use as the srcset tag in html. This functionality provides the browser with a list 

738 of images in different sizes and allows it to choose the smallest file that will fill it's viewport without 

739 upscaling. 

740 

741 :param render: 

742 The render instance that's calling this function. 

743 :param fileObj: 

744 The file-bone (or if multiple=True a single value from it) to generate the srcset for. 

745 :param expires: None if the file is supposed to be public 

746 (which causes it to be cached on the google ede caches), otherwise it's lifetime in seconds. 

747 :param width: A list of widths that should be included in the srcset. 

748 If a given width is not available, it will be skipped. 

749 :param height: A list of heights that should be included in the srcset. 

750 If a given height is not available, it will be skipped. 

751 :param language: Language overwrite if fileObj has multiple languages and we want to explicitly specify one 

752 :return: The srctag generated or an empty string if a invalid file object was supplied 

753 """ 

754 return file.File.create_src_set(fileObj, expires, width, height, language) 

755 

756 

757@jinjaGlobalFunction 

758def serving_url_for(render: Render, *args, **kwargs): 

759 """ 

760 Jinja wrapper for File.create_internal_serving_url(), see there for parameter information. 

761 """ 

762 return file.File.create_internal_serving_url(*args, **kwargs) 

763 

764@jinjaGlobalFunction 

765def seoUrlForEntry(render: Render, *args, **kwargs): 

766 return utils.seoUrlToEntry(*args, **kwargs) 

767 

768 

769@jinjaGlobalFunction 

770def seoUrlToFunction(render: Render, *args, **kwargs): 

771 return utils.seoUrlToFunction(*args, **kwargs) 

772 

773 

774@jinjaGlobalFunction 

775def qrcode(render: Render, data: str) -> str: 

776 """ 

777 Generates a SVG string for a html template 

778 

779 :param data: Any string data that should render to a QR Code. 

780 

781 :return: The SVG string representation. 

782 """ 

783 return qrcode_make(data, image_factory=qrcode_svg.SvgPathImage, box_size=30).to_string().decode("utf-8")