GeneralManager 0.0.0__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.
- general_manager/__init__.py +0 -0
- general_manager/api/graphql.py +732 -0
- general_manager/api/mutation.py +143 -0
- general_manager/api/property.py +20 -0
- general_manager/apps.py +83 -0
- general_manager/auxiliary/__init__.py +2 -0
- general_manager/auxiliary/argsToKwargs.py +25 -0
- general_manager/auxiliary/filterParser.py +97 -0
- general_manager/auxiliary/noneToZero.py +12 -0
- general_manager/cache/cacheDecorator.py +72 -0
- general_manager/cache/cacheTracker.py +33 -0
- general_manager/cache/dependencyIndex.py +300 -0
- general_manager/cache/pathMapping.py +151 -0
- general_manager/cache/signals.py +48 -0
- general_manager/factory/__init__.py +5 -0
- general_manager/factory/factories.py +287 -0
- general_manager/factory/lazy_methods.py +38 -0
- general_manager/interface/__init__.py +3 -0
- general_manager/interface/baseInterface.py +308 -0
- general_manager/interface/calculationInterface.py +406 -0
- general_manager/interface/databaseInterface.py +726 -0
- general_manager/manager/__init__.py +3 -0
- general_manager/manager/generalManager.py +136 -0
- general_manager/manager/groupManager.py +288 -0
- general_manager/manager/input.py +48 -0
- general_manager/manager/meta.py +75 -0
- general_manager/measurement/__init__.py +2 -0
- general_manager/measurement/measurement.py +233 -0
- general_manager/measurement/measurementField.py +152 -0
- general_manager/permission/__init__.py +1 -0
- general_manager/permission/basePermission.py +178 -0
- general_manager/permission/fileBasedPermission.py +0 -0
- general_manager/permission/managerBasedPermission.py +171 -0
- general_manager/permission/permissionChecks.py +53 -0
- general_manager/permission/permissionDataManager.py +55 -0
- general_manager/rule/__init__.py +1 -0
- general_manager/rule/handler.py +122 -0
- general_manager/rule/rule.py +313 -0
- generalmanager-0.0.0.dist-info/METADATA +207 -0
- generalmanager-0.0.0.dist-info/RECORD +43 -0
- generalmanager-0.0.0.dist-info/WHEEL +5 -0
- generalmanager-0.0.0.dist-info/licenses/LICENSE +29 -0
- generalmanager-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,313 @@
|
|
1
|
+
# generalManager/src/rule/rule.py
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
import ast
|
5
|
+
import inspect
|
6
|
+
import re
|
7
|
+
import textwrap
|
8
|
+
from typing import (
|
9
|
+
Callable,
|
10
|
+
ClassVar,
|
11
|
+
Dict,
|
12
|
+
Generic,
|
13
|
+
List,
|
14
|
+
Optional,
|
15
|
+
TypeVar,
|
16
|
+
cast,
|
17
|
+
)
|
18
|
+
|
19
|
+
from django.conf import settings
|
20
|
+
from django.utils.module_loading import import_string
|
21
|
+
|
22
|
+
from general_manager.rule.handler import (
|
23
|
+
BaseRuleHandler,
|
24
|
+
LenHandler,
|
25
|
+
IntersectionCheckHandler,
|
26
|
+
)
|
27
|
+
from general_manager.manager.generalManager import GeneralManager
|
28
|
+
|
29
|
+
GeneralManagerType = TypeVar("GeneralManagerType", bound=GeneralManager)
|
30
|
+
|
31
|
+
|
32
|
+
class Rule(Generic[GeneralManagerType]):
|
33
|
+
"""
|
34
|
+
Rule kapselt eine boolsche Bedingungsfunktion und erzeugt bei Fehlschlag
|
35
|
+
automatisierte oder benutzerdefinierte Fehlermeldungen auf Basis des AST.
|
36
|
+
"""
|
37
|
+
|
38
|
+
_func: Callable[[GeneralManagerType], bool]
|
39
|
+
_custom_error_message: Optional[str]
|
40
|
+
_ignore_if_none: bool
|
41
|
+
_last_result: Optional[bool]
|
42
|
+
_last_input: Optional[GeneralManagerType]
|
43
|
+
_tree: ast.AST
|
44
|
+
_variables: List[str]
|
45
|
+
_handlers: Dict[str, BaseRuleHandler]
|
46
|
+
|
47
|
+
def __init__(
|
48
|
+
self,
|
49
|
+
func: Callable[[GeneralManagerType], bool],
|
50
|
+
custom_error_message: Optional[str] = None,
|
51
|
+
ignore_if_none: bool = True,
|
52
|
+
) -> None:
|
53
|
+
self._func = func
|
54
|
+
self._custom_error_message = custom_error_message
|
55
|
+
self._ignore_if_none = ignore_if_none
|
56
|
+
self._last_result = None
|
57
|
+
self._last_input = None
|
58
|
+
|
59
|
+
# 1) Quelltext holen, Decorators abschneiden, Dedent
|
60
|
+
src = inspect.getsource(func)
|
61
|
+
lines = src.splitlines()
|
62
|
+
if lines and lines[0].strip().startswith("@"):
|
63
|
+
idx = next(i for i, L in enumerate(lines) if not L.strip().startswith("@"))
|
64
|
+
src = "\n".join(lines[idx:])
|
65
|
+
src = textwrap.dedent(src)
|
66
|
+
|
67
|
+
# 2) AST parsen & Elternverweise setzen
|
68
|
+
self._tree = ast.parse(src)
|
69
|
+
for parent in ast.walk(self._tree):
|
70
|
+
for child in ast.iter_child_nodes(parent):
|
71
|
+
setattr(child, "parent", parent)
|
72
|
+
|
73
|
+
# 3) Variablen extrahieren
|
74
|
+
self._variables = self._extract_variables()
|
75
|
+
|
76
|
+
# 4) Handler registrieren
|
77
|
+
self._handlers = {} # type: Dict[str, BaseRuleHandler]
|
78
|
+
for cls in (LenHandler, IntersectionCheckHandler):
|
79
|
+
inst = cls()
|
80
|
+
self._handlers[inst.function_name] = inst
|
81
|
+
for path in getattr(settings, "RULE_HANDLERS", []):
|
82
|
+
handler_cls = import_string(path)
|
83
|
+
inst = handler_cls()
|
84
|
+
self._handlers[inst.function_name] = inst
|
85
|
+
|
86
|
+
@property
|
87
|
+
def func(self) -> Callable[[GeneralManagerType], bool]:
|
88
|
+
return self._func
|
89
|
+
|
90
|
+
@property
|
91
|
+
def customErrorMessage(self) -> Optional[str]:
|
92
|
+
return self._custom_error_message
|
93
|
+
|
94
|
+
@property
|
95
|
+
def variables(self) -> List[str]:
|
96
|
+
return self._variables
|
97
|
+
|
98
|
+
@property
|
99
|
+
def lastEvaluationResult(self) -> Optional[bool]:
|
100
|
+
return self._last_result
|
101
|
+
|
102
|
+
@property
|
103
|
+
def lastEvaluationInput(self) -> Optional[GeneralManagerType]:
|
104
|
+
return self._last_input
|
105
|
+
|
106
|
+
@property
|
107
|
+
def ignoreIfNone(self) -> bool:
|
108
|
+
return self._ignore_if_none
|
109
|
+
|
110
|
+
def evaluate(self, x: GeneralManagerType) -> Optional[bool]:
|
111
|
+
"""
|
112
|
+
Führt die Regel aus. Gibt False bei Fehlschlag, True bei Erfolg
|
113
|
+
und None, falls ignore_if_none aktiv ist und eine Variable None war.
|
114
|
+
"""
|
115
|
+
self._last_input = x
|
116
|
+
vals = self._extract_variable_values(x)
|
117
|
+
if self._ignore_if_none and any(v is None for v in vals.values()):
|
118
|
+
self._last_result = None
|
119
|
+
return None
|
120
|
+
|
121
|
+
self._last_result = self._func(x)
|
122
|
+
return self._last_result
|
123
|
+
|
124
|
+
def validateCustomErrorMessage(self) -> None:
|
125
|
+
"""
|
126
|
+
Stellt sicher, dass in der custom_error_message alle Variablen
|
127
|
+
aus self._variables verwendet werden.
|
128
|
+
"""
|
129
|
+
if not self._custom_error_message:
|
130
|
+
return
|
131
|
+
|
132
|
+
vars_in_msg = set(re.findall(r"{([^}]+)}", self._custom_error_message))
|
133
|
+
missing = [v for v in self._variables if v not in vars_in_msg]
|
134
|
+
if missing:
|
135
|
+
raise ValueError(
|
136
|
+
f"The custom error message does not contain all used variables: {missing}"
|
137
|
+
)
|
138
|
+
|
139
|
+
def getErrorMessage(self) -> Optional[Dict[str, str]]:
|
140
|
+
"""
|
141
|
+
Liefert ein Dict variable→message, oder None, wenn kein Fehler.
|
142
|
+
"""
|
143
|
+
if self._last_result or self._last_result is None:
|
144
|
+
return None
|
145
|
+
if self._last_input is None:
|
146
|
+
raise ValueError("No input provided for error message generation")
|
147
|
+
|
148
|
+
# Validierung und Ersetzen der Template-Platzhalter
|
149
|
+
self.validateCustomErrorMessage()
|
150
|
+
vals = self._extract_variable_values(self._last_input)
|
151
|
+
|
152
|
+
if self._custom_error_message:
|
153
|
+
formatted = re.sub(
|
154
|
+
r"{([^}]+)}",
|
155
|
+
lambda m: str(vals.get(m.group(1), m.group(0))),
|
156
|
+
self._custom_error_message,
|
157
|
+
)
|
158
|
+
return {v: formatted for v in self._variables}
|
159
|
+
|
160
|
+
errors = self._generate_error_messages(vals)
|
161
|
+
return errors or None
|
162
|
+
|
163
|
+
def _extract_variables(self) -> List[str]:
|
164
|
+
class VarVisitor(ast.NodeVisitor):
|
165
|
+
vars: set[str] = set()
|
166
|
+
|
167
|
+
def visit_Attribute(self, node: ast.Attribute) -> None:
|
168
|
+
parts: list[str] = []
|
169
|
+
curr: ast.AST = node
|
170
|
+
while isinstance(curr, ast.Attribute):
|
171
|
+
parts.append(curr.attr)
|
172
|
+
curr = curr.value
|
173
|
+
if isinstance(curr, ast.Name) and curr.id == "x":
|
174
|
+
self.vars.add(".".join(reversed(parts)))
|
175
|
+
self.generic_visit(node)
|
176
|
+
|
177
|
+
visitor = VarVisitor()
|
178
|
+
visitor.visit(self._tree)
|
179
|
+
return sorted(visitor.vars)
|
180
|
+
|
181
|
+
def _extract_variable_values(
|
182
|
+
self, x: GeneralManagerType
|
183
|
+
) -> Dict[str, Optional[object]]:
|
184
|
+
out: Dict[str, Optional[object]] = {}
|
185
|
+
for var in self._variables:
|
186
|
+
obj: object = x # type: ignore
|
187
|
+
for part in var.split("."):
|
188
|
+
obj = getattr(obj, part)
|
189
|
+
if obj is None:
|
190
|
+
break
|
191
|
+
out[var] = obj
|
192
|
+
return out
|
193
|
+
|
194
|
+
def _extract_comparisons(self) -> list[ast.Compare]:
|
195
|
+
class CompVisitor(ast.NodeVisitor):
|
196
|
+
comps: list[ast.Compare] = []
|
197
|
+
|
198
|
+
def visit_Compare(self, node: ast.Compare) -> None:
|
199
|
+
self.comps.append(node)
|
200
|
+
self.generic_visit(node)
|
201
|
+
|
202
|
+
visitor = CompVisitor()
|
203
|
+
visitor.visit(self._tree)
|
204
|
+
return visitor.comps
|
205
|
+
|
206
|
+
def _contains_logical_ops(self) -> bool:
|
207
|
+
class LogicVisitor(ast.NodeVisitor):
|
208
|
+
found: bool = False
|
209
|
+
|
210
|
+
def visit_BoolOp(self, node: ast.BoolOp) -> None:
|
211
|
+
if isinstance(node.op, (ast.And, ast.Or)):
|
212
|
+
self.found = True
|
213
|
+
self.generic_visit(node)
|
214
|
+
|
215
|
+
visitor = LogicVisitor()
|
216
|
+
visitor.visit(self._tree)
|
217
|
+
return visitor.found
|
218
|
+
|
219
|
+
def _generate_error_messages(
|
220
|
+
self, var_values: Dict[str, Optional[object]]
|
221
|
+
) -> Dict[str, str]:
|
222
|
+
errors: Dict[str, str] = {}
|
223
|
+
comparisons = self._extract_comparisons()
|
224
|
+
logical = self._contains_logical_ops()
|
225
|
+
|
226
|
+
if comparisons:
|
227
|
+
for cmp in comparisons:
|
228
|
+
left, rights, ops = cmp.left, cmp.comparators, cmp.ops
|
229
|
+
for right, op in zip(rights, ops):
|
230
|
+
# Spezial-Handler?
|
231
|
+
if isinstance(left, ast.Call):
|
232
|
+
fn = self._get_node_name(left.func)
|
233
|
+
handler = self._handlers.get(fn)
|
234
|
+
if handler:
|
235
|
+
errors.update(
|
236
|
+
handler.handle(cmp, left, right, op, var_values, self)
|
237
|
+
)
|
238
|
+
continue
|
239
|
+
|
240
|
+
# Standard-Fehler
|
241
|
+
lnm = self._get_node_name(left)
|
242
|
+
rnm = self._get_node_name(right)
|
243
|
+
lval = self._eval_node(left)
|
244
|
+
rval = self._eval_node(right)
|
245
|
+
ldisp = f"[{lnm}] ({lval})" if lnm in var_values else str(lval)
|
246
|
+
rdisp = f"[{rnm}] ({rval})" if rnm in var_values else str(rval)
|
247
|
+
sym = self._get_op_symbol(op)
|
248
|
+
msg = f"{ldisp} must be {sym} {rdisp}!"
|
249
|
+
if lnm in var_values:
|
250
|
+
errors[lnm] = msg
|
251
|
+
if rnm in var_values and rnm != lnm:
|
252
|
+
errors[rnm] = msg
|
253
|
+
|
254
|
+
if logical and not self._last_result:
|
255
|
+
combo = ", ".join(f"[{v}]" for v in self._variables)
|
256
|
+
msg = f"{combo} combination is not valid"
|
257
|
+
for v in self._variables:
|
258
|
+
errors[v] = msg
|
259
|
+
|
260
|
+
return errors
|
261
|
+
|
262
|
+
# kein Vergleich → pauschale Meldung
|
263
|
+
combo = ", ".join(f"[{v}]" for v in self._variables)
|
264
|
+
return {v: f"{combo} combination is not valid" for v in self._variables}
|
265
|
+
|
266
|
+
def _get_op_symbol(self, op: Optional[ast.cmpop]) -> str:
|
267
|
+
return {
|
268
|
+
ast.Lt: "<",
|
269
|
+
ast.LtE: "<=",
|
270
|
+
ast.Gt: ">",
|
271
|
+
ast.GtE: ">=",
|
272
|
+
ast.Eq: "==",
|
273
|
+
ast.NotEq: "!=",
|
274
|
+
ast.Is: "is",
|
275
|
+
ast.IsNot: "is not",
|
276
|
+
ast.In: "in",
|
277
|
+
ast.NotIn: "not in",
|
278
|
+
}.get(type(op), "?")
|
279
|
+
|
280
|
+
def _get_node_name(self, node: ast.AST) -> str:
|
281
|
+
if isinstance(node, ast.Attribute):
|
282
|
+
parts: list[str] = []
|
283
|
+
curr: ast.AST = node
|
284
|
+
while isinstance(curr, ast.Attribute):
|
285
|
+
parts.insert(0, curr.attr)
|
286
|
+
curr = curr.value
|
287
|
+
return ".".join(parts)
|
288
|
+
if isinstance(node, ast.Name):
|
289
|
+
return node.id
|
290
|
+
if isinstance(node, ast.Constant):
|
291
|
+
return ""
|
292
|
+
if isinstance(node, ast.Call):
|
293
|
+
fn = self._get_node_name(node.func)
|
294
|
+
args = ", ".join(self._get_node_name(a) for a in node.args)
|
295
|
+
return f"{fn}({args})"
|
296
|
+
try:
|
297
|
+
# ast.unparse gibt einen str zurück
|
298
|
+
return ast.unparse(node)
|
299
|
+
except Exception:
|
300
|
+
return ""
|
301
|
+
|
302
|
+
def _eval_node(self, node: ast.expr) -> Optional[object]:
|
303
|
+
"""
|
304
|
+
Evaluiert einen AST-Ausdruck im Kontext von `x`.
|
305
|
+
"""
|
306
|
+
if not isinstance(node, ast.expr):
|
307
|
+
return None
|
308
|
+
try:
|
309
|
+
expr = ast.Expression(body=node)
|
310
|
+
code = compile(expr, "<ast>", "eval")
|
311
|
+
return eval(code, {"x": self._last_input}, {})
|
312
|
+
except Exception:
|
313
|
+
return None
|
@@ -0,0 +1,207 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: GeneralManager
|
3
|
+
Version: 0.0.0
|
4
|
+
Summary: Kurzbeschreibung deines Pakets
|
5
|
+
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
|
+
License: Non-Commercial MIT License
|
7
|
+
|
8
|
+
Copyright (c) 2025 Tim Kleindick
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
of this software and associated documentation files (the “Software”), to deal
|
12
|
+
in the Software **solely for non-commercial purposes**, including without
|
13
|
+
limitation the rights to use, copy, modify, merge, publish, distribute,
|
14
|
+
sublicense, and/or sell copies of the Software **for non-commercial use only**,
|
15
|
+
and to permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
the following conditions:
|
17
|
+
|
18
|
+
1. Non-Commercial Use Only
|
19
|
+
The Software may **not** be used for commercial purposes. “Commercial” means
|
20
|
+
any activity intended for or directed toward commercial advantage or
|
21
|
+
monetary compensation.
|
22
|
+
|
23
|
+
2. Copyright Notice & License Text
|
24
|
+
The above copyright notice and this permission notice shall be included in
|
25
|
+
all copies or substantial portions of the Software.
|
26
|
+
|
27
|
+
3. Disclaimer of Warranty
|
28
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
29
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
31
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
32
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
33
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
34
|
+
SOFTWARE.
|
35
|
+
Requires-Python: >=3.12
|
36
|
+
Description-Content-Type: text/markdown
|
37
|
+
License-File: LICENSE
|
38
|
+
Requires-Dist: asgiref>=3.8.1
|
39
|
+
Requires-Dist: Django>=5.2
|
40
|
+
Requires-Dist: django-simple-history>=3.8.0
|
41
|
+
Requires-Dist: exrex>=0.12.0
|
42
|
+
Requires-Dist: factory_boy>=3.3.3
|
43
|
+
Requires-Dist: Faker>=37.1.0
|
44
|
+
Requires-Dist: flexcache>=0.3
|
45
|
+
Requires-Dist: flexparser>=0.4
|
46
|
+
Requires-Dist: gitdb>=4.0.12
|
47
|
+
Requires-Dist: GitPython>=3.1.41
|
48
|
+
Requires-Dist: graphene>=3.4.3
|
49
|
+
Requires-Dist: graphene-django>=3.2.3
|
50
|
+
Requires-Dist: graphql-core>=3.2.6
|
51
|
+
Requires-Dist: graphql-relay>=3.2.0
|
52
|
+
Requires-Dist: numpy>=2.2.5
|
53
|
+
Requires-Dist: Pint>=0.24.4
|
54
|
+
Requires-Dist: platformdirs>=4.3.7
|
55
|
+
Requires-Dist: promise>=2.3
|
56
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
57
|
+
Requires-Dist: setuptools>=75.6.0
|
58
|
+
Requires-Dist: six>=1.17.0
|
59
|
+
Requires-Dist: smmap>=5.0.2
|
60
|
+
Requires-Dist: sqlparse>=0.5.3
|
61
|
+
Requires-Dist: text-unidecode>=1.3
|
62
|
+
Requires-Dist: typing_extensions>=4.13.2
|
63
|
+
Requires-Dist: tzdata>=2025.2
|
64
|
+
Dynamic: license-file
|
65
|
+
|
66
|
+
# GeneralManager
|
67
|
+
|
68
|
+
## Überblick
|
69
|
+
|
70
|
+
Das GeneralManager-Modul ist ein leistungsstarkes und flexibles Framework, das speziell für die Verwaltung und Verarbeitung von Daten entwickelt wurde. Es bietet eine modulare Struktur, die es Entwicklern ermöglicht, komplexe Geschäftslogiken effizient zu implementieren und zu verwalten. Das Modul ist vollständig in Python geschrieben und nutzt Django als Backend-Framework.
|
71
|
+
|
72
|
+
## Hauptfunktionen
|
73
|
+
|
74
|
+
### 1. **Datenmanagement**
|
75
|
+
- **Flexibilität**: Unterstützt die Verwaltung aller Arten von Daten, nicht nur Projekte und Derivate.
|
76
|
+
- **Datenbank-Integration**: Nahtlose Integration mit dem Django ORM für Datenbankoperationen.
|
77
|
+
- **Externe Schnittstellen**: Unterstützung für Schnittstellen zu anderen Programmen, wie z. B. Excel-Interfaces.
|
78
|
+
|
79
|
+
### 2. **Datenmodellierung**
|
80
|
+
- **Django-Modelle**: Die Datenstruktur basiert auf Django-Modellen, die durch benutzerdefinierte Felder wie `MeasurementField` erweitert werden.
|
81
|
+
- **Regeln und Validierungen**: Definieren Sie Regeln für Datenvalidierungen, z. B. dass das Startdatum eines Projekts vor dem Enddatum liegen muss.
|
82
|
+
|
83
|
+
### 3. **GraphQL-Integration**
|
84
|
+
- Automatische Generierung von GraphQL-Schnittstellen für alle Modelle.
|
85
|
+
- Unterstützung für benutzerdefinierte Abfragen und Mutationen.
|
86
|
+
|
87
|
+
### 4. **Berechtigungssystem**
|
88
|
+
- **ManagerBasedPermission**: Ein flexibles Berechtigungssystem, das auf Benutzerrollen und Attributen basiert.
|
89
|
+
- Unterstützung für CRUD-Berechtigungen auf Attributebene.
|
90
|
+
|
91
|
+
### 5. **Interfaces**
|
92
|
+
- **CalculationInterface**: Ermöglicht die Implementierung von Berechnungslogiken.
|
93
|
+
- **DatabaseInterface**: Bietet eine standardisierte Schnittstelle für Datenbankoperationen.
|
94
|
+
- **ReadOnlyInterface**: Für schreibgeschützte Datenzugriffe.
|
95
|
+
|
96
|
+
### 6. **Datenverteilung und Berechnung**
|
97
|
+
- **Volumenverteilung**: Automatische Berechnung und Verteilung von Volumen über mehrere Jahre.
|
98
|
+
- **Kommerzielle Berechnungen**: Berechnung von Gesamtvolumen, Versandkosten und Einnahmen für Projekte.
|
99
|
+
|
100
|
+
## Anwendung
|
101
|
+
|
102
|
+
### Installation
|
103
|
+
|
104
|
+
Installieren Sie das Modul über `pip`:
|
105
|
+
|
106
|
+
```bash
|
107
|
+
pip install GeneralManager
|
108
|
+
```
|
109
|
+
|
110
|
+
### Beispielcode
|
111
|
+
|
112
|
+
Hier ist ein Beispiel, wie Sie einen GeneralManager erstellen und Testdaten (in diesem Fall 10 Projekte) generieren können:
|
113
|
+
|
114
|
+
```python
|
115
|
+
from general_manager import GeneralManager
|
116
|
+
from general_manager.interface.database import DatabaseInterface
|
117
|
+
from general_manager.measurement import MeasurementField, Measurement
|
118
|
+
from general_manager.permission import ManagerBasedPermission
|
119
|
+
|
120
|
+
class Project(GeneralManager):
|
121
|
+
name: str
|
122
|
+
start_date: Optional[date]
|
123
|
+
end_date: Optional[date]
|
124
|
+
total_capex: Optional[Measurement]
|
125
|
+
derivative_list: DatabaseBucket[Derivative]
|
126
|
+
|
127
|
+
class Interface(DatabaseInterface):
|
128
|
+
name = CharField(max_length=50)
|
129
|
+
number = CharField(max_length=7, validators=[RegexValidator(r"^AP\d{4,5}$")])
|
130
|
+
description = TextField(null=True, blank=True)
|
131
|
+
start_date = DateField(null=True, blank=True)
|
132
|
+
end_date = DateField(null=True, blank=True)
|
133
|
+
total_capex = MeasurementField(base_unit="EUR", null=True, blank=True)
|
134
|
+
|
135
|
+
class Meta:
|
136
|
+
constraints = [
|
137
|
+
constraints.UniqueConstraint(
|
138
|
+
fields=["name", "number"], name="unique_booking"
|
139
|
+
)
|
140
|
+
]
|
141
|
+
|
142
|
+
rules = [
|
143
|
+
Rule["Project"](
|
144
|
+
lambda x: cast(date, x.start_date) < cast(date, x.end_date)
|
145
|
+
),
|
146
|
+
Rule["Project"](lambda x: cast(Measurement, x.total_capex) >= "0 EUR"),
|
147
|
+
]
|
148
|
+
|
149
|
+
class Factory:
|
150
|
+
name = LazyProjectName()
|
151
|
+
end_date = LazyDeltaDate(365 * 6, "start_date")
|
152
|
+
total_capex = LazyMeasurement(75_000, 1_000_000, "EUR")
|
153
|
+
|
154
|
+
class Permission(ManagerBasedPermission):
|
155
|
+
__read__ = ["ends_with:name:X-771", "public"]
|
156
|
+
__create__ = ["admin", "isMatchingKeyAccount"]
|
157
|
+
__update__ = ["admin", "isMatchingKeyAccount", "isProjectTeamMember"]
|
158
|
+
__delete__ = ["admin", "isMatchingKeyAccount", "isProjectTeamMember"]
|
159
|
+
|
160
|
+
total_capex = {"update": ["isSalesResponsible", "isProjectManager"]}
|
161
|
+
|
162
|
+
Project.Factory.createBatch(10)
|
163
|
+
```
|
164
|
+
|
165
|
+
### GraphQL-Integration
|
166
|
+
|
167
|
+
Das Modul generiert automatisch GraphQL-Schnittstellen für alle Modelle. Sie können Abfragen und Mutationen über die GraphQL-URL ausführen, die in den Django-Einstellungen definiert ist.
|
168
|
+
|
169
|
+
Beispiel für eine GraphQL-Abfrage:
|
170
|
+
|
171
|
+
```graphql
|
172
|
+
query {
|
173
|
+
projectList {
|
174
|
+
name
|
175
|
+
startDate
|
176
|
+
endDate
|
177
|
+
totalCapex {
|
178
|
+
value
|
179
|
+
unit
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
```
|
184
|
+
|
185
|
+
## Vorteile
|
186
|
+
|
187
|
+
- **Modularität**: Einfach erweiterbar und anpassbar.
|
188
|
+
- **Flexibilität**: Unterstützt komplexe Geschäftslogiken und Berechnungen.
|
189
|
+
- **Integration**: Nahtlose Integration mit Django und GraphQL.
|
190
|
+
- **Berechtigungen**: Fein abgestimmte Berechtigungen für Benutzer und Attribute.
|
191
|
+
- **Datenvalidierung**: Automatische Validierung von Daten durch Regeln und Constraints.
|
192
|
+
- **Caching**: Automatische Cache-Generierung mit @cached Decorator, um die Leistung zu verbessern.
|
193
|
+
|
194
|
+
## Anforderungen
|
195
|
+
|
196
|
+
- Python >= 3.12
|
197
|
+
- Django >= 5.2
|
198
|
+
- Zusätzliche Abhängigkeiten (siehe `requirements.txt`):
|
199
|
+
- `graphene`
|
200
|
+
- `numpy`
|
201
|
+
- `Pint`
|
202
|
+
- `factory_boy`
|
203
|
+
- uvm.
|
204
|
+
|
205
|
+
## Lizenz
|
206
|
+
|
207
|
+
Dieses Projekt steht unter der **Non-Commercial MIT License**. Es darf nur für nicht-kommerzielle Zwecke verwendet werden. Weitere Details finden Sie in der [LICENSE](./LICENSE).
|
@@ -0,0 +1,43 @@
|
|
1
|
+
general_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
general_manager/apps.py,sha256=-AX9imxqht55Xr8q5abnoeKc48ubj-tzDq7WhUrcNUg,3313
|
3
|
+
general_manager/api/graphql.py,sha256=Virzi9njCD6qVcOLxmD9mdQOgd6ap8JeuKWovBZ73HA,27514
|
4
|
+
general_manager/api/mutation.py,sha256=uu5RVxc9wbb-Zrbtt4azegvyKymMqEsxk_CkerKCd9Q,5178
|
5
|
+
general_manager/api/property.py,sha256=oc93p1P8dcIvrNorRuqD1EJVsd6eYttYhZuAS0s28gs,696
|
6
|
+
general_manager/auxiliary/__init__.py,sha256=4IwKJzsNxGduF-Ej0u1BNHVaMhkql8PjHbVtx9DOTSY,76
|
7
|
+
general_manager/auxiliary/argsToKwargs.py,sha256=kmp1xonpQp4X_y8ZJG6c5uOW7zQwo0HtPqsHWVzXRSM,921
|
8
|
+
general_manager/auxiliary/filterParser.py,sha256=exmRGVwvdMPgN-65MLwAV2q6L23bI4gTWb6lWXO4ZuU,3108
|
9
|
+
general_manager/auxiliary/noneToZero.py,sha256=UtIdye6wPdiIZqOnHxSdHnc0aKcH_sMjeCHPs7uLwKo,276
|
10
|
+
general_manager/cache/cacheDecorator.py,sha256=RmUDyJfkouO2-R2KZZJF1n8RCB0FjVa2OoTeLQ0vUyg,2934
|
11
|
+
general_manager/cache/cacheTracker.py,sha256=vUQCarqABIW_O02hbKeRo0thurmPD0TfrOlIUh7kneI,1045
|
12
|
+
general_manager/cache/dependencyIndex.py,sha256=KUDroajKrWfJv0PO2EQVNTrYq7XxrpBT4MGKBgPwMwo,10676
|
13
|
+
general_manager/cache/pathMapping.py,sha256=WtECIek9fI-2_nqIYI4Ux9Lan6g8P9TMO_AfthkznX8,5656
|
14
|
+
general_manager/cache/signals.py,sha256=ZHeXKFMN7tj9t0J-vSqf_05_NhGqEF2sZtbZO3vaRqI,1234
|
15
|
+
general_manager/factory/__init__.py,sha256=DLSQbpSBpujPtDSZcruPc43OLWzKCCtf20gbalCDYRU,91
|
16
|
+
general_manager/factory/factories.py,sha256=31NyVw2Ju5ZDPJ6sax6_seyZesCU5UrviNbk-2PaKnA,11559
|
17
|
+
general_manager/factory/lazy_methods.py,sha256=UJC50a4Gbe4T5IQPh7ucyQqpaS2x4zhQzznwpJvyVLo,1155
|
18
|
+
general_manager/interface/__init__.py,sha256=6x5adQLefTugvrJeyPcAxstyqgLAYeaJ1EPdAbac9pE,213
|
19
|
+
general_manager/interface/baseInterface.py,sha256=mvSKUlA-0fazNnaIXGBwkiZxmX8DM_sOn-SaAIpaW8I,10273
|
20
|
+
general_manager/interface/calculationInterface.py,sha256=GzSNXjU6Z7bFz60gHyMKkI5xNUDIPuniV8wbyVtQT50,14250
|
21
|
+
general_manager/interface/databaseInterface.py,sha256=2Xzb04J7c1Z4ZKlA-6-8AOXaWNvF6zUVNNzM6k0jzLQ,27337
|
22
|
+
general_manager/manager/__init__.py,sha256=l3RYp62aEhj3Y975_XUTIzo35LUnkTJHkb_hgChnXXI,111
|
23
|
+
general_manager/manager/generalManager.py,sha256=xt7tvwTaPGhh7Z8VhbdMJuScsl78B9QDVQoMWv4amuo,5158
|
24
|
+
general_manager/manager/groupManager.py,sha256=O4FABqbm7KlZw6t36Ot3HU1FsBYN0h6Zhmk7ktN8a-Y,10087
|
25
|
+
general_manager/manager/input.py,sha256=NTZis3QsF0N3SODZBeQrR2R-_iMbmdEBvoYNuVKWNtE,1737
|
26
|
+
general_manager/manager/meta.py,sha256=5wHrCVnua5c38vpVZSCesrNvgydQDH8h6pxW6_QgCDg,3107
|
27
|
+
general_manager/measurement/__init__.py,sha256=X97meFujBldE5v0WMF7SmKeGpC5R0JTczfLo_Lq1Xek,84
|
28
|
+
general_manager/measurement/measurement.py,sha256=hTISANW7nAN2voakq9f-naJxa1MUcxWLDrcDW1Rdn3s,9340
|
29
|
+
general_manager/measurement/measurementField.py,sha256=iq9Hqe6ZGX8CxXm4nIqTAWTRkQVptzpqE9ExX-jFyNs,5928
|
30
|
+
general_manager/permission/__init__.py,sha256=5UlDERN60Vn8obGVkT-cOM8kHjzmoxgK5w5FgTCDhGE,59
|
31
|
+
general_manager/permission/basePermission.py,sha256=PsJiX-UNeSh6xUlcUwuQNCLYvHZipWUJ0kAoMkdxXJc,6113
|
32
|
+
general_manager/permission/fileBasedPermission.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
general_manager/permission/managerBasedPermission.py,sha256=VgZJVgkXWdaLk6K7c0kYxTbELnTYK6UY9mMuKH5u64w,5728
|
34
|
+
general_manager/permission/permissionChecks.py,sha256=T-9khBqiwM4ASBdey9p07sC_xgzceIU9EAE0reukguM,1655
|
35
|
+
general_manager/permission/permissionDataManager.py,sha256=Ji7fsnuaKTa6M8yzCGyzrIHyGa_ZvqJM7sXR97-uTrA,1937
|
36
|
+
general_manager/rule/__init__.py,sha256=4Har5cfPD1fmOsilTDod-ZUz3Com-tkl58jz7yY4fD0,23
|
37
|
+
general_manager/rule/handler.py,sha256=O3BZbTnUE9o3LTSWpoq6hgm3tKy1sEBjRcDrRaA54Gw,3936
|
38
|
+
general_manager/rule/rule.py,sha256=tu3pNe_avs6ayI16KYeaQX85o2lXcPip_LN9CJEhr8Y,10708
|
39
|
+
generalmanager-0.0.0.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
|
40
|
+
generalmanager-0.0.0.dist-info/METADATA,sha256=xCCtFrVLe_ta1Y9q5Ezw5SeJhZTQ7aVIq53ggT3L88s,8188
|
41
|
+
generalmanager-0.0.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
42
|
+
generalmanager-0.0.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
|
43
|
+
generalmanager-0.0.0.dist-info/RECORD,,
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Non-Commercial MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Tim Kleindick
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the “Software”), to deal
|
7
|
+
in the Software **solely for non-commercial purposes**, including without
|
8
|
+
limitation the rights to use, copy, modify, merge, publish, distribute,
|
9
|
+
sublicense, and/or sell copies of the Software **for non-commercial use only**,
|
10
|
+
and to permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
1. Non-Commercial Use Only
|
14
|
+
The Software may **not** be used for commercial purposes. “Commercial” means
|
15
|
+
any activity intended for or directed toward commercial advantage or
|
16
|
+
monetary compensation.
|
17
|
+
|
18
|
+
2. Copyright Notice & License Text
|
19
|
+
The above copyright notice and this permission notice shall be included in
|
20
|
+
all copies or substantial portions of the Software.
|
21
|
+
|
22
|
+
3. Disclaimer of Warranty
|
23
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
26
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
29
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
general_manager
|