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

346 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-07 19:28 +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 if not (obj := getattr(conf.main_app, module, None)): 

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

167 

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

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

170 

171 # Retrieve a skeleton 

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

173 if not isinstance(skel, SkeletonInstance): 

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

175 

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

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

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

179 

180 elif not key: 

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

182 

183 if hasattr(obj, "canView"): 

184 if not skel.read(key): 

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

186 return None 

187 

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

189 is_allowed = obj.canView() 

190 

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

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

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

194 else: 

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

196 

197 else: 

198 is_allowed = obj.canView(skel) 

199 

200 if not is_allowed: 

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

202 return None 

203 

204 elif hasattr(obj, "listFilter"): 

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

206 qry = obj.listFilter(qry) 

207 if not qry: 

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

209 return None 

210 

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

212 return None 

213 

214 else: # No Access-Test for this module 

215 if not skel.read(key): 

216 return None 

217 

218 skel.renderPreparation = render.renderBoneValue 

219 return skel 

220 

221 

222@jinjaGlobalFunction 

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

224 """ 

225 Jinja2 global: Retrieve hostname with protocol. 

226 

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

228 :rtype: str 

229 """ 

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

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

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

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

234 return url 

235 

236 

237@jinjaGlobalFunction 

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

239 """ 

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

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

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

243 :return: The current version hash 

244 """ 

245 return conf.instance.version_hash 

246 

247 

248@jinjaGlobalFunction 

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

250 """ 

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

252 :return: The current version 

253 """ 

254 return conf.instance.app_version 

255 

256 

257@jinjaGlobalFunction 

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

259 """ 

260 Jinja2 global: Redirect to another URL. 

261 

262 :param url: URL to redirect to. 

263 

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

265 """ 

266 raise errors.Redirect(url) 

267 

268 

269@jinjaGlobalFunction 

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

271 """ 

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

273 

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

275 using conf.i18n.language_alias_map. 

276 """ 

277 lang = current.language.get() 

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

279 lang = conf.i18n.language_alias_map[lang] 

280 return lang 

281 

282 

283@jinjaGlobalFunction 

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

285 """ 

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

287 

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

289 """ 

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

291 return render.parent.moduleName 

292 return "" 

293 

294 

295@jinjaGlobalFunction 

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

297 """ 

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

299 

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

301 """ 

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

303 return render.parent.modulePath 

304 return "" 

305 

306 

307@jinjaGlobalFunction 

308def getList( 

309 render: Render, 

310 module: str, 

311 skel: str = "viewSkel", 

312 *, 

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

314 _noEmptyFilter: bool = False, 

315 **kwargs 

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

317 """ 

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

319 

320 :param render: The html-renderer instance. 

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

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

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

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

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

326 performed because the list is empty. 

327 

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

329 or None on error case. 

330 """ 

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

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

333 

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

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

336 

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

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

339 

340 # Retrieve a skeleton 

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

342 if not isinstance(skel, SkeletonInstance): 

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

344 

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

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

347 return [] 

348 

349 # Create initial query 

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

351 

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

353 caller._apply_default_order(query) 

354 

355 if query is None: 

356 return None 

357 

358 mylist = query.fetch() 

359 

360 if mylist: 

361 for skel in mylist: 

362 skel.renderPreparation = render.renderBoneValue 

363 

364 return mylist 

365 

366 

367@jinjaGlobalFunction 

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

369 """ 

370 Jinja2 global: Creates a new ViUR security key. 

371 """ 

372 return securitykey.create(**kwargs) 

373 

374 

375@jinjaGlobalFunction 

376def getStructure(render: Render, 

377 module: str, 

378 skel: str = "viewSkel", 

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

380 """ 

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

382 

383 :param render: The html-renderer instance. 

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

385 :param skel: Name of the skeleton. 

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

387 """ 

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

389 return False 

390 

391 obj = getattr(conf.main_app, module) 

392 

393 if skel in dir(obj): 

394 skel = getattr(obj, skel)() 

395 

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

397 if subSkel is not None: 

398 try: 

399 skel = skel.subSkel(subSkel) 

400 

401 except Exception as e: 

402 logging.exception(e) 

403 return False 

404 

405 return skel.structure() 

406 

407 return False 

408 

409 

410@jinjaGlobalFunction 

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

412 """ 

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

414 

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

416 

417 :returns: dict of parameter and values. 

418 """ 

419 res = {} 

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

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

422 return res 

423 

424 

425@jinjaGlobalFunction 

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

427 """ 

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

429 

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

431 

432 :returns: Returns a well-formed URL. 

433 """ 

434 tmpparams = {} 

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

436 

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

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

439 del tmpparams[key] 

440 

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

442 if value is None: 

443 if key in tmpparams: 

444 del tmpparams[key] 

445 else: 

446 tmpparams[key] = value 

447 

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

449 

450 

451@jinjaGlobalFilter 

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

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

454 """ 

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

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

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

458 

459 :param render: The html-renderer instance. 

460 :param value: Value to be calculated. 

461 :param binary: Decimal prefixes behavior 

462 

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

464 """ 

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

466 

467 

468# TODO 

469''' 

470This has been disabled until we are sure 

471 a) what use-cases it has 

472 b) how it's best implemented 

473 c) doesn't introduce any XSS vulnerability 

474 - TS 13.03.2016 

475@jinjaGlobalFilter 

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

477 """ 

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

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

480 

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

482 

483 :return: CSS class name. 

484 """ 

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

486 if l: 

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

488 return " ".join(l) 

489 

490 return "" 

491''' 

492 

493 

494@jinjaGlobalFilter 

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

496 """ 

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

498 

499 :param render: The html-renderer instance. 

500 :param val: Entity-key as string. 

501 

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

503 """ 

504 try: 

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

506 return k.id_or_name 

507 except: 

508 return None 

509 

510 

511@jinjaGlobalFunction 

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

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

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

515 

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

517 

518 if not boneParams: 

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

520 

521 if not boneParams["visible"]: 

522 fileName = "editform_bone_hidden" 

523 else: 

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

525 

526 while fileName: 

527 try: 

528 fn = render.getTemplateFileName(fileName) 

529 break 

530 

531 except errors.NotFound: 

532 if "." in fileName: 

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

534 else: 

535 fn = render.getTemplateFileName("editform_bone_bone") 

536 break 

537 

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

539 

540 return tpl.render( 

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

542 boneParams=boneParams, 

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

544 boneErrors=boneErrors 

545 ) 

546 

547 

548@jinjaGlobalFunction 

549def renderEditForm(render: Render, 

550 skel: dict, 

551 ignore: list[str] = None, 

552 hide: list[str] = None, 

553 prefix=None, 

554 bones: list[str] = None, 

555 ) -> str: 

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

557 

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

559 using templates for each bone-types. 

560 

561 :param render: The html-renderer instance. 

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

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

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

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

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

567 

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

569 """ 

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

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

572 

573 res = "" 

574 

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

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

577 sections = OrderedDict() 

578 

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

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

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

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

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

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

585 if not category in sections: 

586 sections[category] = [] 

587 

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

589 

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

591 allReadOnly = True 

592 allHidden = True 

593 categoryContent = "" 

594 

595 for boneName, boneParams in boneList: 

596 if ignore and boneName in ignore: 

597 continue 

598 if bones and boneName not in bones: 

599 continue 

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

601 # print(skel["errors"]) 

602 

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

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

605 

606 if hide and boneName in hide: 

607 boneParams["visible"] = False 

608 

609 if not boneParams["readonly"]: 

610 allReadOnly = False 

611 

612 if boneParams["visible"]: 

613 allHidden = False 

614 

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

616 categoryContent += rowTpl.render( 

617 boneName=pathToBone, 

618 boneParams=boneParams, 

619 boneErrors=boneErrors, 

620 editWidget=editWidget 

621 ) 

622 

623 res += sectionTpl.render( 

624 categoryName=category, 

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

626 categoryContent=categoryContent, 

627 allReadOnly=allReadOnly, 

628 allHidden=allHidden 

629 ) 

630 

631 return res 

632 

633 

634@jinjaGlobalFunction 

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

636 """ 

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

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

639 

640 :param render: The jinja renderer instance 

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

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

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

644 :return: A <img/>-tag 

645 """ 

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

647 return "" 

648 

649 if classes is None: 

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

651 else: 

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

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

654 

655 attributes = { 

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

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

658 **kwargs 

659 } 

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

661 

662 

663@jinjaGlobalFunction 

664def downloadUrlFor( 

665 render: Render, 

666 fileObj: dict, 

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

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

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

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

671) -> str: 

672 """ 

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

674 :meth:`file.File.create_download_url`. 

675 

676 :param render: The jinja renderer instance 

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

678 :param expires: 

679 None if the file is supposed to be public 

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

681 :param derived: 

682 Optional the filename of a derived file, 

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

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

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

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

687 """ 

688 

689 if isinstance(fileObj, LanguageWrapper): 

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

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

692 return "" 

693 

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

695 fileObj = fileObj["dest"] 

696 

697 if expires: 

698 expires = timedelta(minutes=expires) 

699 

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

701 logging.error("Invalid fileObj supplied") 

702 return "" 

703 

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

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

706 return "" 

707 

708 if derived: 

709 return file.File.create_download_url( 

710 fileObj["dlkey"], 

711 filename=derived, 

712 derived=True, 

713 expires=expires, 

714 download_filename=downloadFileName, 

715 ) 

716 

717 return file.File.create_download_url( 

718 fileObj["dlkey"], 

719 filename=fileObj["name"], 

720 expires=expires, 

721 download_filename=downloadFileName 

722 ) 

723 

724 

725@jinjaGlobalFunction 

726def srcSetFor( 

727 render: Render, 

728 fileObj: dict, 

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

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

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

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

733) -> str: 

734 """ 

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

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

737 upscaling. 

738 

739 :param render: 

740 The render instance that's calling this function. 

741 :param fileObj: 

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

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

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

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

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

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

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

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

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

751 """ 

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

753 

754 

755@jinjaGlobalFunction 

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

757 """ 

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

759 """ 

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

761 

762@jinjaGlobalFunction 

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

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

765 

766 

767@jinjaGlobalFunction 

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

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

770 

771 

772@jinjaGlobalFunction 

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

774 """ 

775 Generates a SVG string for a html template 

776 

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

778 

779 :return: The SVG string representation. 

780 """ 

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