django-abstract 0.1.0__tar.gz
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.
- django_abstract-0.1.0/.github/FUNDING.yml +15 -0
- django_abstract-0.1.0/LICENSE +21 -0
- django_abstract-0.1.0/MANIFEST.in +5 -0
- django_abstract-0.1.0/PKG-INFO +101 -0
- django_abstract-0.1.0/README.md +61 -0
- django_abstract-0.1.0/docs/base.md +67 -0
- django_abstract-0.1.0/docs/django_abstract/admin.md +13 -0
- django_abstract-0.1.0/docs/django_abstract/apps.md +13 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_abstract_view.md +12 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_creator.md +25 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_dependency.md +33 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_exception.md +34 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_form.md +27 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_model.md +31 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_model_service.md +24 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_model_system.md +20 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_operator.md +27 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_operator_service.md +19 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_selector.md +28 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_service.md +33 -0
- django_abstract-0.1.0/docs/django_abstract/base/base_system.md +26 -0
- django_abstract-0.1.0/docs/django_abstract/exceptions.md +15 -0
- django_abstract-0.1.0/docs/django_abstract/generic/generic_creators.md +22 -0
- django_abstract-0.1.0/docs/django_abstract/generic/generic_selectors.md +28 -0
- django_abstract-0.1.0/docs/django_abstract/log/dependencies.md +12 -0
- django_abstract-0.1.0/docs/django_abstract/log/models.md +22 -0
- django_abstract-0.1.0/docs/django_abstract/log/selectors/selectors.md +10 -0
- django_abstract-0.1.0/docs/django_abstract/log/selectors/selectors_dependency.md +7 -0
- django_abstract-0.1.0/docs/django_abstract/log/services/creators.md +10 -0
- django_abstract-0.1.0/docs/django_abstract/log/services/creators_dependency.md +7 -0
- django_abstract-0.1.0/docs/django_abstract/log/services/model_services.md +16 -0
- django_abstract-0.1.0/docs/django_abstract/log/utilities.md +14 -0
- django_abstract-0.1.0/docs/django_abstract/models.md +13 -0
- django_abstract-0.1.0/docs/django_abstract/registry.md +21 -0
- django_abstract-0.1.0/docs/django_abstract/signals.md +13 -0
- django_abstract-0.1.0/docs/django_abstract/tests.md +13 -0
- django_abstract-0.1.0/docs/django_abstract/utilities.md +18 -0
- django_abstract-0.1.0/pyproject.toml +32 -0
- django_abstract-0.1.0/pytest.ini +10 -0
- django_abstract-0.1.0/requirements.txt +0 -0
- django_abstract-0.1.0/src/django_abstract/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/admin.py +3 -0
- django_abstract-0.1.0/src/django_abstract/apps.py +20 -0
- django_abstract-0.1.0/src/django_abstract/base/__init__.py +37 -0
- django_abstract-0.1.0/src/django_abstract/base/base_abstract_view.py +13 -0
- django_abstract-0.1.0/src/django_abstract/base/base_creator.py +34 -0
- django_abstract-0.1.0/src/django_abstract/base/base_dependency.py +83 -0
- django_abstract-0.1.0/src/django_abstract/base/base_exception.py +37 -0
- django_abstract-0.1.0/src/django_abstract/base/base_form.py +58 -0
- django_abstract-0.1.0/src/django_abstract/base/base_model.py +89 -0
- django_abstract-0.1.0/src/django_abstract/base/base_model_service.py +34 -0
- django_abstract-0.1.0/src/django_abstract/base/base_model_system.py +111 -0
- django_abstract-0.1.0/src/django_abstract/base/base_operator.py +149 -0
- django_abstract-0.1.0/src/django_abstract/base/base_operator_service.py +370 -0
- django_abstract-0.1.0/src/django_abstract/base/base_selector.py +63 -0
- django_abstract-0.1.0/src/django_abstract/base/base_service.py +220 -0
- django_abstract-0.1.0/src/django_abstract/base/base_system.py +99 -0
- django_abstract-0.1.0/src/django_abstract/exceptions.py +170 -0
- django_abstract-0.1.0/src/django_abstract/generic/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/generic/generic_creators.py +79 -0
- django_abstract-0.1.0/src/django_abstract/generic/generic_selectors.py +168 -0
- django_abstract-0.1.0/src/django_abstract/log/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/log/dependencies.py +26 -0
- django_abstract-0.1.0/src/django_abstract/log/models.py +151 -0
- django_abstract-0.1.0/src/django_abstract/log/selectors/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/log/selectors/selectors.py +37 -0
- django_abstract-0.1.0/src/django_abstract/log/selectors/selectors_dependency.py +14 -0
- django_abstract-0.1.0/src/django_abstract/log/services/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/log/services/creators.py +38 -0
- django_abstract-0.1.0/src/django_abstract/log/services/creators_dependency.py +9 -0
- django_abstract-0.1.0/src/django_abstract/log/services/model_services.py +290 -0
- django_abstract-0.1.0/src/django_abstract/log/utilities.py +182 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0001_initial.py +186 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0002_alter_systemerrorlog_reported_by.py +18 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0003_systemerrorlog_method_name.py +18 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0004_abstractbanneduser_is_deactivated_and_more.py +126 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0005_abstractguestidentity_abstractsessionlink_and_more.py +68 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0006_alter_abstractsessionmetrics_start_time.py +18 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0007_remove_abstractauthenticatedmoderegestry_created_by_and_more.py +111 -0
- django_abstract-0.1.0/src/django_abstract/migrations/0008_alter_adminactionlog_deactivated_by_and_more.py +36 -0
- django_abstract-0.1.0/src/django_abstract/migrations/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/models.py +11 -0
- django_abstract-0.1.0/src/django_abstract/registry.py +318 -0
- django_abstract-0.1.0/src/django_abstract/services/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/signals.py +1 -0
- django_abstract-0.1.0/src/django_abstract/systems/__init__.py +0 -0
- django_abstract-0.1.0/src/django_abstract/tests.py +3 -0
- django_abstract-0.1.0/src/django_abstract/utilities.py +735 -0
- django_abstract-0.1.0/tests/__init__.py +0 -0
- django_abstract-0.1.0/tests/django_abstract/__init__.py +0 -0
- django_abstract-0.1.0/tests/django_abstract/base/__init__.py +0 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base.py +129 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_abstract_view.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_creator.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_dependency.py +58 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_exception.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_form.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_model.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_model_service.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_model_system.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_operator.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_operator_service.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_selector.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_service.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/base/test_base_system.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/generic/test_generic_creators.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/generic/test_generic_selectors.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/selectors/test_selectors.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/selectors/test_selectors_dependency.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/services/test_creators.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/services/test_creators_dependency.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/services/test_model_services.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/test_dependencies.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/test_models.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/log/test_utilities.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_admin.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_apps.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_exceptions.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_models.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_registry.py +98 -0
- django_abstract-0.1.0/tests/django_abstract/test_signals.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_tests.py +12 -0
- django_abstract-0.1.0/tests/django_abstract/test_utilities.py +48 -0
- django_abstract-0.1.0/tests/models.py +8 -0
- django_abstract-0.1.0/tests/settings.py +19 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: 'MojahiD-0-YouneSS' # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
12
|
+
polar: # Replace with a single Polar username
|
|
13
|
+
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
|
14
|
+
thanks_dev: # Replace with a single thanks.dev username
|
|
15
|
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 YOUNESS MOJAHID
|
|
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 without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-abstract
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A django app, that ofers abstracted logic for Django.
|
|
5
|
+
Author-email: Youness Mojahid <mojahidyouness0@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 YOUNESS MOJAHID
|
|
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 without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Classifier: Framework :: Django
|
|
29
|
+
Classifier: Framework :: Django :: 4.2
|
|
30
|
+
Classifier: Framework :: Django :: 5.0
|
|
31
|
+
Classifier: Framework :: Django :: 6.0
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
36
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
|
+
Requires-Python: >=3.9
|
|
38
|
+
Requires-Dist: django>=5.2
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<h1>🛡️ Django Abstract</h1>
|
|
43
|
+
<p><strong>An Enterprise-Grade Architectural Layer for Django</strong></p>
|
|
44
|
+
<p>Clean Architecture | Dependency Injection | CQRS Principles | Domain-Driven Design</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📖 Overview
|
|
50
|
+
|
|
51
|
+
**Django Abstract** is a highly advanced framework built on top of Django that strictly enforces **Clean Architecture** and **Domain-Driven Design (DDD)**. It solves the infamous "Fat Model / Fat View" anti-pattern by completely decoupling:
|
|
52
|
+
|
|
53
|
+
1. **Data Access** (`Selectors` / `Creators`)
|
|
54
|
+
2. **Business Logic** (`Services`)
|
|
55
|
+
3. **Flow Control & Permissions** (`Operators` / `Systems`)
|
|
56
|
+
|
|
57
|
+
This framework provides a unified, predictable way to scale complex Django applications, manage high-throughput operations via Redis queues, and dynamically inject dependencies.
|
|
58
|
+
|
|
59
|
+
## 🚀 Key Features
|
|
60
|
+
|
|
61
|
+
### 🧩 Global Registry & Dependency Injection
|
|
62
|
+
- Dynamically registers all architectural components using decorators (`@creator_selector`, `@register_service`, `@register_operator`).
|
|
63
|
+
- Injects `BaseDependency` instances at runtime to prevent circular imports and allow test mocking.
|
|
64
|
+
- Magic attribute resolution (`__getattr__`) allows developers to resolve dependencies seamlessly (`dependency.select_user`).
|
|
65
|
+
|
|
66
|
+
### 🏛️ Strict Architectural Separation
|
|
67
|
+
- **`BaseModel`**: Adds soft-delete, active toggles, and rich audit trails natively.
|
|
68
|
+
- **`BaseSelector` / `BaseCreator`**: Isolates raw Django ORM calls from business logic.
|
|
69
|
+
- **`BaseService`**: Pure business logic that operates exclusively on an abstract `ServiceEntryData` state.
|
|
70
|
+
- **`BaseSystem`**: Orchestrates workflows and manages global database transactions (`transaction.atomic()`).
|
|
71
|
+
|
|
72
|
+
### ⚡ High-Throughput Background Logging
|
|
73
|
+
- Includes a robust `log/` app that tracks `SystemErrorLog`, `SessionMetrics`, and `AdminActionLog`.
|
|
74
|
+
- Uses a **Redis-backed queueing system** within `ModelServices` to buffer high-frequency inserts and bulk-flush (`bulk_create`) them to PostgreSQL, completely preventing database locking during traffic spikes.
|
|
75
|
+
|
|
76
|
+
### 🔌 Seamless View Binding
|
|
77
|
+
- Replaces traditional Django Views with `EntryBindingMixin`.
|
|
78
|
+
- Automatically extracts `session_key`, `ip_address`, and POST/GET payloads, hydrates a master `Entry` object, and passes it directly to the orchestration `Systems`.
|
|
79
|
+
|
|
80
|
+
## 📁 Repository Structure
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
src/django_abstract/
|
|
84
|
+
├── base/ # Core architectural base classes
|
|
85
|
+
├── generic/ # Generic, reusable creation/selection patterns
|
|
86
|
+
├── log/ # High-performance asynchronous auditing system
|
|
87
|
+
├── registry.py # Global Dependency Injection container
|
|
88
|
+
└── utilities.py # Context extraction, View Mixins, State Objects
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 📚 Documentation
|
|
92
|
+
Comprehensive documentation for every module can be found in the `docs/` folder:
|
|
93
|
+
- [Base Architecture Overview](docs/django_abstract/base)
|
|
94
|
+
- [Generic Utilities](docs/django_abstract/generic)
|
|
95
|
+
- [High-Performance Logging](docs/django_abstract/log)
|
|
96
|
+
|
|
97
|
+
## 🧪 Testing
|
|
98
|
+
The repository is fully testable. Due to strict dependency injection, any `Selector` or `Creator` can be swapped out with a mock class during tests. Test stubs for all modules are located in the `tests/` directory.
|
|
99
|
+
|
|
100
|
+
## 👨💻 Author
|
|
101
|
+
Designed and implemented as an enterprise architectural study to showcase advanced systems design, Python metaprogramming, and scalable Django architecture.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>🛡️ Django Abstract</h1>
|
|
3
|
+
<p><strong>An Enterprise-Grade Architectural Layer for Django</strong></p>
|
|
4
|
+
<p>Clean Architecture | Dependency Injection | CQRS Principles | Domain-Driven Design</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📖 Overview
|
|
10
|
+
|
|
11
|
+
**Django Abstract** is a highly advanced framework built on top of Django that strictly enforces **Clean Architecture** and **Domain-Driven Design (DDD)**. It solves the infamous "Fat Model / Fat View" anti-pattern by completely decoupling:
|
|
12
|
+
|
|
13
|
+
1. **Data Access** (`Selectors` / `Creators`)
|
|
14
|
+
2. **Business Logic** (`Services`)
|
|
15
|
+
3. **Flow Control & Permissions** (`Operators` / `Systems`)
|
|
16
|
+
|
|
17
|
+
This framework provides a unified, predictable way to scale complex Django applications, manage high-throughput operations via Redis queues, and dynamically inject dependencies.
|
|
18
|
+
|
|
19
|
+
## 🚀 Key Features
|
|
20
|
+
|
|
21
|
+
### 🧩 Global Registry & Dependency Injection
|
|
22
|
+
- Dynamically registers all architectural components using decorators (`@creator_selector`, `@register_service`, `@register_operator`).
|
|
23
|
+
- Injects `BaseDependency` instances at runtime to prevent circular imports and allow test mocking.
|
|
24
|
+
- Magic attribute resolution (`__getattr__`) allows developers to resolve dependencies seamlessly (`dependency.select_user`).
|
|
25
|
+
|
|
26
|
+
### 🏛️ Strict Architectural Separation
|
|
27
|
+
- **`BaseModel`**: Adds soft-delete, active toggles, and rich audit trails natively.
|
|
28
|
+
- **`BaseSelector` / `BaseCreator`**: Isolates raw Django ORM calls from business logic.
|
|
29
|
+
- **`BaseService`**: Pure business logic that operates exclusively on an abstract `ServiceEntryData` state.
|
|
30
|
+
- **`BaseSystem`**: Orchestrates workflows and manages global database transactions (`transaction.atomic()`).
|
|
31
|
+
|
|
32
|
+
### ⚡ High-Throughput Background Logging
|
|
33
|
+
- Includes a robust `log/` app that tracks `SystemErrorLog`, `SessionMetrics`, and `AdminActionLog`.
|
|
34
|
+
- Uses a **Redis-backed queueing system** within `ModelServices` to buffer high-frequency inserts and bulk-flush (`bulk_create`) them to PostgreSQL, completely preventing database locking during traffic spikes.
|
|
35
|
+
|
|
36
|
+
### 🔌 Seamless View Binding
|
|
37
|
+
- Replaces traditional Django Views with `EntryBindingMixin`.
|
|
38
|
+
- Automatically extracts `session_key`, `ip_address`, and POST/GET payloads, hydrates a master `Entry` object, and passes it directly to the orchestration `Systems`.
|
|
39
|
+
|
|
40
|
+
## 📁 Repository Structure
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
src/django_abstract/
|
|
44
|
+
├── base/ # Core architectural base classes
|
|
45
|
+
├── generic/ # Generic, reusable creation/selection patterns
|
|
46
|
+
├── log/ # High-performance asynchronous auditing system
|
|
47
|
+
├── registry.py # Global Dependency Injection container
|
|
48
|
+
└── utilities.py # Context extraction, View Mixins, State Objects
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 📚 Documentation
|
|
52
|
+
Comprehensive documentation for every module can be found in the `docs/` folder:
|
|
53
|
+
- [Base Architecture Overview](docs/django_abstract/base)
|
|
54
|
+
- [Generic Utilities](docs/django_abstract/generic)
|
|
55
|
+
- [High-Performance Logging](docs/django_abstract/log)
|
|
56
|
+
|
|
57
|
+
## 🧪 Testing
|
|
58
|
+
The repository is fully testable. Due to strict dependency injection, any `Selector` or `Creator` can be swapped out with a mock class during tests. Test stubs for all modules are located in the `tests/` directory.
|
|
59
|
+
|
|
60
|
+
## 👨💻 Author
|
|
61
|
+
Designed and implemented as an enterprise architectural study to showcase advanced systems design, Python metaprogramming, and scalable Django architecture.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Django-Abstract Core Architecture
|
|
2
|
+
|
|
3
|
+
django-abstract enforces Domain-Driven Design (DDD) by strictly separating concerns. Instead of writing monolithic Django views, you compose your application using our specialized base classes.
|
|
4
|
+
|
|
5
|
+
1. Data Layer (BaseModel & BaseForm)
|
|
6
|
+
|
|
7
|
+
BaseModel
|
|
8
|
+
|
|
9
|
+
An abstract Django model that provides enterprise-grade defaults to every table in your database.
|
|
10
|
+
|
|
11
|
+
UUID Primary Keys: Uses secure uuid4 instead of predictable auto-incrementing integers.
|
|
12
|
+
|
|
13
|
+
Audit Trails: Automatically tracks created_at, updated_at, created_by, and updated_by.
|
|
14
|
+
|
|
15
|
+
Soft Deletes: Built-in soft_delete() and reactivate() methods. It toggles is_active and logs deactivated_at instead of destroying data.
|
|
16
|
+
|
|
17
|
+
BaseForm
|
|
18
|
+
|
|
19
|
+
A smart ModelForm that automatically formats itself for modern frontend frameworks (like Bootstrap 5).
|
|
20
|
+
|
|
21
|
+
Auto-Styling: Injects form-control into standard inputs and form-check-input into checkboxes/radios automatically.
|
|
22
|
+
|
|
23
|
+
Audit Protection: Automatically strips out sensitive audit fields (created_by, deactivated_at) so users can't overwrite them via POST requests.
|
|
24
|
+
|
|
25
|
+
2. Dependency Injection (BaseDependency)
|
|
26
|
+
|
|
27
|
+
Instead of tightly coupling models to services, we use Dependencies.
|
|
28
|
+
|
|
29
|
+
A BaseDependency acts as a bucket for Selectors (read queries) and Creators (write queries).
|
|
30
|
+
|
|
31
|
+
Dynamic Resolution: Thanks to a custom __getattr__ implementation, accessing dependency.select_user() automatically routes to the correct localized or global registry.
|
|
32
|
+
|
|
33
|
+
3. Business Logic (BaseOperatorService & BaseModelService)
|
|
34
|
+
|
|
35
|
+
This is the brain of your application. Views do not contain logic; Services do.
|
|
36
|
+
|
|
37
|
+
BaseOperatorService: The heavyweight base class. It handles data validation via the nested BaseServiceValidator, tracks db_record states, and safely routes read_entry, create_entry, and delete_entry operations.
|
|
38
|
+
|
|
39
|
+
BaseModelService: Inherits from BaseOperatorService but adds model-specific utilities, like get_or_raise(), which standardizes 404/Missing error handling.
|
|
40
|
+
|
|
41
|
+
4. Security & Routing (BaseOperator)
|
|
42
|
+
|
|
43
|
+
Operators act as the "Bouncer" and "Router" before a Service is executed.
|
|
44
|
+
|
|
45
|
+
Uses a ControlEntryData envelope to track state.
|
|
46
|
+
|
|
47
|
+
The Bouncer (can_run): Validates if the current session or user is allowed to execute the requested target service.
|
|
48
|
+
|
|
49
|
+
The Router (run): Finds the target service in the SERVICE_REGISTRY, packs the payload, and fires the service hook safely.
|
|
50
|
+
|
|
51
|
+
5. Orchestration (BaseModelSystem)
|
|
52
|
+
|
|
53
|
+
When an action requires multiple services to run (e.g., "Checkout" requires the Order Service, Inventory Service, and Email Service), you use a System.
|
|
54
|
+
|
|
55
|
+
Transactional Safety: The run() method is permanently wrapped in transaction.atomic(). If any service fails, the entire database state rolls back safely.
|
|
56
|
+
|
|
57
|
+
Operator Invocation: Dynamically loads allowed Operators to handle the complex multi-step flows.
|
|
58
|
+
|
|
59
|
+
6. Error Handling (CoreException)
|
|
60
|
+
|
|
61
|
+
Generic 500 errors are a nightmare to debug. CoreException provides:
|
|
62
|
+
|
|
63
|
+
Standardized formatting with UTC timestamps.
|
|
64
|
+
|
|
65
|
+
Explicit error_code injection.
|
|
66
|
+
|
|
67
|
+
A context dictionary to log exactly what payload caused the failure.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# GMES View Bindings
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
While the framework operates abstractly, `EntryBindingMixin` (located in `utilities.py` but representing the View layer) automatically binds an incoming Django HTTP Request to a framework `Entry`.
|
|
5
|
+
|
|
6
|
+
## Workflow
|
|
7
|
+
1. The View Mixin intercepts the request.
|
|
8
|
+
2. It extracts the `session_key`, `ip_address`, `user_agent`, and payload (POST/GET parameters) via `ExtractRequestDataUtilities`.
|
|
9
|
+
3. It creates a master `Entry` object.
|
|
10
|
+
4. It attaches the `Entry` to the `request` object (e.g., `request.GMS_OBJECT.entry`).
|
|
11
|
+
|
|
12
|
+
This allows `BaseSystem` to receive a fully hydrated context without caring if the request came from an API, an HTMX call, or a standard form submission.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# BaseCreator
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseCreator` handles the write operations (Create, Update, Delete) for a specific Django model. It abstracts away direct database mutations, ensuring consistent data handling. Inherits from `GenericCreator`.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `model_class`: The Django model to mutate.
|
|
8
|
+
- `status`: Execution status flag.
|
|
9
|
+
- `system_infos`: Resolved class information for logging/metadata.
|
|
10
|
+
|
|
11
|
+
## Methods
|
|
12
|
+
- `access_db`: Property returning the model's default manager (`objects`).
|
|
13
|
+
|
|
14
|
+
## Usage Example
|
|
15
|
+
```python
|
|
16
|
+
from django_abstract.base.base_creator import BaseCreator
|
|
17
|
+
from .models import Product
|
|
18
|
+
|
|
19
|
+
class ProductCreator(BaseCreator):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__(Product)
|
|
22
|
+
|
|
23
|
+
def bulk_create_products(self, data_list):
|
|
24
|
+
return self.access_db.bulk_create([self.model_class(**data) for data in data_list])
|
|
25
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# BaseDependency
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseDependency` is the foundation for dependency injection in the framework. It acts as a registry container, allowing components (like selectors and creators) to be resolved dynamically via custom `__getattr__` logic.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `_registry`: The global or domain-specific registry (usually `GLOBAL_REGISTRY`).
|
|
8
|
+
- `selectors`: A mapping of registered selectors for this dependency.
|
|
9
|
+
- `creators`: A mapping of registered creators for this dependency.
|
|
10
|
+
- `model_class`: The associated Django model class, automatically attached during registration.
|
|
11
|
+
|
|
12
|
+
## Dynamic Resolution
|
|
13
|
+
The magic of `BaseDependency` comes from overriding `__getattr__`.
|
|
14
|
+
- If you call `dependency.select_user`, it checks `selectors` in the registry and instantiates the `UserSelector`.
|
|
15
|
+
- If you call `dependency.create_user`, it checks `creators` and instantiates the `UserCreator`.
|
|
16
|
+
|
|
17
|
+
## Usage Example
|
|
18
|
+
```python
|
|
19
|
+
from django_abstract.base.base_dependency import BaseDependency
|
|
20
|
+
from django_abstract.registry import creator_selector
|
|
21
|
+
|
|
22
|
+
class ShopDependency(BaseDependency):
|
|
23
|
+
app_name = "shop"
|
|
24
|
+
domain = "e-commerce"
|
|
25
|
+
|
|
26
|
+
@creator_selector(dependency=ShopDependency())
|
|
27
|
+
class Product(BaseModel):
|
|
28
|
+
name = models.CharField(max_length=255)
|
|
29
|
+
|
|
30
|
+
# Later in the code:
|
|
31
|
+
dependency = ShopDependency()
|
|
32
|
+
selector = dependency.select_product # Returns ProductSelector instance
|
|
33
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# CoreException
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`CoreException` provides a standardized exception hierarchy for the entire framework. It allows for contextual error reporting and captures execution state, making debugging significantly easier.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `message`: Human-readable error message.
|
|
8
|
+
- `error_code`: Specific application error code.
|
|
9
|
+
- `original_exception`: The underlying exception (if wrapping an existing error).
|
|
10
|
+
- `context`: Additional dictionary payload associated with the error.
|
|
11
|
+
- `timestamp`: UTC timestamp of when the exception occurred.
|
|
12
|
+
|
|
13
|
+
## Derived Exceptions
|
|
14
|
+
The framework defines several derived exceptions in `exceptions.py`, such as:
|
|
15
|
+
- `ServiceException`
|
|
16
|
+
- `SelectorException`
|
|
17
|
+
- `SystemException`
|
|
18
|
+
- `ModelNotBindedException`
|
|
19
|
+
|
|
20
|
+
## Usage Example
|
|
21
|
+
```python
|
|
22
|
+
from django_abstract.base.base_exception import CoreException
|
|
23
|
+
|
|
24
|
+
def do_something_risky():
|
|
25
|
+
try:
|
|
26
|
+
1 / 0
|
|
27
|
+
except ZeroDivisionError as e:
|
|
28
|
+
raise CoreException(
|
|
29
|
+
message="Math calculation failed",
|
|
30
|
+
error_code="MATH_001",
|
|
31
|
+
original_exception=e,
|
|
32
|
+
context={"user_id": 123}
|
|
33
|
+
)
|
|
34
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# BaseForm
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseForm` extends Django's `ModelForm` to automatically exclude audit fields (like `created_at`, `updated_at`, etc.) and apply standard CSS classes (e.g., Bootstrap's `form-control`) to widgets.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `deafault_exclude_fields`: List of standard audit fields to exclude automatically.
|
|
8
|
+
- `exclude_fields`: Dynamically populated list of fields to exclude.
|
|
9
|
+
|
|
10
|
+
## Methods
|
|
11
|
+
- `exclude(*fields)`: Dynamically adds fields to the exclusion list and re-processes the form.
|
|
12
|
+
- `_form_process()`: Internal method that applies CSS classes and removes excluded fields from the form.
|
|
13
|
+
|
|
14
|
+
## Usage Example
|
|
15
|
+
```python
|
|
16
|
+
from django_abstract.base.base_form import BaseForm
|
|
17
|
+
from .models import Product
|
|
18
|
+
|
|
19
|
+
class ProductForm(BaseForm):
|
|
20
|
+
class Meta:
|
|
21
|
+
model = Product
|
|
22
|
+
fields = "__all__"
|
|
23
|
+
|
|
24
|
+
# In a view:
|
|
25
|
+
form = ProductForm()
|
|
26
|
+
form.exclude("internal_code", "supplier_price")
|
|
27
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# BaseModel
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseModel` provides a foundational Django model abstraction that adds standard audit fields and implements generic soft-deletion mechanics out of the box.
|
|
5
|
+
|
|
6
|
+
## Fields
|
|
7
|
+
- `id`: UUIDField (Primary Key).
|
|
8
|
+
- `created_at`: DateTimeField (auto-populated on creation).
|
|
9
|
+
- `updated_at`: DateTimeField (auto-updated on save).
|
|
10
|
+
- `is_active`: BooleanField (defaults to True).
|
|
11
|
+
- `is_disabled`: BooleanField (defaults to False).
|
|
12
|
+
- `deactivated_at`: DateTimeField.
|
|
13
|
+
- `deactivated_by`: CharField.
|
|
14
|
+
- `created_by`: CharField.
|
|
15
|
+
- `updated_by`: CharField.
|
|
16
|
+
- `notes`: TextField.
|
|
17
|
+
|
|
18
|
+
## Methods
|
|
19
|
+
- `deactivate(user_id=None)`: Soft-deletes the record by setting `is_active=False` and recording the timestamp/user.
|
|
20
|
+
- `activate()`: Re-activates a soft-deleted record.
|
|
21
|
+
- `disable()`: Marks the record as completely disabled (distinct from soft-delete).
|
|
22
|
+
|
|
23
|
+
## Usage Example
|
|
24
|
+
```python
|
|
25
|
+
from django_abstract.base.base_model import BaseModel
|
|
26
|
+
from django.db import models
|
|
27
|
+
|
|
28
|
+
class Product(BaseModel):
|
|
29
|
+
name = models.CharField(max_length=255)
|
|
30
|
+
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
31
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# BaseModelService
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseModelService` inherits from `BaseOperatorService` and acts as the Model-Specific Logic Layer. It automatically gains access to the `selector` and `creator` for a registered Django model.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `service_slug`: Unique identifier for the service.
|
|
8
|
+
|
|
9
|
+
## Methods
|
|
10
|
+
- `get_or_raise(**kwargs)`: Standardized fetch operation that raises a `ModelServiceException` if the record is not found, ensuring strict data expectations.
|
|
11
|
+
|
|
12
|
+
## Usage Example
|
|
13
|
+
```python
|
|
14
|
+
from django_abstract.base.base_model_service import BaseModelService
|
|
15
|
+
from django_abstract.registry import register_service
|
|
16
|
+
|
|
17
|
+
@register_service()
|
|
18
|
+
class ProductModelService(BaseModelService):
|
|
19
|
+
model_dependency = ShopDependency()
|
|
20
|
+
model_slug = "product"
|
|
21
|
+
|
|
22
|
+
def fetch_product(self, product_id):
|
|
23
|
+
return self.get_or_raise(id=product_id)
|
|
24
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# BaseModelSystem
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseModelSystem` is identical in structure to `BaseSystem`, but **always wraps execution in a database transaction** (`transaction.atomic()`). If any service or operator fails during `.execute()`, the entire database state rolls back.
|
|
5
|
+
|
|
6
|
+
## Key Differences
|
|
7
|
+
- The `.run()` method enforces `transaction.atomic()`. Use this system whenever orchestrating data writes across multiple tables (e.g., fulfilling an order and updating inventory).
|
|
8
|
+
|
|
9
|
+
## Usage Example
|
|
10
|
+
```python
|
|
11
|
+
from django_abstract.base.base_model_system import BaseModelSystem
|
|
12
|
+
|
|
13
|
+
class CheckoutSystem(BaseModelSystem):
|
|
14
|
+
allowed_operators = ["cart_operator", "inventory_operator"]
|
|
15
|
+
|
|
16
|
+
def execute(self, cart_id):
|
|
17
|
+
# If inventory update fails, the cart charge is rolled back automatically
|
|
18
|
+
self.invoke_operator("cart_operator", "payment_service", "charge", {"cart_id": cart_id})
|
|
19
|
+
self.invoke_operator("inventory_operator", "inventory_service", "deduct", {"cart_id": cart_id})
|
|
20
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# BaseOperator
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseOperator` defines the flow control and permission layer. Operators determine who can access which services under what conditions. They act as "Routers" inside the `BaseSystem` to delegate actions to specific `BaseServices`.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `allowed_services`: A whitelist of service names this operator is allowed to invoke.
|
|
8
|
+
- `domain`: The domain context the operator belongs to.
|
|
9
|
+
- `entry`: A `ControlEntryData` instance tracking execution state, flags, and errors.
|
|
10
|
+
- `entry_operator`: A `ControlDataOperator` to mutate the entry data.
|
|
11
|
+
|
|
12
|
+
## Methods
|
|
13
|
+
- `dispatch(target_service_name, target_method, payload)`: Validates if the service is allowed, instantiates it with the current payload, and executes the target method.
|
|
14
|
+
|
|
15
|
+
## Usage Example
|
|
16
|
+
```python
|
|
17
|
+
from django_abstract.base.base_operator import BaseOperator
|
|
18
|
+
from django_abstract.registry import register_operator
|
|
19
|
+
|
|
20
|
+
@register_operator()
|
|
21
|
+
class GuestCartOperator(BaseOperator):
|
|
22
|
+
allowed_services = ["cart_model_service", "session_metrics_service"]
|
|
23
|
+
|
|
24
|
+
def dispatch(self, target_service_name, target_method, payload):
|
|
25
|
+
# Additional permission checks for guests
|
|
26
|
+
return super().dispatch(target_service_name, target_method, payload)
|
|
27
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# BaseOperatorService
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseOperatorService` is the core service base class that bridges `BaseService` logic with Django model dependencies. It manages validation, caching bulk updates, and database transaction tracking.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `model_dependency`: The injected model dependency registry.
|
|
8
|
+
- `model_slug`: The slug identifying the target model.
|
|
9
|
+
- `last_updated`: Timestamp of the last bulk update.
|
|
10
|
+
- `operator_class`: Set to `ServiceDataOperator`.
|
|
11
|
+
- `entry_class`: Set to `ServiceEntryData`.
|
|
12
|
+
|
|
13
|
+
## Key Features
|
|
14
|
+
- **Validation**: Implements an inner `BaseServiceValidator` class to handle `MINIMUM_WRITE_FIELDS` and permission checks before any method runs.
|
|
15
|
+
- **Bulk Updating**: Collects pending updates in memory and only flushes to the database via `bulk_update` periodically (e.g., every 5 seconds) to prevent database locking on high-throughput models.
|
|
16
|
+
- **Auto-loading**: Automatically loads the target database record into memory upon instantiation if `load_record=True`.
|
|
17
|
+
|
|
18
|
+
## Usage Example
|
|
19
|
+
Usually, developers inherit from `BaseModelService` instead of directly from `BaseOperatorService`, but it can be used for custom logic bridging.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# BaseSelector
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseSelector` acts as the read-only layer of the data access pattern. It abstracts away complex Django ORM queries into clean, testable methods.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `model_class`: The Django model this selector is responsible for.
|
|
8
|
+
|
|
9
|
+
## Methods
|
|
10
|
+
- `__init__(model_class)`: Initializes the selector.
|
|
11
|
+
- `get(**kwargs)`: Returns a single instance matching the query parameters or None.
|
|
12
|
+
- `filter(**kwargs)`: Returns a queryset of instances matching the query parameters.
|
|
13
|
+
- `all()`: Returns all instances of the model.
|
|
14
|
+
- `exists(**kwargs)`: Checks if any instances match the parameters.
|
|
15
|
+
- `count(**kwargs)`: Returns the count of matching instances.
|
|
16
|
+
|
|
17
|
+
## Usage Example
|
|
18
|
+
```python
|
|
19
|
+
from django_abstract.base.base_selector import BaseSelector
|
|
20
|
+
from .models import Product
|
|
21
|
+
|
|
22
|
+
class ProductSelector(BaseSelector):
|
|
23
|
+
def __init__(self):
|
|
24
|
+
super().__init__(Product)
|
|
25
|
+
|
|
26
|
+
def get_expensive_products(self):
|
|
27
|
+
return self.filter(price__gt=1000)
|
|
28
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# BaseService
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseService` is the pure business logic layer. It operates entirely on `ServiceEntryData` and does NOT have direct database dependencies (unlike `BaseModelService`).
|
|
5
|
+
|
|
6
|
+
## Key Concepts
|
|
7
|
+
- **Validation**: Enforces `MINIMUM_WRITE_FIELDS` or required payload parameters before execution.
|
|
8
|
+
- **State Mutation**: Operations update `self.entry.service_data` instead of returning discrete values, ensuring a uniform data pipeline.
|
|
9
|
+
- **Hook Architecture**: Uses hooks and execution proxies to chain multiple services together cleanly.
|
|
10
|
+
|
|
11
|
+
## Attributes
|
|
12
|
+
- `operator_class`: Set to `ServiceDataOperator`.
|
|
13
|
+
- `entry_class`: Set to `ServiceEntryData`.
|
|
14
|
+
- `hooks_list`: Allowed hooks.
|
|
15
|
+
|
|
16
|
+
## Methods
|
|
17
|
+
- `init_state_hook()`: Initializes the entry and operator state.
|
|
18
|
+
- `hook()`: Lifecycle execution method to be called by operators.
|
|
19
|
+
|
|
20
|
+
## Usage Example
|
|
21
|
+
```python
|
|
22
|
+
from django_abstract.base.base_service import BaseService
|
|
23
|
+
from django_abstract.registry import register_service, action_method_fields
|
|
24
|
+
|
|
25
|
+
@register_service(service_type="BARE_SERVICE")
|
|
26
|
+
class EmailNotificationService(BaseService):
|
|
27
|
+
|
|
28
|
+
@action_method_fields("email_address", "template_id")
|
|
29
|
+
def send_welcome_email(self, email_address, template_id):
|
|
30
|
+
# Call third party API
|
|
31
|
+
self.entry.service_data.update({"email_sent": True})
|
|
32
|
+
return self.entry
|
|
33
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# BaseSystem
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`BaseSystem` acts as the primary Orchestration Layer for actions that DO NOT require strict database transactions. It defines the workflow entry point and manages the master `Entry` object.
|
|
5
|
+
|
|
6
|
+
## Attributes
|
|
7
|
+
- `allowed_operators`: Whitelist of operators this system is allowed to invoke.
|
|
8
|
+
- `request`: The incoming HTTP request.
|
|
9
|
+
- `session_key`: The active session identifier.
|
|
10
|
+
- `entry`: The master `Entry` object that holds `ControlEntryData`, `ServiceEntryData`, and `EntryData`.
|
|
11
|
+
|
|
12
|
+
## Methods
|
|
13
|
+
- `execute(*args, **kwargs)`: Abstract method. Must be implemented to define the main orchestration logic.
|
|
14
|
+
- `run(*args, **kwargs)`: Standard execution wrapper. Calls `execute()` and handles high-level failure logging.
|
|
15
|
+
- `invoke_operator(operator_name, target_service, target_method, payload)`: Dynamically fetches the specified operator, validates system permissions, and dispatches the payload to the service.
|
|
16
|
+
|
|
17
|
+
## Usage Example
|
|
18
|
+
```python
|
|
19
|
+
from django_abstract.base.base_system import BaseSystem
|
|
20
|
+
|
|
21
|
+
class AnalyticsSystem(BaseSystem):
|
|
22
|
+
allowed_operators = ["metrics_operator"]
|
|
23
|
+
|
|
24
|
+
def execute(self, path):
|
|
25
|
+
self.invoke_operator("metrics_operator", "session_metrics", "record_hit", {"path": path})
|
|
26
|
+
```
|