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,233 @@
|
|
1
|
+
# units.py
|
2
|
+
from __future__ import annotations
|
3
|
+
from typing import Any, Callable
|
4
|
+
import pint
|
5
|
+
from decimal import Decimal, getcontext, InvalidOperation
|
6
|
+
from operator import eq, ne, lt, le, gt, ge
|
7
|
+
|
8
|
+
|
9
|
+
# Set precision for Decimal
|
10
|
+
getcontext().prec = 28
|
11
|
+
|
12
|
+
# Create a new UnitRegistry
|
13
|
+
ureg = pint.UnitRegistry(auto_reduce_dimensions=True)
|
14
|
+
|
15
|
+
# Define currency units
|
16
|
+
currency_units = ["EUR", "USD", "GBP", "JPY", "CHF", "AUD", "CAD"]
|
17
|
+
for currency in currency_units:
|
18
|
+
# Define each currency as its own dimension
|
19
|
+
ureg.define(f"{currency} = [{currency}]")
|
20
|
+
|
21
|
+
|
22
|
+
class Measurement:
|
23
|
+
def __init__(self, value: Decimal | float | int | str, unit: str):
|
24
|
+
if not isinstance(value, (Decimal, float, int)):
|
25
|
+
try:
|
26
|
+
value = Decimal(str(value))
|
27
|
+
except Exception:
|
28
|
+
raise TypeError("Value must be a Decimal, float, int or compatible.")
|
29
|
+
if not isinstance(value, Decimal):
|
30
|
+
value = Decimal(str(value))
|
31
|
+
self.__quantity = self.formatDecimal(value) * ureg.Quantity(1, unit)
|
32
|
+
|
33
|
+
def __getstate__(self):
|
34
|
+
state = {
|
35
|
+
"magnitude": str(self.quantity.magnitude),
|
36
|
+
"unit": str(self.quantity.units),
|
37
|
+
}
|
38
|
+
return state
|
39
|
+
|
40
|
+
def __setstate__(self, state):
|
41
|
+
value = Decimal(state["magnitude"])
|
42
|
+
unit = state["unit"]
|
43
|
+
self.__quantity = self.formatDecimal(value) * ureg.Quantity(1, unit)
|
44
|
+
|
45
|
+
@property
|
46
|
+
def quantity(self) -> pint.Quantity:
|
47
|
+
return self.__quantity
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def from_string(cls, value: str) -> Measurement:
|
51
|
+
value, unit = value.split(" ")
|
52
|
+
return cls(value, unit)
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def formatDecimal(value: Decimal) -> Decimal:
|
56
|
+
value = value.normalize()
|
57
|
+
if value == value.to_integral_value():
|
58
|
+
try:
|
59
|
+
return value.quantize(Decimal("1"))
|
60
|
+
except InvalidOperation:
|
61
|
+
return value
|
62
|
+
else:
|
63
|
+
return value
|
64
|
+
|
65
|
+
def to(self, target_unit: str, exchange_rate: float | None = None):
|
66
|
+
if self.is_currency():
|
67
|
+
if self.quantity.units == ureg(target_unit):
|
68
|
+
return self # Same currency, no conversion needed
|
69
|
+
elif exchange_rate is not None:
|
70
|
+
# Convert using the provided exchange rate
|
71
|
+
value = self.quantity.magnitude * Decimal(str(exchange_rate))
|
72
|
+
return Measurement(value, target_unit)
|
73
|
+
else:
|
74
|
+
raise ValueError(
|
75
|
+
"Conversion between currencies requires an exchange rate."
|
76
|
+
)
|
77
|
+
else:
|
78
|
+
# Standard conversion for physical units
|
79
|
+
converted_quantity: pint.Quantity = self.quantity.to(target_unit) # type: ignore
|
80
|
+
value = Decimal(str(converted_quantity.magnitude))
|
81
|
+
unit = str(converted_quantity.units)
|
82
|
+
return Measurement(value, unit)
|
83
|
+
|
84
|
+
def is_currency(self):
|
85
|
+
# Check if the unit is a defined currency
|
86
|
+
return str(self.quantity.units) in currency_units
|
87
|
+
|
88
|
+
def __add__(self, other: Any) -> Measurement:
|
89
|
+
if not isinstance(other, Measurement):
|
90
|
+
raise TypeError("Addition is only allowed between Measurement instances.")
|
91
|
+
if self.is_currency() and other.is_currency():
|
92
|
+
# Both are currencies
|
93
|
+
if self.quantity.units != other.quantity.units:
|
94
|
+
raise ValueError(
|
95
|
+
"Addition between different currencies is not allowed."
|
96
|
+
)
|
97
|
+
result_quantity = self.quantity + other.quantity
|
98
|
+
if not isinstance(result_quantity, pint.Quantity):
|
99
|
+
raise ValueError("Units are not compatible for addition.")
|
100
|
+
return Measurement(
|
101
|
+
Decimal(str(result_quantity.magnitude)), str(result_quantity.units)
|
102
|
+
)
|
103
|
+
elif not self.is_currency() and not other.is_currency():
|
104
|
+
# Both are physical units
|
105
|
+
if self.quantity.dimensionality != other.quantity.dimensionality:
|
106
|
+
raise ValueError("Units are not compatible for addition.")
|
107
|
+
result_quantity = self.quantity + other.quantity
|
108
|
+
if not isinstance(result_quantity, pint.Quantity):
|
109
|
+
raise ValueError("Units are not compatible for addition.")
|
110
|
+
return Measurement(
|
111
|
+
Decimal(str(result_quantity.magnitude)), str(result_quantity.units)
|
112
|
+
)
|
113
|
+
else:
|
114
|
+
raise TypeError(
|
115
|
+
"Addition between currency and physical unit is not allowed."
|
116
|
+
)
|
117
|
+
|
118
|
+
def __sub__(self, other: Any) -> Measurement:
|
119
|
+
if not isinstance(other, Measurement):
|
120
|
+
raise TypeError(
|
121
|
+
"Subtraction is only allowed between Measurement instances."
|
122
|
+
)
|
123
|
+
if self.is_currency() and other.is_currency():
|
124
|
+
# Both are currencies
|
125
|
+
if self.quantity.units != other.quantity.units:
|
126
|
+
raise ValueError(
|
127
|
+
"Subtraction between different currencies is not allowed."
|
128
|
+
)
|
129
|
+
result_quantity = self.quantity - other.quantity
|
130
|
+
return Measurement(
|
131
|
+
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
132
|
+
)
|
133
|
+
elif not self.is_currency() and not other.is_currency():
|
134
|
+
# Both are physical units
|
135
|
+
if self.quantity.dimensionality != other.quantity.dimensionality:
|
136
|
+
raise ValueError("Units are not compatible for subtraction.")
|
137
|
+
result_quantity = self.quantity - other.quantity
|
138
|
+
return Measurement(
|
139
|
+
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
140
|
+
)
|
141
|
+
else:
|
142
|
+
raise TypeError(
|
143
|
+
"Subtraction between currency and physical unit is not allowed."
|
144
|
+
)
|
145
|
+
|
146
|
+
def __mul__(self, other: Any) -> Measurement:
|
147
|
+
if isinstance(other, Measurement):
|
148
|
+
if self.is_currency() or other.is_currency():
|
149
|
+
raise TypeError(
|
150
|
+
"Multiplication between two currency amounts is not allowed."
|
151
|
+
)
|
152
|
+
result_quantity = self.quantity * other.quantity
|
153
|
+
return Measurement(
|
154
|
+
Decimal(str(result_quantity.magnitude)), str(result_quantity.units)
|
155
|
+
)
|
156
|
+
elif isinstance(other, (Decimal, float, int)):
|
157
|
+
if not isinstance(other, Decimal):
|
158
|
+
other = Decimal(str(other))
|
159
|
+
result_quantity = self.quantity * other
|
160
|
+
return Measurement(
|
161
|
+
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
162
|
+
)
|
163
|
+
else:
|
164
|
+
raise TypeError(
|
165
|
+
"Multiplication is only allowed with Measurement or numeric values."
|
166
|
+
)
|
167
|
+
|
168
|
+
def __truediv__(self, other: Any) -> Measurement:
|
169
|
+
if isinstance(other, Measurement):
|
170
|
+
if self.is_currency() and other.is_currency():
|
171
|
+
raise TypeError("Division between two currency amounts is not allowed.")
|
172
|
+
result_quantity = self.quantity / other.quantity
|
173
|
+
return Measurement(
|
174
|
+
Decimal(str(result_quantity.magnitude)), str(result_quantity.units)
|
175
|
+
)
|
176
|
+
elif isinstance(other, (Decimal, float, int)):
|
177
|
+
if not isinstance(other, Decimal):
|
178
|
+
other = Decimal(str(other))
|
179
|
+
result_quantity = self.quantity / other
|
180
|
+
return Measurement(
|
181
|
+
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
182
|
+
)
|
183
|
+
else:
|
184
|
+
raise TypeError(
|
185
|
+
"Division is only allowed with Measurement or numeric values."
|
186
|
+
)
|
187
|
+
|
188
|
+
def __str__(self):
|
189
|
+
if not str(self.quantity.units) == "dimensionless":
|
190
|
+
return f"{self.quantity.magnitude} {self.quantity.units}"
|
191
|
+
return f"{self.quantity.magnitude}"
|
192
|
+
|
193
|
+
def __repr__(self):
|
194
|
+
return f"Measurement({self.quantity.magnitude}, '{self.quantity.units}')"
|
195
|
+
|
196
|
+
def _compare(self, other: Any, operation: Callable[..., bool]) -> bool:
|
197
|
+
if isinstance(other, str):
|
198
|
+
other = Measurement.from_string(other)
|
199
|
+
|
200
|
+
# Überprüfen, ob `other` ein Measurement-Objekt ist
|
201
|
+
if not isinstance(other, Measurement):
|
202
|
+
return NotImplemented
|
203
|
+
try:
|
204
|
+
# Convert `other` to the same units as `self`
|
205
|
+
other_converted: pint.Quantity = other.quantity.to(self.quantity.units) # type: ignore
|
206
|
+
# Apply the comparison operation
|
207
|
+
return operation(self.quantity.magnitude, other_converted.magnitude)
|
208
|
+
except pint.DimensionalityError:
|
209
|
+
raise ValueError("Cannot compare measurements with different dimensions.")
|
210
|
+
|
211
|
+
def __radd__(self, other: Any) -> Measurement:
|
212
|
+
if other == 0:
|
213
|
+
return self
|
214
|
+
return self.__add__(other)
|
215
|
+
|
216
|
+
# Comparison Operators
|
217
|
+
def __eq__(self, other: Any) -> bool:
|
218
|
+
return self._compare(other, eq)
|
219
|
+
|
220
|
+
def __ne__(self, other: Any) -> bool:
|
221
|
+
return self._compare(other, ne)
|
222
|
+
|
223
|
+
def __lt__(self, other: Any) -> bool:
|
224
|
+
return self._compare(other, lt)
|
225
|
+
|
226
|
+
def __le__(self, other: Any) -> bool:
|
227
|
+
return self._compare(other, le)
|
228
|
+
|
229
|
+
def __gt__(self, other: Any) -> bool:
|
230
|
+
return self._compare(other, gt)
|
231
|
+
|
232
|
+
def __ge__(self, other: Any) -> bool:
|
233
|
+
return self._compare(other, ge)
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# fields.py
|
2
|
+
from __future__ import annotations
|
3
|
+
from django.db import models
|
4
|
+
from django.core.exceptions import ValidationError
|
5
|
+
from decimal import Decimal
|
6
|
+
from general_manager.measurement.measurement import Measurement, ureg, currency_units
|
7
|
+
import pint
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
|
11
|
+
class MeasurementField(models.Field): # type: ignore
|
12
|
+
description = (
|
13
|
+
"A field that stores a measurement value, both in base unit and original unit"
|
14
|
+
)
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
base_unit: str,
|
19
|
+
null: bool = False,
|
20
|
+
blank: bool = False,
|
21
|
+
editable: bool = True,
|
22
|
+
*args: list[Any],
|
23
|
+
**kwargs: dict[str, Any],
|
24
|
+
):
|
25
|
+
self.base_unit = base_unit # E.g., 'meter' for length units
|
26
|
+
# Determine the dimensionality of the base unit
|
27
|
+
self.base_dimension = ureg.parse_expression(self.base_unit).dimensionality
|
28
|
+
# Internal fields
|
29
|
+
null_blank_kwargs = {}
|
30
|
+
if null is True:
|
31
|
+
null_blank_kwargs["null"] = True
|
32
|
+
if blank is True:
|
33
|
+
null_blank_kwargs["blank"] = True
|
34
|
+
self.editable = editable
|
35
|
+
self.value_field: models.DecimalField[Decimal] = models.DecimalField(
|
36
|
+
max_digits=30,
|
37
|
+
decimal_places=10,
|
38
|
+
db_index=True,
|
39
|
+
**null_blank_kwargs,
|
40
|
+
editable=editable,
|
41
|
+
)
|
42
|
+
self.unit_field: models.CharField[str] = models.CharField(
|
43
|
+
max_length=30, **null_blank_kwargs, editable=editable
|
44
|
+
)
|
45
|
+
super().__init__(null=null, blank=blank, *args, **kwargs)
|
46
|
+
|
47
|
+
def contribute_to_class(
|
48
|
+
self, cls: type, name: str, private_only: bool = False, **kwargs: dict[str, Any]
|
49
|
+
) -> None:
|
50
|
+
self.name = name
|
51
|
+
self.value_attr = f"{name}_value"
|
52
|
+
self.unit_attr = f"{name}_unit"
|
53
|
+
self.value_field.attname = self.value_attr
|
54
|
+
self.unit_field.attname = self.unit_attr
|
55
|
+
self.value_field.name = self.value_attr
|
56
|
+
self.unit_field.name = self.unit_attr
|
57
|
+
self.value_field.column = self.value_attr
|
58
|
+
self.unit_field.column = self.unit_attr
|
59
|
+
|
60
|
+
self.value_field.model = cls
|
61
|
+
self.unit_field.model = cls
|
62
|
+
|
63
|
+
self.value_field.contribute_to_class(cls, self.value_attr)
|
64
|
+
self.unit_field.contribute_to_class(cls, self.unit_attr)
|
65
|
+
|
66
|
+
setattr(cls, self.name, self)
|
67
|
+
|
68
|
+
def __get__(self, instance: Any, owner: Any) -> Any:
|
69
|
+
if instance is None:
|
70
|
+
return self
|
71
|
+
value = getattr(instance, self.value_attr)
|
72
|
+
unit = getattr(instance, self.unit_attr)
|
73
|
+
if value is None or unit is None:
|
74
|
+
return None
|
75
|
+
# Create a Measurement object with the value in the original unit
|
76
|
+
quantity_in_base_unit = Decimal(value) * ureg(self.base_unit)
|
77
|
+
# Convert back to the original unit
|
78
|
+
try:
|
79
|
+
quantity_in_original_unit: pint.Quantity = quantity_in_base_unit.to(unit) # type: ignore
|
80
|
+
except pint.errors.DimensionalityError:
|
81
|
+
# If the unit is not compatible, return the value in base unit
|
82
|
+
quantity_in_original_unit = quantity_in_base_unit
|
83
|
+
return Measurement(
|
84
|
+
quantity_in_original_unit.magnitude, str(quantity_in_original_unit.units)
|
85
|
+
)
|
86
|
+
|
87
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
88
|
+
if self.editable is False:
|
89
|
+
raise ValidationError(f"{self.name} is not editable.")
|
90
|
+
if value is None:
|
91
|
+
setattr(instance, self.value_attr, None)
|
92
|
+
setattr(instance, self.unit_attr, None)
|
93
|
+
return
|
94
|
+
elif isinstance(value, str):
|
95
|
+
try:
|
96
|
+
value = Measurement.from_string(value)
|
97
|
+
except ValueError:
|
98
|
+
raise ValidationError(
|
99
|
+
{self.name: ["Value must be a Measurement instance or None."]}
|
100
|
+
)
|
101
|
+
if isinstance(value, Measurement):
|
102
|
+
if str(self.base_unit) in currency_units:
|
103
|
+
# Base unit is a currency
|
104
|
+
if not value.is_currency():
|
105
|
+
raise ValidationError(
|
106
|
+
{
|
107
|
+
self.name: [
|
108
|
+
f"The unit must be a currency ({', '.join(currency_units)})."
|
109
|
+
]
|
110
|
+
}
|
111
|
+
)
|
112
|
+
else:
|
113
|
+
# Physical unit
|
114
|
+
if value.is_currency():
|
115
|
+
raise ValidationError(
|
116
|
+
{self.name: ["The unit cannot be a currency."]}
|
117
|
+
)
|
118
|
+
elif value.quantity.dimensionality != self.base_dimension:
|
119
|
+
raise ValidationError(
|
120
|
+
{
|
121
|
+
self.name: [
|
122
|
+
f"The unit must be compatible with '{self.base_unit}'."
|
123
|
+
]
|
124
|
+
}
|
125
|
+
)
|
126
|
+
# Store the value in the base unit
|
127
|
+
try:
|
128
|
+
value_in_base_unit: Any = value.quantity.to(self.base_unit).magnitude # type: ignore
|
129
|
+
except pint.errors.DimensionalityError:
|
130
|
+
raise ValidationError(
|
131
|
+
{
|
132
|
+
self.name: [
|
133
|
+
f"The unit must be compatible with '{self.base_unit}'."
|
134
|
+
]
|
135
|
+
}
|
136
|
+
)
|
137
|
+
setattr(instance, self.value_attr, Decimal(str(value_in_base_unit)))
|
138
|
+
# Store the original unit
|
139
|
+
setattr(instance, self.unit_attr, str(value.quantity.units))
|
140
|
+
else:
|
141
|
+
raise ValidationError(
|
142
|
+
{self.name: ["Value must be a Measurement instance or None."]}
|
143
|
+
)
|
144
|
+
|
145
|
+
def get_prep_value(self, value: Any) -> Any:
|
146
|
+
# Not needed since we use internal fields
|
147
|
+
pass
|
148
|
+
|
149
|
+
def deconstruct(self):
|
150
|
+
name, path, args, kwargs = super().deconstruct()
|
151
|
+
kwargs["base_unit"] = self.base_unit
|
152
|
+
return name, path, args, kwargs
|
@@ -0,0 +1 @@
|
|
1
|
+
from .managerBasedPermission import ManagerBasedPermission
|
@@ -0,0 +1,178 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal
|
4
|
+
from general_manager.permission.permissionChecks import (
|
5
|
+
permission_functions,
|
6
|
+
permission_filter,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from django.contrib.auth.models import AbstractUser, AnonymousUser
|
12
|
+
from general_manager.permission.permissionDataManager import (
|
13
|
+
PermissionDataManager,
|
14
|
+
)
|
15
|
+
from general_manager.manager.generalManager import GeneralManager
|
16
|
+
from general_manager.manager.meta import GeneralManagerMeta
|
17
|
+
|
18
|
+
|
19
|
+
class BasePermission(ABC):
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
|
24
|
+
request_user: AbstractUser | AnonymousUser,
|
25
|
+
) -> None:
|
26
|
+
self._instance = instance
|
27
|
+
self._request_user = request_user
|
28
|
+
|
29
|
+
@property
|
30
|
+
def instance(self) -> PermissionDataManager | GeneralManager | GeneralManagerMeta:
|
31
|
+
return self._instance
|
32
|
+
|
33
|
+
@property
|
34
|
+
def request_user(self) -> AbstractUser | AnonymousUser:
|
35
|
+
return self._request_user
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def checkCreatePermission(
|
39
|
+
cls,
|
40
|
+
data: dict[str, Any],
|
41
|
+
manager: type[GeneralManager],
|
42
|
+
request_user: AbstractUser | AnonymousUser | Any,
|
43
|
+
) -> None:
|
44
|
+
request_user = cls.getUserWithId(request_user)
|
45
|
+
errors = []
|
46
|
+
permission_data = PermissionDataManager(permission_data=data, manager=manager)
|
47
|
+
Permission = cls(permission_data, request_user)
|
48
|
+
for key in data.keys():
|
49
|
+
is_allowed = Permission.checkPermission("create", key)
|
50
|
+
if not is_allowed:
|
51
|
+
errors.append(
|
52
|
+
f"Permission denied for {key} with value {data[key]} for user {request_user}"
|
53
|
+
)
|
54
|
+
if errors:
|
55
|
+
raise PermissionError(
|
56
|
+
f"Permission denied for user {request_user} with errors: {errors}"
|
57
|
+
)
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def checkUpdatePermission(
|
61
|
+
cls,
|
62
|
+
data: dict[str, Any],
|
63
|
+
old_manager_instance: GeneralManager,
|
64
|
+
request_user: AbstractUser | AnonymousUser | Any,
|
65
|
+
) -> None:
|
66
|
+
request_user = cls.getUserWithId(request_user)
|
67
|
+
|
68
|
+
errors = []
|
69
|
+
permission_data = PermissionDataManager[GeneralManager].forUpdate(
|
70
|
+
base_data=old_manager_instance, update_data=data
|
71
|
+
)
|
72
|
+
Permission = cls(permission_data, request_user)
|
73
|
+
for key in data.keys():
|
74
|
+
is_allowed = Permission.checkPermission("update", key)
|
75
|
+
if not is_allowed:
|
76
|
+
errors.append(
|
77
|
+
f"Permission denied for {key} with value {data[key]} for user {request_user}"
|
78
|
+
)
|
79
|
+
if errors:
|
80
|
+
raise PermissionError(
|
81
|
+
f"Permission denied for user {request_user} with errors: {errors}"
|
82
|
+
)
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def checkDeletePermission(
|
86
|
+
cls,
|
87
|
+
manager_instance: GeneralManager,
|
88
|
+
request_user: AbstractUser | AnonymousUser | Any,
|
89
|
+
) -> None:
|
90
|
+
request_user = cls.getUserWithId(request_user)
|
91
|
+
|
92
|
+
errors = []
|
93
|
+
permission_data = PermissionDataManager[GeneralManager](manager_instance)
|
94
|
+
Permission = cls(permission_data, request_user)
|
95
|
+
for key in manager_instance.__dict__.keys():
|
96
|
+
is_allowed = Permission.checkPermission("delete", key)
|
97
|
+
if not is_allowed:
|
98
|
+
errors.append(
|
99
|
+
f"Permission denied for {key} with value {getattr(manager_instance, key)} for user {request_user}"
|
100
|
+
)
|
101
|
+
if errors:
|
102
|
+
raise PermissionError(
|
103
|
+
f"Permission denied for user {request_user} with errors: {errors}"
|
104
|
+
)
|
105
|
+
|
106
|
+
@staticmethod
|
107
|
+
def getUserWithId(
|
108
|
+
user: Any | AbstractUser | AnonymousUser,
|
109
|
+
) -> AbstractUser | AnonymousUser:
|
110
|
+
"""
|
111
|
+
Returns the user with the given id
|
112
|
+
"""
|
113
|
+
from django.contrib.auth.models import User
|
114
|
+
|
115
|
+
if isinstance(user, (AbstractUser, AnonymousUser)):
|
116
|
+
return user
|
117
|
+
try:
|
118
|
+
return User.objects.get(id=user)
|
119
|
+
except User.DoesNotExist:
|
120
|
+
return AnonymousUser()
|
121
|
+
|
122
|
+
@abstractmethod
|
123
|
+
def checkPermission(
|
124
|
+
self,
|
125
|
+
action: Literal["create", "read", "update", "delete"],
|
126
|
+
attriubte: str,
|
127
|
+
) -> bool:
|
128
|
+
raise NotImplementedError
|
129
|
+
|
130
|
+
def getPermissionFilter(
|
131
|
+
self,
|
132
|
+
) -> list[dict[Literal["filter", "exclude"], dict[str, str]]]:
|
133
|
+
"""
|
134
|
+
Returns the filter for the permission
|
135
|
+
"""
|
136
|
+
raise NotImplementedError
|
137
|
+
|
138
|
+
def _getPermissionFilter(
|
139
|
+
self, permission: str
|
140
|
+
) -> dict[Literal["filter", "exclude"], dict[str, str]]:
|
141
|
+
"""
|
142
|
+
Returns the filter for the permission
|
143
|
+
"""
|
144
|
+
permission_function, *config = permission.split(":")
|
145
|
+
if permission_function not in permission_functions:
|
146
|
+
raise ValueError(f"Permission {permission} not found")
|
147
|
+
permission_filter = permission_functions[permission_function][
|
148
|
+
"permission_filter"
|
149
|
+
](self.request_user, config)
|
150
|
+
if permission_filter is None:
|
151
|
+
return {"filter": {}, "exclude": {}}
|
152
|
+
return permission_filter
|
153
|
+
|
154
|
+
def validatePermissionString(
|
155
|
+
self,
|
156
|
+
permission: str,
|
157
|
+
) -> bool:
|
158
|
+
# permission can be a combination of multiple permissions
|
159
|
+
# separated by "&" (e.g. "isAuthenticated&isMatchingKeyAccount")
|
160
|
+
# this means that all sub_permissions must be true
|
161
|
+
return all(
|
162
|
+
[
|
163
|
+
self.__validateSinglePermission(sub_permission)
|
164
|
+
for sub_permission in permission.split("&")
|
165
|
+
]
|
166
|
+
)
|
167
|
+
|
168
|
+
def __validateSinglePermission(
|
169
|
+
self,
|
170
|
+
permission: str,
|
171
|
+
) -> bool:
|
172
|
+
permission_function, *config = permission.split(":")
|
173
|
+
if permission_function not in permission_functions:
|
174
|
+
raise ValueError(f"Permission {permission} not found")
|
175
|
+
|
176
|
+
return permission_functions[permission_function]["permission_method"](
|
177
|
+
self.instance, self.request_user, config
|
178
|
+
)
|
File without changes
|