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
« 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
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 if not (obj := getattr(conf.main_app, module, None)):
166 raise ValueError(f"getSkel: Can't read a skeleton from unknown module {module!r}")
168 if not getattr(obj, "html", False):
169 raise PermissionError(f"getSkel: module {module!r} is not allowed to be accessed")
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")
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())
180 elif not key:
181 raise ValueError(f"getSkel has to be called with a valid key! Got {key!r}")
183 if hasattr(obj, "canView"):
184 if not skel.read(key):
185 logging.info(f"getSkel: Entry {key!r} not found")
186 return None
188 if isinstance(obj, prototypes.singleton.Singleton):
189 is_allowed = obj.canView()
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)
197 else:
198 is_allowed = obj.canView(skel)
200 if not is_allowed:
201 logging.error(f"getSkel: Access to {key} denied from canView")
202 return None
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
211 if not (skel := qry.getSkel()):
212 return None
214 else: # No Access-Test for this module
215 if not skel.read(key):
216 return None
218 skel.renderPreparation = render.renderBoneValue
219 return skel
222@jinjaGlobalFunction
223def getHostUrl(render: Render, forceSSL=False, *args, **kwargs):
224 """
225 Jinja2 global: Retrieve hostname with protocol.
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
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
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
257@jinjaGlobalFunction
258def redirect(render: Render, url: str) -> t.NoReturn:
259 """
260 Jinja2 global: Redirect to another URL.
262 :param url: URL to redirect to.
264 :raises: :exc:`viur.core.errors.Redirect`
265 """
266 raise errors.Redirect(url)
269@jinjaGlobalFunction
270def getLanguage(render: Render, resolveAlias: bool = False) -> str:
271 """
272 Jinja2 global: Returns the language used for this request.
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
283@jinjaGlobalFunction
284def moduleName(render: Render) -> str:
285 """
286 Jinja2 global: Retrieve name of current module where this renderer is used within.
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 ""
295@jinjaGlobalFunction
296def modulePath(render: Render) -> str:
297 """
298 Jinja2 global: Retrieve path of current module the renderer is used within.
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 ""
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.
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.
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}")
334 if not getattr(caller, "html", False):
335 raise PermissionError(f"getList: module {module!r} is not allowed to be accessed from html")
337 if not hasattr(caller, "listFilter"):
338 raise NotImplementedError(f"getList: The module {module!r} is not designed for a list retrieval")
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")
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 []
349 # Create initial query
350 query = skel.all().mergeExternalFilter(kwargs)
352 if query := caller.listFilter(query):
353 caller._apply_default_order(query)
355 if query is None:
356 return None
358 mylist = query.fetch()
360 if mylist:
361 for skel in mylist:
362 skel.renderPreparation = render.renderBoneValue
364 return mylist
367@jinjaGlobalFunction
368def getSecurityKey(render: Render, **kwargs) -> str:
369 """
370 Jinja2 global: Creates a new ViUR security key.
371 """
372 return securitykey.create(**kwargs)
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.
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
391 obj = getattr(conf.main_app, module)
393 if skel in dir(obj):
394 skel = getattr(obj, skel)()
396 if isinstance(skel, SkeletonInstance) or isinstance(skel, RelSkel):
397 if subSkel is not None:
398 try:
399 skel = skel.subSkel(subSkel)
401 except Exception as e:
402 logging.exception(e)
403 return False
405 return skel.structure()
407 return False
410@jinjaGlobalFunction
411def requestParams(render: Render) -> dict[str, str]:
412 """
413 Jinja2 global: Allows for accessing the request-parameters from the template.
415 These returned values are escaped, as users tend to use these in an unsafe manner.
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
425@jinjaGlobalFunction
426def updateURL(render: Render, **kwargs) -> str:
427 """
428 Jinja2 global: Constructs a new URL based on the current requests url.
430 Given parameters are replaced if they exists in the current requests url, otherwise there appended.
432 :returns: Returns a well-formed URL.
433 """
434 tmpparams = {}
435 tmpparams.update(current.request.get().kwargs)
437 for key in list(tmpparams.keys()):
438 if not key or key[0] == "_":
439 del tmpparams[key]
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
448 return "?" + urllib.parse.urlencode(tmpparams).replace("&", "&")
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).
459 :param render: The html-renderer instance.
460 :param value: Value to be calculated.
461 :param binary: Decimal prefixes behavior
463 :returns: The formatted file size string in human readable format.
464 """
465 return jinja2.filters.do_filesizeformat(value, binary)
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}}```.
481 :param s: The string to be converted, probably ``self|string`` in the Jinja2 template.
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)
490 return ""
491'''
494@jinjaGlobalFilter
495def shortKey(render: Render, val: str) -> t.Optional[str]:
496 """
497 Jinja2 filter: Make a shortkey from an entity-key.
499 :param render: The html-renderer instance.
500 :param val: Entity-key as string.
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
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!")
516 boneParams = skel["structure"].get(boneName)
518 if not boneParams:
519 raise ValueError(f"Bone {boneName} is not part of that skeleton")
521 if not boneParams["visible"]:
522 fileName = "editform_bone_hidden"
523 else:
524 fileName = "editform_bone_" + boneParams["type"]
526 while fileName:
527 try:
528 fn = render.getTemplateFileName(fileName)
529 break
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
538 tpl = render.getEnv().get_template(fn)
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 )
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.
558 Render an HTML-form with lables and inputs from the skeleton structure
559 using templates for each bone-types.
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.
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!")
573 res = ""
575 sectionTpl = render.getEnv().get_template(render.getTemplateFileName("editform_category"))
576 rowTpl = render.getEnv().get_template(render.getTemplateFileName("editform_row"))
577 sections = OrderedDict()
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] = []
588 sections[category].append((boneName, boneParams))
590 for category, boneList in sections.items():
591 allReadOnly = True
592 allHidden = True
593 categoryContent = ""
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"])
603 pathToBone = ((prefix + ".") if prefix else "") + boneName
604 boneErrors = [entry for entry in skel["errors"] if ".".join(entry.fieldPath).startswith(pathToBone)]
606 if hide and boneName in hide:
607 boneParams["visible"] = False
609 if not boneParams["readonly"]:
610 allReadOnly = False
612 if boneParams["visible"]:
613 allHidden = False
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 )
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 )
631 return res
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!
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 ""
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]])
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())}>"""
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`.
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 """
689 if isinstance(fileObj, LanguageWrapper):
690 language = language or current.language.get()
691 if not language or not (fileObj := fileObj.get(language)):
692 return ""
694 if "dlkey" not in fileObj and "dest" in fileObj:
695 fileObj = fileObj["dest"]
697 if expires:
698 expires = timedelta(minutes=expires)
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 ""
704 if derived and ("derived" not in fileObj or not isinstance(fileObj["derived"], dict)):
705 logging.error("No derivation for this fileObj")
706 return ""
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 )
717 return file.File.create_download_url(
718 fileObj["dlkey"],
719 filename=fileObj["name"],
720 expires=expires,
721 download_filename=downloadFileName
722 )
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.
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)
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)
762@jinjaGlobalFunction
763def seoUrlForEntry(render: Render, *args, **kwargs):
764 return utils.seoUrlToEntry(*args, **kwargs)
767@jinjaGlobalFunction
768def seoUrlToFunction(render: Render, *args, **kwargs):
769 return utils.seoUrlToFunction(*args, **kwargs)
772@jinjaGlobalFunction
773def qrcode(render: Render, data: str) -> str:
774 """
775 Generates a SVG string for a html template
777 :param data: Any string data that should render to a QR Code.
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")