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
« 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
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
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
37 See also :class:`core.i18n.TranslationExtension`.
38 """
39 return translate_class(key, default_text, hint, force_lang, caller_is_jinja=True)(**kwargs)
42@jinjaGlobalFunction
43def execRequest(render: Render, path: str, *args, **kwargs) -> t.Any:
44 """
45 Jinja2 global: Perform an internal Request.
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.
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.
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})"
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
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.
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
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.
154 It is possible to specify a different data-model as the one used for rendering
155 (e.g. an editSkel).
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)
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}")
170 if not getattr(obj, "html", False):
171 raise PermissionError(f"getSkel: module {module!r} is not allowed to be accessed")
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")
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())
182 elif not key:
183 raise ValueError(f"getSkel has to be called with a valid key! Got {key!r}")
185 if hasattr(obj, "canView"):
186 if not skel.read(key):
187 logging.info(f"getSkel: Entry {key!r} not found")
188 return None
190 if isinstance(obj, prototypes.singleton.Singleton):
191 is_allowed = obj.canView()
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)
199 else:
200 is_allowed = obj.canView(skel)
202 if not is_allowed:
203 logging.error(f"getSkel: Access to {key} denied from canView")
204 return None
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
213 if not (skel := qry.getSkel()):
214 return None
216 else: # No Access-Test for this module
217 if not skel.read(key):
218 return None
220 skel.renderPreparation = render.renderBoneValue
221 return skel
224@jinjaGlobalFunction
225def getHostUrl(render: Render, forceSSL=False, *args, **kwargs):
226 """
227 Jinja2 global: Retrieve hostname with protocol.
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
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
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
259@jinjaGlobalFunction
260def redirect(render: Render, url: str) -> t.NoReturn:
261 """
262 Jinja2 global: Redirect to another URL.
264 :param url: URL to redirect to.
266 :raises: :exc:`viur.core.errors.Redirect`
267 """
268 raise errors.Redirect(url)
271@jinjaGlobalFunction
272def getLanguage(render: Render, resolveAlias: bool = False) -> str:
273 """
274 Jinja2 global: Returns the language used for this request.
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
285@jinjaGlobalFunction
286def moduleName(render: Render) -> str:
287 """
288 Jinja2 global: Retrieve name of current module where this renderer is used within.
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 ""
297@jinjaGlobalFunction
298def modulePath(render: Render) -> str:
299 """
300 Jinja2 global: Retrieve path of current module the renderer is used within.
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 ""
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.
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.
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}")
336 if not getattr(caller, "html", False):
337 raise PermissionError(f"getList: module {module!r} is not allowed to be accessed from html")
339 if not hasattr(caller, "listFilter"):
340 raise NotImplementedError(f"getList: The module {module!r} is not designed for a list retrieval")
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")
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 []
351 # Create initial query
352 query = skel.all().mergeExternalFilter(kwargs)
354 if query := caller.listFilter(query):
355 caller._apply_default_order(query)
357 if query is None:
358 return None
360 mylist = query.fetch()
362 if mylist:
363 for skel in mylist:
364 skel.renderPreparation = render.renderBoneValue
366 return mylist
369@jinjaGlobalFunction
370def getSecurityKey(render: Render, **kwargs) -> str:
371 """
372 Jinja2 global: Creates a new ViUR security key.
373 """
374 return securitykey.create(**kwargs)
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.
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
393 obj = getattr(conf.main_app, module)
395 if skel in dir(obj):
396 skel = getattr(obj, skel)()
398 if isinstance(skel, SkeletonInstance) or isinstance(skel, RelSkel):
399 if subSkel is not None:
400 try:
401 skel = skel.subSkel(subSkel)
403 except Exception as e:
404 logging.exception(e)
405 return False
407 return skel.structure()
409 return False
412@jinjaGlobalFunction
413def requestParams(render: Render) -> dict[str, str]:
414 """
415 Jinja2 global: Allows for accessing the request-parameters from the template.
417 These returned values are escaped, as users tend to use these in an unsafe manner.
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
427@jinjaGlobalFunction
428def updateURL(render: Render, **kwargs) -> str:
429 """
430 Jinja2 global: Constructs a new URL based on the current requests url.
432 Given parameters are replaced if they exists in the current requests url, otherwise there appended.
434 :returns: Returns a well-formed URL.
435 """
436 tmpparams = {}
437 tmpparams.update(current.request.get().kwargs)
439 for key in list(tmpparams.keys()):
440 if not key or key[0] == "_":
441 del tmpparams[key]
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
450 return "?" + urllib.parse.urlencode(tmpparams).replace("&", "&")
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).
461 :param render: The html-renderer instance.
462 :param value: Value to be calculated.
463 :param binary: Decimal prefixes behavior
465 :returns: The formatted file size string in human readable format.
466 """
467 return jinja2.filters.do_filesizeformat(value, binary)
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}}```.
483 :param s: The string to be converted, probably ``self|string`` in the Jinja2 template.
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)
492 return ""
493'''
496@jinjaGlobalFilter
497def shortKey(render: Render, val: str) -> t.Optional[str]:
498 """
499 Jinja2 filter: Make a shortkey from an entity-key.
501 :param render: The html-renderer instance.
502 :param val: Entity-key as string.
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
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!")
518 boneParams = skel["structure"].get(boneName)
520 if not boneParams:
521 raise ValueError(f"Bone {boneName} is not part of that skeleton")
523 if not boneParams["visible"]:
524 fileName = "editform_bone_hidden"
525 else:
526 fileName = "editform_bone_" + boneParams["type"]
528 while fileName:
529 try:
530 fn = render.getTemplateFileName(fileName)
531 break
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
540 tpl = render.getEnv().get_template(fn)
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 )
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.
560 Render an HTML-form with lables and inputs from the skeleton structure
561 using templates for each bone-types.
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.
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!")
575 res = ""
577 sectionTpl = render.getEnv().get_template(render.getTemplateFileName("editform_category"))
578 rowTpl = render.getEnv().get_template(render.getTemplateFileName("editform_row"))
579 sections = OrderedDict()
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] = []
590 sections[category].append((boneName, boneParams))
592 for category, boneList in sections.items():
593 allReadOnly = True
594 allHidden = True
595 categoryContent = ""
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"])
605 pathToBone = ((prefix + ".") if prefix else "") + boneName
606 boneErrors = [entry for entry in skel["errors"] if ".".join(entry.fieldPath).startswith(pathToBone)]
608 if hide and boneName in hide:
609 boneParams["visible"] = False
611 if not boneParams["readonly"]:
612 allReadOnly = False
614 if boneParams["visible"]:
615 allHidden = False
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 )
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 )
633 return res
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!
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 ""
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]])
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())}>"""
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`.
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 """
691 if isinstance(fileObj, LanguageWrapper):
692 language = language or current.language.get()
693 if not language or not (fileObj := fileObj.get(language)):
694 return ""
696 if "dlkey" not in fileObj and "dest" in fileObj:
697 fileObj = fileObj["dest"]
699 if expires:
700 expires = timedelta(minutes=expires)
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 ""
706 if derived and ("derived" not in fileObj or not isinstance(fileObj["derived"], dict)):
707 logging.error("No derivation for this fileObj")
708 return ""
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 )
719 return file.File.create_download_url(
720 fileObj["dlkey"],
721 filename=fileObj["name"],
722 expires=expires,
723 download_filename=downloadFileName
724 )
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.
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)
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)
764@jinjaGlobalFunction
765def seoUrlForEntry(render: Render, *args, **kwargs):
766 return utils.seoUrlToEntry(*args, **kwargs)
769@jinjaGlobalFunction
770def seoUrlToFunction(render: Render, *args, **kwargs):
771 return utils.seoUrlToFunction(*args, **kwargs)
774@jinjaGlobalFunction
775def qrcode(render: Render, data: str) -> str:
776 """
777 Generates a SVG string for a html template
779 :param data: Any string data that should render to a QR Code.
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")