GeneralManager 0.19.1__py3-none-any.whl → 0.19.2__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.
Potentially problematic release.
This version of GeneralManager might be problematic. Click here for more details.
- general_manager/_types/api.py +4 -4
- general_manager/_types/bucket.py +4 -4
- general_manager/_types/cache.py +6 -6
- general_manager/_types/factory.py +35 -35
- general_manager/_types/general_manager.py +9 -9
- general_manager/_types/interface.py +5 -5
- general_manager/_types/manager.py +4 -4
- general_manager/_types/measurement.py +1 -1
- general_manager/_types/permission.py +3 -3
- general_manager/_types/utils.py +12 -12
- general_manager/api/graphql.py +118 -95
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +31 -31
- general_manager/bucket/{baseBucket.py → base_bucket.py} +5 -5
- general_manager/bucket/{calculationBucket.py → calculation_bucket.py} +10 -10
- general_manager/bucket/{databaseBucket.py → database_bucket.py} +16 -19
- general_manager/bucket/{groupBucket.py → group_bucket.py} +8 -8
- general_manager/cache/{cacheDecorator.py → cache_decorator.py} +5 -5
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +1 -1
- general_manager/cache/{modelDependencyCollector.py → model_dependency_collector.py} +4 -4
- general_manager/cache/signals.py +1 -1
- general_manager/factory/{autoFactory.py → auto_factory.py} +24 -19
- general_manager/factory/factories.py +10 -13
- general_manager/factory/{factoryMethods.py → factory_methods.py} +19 -17
- general_manager/interface/{baseInterface.py → base_interface.py} +29 -21
- general_manager/interface/{calculationInterface.py → calculation_interface.py} +10 -10
- general_manager/interface/{databaseBasedInterface.py → database_based_interface.py} +42 -42
- general_manager/interface/{databaseInterface.py → database_interface.py} +15 -15
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +12 -12
- general_manager/manager/{generalManager.py → general_manager.py} +11 -11
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +15 -15
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +21 -21
- general_manager/permission/{managerBasedPermission.py → manager_based_permission.py} +21 -21
- general_manager/permission/{mutationPermission.py → mutation_permission.py} +12 -12
- general_manager/permission/{permissionChecks.py → permission_checks.py} +2 -2
- general_manager/permission/{permissionDataManager.py → permission_data_manager.py} +6 -6
- general_manager/permission/utils.py +6 -6
- general_manager/public_api_registry.py +75 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +17 -11
- general_manager/utils/{filterParser.py → filter_parser.py} +3 -3
- general_manager/utils/{jsonEncoder.py → json_encoder.py} +1 -1
- general_manager/utils/{makeCacheKey.py → make_cache_key.py} +1 -1
- general_manager/utils/{noneToZero.py → none_to_zero.py} +1 -1
- general_manager/utils/{pathMapping.py → path_mapping.py} +14 -14
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/METADATA +1 -1
- generalmanager-0.19.2.dist-info/RECORD +77 -0
- generalmanager-0.19.1.dist-info/RECORD +0 -77
- /general_manager/measurement/{measurementField.py → measurement_field.py} +0 -0
- /general_manager/permission/{fileBasedPermission.py → file_based_permission.py} +0 -0
- /general_manager/utils/{argsToKwargs.py → args_to_kwargs.py} +0 -0
- /general_manager/utils/{formatString.py → format_string.py} +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/top_level.txt +0 -0
general_manager/manager/meta.py
CHANGED
|
@@ -4,10 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
from typing import Any, Type, TYPE_CHECKING, ClassVar, TypeVar, Iterable, cast
|
|
7
|
-
from general_manager.interface.
|
|
7
|
+
from general_manager.interface.base_interface import InterfaceBase
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from general_manager.manager.
|
|
10
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
|
|
@@ -78,7 +78,7 @@ class GeneralManagerMeta(type):
|
|
|
78
78
|
"""
|
|
79
79
|
Create a GeneralManager subclass, integrate any declared Interface hooks, and register the class for pending initialization and GraphQL processing.
|
|
80
80
|
|
|
81
|
-
If the class body defines an `Interface`, validates it is a subclass of `InterfaceBase`, invokes the interface's `
|
|
81
|
+
If the class body defines an `Interface`, validates it is a subclass of `InterfaceBase`, invokes the interface's `handle_interface()` pre-creation hook to allow modification of the class namespace, creates the class, then invokes the post-creation hook and registers the class for attribute initialization and global tracking. If `Interface` is not defined, creates the class directly. If `settings.AUTOCREATE_GRAPHQL` is true, registers the created class for GraphQL interface processing.
|
|
82
82
|
|
|
83
83
|
Parameters:
|
|
84
84
|
mcs (type): The metaclass creating the class.
|
|
@@ -90,7 +90,7 @@ class GeneralManagerMeta(type):
|
|
|
90
90
|
type: The newly created subclass, possibly modified by Interface hooks.
|
|
91
91
|
"""
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def create_new_general_manager_class(
|
|
94
94
|
mcs: type["GeneralManagerMeta"],
|
|
95
95
|
name: str,
|
|
96
96
|
bases: tuple[type, ...],
|
|
@@ -103,15 +103,15 @@ class GeneralManagerMeta(type):
|
|
|
103
103
|
interface = attrs.pop("Interface")
|
|
104
104
|
if not issubclass(interface, InterfaceBase):
|
|
105
105
|
raise InvalidInterfaceTypeError(interface.__name__)
|
|
106
|
-
|
|
107
|
-
attrs, interface_cls, model =
|
|
108
|
-
new_class =
|
|
109
|
-
|
|
106
|
+
pre_creation, post_creation = interface.handle_interface()
|
|
107
|
+
attrs, interface_cls, model = pre_creation(name, attrs, interface)
|
|
108
|
+
new_class = create_new_general_manager_class(mcs, name, bases, attrs)
|
|
109
|
+
post_creation(new_class, interface_cls, model)
|
|
110
110
|
mcs.pending_attribute_initialization.append(new_class)
|
|
111
111
|
mcs.all_classes.append(new_class)
|
|
112
112
|
|
|
113
113
|
else:
|
|
114
|
-
new_class =
|
|
114
|
+
new_class = create_new_general_manager_class(mcs, name, bases, attrs)
|
|
115
115
|
|
|
116
116
|
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
|
117
117
|
mcs.pending_graphql_interfaces.append(new_class)
|
|
@@ -119,7 +119,7 @@ class GeneralManagerMeta(type):
|
|
|
119
119
|
return new_class
|
|
120
120
|
|
|
121
121
|
@staticmethod
|
|
122
|
-
def
|
|
122
|
+
def create_at_properties_for_attributes(
|
|
123
123
|
attributes: Iterable[str], new_class: Type[GeneralManager]
|
|
124
124
|
) -> None:
|
|
125
125
|
"""
|
|
@@ -132,14 +132,14 @@ class GeneralManagerMeta(type):
|
|
|
132
132
|
new_class (Type[GeneralManager]): Class that will receive the generated descriptor attributes.
|
|
133
133
|
"""
|
|
134
134
|
|
|
135
|
-
def
|
|
135
|
+
def descriptor_method(
|
|
136
136
|
attr_name: str,
|
|
137
137
|
new_class: type,
|
|
138
138
|
) -> object:
|
|
139
139
|
"""
|
|
140
140
|
Create a descriptor that provides attribute access backed by an instance's interface attributes.
|
|
141
141
|
|
|
142
|
-
When accessed on the class, the descriptor returns the field type by delegating to the class's `Interface.
|
|
142
|
+
When accessed on the class, the descriptor returns the field type by delegating to the class's `Interface.get_field_type` for the configured attribute name. When accessed on an instance, it returns the value stored in `instance._attributes[attr_name]`. If the stored value is callable, it is invoked with `instance._interface` and the resulting value is returned. If the attribute is not present on the instance, a `MissingAttributeError` is raised. If invoking a callable attribute raises an exception, that error is wrapped in `AttributeEvaluationError`.
|
|
143
143
|
|
|
144
144
|
Parameters:
|
|
145
145
|
attr_name (str): The name of the attribute the descriptor resolves.
|
|
@@ -164,7 +164,7 @@ class GeneralManagerMeta(type):
|
|
|
164
164
|
"""
|
|
165
165
|
Provide the class field type when accessed on the class, or resolve and return the stored attribute value for an instance.
|
|
166
166
|
|
|
167
|
-
When accessed on a class, returns the field type from the class's Interface via Interface.
|
|
167
|
+
When accessed on a class, returns the field type from the class's Interface via Interface.get_field_type.
|
|
168
168
|
When accessed on an instance, retrieves the value stored in instance._attributes for this descriptor's attribute name;
|
|
169
169
|
if the stored value is callable, it is invoked with instance._interface and the result is returned.
|
|
170
170
|
|
|
@@ -176,7 +176,7 @@ class GeneralManagerMeta(type):
|
|
|
176
176
|
AttributeEvaluationError: If calling a callable attribute raises an exception; the original exception is wrapped.
|
|
177
177
|
"""
|
|
178
178
|
if instance is None:
|
|
179
|
-
return self._class.Interface.
|
|
179
|
+
return self._class.Interface.get_field_type(self._attr_name)
|
|
180
180
|
attribute = instance._attributes.get(self._attr_name, _nonExistent)
|
|
181
181
|
if attribute is _nonExistent:
|
|
182
182
|
raise MissingAttributeError(
|
|
@@ -192,4 +192,4 @@ class GeneralManagerMeta(type):
|
|
|
192
192
|
return Descriptor(attr_name, cast(Type[Any], new_class))
|
|
193
193
|
|
|
194
194
|
for attr_name in attributes:
|
|
195
|
-
setattr(new_class, attr_name,
|
|
195
|
+
setattr(new_class, attr_name, descriptor_method(attr_name, new_class))
|
|
@@ -194,7 +194,7 @@ class Measurement:
|
|
|
194
194
|
raise InvalidMeasurementInitializationError() from error
|
|
195
195
|
if not isinstance(value, Decimal):
|
|
196
196
|
value = Decimal(str(value))
|
|
197
|
-
self.__quantity = ureg.Quantity(self.
|
|
197
|
+
self.__quantity = ureg.Quantity(self.format_decimal(value), unit)
|
|
198
198
|
|
|
199
199
|
def __getstate__(self) -> dict[str, str]:
|
|
200
200
|
"""
|
|
@@ -221,7 +221,7 @@ class Measurement:
|
|
|
221
221
|
"""
|
|
222
222
|
value = Decimal(state["magnitude"])
|
|
223
223
|
unit = state["unit"]
|
|
224
|
-
self.__quantity = ureg.Quantity(self.
|
|
224
|
+
self.__quantity = ureg.Quantity(self.format_decimal(value), unit)
|
|
225
225
|
|
|
226
226
|
@property
|
|
227
227
|
def quantity(self) -> PlainQuantity:
|
|
@@ -282,7 +282,7 @@ class Measurement:
|
|
|
282
282
|
return cls(value, unit)
|
|
283
283
|
|
|
284
284
|
@staticmethod
|
|
285
|
-
def
|
|
285
|
+
def format_decimal(value: Decimal) -> Decimal:
|
|
286
286
|
"""
|
|
287
287
|
Normalise decimals so integers have no fractional component.
|
|
288
288
|
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, cast
|
|
6
|
-
from general_manager.permission.
|
|
6
|
+
from general_manager.permission.permission_checks import permission_functions
|
|
7
7
|
|
|
8
8
|
from django.contrib.auth.models import AnonymousUser, AbstractBaseUser, AbstractUser
|
|
9
|
-
from general_manager.permission.
|
|
9
|
+
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
10
10
|
from general_manager.permission.utils import (
|
|
11
|
-
|
|
11
|
+
validate_permission_string,
|
|
12
12
|
PermissionNotFoundError,
|
|
13
13
|
)
|
|
14
14
|
import logging
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from general_manager.manager.
|
|
17
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
18
18
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
@@ -63,7 +63,7 @@ class BasePermission(ABC):
|
|
|
63
63
|
return self._request_user
|
|
64
64
|
|
|
65
65
|
@classmethod
|
|
66
|
-
def
|
|
66
|
+
def check_create_permission(
|
|
67
67
|
cls,
|
|
68
68
|
data: dict[str, Any],
|
|
69
69
|
manager: type[GeneralManager],
|
|
@@ -82,12 +82,12 @@ class BasePermission(ABC):
|
|
|
82
82
|
Raises:
|
|
83
83
|
PermissionCheckError: If one or more attributes in `data` are denied for the resolved `request_user`.
|
|
84
84
|
"""
|
|
85
|
-
request_user = cls.
|
|
85
|
+
request_user = cls.get_user_with_id(request_user)
|
|
86
86
|
errors = []
|
|
87
87
|
permission_data = PermissionDataManager(permission_data=data, manager=manager)
|
|
88
88
|
Permission = cls(permission_data, request_user)
|
|
89
89
|
for key in data.keys():
|
|
90
|
-
is_allowed = Permission.
|
|
90
|
+
is_allowed = Permission.check_permission("create", key)
|
|
91
91
|
if not is_allowed:
|
|
92
92
|
logger.debug(
|
|
93
93
|
f"Permission denied for {key} with value {data[key]} for user {request_user}"
|
|
@@ -97,7 +97,7 @@ class BasePermission(ABC):
|
|
|
97
97
|
raise PermissionCheckError(request_user, errors)
|
|
98
98
|
|
|
99
99
|
@classmethod
|
|
100
|
-
def
|
|
100
|
+
def check_update_permission(
|
|
101
101
|
cls,
|
|
102
102
|
data: dict[str, Any],
|
|
103
103
|
old_manager_instance: GeneralManager,
|
|
@@ -109,20 +109,20 @@ class BasePermission(ABC):
|
|
|
109
109
|
Parameters:
|
|
110
110
|
data (dict[str, Any]): Mapping of attribute names to new values to be applied.
|
|
111
111
|
old_manager_instance (GeneralManager): Existing manager instance whose current state is used to evaluate update permissions.
|
|
112
|
-
request_user (UserLike | Any): User instance or user id; non-user values will be resolved to a User or AnonymousUser via
|
|
112
|
+
request_user (UserLike | Any): User instance or user id; non-user values will be resolved to a User or AnonymousUser via get_user_with_id.
|
|
113
113
|
|
|
114
114
|
Raises:
|
|
115
115
|
PermissionCheckError: Raised with a list of error messages when one or more fields are not permitted to be updated.
|
|
116
116
|
"""
|
|
117
|
-
request_user = cls.
|
|
117
|
+
request_user = cls.get_user_with_id(request_user)
|
|
118
118
|
|
|
119
119
|
errors = []
|
|
120
|
-
permission_data = PermissionDataManager.
|
|
120
|
+
permission_data = PermissionDataManager.for_update(
|
|
121
121
|
base_data=old_manager_instance, update_data=data
|
|
122
122
|
)
|
|
123
123
|
Permission = cls(permission_data, request_user)
|
|
124
124
|
for key in data.keys():
|
|
125
|
-
is_allowed = Permission.
|
|
125
|
+
is_allowed = Permission.check_permission("update", key)
|
|
126
126
|
if not is_allowed:
|
|
127
127
|
logger.debug(
|
|
128
128
|
f"Permission denied for {key} with value {data[key]} for user {request_user}"
|
|
@@ -132,7 +132,7 @@ class BasePermission(ABC):
|
|
|
132
132
|
raise PermissionCheckError(request_user, errors)
|
|
133
133
|
|
|
134
134
|
@classmethod
|
|
135
|
-
def
|
|
135
|
+
def check_delete_permission(
|
|
136
136
|
cls,
|
|
137
137
|
manager_instance: GeneralManager,
|
|
138
138
|
request_user: UserLike | Any,
|
|
@@ -149,13 +149,13 @@ class BasePermission(ABC):
|
|
|
149
149
|
Raises:
|
|
150
150
|
PermissionCheckError: If one or more attributes are not permitted for deletion by request_user. The exception carries the user and the list of denial messages.
|
|
151
151
|
"""
|
|
152
|
-
request_user = cls.
|
|
152
|
+
request_user = cls.get_user_with_id(request_user)
|
|
153
153
|
|
|
154
154
|
errors = []
|
|
155
155
|
permission_data = PermissionDataManager(manager_instance)
|
|
156
156
|
Permission = cls(permission_data, request_user)
|
|
157
157
|
for key in manager_instance.__dict__.keys():
|
|
158
|
-
is_allowed = Permission.
|
|
158
|
+
is_allowed = Permission.check_permission("delete", key)
|
|
159
159
|
if not is_allowed:
|
|
160
160
|
logger.debug(
|
|
161
161
|
f"Permission denied for {key} with value {getattr(manager_instance, key)} for user {request_user}"
|
|
@@ -165,7 +165,7 @@ class BasePermission(ABC):
|
|
|
165
165
|
raise PermissionCheckError(request_user, errors)
|
|
166
166
|
|
|
167
167
|
@staticmethod
|
|
168
|
-
def
|
|
168
|
+
def get_user_with_id(
|
|
169
169
|
user: Any | UserLike,
|
|
170
170
|
) -> UserLike:
|
|
171
171
|
"""
|
|
@@ -190,7 +190,7 @@ class BasePermission(ABC):
|
|
|
190
190
|
return AnonymousUser()
|
|
191
191
|
|
|
192
192
|
@abstractmethod
|
|
193
|
-
def
|
|
193
|
+
def check_permission(
|
|
194
194
|
self,
|
|
195
195
|
action: Literal["create", "read", "update", "delete"],
|
|
196
196
|
attribute: str,
|
|
@@ -207,13 +207,13 @@ class BasePermission(ABC):
|
|
|
207
207
|
"""
|
|
208
208
|
raise NotImplementedError
|
|
209
209
|
|
|
210
|
-
def
|
|
210
|
+
def get_permission_filter(
|
|
211
211
|
self,
|
|
212
212
|
) -> list[dict[Literal["filter", "exclude"], dict[str, str]]]:
|
|
213
213
|
"""Return the filter/exclude constraints associated with this permission."""
|
|
214
214
|
raise NotImplementedError
|
|
215
215
|
|
|
216
|
-
def
|
|
216
|
+
def _get_permission_filter(
|
|
217
217
|
self, permission: str
|
|
218
218
|
) -> dict[Literal["filter", "exclude"], dict[str, str]]:
|
|
219
219
|
"""
|
|
@@ -241,7 +241,7 @@ class BasePermission(ABC):
|
|
|
241
241
|
return {"filter": {}, "exclude": {}}
|
|
242
242
|
return permission_filter
|
|
243
243
|
|
|
244
|
-
def
|
|
244
|
+
def validate_permission_string(
|
|
245
245
|
self,
|
|
246
246
|
permission: str,
|
|
247
247
|
) -> bool:
|
|
@@ -254,7 +254,7 @@ class BasePermission(ABC):
|
|
|
254
254
|
Returns:
|
|
255
255
|
bool: True when every sub-permission evaluates to True for the current user.
|
|
256
256
|
"""
|
|
257
|
-
return
|
|
257
|
+
return validate_permission_string(
|
|
258
258
|
permission,
|
|
259
259
|
self.instance,
|
|
260
260
|
cast(AbstractUser | AnonymousUser, self.request_user),
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from typing import TYPE_CHECKING, Literal, Optional, Dict
|
|
5
|
-
from general_manager.permission.
|
|
5
|
+
from general_manager.permission.base_permission import BasePermission
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from general_manager.permission.
|
|
8
|
+
from general_manager.permission.permission_data_manager import (
|
|
9
9
|
PermissionDataManager,
|
|
10
10
|
)
|
|
11
|
-
from general_manager.manager.
|
|
11
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
12
12
|
from django.contrib.auth.models import AbstractUser
|
|
13
13
|
|
|
14
14
|
type permission_type = Literal[
|
|
@@ -86,10 +86,10 @@ class ManagerBasedPermission(BasePermission):
|
|
|
86
86
|
request_user (AbstractUser): User whose permissions are being checked.
|
|
87
87
|
"""
|
|
88
88
|
super().__init__(instance, request_user)
|
|
89
|
-
self.
|
|
89
|
+
self.__set_permissions()
|
|
90
90
|
|
|
91
|
-
self.__attribute_permissions = self.
|
|
92
|
-
self.__based_on_permission = self.
|
|
91
|
+
self.__attribute_permissions = self.__get_attribute_permissions()
|
|
92
|
+
self.__based_on_permission = self.__get_based_on_permission()
|
|
93
93
|
self.__overall_results: Dict[permission_type, Optional[bool]] = {
|
|
94
94
|
"create": None,
|
|
95
95
|
"read": None,
|
|
@@ -97,7 +97,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
97
97
|
"delete": None,
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
def
|
|
100
|
+
def __set_permissions(self, skip_based_on: bool = False) -> None:
|
|
101
101
|
"""Populate CRUD permissions using class-level defaults and overrides."""
|
|
102
102
|
default_read = ["public"]
|
|
103
103
|
default_write = ["isAuthenticated"]
|
|
@@ -111,7 +111,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
111
111
|
self.__update__ = getattr(self.__class__, "__update__", default_write)
|
|
112
112
|
self.__delete__ = getattr(self.__class__, "__delete__", default_write)
|
|
113
113
|
|
|
114
|
-
def
|
|
114
|
+
def __get_based_on_permission(self) -> Optional[BasePermission]:
|
|
115
115
|
"""
|
|
116
116
|
Resolve and return a BasePermission instance from the manager attribute named by the class-level `__based_on__` configuration.
|
|
117
117
|
|
|
@@ -124,7 +124,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
124
124
|
InvalidBasedOnConfigurationError: If the configured `__based_on__` attribute does not exist on the target instance.
|
|
125
125
|
InvalidBasedOnTypeError: If the configured attribute exists but does not resolve to a `GeneralManager` or subclass.
|
|
126
126
|
"""
|
|
127
|
-
from general_manager.manager.
|
|
127
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
128
128
|
|
|
129
129
|
__based_on__ = self.__based_on__
|
|
130
130
|
if __based_on__ is None:
|
|
@@ -134,7 +134,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
134
134
|
if basis_object is notExistent:
|
|
135
135
|
raise InvalidBasedOnConfigurationError(__based_on__)
|
|
136
136
|
if basis_object is None:
|
|
137
|
-
self.
|
|
137
|
+
self.__set_permissions(skip_based_on=True)
|
|
138
138
|
return None
|
|
139
139
|
if not isinstance(basis_object, GeneralManager) and not (
|
|
140
140
|
isinstance(basis_object, type) and issubclass(basis_object, GeneralManager)
|
|
@@ -154,7 +154,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
154
154
|
request_user=self.request_user,
|
|
155
155
|
)
|
|
156
156
|
|
|
157
|
-
def
|
|
157
|
+
def __get_attribute_permissions(
|
|
158
158
|
self,
|
|
159
159
|
) -> dict[str, dict[permission_type, list[str]]]:
|
|
160
160
|
"""Collect attribute-level permission overrides defined on the class."""
|
|
@@ -164,7 +164,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
164
164
|
attribute_permissions[attribute] = getattr(self, attribute)
|
|
165
165
|
return attribute_permissions
|
|
166
166
|
|
|
167
|
-
def
|
|
167
|
+
def check_permission(
|
|
168
168
|
self,
|
|
169
169
|
action: permission_type,
|
|
170
170
|
attribute: str,
|
|
@@ -184,7 +184,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
184
184
|
"""
|
|
185
185
|
if (
|
|
186
186
|
self.__based_on_permission
|
|
187
|
-
and not self.__based_on_permission.
|
|
187
|
+
and not self.__based_on_permission.check_permission(action, attribute)
|
|
188
188
|
):
|
|
189
189
|
return False
|
|
190
190
|
|
|
@@ -210,15 +210,15 @@ class ManagerBasedPermission(BasePermission):
|
|
|
210
210
|
return last_result
|
|
211
211
|
attribute_permission = True
|
|
212
212
|
else:
|
|
213
|
-
attribute_permission = self.
|
|
213
|
+
attribute_permission = self.__check_specific_permission(
|
|
214
214
|
self.__attribute_permissions[attribute][action]
|
|
215
215
|
)
|
|
216
216
|
|
|
217
|
-
permission = self.
|
|
217
|
+
permission = self.__check_specific_permission(permissions)
|
|
218
218
|
self.__overall_results[action] = permission
|
|
219
219
|
return permission and attribute_permission
|
|
220
220
|
|
|
221
|
-
def
|
|
221
|
+
def __check_specific_permission(
|
|
222
222
|
self,
|
|
223
223
|
permissions: list[str],
|
|
224
224
|
) -> bool:
|
|
@@ -226,17 +226,17 @@ class ManagerBasedPermission(BasePermission):
|
|
|
226
226
|
if not permissions:
|
|
227
227
|
return True
|
|
228
228
|
for permission in permissions:
|
|
229
|
-
if self.
|
|
229
|
+
if self.validate_permission_string(permission):
|
|
230
230
|
return True
|
|
231
231
|
return False
|
|
232
232
|
|
|
233
|
-
def
|
|
233
|
+
def get_permission_filter(
|
|
234
234
|
self,
|
|
235
235
|
) -> list[dict[Literal["filter", "exclude"], dict[str, str]]]:
|
|
236
236
|
"""
|
|
237
237
|
Builds queryset filter and exclude mappings derived from this permission configuration.
|
|
238
238
|
|
|
239
|
-
If a based-on permission exists, its filters and excludes are included with each key prefixed by the name in __based_on__. Then appends filters produced from this class's read permissions via
|
|
239
|
+
If a based-on permission exists, its filters and excludes are included with each key prefixed by the name in __based_on__. Then appends filters produced from this class's read permissions via _get_permission_filter.
|
|
240
240
|
|
|
241
241
|
Returns:
|
|
242
242
|
list[dict[Literal["filter", "exclude"], dict[str, str]]]: A list of dictionaries each containing "filter" and "exclude" mappings where keys are queryset lookups and values are lookup values.
|
|
@@ -245,7 +245,7 @@ class ManagerBasedPermission(BasePermission):
|
|
|
245
245
|
filters: list[dict[Literal["filter", "exclude"], dict[str, str]]] = []
|
|
246
246
|
|
|
247
247
|
if self.__based_on_permission is not None:
|
|
248
|
-
base_permissions = self.__based_on_permission.
|
|
248
|
+
base_permissions = self.__based_on_permission.get_permission_filter()
|
|
249
249
|
for base_permission in base_permissions:
|
|
250
250
|
filter = base_permission.get("filter", {})
|
|
251
251
|
exclude = base_permission.get("exclude", {})
|
|
@@ -263,6 +263,6 @@ class ManagerBasedPermission(BasePermission):
|
|
|
263
263
|
)
|
|
264
264
|
|
|
265
265
|
for permission in self.__read__:
|
|
266
|
-
filters.append(self.
|
|
266
|
+
filters.append(self._get_permission_filter(permission))
|
|
267
267
|
|
|
268
268
|
return filters
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from django.contrib.auth.models import AbstractUser, AnonymousUser
|
|
5
5
|
from typing import Any
|
|
6
|
-
from general_manager.permission.
|
|
6
|
+
from general_manager.permission.base_permission import (
|
|
7
7
|
BasePermission,
|
|
8
8
|
PermissionCheckError,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
from general_manager.permission.
|
|
12
|
-
from general_manager.permission.utils import
|
|
11
|
+
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
12
|
+
from general_manager.permission.utils import validate_permission_string
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MutationPermission:
|
|
@@ -29,7 +29,7 @@ class MutationPermission:
|
|
|
29
29
|
"""
|
|
30
30
|
self._data: PermissionDataManager = PermissionDataManager(data)
|
|
31
31
|
self._request_user = request_user
|
|
32
|
-
self.__attribute_permissions = self.
|
|
32
|
+
self.__attribute_permissions = self.__get_attribute_permissions()
|
|
33
33
|
|
|
34
34
|
self.__overall_result: bool | None = None
|
|
35
35
|
|
|
@@ -43,7 +43,7 @@ class MutationPermission:
|
|
|
43
43
|
"""Return the user whose permissions are being evaluated."""
|
|
44
44
|
return self._request_user
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def __get_attribute_permissions(
|
|
47
47
|
self,
|
|
48
48
|
) -> dict[str, list[str]]:
|
|
49
49
|
"""Collect attribute-specific permission expressions declared on the class."""
|
|
@@ -71,17 +71,17 @@ class MutationPermission:
|
|
|
71
71
|
"""
|
|
72
72
|
errors = []
|
|
73
73
|
if not isinstance(request_user, (AbstractUser, AnonymousUser)):
|
|
74
|
-
request_user = BasePermission.
|
|
74
|
+
request_user = BasePermission.get_user_with_id(request_user)
|
|
75
75
|
Permission = cls(data, request_user)
|
|
76
76
|
for key in data:
|
|
77
|
-
if not Permission.
|
|
77
|
+
if not Permission.check_permission(key):
|
|
78
78
|
errors.append(
|
|
79
79
|
f"Permission denied for {key} with value {data[key]} for user {request_user}"
|
|
80
80
|
)
|
|
81
81
|
if errors:
|
|
82
82
|
raise PermissionCheckError(request_user, errors)
|
|
83
83
|
|
|
84
|
-
def
|
|
84
|
+
def check_permission(
|
|
85
85
|
self,
|
|
86
86
|
attribute: str,
|
|
87
87
|
) -> bool:
|
|
@@ -105,20 +105,20 @@ class MutationPermission:
|
|
|
105
105
|
return last_result
|
|
106
106
|
attribute_permission = True
|
|
107
107
|
else:
|
|
108
|
-
attribute_permission = self.
|
|
108
|
+
attribute_permission = self.__check_specific_permission(
|
|
109
109
|
self.__attribute_permissions[attribute]
|
|
110
110
|
)
|
|
111
111
|
|
|
112
|
-
permission = self.
|
|
112
|
+
permission = self.__check_specific_permission(self.__mutate__)
|
|
113
113
|
self.__overall_result = permission
|
|
114
114
|
return permission and attribute_permission
|
|
115
115
|
|
|
116
|
-
def
|
|
116
|
+
def __check_specific_permission(
|
|
117
117
|
self,
|
|
118
118
|
permissions: list[str],
|
|
119
119
|
) -> bool:
|
|
120
120
|
"""Return True when any permission expression evaluates to True."""
|
|
121
121
|
for permission in permissions:
|
|
122
|
-
if
|
|
122
|
+
if validate_permission_string(permission, self.data, self.request_user):
|
|
123
123
|
return True
|
|
124
124
|
return False
|
|
@@ -4,10 +4,10 @@ from typing import Any, Callable, TYPE_CHECKING, TypedDict, Literal
|
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from django.contrib.auth.models import AbstractUser, AnonymousUser
|
|
7
|
-
from general_manager.permission.
|
|
7
|
+
from general_manager.permission.permission_data_manager import (
|
|
8
8
|
PermissionDataManager,
|
|
9
9
|
)
|
|
10
|
-
from general_manager.manager.
|
|
10
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
11
11
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
12
12
|
|
|
13
13
|
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
from typing import Callable, Optional, TypeVar, Generic, cast
|
|
5
5
|
from django.contrib.auth.models import AbstractUser
|
|
6
6
|
|
|
7
|
-
from general_manager.manager.
|
|
7
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class InvalidPermissionDataError(TypeError):
|
|
@@ -42,7 +42,7 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
42
42
|
Raises:
|
|
43
43
|
InvalidPermissionDataError: If `permission_data` is neither a dict nor an instance of GeneralManager.
|
|
44
44
|
"""
|
|
45
|
-
self.
|
|
45
|
+
self.get_data: Callable[[str], object]
|
|
46
46
|
self._permission_data = permission_data
|
|
47
47
|
self._manager: type[GeneralManagerData] | None
|
|
48
48
|
if isinstance(permission_data, GeneralManager):
|
|
@@ -51,7 +51,7 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
51
51
|
def manager_getter(name: str) -> object:
|
|
52
52
|
return getattr(gm_instance, name)
|
|
53
53
|
|
|
54
|
-
self.
|
|
54
|
+
self.get_data = manager_getter
|
|
55
55
|
self._manager = cast(type[GeneralManagerData], permission_data.__class__)
|
|
56
56
|
elif isinstance(permission_data, dict):
|
|
57
57
|
data_mapping = permission_data
|
|
@@ -59,13 +59,13 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
59
59
|
def dict_getter(name: str) -> object:
|
|
60
60
|
return data_mapping.get(name)
|
|
61
61
|
|
|
62
|
-
self.
|
|
62
|
+
self.get_data = dict_getter
|
|
63
63
|
self._manager = manager
|
|
64
64
|
else:
|
|
65
65
|
raise InvalidPermissionDataError()
|
|
66
66
|
|
|
67
67
|
@classmethod
|
|
68
|
-
def
|
|
68
|
+
def for_update(
|
|
69
69
|
cls,
|
|
70
70
|
base_data: GeneralManagerData,
|
|
71
71
|
update_data: dict[str, object],
|
|
@@ -95,4 +95,4 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
95
95
|
|
|
96
96
|
def __getattr__(self, name: str) -> object:
|
|
97
97
|
"""Proxy attribute access to the wrapped permission data."""
|
|
98
|
-
return self.
|
|
98
|
+
return self.get_data(name)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Utility helpers for evaluating permission expressions."""
|
|
2
2
|
|
|
3
|
-
from general_manager.permission.
|
|
3
|
+
from general_manager.permission.permission_checks import (
|
|
4
4
|
permission_functions,
|
|
5
5
|
)
|
|
6
|
-
from general_manager.permission.
|
|
6
|
+
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
7
7
|
from django.contrib.auth.models import AbstractUser, AnonymousUser
|
|
8
8
|
|
|
9
|
-
from general_manager.manager.
|
|
9
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
10
10
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
11
11
|
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ class PermissionNotFoundError(ValueError):
|
|
|
23
23
|
super().__init__(f"Permission {permission} not found.")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def validate_permission_string(
|
|
27
27
|
permission: str,
|
|
28
28
|
data: PermissionDataManager | GeneralManager | GeneralManagerMeta,
|
|
29
29
|
request_user: AbstractUser | AnonymousUser,
|
|
@@ -43,7 +43,7 @@ def validatePermissionString(
|
|
|
43
43
|
PermissionNotFoundError: If a referenced permission function is not registered.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def _validate_single_permission(
|
|
47
47
|
permission: str,
|
|
48
48
|
) -> bool:
|
|
49
49
|
"""
|
|
@@ -68,7 +68,7 @@ def validatePermissionString(
|
|
|
68
68
|
|
|
69
69
|
return all(
|
|
70
70
|
[
|
|
71
|
-
|
|
71
|
+
_validate_single_permission(sub_permission)
|
|
72
72
|
for sub_permission in permission.split("&")
|
|
73
73
|
]
|
|
74
74
|
)
|