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.
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/PKG-INFO +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/__init__.py +2 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/base.py +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/file.py +6 -2
- viur_core-3.8.0.dev7/src/viur/core/bones/image.py +36 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/uid.py +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/config.py +2 -2
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/utils.py +0 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/i18n.py +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/file.py +21 -21
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/history.py +46 -33
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/translation.py +9 -11
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/singleton.py +10 -4
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/tree.py +8 -2
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/request.py +5 -2
- viur_core-3.8.0.dev7/src/viur/core/skeleton/__init__.py +53 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/adapter.py +144 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/instance.py +330 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/meta.py +448 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/relskel.py +85 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/skeleton.py +844 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/tasks.py +279 -0
- viur_core-3.8.0.dev7/src/viur/core/skeleton/utils.py +59 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/tasks.py +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/__init__.py +48 -4
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/string.py +23 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/version.py +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/PKG-INFO +1 -1
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/SOURCES.txt +9 -1
- viur_core-3.8.0.dev5/src/viur/core/skeleton.py +0 -2151
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/LICENSE +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/README.md +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/pyproject.toml +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/setup.cfg +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/boolean.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/captcha.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/color.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/credential.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/date.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/email.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/json.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/key.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/numeric.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/password.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/phone.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/randomslice.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/raw.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/record.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/relational.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/select.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/selectcountry.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/sortindex.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/spam.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/spatial.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/string.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/text.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/treeleaf.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/treenode.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/uri.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/bones/user.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/cache.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/current.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/cache.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/config.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/overrides.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/query.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/transport.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/db/types.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/decorators.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/email.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/errors.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/de.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/languages/en.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/logging.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/module.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/formmailer.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/moduleconf.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/page.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/script.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/site.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/modules/user.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/pagination.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/instanced_module.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/list.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/skelmodule.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/ratelimit.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/abstract.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/default.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/date.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/debug.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/regex.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/session.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/strings.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/tests.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/viur.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/user.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/html/utils.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/default.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/json/user.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/__init__.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/user.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/scripts/viur_migrate.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/secret.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/securityheaders.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/securitykey.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/session.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/template/error.html +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/template/vi_user_google_login.html +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/json.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur/core/utils/parse.py +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/dependency_links.txt +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/entry_points.txt +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/requires.txt +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/top_level.txt +0 -0
- {viur_core-3.8.0.dev5 → viur_core-3.8.0.dev7}/tests/test_config.py +0 -0
- {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.
|
|
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",
|
|
@@ -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
|
|
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
|
|
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
|
+
)
|
|
@@ -578,13 +578,12 @@ class Email(ConfigType):
|
|
|
578
578
|
|
|
579
579
|
|
|
580
580
|
class History(ConfigType):
|
|
581
|
-
databases: Multiple[str] = ["viur"
|
|
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"""
|
|
@@ -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.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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="
|
|
74
|
+
descr="Human-readable diff",
|
|
76
75
|
indexed=False,
|
|
77
76
|
)
|
|
78
77
|
|
|
79
78
|
|
|
80
79
|
class BigQueryHistory:
|
|
81
80
|
"""
|
|
82
|
-
|
|
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
|
|
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.
|
|
201
|
-
raise ValueError(
|
|
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.
|
|
216
|
+
return self.client.get_table(self.PATH)
|
|
210
217
|
|
|
211
218
|
except exceptions.NotFound:
|
|
212
|
-
app, dataset, table = self.
|
|
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.
|
|
230
|
+
return self.client.get_table(self.PATH)
|
|
224
231
|
except exceptions.NotFound:
|
|
225
|
-
logging.info(f"Table {self.
|
|
232
|
+
logging.info(f"Table {self.PATH!r} does not exist, creating")
|
|
226
233
|
self.client.create_table(
|
|
227
234
|
bigquery.Table(
|
|
228
|
-
self.
|
|
235
|
+
self.PATH,
|
|
229
236
|
schema=self.SCHEMA
|
|
230
237
|
)
|
|
231
238
|
)
|
|
232
|
-
return self.client.get_table(self.
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
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().
|
|
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,
|
|
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:
|
|
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(
|
|
173
|
+
def getContents(
|
|
174
|
+
self,
|
|
175
|
+
create: bool | dict | t.Callable[[SkeletonInstance], None] = False,
|
|
176
|
+
) -> SkeletonInstance | None:
|
|
174
177
|
"""
|
|
175
|
-
|
|
178
|
+
Return the entity of this singleton application as :class:`SkeletonInstance` object.
|
|
176
179
|
|
|
177
|
-
:
|
|
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["
|
|
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
|
+
]
|