django-cfg 1.4.120__py3-none-any.whl → 1.5.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 django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +13 -8
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +16 -26
- django_cfg/core/builders/apps_builder.py +7 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +15 -15
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +19 -6
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +205 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_unfold/navigation.py +21 -18
- django_cfg/pyproject.toml +4 -6
- django_cfg/registry/core.py +4 -7
- django_cfg/registry/modules.py +6 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/task_log.py +0 -265
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
- /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
# Code Block Methods Documentation
|
|
2
|
-
|
|
3
|
-
New methods added to `HtmlBuilder` for displaying code in Django Admin.
|
|
4
|
-
|
|
5
|
-
## Methods Added
|
|
6
|
-
|
|
7
|
-
### 1. `code()` - Inline Code
|
|
8
|
-
|
|
9
|
-
Display short code snippets inline.
|
|
10
|
-
|
|
11
|
-
**Signature:**
|
|
12
|
-
```python
|
|
13
|
-
def code(text: Any, css_class: str = "") -> SafeString
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
**Parameters:**
|
|
17
|
-
- `text`: Code text to display
|
|
18
|
-
- `css_class`: Additional CSS classes (optional)
|
|
19
|
-
|
|
20
|
-
**Usage:**
|
|
21
|
-
```python
|
|
22
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
23
|
-
|
|
24
|
-
class MyAdmin(PydanticAdmin):
|
|
25
|
-
@computed_field("Command")
|
|
26
|
-
def command_display(self, obj):
|
|
27
|
-
return self.html.code(obj.full_command)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**Examples:**
|
|
31
|
-
```python
|
|
32
|
-
# File path
|
|
33
|
-
self.html.code("/path/to/file")
|
|
34
|
-
|
|
35
|
-
# Command
|
|
36
|
-
self.html.code("python manage.py migrate")
|
|
37
|
-
|
|
38
|
-
# Configuration value
|
|
39
|
-
self.html.code("DEBUG=True")
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Renders as:**
|
|
43
|
-
```html
|
|
44
|
-
<code class="font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded">
|
|
45
|
-
/path/to/file
|
|
46
|
-
</code>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
### 2. `code_block()` - Multi-line Code Block
|
|
52
|
-
|
|
53
|
-
Display multi-line code with syntax highlighting hints, scrolling, and color variants.
|
|
54
|
-
|
|
55
|
-
**Signature:**
|
|
56
|
-
```python
|
|
57
|
-
def code_block(
|
|
58
|
-
text: Any,
|
|
59
|
-
language: Optional[str] = None,
|
|
60
|
-
max_height: Optional[str] = None,
|
|
61
|
-
variant: str = "default"
|
|
62
|
-
) -> SafeString
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**Parameters:**
|
|
66
|
-
- `text`: Code content (multi-line supported)
|
|
67
|
-
- `language`: Programming language hint (`"json"`, `"python"`, `"bash"`, etc.) - for future syntax highlighting
|
|
68
|
-
- `max_height`: Maximum height with overflow scroll (e.g., `"400px"`, `"20rem"`)
|
|
69
|
-
- `variant`: Color variant - `"default"`, `"warning"`, `"danger"`, `"success"`, `"info"`
|
|
70
|
-
|
|
71
|
-
**Usage:**
|
|
72
|
-
```python
|
|
73
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
74
|
-
import json
|
|
75
|
-
|
|
76
|
-
class MyAdmin(PydanticAdmin):
|
|
77
|
-
@computed_field("Configuration")
|
|
78
|
-
def config_display(self, obj):
|
|
79
|
-
if not obj.config:
|
|
80
|
-
return self.html.empty()
|
|
81
|
-
|
|
82
|
-
return self.html.code_block(
|
|
83
|
-
json.dumps(obj.config, indent=2),
|
|
84
|
-
language="json"
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
@computed_field("Standard Output")
|
|
88
|
-
def stdout_display(self, obj):
|
|
89
|
-
if not obj.stdout:
|
|
90
|
-
return self.html.empty()
|
|
91
|
-
|
|
92
|
-
return self.html.code_block(
|
|
93
|
-
obj.stdout,
|
|
94
|
-
max_height="400px"
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
@computed_field("Error Message")
|
|
98
|
-
def error_display(self, obj):
|
|
99
|
-
if not obj.error_message:
|
|
100
|
-
return self.html.empty()
|
|
101
|
-
|
|
102
|
-
return self.html.code_block(
|
|
103
|
-
obj.error_message,
|
|
104
|
-
max_height="200px",
|
|
105
|
-
variant="danger"
|
|
106
|
-
)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Examples:**
|
|
110
|
-
|
|
111
|
-
#### JSON Display
|
|
112
|
-
```python
|
|
113
|
-
# Display JSON configuration
|
|
114
|
-
self.html.code_block(
|
|
115
|
-
json.dumps({"key": "value", "nested": {"foo": "bar"}}, indent=2),
|
|
116
|
-
language="json"
|
|
117
|
-
)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
#### Log Output with Scrolling
|
|
121
|
-
```python
|
|
122
|
-
# Display command output with scroll
|
|
123
|
-
self.html.code_block(
|
|
124
|
-
"Line 1\nLine 2\nLine 3\n...",
|
|
125
|
-
max_height="400px"
|
|
126
|
-
)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### Error Messages
|
|
130
|
-
```python
|
|
131
|
-
# Display error with danger styling
|
|
132
|
-
self.html.code_block(
|
|
133
|
-
"Traceback (most recent call last):\n File ...",
|
|
134
|
-
variant="danger",
|
|
135
|
-
max_height="300px"
|
|
136
|
-
)
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
#### Warnings
|
|
140
|
-
```python
|
|
141
|
-
# Display warnings with warning styling
|
|
142
|
-
self.html.code_block(
|
|
143
|
-
"Warning: This is deprecated\nUse new_method() instead",
|
|
144
|
-
variant="warning"
|
|
145
|
-
)
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**Renders as:**
|
|
149
|
-
```html
|
|
150
|
-
<!-- Default variant -->
|
|
151
|
-
<pre class="font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3
|
|
152
|
-
bg-base-50 dark:bg-base-900 border-base-200 dark:border-base-700"
|
|
153
|
-
style="max-height: 400px; overflow-y: auto;">
|
|
154
|
-
<code>{"key": "value"}</code>
|
|
155
|
-
</pre>
|
|
156
|
-
|
|
157
|
-
<!-- Danger variant -->
|
|
158
|
-
<pre class="font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3
|
|
159
|
-
bg-danger-50 dark:bg-danger-900/20 border-danger-200 dark:border-danger-700"
|
|
160
|
-
style="max-height: 200px; overflow-y: auto;">
|
|
161
|
-
<code>Error: Something went wrong</code>
|
|
162
|
-
</pre>
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## Color Variants
|
|
168
|
-
|
|
169
|
-
| Variant | Background | Border | Use Case |
|
|
170
|
-
|---------|-----------|--------|----------|
|
|
171
|
-
| `default` | Gray | Gray | Standard code, JSON, logs |
|
|
172
|
-
| `warning` | Yellow/Orange | Yellow/Orange | Warnings, stderr output |
|
|
173
|
-
| `danger` | Red | Red | Errors, exceptions, failures |
|
|
174
|
-
| `success` | Green | Green | Success messages, confirmations |
|
|
175
|
-
| `info` | Blue | Blue | Info messages, metadata |
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## Use Cases
|
|
180
|
-
|
|
181
|
-
### 1. Command Execution Logs
|
|
182
|
-
```python
|
|
183
|
-
@computed_field("Output")
|
|
184
|
-
def output_display(self, obj):
|
|
185
|
-
return self.html.code_block(
|
|
186
|
-
obj.stdout,
|
|
187
|
-
max_height="400px"
|
|
188
|
-
)
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### 2. JSON Configuration
|
|
192
|
-
```python
|
|
193
|
-
@computed_field("Settings")
|
|
194
|
-
def settings_display(self, obj):
|
|
195
|
-
return self.html.code_block(
|
|
196
|
-
json.dumps(obj.settings, indent=2),
|
|
197
|
-
language="json"
|
|
198
|
-
)
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### 3. Error Details
|
|
202
|
-
```python
|
|
203
|
-
@computed_field("Error")
|
|
204
|
-
def error_display(self, obj):
|
|
205
|
-
if not obj.error_message:
|
|
206
|
-
return self.html.empty()
|
|
207
|
-
|
|
208
|
-
return self.html.code_block(
|
|
209
|
-
obj.error_message,
|
|
210
|
-
max_height="300px",
|
|
211
|
-
variant="danger"
|
|
212
|
-
)
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### 4. API Request/Response
|
|
216
|
-
```python
|
|
217
|
-
@computed_field("Request Body")
|
|
218
|
-
def request_body_display(self, obj):
|
|
219
|
-
return self.html.code_block(
|
|
220
|
-
obj.request_body,
|
|
221
|
-
language="json",
|
|
222
|
-
max_height="300px"
|
|
223
|
-
)
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### 5. SQL Queries
|
|
227
|
-
```python
|
|
228
|
-
@computed_field("Query")
|
|
229
|
-
def query_display(self, obj):
|
|
230
|
-
return self.html.code_block(
|
|
231
|
-
obj.sql_query,
|
|
232
|
-
language="sql",
|
|
233
|
-
max_height="200px"
|
|
234
|
-
)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Styling Details
|
|
240
|
-
|
|
241
|
-
### Tailwind Classes Used
|
|
242
|
-
|
|
243
|
-
**Base Classes:**
|
|
244
|
-
- `font-mono` - Monospace font
|
|
245
|
-
- `text-xs` - Small text size
|
|
246
|
-
- `whitespace-pre-wrap` - Preserve whitespace, wrap long lines
|
|
247
|
-
- `break-words` - Break long words
|
|
248
|
-
- `border` - Border
|
|
249
|
-
- `rounded-md` - Rounded corners
|
|
250
|
-
- `p-3` - Padding
|
|
251
|
-
|
|
252
|
-
**Variant-specific:**
|
|
253
|
-
- Light mode: `bg-{variant}-50 border-{variant}-200`
|
|
254
|
-
- Dark mode: `dark:bg-{variant}-900/20 dark:border-{variant}-700`
|
|
255
|
-
|
|
256
|
-
**Scrolling:**
|
|
257
|
-
- Applied via inline `style` attribute
|
|
258
|
-
- `max-height` and `overflow-y: auto`
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
|
-
## Implementation Notes
|
|
263
|
-
|
|
264
|
-
1. **Escaping**: All text is automatically escaped using Django's `escape()` to prevent XSS
|
|
265
|
-
2. **SafeString**: Returns Django's `SafeString` for safe HTML rendering
|
|
266
|
-
3. **Dark Mode**: Full dark mode support with Tailwind's dark: prefix
|
|
267
|
-
4. **Responsive**: Works on mobile with `break-words` and `whitespace-pre-wrap`
|
|
268
|
-
5. **Future-proof**: `language` parameter allows for syntax highlighting integration later
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Complete Example: Command Execution Admin
|
|
273
|
-
|
|
274
|
-
```python
|
|
275
|
-
from django.contrib import admin
|
|
276
|
-
from django_cfg.modules.django_admin import (
|
|
277
|
-
AdminConfig, BadgeField, DateTimeField, Icons, computed_field
|
|
278
|
-
)
|
|
279
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
280
|
-
import json
|
|
281
|
-
|
|
282
|
-
commandexecution_config = AdminConfig(
|
|
283
|
-
model=CommandExecution,
|
|
284
|
-
list_display=["id", "command", "status", "created_at"],
|
|
285
|
-
readonly_fields=[
|
|
286
|
-
"command_display",
|
|
287
|
-
"args_display",
|
|
288
|
-
"stdout_display",
|
|
289
|
-
"stderr_display",
|
|
290
|
-
"error_display",
|
|
291
|
-
],
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
@admin.register(CommandExecution)
|
|
295
|
-
class CommandExecutionAdmin(PydanticAdmin):
|
|
296
|
-
config = commandexecution_config
|
|
297
|
-
|
|
298
|
-
@computed_field("Command")
|
|
299
|
-
def command_display(self, obj):
|
|
300
|
-
"""Inline code for command."""
|
|
301
|
-
return self.html.code(obj.full_command)
|
|
302
|
-
|
|
303
|
-
@computed_field("Arguments")
|
|
304
|
-
def args_display(self, obj):
|
|
305
|
-
"""JSON code block for args."""
|
|
306
|
-
if not obj.args:
|
|
307
|
-
return self.html.empty()
|
|
308
|
-
|
|
309
|
-
return self.html.code_block(
|
|
310
|
-
json.dumps(obj.args, indent=2),
|
|
311
|
-
language="json"
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
@computed_field("Standard Output")
|
|
315
|
-
def stdout_display(self, obj):
|
|
316
|
-
"""Scrollable output."""
|
|
317
|
-
if not obj.stdout:
|
|
318
|
-
return self.html.empty()
|
|
319
|
-
|
|
320
|
-
return self.html.code_block(
|
|
321
|
-
obj.stdout,
|
|
322
|
-
max_height="400px"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
@computed_field("Standard Error")
|
|
326
|
-
def stderr_display(self, obj):
|
|
327
|
-
"""Warning-styled error output."""
|
|
328
|
-
if not obj.stderr:
|
|
329
|
-
return self.html.empty()
|
|
330
|
-
|
|
331
|
-
return self.html.code_block(
|
|
332
|
-
obj.stderr,
|
|
333
|
-
max_height="400px",
|
|
334
|
-
variant="warning"
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
@computed_field("Error Message")
|
|
338
|
-
def error_display(self, obj):
|
|
339
|
-
"""Danger-styled error message."""
|
|
340
|
-
if not obj.error_message:
|
|
341
|
-
return self.html.empty()
|
|
342
|
-
|
|
343
|
-
return self.html.code_block(
|
|
344
|
-
obj.error_message,
|
|
345
|
-
max_height="200px",
|
|
346
|
-
variant="danger"
|
|
347
|
-
)
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## Testing
|
|
353
|
-
|
|
354
|
-
```python
|
|
355
|
-
# Test inline code
|
|
356
|
-
assert 'font-mono' in str(html.code("test"))
|
|
357
|
-
|
|
358
|
-
# Test code block
|
|
359
|
-
assert '<pre' in str(html.code_block("test"))
|
|
360
|
-
assert 'overflow-y: auto' in str(html.code_block("test", max_height="400px"))
|
|
361
|
-
|
|
362
|
-
# Test variants
|
|
363
|
-
assert 'bg-danger' in str(html.code_block("error", variant="danger"))
|
|
364
|
-
assert 'bg-warning' in str(html.code_block("warn", variant="warning"))
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## Migration from Old Format
|
|
370
|
-
|
|
371
|
-
**Before (manual HTML):**
|
|
372
|
-
```python
|
|
373
|
-
def output_display(self, obj):
|
|
374
|
-
return format_html(
|
|
375
|
-
'<pre style="max-height: 400px; overflow-y: auto; '
|
|
376
|
-
'background: #f8f9fa; padding: 10px; border-radius: 4px;">{}</pre>',
|
|
377
|
-
obj.stdout
|
|
378
|
-
)
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**After (with code_block):**
|
|
382
|
-
```python
|
|
383
|
-
def output_display(self, obj):
|
|
384
|
-
return self.html.code_block(
|
|
385
|
-
obj.stdout,
|
|
386
|
-
max_height="400px"
|
|
387
|
-
)
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
Benefits:
|
|
391
|
-
- ✅ Cleaner, more readable code
|
|
392
|
-
- ✅ Consistent styling across project
|
|
393
|
-
- ✅ Dark mode support
|
|
394
|
-
- ✅ Proper escaping
|
|
395
|
-
- ✅ Tailwind CSS classes
|
|
396
|
-
- ✅ Variant support for different contexts
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# Django-Q2 Module
|
|
2
|
-
|
|
3
|
-
Автоматическая синхронизация расписаний Django-Q2 из конфига в базу данных.
|
|
4
|
-
|
|
5
|
-
## Зачем это нужно?
|
|
6
|
-
|
|
7
|
-
Django-Q2 хранит расписания в базе данных, но **не создаёт их автоматически** из конфига.
|
|
8
|
-
Этот модуль решает эту проблему - синхронизирует расписания после каждой миграции.
|
|
9
|
-
|
|
10
|
-
## Использование
|
|
11
|
-
|
|
12
|
-
### 1. Включи Django-Q2 в конфиге
|
|
13
|
-
|
|
14
|
-
```python
|
|
15
|
-
# config.py
|
|
16
|
-
from django_cfg.models.django import DjangoQ2Config
|
|
17
|
-
|
|
18
|
-
django_q2 = DjangoQ2Config(
|
|
19
|
-
enabled=True, # ← Автоматически добавит django_q и django_cfg.modules.django_q2 в INSTALLED_APPS
|
|
20
|
-
schedules=[...]
|
|
21
|
-
)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Модуль подключается автоматически!** Не нужно вручную добавлять в INSTALLED_APPS.
|
|
25
|
-
|
|
26
|
-
### 2. Определи расписания в конфиге
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from django_cfg.models.django import DjangoQ2Config, DjangoQ2ScheduleConfig
|
|
30
|
-
|
|
31
|
-
django_q2 = DjangoQ2Config(
|
|
32
|
-
enabled=True,
|
|
33
|
-
schedules=[
|
|
34
|
-
DjangoQ2ScheduleConfig(
|
|
35
|
-
name="Sync balances hourly",
|
|
36
|
-
schedule_type="hourly",
|
|
37
|
-
command="sync_account_balances",
|
|
38
|
-
command_args=["--verbose"],
|
|
39
|
-
),
|
|
40
|
-
DjangoQ2ScheduleConfig(
|
|
41
|
-
name="Cleanup daily",
|
|
42
|
-
schedule_type="cron",
|
|
43
|
-
cron="0 2 * * *", # 2 AM каждый день
|
|
44
|
-
command="cleanup_old_data",
|
|
45
|
-
command_kwargs={"days": 30},
|
|
46
|
-
),
|
|
47
|
-
],
|
|
48
|
-
)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### 3. Запусти миграции
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
python manage.py migrate
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Вывод:**
|
|
58
|
-
```
|
|
59
|
-
Running migrations:
|
|
60
|
-
...
|
|
61
|
-
Syncing 2 Django-Q2 schedule(s)...
|
|
62
|
-
✓ Created schedule: Sync balances hourly
|
|
63
|
-
✓ Created schedule: Cleanup daily
|
|
64
|
-
✅ Django-Q2 schedules synced: 2 created, 0 updated
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 4. Запусти qcluster
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
python manage.py qcluster
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Готово! Расписания автоматически синхронизированы и работают.
|
|
74
|
-
|
|
75
|
-
## Как это работает?
|
|
76
|
-
|
|
77
|
-
1. **Модуль подключается** к сигналу `post_migrate`
|
|
78
|
-
2. **После миграций** автоматически:
|
|
79
|
-
- Читает расписания из конфига
|
|
80
|
-
- Создаёт/обновляет их в базе данных (Schedule model)
|
|
81
|
-
3. **Django-Q2 читает** расписания из базы и выполняет задачи
|
|
82
|
-
|
|
83
|
-
## Ручная синхронизация (опционально)
|
|
84
|
-
|
|
85
|
-
Если нужно синхронизировать без миграций:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
python manage.py sync_django_q_schedules
|
|
89
|
-
|
|
90
|
-
# Или с --dry-run для проверки:
|
|
91
|
-
python manage.py sync_django_q_schedules --dry-run
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Безопасность
|
|
95
|
-
|
|
96
|
-
- ✅ **Идемпотентность**: можно запускать много раз, не создаст дубликаты
|
|
97
|
-
- ✅ **Без race conditions**: синхронизация происходит один раз за цикл миграций
|
|
98
|
-
- ✅ **Graceful degradation**: если Django-Q2 не установлен, модуль просто молча пропустит синхронизацию
|
|
99
|
-
- ✅ **Logging**: все операции логируются для отладки
|
|
100
|
-
|
|
101
|
-
## Преимущества перед ручной синхронизацией
|
|
102
|
-
|
|
103
|
-
| Аспект | Ручная синхронизация | Модуль |
|
|
104
|
-
|--------|---------------------|--------|
|
|
105
|
-
| Автоматизация | Нужно помнить запускать | Автоматически |
|
|
106
|
-
| Деплой | Легко забыть | Всегда синхронизировано |
|
|
107
|
-
| CI/CD | Нужно добавлять в скрипты | Работает из коробки |
|
|
108
|
-
| Ошибки | Легко пропустить | Логи миграций |
|
|
109
|
-
|
|
110
|
-
## Troubleshooting
|
|
111
|
-
|
|
112
|
-
### Расписания не создаются
|
|
113
|
-
|
|
114
|
-
Проверь:
|
|
115
|
-
1. Модуль добавлен в INSTALLED_APPS
|
|
116
|
-
2. `django_q2.enabled = True` в конфиге
|
|
117
|
-
3. В конфиге есть расписания
|
|
118
|
-
4. Миграции запущены: `python manage.py migrate`
|
|
119
|
-
|
|
120
|
-
### Расписания не обновляются
|
|
121
|
-
|
|
122
|
-
Запусти миграции повторно или используй ручную синхронизацию:
|
|
123
|
-
```bash
|
|
124
|
-
python manage.py migrate --run-syncdb
|
|
125
|
-
# или
|
|
126
|
-
python manage.py sync_django_q_schedules
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Логи синхронизации
|
|
130
|
-
|
|
131
|
-
Включи DEBUG логи:
|
|
132
|
-
```python
|
|
133
|
-
LOGGING = {
|
|
134
|
-
'loggers': {
|
|
135
|
-
'django_cfg.modules.django_q2': {
|
|
136
|
-
'level': 'DEBUG',
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
```
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AppConfig for Django-Q2 module with automatic schedule synchronization.
|
|
3
|
-
"""
|
|
4
|
-
import logging
|
|
5
|
-
from django.apps import AppConfig
|
|
6
|
-
from django.db.models.signals import post_migrate
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def sync_schedules_after_migrate(sender, **kwargs):
|
|
12
|
-
"""
|
|
13
|
-
Automatically sync Django-Q2 schedules after migrations.
|
|
14
|
-
|
|
15
|
-
This ensures schedules are always up-to-date after deployment.
|
|
16
|
-
Runs only once per migration cycle, safe from race conditions.
|
|
17
|
-
"""
|
|
18
|
-
# Only run for the django_cfg_django_q2 app itself
|
|
19
|
-
if sender.name != 'django_cfg.modules.django_q2':
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
# Import here to avoid circular imports and ensure Django is ready
|
|
23
|
-
try:
|
|
24
|
-
from django_q.models import Schedule
|
|
25
|
-
from django_cfg.core.config import get_current_config
|
|
26
|
-
except ImportError as e:
|
|
27
|
-
logger.warning(f"Could not import Django-Q2 dependencies: {e}")
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
config = get_current_config()
|
|
31
|
-
|
|
32
|
-
if not config or not hasattr(config, 'django_q2') or not config.django_q2 or not config.django_q2.enabled:
|
|
33
|
-
logger.debug("Django-Q2 not enabled, skipping schedule sync")
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
enabled_schedules = config.django_q2.get_enabled_schedules()
|
|
37
|
-
|
|
38
|
-
if not enabled_schedules:
|
|
39
|
-
logger.debug("No Django-Q2 schedules found in config")
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
logger.info(f"Syncing {len(enabled_schedules)} Django-Q2 schedule(s)...")
|
|
43
|
-
|
|
44
|
-
created = 0
|
|
45
|
-
updated = 0
|
|
46
|
-
|
|
47
|
-
for schedule_config in enabled_schedules:
|
|
48
|
-
schedule_dict = schedule_config.to_django_q_format()
|
|
49
|
-
name = schedule_dict['name']
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
schedule, created_flag = Schedule.objects.update_or_create(
|
|
53
|
-
name=name,
|
|
54
|
-
defaults=schedule_dict
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
if created_flag:
|
|
58
|
-
created += 1
|
|
59
|
-
logger.info(f" ✓ Created schedule: {name}")
|
|
60
|
-
else:
|
|
61
|
-
updated += 1
|
|
62
|
-
logger.debug(f" ✓ Updated schedule: {name}")
|
|
63
|
-
|
|
64
|
-
except Exception as e:
|
|
65
|
-
logger.error(f" ✗ Failed to sync schedule '{name}': {e}")
|
|
66
|
-
|
|
67
|
-
logger.info(f"✅ Django-Q2 schedules synced: {created} created, {updated} updated")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class DjangoQ2ModuleConfig(AppConfig):
|
|
71
|
-
"""
|
|
72
|
-
AppConfig for Django-Q2 module.
|
|
73
|
-
|
|
74
|
-
Automatically syncs schedules from config to database after migrations.
|
|
75
|
-
This eliminates the need for manual `sync_django_q_schedules` command.
|
|
76
|
-
|
|
77
|
-
Features:
|
|
78
|
-
- Automatic schedule sync after migrations
|
|
79
|
-
- Safe from race conditions (runs only once)
|
|
80
|
-
- Logs all sync operations
|
|
81
|
-
- Gracefully handles missing dependencies
|
|
82
|
-
|
|
83
|
-
Usage:
|
|
84
|
-
Add to INSTALLED_APPS:
|
|
85
|
-
INSTALLED_APPS = [
|
|
86
|
-
...
|
|
87
|
-
'django_cfg.modules.django_q2', # Auto-syncs schedules
|
|
88
|
-
]
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
default_auto_field = 'django.db.models.BigAutoField'
|
|
92
|
-
name = 'django_cfg.modules.django_q2'
|
|
93
|
-
verbose_name = 'Django-CFG Django-Q2 Module'
|
|
94
|
-
|
|
95
|
-
def ready(self):
|
|
96
|
-
"""
|
|
97
|
-
Connect post_migrate signal to automatically sync schedules.
|
|
98
|
-
|
|
99
|
-
This runs after all migrations are complete, ensuring:
|
|
100
|
-
1. Database tables exist
|
|
101
|
-
2. Config is loaded
|
|
102
|
-
3. Schedules are synced only once per migration cycle
|
|
103
|
-
"""
|
|
104
|
-
# Connect the signal
|
|
105
|
-
post_migrate.connect(sync_schedules_after_migrate, sender=self)
|
|
106
|
-
|
|
107
|
-
logger.debug(f"{self.verbose_name} initialized - auto-sync enabled")
|
|
File without changes
|