viur-core 3.8.0.dev5__tar.gz → 3.8.0.dev7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/PKG-INFO +1 -1
  2. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/__init__.py +2 -0
  3. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/base.py +1 -1
  4. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/file.py +6 -2
  5. viur_core-3.8.0.dev7/src/viur/core/bones/image.py +36 -0
  6. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/uid.py +1 -1
  7. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/config.py +2 -2
  8. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/utils.py +0 -1
  9. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/i18n.py +1 -1
  10. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/file.py +21 -21
  11. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/history.py +46 -33
  12. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/translation.py +9 -11
  13. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/singleton.py +10 -4
  14. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/tree.py +8 -2
  15. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/request.py +5 -2
  16. viur_core-3.8.0.dev7/src/viur/core/skeleton/__init__.py +53 -0
  17. viur_core-3.8.0.dev7/src/viur/core/skeleton/adapter.py +144 -0
  18. viur_core-3.8.0.dev7/src/viur/core/skeleton/instance.py +330 -0
  19. viur_core-3.8.0.dev7/src/viur/core/skeleton/meta.py +448 -0
  20. viur_core-3.8.0.dev7/src/viur/core/skeleton/relskel.py +85 -0
  21. viur_core-3.8.0.dev7/src/viur/core/skeleton/skeleton.py +844 -0
  22. viur_core-3.8.0.dev7/src/viur/core/skeleton/tasks.py +279 -0
  23. viur_core-3.8.0.dev7/src/viur/core/skeleton/utils.py +59 -0
  24. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/tasks.py +1 -1
  25. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/__init__.py +48 -4
  26. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/string.py +23 -0
  27. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/version.py +1 -1
  28. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/PKG-INFO +1 -1
  29. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/SOURCES.txt +9 -1
  30. viur_core-3.8.0.dev5/src/viur/core/skeleton.py +0 -2151
  31. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/LICENSE +0 -0
  32. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/README.md +0 -0
  33. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/pyproject.toml +0 -0
  34. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/setup.cfg +0 -0
  35. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/__init__.py +0 -0
  36. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/boolean.py +0 -0
  37. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/captcha.py +0 -0
  38. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/color.py +0 -0
  39. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/credential.py +0 -0
  40. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/date.py +0 -0
  41. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/email.py +0 -0
  42. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/json.py +0 -0
  43. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/key.py +0 -0
  44. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/numeric.py +0 -0
  45. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/password.py +0 -0
  46. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/phone.py +0 -0
  47. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/randomslice.py +0 -0
  48. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/raw.py +0 -0
  49. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/record.py +0 -0
  50. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/relational.py +0 -0
  51. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/select.py +0 -0
  52. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/selectcountry.py +0 -0
  53. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/sortindex.py +0 -0
  54. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/spam.py +0 -0
  55. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/spatial.py +0 -0
  56. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/string.py +0 -0
  57. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/text.py +0 -0
  58. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/treeleaf.py +0 -0
  59. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/treenode.py +0 -0
  60. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/uri.py +0 -0
  61. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/user.py +0 -0
  62. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/cache.py +0 -0
  63. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/current.py +0 -0
  64. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/__init__.py +0 -0
  65. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/cache.py +0 -0
  66. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/config.py +0 -0
  67. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/overrides.py +0 -0
  68. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/query.py +0 -0
  69. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/transport.py +0 -0
  70. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/types.py +0 -0
  71. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/decorators.py +0 -0
  72. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/email.py +0 -0
  73. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/errors.py +0 -0
  74. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/__init__.py +0 -0
  75. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/de.py +0 -0
  76. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/en.py +0 -0
  77. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/logging.py +0 -0
  78. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/module.py +0 -0
  79. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/__init__.py +0 -0
  80. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/formmailer.py +0 -0
  81. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/moduleconf.py +0 -0
  82. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/page.py +0 -0
  83. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/script.py +0 -0
  84. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/site.py +0 -0
  85. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/user.py +0 -0
  86. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/pagination.py +0 -0
  87. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/__init__.py +0 -0
  88. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/instanced_module.py +0 -0
  89. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/list.py +0 -0
  90. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/skelmodule.py +0 -0
  91. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/ratelimit.py +0 -0
  92. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/__init__.py +0 -0
  93. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/abstract.py +0 -0
  94. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/__init__.py +0 -0
  95. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/default.py +0 -0
  96. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/__init__.py +0 -0
  97. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/date.py +0 -0
  98. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/debug.py +0 -0
  99. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/regex.py +0 -0
  100. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/session.py +0 -0
  101. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/strings.py +0 -0
  102. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/tests.py +0 -0
  103. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/viur.py +0 -0
  104. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/user.py +0 -0
  105. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/utils.py +0 -0
  106. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/__init__.py +0 -0
  107. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/default.py +0 -0
  108. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/user.py +0 -0
  109. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/__init__.py +0 -0
  110. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/user.py +0 -0
  111. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/scripts/viur_migrate.py +0 -0
  112. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/secret.py +0 -0
  113. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/securityheaders.py +0 -0
  114. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/securitykey.py +0 -0
  115. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/session.py +0 -0
  116. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/template/error.html +0 -0
  117. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/template/vi_user_google_login.html +0 -0
  118. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/json.py +0 -0
  119. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/parse.py +0 -0
  120. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/dependency_links.txt +0 -0
  121. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/entry_points.txt +0 -0
  122. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/requires.txt +0 -0
  123. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/top_level.txt +0 -0
  124. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/tests/test_config.py +0 -0
  125. {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: viur-core
3
- Version: 3.8.0.dev5
3
+ Version: 3.8.0.dev7
4
4
  Summary: The core component of ViUR, a development framework for Google App Engine
5
5
  Author-email: Mausbrand Informationssysteme GmbH <devs@viur.dev>
6
6
  Maintainer-email: Jan Max Meyer <jm@mausbrand.de>
@@ -19,6 +19,7 @@ from .credential import CredentialBone
19
19
  from .date import DateBone
20
20
  from .email import EmailBone
21
21
  from .file import FileBone
22
+ from .image import ImageBone
22
23
  from .json import JsonBone
23
24
  from .key import KeyBone
24
25
  from .numeric import NumericBone
@@ -60,6 +61,7 @@ __all = [
60
61
  "DateBone",
61
62
  "EmailBone",
62
63
  "FileBone",
64
+ "ImageBone",
63
65
  "JsonBone",
64
66
  "KeyBone",
65
67
  "MultipleConstraints",
@@ -1070,7 +1070,7 @@ class BaseBone(object):
1070
1070
  db_obj[name] = value
1071
1071
  db.put(db_obj)
1072
1072
 
1073
- if db.IsInTransaction():
1073
+ if db.is_in_transaction():
1074
1074
  transact()
1075
1075
  else:
1076
1076
  db.run_in_transaction(transact)
@@ -35,7 +35,8 @@ def ensureDerived(key: db.Key, srcKey, deriveMap: dict[str, t.Any], refreshKey:
35
35
  the updated results are written back to the database and the updateRelations function is called
36
36
  to ensure proper relations are maintained.
37
37
  """
38
- from viur.core.skeleton import skeletonByKind, updateRelations
38
+ from viur.core.skeleton.utils import skeletonByKind
39
+ from viur.core.skeleton.tasks import updateRelations
39
40
  deriveFuncMap = conf.file_derivations
40
41
  skel = skeletonByKind("file")()
41
42
  if not skel.read(key):
@@ -226,7 +227,10 @@ class FileBone(TreeLeafBone):
226
227
  the derived files directly.
227
228
  """
228
229
  super().postSavedHandler(skel, boneName, key)
229
- if current.request.get().is_deferred and current.request_data.get().get("__update_relations_bone") == "derived":
230
+ if (
231
+ current.request.get().is_deferred
232
+ and "derived" in current.request_data.get().get("__update_relations_bones")
233
+ ):
230
234
  return
231
235
  from viur.core.skeleton import RelSkel, Skeleton
232
236
 
@@ -0,0 +1,36 @@
1
+ import typing as t
2
+ from .. import i18n
3
+ from .file import FileBone
4
+ from .string import StringBone
5
+ from ..skeleton.relskel import RelSkel
6
+ from ..config import conf
7
+
8
+
9
+ class ImageBoneRelSkel(RelSkel):
10
+ alt = StringBone(
11
+ descr=i18n.translate(
12
+ "viur.core.image.alt",
13
+ defaultText="Alternative description",
14
+ ),
15
+ searchable=True,
16
+ languages=conf.i18n.available_languages,
17
+ )
18
+
19
+
20
+ class ImageBone(FileBone):
21
+ type = FileBone.type + ".image"
22
+
23
+ def __init__(
24
+ self,
25
+ *,
26
+ public: bool = True,
27
+ using: t.Optional[RelSkel] = ImageBoneRelSkel,
28
+ validMimeTypes: None | t.Iterable[str] = ["image/*"],
29
+ **kwargs,
30
+ ):
31
+ super().__init__(
32
+ public=public,
33
+ using=using,
34
+ validMimeTypes=validMimeTypes,
35
+ **kwargs,
36
+ )
@@ -25,7 +25,7 @@ def generate_number(db_key: db.Key) -> int:
25
25
  raise ValueError("Can't set the Uid")
26
26
  return db_obj["count"]
27
27
 
28
- if db.IsInTransaction():
28
+ if db.is_in_transaction():
29
29
  return transact(db_key)
30
30
  else:
31
31
  return db.run_in_transaction(transact, db_key)
@@ -578,13 +578,12 @@ class Email(ConfigType):
578
578
 
579
579
 
580
580
  class History(ConfigType):
581
- databases: Multiple[str] = ["viur", "bigquery"]
581
+ databases: Multiple[str] = ["viur"]
582
582
  """All history related settings."""
583
583
  excluded_actions: Multiple[str] = []
584
584
  """List of all action that are should not be logged."""
585
585
  excluded_kinds: Multiple[str] = []
586
586
  """List of all kinds that should be logged."""
587
- bigquery_table_path: str = f"""{_project_id}.history.default"""
588
587
 
589
588
 
590
589
  class I18N(ConfigType):
@@ -920,6 +919,7 @@ class Conf(ConfigType):
920
919
  skeleton_search_path: Multiple[str] = [
921
920
  "/skeletons/", # skeletons of the project
922
921
  "/viur/core/", # system-defined skeletons of viur-core
922
+ "/viur/src/viur/core/", # fixme: test suite
923
923
  "/viur-core/core/" # system-defined skeletons of viur-core, only used by editable installation
924
924
  ]
925
925
  """Priority, in which skeletons are loaded"""
@@ -88,7 +88,6 @@ def key_helper(
88
88
  decoded_key = None
89
89
 
90
90
  # If it did decode, recall keyHelper with Key object
91
- print(f"decoded key: {decoded_key!r}")
92
91
  if decoded_key:
93
92
  return key_helper(
94
93
  decoded_key,
@@ -501,7 +501,7 @@ def add_missing_translation(
501
501
  key = key.lower()
502
502
 
503
503
  # Check if key already exists
504
- # if db.Get(db.Key(KINDNAME, key)): # FIXME ViUR4 should only use named keys
504
+ # if db.get(db.Key(KINDNAME, key)): # FIXME ViUR4 should only use named keys
505
505
  entity = db.Query(KINDNAME).filter("name =", key).getEntry()
506
506
  if entity is not None:
507
507
  # Ensure it doesn't exist to avoid datastore conflicts
@@ -23,10 +23,9 @@ from google.appengine.api import blobstore, images
23
23
  from google.cloud import storage
24
24
  from google.oauth2.service_account import Credentials as ServiceAccountCredentials
25
25
 
26
- from viur.core import conf, current, db, errors, utils
26
+ from viur.core import conf, current, db, errors, utils, i18n
27
27
  from viur.core.bones import BaseBone, BooleanBone, KeyBone, NumericBone, StringBone
28
28
  from viur.core.decorators import *
29
- from viur.core.i18n import LanguageWrapper
30
29
  from viur.core.prototypes.tree import SkelType, Tree, TreeSkel
31
30
  from viur.core.skeleton import SkeletonInstance, skeletonByKind
32
31
  from viur.core.tasks import CallDeferred, DeleteEntitiesIter, PeriodicTask
@@ -199,7 +198,7 @@ def cloudfunction_thumbnailer(fileSkel, existingFiles, params):
199
198
  authRequest = google.auth.transport.requests.Request()
200
199
  expiresAt = datetime.datetime.now() + datetime.timedelta(seconds=60)
201
200
  signing_credentials = google.auth.compute_engine.IDTokenCredentials(authRequest, "")
202
- content_disposition = f"""filename={fileSkel["name"]}"""
201
+ content_disposition = utils.build_content_disposition_header(fileSkel["name"])
203
202
  signedUrl = blob.generate_signed_url(
204
203
  expiresAt,
205
204
  credentials=signing_credentials,
@@ -303,6 +302,22 @@ class FileLeafSkel(TreeSkel):
303
302
  """
304
303
  kindName = "file"
305
304
 
305
+ name = StringBone(
306
+ descr="Filename",
307
+ caseSensitive=False,
308
+ searchable=True,
309
+ vfunc=lambda val: None if File.is_valid_filename(val) else "Invalid filename provided",
310
+ )
311
+
312
+ alt = StringBone(
313
+ descr=i18n.translate(
314
+ "viur.core.image.alt",
315
+ defaultText="Alternative description",
316
+ ),
317
+ searchable=True,
318
+ languages=conf.i18n.available_languages,
319
+ )
320
+
306
321
  size = StringBone(
307
322
  descr="Size",
308
323
  readOnly=True,
@@ -314,13 +329,6 @@ class FileLeafSkel(TreeSkel):
314
329
  readOnly=True,
315
330
  )
316
331
 
317
- name = StringBone(
318
- descr="Filename",
319
- caseSensitive=False,
320
- searchable=True,
321
- vfunc=lambda val: None if conf.main_app.file.is_valid_filename(val) else "Invalid filename provided",
322
- )
323
-
324
332
  mimetype = StringBone(
325
333
  descr="MIME-Type",
326
334
  readOnly=True,
@@ -713,7 +721,7 @@ class File(Tree):
713
721
  if not file:
714
722
  return ""
715
723
 
716
- if isinstance(file, LanguageWrapper):
724
+ if isinstance(file, i18n.LanguageWrapper):
717
725
  language = language or current.language.get()
718
726
  if not language or not (file := cls.get(language)):
719
727
  return ""
@@ -1021,12 +1029,7 @@ class File(Tree):
1021
1029
  if not filename:
1022
1030
  filename = download_filename or urlquote(blob.name.rsplit("/", 1)[-1])
1023
1031
 
1024
- content_disposition = "; ".join(
1025
- item for item in (
1026
- "attachment" if download else None,
1027
- f"filename={filename}" if filename else None,
1028
- ) if item
1029
- )
1032
+ content_disposition = utils.build_content_disposition_header(filename, attachment=download)
1030
1033
 
1031
1034
  if isinstance(_CREDENTIALS, ServiceAccountCredentials):
1032
1035
  expiresAt = datetime.datetime.now() + datetime.timedelta(seconds=60)
@@ -1144,10 +1147,7 @@ class File(Tree):
1144
1147
  response = current.request.get().response
1145
1148
  response.headers["Content-Type"] = f"image/{file_fmt}"
1146
1149
  response.headers["Cache-Control"] = "public, max-age=604800" # 7 Days
1147
- if download:
1148
- response.headers["Content-Disposition"] = f"attachment; filename={filename}"
1149
- else:
1150
- response.headers["Content-Disposition"] = f"filename={filename}"
1150
+ response.headers["Content-Disposition"] = utils.build_content_disposition_header(filename, attachment=download)
1151
1151
 
1152
1152
  answ = requests.get(url, timeout=20)
1153
1153
  if not answ.ok:
@@ -1,14 +1,13 @@
1
1
  import difflib
2
2
  import enum
3
- import json
4
3
  import logging
5
4
  import typing as t
5
+ from google.cloud import exceptions, bigquery
6
6
  from viur.core import db, conf, utils, current, tasks
7
+ from viur.core.bones import *
8
+ from viur.core.prototypes.list import List
7
9
  from viur.core.render.json.default import CustomJsonEncoder
8
10
  from viur.core.skeleton import SkeletonInstance, Skeleton, DatabaseAdapter
9
- from viur.core.prototypes.list import List
10
- from viur.core.bones import *
11
- from google.cloud import exceptions, bigquery
12
11
 
13
12
 
14
13
  class HistorySkel(Skeleton):
@@ -72,15 +71,21 @@ class HistorySkel(Skeleton):
72
71
  )
73
72
 
74
73
  diff = RawBone(
75
- descr="Diff",
74
+ descr="Human-readable diff",
76
75
  indexed=False,
77
76
  )
78
77
 
79
78
 
80
79
  class BigQueryHistory:
81
80
  """
82
- Schema and connector for BigQuery history entries.
81
+ Connector for BigQuery history entries.
83
82
  """
83
+
84
+ PATH = f"""{conf.instance.project_id}.history.default"""
85
+ """
86
+ Path to the big query table for history entries.
87
+ """
88
+
84
89
  SCHEMA = (
85
90
  {
86
91
  "type": "STRING",
@@ -191,25 +196,27 @@ class BigQueryHistory:
191
196
  "description": "diff data",
192
197
  },
193
198
  )
199
+ """
200
+ Schema used for the BigQuery table for its initial construction.
201
+ Keep to the provided format!
202
+ """
194
203
 
195
- def __init__(self, table_path: str = conf.history.bigquery_table_path):
204
+ def __init__(self):
196
205
  super().__init__()
197
206
 
198
- self.table_path = str(table_path)
199
207
  # checks for the table_path
200
- if self.table_path.count(".") != 2:
201
- raise ValueError(f"The table_path {self.table_path} must have exactly 3 parts "
202
- f"that are must be separated by a dot.")
208
+ if self.PATH.count(".") != 2:
209
+ raise ValueError("{self.PATH!r} must have exactly 3 parts that separated by a dot.")
203
210
 
204
211
  self.client = bigquery.Client()
205
212
  self.table = self.select_or_create_table()
206
213
 
207
214
  def select_or_create_table(self):
208
215
  try:
209
- return self.client.get_table(self.table_path)
216
+ return self.client.get_table(self.PATH)
210
217
 
211
218
  except exceptions.NotFound:
212
- app, dataset, table = self.table_path.split(".")
219
+ app, dataset, table = self.PATH.split(".")
213
220
  logging.error(f"{app}:{dataset}:{table}")
214
221
  # create dataset if needed
215
222
  try:
@@ -220,16 +227,16 @@ class BigQueryHistory:
220
227
 
221
228
  # create table if needed
222
229
  try:
223
- return self.client.get_table(self.table_path)
230
+ return self.client.get_table(self.PATH)
224
231
  except exceptions.NotFound:
225
- logging.info(f"Table {self.table_path!r} does not exist, creating")
232
+ logging.info(f"Table {self.PATH!r} does not exist, creating")
226
233
  self.client.create_table(
227
234
  bigquery.Table(
228
- self.table_path,
235
+ self.PATH,
229
236
  schema=self.SCHEMA
230
237
  )
231
238
  )
232
- return self.client.get_table(self.table_path)
239
+ return self.client.get_table(self.PATH)
233
240
 
234
241
  def write_row(self, data):
235
242
  if res := self.client.insert_rows(self.table, [data]):
@@ -272,11 +279,11 @@ class HistoryAdapter(DatabaseAdapter):
272
279
  self.trigger("delete", skel, None)
273
280
 
274
281
  def trigger(
275
- self,
276
- action: str,
277
- old_skel: SkeletonInstance,
278
- new_skel: SkeletonInstance,
279
- change_list: t.Iterable[str] = (),
282
+ self,
283
+ action: str,
284
+ old_skel: SkeletonInstance,
285
+ new_skel: SkeletonInstance,
286
+ change_list: t.Iterable[str] = (),
280
287
  ) -> str | None:
281
288
 
282
289
  # skip excluded actions like login or logout
@@ -320,7 +327,7 @@ class History(List):
320
327
  "icon": "clock-history",
321
328
  "filter": {
322
329
  "orderby": "timestamp",
323
- "orderdir": "1",
330
+ "orderdir": "desc",
324
331
  },
325
332
  "disabledActions": ["add", "clone", "delete"],
326
333
  }
@@ -334,19 +341,28 @@ class History(List):
334
341
  History format version.
335
342
  """
336
343
 
337
- def __init__(self, *args, bigquery_history_cls=BigQueryHistory, **kwargs):
344
+ BigQueryHistoryCls = BigQueryHistory
345
+ """
346
+ The connector class used to store entries to BigQuery.
347
+ """
348
+
349
+ def __init__(self, *args, **kwargs):
338
350
  super().__init__(*args, **kwargs)
339
- self.bigquery_history_cls = bigquery_history_cls
340
- self.bigquery = self.bigquery_history_cls and self.bigquery_history_cls()
341
351
 
342
- def baseSkel(self, *args, **kwargs):
352
+ if self.BigQueryHistoryCls and "bigquery" in conf.history.databases:
353
+ assert issubclass(self.BigQueryHistoryCls, BigQueryHistory)
354
+ self.bigquery = self.BigQueryHistoryCls()
355
+ else:
356
+ self.bigquery = None
357
+
358
+ def skel(self, **kwargs):
343
359
  # Make all bones readonly!
344
- skel = super().baseSkel(*args, **kwargs).clone()
360
+ skel = super().skel(**kwargs).clone()
345
361
  skel.readonly()
346
362
  return skel
347
363
 
348
364
  def canEdit(self, skel):
349
- return self.canView(skel)
365
+ return self.canView(skel) # this is needed to open an entry in admin (all bones are readonly!)
350
366
 
351
367
  def canDelete(self, _skel):
352
368
  return False
@@ -430,7 +446,6 @@ class History(List):
430
446
  user: t.Optional[SkeletonInstance] = None,
431
447
  tags: t.Iterable[str] = (),
432
448
  diff_excludes: t.Set[str] = set(),
433
-
434
449
  ):
435
450
  skel = new_skel or old_skel
436
451
  new_data = self._skel_to_dict(skel)
@@ -519,7 +534,6 @@ class History(List):
519
534
  user: t.Optional[SkeletonInstance] = None,
520
535
  tags: t.Iterable[str] = (),
521
536
  diff_excludes: t.Set[str] = set(),
522
-
523
537
  ) -> str | None:
524
538
 
525
539
  # create entry
@@ -547,8 +561,7 @@ class History(List):
547
561
 
548
562
  # write into BigQuery
549
563
  if self.bigquery and "bigquery" in conf.history.databases:
550
- # need to do this as biquery functions modifies
551
- # entry and seems to be called first
564
+ # need to do this as biquery functions modifies entry and seems to be called first
552
565
  if conf.instance.is_dev_server:
553
566
  entry = entry.copy() # need to do this as biquery functions modifiy entry
554
567
 
@@ -255,14 +255,17 @@ class Translation(List):
255
255
  def dump(
256
256
  self,
257
257
  *,
258
- pattern: list[str] = [],
259
- language: list[str] = [],
260
- ) -> dict[str, str] | dict[str, dict[str, str]]:
258
+ pattern: list[str] | None = None,
259
+ language: list[str] | None = None,
260
+ ) -> dict[str, dict[str, str]]:
261
261
  """
262
262
  Dumps translations as JSON.
263
263
 
264
- :param pattern: Required, provide fnmatch-style tramslaton key filter patterns of the translations wanted.
264
+ :param pattern: Optional, provide fnmatch-style translation key filter patterns of the translations wanted.
265
265
  :param language: Allows to request a specific language.
266
+ By default, the language of the current request is used.
267
+
268
+ :return: A dictionary with translations as JSON. Structure: ``{language: {key: value, ...}, ...}``
266
269
 
267
270
  Example calls:
268
271
 
@@ -276,11 +279,6 @@ class Translation(List):
276
279
 
277
280
  current.request.get().response.headers["Content-Type"] = "application/json"
278
281
 
279
- # The pattern may not be a matcher for all!
280
- for pat in pattern:
281
- if not pat.strip("*?."):
282
- raise errors.BadRequest("Pattern is too generic.")
283
-
284
282
  if (
285
283
  not (conf.debug.disable_cache and current.request.get().disableCache)
286
284
  and any(os.getenv("HTTP_HOST", "") in dlm for dlm in conf.i18n.domain_language_mapping)
@@ -294,12 +292,12 @@ class Translation(List):
294
292
  else:
295
293
  language = [current.language.get()]
296
294
 
297
- return json.dumps({
295
+ return json.dumps({ # type: ignore
298
296
  lang: {
299
297
  name: str(translate(name, force_lang=lang))
300
298
  for name, values in systemTranslations.items()
301
299
  if (conf.i18n.dump_can_view(name) or values.get("_public_"))
302
- and any(fnmatch.fnmatch(name, pat) for pat in pattern)
300
+ and (not pattern or any(fnmatch.fnmatch(name, pat) for pat in pattern))
303
301
  }
304
302
  for lang in language
305
303
  })
@@ -170,16 +170,22 @@ class Singleton(SkelModule):
170
170
  self.onEdited(skel)
171
171
  return self.render.editSuccess(skel)
172
172
 
173
- def getContents(self) -> SkeletonInstance | None:
173
+ def getContents(
174
+ self,
175
+ create: bool | dict | t.Callable[[SkeletonInstance], None] = False,
176
+ ) -> SkeletonInstance | None:
174
177
  """
175
- Returns the entity of this singleton application as :class:`viur.core.skeleton.Skeleton` object.
178
+ Return the entity of this singleton application as :class:`SkeletonInstance` object.
176
179
 
177
- :returns: The content as Skeleton provided by :func:`viewSkel`.
180
+ :param create: Whether the entity should be created if it does not exist.
181
+ See :meth:`Skeleton.read` for more details.
182
+
183
+ :returns: The read skeleton or `None`.
178
184
  """
179
185
  skel = self.viewSkel()
180
186
  key = db.Key(self.viewSkel().kindName, self.getKey())
181
187
 
182
- if not skel.read(key):
188
+ if not skel.read(key, create=create):
183
189
  return None
184
190
 
185
191
  return skel
@@ -3,7 +3,7 @@ import typing as t
3
3
  from deprecated.sphinx import deprecated
4
4
  from viur.core import utils, errors, db, current
5
5
  from viur.core.decorators import *
6
- from viur.core.bones import KeyBone, SortIndexBone
6
+ from viur.core.bones import KeyBone, SortIndexBone, BooleanBone
7
7
  from viur.core.cache import flushCache
8
8
  from viur.core.skeleton import Skeleton, SkeletonInstance
9
9
  from viur.core.tasks import CallDeferred
@@ -31,6 +31,12 @@ class TreeSkel(Skeleton):
31
31
  readOnly=True,
32
32
  )
33
33
 
34
+ is_root_node = BooleanBone(
35
+ defaultValue=False,
36
+ readOnly=True,
37
+ visible=False,
38
+ )
39
+
34
40
  @classmethod
35
41
  def refresh(cls, skelValues): # ViUR2 Compatibility
36
42
  super().refresh(skelValues)
@@ -160,7 +166,7 @@ class Tree(SkelModule):
160
166
  skel = self.baseSkel("node")
161
167
 
162
168
  skel["key"] = db.Key(skel.kindName, identifier)
163
- skel["rootNode"] = True
169
+ skel["is_root_node"] = True
164
170
 
165
171
  if ensure not in (False, None):
166
172
  return skel.read(create=ensure)
@@ -14,11 +14,11 @@ import re
14
14
  import time
15
15
  import traceback
16
16
  import typing as t
17
+ import unicodedata
17
18
  from abc import ABC, abstractmethod
18
19
  from urllib import parse
19
- from urllib.parse import unquote, urljoin, urlparse
20
+ from urllib.parse import quote, unquote, urljoin, urlparse
20
21
 
21
- import unicodedata
22
22
  import webob
23
23
 
24
24
  from viur.core import current, db, errors, session, utils
@@ -367,6 +367,9 @@ class Router:
367
367
  raise
368
368
  self.response.status = f"{e.status} {e.name}"
369
369
  url = e.url
370
+ url = unquote(url) # decode first
371
+ # safe = https://url.spec.whatwg.org/#url-path-segment-string
372
+ url = quote(url, encoding="utf-8", safe="!$&'()*+,-./:;=?@_~") # re-encode all in utf-8
370
373
  if url.startswith(('.', '/')):
371
374
  url = str(urljoin(self.request.url, url))
372
375
  self.response.headers['Location'] = url
@@ -0,0 +1,53 @@
1
+ import logging
2
+ import warnings
3
+
4
+ from ..bones.base import getSystemInitialized
5
+
6
+ from .adapter import DatabaseAdapter, ViurTagsSearchAdapter
7
+ from .instance import SkeletonInstance
8
+ from .meta import MetaSkel, MetaBaseSkel, BaseSkeleton
9
+
10
+ from .relskel import RelSkel, RefSkel
11
+ from .skeleton import Skeleton, SeoKeyBone, _UNDEFINED_KINDNAME
12
+ from .utils import SkelList, skeletonByKind, listKnownSkeletons, iterAllSkelClasses
13
+
14
+
15
+ # Forward our references to SkelInstance to the database (needed for queries)
16
+
17
+ # DEPRECATED ATTRIBUTES HANDLING
18
+
19
+ __DEPRECATED_NAMES = {
20
+ # stuff prior viur-core < 3.6
21
+ "seoKeyBone": ("SeoKeyBone", SeoKeyBone),
22
+ }
23
+
24
+
25
+ def __getattr__(attr: str) -> object:
26
+ if entry := __DEPRECATED_NAMES.get(attr):
27
+ func = entry[1]
28
+ msg = f"{attr} was replaced by {entry[0]}"
29
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
30
+ logging.warning(msg, stacklevel=2)
31
+ return func
32
+
33
+ return super(__import__(__name__).__class__).__getattribute__(attr)
34
+
35
+
36
+ __all__ = [
37
+ BaseSkeleton,
38
+ DatabaseAdapter,
39
+ iterAllSkelClasses,
40
+ listKnownSkeletons,
41
+ MetaBaseSkel,
42
+ MetaSkel,
43
+ RefSkel,
44
+ RelSkel,
45
+ SeoKeyBone,
46
+ Skeleton,
47
+ skeletonByKind,
48
+ SkeletonInstance,
49
+ SkelList,
50
+ ViurTagsSearchAdapter,
51
+ getSystemInitialized, # FIXME: This is an import from BaseBone
52
+ _UNDEFINED_KINDNAME
53
+ ]