Coverage for / home / runner / work / viur-core / viur-core / viur / src / viur / core / render / html / env / viur.py: 0%
344 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 12:35 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 12:35 +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, *args, **kwargs) -> str:
226 """
227 Jinja2 global: Retrieve hostname with protocol.
229 :returns: Returns the hostname, including the currently used protocol, e.g: https://www.example.com
230 :rtype: str
231 """
232 return utils.get_base_url()
235@jinjaGlobalFunction
236def getVersionHash(render: Render) -> str:
237 """
238 Jinja2 global: Return the application hash for the current version. This can be used for cache-busting in
239 resource links (eg. /static/css/style.css?v={{ getVersionHash() }}. This hash is stable for each version
240 deployed (identical across all instances), but will change whenever a new version is deployed.
241 :return: The current version hash
242 """
243 return conf.instance.version_hash
246@jinjaGlobalFunction
247def getAppVersion(render: Render) -> str:
248 """
249 Jinja2 global: Return the application version for the current version as set on deployment.
250 :return: The current version
251 """
252 return conf.instance.app_version
255@jinjaGlobalFunction
256def redirect(render: Render, url: str) -> t.NoReturn:
257 """
258 Jinja2 global: Redirect to another URL.
260 :param url: URL to redirect to.
262 :raises: :exc:`viur.core.errors.Redirect`
263 """
264 raise errors.Redirect(url)
267@jinjaGlobalFunction
268def getLanguage(render: Render, resolveAlias: bool = False) -> str:
269 """
270 Jinja2 global: Returns the language used for this request.
272 :param resolveAlias: If True, the function tries to resolve the current language
273 using conf.i18n.language_alias_map.
274 """
275 lang = current.language.get()
276 if resolveAlias and lang in conf.i18n.language_alias_map:
277 lang = conf.i18n.language_alias_map[lang]
278 return lang
281@jinjaGlobalFunction
282def moduleName(render: Render) -> str:
283 """
284 Jinja2 global: Retrieve name of current module where this renderer is used within.
286 :return: Returns the name of the current module, or empty string if there is no module set.
287 """
288 if render.parent and "moduleName" in dir(render.parent):
289 return render.parent.moduleName
290 return ""
293@jinjaGlobalFunction
294def modulePath(render: Render) -> str:
295 """
296 Jinja2 global: Retrieve path of current module the renderer is used within.
298 :return: Returns the path of the current module, or empty string if there is no module set.
299 """
300 if render.parent and "modulePath" in dir(render.parent):
301 return render.parent.modulePath
302 return ""
305@jinjaGlobalFunction
306def getList(
307 render: Render,
308 module: str,
309 skel: str = "viewSkel",
310 *,
311 skel_args: tuple[t.Any] = (),
312 _noEmptyFilter: bool = False,
313 **kwargs
314) -> bool | None | list[SkeletonInstance]:
315 """
316 Jinja2 global: Fetches a list of entries which match the given filter criteria.
318 :param render: The html-renderer instance.
319 :param module: Name of the module from which list should be fetched.
320 :param skel: Name of the skeleton that is used to fetching the list.
321 :param skel_arg: Optional skeleton arguments to be passed to the skel-function (e.g. for Tree-Modules)
322 :param _noEmptyFilter: If True, this function will not return any results if at least one
323 parameter is an empty list. This is useful to prevent filtering (e.g. by key) not being
324 performed because the list is empty.
326 :returns: Returns a dict that contains the "skellist" and "cursor" information,
327 or None on error case.
328 """
329 if not (caller := getattr(conf.main_app, module, None)):
330 raise ValueError(f"getList: Can't fetch a list from unknown module {module!r}")
332 if not getattr(caller, "html", False):
333 raise PermissionError(f"getList: module {module!r} is not allowed to be accessed from html")
335 if not hasattr(caller, "listFilter"):
336 raise NotImplementedError(f"getList: The module {module!r} is not designed for a list retrieval")
338 # Retrieve a skeleton
339 skel = getattr(caller, skel)(*skel_args)
340 if not isinstance(skel, SkeletonInstance):
341 raise RuntimeError("getList: Invalid skel name provided")
343 # Test if any value of kwargs is an empty list
344 if _noEmptyFilter and any(isinstance(value, list) and not value for value in kwargs.values()):
345 return []
347 # Create initial query
348 query = skel.all().mergeExternalFilter(kwargs)
350 if query := caller.listFilter(query):
351 caller._apply_default_order(query)
353 if query is None:
354 return None
356 mylist = query.fetch()
358 if mylist:
359 for skel in mylist:
360 skel.renderPreparation = render.renderBoneValue
362 return mylist
365@jinjaGlobalFunction
366def getSecurityKey(render: Render, **kwargs) -> str:
367 """
368 Jinja2 global: Creates a new ViUR security key.
369 """
370 return securitykey.create(**kwargs)
373@jinjaGlobalFunction
374def getStructure(render: Render,
375 module: str,
376 skel: str = "viewSkel",
377 subSkel: t.Optional[str] = None) -> dict | bool:
378 """
379 Jinja2 global: Returns the skeleton structure instead of data for a module.
381 :param render: The html-renderer instance.
382 :param module: Module from which the skeleton is retrieved.
383 :param skel: Name of the skeleton.
384 :param subSkel: If set, return just that subskel instead of the whole skeleton
385 """
386 if module not in dir(conf.main_app):
387 return False
389 obj = getattr(conf.main_app, module)
391 if skel in dir(obj):
392 skel = getattr(obj, skel)()
394 if isinstance(skel, SkeletonInstance) or isinstance(skel, RelSkel):
395 if subSkel is not None:
396 try:
397 skel = skel.subSkel(subSkel)
399 except Exception as e:
400 logging.exception(e)
401 return False
403 return skel.structure()
405 return False
408@jinjaGlobalFunction
409def requestParams(render: Render) -> dict[str, str]:
410 """
411 Jinja2 global: Allows for accessing the request-parameters from the template.
413 These returned values are escaped, as users tend to use these in an unsafe manner.
415 :returns: dict of parameter and values.
416 """
417 res = {}
418 for k, v in current.request.get().kwargs.items():
419 res[utils.string.escape(k)] = utils.string.escape(v)
420 return res
423@jinjaGlobalFunction
424def updateURL(render: Render, **kwargs) -> str:
425 """
426 Jinja2 global: Constructs a new URL based on the current requests url.
428 Given parameters are replaced if they exists in the current requests url, otherwise there appended.
430 :returns: Returns a well-formed URL.
431 """
432 tmpparams = {}
433 tmpparams.update(current.request.get().kwargs)
435 for key in list(tmpparams.keys()):
436 if not key or key[0] == "_":
437 del tmpparams[key]
439 for key, value in list(kwargs.items()):
440 if value is None:
441 if key in tmpparams:
442 del tmpparams[key]
443 else:
444 tmpparams[key] = value
446 return "?" + urllib.parse.urlencode(tmpparams).replace("&", "&")
449@jinjaGlobalFilter
450@deprecated(version="3.7.0", reason="Use Jinja filter filesizeformat instead")
451def fileSize(render: Render, value: int | float, binary: bool = False) -> str:
452 """
453 Jinja2 filter: Format the value in an 'human-readable' file size (i.e. 13 kB, 4.1 MB, 102 Bytes, etc).
454 Per default, decimal prefixes are used (Mega, Giga, etc.). When the second parameter is set to True,
455 the binary prefixes are used (Mebi, Gibi).
457 :param render: The html-renderer instance.
458 :param value: Value to be calculated.
459 :param binary: Decimal prefixes behavior
461 :returns: The formatted file size string in human readable format.
462 """
463 return jinja2.filters.do_filesizeformat(value, binary)
466# TODO
467'''
468This has been disabled until we are sure
469 a) what use-cases it has
470 b) how it's best implemented
471 c) doesn't introduce any XSS vulnerability
472 - TS 13.03.2016
473@jinjaGlobalFilter
474def className(render: Render, s: str) -> str:
475 """
476 Jinja2 filter: Converts a URL or name into a CSS-class name, by replacing slashes by underscores.
477 Example call could be```{{self|string|toClassName}}```.
479 :param s: The string to be converted, probably ``self|string`` in the Jinja2 template.
481 :return: CSS class name.
482 """
483 l = re.findall('\'([^\']*)\'', str(s))
484 if l:
485 l = set(re.split(r'/|_', l[0].replace(".html", "")))
486 return " ".join(l)
488 return ""
489'''
492@jinjaGlobalFilter
493def shortKey(render: Render, val: str) -> t.Optional[str]:
494 """
495 Jinja2 filter: Make a shortkey from an entity-key.
497 :param render: The html-renderer instance.
498 :param val: Entity-key as string.
500 :returns: Shortkey on success, None on error.
501 """
502 try:
503 k = db.Key.from_legacy_urlsafe(str(val))
504 return k.id_or_name
505 except:
506 return None
509@jinjaGlobalFunction
510def renderEditBone(render: Render, skel, boneName, boneErrors=None, prefix=None):
511 if not isinstance(skel, dict) or not all([x in skel for x in ["errors", "structure", "value"]]):
512 raise ValueError("This does not look like an editable Skeleton!")
514 boneParams = skel["structure"].get(boneName)
516 if not boneParams:
517 raise ValueError(f"Bone {boneName} is not part of that skeleton")
519 if not boneParams["visible"]:
520 fileName = "editform_bone_hidden"
521 else:
522 fileName = "editform_bone_" + boneParams["type"]
524 while fileName:
525 try:
526 fn = render.getTemplateFileName(fileName)
527 break
529 except errors.NotFound:
530 if "." in fileName:
531 fileName, unused = fileName.rsplit(".", 1)
532 else:
533 fn = render.getTemplateFileName("editform_bone_bone")
534 break
536 tpl = render.getEnv().get_template(fn)
538 return tpl.render(
539 boneName=((prefix + ".") if prefix else "") + boneName,
540 boneParams=boneParams,
541 boneValue=skel["value"][boneName] if boneName in skel["value"] else None,
542 boneErrors=boneErrors
543 )
546@jinjaGlobalFunction
547def renderEditForm(render: Render,
548 skel: dict,
549 ignore: list[str] = None,
550 hide: list[str] = None,
551 prefix=None,
552 bones: list[str] = None,
553 ) -> str:
554 """Render an edit-form based on a skeleton.
556 Render an HTML-form with lables and inputs from the skeleton structure
557 using templates for each bone-types.
559 :param render: The html-renderer instance.
560 :param skel: The skelton which provides the structure.
561 :param ignore: Don't render fields for these bones (name of the bone).
562 :param hide: Render these fields as hidden fields (name of the bone).
563 :param prefix: Prefix added to the bone names.
564 :param bones: If set only the bone with a name in the list would be rendered.
566 :return: A string containing the HTML-form.
567 """
568 if not isinstance(skel, dict) or not all([x in skel.keys() for x in ["errors", "structure", "value"]]):
569 raise ValueError("This does not look like an editable Skeleton!")
571 res = ""
573 sectionTpl = render.getEnv().get_template(render.getTemplateFileName("editform_category"))
574 rowTpl = render.getEnv().get_template(render.getTemplateFileName("editform_row"))
575 sections = OrderedDict()
577 if ignore and bones and (both := set(ignore).intersection(bones)):
578 raise ValueError(f"You have specified the same bones {', '.join(both)} in *ignore* AND *bones*!")
579 for boneName, boneParams in skel["structure"].items():
580 category = str("server.render.html.default_category")
581 if "params" in boneParams and isinstance(boneParams["params"], dict):
582 category = boneParams["params"].get("category", category)
583 if not category in sections:
584 sections[category] = []
586 sections[category].append((boneName, boneParams))
588 for category, boneList in sections.items():
589 allReadOnly = True
590 allHidden = True
591 categoryContent = ""
593 for boneName, boneParams in boneList:
594 if ignore and boneName in ignore:
595 continue
596 if bones and boneName not in bones:
597 continue
598 # print("--- skel[\"errors\"] ---")
599 # print(skel["errors"])
601 pathToBone = ((prefix + ".") if prefix else "") + boneName
602 boneErrors = [entry for entry in skel["errors"] if ".".join(entry.fieldPath).startswith(pathToBone)]
604 if hide and boneName in hide:
605 boneParams["visible"] = False
607 if not boneParams["readonly"]:
608 allReadOnly = False
610 if boneParams["visible"]:
611 allHidden = False
613 editWidget = renderEditBone(render, skel, boneName, boneErrors, prefix=prefix)
614 categoryContent += rowTpl.render(
615 boneName=pathToBone,
616 boneParams=boneParams,
617 boneErrors=boneErrors,
618 editWidget=editWidget
619 )
621 res += sectionTpl.render(
622 categoryName=category,
623 categoryClassName="".join(ch for ch in str(category) if ch in string.ascii_letters),
624 categoryContent=categoryContent,
625 allReadOnly=allReadOnly,
626 allHidden=allHidden
627 )
629 return res
632@jinjaGlobalFunction
633def embedSvg(render: Render, name: str, classes: list[str] | None = None, **kwargs: dict[str, str]) -> str:
634 """
635 jinja2 function to get an <img/>-tag for a SVG.
636 This method will not check the existence of a SVG!
638 :param render: The jinja renderer instance
639 :param name: Name of the icon (basename of file)
640 :param classes: A list of css-classes for the <img/>-tag
641 :param kwargs: Further html-attributes for this tag (e.g. "alt" or "title")
642 :return: A <img/>-tag
643 """
644 if any([x in name for x in ["..", "~", "/"]]):
645 return ""
647 if classes is None:
648 classes = ["js-svg", name.split("-", 1)[0]]
649 else:
650 assert isinstance(classes, list), "*classes* must be a list"
651 classes.extend(["js-svg", name.split("-", 1)[0]])
653 attributes = {
654 "src": os.path.join(conf.static_embed_svg_path, f"{name}.svg"),
655 "class": " ".join(classes),
656 **kwargs
657 }
658 return f"""<img {" ".join(f'{k}="{v}"' for k, v in attributes.items())}>"""
661@jinjaGlobalFunction
662def downloadUrlFor(
663 render: Render,
664 fileObj: dict,
665 expires: t.Optional[int] = conf.render_html_download_url_expiration,
666 derived: t.Optional[str] = None,
667 downloadFileName: t.Optional[str] = None,
668 language: t.Optional[str] = None,
669) -> str:
670 """
671 Constructs a signed download-url for the given file-bone. Mostly a wrapper around
672 :meth:`file.File.create_download_url`.
674 :param render: The jinja renderer instance
675 :param fileObj: The file-bone (eg. skel["file"])
676 :param expires:
677 None if the file is supposed to be public
678 (which causes it to be cached on the google ede caches), otherwise it's lifetime in seconds.
679 :param derived:
680 Optional the filename of a derived file,
681 otherwise the download-link will point to the originally uploaded file.
682 :param downloadFileName: The filename to use when saving the response payload locally.
683 :param language: Language overwrite if fileObj has multiple languages and we want to explicitly specify one
684 :return: THe signed download-url relative to the current domain (eg /download/...)
685 """
687 if isinstance(fileObj, LanguageWrapper):
688 language = language or current.language.get()
689 if not language or not (fileObj := fileObj.get(language)):
690 return ""
692 if "dlkey" not in fileObj and "dest" in fileObj:
693 fileObj = fileObj["dest"]
695 if expires:
696 expires = timedelta(minutes=expires)
698 if not isinstance(fileObj, (SkeletonInstance, dict)) or "dlkey" not in fileObj or "name" not in fileObj:
699 logging.error("Invalid fileObj supplied")
700 return ""
702 if derived and ("derived" not in fileObj or not isinstance(fileObj["derived"], dict)):
703 logging.error("No derivation for this fileObj")
704 return ""
706 if derived:
707 return conf.main_app.file.create_download_url(
708 fileObj["dlkey"],
709 filename=derived,
710 derived=True,
711 expires=expires,
712 download_filename=downloadFileName,
713 )
715 return conf.main_app.file.create_download_url(
716 fileObj["dlkey"],
717 filename=fileObj["name"],
718 expires=expires,
719 download_filename=downloadFileName
720 )
723@jinjaGlobalFunction
724def srcSetFor(
725 render: Render,
726 fileObj: dict,
727 expires: t.Optional[int] = conf.render_html_download_url_expiration,
728 width: t.Optional[int] = None,
729 height: t.Optional[int] = None,
730 language: t.Optional[str] = None,
731) -> str:
732 """
733 Generates a string suitable for use as the srcset tag in html. This functionality provides the browser with a list
734 of images in different sizes and allows it to choose the smallest file that will fill it's viewport without
735 upscaling.
737 :param render:
738 The render instance that's calling this function.
739 :param fileObj:
740 The file-bone (or if multiple=True a single value from it) to generate the srcset for.
741 :param expires: None if the file is supposed to be public
742 (which causes it to be cached on the google ede caches), otherwise it's lifetime in seconds.
743 :param width: A list of widths that should be included in the srcset.
744 If a given width is not available, it will be skipped.
745 :param height: A list of heights that should be included in the srcset.
746 If a given height is not available, it will be skipped.
747 :param language: Language overwrite if fileObj has multiple languages and we want to explicitly specify one
748 :return: The srctag generated or an empty string if a invalid file object was supplied
749 """
750 return conf.main_app.file.create_src_set(fileObj, expires, width, height, language)
753@jinjaGlobalFunction
754def serving_url_for(render: Render, *args, **kwargs):
755 """
756 Jinja wrapper for File.create_internal_serving_url(), see there for parameter information.
757 """
758 return conf.main_app.file.create_internal_serving_url(*args, **kwargs)
760@jinjaGlobalFunction
761def seoUrlForEntry(render: Render, *args, **kwargs):
762 return utils.seoUrlToEntry(*args, **kwargs)
765@jinjaGlobalFunction
766def seoUrlToFunction(render: Render, *args, **kwargs):
767 return utils.seoUrlToFunction(*args, **kwargs)
770@jinjaGlobalFunction
771def qrcode(render: Render, data: str) -> str:
772 """
773 Generates a SVG string for a html template
775 :param data: Any string data that should render to a QR Code.
777 :return: The SVG string representation.
778 """
779 return qrcode_make(data, image_factory=qrcode_svg.SvgPathImage, box_size=30).to_string().decode("utf-8")