Skip to content

Commit b9dcb36

Browse files
committed
wip: Entire rework of the skel()/*Skel() behavior
- faces some review issues of @sveneberth - no satisfying integration with existing code
1 parent 32399e2 commit b9dcb36

File tree

4 files changed

+141
-137
lines changed

4 files changed

+141
-137
lines changed

src/viur/core/prototypes/list.py

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,7 @@ class List(SkelModule):
2020
handler = "list"
2121
accessRights = ("add", "edit", "view", "delete", "manage")
2222

23-
def viewSkel(self, *args, **kwargs) -> SkeletonInstance:
24-
"""
25-
Retrieve a new instance of a :class:`viur.core.skeleton.SkeletonInstance` that is used by the application
26-
for viewing an existing entry from the list.
27-
28-
The default is a Skeleton instance returned by :func:`~baseSkel`.
29-
30-
This SkeletonInstance can be post-processed (just returning a subskel or manually removing single bones) - which
31-
is the recommended way to ensure a given user cannot see certain fields. A Jinja-Template may choose not to
32-
display certain bones, but if the json or xml render is attached (or the user can use the vi or admin render)
33-
he could still see all values. This also prevents the user from filtering by these bones, so no binary search
34-
is possible.
35-
36-
.. seealso:: :func:`addSkel`, :func:`editSkel`, :func:`~baseSkel`
37-
38-
:return: Returns a Skeleton instance for viewing an entry.
39-
"""
40-
return self.skel(bones_from_request=True, **kwargs)
41-
42-
def addSkel(self, *args, **kwargs) -> SkeletonInstance:
23+
def addSkel(self, **kwargs) -> SkeletonInstance:
4324
"""
4425
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
4526
for adding an entry to the list.
@@ -55,26 +36,9 @@ def addSkel(self, *args, **kwargs) -> SkeletonInstance:
5536
5637
:return: Returns a Skeleton instance for adding an entry.
5738
"""
58-
return self.baseSkel(*args, **kwargs)
59-
60-
def editSkel(self, *args, **kwargs) -> SkeletonInstance:
61-
"""
62-
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
63-
for editing an existing entry from the list.
64-
65-
The default is a Skeleton instance returned by :func:`~baseSkel`.
66-
67-
Like in :func:`viewSkel`, the skeleton can be post-processed. Bones that are being removed aren't visible
68-
and cannot be set, but it's also possible to just set a bone to readOnly (revealing it's value to the user,
69-
but preventing any modification.
70-
71-
.. seealso:: :func:`viewSkel`, :func:`editSkel`, :func:`~baseSkel`
72-
73-
:return: Returns a Skeleton instance for editing an entry.
74-
"""
75-
return self.baseSkel(*args, **kwargs)
39+
return self.skel(**kwargs)
7640

77-
def cloneSkel(self, *args, **kwargs) -> SkeletonInstance:
41+
def cloneSkel(self, **kwargs) -> SkeletonInstance:
7842
"""
7943
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
8044
for cloning an existing entry from the list.
@@ -89,7 +53,7 @@ def cloneSkel(self, *args, **kwargs) -> SkeletonInstance:
8953
9054
:return: Returns a SkeletonInstance for editing an entry.
9155
"""
92-
return self.baseSkel(*args, **kwargs)
56+
return self.skel(**kwargs)
9357

9458
## External exposed functions
9559

@@ -110,7 +74,7 @@ def preview(self, *args, **kwargs) -> t.Any:
11074
if not self.canPreview():
11175
raise errors.Unauthorized()
11276

113-
skel = self.viewSkel()
77+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
11478
skel.fromClient(kwargs)
11579

11680
return self.render.view(skel)
@@ -126,7 +90,7 @@ def structure(self, action: t.Optional[str] = "view") -> t.Any:
12690
# FIXME: In ViUR > 3.7 this could also become dynamic (ActionSkel paradigm).
12791
match action:
12892
case "view":
129-
skel = self.viewSkel()
93+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
13094
if not self.canView(skel):
13195
raise errors.Unauthorized()
13296

@@ -168,7 +132,7 @@ def view(self, key: db.Key | int | str, *args, **kwargs) -> t.Any:
168132
:raises: :exc:`viur.core.errors.NotFound`, when no entry with the given *key* was found.
169133
:raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
170134
"""
171-
skel = self.viewSkel()
135+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
172136
if not skel.read(key):
173137
raise errors.NotFound()
174138

@@ -195,8 +159,10 @@ def list(self, *args, **kwargs) -> t.Any:
195159
196160
:raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
197161
"""
162+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
163+
198164
# The general access control is made via self.listFilter()
199-
if not (query := self.listFilter(self.viewSkel().all().mergeExternalFilter(kwargs))):
165+
if not (query := self.listFilter(skel.all().mergeExternalFilter(kwargs))):
200166
raise errors.Unauthorized()
201167

202168
self._apply_default_order(query)
@@ -323,10 +289,13 @@ def index(self, *args, **kwargs) -> t.Any:
323289
:return: The rendered entity or list.
324290
"""
325291
if args and args[0]:
292+
skel = self.viewSkel(
293+
allow_client_defined=utils.string.is_prefix(self.render.kind, "json"),
294+
_excludeFromAccessLog=True,
295+
)
296+
326297
# We probably have a Database or SEO-Key here
327-
seoKey = str(args[0]).lower()
328-
skel = self.viewSkel().all(_excludeFromAccessLog=True).filter("viur.viurActiveSeoKeys =", seoKey).getSkel()
329-
if skel:
298+
if skel := skel.all().filter("viur.viurActiveSeoKeys =", str(args[0]).lower()).getSkel():
330299
db.currentDbAccessLog.get(set()).add(skel["key"])
331300
if not self.canView(skel):
332301
raise errors.Forbidden()

src/viur/core/prototypes/singleton.py

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,6 @@ def getKey(self) -> str:
2727
"""
2828
return f"{self.editSkel().kindName}-modulekey"
2929

30-
def viewSkel(self, *args, **kwargs) -> SkeletonInstance:
31-
"""
32-
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
33-
for viewing the existing entry.
34-
35-
The default is a Skeleton instance returned by :func:`~baseSkel`.
36-
37-
.. seealso:: :func:`addSkel`, :func:`editSkel`, :func:`~baseSkel`
38-
39-
:return: Returns a Skeleton instance for viewing the singleton entry.
40-
"""
41-
return self.skel(bones_from_request=True, **kwargs)
42-
43-
def editSkel(self, *args, **kwargs) -> SkeletonInstance:
44-
"""
45-
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
46-
for editing the existing entry.
47-
48-
The default is a Skeleton instance returned by :func:`~baseSkel`.
49-
50-
.. seealso:: :func:`viewSkel`, :func:`editSkel`, :func:`~baseSkel`
51-
52-
:return: Returns a Skeleton instance for editing the entry.
53-
"""
54-
return self.baseSkel(*args, **kwargs)
55-
5630
## External exposed functions
5731

5832
@exposed
@@ -75,7 +49,7 @@ def preview(self, *args, **kwargs) -> t.Any:
7549
if not self.canPreview():
7650
raise errors.Unauthorized()
7751

78-
skel = self.viewSkel()
52+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
7953
skel.fromClient(kwargs)
8054

8155
return self.render.view(skel)
@@ -91,7 +65,7 @@ def structure(self, action: t.Optional[str] = "view") -> t.Any:
9165
# FIXME: In ViUR > 3.7 this could also become dynamic (ActionSkel paradigm).
9266
match action:
9367
case "view":
94-
skel = self.viewSkel()
68+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
9569
if not self.canView():
9670
raise errors.Unauthorized()
9771

@@ -120,7 +94,7 @@ def view(self, *args, **kwargs) -> t.Any:
12094
:raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
12195
"""
12296

123-
skel = self.viewSkel()
97+
skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
12498
if not self.canView():
12599
raise errors.Unauthorized()
126100

src/viur/core/prototypes/skelmodule.py

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import yaml
33
import logging
4-
from viur.core import Module, db, current
4+
from viur.core import Module, db, current, errors
55
from viur.core.decorators import *
66
from viur.core.config import conf
77
from viur.core.skeleton import skeletonByKind, Skeleton, SkeletonInstance
@@ -47,6 +47,8 @@ def __load_indexes_from_file() -> dict[str, list]:
4747

4848
DATASTORE_INDEXES = __load_indexes_from_file()
4949

50+
X_VIUR_BONELIST = "X-VIUR-BONELIST"
51+
"""Defines the header parameter that might contain a client-defined bone list."""
5052

5153
class SkelModule(Module):
5254
"""
@@ -99,7 +101,7 @@ def _resolveSkelCls(self, *args, **kwargs) -> t.Type[Skeleton]:
99101
"""
100102
return skeletonByKind(self.kindName)
101103

102-
def baseSkel(self, *args, **kwargs) -> SkeletonInstance:
104+
def baseSkel(self, **kwargs) -> SkeletonInstance:
103105
"""
104106
Returns an instance of an unmodified base skeleton for this module.
105107
@@ -110,17 +112,56 @@ def baseSkel(self, *args, **kwargs) -> SkeletonInstance:
110112
"""
111113
return self._resolveSkelCls(**kwargs)()
112114

115+
def viewSkel(self, **kwargs) -> SkeletonInstance:
116+
"""
117+
Retrieve a new instance of a :class:`viur.core.skeleton.SkeletonInstance` that is used by the application
118+
for viewing an existing entry from the list.
119+
120+
The default is a Skeleton instance returned by :func:`~baseSkel`.
121+
122+
This SkeletonInstance can be post-processed (just returning a subskel or manually removing single bones) - which
123+
is the recommended way to ensure a given user cannot see certain fields. A Jinja-Template may choose not to
124+
display certain bones, but if the json or xml render is attached (or the user can use the vi or admin render)
125+
he could still see all values. This also prevents the user from filtering by these bones, so no binary search
126+
is possible.
127+
128+
:param client_derive: Allows to
129+
130+
.. seealso:: :func:`addSkel`, :func:`editSkel`, :func:`~baseSkel`
131+
132+
:return: Returns a Skeleton instance for viewing an entry.
133+
"""
134+
return self.skel(**kwargs)
135+
136+
def editSkel(self, **kwargs) -> SkeletonInstance:
137+
"""
138+
Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
139+
for editing an existing entry from the list.
140+
141+
The default is a Skeleton instance returned by :func:`~baseSkel`.
142+
143+
Like in :func:`viewSkel`, the skeleton can be post-processed. Bones that are being removed aren't visible
144+
and cannot be set, but it's also possible to just set a bone to readOnly (revealing it's value to the user,
145+
but preventing any modification.
146+
147+
.. seealso:: :func:`viewSkel`, :func:`editSkel`, :func:`~baseSkel`
148+
149+
:return: Returns a Skeleton instance for editing an entry.
150+
"""
151+
return self.skel(**kwargs)
152+
113153
def skel(
114154
self,
155+
*,
115156
bones: t.Iterable[str] = (),
116-
bones_from_request: bool = False,
157+
allow_client_defined: bool = False,
117158
**kwargs,
118159
) -> SkeletonInstance:
119160
"""
120161
Retrieve module-specific skeleton, optionally as subskel.
121162
122-
:param bones: ALlows to specify a list of bones to form a subskel.
123-
:param bones_from_request: Evaluates header X-VIUR-BONELIST to contain a comma-separated list of bones.
163+
:param bones: Allows to specify a list of bones to form a subskel.
164+
:param client_derive: Evaluates header X-VIUR-BONELIST to contain a comma-separated list of bones.
124165
Using this parameter enforces that the Skeleton class has a subskel named "*" for required bones that
125166
must exist.
126167
@@ -129,24 +170,24 @@ def skel(
129170
skel_cls = self._resolveSkelCls(**kwargs)
130171
bones = set(bones) if bones else set()
131172

132-
if (
133-
bones_from_request # feature generally enabled?
134-
and skel_cls.subSkels.get("*") # a named subSkel "*"" must exist
135-
# and (bonelist := current.request.get().kwargs.get("x-viur-bonelist")) # param must be given (DEBUG!)
136-
and (bonelist := current.request.get().request.headers.get("x-viur-bonelist")) # header must be given
137-
):
138-
bones |= {bone.strip() for bone in bonelist.split(",")}
173+
if allow_client_defined:
174+
if bonelist := current.request.get().kwargs.get(X_VIUR_BONELIST.lower()): # DEBUG
175+
# if bonelist := current.request.get().request.headers.get(X_VIUR_BONELIST)
176+
if "*" not in skel_cls.subSkels: # a named star-subskel "*"" must exist!
177+
raise errors.BadRequest(f"Use of {X_VIUR_BONELIST!r} requires for a star-subskel")
178+
179+
bones |= {bone.strip() for bone in bonelist.split(",")}
139180

140181
# Return a subskel?
141182
if bones:
142-
# When coming from outside of a request, "*" must always be contained.
143-
if bones_from_request:
183+
# When coming from outside of a request, "*" is always involved.
184+
if allow_client_defined:
144185
return skel_cls.subskel("*", bones=bones)
145186

146187
return skel_cls.subskel(bones=bones)
147188

148189
# Otherwise, return full skeleton
149-
return skel_cls()
190+
return skel_cls() # FIXME: This is fishy, it should return a baseSkel(), but then some customer project break
150191

151192
def _apply_default_order(self, query: db.Query):
152193
"""

0 commit comments

Comments
 (0)