diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fa241c9..c388ddebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This file documents any relevant changes done to ViUR-core since version 3. - feat: Make SkeletonInstance json serializable (#1262) - feat: Provide `ignore`-parameter for `Skeleton.fromClient` (#1330) - feat: Provide `User.is_active()` function (#1309) +- feat: Public-files repos and improved rootnodes - feat: Retrieve default `descr` from bone's name in its Skeleton (#1227) - feat+refactor: Improved and extended `Skeleton.subskel()` (#1259) - fix: `File.write()` didn't return `db.Key` (#1303) diff --git a/src/viur/core/modules/file.py b/src/viur/core/modules/file.py index cb1312d1d..532f5bceb 100644 --- a/src/viur/core/modules/file.py +++ b/src/viur/core/modules/file.py @@ -428,8 +428,19 @@ class FileNodeSkel(TreeSkel): rootNode = BooleanBone( descr="Is RootNode", defaultValue=False, + readOnly=True, + visible=False, + ) + + public = BooleanBone( + descr="Is public?", + defaultValue=False, + readOnly=True, + visible=False, ) + viurCurrentSeoKeys = None + class File(Tree): PENDING_POSTFIX = " (pending)" @@ -859,6 +870,9 @@ def getUploadURL( if not self.canAdd("leaf", rootNode): raise errors.Forbidden() + if rootNode and public != bool(rootNode.get("public")): + raise errors.Forbidden("Cannot upload a public file into private repository or vice versa") + maxSize = None # The user has some file/add permissions, don't restrict fileSize if maxSize: diff --git a/src/viur/core/prototypes/tree.py b/src/viur/core/prototypes/tree.py index e7969188d..325a11d67 100644 --- a/src/viur/core/prototypes/tree.py +++ b/src/viur/core/prototypes/tree.py @@ -1,5 +1,6 @@ import logging import typing as t +from deprecated.sphinx import deprecated from viur.core import utils, errors, db, current from viur.core.decorators import * from viur.core.bones import KeyBone, SortIndexBone @@ -138,6 +139,38 @@ def cloneSkel(self, skelType: SkelType, *args, **kwargs) -> SkeletonInstance: """ return self.baseSkel(skelType, *args, **kwargs) + def rootnodeSkel( + self, + *, + identifier: str = "rep_module_repo", + ensure: bool | dict | t.Callable[[SkeletonInstance], None] = False, + ) -> SkeletonInstance: + """ + Retrieve a new :class:`viur.core.skeleton.SkeletonInstance` that is used by the application + for rootnode entries. + + The default is a SkeletonInstance returned by :func:`~baseSkel`, with a preset key created from identifier. + + :param identifier: Unique identifier (name) for this rootnode. + :param ensure: If provided, ensures that the skeleton is available, and created with optionally provided values. + + :return: Returns a SkeletonInstance for handling root nodes. + """ + skel = self.baseSkel("node") + + skel["key"] = db.Key(skel.kindName, identifier) + skel["rootNode"] = True + + if ensure not in (False, None): + return skel.read(create=ensure) + + return skel + + @deprecated( + version="3.7.0", + reason="Use rootnodeSkel(ensure=True) instead.", + action="always" + ) def ensureOwnModuleRootNode(self) -> db.Entity: """ Ensures, that general root-node for the current module exists. @@ -145,9 +178,7 @@ def ensureOwnModuleRootNode(self) -> db.Entity: :returns: The entity of the root-node. """ - key = "rep_module_repo" - kindName = self.viewSkel("node").kindName - return db.GetOrInsert(db.Key(kindName, key), creationdate=utils.utcNow(), rootNode=1) + return self.rootnodeSkel(ensure=True).dbEntity def getAvailableRootNodes(self, *args, **kwargs) -> list[dict[t.Literal["name", "key"], str]]: """ diff --git a/src/viur/core/render/json/default.py b/src/viur/core/render/json/default.py index 1d287f069..fde5d29ee 100644 --- a/src/viur/core/render/json/default.py +++ b/src/viur/core/render/json/default.py @@ -233,6 +233,7 @@ def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs): return json.dumps("OKAY") def listRootNodes(self, rootNodes, *args, **kwargs): + current.request.get().response.headers["Content-Type"] = "application/json" return json.dumps(rootNodes, cls=CustomJsonEncoder) def render(self, action: str, skel: t.Optional[SkeletonInstance] = None, **kwargs): diff --git a/src/viur/core/skeleton.py b/src/viur/core/skeleton.py index 49b42a2ca..a39492ed6 100644 --- a/src/viur/core/skeleton.py +++ b/src/viur/core/skeleton.py @@ -1112,6 +1112,7 @@ def read( skel: SkeletonInstance, key: t.Optional[KeyType] = None, *, + create: bool | dict | t.Callable[[SkeletonInstance], None] = False, _check_legacy: bool = True ) -> t.Optional[SkeletonInstance]: """ @@ -1126,6 +1127,8 @@ def read( :param key: A :class:`viur.core.db.Key`, string, or int; from which the data shall be fetched. If not provided, skel["key"] will be used. + :param create: Allows to specify a dict or initial callable that is executed in case the Skeleton with the + given key does not exist, it will be created. :returns: None on error, or the given SkeletonInstance on success. @@ -1143,11 +1146,20 @@ def read( except (ValueError, NotImplementedError): # This key did not parse return None - if not (db_res := db.Get(db_key)): + if db_res := db.Get(db_key): + skel.setEntity(db_res) + return skel + elif create in (False, None): return None - - skel.setEntity(db_res) - return skel + elif isinstance(create, dict): + if create and not skel.fromClient(create, amend=True): + raise ReadFromClientException(skel.errors) + elif callable(create): + create(skel) + elif create is not True: + raise ValueError("'create' must either be dict, a callable or True.") + + return skel.write() @classmethod @deprecated( diff --git a/src/viur/core/version.py b/src/viur/core/version.py index 602cf3ce8..f278d230a 100644 --- a/src/viur/core/version.py +++ b/src/viur/core/version.py @@ -3,7 +3,7 @@ # This will mark it as a pre-release as well on PyPI. # See CONTRIBUTING.md for further information. -__version__ = "3.7.0.rc8" +__version__ = "3.7.0.rc9" assert __version__.count(".") >= 2 and "".join(__version__.split(".", 3)[:3]).isdigit(), \ "Semantic __version__ expected!"