Skip to content

Commit

Permalink
Merge branch 'develop' into refactor-File.getUploadURL
Browse files Browse the repository at this point in the history
  • Loading branch information
phorward committed Mar 1, 2024
2 parents 6a3836b + a03620d commit 5e71bc8
Show file tree
Hide file tree
Showing 27 changed files with 750 additions and 446 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

This file documents any relevant changes done to ViUR-core since version 3.

## [3.5.15]

- fix: Several improvements on `ModuleConf` (#1073)

## [3.5.14]

- fix: `current.user` unset in deferred task calls (#1067)

## [3.5.13]

- fix: `RelationalBone` locking bug (#1052)
Expand Down
12 changes: 3 additions & 9 deletions src/viur/core/bones/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,14 @@ class FileBone(TreeLeafBone):
"""The kind of this bone is 'file'"""
type = "relational.tree.leaf.file"
"""The type of this bone is 'relational.tree.leaf.file'."""
refKeys = ["name", "key", "mimetype", "dlkey", "size", "width", "height", "derived"]
"""
The list of reference keys for this bone includes "name", "key", "mimetype", "dlkey", "size", "width",
"height", and "derived".
"""

def __init__(
self,
*,
derive: None | dict[str, t.Any] = None,
maxFileSize: None | int = None,
validMimeTypes: None | list[str] = None,
refKeys: t.Optional[t.Iterable[str]] = ("name", "mimetype", "size", "width", "height", "derived"),
**kwargs
):
r"""
Expand Down Expand Up @@ -170,11 +166,9 @@ def __init__(
validMimeTypes=["application/pdf", "image/*"]
"""
super().__init__(**kwargs)

if "dlkey" not in self.refKeys:
self.refKeys.append("dlkey")
super().__init__(refKeys=refKeys, **kwargs)

self.refKeys.add("dlkey")
self.derive = derive
self.validMimeTypes = validMimeTypes
self.maxFileSize = maxFileSize
Expand Down
29 changes: 18 additions & 11 deletions src/viur/core/bones/json.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import ast
import json
import typing as t

import jsonschema

import typing as t
from viur.core.bones.base import ReadFromClientError, ReadFromClientErrorSeverity
from viur.core.bones.raw import RawBone
from viur.core import utils


class JsonBone(RawBone):
Expand All @@ -23,9 +22,14 @@ class JsonBone(RawBone):

type = "raw.json"

def __init__(self, indexed: bool = False, multiple: bool = False, languages: bool = None, schema: t.Mapping = {},
*args,
**kwargs):
def __init__(
self,
indexed: bool = False,
multiple: bool = False,
languages: bool = None,
schema: t.Mapping = {},
*args, **kwargs
):
super().__init__(*args, **kwargs)
assert not multiple
assert not languages
Expand All @@ -36,7 +40,7 @@ def __init__(self, indexed: bool = False, multiple: bool = False, languages: boo

def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) -> bool:
if name in skel.accessedValues:
skel.dbEntity[name] = json.dumps(skel.accessedValues[name])
skel.dbEntity[name] = utils.json.dumps(skel.accessedValues[name])

# Ensure this bone is NOT indexed!
skel.dbEntity.exclude_from_indexes.add(name)
Expand All @@ -47,7 +51,7 @@ def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) ->

def unserialize(self, skel: 'viur.core.skeleton.SkeletonInstance', name: str) -> bool:
if data := skel.dbEntity.get(name):
skel.accessedValues[name] = json.loads(data)
skel.accessedValues[name] = utils.json.loads(data)
return True

return False
Expand All @@ -59,7 +63,7 @@ def singleValueFromClient(self, value: str | list | dict, skel, bone_name, clien

# Try to parse a JSON string
try:
value = json.loads(value)
value = utils.json.loads(value)

except json.decoder.JSONDecodeError as e:
# Try to parse a Python dict as fallback
Expand All @@ -76,8 +80,11 @@ def singleValueFromClient(self, value: str | list | dict, skel, bone_name, clien
jsonschema.validate(value, self.schema)
except (jsonschema.exceptions.ValidationError, jsonschema.exceptions.SchemaError) as e:
return self.getEmptyValue(), [
ReadFromClientError(ReadFromClientErrorSeverity.Invalid,
f"Invalid JSON for schema supplied: {e!s}")]
ReadFromClientError(
ReadFromClientErrorSeverity.Invalid,
f"Invalid JSON for schema supplied: {e!s}")
]

return super().singleValueFromClient(value, skel, bone_name, client_data)

def structure(self) -> dict:
Expand Down
18 changes: 8 additions & 10 deletions src/viur/core/bones/record.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@

import json
from viur.core.bones.base import BaseBone, ReadFromClientError, ReadFromClientErrorSeverity

try:
import extjson
except ImportError:
# FIXME: That json will not read datetime objects
import json as extjson


class RecordBone(BaseBone):
"""
Expand Down Expand Up @@ -53,16 +47,20 @@ def singleValueUnserialize(self, val):
"""
if isinstance(val, str):
try:
value = extjson.loads(val)
except:
value = json.loads(val)
except ValueError:
value = None
else:
value = val

if not value:
return None
elif isinstance(value, list) and value:

if isinstance(value, list) and value:
value = value[0]

assert isinstance(value, dict), f"Read something from the datastore thats not a dict: {type(value)}"

usingSkel = self.using()
usingSkel.unserialize(value)
return usingSkel
Expand Down
57 changes: 21 additions & 36 deletions src/viur/core/bones/relational.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@
This module contains the RelationalBone to create and manage relationships between skeletons
and enums to parameterize it.
"""
import json
import logging
import typing as t
import warnings
from enum import Enum
import typing as t

from itertools import chain
from time import time

from viur.core import db, utils
from viur.core.bones.base import BaseBone, ReadFromClientError, ReadFromClientErrorSeverity, getSystemInitialized

try:
import extjson
except ImportError:
# FIXME: That json will not read datetime objects
import json as extjson


class RelationalConsistency(Enum):
"""
Expand Down Expand Up @@ -147,16 +140,6 @@ class RelationalBone(BaseBone):
relational updates this will cascade. If Entity A references B with CascadeDeletion set, and
B references C also with CascadeDeletion; if C gets deleted, both B and A will be deleted as well.
"""
refKeys = ["key", "name"] # todo: turn into a tuple, as it should not be mutable.
"""
A list of properties to include from the referenced property. These properties will be available in the template
without having to fetch the referenced property. Filtering is also only possible by properties named here!
"""
parentKeys = ["key", "name"] # todo: turn into a tuple, as it should not be mutable.
"""
A list of properties from the current skeleton to include. If mixing filtering by relational properties and
properties of the class itself, these must be named here.
"""
type = "relational"
kind = None
Expand All @@ -168,8 +151,8 @@ def __init__(
format: str = "$(dest.name)",
kind: str = None,
module: t.Optional[str] = None,
parentKeys: t.Optional[list[str]] = None,
refKeys: t.Optional[list[str]] = None,
parentKeys: t.Optional[t.Iterable[str]] = {"name"},
refKeys: t.Optional[t.Iterable[str]] = {"name"},
updateLevel: RelationalUpdateLevel = RelationalUpdateLevel.Always,
using: t.Optional['viur.core.skeleton.RelSkel'] = None,
**kwargs
Expand All @@ -183,11 +166,11 @@ def __init__(
Name of the module which should be used to select entities of kind "type". If not set,
the value of "type" will be used (the kindName must match the moduleName)
:param refKeys:
A list of properties to include from the referenced property. These properties will be
available in the template without having to fetch the referenced property. Filtering is also only possible
by properties named here!
An iterable of properties to include from the referenced property. These properties will be
available in the template without having to fetch the referenced property. Filtering is also only
possible by properties named here!
:param parentKeys:
A list of properties from the current skeleton to include. If mixing filtering by
An iterable of properties from the current skeleton to include. If mixing filtering by
relational properties and properties of the class itself, these must be named here.
:param multiple:
If True, allow referencing multiple Elements of the given class. (Eg. n:n-relation).
Expand Down Expand Up @@ -265,17 +248,19 @@ def __init__(
if self.kind is None or self.module is None:
raise NotImplementedError("Type and Module of RelationalBone must not be None")

# Referenced keys
self.refKeys = {"key"}
if refKeys:
if not "key" in refKeys:
refKeys.append("key")
self.refKeys = refKeys
self.refKeys |= set(refKeys)

# Parent keys
self.parentKeys = {"key"}
if parentKeys:
if not "key" in parentKeys:
parentKeys.append("key")
self.parentKeys = parentKeys
self.parentKeys |= set(parentKeys)

self.using = using

# FIXME: Remove in VIUR4!!
if isinstance(updateLevel, int):
msg = f"parameter updateLevel={updateLevel} in RelationalBone is deprecated. " \
f"Please use the RelationalUpdateLevel enum instead"
Expand Down Expand Up @@ -371,14 +356,14 @@ def fixFromDictToEntry(inDict):

if isinstance(val, str): # ViUR2 compatibility
try:
value = extjson.loads(val)
value = json.loads(val)
if isinstance(value, list):
value = [fixFromDictToEntry(x) for x in value]
elif isinstance(value, dict):
value = fixFromDictToEntry(value)
else:
value = None
except:
except ValueError:
value = None
else:
value = val
Expand Down Expand Up @@ -533,7 +518,7 @@ def delete(self, skel: 'viur.core.skeleton.SkeletonInstance', name: str):
:raises Warning: If a referenced entry is missing despite the lock.
"""
if skel.dbEntity.get(f"{name}_outgoingRelationalLocks"):
for refKey in skel.dbEntity[f"_outgoingRelationalLocks" % name]:
for refKey in skel.dbEntity[f"{name}_outgoingRelationalLocks"]:
referencedEntry = db.Get(refKey)
if not referencedEntry:
logging.warning(f"Programming error detected: Entry {refKey} is gone despite lock")
Expand Down Expand Up @@ -600,7 +585,7 @@ def postSavedHandler(self, skel, boneName, key):
dbObj["viur_delayed_update_tag"] = time()
dbObj["viur_relational_updateLevel"] = self.updateLevel.value
dbObj["viur_relational_consistency"] = self.consistency.value
dbObj["viur_foreign_keys"] = self.refKeys
dbObj["viur_foreign_keys"] = list(self.refKeys)
dbObj["viurTags"] = srcEntity.get("viurTags") # Copy tags over so we can still use our searchengine
db.Put(dbObj)
values.remove(data)
Expand All @@ -619,7 +604,7 @@ def postSavedHandler(self, skel, boneName, key):
dbObj["viur_dest_kind"] = self.kind
dbObj["viur_relational_updateLevel"] = self.updateLevel.value
dbObj["viur_relational_consistency"] = self.consistency.value
dbObj["viur_foreign_keys"] = self.refKeys
dbObj["viur_foreign_keys"] = list(self.refKeys)
db.Put(dbObj)

def postDeletedHandler(self, skel, boneName, key):
Expand Down
4 changes: 2 additions & 2 deletions src/viur/core/bones/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def handle_starttag(self, tag, attrs):
if tag in ["a", "img"]:
for k, v in attrs:
if k == "src":
file = getattr(conf.main_app, "file", None)
file = getattr(conf.main_app.vi, "file", None)
if file and (filepath := file.parse_download_url(v)):
self.blobs.add(filepath.dlkey)

Expand Down Expand Up @@ -179,7 +179,7 @@ def handle_starttag(self, tag, attrs):
if not (checker.startswith("http://") or checker.startswith("https://") or checker.startswith("/")):
continue

file = getattr(conf.main_app, "file", None)
file = getattr(conf.main_app.vi, "file", None)
if file and (filepath := file.parse_download_url(v)):
v = file.create_download_url(
filepath.dlkey,
Expand Down
46 changes: 34 additions & 12 deletions src/viur/core/bones/user.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
import typing as t
from viur.core import current
from viur.core.bones.relational import RelationalBone


class UserBone(RelationalBone):
"""
A specialized relational bone for handling user references. Extends the functionality of
:class:`viur.core.bones.relational.RelationalBone` to include support for creation and update magic.
:param bool creationMagic: If True, the bone will automatically store the creating user when a new entry is added.
:param bool updateMagic: If True, the bone will automatically store the last user who updated the entry.
:param bool visible: If True, the bone will be visible in the skeleton.
:param bool readOnly: If True, the bone will be read-only and its value cannot be changed through user input.
:param dict kwargs: Additional keyword arguments passed to the parent class constructor.
:raises ValueError: If the bone has multiple set to True and a creation/update magic is set.
:class:`viur.core.bones.relational.RelationalBone` to include support for creation and update magic,
and comes with a predefined descr, format, kind and refKeys setting.
"""
kind = "user"
datafields = ["name"]

def __init__(self, *, creationMagic=False, updateMagic=False, visible=None, readOnly=False, **kwargs):
def __init__(
self,
*,
creationMagic: bool = False,
descr: str = "User",
format: str = "$(dest.lastname), $(dest.firstname) ($(dest.name))",
kind: str = "user",
readOnly: bool = False,
refKeys: t.Iterable[str] = ("key", "name", "firstname", "lastname"),
updateMagic: bool = False,
visible: t.Optional[bool] = None,
**kwargs
):
"""
Initializes a new UserBone.
:param creationMagic: If True, the bone will automatically store the creating user when a new entry is added.
:param updateMagic: If True, the bone will automatically store the last user who updated the entry.
:raises ValueError: If the bone is multiple=True and creation/update magic is set.
"""
if creationMagic or updateMagic:
readOnly = False
if visible is None:
visible = False # defaults
elif visible is None:
visible = True

super().__init__(visible=visible, readOnly=readOnly, **kwargs)
super().__init__(
kind=kind,
descr=descr,
format=format,
refKeys=refKeys,
visible=visible,
readOnly=readOnly,
**kwargs
)

self.creationMagic = creationMagic
self.updateMagic = updateMagic
Expand All @@ -51,5 +72,6 @@ def performMagic(self, skel, key, isAdd, *args, **kwargs):
if self.updateMagic or (self.creationMagic and isAdd):
if user := current.user.get():
return self.setBoneValue(skel, key, user["key"], False)

skel[key] = None
return True
Loading

0 comments on commit 5e71bc8

Please sign in to comment.