backo 0.0.1__py3-none-any.whl
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.
- backo/__init__.py +25 -0
- backo/action.py +89 -0
- backo/api_toolbox.py +75 -0
- backo/backoffice.py +100 -0
- backo/collection.py +415 -0
- backo/current_user.py +51 -0
- backo/db_connector.py +92 -0
- backo/db_mongo_connector.py +192 -0
- backo/db_yml_connector.py +146 -0
- backo/error.py +57 -0
- backo/item.py +361 -0
- backo/log.py +168 -0
- backo/meta_data_handler.py +118 -0
- backo/patch.py +34 -0
- backo/reference.py +715 -0
- backo/request_decorators.py +104 -0
- backo/status.py +18 -0
- backo/transaction.py +64 -0
- backo/view.py +60 -0
- backo-0.0.1.dist-info/METADATA +563 -0
- backo-0.0.1.dist-info/RECORD +23 -0
- backo-0.0.1.dist-info/WHEEL +4 -0
- backo-0.0.1.dist-info/licenses/LICENSE +21 -0
backo/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module backo.
|
|
3
|
+
export all classes
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .item import Item
|
|
7
|
+
from .db_yml_connector import DBYmlConnector
|
|
8
|
+
from .db_mongo_connector import DBMongoConnector
|
|
9
|
+
from .current_user import current_user
|
|
10
|
+
from .error import Error, ErrorType
|
|
11
|
+
from .backoffice import Backoffice
|
|
12
|
+
from .collection import Collection
|
|
13
|
+
from .view import View
|
|
14
|
+
from .log import Logger, log_system
|
|
15
|
+
from .reference import Ref, RefsList, FillStrategy, DeleteStrategy
|
|
16
|
+
from .meta_data_handler import GenericMetaDataHandler, StandardMetaDataHandler
|
|
17
|
+
from .status import StatusType
|
|
18
|
+
from .action import Action
|
|
19
|
+
from .request_decorators import (
|
|
20
|
+
check_json,
|
|
21
|
+
check_method,
|
|
22
|
+
return_http_error,
|
|
23
|
+
error_to_http_handler,
|
|
24
|
+
)
|
|
25
|
+
from .api_toolbox import multidict_to_filter
|
backo/action.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module providing the action
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# pylint: disable=wrong-import-position, no-member, import-error, protected-access, wrong-import-order, attribute-defined-outside-init
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
sys.path.insert(1, "../../stricto")
|
|
11
|
+
from stricto import Dict
|
|
12
|
+
|
|
13
|
+
from .log import log_system
|
|
14
|
+
from .error import Error, ErrorType
|
|
15
|
+
from .item import Item
|
|
16
|
+
|
|
17
|
+
log = log_system.get_or_create_logger("action")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Action(Dict): # pylint: disable=too-many-instance-attributes
|
|
21
|
+
"""
|
|
22
|
+
An action
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, schema: dict, on_trig, **kwargs):
|
|
26
|
+
"""
|
|
27
|
+
available arguments
|
|
28
|
+
"""
|
|
29
|
+
self.backoffice = None
|
|
30
|
+
self.name = None
|
|
31
|
+
self.collection = None
|
|
32
|
+
self.on_trig = on_trig
|
|
33
|
+
|
|
34
|
+
# Add default right
|
|
35
|
+
if "can_execute" not in kwargs:
|
|
36
|
+
kwargs["can_execute"] = True
|
|
37
|
+
if "can_see" not in kwargs:
|
|
38
|
+
kwargs["can_see"] = True
|
|
39
|
+
kwargs["can_read"] = True
|
|
40
|
+
kwargs["can_modify"] = True
|
|
41
|
+
kwargs["exists"] = True
|
|
42
|
+
|
|
43
|
+
Dict.__init__(self, schema, **kwargs)
|
|
44
|
+
|
|
45
|
+
def check_params(self, param_name, o: Item) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Check if can execute the action
|
|
48
|
+
"""
|
|
49
|
+
p = self._params.get(param_name, False)
|
|
50
|
+
if not callable(p):
|
|
51
|
+
return bool(p)
|
|
52
|
+
return bool(p(self.backoffice, self.collection, self, o))
|
|
53
|
+
|
|
54
|
+
def can_see(self, o: Item) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Check if this action exists for running
|
|
57
|
+
"""
|
|
58
|
+
return self._rights.has_right("see", self, o)
|
|
59
|
+
|
|
60
|
+
def can_execute(self, o: Item) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Check if can execute the action
|
|
63
|
+
object can be a Dict, a array of Dict, or None, depends ont the target for this actopn
|
|
64
|
+
"""
|
|
65
|
+
return self._rights.has_right("execute", self, o)
|
|
66
|
+
|
|
67
|
+
def go(self, o: Item) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Launch the action
|
|
70
|
+
|
|
71
|
+
objeoct is the object (if exists)
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
if not self.can_see(o):
|
|
75
|
+
log.error("Try to launch non available action %r", self.name)
|
|
76
|
+
raise Error(
|
|
77
|
+
ErrorType.ACTION_NOT_AVAILABLE,
|
|
78
|
+
f"action {self.name} not available",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if not self.can_execute(o):
|
|
82
|
+
log.error("Try to execute forbidden action %r", self.name)
|
|
83
|
+
raise Error(
|
|
84
|
+
ErrorType.ACTION_FORBIDDEN,
|
|
85
|
+
f"action {self.name} forbidden",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
log.debug("Execute action %r", self.name)
|
|
89
|
+
return self.on_trig(self, o)
|
backo/api_toolbox.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The toolbox for api
|
|
3
|
+
A set of functions
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from werkzeug.datastructures import ImmutableMultiDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _append_to_filter(filter_as_dict: dict, key, value: list):
|
|
11
|
+
"""
|
|
12
|
+
adding key to dict with transformation to int or float if we can
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Transform string to int or float
|
|
16
|
+
typed_value = []
|
|
17
|
+
for v in value:
|
|
18
|
+
try:
|
|
19
|
+
vv = int(v)
|
|
20
|
+
except ValueError:
|
|
21
|
+
try:
|
|
22
|
+
vv = float(v)
|
|
23
|
+
except ValueError:
|
|
24
|
+
vv = v
|
|
25
|
+
typed_value.append(vv)
|
|
26
|
+
|
|
27
|
+
val = typed_value[0] if len(typed_value) == 1 else typed_value
|
|
28
|
+
|
|
29
|
+
match = re.search(r"^([^\.]+)\.(.*)", key)
|
|
30
|
+
if not match:
|
|
31
|
+
filter_as_dict[key] = val
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# a toto.$gt (with an operator)
|
|
35
|
+
if re.findall(r"^\$", match.group(2)):
|
|
36
|
+
filter_as_dict[match.group(1)] = (match.group(2), val)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
sub = filter_as_dict.get(match.group(1), {})
|
|
40
|
+
if not isinstance(sub, dict):
|
|
41
|
+
sub = {}
|
|
42
|
+
|
|
43
|
+
_append_to_filter(sub, match.group(2), value)
|
|
44
|
+
filter_as_dict[match.group(1)] = sub
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def multidict_to_filter(md: ImmutableMultiDict):
|
|
48
|
+
"""
|
|
49
|
+
Transform a multi dict to filter (query string are immutable dict)
|
|
50
|
+
|
|
51
|
+
see match in stricto for definition of a filter
|
|
52
|
+
see https://tedboy.github.io/flask/generated/generated/werkzeug.ImmutableMultiDict.html
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
[ ('toto', 'miam'), ('titi.tutu', '23.2') ('tata.$gt', 11)] ->
|
|
56
|
+
{
|
|
57
|
+
'toto' : "miam",
|
|
58
|
+
'titi' : {
|
|
59
|
+
'tutu' : 23.2
|
|
60
|
+
},
|
|
61
|
+
'tata' : ( '$gt', 11 )
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
filter_as_dict = {}
|
|
66
|
+
for key in md.keys():
|
|
67
|
+
|
|
68
|
+
# ignoring keys starting with _
|
|
69
|
+
if re.match(r"^_.*", key):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
value = md.getlist(key)
|
|
73
|
+
_append_to_filter(filter_as_dict, key, value)
|
|
74
|
+
|
|
75
|
+
return filter_as_dict
|
backo/backoffice.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The Backoffice module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from flask import Flask
|
|
7
|
+
|
|
8
|
+
from .item import Item
|
|
9
|
+
from .transaction import Transaction, OperatorType
|
|
10
|
+
from .collection import Collection
|
|
11
|
+
from .log import log_system
|
|
12
|
+
|
|
13
|
+
log = log_system.get_or_create_logger("backoffice", logging.DEBUG)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Backoffice: # pylint: disable=too-many-instance-attributes
|
|
17
|
+
"""
|
|
18
|
+
The main object, the aplication itself
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str):
|
|
22
|
+
"""
|
|
23
|
+
initialize the backoffice with a name
|
|
24
|
+
"""
|
|
25
|
+
self.name = name
|
|
26
|
+
self.collections = {}
|
|
27
|
+
self.transaction_id_reference = 1
|
|
28
|
+
self.transactions = {}
|
|
29
|
+
|
|
30
|
+
def register_collection(self, coll: Collection) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Register a collection into this backoffice
|
|
33
|
+
"""
|
|
34
|
+
self.collections[coll.name] = coll
|
|
35
|
+
coll.backoffice = self
|
|
36
|
+
setattr(self, coll.name, coll)
|
|
37
|
+
|
|
38
|
+
def add_collection(self, coll: Collection) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Register a collection into the backoffice
|
|
41
|
+
"""
|
|
42
|
+
return self.register_collection(coll)
|
|
43
|
+
|
|
44
|
+
def start_transaction(self) -> int:
|
|
45
|
+
"""
|
|
46
|
+
Chose an Id and start the transaction structure
|
|
47
|
+
"""
|
|
48
|
+
self.transaction_id_reference += 1
|
|
49
|
+
my_id = self.transaction_id_reference
|
|
50
|
+
self.transactions[my_id] = []
|
|
51
|
+
return my_id
|
|
52
|
+
|
|
53
|
+
def stop_transaction(self, transaction_id: int) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Close the transaction structure
|
|
56
|
+
"""
|
|
57
|
+
del self.transactions[transaction_id]
|
|
58
|
+
|
|
59
|
+
def record_transaction(
|
|
60
|
+
self,
|
|
61
|
+
transaction_id: int,
|
|
62
|
+
collection: Collection,
|
|
63
|
+
operation: OperatorType,
|
|
64
|
+
_id: str,
|
|
65
|
+
obj: Item,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Append an object to the transaction
|
|
69
|
+
"""
|
|
70
|
+
if not transaction_id:
|
|
71
|
+
return
|
|
72
|
+
self.transactions[transaction_id].append(
|
|
73
|
+
Transaction(collection, operation, _id, obj)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def rollback_transaction(self, transaction_id: int) -> None:
|
|
77
|
+
"""
|
|
78
|
+
An error occure, rollback objects
|
|
79
|
+
"""
|
|
80
|
+
log.info(
|
|
81
|
+
"Rollback transactions %d with %d actions",
|
|
82
|
+
transaction_id,
|
|
83
|
+
len(self.transactions[transaction_id]),
|
|
84
|
+
)
|
|
85
|
+
while self.transactions[transaction_id]:
|
|
86
|
+
t = self.transactions[transaction_id].pop()
|
|
87
|
+
t.rollback(self)
|
|
88
|
+
|
|
89
|
+
del self.transactions[transaction_id]
|
|
90
|
+
|
|
91
|
+
def add_routes(self, flask_app: Flask, prefix: str = "") -> None:
|
|
92
|
+
"""
|
|
93
|
+
Add all routes to flask application
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
my_path = f"/{prefix}/{self.name}/" if prefix else f"/{self.name}"
|
|
97
|
+
log.debug("Adding routes under %s", my_path)
|
|
98
|
+
|
|
99
|
+
for collection in self.collections.values():
|
|
100
|
+
collection.flask_add_routes(flask_app, my_path)
|
backo/collection.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The Collection module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# pylint: disable=logging-fstring-interpolation
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from flask import Flask, request, session
|
|
12
|
+
|
|
13
|
+
sys.path.insert(1, "../../stricto")
|
|
14
|
+
from stricto import StrictoEncoder, Rights
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from .item import Item
|
|
18
|
+
from .action import Action
|
|
19
|
+
from .error import Error, ErrorType
|
|
20
|
+
from .log import log_system
|
|
21
|
+
from .request_decorators import error_to_http_handler
|
|
22
|
+
from .api_toolbox import multidict_to_filter
|
|
23
|
+
from .patch import Patch
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
log = log_system.get_or_create_logger("collection", logging.DEBUG)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Collection:
|
|
30
|
+
"""
|
|
31
|
+
The Collection refer to a "table"
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, name, model: Item, db_handler, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
available arguments
|
|
37
|
+
"""
|
|
38
|
+
self.db_handler = db_handler
|
|
39
|
+
self.name = name
|
|
40
|
+
self.model = model.copy()
|
|
41
|
+
self.model.__dict__["_collection"] = self
|
|
42
|
+
self.model.set_db_handler(db_handler)
|
|
43
|
+
|
|
44
|
+
# Set rights
|
|
45
|
+
self._rights = Rights(read=True, create=True, delete=True, modify=True)
|
|
46
|
+
for key, right in kwargs.items():
|
|
47
|
+
a = re.findall(r"^can_(.*)$", key)
|
|
48
|
+
if a:
|
|
49
|
+
self._rights.add_or_modify_right(a[0], right)
|
|
50
|
+
|
|
51
|
+
# For filtering
|
|
52
|
+
self.refuse_filter = kwargs.pop("refuse_filter", None)
|
|
53
|
+
|
|
54
|
+
# For actions (aka some element work with datas)
|
|
55
|
+
self._actions = {}
|
|
56
|
+
self.backoffice = None
|
|
57
|
+
|
|
58
|
+
# For views
|
|
59
|
+
self._views = {}
|
|
60
|
+
|
|
61
|
+
def set(self, datas: dict | list) -> Item | list:
|
|
62
|
+
"""
|
|
63
|
+
Set an object or a list of object
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(datas, dict):
|
|
66
|
+
o = self.new_item()
|
|
67
|
+
o.set(datas)
|
|
68
|
+
return o
|
|
69
|
+
|
|
70
|
+
if isinstance(datas, list):
|
|
71
|
+
l = []
|
|
72
|
+
for d in datas:
|
|
73
|
+
l.append(self.set(d))
|
|
74
|
+
return l
|
|
75
|
+
|
|
76
|
+
def define_view(self, name: str, list_of_selector: list) -> None:
|
|
77
|
+
"""
|
|
78
|
+
add element into views
|
|
79
|
+
"""
|
|
80
|
+
for selector in list_of_selector:
|
|
81
|
+
f = self.model.select(selector)
|
|
82
|
+
if f is None:
|
|
83
|
+
continue
|
|
84
|
+
if name not in f._views:
|
|
85
|
+
f._views.append(name)
|
|
86
|
+
|
|
87
|
+
def has_right(self, right_name: str, o: Item = None) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Return the right for this collection
|
|
90
|
+
"""
|
|
91
|
+
return self._rights.has_right(right_name, o)
|
|
92
|
+
|
|
93
|
+
def new_item(self):
|
|
94
|
+
"""
|
|
95
|
+
return an Item
|
|
96
|
+
"""
|
|
97
|
+
return self.model.copy()
|
|
98
|
+
|
|
99
|
+
def new(self):
|
|
100
|
+
"""
|
|
101
|
+
return an Item
|
|
102
|
+
"""
|
|
103
|
+
return self.new_item()
|
|
104
|
+
|
|
105
|
+
def create(self, obj: dict, **kwargs):
|
|
106
|
+
"""
|
|
107
|
+
Create and save an item into the DB
|
|
108
|
+
|
|
109
|
+
transaction_id : The id of the transaction (used for rollback )
|
|
110
|
+
m_path : modification path, to avoid loop with references
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
if self._rights.has_right("create", None) is not True:
|
|
114
|
+
raise Error(
|
|
115
|
+
ErrorType.UNAUTHORIZED,
|
|
116
|
+
f"No rights to create in collection {self.name}.",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
item = self.new_item()
|
|
120
|
+
item.create(obj, **kwargs)
|
|
121
|
+
return item
|
|
122
|
+
|
|
123
|
+
def get_other_collection(self, name):
|
|
124
|
+
"""
|
|
125
|
+
Return another collection (ised by Ref and RefsList)
|
|
126
|
+
"""
|
|
127
|
+
if self.backoffice is None:
|
|
128
|
+
raise Error(
|
|
129
|
+
ErrorType.COLLECTION_NOT_REGISTERED,
|
|
130
|
+
f"collection {self.name} not registered into an backoffice",
|
|
131
|
+
)
|
|
132
|
+
return self.backoffice.collections.get(name)
|
|
133
|
+
|
|
134
|
+
def register_action(self, name: str, action: Action):
|
|
135
|
+
"""
|
|
136
|
+
add an action to this collection
|
|
137
|
+
this action will be related to an object
|
|
138
|
+
"""
|
|
139
|
+
self._actions[name] = action
|
|
140
|
+
action.__dict__["backoffice"] = self.backoffice
|
|
141
|
+
action.__dict__["name"] = name
|
|
142
|
+
action.__dict__["collection"] = self
|
|
143
|
+
|
|
144
|
+
def add_action(self, name: str, action: Action):
|
|
145
|
+
"""
|
|
146
|
+
add an action to this collection
|
|
147
|
+
this action will be related to an object
|
|
148
|
+
"""
|
|
149
|
+
return self.register_action(name, action)
|
|
150
|
+
|
|
151
|
+
def drop(self):
|
|
152
|
+
"""
|
|
153
|
+
Drop all elements
|
|
154
|
+
"""
|
|
155
|
+
self.db_handler.drop()
|
|
156
|
+
|
|
157
|
+
def get_by_id(self, _id):
|
|
158
|
+
"""
|
|
159
|
+
return an object by Id.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if self._rights.has_right("read", None) is not True:
|
|
163
|
+
raise Error(
|
|
164
|
+
ErrorType.UNAUTHORIZED,
|
|
165
|
+
f"No rights to create in collection {self.name}.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
obj = self.new_item()
|
|
169
|
+
obj.load(_id)
|
|
170
|
+
return obj
|
|
171
|
+
|
|
172
|
+
def select(
|
|
173
|
+
self,
|
|
174
|
+
db_filter,
|
|
175
|
+
match_filter=None,
|
|
176
|
+
view=None,
|
|
177
|
+
page_size=0,
|
|
178
|
+
num_of_element_to_skip=0,
|
|
179
|
+
db_sort_object={"_id": 1},
|
|
180
|
+
):
|
|
181
|
+
"""
|
|
182
|
+
Do a selection
|
|
183
|
+
|
|
184
|
+
db_filter : Is a filter related to the database system
|
|
185
|
+
match_filter : A filter for matching the object, independant
|
|
186
|
+
from the db. See match() in stricto
|
|
187
|
+
view : The view we want (See views in stricto)
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
if self._rights.has_right("read", None) is not True:
|
|
192
|
+
raise Error(
|
|
193
|
+
ErrorType.UNAUTHORIZED,
|
|
194
|
+
f"No rights to read the entire collection {self.name}.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Do the DB selection without pagination
|
|
198
|
+
db_list = self.db_handler.select(db_filter, {}, 0, 0, db_sort_object)
|
|
199
|
+
if not isinstance(db_list, list):
|
|
200
|
+
raise Error(
|
|
201
|
+
ErrorType.SELECT_ERROR,
|
|
202
|
+
f"select {self.name} error",
|
|
203
|
+
)
|
|
204
|
+
result = {
|
|
205
|
+
"result": [],
|
|
206
|
+
"total": 0,
|
|
207
|
+
"_view": view,
|
|
208
|
+
"_skip": num_of_element_to_skip,
|
|
209
|
+
"_page": page_size,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Get the restriction filter
|
|
213
|
+
# rfilter = (
|
|
214
|
+
# self.refuse_filter() if callable(self.refuse_filter) else self.refuse_filter
|
|
215
|
+
# )
|
|
216
|
+
|
|
217
|
+
# Do the selection on the object
|
|
218
|
+
index = 0
|
|
219
|
+
log.debug(f"try match {match_filter} for {len(db_list)}")
|
|
220
|
+
for obj in db_list:
|
|
221
|
+
obj["_id"] = str(obj["_id"])
|
|
222
|
+
o = self.new_item()
|
|
223
|
+
o.set(obj)
|
|
224
|
+
o.set_status_saved()
|
|
225
|
+
# Do the post match filtering
|
|
226
|
+
|
|
227
|
+
# Ignore all elements matched by the refuse filter
|
|
228
|
+
if self._rights.has_right("read", o) is not True:
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
if o.match(match_filter) is True:
|
|
232
|
+
if index >= num_of_element_to_skip:
|
|
233
|
+
if page_size == 0 or (
|
|
234
|
+
page_size > 0 and index < (num_of_element_to_skip + page_size)
|
|
235
|
+
):
|
|
236
|
+
result["result"].append(o)
|
|
237
|
+
index += 1
|
|
238
|
+
else:
|
|
239
|
+
log.debug(f"Not matchs {match_filter} for {o}")
|
|
240
|
+
|
|
241
|
+
result["total"] = index
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
def flask_add_routes(self, flask_app: Flask, my_path: str = "") -> None:
|
|
245
|
+
"""
|
|
246
|
+
Add CRUD routes and add axtions routes
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
# read datas
|
|
250
|
+
if self._rights.get_strict_right("read") is not False:
|
|
251
|
+
# GET /<_id>
|
|
252
|
+
log.debug(f"Add routes GET {my_path}/coll/{self.name}/<string:_id>")
|
|
253
|
+
|
|
254
|
+
flask_app.add_url_rule(
|
|
255
|
+
f"{my_path}/coll/{self.name}/<string:_id>",
|
|
256
|
+
f"get_{self.name}",
|
|
257
|
+
methods=["GET"],
|
|
258
|
+
)
|
|
259
|
+
flask_app.view_functions[f"get_{self.name}"] = self.http_get_by_id
|
|
260
|
+
|
|
261
|
+
# GET / - The selection
|
|
262
|
+
log.debug(f"Add routes GET {my_path}/coll/{self.name}")
|
|
263
|
+
flask_app.add_url_rule(
|
|
264
|
+
f"{my_path}/coll/{self.name}", f"select_{self.name}", methods=["GET"]
|
|
265
|
+
)
|
|
266
|
+
flask_app.view_functions[f"select_{self.name}"] = self.filtering
|
|
267
|
+
|
|
268
|
+
# POST / Create data
|
|
269
|
+
if self._rights.get_strict_right("create") is not False:
|
|
270
|
+
log.debug(f"Add routes POST {my_path}/coll/{self.name}")
|
|
271
|
+
flask_app.add_url_rule(
|
|
272
|
+
f"{my_path}/coll/{self.name}", f"create_{self.name}", methods=["POST"]
|
|
273
|
+
)
|
|
274
|
+
flask_app.view_functions[f"create_{self.name}"] = self.http_create
|
|
275
|
+
|
|
276
|
+
# PUT /<_id> Modify Data
|
|
277
|
+
if self._rights.get_strict_right("modify") is not False:
|
|
278
|
+
log.debug(f"Add routes PUT {my_path}/coll/{self.name}/<string:_id>")
|
|
279
|
+
flask_app.add_url_rule(
|
|
280
|
+
f"{my_path}/coll/{self.name}/<string:_id>",
|
|
281
|
+
f"put_{self.name}",
|
|
282
|
+
methods=["PUT"],
|
|
283
|
+
)
|
|
284
|
+
flask_app.view_functions[f"put_{self.name}"] = self.http_modify
|
|
285
|
+
|
|
286
|
+
# PATCH /<_id> Modify Data
|
|
287
|
+
if self._rights.get_strict_right("modify") is not False:
|
|
288
|
+
log.debug(f"Add routes PATCH {my_path}/coll/{self.name}/<string:_id>")
|
|
289
|
+
flask_app.add_url_rule(
|
|
290
|
+
f"{my_path}/coll/{self.name}/<string:_id>",
|
|
291
|
+
f"patch_one_{self.name}",
|
|
292
|
+
methods=["PATCH"],
|
|
293
|
+
)
|
|
294
|
+
flask_app.view_functions[f"patch_one_{self.name}"] = self.http_patch_one
|
|
295
|
+
|
|
296
|
+
# DELETE /<_id> Delete Data
|
|
297
|
+
if self._rights.get_strict_right("delete") is not False:
|
|
298
|
+
log.debug(f"Add routes DELETE {my_path}/coll/{self.name}/<string:_id>")
|
|
299
|
+
flask_app.add_url_rule(
|
|
300
|
+
f"{my_path}/coll/{self.name}/<string:_id>",
|
|
301
|
+
f"delete_{self.name}",
|
|
302
|
+
methods=["DELETE"],
|
|
303
|
+
)
|
|
304
|
+
flask_app.view_functions[f"delete_{self.name}"] = self.http_delete
|
|
305
|
+
|
|
306
|
+
# CHECK /<_id> Check values
|
|
307
|
+
if self._rights.get_strict_right("read") is not False:
|
|
308
|
+
log.debug(f"Add routes POST {my_path}/check/{self.name}/<string:_id>")
|
|
309
|
+
flask_app.add_url_rule(
|
|
310
|
+
f"{my_path}/coll/{self.name}/<string:_id>",
|
|
311
|
+
f"delete_{self.name}",
|
|
312
|
+
methods=["DELETE"],
|
|
313
|
+
)
|
|
314
|
+
flask_app.view_functions[f"delete_{self.name}"] = self.http_delete
|
|
315
|
+
|
|
316
|
+
@error_to_http_handler
|
|
317
|
+
def http_get_by_id(self, _id: str):
|
|
318
|
+
"""
|
|
319
|
+
GET HTTP
|
|
320
|
+
"""
|
|
321
|
+
query = request.args
|
|
322
|
+
_view = query.get("_view", "client")
|
|
323
|
+
|
|
324
|
+
obj = self.new_item()
|
|
325
|
+
obj.load(_id)
|
|
326
|
+
|
|
327
|
+
log.debug(f"get by _id {_id} in {self.name} in view {_view}")
|
|
328
|
+
return (json.dumps(obj.get_view(_view), cls=StrictoEncoder), 200)
|
|
329
|
+
|
|
330
|
+
@error_to_http_handler
|
|
331
|
+
def filtering(self):
|
|
332
|
+
"""
|
|
333
|
+
SELECT HTTP
|
|
334
|
+
"""
|
|
335
|
+
query = request.args
|
|
336
|
+
_page = int(query.get("_page", 10))
|
|
337
|
+
_skip = int(query.get("_skip", 0))
|
|
338
|
+
_view = query.get("_view", "client")
|
|
339
|
+
|
|
340
|
+
match_filter = multidict_to_filter(query)
|
|
341
|
+
|
|
342
|
+
log.debug(f"filtering {self.name} with filter={match_filter}")
|
|
343
|
+
|
|
344
|
+
result = self.select(None, match_filter, _view, _page, _skip)
|
|
345
|
+
log.debug(
|
|
346
|
+
f"select in {self.name} {match_filter} {_view}/{_page} skip {_skip} -> {result}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return (json.dumps(result, cls=StrictoEncoder), 200)
|
|
350
|
+
|
|
351
|
+
@error_to_http_handler
|
|
352
|
+
def http_create(self):
|
|
353
|
+
"""
|
|
354
|
+
POST HTTP -> creation
|
|
355
|
+
"""
|
|
356
|
+
query = request.args
|
|
357
|
+
_view = query.get("_view", "client")
|
|
358
|
+
|
|
359
|
+
log.debug(f"post {type(request.json)} {request.json}")
|
|
360
|
+
obj = self.create(request.json)
|
|
361
|
+
|
|
362
|
+
log.debug(f"create {obj._id} in {self.name} in view {_view}")
|
|
363
|
+
return (json.dumps(obj.get_view(_view), cls=StrictoEncoder), 200)
|
|
364
|
+
|
|
365
|
+
@error_to_http_handler
|
|
366
|
+
def http_modify(self, _id: str):
|
|
367
|
+
"""
|
|
368
|
+
PUT HTTP -> modification of an object
|
|
369
|
+
"""
|
|
370
|
+
query = request.args
|
|
371
|
+
_view = query.get("_view", "client")
|
|
372
|
+
|
|
373
|
+
log.debug(f"session {session.keys()}")
|
|
374
|
+
|
|
375
|
+
obj = self.new_item()
|
|
376
|
+
obj.load(_id)
|
|
377
|
+
obj.set(request.json)
|
|
378
|
+
obj.save()
|
|
379
|
+
|
|
380
|
+
return (json.dumps(obj.get_view(_view), cls=StrictoEncoder), 200)
|
|
381
|
+
|
|
382
|
+
@error_to_http_handler
|
|
383
|
+
def http_delete(self, _id: str):
|
|
384
|
+
"""
|
|
385
|
+
DELETE HTTP -> deletion
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
obj = self.new_item()
|
|
389
|
+
obj.load(_id)
|
|
390
|
+
obj.delete()
|
|
391
|
+
|
|
392
|
+
return ("deleted", 200)
|
|
393
|
+
|
|
394
|
+
@error_to_http_handler
|
|
395
|
+
def http_patch_one(self, _id: str):
|
|
396
|
+
"""
|
|
397
|
+
PATCH HTTP -> patch of an object
|
|
398
|
+
"""
|
|
399
|
+
query = request.args
|
|
400
|
+
_view = query.get("_view", "client")
|
|
401
|
+
|
|
402
|
+
patch_list = request.json if isinstance(request.json, list) else [request.json]
|
|
403
|
+
|
|
404
|
+
obj = self.new_item()
|
|
405
|
+
obj.load(_id)
|
|
406
|
+
|
|
407
|
+
# apply patches
|
|
408
|
+
for p in patch_list:
|
|
409
|
+
patch = Patch()
|
|
410
|
+
patch.set(p)
|
|
411
|
+
obj.patch(patch.op, patch.path, patch.value)
|
|
412
|
+
|
|
413
|
+
obj.save()
|
|
414
|
+
|
|
415
|
+
return (json.dumps(obj.get_view(_view), cls=StrictoEncoder), 200)
|