viur-core 3.8.0.dev6__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.dev6 → viur_core-3.8.0.dev7}/PKG-INFO +1 -1
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/file.py +6 -2
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/utils.py +0 -1
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/i18n.py +1 -1
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/file.py +3 -11
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/translation.py +9 -11
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/request.py +5 -2
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/instance.py +1 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/skeleton.py +31 -19
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/tasks.py +42 -27
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/utils/__init__.py +48 -4
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/utils/string.py +23 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/version.py +1 -1
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/PKG-INFO +1 -1
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/LICENSE +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/README.md +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/pyproject.toml +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/setup.cfg +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/base.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/boolean.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/captcha.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/color.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/credential.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/date.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/email.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/image.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/json.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/key.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/numeric.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/password.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/phone.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/randomslice.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/raw.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/record.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/relational.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/select.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/selectcountry.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/sortindex.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/spam.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/spatial.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/string.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/text.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/treeleaf.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/treenode.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/uid.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/uri.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/bones/user.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/cache.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/config.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/current.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/cache.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/config.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/overrides.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/query.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/transport.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/db/types.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/decorators.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/email.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/errors.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/languages/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/languages/de.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/languages/en.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/logging.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/module.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/formmailer.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/history.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/moduleconf.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/page.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/script.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/site.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/modules/user.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/pagination.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/instanced_module.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/list.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/singleton.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/skelmodule.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/prototypes/tree.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/ratelimit.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/abstract.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/default.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/date.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/debug.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/regex.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/session.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/strings.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/tests.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/env/viur.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/user.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/html/utils.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/json/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/json/default.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/json/user.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/render/vi/user.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/scripts/viur_migrate.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/secret.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/securityheaders.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/securitykey.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/session.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/__init__.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/adapter.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/meta.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/relskel.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/skeleton/utils.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/tasks.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/template/error.html +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/template/vi_user_google_login.html +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/utils/json.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/utils/parse.py +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/SOURCES.txt +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/dependency_links.txt +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/entry_points.txt +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/requires.txt +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur_core.egg-info/top_level.txt +0 -0
- {viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/tests/test_config.py +0 -0
- {viur_core-3.8.0.dev6 → 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>
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -198,7 +198,7 @@ def cloudfunction_thumbnailer(fileSkel, existingFiles, params):
|
|
|
198
198
|
authRequest = google.auth.transport.requests.Request()
|
|
199
199
|
expiresAt = datetime.datetime.now() + datetime.timedelta(seconds=60)
|
|
200
200
|
signing_credentials = google.auth.compute_engine.IDTokenCredentials(authRequest, "")
|
|
201
|
-
content_disposition =
|
|
201
|
+
content_disposition = utils.build_content_disposition_header(fileSkel["name"])
|
|
202
202
|
signedUrl = blob.generate_signed_url(
|
|
203
203
|
expiresAt,
|
|
204
204
|
credentials=signing_credentials,
|
|
@@ -1029,12 +1029,7 @@ class File(Tree):
|
|
|
1029
1029
|
if not filename:
|
|
1030
1030
|
filename = download_filename or urlquote(blob.name.rsplit("/", 1)[-1])
|
|
1031
1031
|
|
|
1032
|
-
content_disposition =
|
|
1033
|
-
item for item in (
|
|
1034
|
-
"attachment" if download else None,
|
|
1035
|
-
f"filename={filename}" if filename else None,
|
|
1036
|
-
) if item
|
|
1037
|
-
)
|
|
1032
|
+
content_disposition = utils.build_content_disposition_header(filename, attachment=download)
|
|
1038
1033
|
|
|
1039
1034
|
if isinstance(_CREDENTIALS, ServiceAccountCredentials):
|
|
1040
1035
|
expiresAt = datetime.datetime.now() + datetime.timedelta(seconds=60)
|
|
@@ -1152,10 +1147,7 @@ class File(Tree):
|
|
|
1152
1147
|
response = current.request.get().response
|
|
1153
1148
|
response.headers["Content-Type"] = f"image/{file_fmt}"
|
|
1154
1149
|
response.headers["Cache-Control"] = "public, max-age=604800" # 7 Days
|
|
1155
|
-
|
|
1156
|
-
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
|
|
1157
|
-
else:
|
|
1158
|
-
response.headers["Content-Disposition"] = f"filename={filename}"
|
|
1150
|
+
response.headers["Content-Disposition"] = utils.build_content_disposition_header(filename, attachment=download)
|
|
1159
1151
|
|
|
1160
1152
|
answ = requests.get(url, timeout=20)
|
|
1161
1153
|
if not answ.ok:
|
|
@@ -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
|
})
|
|
@@ -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
|
|
@@ -10,13 +10,25 @@ from deprecated.sphinx import deprecated
|
|
|
10
10
|
from viur.core import conf, db, errors, utils
|
|
11
11
|
|
|
12
12
|
from .meta import BaseSkeleton, MetaSkel, _UNDEFINED_KINDNAME
|
|
13
|
-
from .tasks import updateRelations
|
|
14
|
-
from
|
|
13
|
+
from .tasks import updateRelations, processRemovedRelations
|
|
14
|
+
from .utils import skeletonByKind
|
|
15
|
+
from ..bones.base import (
|
|
16
|
+
Compute,
|
|
17
|
+
ComputeInterval,
|
|
18
|
+
ComputeMethod,
|
|
19
|
+
ReadFromClientException,
|
|
20
|
+
ReadFromClientError,
|
|
21
|
+
ReadFromClientErrorSeverity
|
|
22
|
+
)
|
|
15
23
|
from ..bones.relational import RelationalConsistency
|
|
16
24
|
from ..bones.key import KeyBone
|
|
17
25
|
from ..bones.date import DateBone
|
|
18
26
|
from ..bones.string import StringBone
|
|
19
27
|
|
|
28
|
+
if t.TYPE_CHECKING:
|
|
29
|
+
from .instance import SkeletonInstance
|
|
30
|
+
from .adapter import DatabaseAdapter
|
|
31
|
+
from .meta import KeyType
|
|
20
32
|
|
|
21
33
|
class SeoKeyBone(StringBone):
|
|
22
34
|
"""
|
|
@@ -185,7 +197,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
185
197
|
if boneInstance.unique:
|
|
186
198
|
lockValues = boneInstance.getUniquePropertyIndexValues(skel, boneName)
|
|
187
199
|
for lockValue in lockValues:
|
|
188
|
-
dbObj = db.
|
|
200
|
+
dbObj = db.get(db.Key(f"{skel.kindName}_{boneName}_uniquePropertyIndex", lockValue))
|
|
189
201
|
if dbObj and (not skel["key"] or dbObj["references"] != skel["key"].id_or_name):
|
|
190
202
|
# This value is taken (sadly, not by us)
|
|
191
203
|
complete = False
|
|
@@ -258,7 +270,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
258
270
|
except (ValueError, NotImplementedError): # This key did not parse
|
|
259
271
|
return None
|
|
260
272
|
|
|
261
|
-
if db_res := db.
|
|
273
|
+
if db_res := db.get(db_key):
|
|
262
274
|
skel.setEntity(db_res)
|
|
263
275
|
return skel
|
|
264
276
|
elif create in (False, None):
|
|
@@ -340,7 +352,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
340
352
|
is_add = True
|
|
341
353
|
else:
|
|
342
354
|
db_key = db.keyHelper(db_key, skel.kindName)
|
|
343
|
-
if db_obj := db.
|
|
355
|
+
if db_obj := db.get(db_key):
|
|
344
356
|
skel.dbEntity = db_obj
|
|
345
357
|
old_copy = {k: v for k, v in skel.dbEntity.items()}
|
|
346
358
|
is_add = False
|
|
@@ -399,7 +411,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
399
411
|
new_lock_kind = f"{skel.kindName}_{bone_name}_uniquePropertyIndex"
|
|
400
412
|
for new_lock_value in new_unique_values:
|
|
401
413
|
new_lock_key = db.Key(new_lock_kind, new_lock_value)
|
|
402
|
-
if lock_db_obj := db.
|
|
414
|
+
if lock_db_obj := db.get(new_lock_key):
|
|
403
415
|
|
|
404
416
|
# There's already a lock for that value, check if we hold it
|
|
405
417
|
if lock_db_obj["references"] != skel.dbEntity.key.id_or_name:
|
|
@@ -412,7 +424,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
412
424
|
# This value is locked for the first time, create a new lock-object
|
|
413
425
|
lock_obj = db.Entity(new_lock_key)
|
|
414
426
|
lock_obj["references"] = skel.dbEntity.key.id_or_name
|
|
415
|
-
db.
|
|
427
|
+
db.put(lock_obj)
|
|
416
428
|
if new_lock_value in old_unique_values:
|
|
417
429
|
old_unique_values.remove(new_lock_value)
|
|
418
430
|
skel.dbEntity["viur"][f"{bone_name}_uniqueIndexValue"] = new_unique_values
|
|
@@ -422,7 +434,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
422
434
|
# Try to delete the old lock
|
|
423
435
|
|
|
424
436
|
old_lock_key = db.Key(f"{skel.kindName}_{bone_name}_uniquePropertyIndex", old_unique_value)
|
|
425
|
-
if old_lock_obj := db.
|
|
437
|
+
if old_lock_obj := db.get(old_lock_key):
|
|
426
438
|
if old_lock_obj["references"] != skel.dbEntity.key.id_or_name:
|
|
427
439
|
|
|
428
440
|
# We've been supposed to have that lock - but we don't.
|
|
@@ -430,7 +442,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
430
442
|
logging.critical("Detected Database corruption! A Value-Lock had been reassigned!")
|
|
431
443
|
else:
|
|
432
444
|
# It's our lock which we don't need anymore
|
|
433
|
-
db.
|
|
445
|
+
db.delete(old_lock_key)
|
|
434
446
|
else:
|
|
435
447
|
logging.critical("Detected Database corruption! Could not delete stale lock-object!")
|
|
436
448
|
|
|
@@ -526,7 +538,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
526
538
|
fixDotNames(skel.dbEntity)
|
|
527
539
|
|
|
528
540
|
# Write the core entry back
|
|
529
|
-
db.
|
|
541
|
+
db.put(skel.dbEntity)
|
|
530
542
|
|
|
531
543
|
# Now write the blob-lock object
|
|
532
544
|
blob_list = skel.preProcessBlobLocks(blob_list)
|
|
@@ -537,7 +549,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
537
549
|
logging.error(msg)
|
|
538
550
|
raise ValueError(msg)
|
|
539
551
|
|
|
540
|
-
if not is_add and (old_blob_lock_obj := db.
|
|
552
|
+
if not is_add and (old_blob_lock_obj := db.get(db.Key("viur-blob-locks", db_key.id_or_name))):
|
|
541
553
|
removed_blobs = set(old_blob_lock_obj.get("active_blob_references", [])) - blob_list
|
|
542
554
|
old_blob_lock_obj["active_blob_references"] = list(blob_list)
|
|
543
555
|
if old_blob_lock_obj["old_blob_references"] is None:
|
|
@@ -550,14 +562,14 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
550
562
|
|
|
551
563
|
old_blob_lock_obj["has_old_blob_references"] = bool(old_blob_lock_obj["old_blob_references"])
|
|
552
564
|
old_blob_lock_obj["is_stale"] = False
|
|
553
|
-
db.
|
|
565
|
+
db.put(old_blob_lock_obj)
|
|
554
566
|
else: # We need to create a new blob-lock-object
|
|
555
567
|
blob_lock_obj = db.Entity(db.Key("viur-blob-locks", skel.dbEntity.key.id_or_name))
|
|
556
568
|
blob_lock_obj["active_blob_references"] = list(blob_list)
|
|
557
569
|
blob_lock_obj["old_blob_references"] = []
|
|
558
570
|
blob_lock_obj["has_old_blob_references"] = False
|
|
559
571
|
blob_lock_obj["is_stale"] = False
|
|
560
|
-
db.
|
|
572
|
+
db.put(blob_lock_obj)
|
|
561
573
|
|
|
562
574
|
return skel.dbEntity.key, write_skel, change_list, is_add
|
|
563
575
|
|
|
@@ -620,7 +632,7 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
620
632
|
flushList = []
|
|
621
633
|
for lockValue in viur_data.get(f"{boneName}_uniqueIndexValue") or []:
|
|
622
634
|
lockKey = db.Key(f"{skel.kindName}_{boneName}_uniquePropertyIndex", lockValue)
|
|
623
|
-
lockObj = db.
|
|
635
|
+
lockObj = db.get(lockKey)
|
|
624
636
|
if not lockObj:
|
|
625
637
|
logging.error(f"{lockKey=} missing!")
|
|
626
638
|
elif lockObj["references"] != key.id_or_name:
|
|
@@ -629,15 +641,15 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
629
641
|
else:
|
|
630
642
|
flushList.append(lockObj)
|
|
631
643
|
if flushList:
|
|
632
|
-
db.
|
|
644
|
+
db.delete(flushList)
|
|
633
645
|
|
|
634
646
|
# Delete the blob-key lock object
|
|
635
647
|
lockObjectKey = db.Key("viur-blob-locks", key.id_or_name)
|
|
636
|
-
lockObj = db.
|
|
648
|
+
lockObj = db.get(lockObjectKey)
|
|
637
649
|
|
|
638
650
|
if lockObj is not None:
|
|
639
651
|
if lockObj["old_blob_references"] is None and lockObj["active_blob_references"] is None:
|
|
640
|
-
db.
|
|
652
|
+
db.delete(lockObjectKey) # Nothing to do here
|
|
641
653
|
else:
|
|
642
654
|
if lockObj["old_blob_references"] is None:
|
|
643
655
|
# No old stale entries, move active_blob_references -> old_blob_references
|
|
@@ -648,9 +660,9 @@ class Skeleton(BaseSkeleton, metaclass=MetaSkel):
|
|
|
648
660
|
lockObj["active_blob_references"] = [] # There are no active ones left
|
|
649
661
|
lockObj["is_stale"] = True
|
|
650
662
|
lockObj["has_old_blob_references"] = True
|
|
651
|
-
db.
|
|
663
|
+
db.put(lockObj)
|
|
652
664
|
|
|
653
|
-
db.
|
|
665
|
+
db.delete(key)
|
|
654
666
|
processRemovedRelations(key)
|
|
655
667
|
|
|
656
668
|
if key := (key or skel["key"]):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import typing as t
|
|
3
3
|
|
|
4
|
-
from viur.core import conf, current, db, email, translate
|
|
4
|
+
from viur.core import conf, current, db, email, translate, utils
|
|
5
5
|
from .utils import skeletonByKind, listKnownSkeletons
|
|
6
6
|
from .meta import BaseSkeleton
|
|
7
7
|
|
|
@@ -58,7 +58,13 @@ def processRemovedRelations(removedKey, cursor=None):
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
@CallDeferred
|
|
61
|
-
def updateRelations(
|
|
61
|
+
def updateRelations(
|
|
62
|
+
dest_key: db.Key,
|
|
63
|
+
min_change_time: int,
|
|
64
|
+
changed_bones: t.Optional[t.Iterable[str] | str] = (),
|
|
65
|
+
cursor: t.Optional[str] = None,
|
|
66
|
+
**kwargs
|
|
67
|
+
):
|
|
62
68
|
"""
|
|
63
69
|
This function updates Entities, which may have a copy of values from another entity which has been recently
|
|
64
70
|
edited (updated). In ViUR, relations are implemented by copying the values from the referenced entity into the
|
|
@@ -67,51 +73,60 @@ def updateRelations(destKey: db.Key, minChangeTime: int, changedBone: t.Optional
|
|
|
67
73
|
us to track changes made to entities as we might have to update these mirrored values. This is the deferred
|
|
68
74
|
call from meth:`viur.core.skeleton.Skeleton.write()` after an update (edit) on one Entity to do exactly that.
|
|
69
75
|
|
|
70
|
-
:param
|
|
71
|
-
:param
|
|
76
|
+
:param dest_key: The database-key of the entity that has been edited
|
|
77
|
+
:param min_change_time: The timestamp on which the edit occurred. As we run deferred, and the entity might have
|
|
72
78
|
been edited multiple times before we get acutally called, we can ignore entities that have been updated
|
|
73
|
-
in the meantime as they're already
|
|
74
|
-
:param
|
|
79
|
+
in the meantime as they're already up-to-date
|
|
80
|
+
:param changed_bones: If set, we'll update only entites that have a copy of that bones. Relations mirror only
|
|
75
81
|
key and name by default, so we don't have to update these if only another bone has been changed.
|
|
76
82
|
:param cursor: The database cursor for the current request as we only process five entities at once and then
|
|
77
83
|
defer again.
|
|
78
84
|
"""
|
|
79
|
-
|
|
85
|
+
changed_bones = list(utils.ensure_iterable(changed_bones))
|
|
86
|
+
if "changedBone" in kwargs:
|
|
87
|
+
logging.warning("Use of `changedBone` is deprecated; Use `changed_bones` instead!", stacklevel=2)
|
|
88
|
+
changed_bones.extend(utils.ensure_iterable(kwargs["changedBone"]))
|
|
89
|
+
if "minChangeTime" in kwargs:
|
|
90
|
+
logging.warning("Use of `minChangeTime` is deprecated; Use `min_cahnge_time` instead!", stacklevel=2)
|
|
91
|
+
min_change_time = kwargs["minChangeTime"]
|
|
92
|
+
if "destKey" in kwargs:
|
|
93
|
+
logging.warning("Use of `destKey` is deprecated; Use `dest_key` instead!", stacklevel=2)
|
|
94
|
+
dest_key = kwargs["destKey"]
|
|
95
|
+
logging.debug(f"Starting updateRelations for {dest_key=}; {min_change_time=}, {changed_bones=}, {cursor=}")
|
|
80
96
|
if request_data := current.request_data.get():
|
|
81
|
-
request_data["
|
|
82
|
-
|
|
97
|
+
request_data["__update_relations_bones"] = changed_bones
|
|
98
|
+
update_list_query = (
|
|
83
99
|
db.Query("viur-relations")
|
|
84
|
-
.filter("dest.__key__ =",
|
|
85
|
-
.filter("viur_delayed_update_tag <",
|
|
100
|
+
.filter("dest.__key__ =", dest_key)
|
|
101
|
+
.filter("viur_delayed_update_tag <", min_change_time)
|
|
86
102
|
.filter("viur_relational_updateLevel =", RelationalUpdateLevel.Always.value)
|
|
87
103
|
)
|
|
88
|
-
if
|
|
89
|
-
|
|
104
|
+
if changed_bones:
|
|
105
|
+
update_list_query.filter("viur_foreign_keys IN", changed_bones)
|
|
90
106
|
if cursor:
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
update_list_query.setCursor(cursor)
|
|
108
|
+
update_list = update_list_query.run(limit=5)
|
|
93
109
|
|
|
94
|
-
def
|
|
95
|
-
if not
|
|
110
|
+
def __txn_update(_skel, key, srcRelKey):
|
|
111
|
+
if not _skel.read(key):
|
|
96
112
|
logging.warning(f"Cannot update stale reference to {key=} (referenced from {srcRelKey=})")
|
|
97
113
|
return
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
_skel.refresh()
|
|
116
|
+
_skel.write(update_relations=False)
|
|
101
117
|
|
|
102
|
-
for
|
|
118
|
+
for src_rel in update_list:
|
|
103
119
|
try:
|
|
104
|
-
skel = skeletonByKind(
|
|
120
|
+
skel = skeletonByKind(src_rel["viur_src_kind"])()
|
|
105
121
|
except AssertionError:
|
|
106
|
-
logging.info(f"""Ignoring {
|
|
122
|
+
logging.info(f"""Ignoring {src_rel.key!r} which refers to unknown kind {src_rel["viur_src_kind"]!r}""")
|
|
107
123
|
continue
|
|
108
124
|
if db.is_in_transaction():
|
|
109
|
-
|
|
125
|
+
__txn_update(skel, src_rel["src"].key, src_rel.key)
|
|
110
126
|
else:
|
|
111
|
-
db.run_in_transaction(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
updateRelations(destKey, minChangeTime, changedBone, nextCursor)
|
|
127
|
+
db.run_in_transaction(__txn_update, skel, src_rel["src"].key, src_rel.key)
|
|
128
|
+
if len(update_list) == 5 and (next_cursor := update_list_query.getCursor()):
|
|
129
|
+
updateRelations(dest_key, min_change_time, changed_bones, next_cursor)
|
|
115
130
|
|
|
116
131
|
|
|
117
132
|
@CallableTask
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
import logging
|
|
2
3
|
import typing as t
|
|
4
|
+
import urllib.parse
|
|
3
5
|
import warnings
|
|
4
|
-
import datetime
|
|
5
6
|
from collections.abc import Iterable
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
from viur.core import current, db
|
|
8
9
|
from viur.core.config import conf
|
|
9
10
|
from deprecated.sphinx import deprecated
|
|
11
|
+
from . import json, parse, string # noqa: used by external imports
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def utcNow() -> datetime.datetime:
|
|
@@ -114,18 +116,60 @@ def ensure_iterable(
|
|
|
114
116
|
if allow_callable and callable(obj):
|
|
115
117
|
obj = obj()
|
|
116
118
|
|
|
117
|
-
if isinstance(obj, Iterable): # uses collections.abc.Iterable
|
|
119
|
+
if not isinstance(obj, str) and isinstance(obj, Iterable): # uses collections.abc.Iterable
|
|
118
120
|
if test is None or test(obj):
|
|
119
121
|
return obj # return the obj, which is an iterable
|
|
120
122
|
|
|
121
123
|
return () # empty tuple
|
|
122
124
|
|
|
123
|
-
elif obj is None:
|
|
125
|
+
elif obj is None or (isinstance(obj, str) and not obj):
|
|
124
126
|
return () # empty tuple
|
|
125
127
|
|
|
126
128
|
return obj, # return a tuple with the obj
|
|
127
129
|
|
|
128
130
|
|
|
131
|
+
def build_content_disposition_header(
|
|
132
|
+
filename: str,
|
|
133
|
+
*,
|
|
134
|
+
attachment: bool = False,
|
|
135
|
+
inline: bool = False,
|
|
136
|
+
) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Build a Content-Disposition header with UTF-8 support and ASCII fallback.
|
|
139
|
+
|
|
140
|
+
Generates a properly formatted `Content-Disposition` header value, including
|
|
141
|
+
both a fallback ASCII filename and a UTF-8 encoded filename using RFC 5987.
|
|
142
|
+
|
|
143
|
+
Set either `attachment` or `inline` to control content disposition type.
|
|
144
|
+
If both are False, the header will omit disposition type (not recommended).
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
filename = "Änderung.pdf" ➜
|
|
148
|
+
'attachment; filename="Anderung.pdf"; filename*=UTF-8\'\'%C3%84nderung.pdf'
|
|
149
|
+
|
|
150
|
+
:param filename: The desired filename for the content.
|
|
151
|
+
:param attachment: Whether to mark the content as an attachment.
|
|
152
|
+
:param inline: Whether to mark the content as inline.
|
|
153
|
+
:return: A `Content-Disposition` header string.
|
|
154
|
+
"""
|
|
155
|
+
if attachment and inline:
|
|
156
|
+
raise ValueError("Only one of 'attachment' or 'inline' may be True.")
|
|
157
|
+
|
|
158
|
+
fallback = string.normalize_ascii(filename)
|
|
159
|
+
quoted_utf8 = urllib.parse.quote_from_bytes(filename.encode("utf-8"))
|
|
160
|
+
|
|
161
|
+
content_disposition = "; ".join(
|
|
162
|
+
item for item in (
|
|
163
|
+
"attachment" if attachment else None,
|
|
164
|
+
"inline" if inline else None,
|
|
165
|
+
f'filename="{fallback}"' if filename else None,
|
|
166
|
+
f'filename*=UTF-8\'\'{quoted_utf8}' if filename else None,
|
|
167
|
+
) if item
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return content_disposition
|
|
171
|
+
|
|
172
|
+
|
|
129
173
|
# DEPRECATED ATTRIBUTES HANDLING
|
|
130
174
|
__UTILS_CONF_REPLACEMENT = {
|
|
131
175
|
"projectID": "viur.instance.project_id",
|
|
@@ -4,6 +4,7 @@ ViUR utility functions regarding string processing.
|
|
|
4
4
|
import re
|
|
5
5
|
import secrets
|
|
6
6
|
import string
|
|
7
|
+
import unicodedata
|
|
7
8
|
import warnings
|
|
8
9
|
|
|
9
10
|
|
|
@@ -85,6 +86,28 @@ def unescape(val: str) -> str:
|
|
|
85
86
|
return re.sub(r"&(\w{2,4}|#0*(\d{2}));", __escape_replace, str(val).strip())
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def normalize_ascii(text: str) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Normalize a Unicode string to an ASCII-only version.
|
|
92
|
+
|
|
93
|
+
This function uses NFKD normalization to decompose accented and special characters,
|
|
94
|
+
then encodes the result to ASCII, ignoring any characters that can't be represented.
|
|
95
|
+
|
|
96
|
+
For example:
|
|
97
|
+
"Änderung" -> "Anderung"
|
|
98
|
+
"Café" -> "Cafe"
|
|
99
|
+
|
|
100
|
+
:param text: The input Unicode string to normalize.
|
|
101
|
+
:return: A normalized ASCII-only version of the input string.
|
|
102
|
+
"""
|
|
103
|
+
return (
|
|
104
|
+
unicodedata
|
|
105
|
+
.normalize("NFKD", text)
|
|
106
|
+
.encode("ascii", "ignore")
|
|
107
|
+
.decode("ascii")
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
88
111
|
def is_prefix(name: str, prefix: str, delimiter: str = ".") -> bool:
|
|
89
112
|
"""
|
|
90
113
|
Utility function to check if a given name matches a prefix,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This will mark it as a pre-release as well on PyPI.
|
|
4
4
|
# See CONTRIBUTING.md for further information.
|
|
5
5
|
|
|
6
|
-
__version__ = "3.8.0.
|
|
6
|
+
__version__ = "3.8.0.dev7"
|
|
7
7
|
|
|
8
8
|
assert __version__.count(".") >= 2 and "".join(__version__.split(".", 3)[:3]).isdigit(), \
|
|
9
9
|
"Semantic __version__ expected!"
|
|
@@ -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>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{viur_core-3.8.0.dev6 → viur_core-3.8.0.dev7}/src/viur/core/template/vi_user_google_login.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|