Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/prototypes/skelmodule.py: 0%
77 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-07 19:28 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-07 19:28 +0000
1import os
2import yaml
3import logging
4from viur.core import Module, db, current
5from viur.core.decorators import *
6from viur.core.config import conf
7from viur.core.skeleton import skeletonByKind, Skeleton, SkeletonInstance
8import typing as t
11SINGLE_ORDER_TYPE = str | tuple[str, db.SortOrder]
12"""
13Type for exactly one sort order definitions.
14"""
16ORDER_TYPE = SINGLE_ORDER_TYPE | tuple[SINGLE_ORDER_TYPE] | list[SINGLE_ORDER_TYPE] | dict[str, str | int] | None
17"""
18Type for sort order definitions (any amount of single order definitions).
19"""
21DEFAULT_ORDER_TYPE = ORDER_TYPE | t.Callable[[db.Query], ORDER_TYPE]
22"""
23Type for default sort order definitions.
24"""
27def __load_indexes_from_file() -> dict[str, list]:
28 """
29 Loads all indexes from the index.yaml and stores it in a dictionary sorted by the module(kind)
30 :return A dictionary of indexes per module
31 """
32 indexes_dict = {}
33 try:
34 with open(os.path.join(conf.instance.project_base_path, "index.yaml"), "r") as file:
35 indexes = yaml.safe_load(file)
36 indexes = indexes.get("indexes", [])
37 for index in indexes or ():
38 index["properties"] = [_property["name"] for _property in index["properties"]]
39 indexes_dict.setdefault(index["kind"], []).append(index)
41 except FileNotFoundError:
42 logging.warning("index.yaml not found")
43 return {}
45 return indexes_dict
48DATASTORE_INDEXES = __load_indexes_from_file()
51class SkelModule(Module):
52 """
53 This is the extended module prototype used by any other ViUR module prototype.
54 It a prototype which generally is bound to some database model abstracted by the ViUR skeleton system.
55 """
57 kindName: str = None
58 """
59 Name of the datastore kind that is handled by this module.
61 This information is used to bind a specific :class:`viur.core.skeleton.Skeleton`-class to this
62 prototype. By default, it is automatically determined from the module's class name, so a module named
63 `Animal` refers to a Skeleton named `AnimalSkel` and its kindName is `animal`.
65 For more information, refer to the function :func:`~_resolveSkelCls`.
66 """
68 default_order: DEFAULT_ORDER_TYPE = None
69 """
70 Allows to specify a default order for this module, which is applied when no other order is specified.
72 Setting a default_order might result in the requirement of additional indexes, which are being raised
73 and must be specified.
74 """
76 def __init__(self, *args, **kwargs):
77 super().__init__(*args, **kwargs)
79 # automatically determine kindName when not set
80 if self.kindName is None:
81 self.kindName = str(type(self).__name__).lower()
83 # assign index descriptions from index.yaml
84 self.indexes = DATASTORE_INDEXES.get(self.kindName, [])
86 def _resolveSkelCls(self, *args, **kwargs) -> t.Type[Skeleton]:
87 """
88 Retrieve the generally associated :class:`viur.core.skeleton.Skeleton` that is used by
89 the application.
91 This is either be defined by the member variable *kindName* or by a Skeleton named like the
92 application class in lower-case order.
94 If this behavior is not wanted, it can be definitely overridden by defining module-specific
95 :func:`~viewSkel`, :func:`~addSkel`, or :func:`~editSkel` functions, or by overriding this
96 function in general.
98 :return: Returns a Skeleton class that matches the application.
99 """
100 return skeletonByKind(self.kindName)
102 def baseSkel(self, *args, **kwargs) -> SkeletonInstance:
103 """
104 Returns an instance of an unmodified base skeleton for this module.
106 This function should only be used in cases where a full, unmodified skeleton of the module is required, e.g.
107 for administrative or maintenance purposes.
109 By default, baseSkel is used by :func:`~viewSkel`, :func:`~addSkel`, and :func:`~editSkel`.
110 """
111 return self._resolveSkelCls(*args, **kwargs)()
113 def _apply_default_order(self, query: db.Query):
114 """
115 Apply the setting from `default_order` to a given db.Query.
117 The `default_order` will only be applied when the query has no other order, or is on a multquery.
118 """
120 # Apply default_order when possible!
121 if (
122 self.default_order
123 and query.queries
124 and not isinstance(query.queries, list)
125 and not query.queries.orders
126 and not current.request.get().kwargs.get("search")
127 ):
128 if callable(default_order := self.default_order):
129 default_order = default_order(query)
131 if isinstance(default_order, dict):
132 logging.debug(f"Applying filter {default_order=}")
133 query.mergeExternalFilter(default_order)
135 elif default_order:
136 logging.debug(f"Applying {default_order=}")
138 # FIXME: This ugly test can be removed when there is type that abstracts SortOrders
139 if (
140 isinstance(default_order, str)
141 or (
142 isinstance(default_order, tuple)
143 and len(default_order) == 2
144 and isinstance(default_order[0], str)
145 and isinstance(default_order[1], db.SortOrder)
146 )
147 ):
148 query.order(default_order)
149 else:
150 query.order(*default_order)
152 @force_ssl
153 @force_post
154 @exposed
155 @skey
156 @access("root")
157 def add_or_edit(self, key: db.Key | int | str, **kwargs) -> t.Any:
158 """
159 This function is intended to be used by importers.
160 Only "root"-users are allowed to use it.
161 """
162 db_key = db.keyHelper(key, targetKind=self.kindName, adjust_kind=self.kindName)
163 is_add = not bool(db.Get(db_key))
165 if is_add:
166 skel = self.addSkel()
167 else:
168 skel = self.editSkel()
170 skel["key"] = db_key
172 if (
173 not kwargs # no data supplied
174 or not skel.fromClient(kwargs) # failure on reading into the bones
175 ):
176 # render the skeleton in the version it could as far as it could be read.
177 return self.render.render("add_or_edit", skel)
179 if is_add:
180 self.onAdd(skel)
181 else:
182 self.onEdit(skel)
184 skel.write()
186 if is_add:
187 self.onAdded(skel)
188 return self.render.addSuccess(skel)
190 self.onEdited(skel)
191 return self.render.editSuccess(skel)