django-cfg 1.5.8__py3-none-any.whl → 1.5.20__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 +1 -1
- django_cfg/apps/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +126 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Django Management Command Base Classes
|
|
2
|
+
|
|
3
|
+
Simplified base classes for Django management commands with automatic logger initialization and web execution safety metadata.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Logger**: No need to manually create loggers - use `self.logger`
|
|
8
|
+
- **Security Metadata**: Integrated with django_cfg's web execution security system
|
|
9
|
+
- **DRY Principle**: Eliminate boilerplate code in every command
|
|
10
|
+
- **Type Safety**: Clear command categorization with semantic base classes
|
|
11
|
+
|
|
12
|
+
## Available Base Classes
|
|
13
|
+
|
|
14
|
+
### 1. SafeCommand
|
|
15
|
+
For read-only commands safe for web execution.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from django_cfg.management.utils import SafeCommand
|
|
19
|
+
|
|
20
|
+
class Command(SafeCommand):
|
|
21
|
+
help = 'Display current configuration'
|
|
22
|
+
|
|
23
|
+
def handle(self, *args, **options):
|
|
24
|
+
self.logger.info("Showing configuration")
|
|
25
|
+
# Read-only operations
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Metadata:**
|
|
29
|
+
- `web_executable = True`
|
|
30
|
+
- `requires_input = False`
|
|
31
|
+
- `is_destructive = False`
|
|
32
|
+
|
|
33
|
+
**Use for:** show_config, list_urls, check_settings
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 2. InteractiveCommand
|
|
38
|
+
For commands requiring user input (blocked from web).
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from django_cfg.management.utils import InteractiveCommand
|
|
42
|
+
|
|
43
|
+
class Command(InteractiveCommand):
|
|
44
|
+
help = 'Create superuser with prompts'
|
|
45
|
+
|
|
46
|
+
def handle(self, *args, **options):
|
|
47
|
+
self.logger.info("Creating superuser")
|
|
48
|
+
username = input("Username: ")
|
|
49
|
+
# Interactive operations
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Metadata:**
|
|
53
|
+
- `web_executable = False`
|
|
54
|
+
- `requires_input = True`
|
|
55
|
+
- `is_destructive = False`
|
|
56
|
+
|
|
57
|
+
**Use for:** superuser, createsuperuser, any command with `input()` or `questionary`
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 3. DestructiveCommand
|
|
62
|
+
For commands that modify or delete data (blocked from web).
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from django_cfg.management.utils import DestructiveCommand
|
|
66
|
+
|
|
67
|
+
class Command(DestructiveCommand):
|
|
68
|
+
help = 'Clear all cache data'
|
|
69
|
+
|
|
70
|
+
def handle(self, *args, **options):
|
|
71
|
+
self.logger.warning("Clearing cache")
|
|
72
|
+
# Destructive operations
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Metadata:**
|
|
76
|
+
- `web_executable = False`
|
|
77
|
+
- `requires_input = True`
|
|
78
|
+
- `is_destructive = True`
|
|
79
|
+
|
|
80
|
+
**Use for:** clear_constance, flush, clear cache commands
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 4. AdminCommand
|
|
85
|
+
For administrative commands safe for web execution.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from django_cfg.management.utils import AdminCommand
|
|
89
|
+
|
|
90
|
+
class Command(AdminCommand):
|
|
91
|
+
help = 'Run database migrations'
|
|
92
|
+
|
|
93
|
+
def handle(self, *args, **options):
|
|
94
|
+
self.logger.info("Running migrations")
|
|
95
|
+
# Admin operations
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Metadata:**
|
|
99
|
+
- `web_executable = True`
|
|
100
|
+
- `requires_input = False`
|
|
101
|
+
- `is_destructive = False`
|
|
102
|
+
|
|
103
|
+
**Use for:** migrate, collectstatic, createcachetable
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Refactoring Examples
|
|
108
|
+
|
|
109
|
+
### Before: Manual Logger & Metadata (15 lines)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from django.core.management.base import BaseCommand
|
|
113
|
+
from django_cfg.modules.django_logging import get_logger
|
|
114
|
+
|
|
115
|
+
logger = get_logger('show_config')
|
|
116
|
+
|
|
117
|
+
class Command(BaseCommand):
|
|
118
|
+
logger = get_logger('show_config') # Duplicate!
|
|
119
|
+
|
|
120
|
+
# Web execution metadata
|
|
121
|
+
web_executable = True
|
|
122
|
+
requires_input = False
|
|
123
|
+
is_destructive = False
|
|
124
|
+
|
|
125
|
+
help = 'Show Django Config configuration'
|
|
126
|
+
|
|
127
|
+
def handle(self, *args, **options):
|
|
128
|
+
logger.info("Starting command")
|
|
129
|
+
# Command logic
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### After: Using SafeCommand (5 lines)
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from django_cfg.management.utils import SafeCommand
|
|
136
|
+
|
|
137
|
+
class Command(SafeCommand):
|
|
138
|
+
help = 'Show Django Config configuration'
|
|
139
|
+
|
|
140
|
+
def handle(self, *args, **options):
|
|
141
|
+
self.logger.info("Starting command")
|
|
142
|
+
# Command logic
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Result: 67% less boilerplate code!**
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## More Examples
|
|
150
|
+
|
|
151
|
+
### Example 1: clear_constance.py
|
|
152
|
+
|
|
153
|
+
**Before:**
|
|
154
|
+
```python
|
|
155
|
+
from django.core.management.base import BaseCommand
|
|
156
|
+
from django_cfg.modules.django_logging import get_logger
|
|
157
|
+
|
|
158
|
+
class Command(BaseCommand):
|
|
159
|
+
logger = get_logger('clear_constance')
|
|
160
|
+
|
|
161
|
+
web_executable = False
|
|
162
|
+
requires_input = True
|
|
163
|
+
is_destructive = True
|
|
164
|
+
|
|
165
|
+
help = 'Clear Constance configuration cache'
|
|
166
|
+
|
|
167
|
+
def handle(self, *args, **options):
|
|
168
|
+
self.logger.info("Clearing cache")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**After:**
|
|
172
|
+
```python
|
|
173
|
+
from django_cfg.management.utils import DestructiveCommand
|
|
174
|
+
|
|
175
|
+
class Command(DestructiveCommand):
|
|
176
|
+
command_name = 'clear_constance' # Optional: for custom logger name
|
|
177
|
+
help = 'Clear Constance configuration cache'
|
|
178
|
+
|
|
179
|
+
def handle(self, *args, **options):
|
|
180
|
+
self.logger.info("Clearing cache")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Example 2: superuser.py
|
|
186
|
+
|
|
187
|
+
**Before:**
|
|
188
|
+
```python
|
|
189
|
+
from django.core.management.base import BaseCommand
|
|
190
|
+
from django_cfg.modules.django_logging import get_logger
|
|
191
|
+
import questionary
|
|
192
|
+
|
|
193
|
+
logger = get_logger('superuser')
|
|
194
|
+
|
|
195
|
+
class Command(BaseCommand):
|
|
196
|
+
web_executable = False
|
|
197
|
+
requires_input = True
|
|
198
|
+
is_destructive = False
|
|
199
|
+
|
|
200
|
+
help = 'Create superuser'
|
|
201
|
+
|
|
202
|
+
def handle(self, *args, **options):
|
|
203
|
+
logger.info("Starting")
|
|
204
|
+
username = questionary.text("Username:").ask()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**After:**
|
|
208
|
+
```python
|
|
209
|
+
from django_cfg.management.utils import InteractiveCommand
|
|
210
|
+
import questionary
|
|
211
|
+
|
|
212
|
+
class Command(InteractiveCommand):
|
|
213
|
+
help = 'Create superuser'
|
|
214
|
+
|
|
215
|
+
def handle(self, *args, **options):
|
|
216
|
+
self.logger.info("Starting")
|
|
217
|
+
username = questionary.text("Username:").ask()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### Example 3: migrate_all.py
|
|
223
|
+
|
|
224
|
+
**Before:**
|
|
225
|
+
```python
|
|
226
|
+
from django.core.management.base import BaseCommand
|
|
227
|
+
from django_cfg.modules.django_logging import get_logger
|
|
228
|
+
|
|
229
|
+
class Command(BaseCommand):
|
|
230
|
+
logger = get_logger('migrate_all')
|
|
231
|
+
|
|
232
|
+
web_executable = True
|
|
233
|
+
requires_input = False
|
|
234
|
+
is_destructive = False
|
|
235
|
+
|
|
236
|
+
help = 'Run migrations'
|
|
237
|
+
|
|
238
|
+
def handle(self, *args, **options):
|
|
239
|
+
self.logger.info("Running migrations")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**After:**
|
|
243
|
+
```python
|
|
244
|
+
from django_cfg.management.utils import AdminCommand
|
|
245
|
+
|
|
246
|
+
class Command(AdminCommand):
|
|
247
|
+
help = 'Run migrations'
|
|
248
|
+
|
|
249
|
+
def handle(self, *args, **options):
|
|
250
|
+
self.logger.info("Running migrations")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Custom Logger Name
|
|
256
|
+
|
|
257
|
+
By default, the logger name is auto-detected from the module name. To override:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
class Command(SafeCommand):
|
|
261
|
+
command_name = 'my_custom_logger_name'
|
|
262
|
+
help = 'My command'
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Quick Selection Guide
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
┌─────────────────────────────────────┬─────────────────────┐
|
|
271
|
+
│ Command Type │ Use This │
|
|
272
|
+
├─────────────────────────────────────┼─────────────────────┤
|
|
273
|
+
│ Read-only (no modifications) │ SafeCommand │
|
|
274
|
+
│ Requires input() or questionary │ InteractiveCommand │
|
|
275
|
+
│ Deletes or modifies data │ DestructiveCommand │
|
|
276
|
+
│ Admin tasks (migrations, etc) │ AdminCommand │
|
|
277
|
+
└─────────────────────────────────────┴─────────────────────┘
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Security Integration
|
|
283
|
+
|
|
284
|
+
These base classes integrate with django_cfg's security system:
|
|
285
|
+
|
|
286
|
+
1. **commands_security.py** - Analyzes `web_executable`, `requires_input`, `is_destructive` attributes
|
|
287
|
+
2. **commands_service.py** - Filters commands based on safety metadata
|
|
288
|
+
3. **API views** - Blocks unsafe commands from web execution
|
|
289
|
+
|
|
290
|
+
When you use these base classes, your commands are automatically categorized and protected.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Refactoring Checklist
|
|
295
|
+
|
|
296
|
+
When refactoring existing commands:
|
|
297
|
+
|
|
298
|
+
- [ ] Remove `from django_cfg.modules.django_logging import get_logger`
|
|
299
|
+
- [ ] Remove `logger = get_logger('...')` (both global and in class)
|
|
300
|
+
- [ ] Choose appropriate base class (SafeCommand, InteractiveCommand, etc)
|
|
301
|
+
- [ ] Import base class: `from django_cfg.management.utils import SafeCommand`
|
|
302
|
+
- [ ] Change inheritance: `class Command(SafeCommand):`
|
|
303
|
+
- [ ] Remove metadata attributes (if they match base class defaults)
|
|
304
|
+
- [ ] Replace `logger.info()` with `self.logger.info()`
|
|
305
|
+
- [ ] Add `command_name = '...'` if custom logger name needed
|
|
306
|
+
- [ ] Test the command
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Migration Strategy
|
|
311
|
+
|
|
312
|
+
### Find All Commands to Refactor
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# Find all commands with manual metadata
|
|
316
|
+
find . -name "*.py" -path "*/management/commands/*" -exec grep -l "web_executable" {} \;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Refactor by Priority
|
|
320
|
+
|
|
321
|
+
1. **Start with SafeCommand** - Easiest wins, most common
|
|
322
|
+
2. **Then InteractiveCommand** - Clear pattern with input()
|
|
323
|
+
3. **Then DestructiveCommand** - Important for security
|
|
324
|
+
4. **Finally AdminCommand** - Review case-by-case
|
|
325
|
+
|
|
326
|
+
### Testing
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Test command help
|
|
330
|
+
python manage.py <command> --help
|
|
331
|
+
|
|
332
|
+
# Test command execution
|
|
333
|
+
python manage.py <command>
|
|
334
|
+
|
|
335
|
+
# Test logger
|
|
336
|
+
# Should see: "Command initialized: <name> (web_executable=..., ...)"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Advanced: Override Metadata
|
|
342
|
+
|
|
343
|
+
If you need custom metadata different from base class defaults:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from django_cfg.management.utils import SafeCommand
|
|
347
|
+
|
|
348
|
+
class Command(SafeCommand):
|
|
349
|
+
# Override defaults if needed
|
|
350
|
+
web_executable = False # Make it non-web-executable
|
|
351
|
+
is_destructive = True # Mark as destructive
|
|
352
|
+
|
|
353
|
+
help = 'Special command'
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Though generally, if you need different metadata, choose a different base class.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Benefits Summary
|
|
361
|
+
|
|
362
|
+
| Feature | Before | After |
|
|
363
|
+
|---------|--------|-------|
|
|
364
|
+
| Lines of boilerplate | ~15 lines | ~3 lines |
|
|
365
|
+
| Logger setup | Manual | Automatic |
|
|
366
|
+
| Metadata | Manual | Automatic |
|
|
367
|
+
| Type safety | None | Built-in |
|
|
368
|
+
| Code clarity | Low | High |
|
|
369
|
+
| Maintenance | Hard | Easy |
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## FAQ
|
|
374
|
+
|
|
375
|
+
**Q: Can I still use `BaseCommand` directly?**
|
|
376
|
+
A: Yes, but you'll need to manually add logger and metadata.
|
|
377
|
+
|
|
378
|
+
**Q: What if I need different metadata than the base classes?**
|
|
379
|
+
A: Choose the closest base class or override the attributes you need.
|
|
380
|
+
|
|
381
|
+
**Q: Does this work with existing Django commands?**
|
|
382
|
+
A: Yes, these are just base classes that extend `BaseCommand`.
|
|
383
|
+
|
|
384
|
+
**Q: Can I use multiple inheritance?**
|
|
385
|
+
A: Not needed - each base class already has everything you need.
|
|
386
|
+
|
|
387
|
+
**Q: What about existing code using the old approach?**
|
|
388
|
+
A: Both approaches work. Refactor gradually as you touch files.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Support
|
|
393
|
+
|
|
394
|
+
For issues or questions:
|
|
395
|
+
- Check `commands_security.py` for security system details
|
|
396
|
+
- Review `commands_service.py` for filtering logic
|
|
397
|
+
- See `mixins.py` source code for implementation
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
**Last Updated:** 2025-01-04
|
|
402
|
+
**Version:** 1.0.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Management Command Utilities
|
|
3
|
+
|
|
4
|
+
Ready-to-use base classes for Django management commands.
|
|
5
|
+
|
|
6
|
+
Quick Start:
|
|
7
|
+
from django_cfg.management.utils import SafeCommand
|
|
8
|
+
|
|
9
|
+
class Command(SafeCommand):
|
|
10
|
+
help = 'My safe command'
|
|
11
|
+
|
|
12
|
+
def handle(self, *args, **options):
|
|
13
|
+
self.logger.info("Running command")
|
|
14
|
+
# Your code here
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .mixins import (
|
|
18
|
+
AdminCommand,
|
|
19
|
+
DestructiveCommand,
|
|
20
|
+
InteractiveCommand,
|
|
21
|
+
SafeCommand,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
'SafeCommand',
|
|
26
|
+
'InteractiveCommand',
|
|
27
|
+
'DestructiveCommand',
|
|
28
|
+
'AdminCommand',
|
|
29
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Management Command Base Classes
|
|
3
|
+
|
|
4
|
+
Ready-to-use base classes for Django management commands with automatic
|
|
5
|
+
logger initialization and web execution safety metadata.
|
|
6
|
+
|
|
7
|
+
Security Integration:
|
|
8
|
+
These classes integrate with django_cfg's security system via:
|
|
9
|
+
- commands_security.py: Analyzes web_executable, requires_input, is_destructive
|
|
10
|
+
- commands_service.py: Filters commands based on safety metadata
|
|
11
|
+
- API views: Blocks unsafe commands from web execution
|
|
12
|
+
|
|
13
|
+
Available Classes:
|
|
14
|
+
- SafeCommand: Read-only, web-executable commands
|
|
15
|
+
- InteractiveCommand: Commands requiring user input (blocked from web)
|
|
16
|
+
- DestructiveCommand: Commands that modify/delete data (blocked from web)
|
|
17
|
+
- AdminCommand: Administrative commands (safe for web execution)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from django.core.management.base import BaseCommand
|
|
21
|
+
|
|
22
|
+
from django_cfg.modules.django_logging import get_logger
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _BaseCommandWithMetadata(BaseCommand):
|
|
26
|
+
"""
|
|
27
|
+
Internal base class that adds logger and metadata to commands.
|
|
28
|
+
|
|
29
|
+
Do not use directly - use SafeCommand, InteractiveCommand, etc.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Default safety metadata (override in subclasses)
|
|
33
|
+
web_executable = False
|
|
34
|
+
requires_input = False
|
|
35
|
+
is_destructive = False
|
|
36
|
+
|
|
37
|
+
def __init__(self, *args, **kwargs):
|
|
38
|
+
"""Initialize command with automatic logger."""
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
# Auto-detect command name from module if not set
|
|
42
|
+
if not hasattr(self, 'command_name'):
|
|
43
|
+
self.command_name = self.__module__.split('.')[-1]
|
|
44
|
+
|
|
45
|
+
# Initialize logger
|
|
46
|
+
command_name = f"command.{self.command_name}"
|
|
47
|
+
self.logger = get_logger(command_name)
|
|
48
|
+
|
|
49
|
+
# Log initialization in debug mode
|
|
50
|
+
self.logger.debug(
|
|
51
|
+
f"Command initialized: {self.command_name} "
|
|
52
|
+
f"(web_executable={self.web_executable}, "
|
|
53
|
+
f"requires_input={self.requires_input}, "
|
|
54
|
+
f"is_destructive={self.is_destructive})"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SafeCommand(_BaseCommandWithMetadata):
|
|
59
|
+
"""
|
|
60
|
+
Base class for safe, read-only commands that can be executed via web.
|
|
61
|
+
|
|
62
|
+
Use this for commands that:
|
|
63
|
+
- Only read data (no modifications)
|
|
64
|
+
- Don't require user input
|
|
65
|
+
- Are safe to run from web interface
|
|
66
|
+
|
|
67
|
+
Examples: show_config, list_urls, check_settings
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
class Command(SafeCommand):
|
|
71
|
+
help = 'Display current configuration'
|
|
72
|
+
|
|
73
|
+
def handle(self, *args, **options):
|
|
74
|
+
self.logger.info("Showing configuration")
|
|
75
|
+
# Read-only operations here
|
|
76
|
+
|
|
77
|
+
Metadata:
|
|
78
|
+
web_executable = True
|
|
79
|
+
requires_input = False
|
|
80
|
+
is_destructive = False
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
web_executable = True
|
|
84
|
+
requires_input = False
|
|
85
|
+
is_destructive = False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InteractiveCommand(_BaseCommandWithMetadata):
|
|
89
|
+
"""
|
|
90
|
+
Base class for interactive commands requiring user input.
|
|
91
|
+
|
|
92
|
+
Use this for commands that:
|
|
93
|
+
- Require input() or questionary prompts
|
|
94
|
+
- Need user interaction
|
|
95
|
+
- Cannot run via web interface
|
|
96
|
+
|
|
97
|
+
Examples: superuser, createsuperuser
|
|
98
|
+
|
|
99
|
+
Usage:
|
|
100
|
+
class Command(InteractiveCommand):
|
|
101
|
+
help = 'Create superuser with prompts'
|
|
102
|
+
|
|
103
|
+
def handle(self, *args, **options):
|
|
104
|
+
self.logger.info("Creating superuser")
|
|
105
|
+
username = input("Username: ")
|
|
106
|
+
# Interactive operations here
|
|
107
|
+
|
|
108
|
+
Metadata:
|
|
109
|
+
web_executable = False
|
|
110
|
+
requires_input = True
|
|
111
|
+
is_destructive = False
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
web_executable = False
|
|
115
|
+
requires_input = True
|
|
116
|
+
is_destructive = False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DestructiveCommand(_BaseCommandWithMetadata):
|
|
120
|
+
"""
|
|
121
|
+
Base class for destructive commands that modify or delete data.
|
|
122
|
+
|
|
123
|
+
Use this for commands that:
|
|
124
|
+
- Delete or modify data
|
|
125
|
+
- Clear caches
|
|
126
|
+
- Perform irreversible operations
|
|
127
|
+
|
|
128
|
+
Examples: clear_constance, flush, sqlflush
|
|
129
|
+
|
|
130
|
+
Usage:
|
|
131
|
+
class Command(DestructiveCommand):
|
|
132
|
+
help = 'Clear all cache data'
|
|
133
|
+
|
|
134
|
+
def handle(self, *args, **options):
|
|
135
|
+
self.logger.warning("Clearing cache - destructive operation")
|
|
136
|
+
# Destructive operations here
|
|
137
|
+
|
|
138
|
+
Metadata:
|
|
139
|
+
web_executable = False
|
|
140
|
+
requires_input = True
|
|
141
|
+
is_destructive = True
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
web_executable = False
|
|
145
|
+
requires_input = True
|
|
146
|
+
is_destructive = True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class AdminCommand(_BaseCommandWithMetadata):
|
|
150
|
+
"""
|
|
151
|
+
Base class for administrative commands safe for web execution.
|
|
152
|
+
|
|
153
|
+
Use this for commands that:
|
|
154
|
+
- Perform administrative tasks
|
|
155
|
+
- Are safe despite needing privileges
|
|
156
|
+
- Can be run via web interface
|
|
157
|
+
|
|
158
|
+
Examples: migrate, collectstatic, createcachetable
|
|
159
|
+
|
|
160
|
+
Usage:
|
|
161
|
+
class Command(AdminCommand):
|
|
162
|
+
help = 'Run database migrations'
|
|
163
|
+
|
|
164
|
+
def handle(self, *args, **options):
|
|
165
|
+
self.logger.info("Running migrations")
|
|
166
|
+
# Admin operations here
|
|
167
|
+
|
|
168
|
+
Metadata:
|
|
169
|
+
web_executable = True
|
|
170
|
+
requires_input = False
|
|
171
|
+
is_destructive = False
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
web_executable = True
|
|
175
|
+
requires_input = False
|
|
176
|
+
is_destructive = False
|