django-cfg 1.5.29__py3-none-any.whl → 1.5.31__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/integrations/centrifugo/services/logging.py +49 -20
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +16 -6
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +16 -6
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +2 -1
- django_cfg/apps/integrations/grpc/services/discovery/registry.py +40 -39
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +12 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +22 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja +133 -0
- django_cfg/modules/django_client/urls.py +38 -5
- django_cfg/modules/django_twilio/email_otp.py +3 -1
- django_cfg/modules/django_twilio/sms.py +3 -1
- django_cfg/modules/django_twilio/unified.py +6 -2
- django_cfg/modules/django_twilio/whatsapp.py +3 -1
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +17 -18
- {django_cfg-1.5.29.dist-info → django_cfg-1.5.31.dist-info}/METADATA +1 -1
- {django_cfg-1.5.29.dist-info → django_cfg-1.5.31.dist-info}/RECORD +24 -23
- {django_cfg-1.5.29.dist-info → django_cfg-1.5.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.29.dist-info → django_cfg-1.5.31.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.29.dist-info → django_cfg-1.5.31.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -8,6 +8,7 @@ Mirrors RPCLogger patterns from legacy WebSocket solution for easy migration.
|
|
|
8
8
|
import time
|
|
9
9
|
from typing import Any, Optional
|
|
10
10
|
|
|
11
|
+
from django.utils import timezone
|
|
11
12
|
from django_cfg.modules.django_logging import get_logger
|
|
12
13
|
|
|
13
14
|
logger = get_logger("centrifugo")
|
|
@@ -87,11 +88,11 @@ class CentrifugoLogger:
|
|
|
87
88
|
|
|
88
89
|
logger.info(f"✅ Creating CentrifugoLog entry for {message_id} (async)")
|
|
89
90
|
try:
|
|
90
|
-
from asgiref.sync import sync_to_async
|
|
91
91
|
from ..models import CentrifugoLog
|
|
92
92
|
|
|
93
|
-
#
|
|
94
|
-
|
|
93
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
94
|
+
# This prevents connection leaks from sync_to_async threads
|
|
95
|
+
log_entry = await CentrifugoLog.objects.acreate(
|
|
95
96
|
message_id=message_id,
|
|
96
97
|
channel=channel,
|
|
97
98
|
data=data,
|
|
@@ -234,14 +235,17 @@ class CentrifugoLogger:
|
|
|
234
235
|
return
|
|
235
236
|
|
|
236
237
|
try:
|
|
237
|
-
from asgiref.sync import sync_to_async
|
|
238
238
|
from ..models import CentrifugoLog
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
241
|
+
log_entry.status = CentrifugoLog.StatusChoices.SUCCESS
|
|
242
|
+
log_entry.acks_received = acks_received
|
|
243
|
+
log_entry.completed_at = timezone.now()
|
|
244
|
+
|
|
245
|
+
if duration_ms is not None:
|
|
246
|
+
log_entry.duration_ms = duration_ms
|
|
247
|
+
|
|
248
|
+
await log_entry.asave(update_fields=["status", "acks_received", "completed_at", "duration_ms"])
|
|
245
249
|
|
|
246
250
|
logger.info(
|
|
247
251
|
f"Centrifugo publish successful: {log_entry.message_id}",
|
|
@@ -420,14 +424,25 @@ class CentrifugoLogger:
|
|
|
420
424
|
return
|
|
421
425
|
|
|
422
426
|
try:
|
|
423
|
-
from asgiref.sync import sync_to_async
|
|
424
427
|
from ..models import CentrifugoLog
|
|
425
428
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
430
|
+
log_entry.status = CentrifugoLog.StatusChoices.FAILED
|
|
431
|
+
log_entry.error_code = error_code
|
|
432
|
+
log_entry.error_message = error_message
|
|
433
|
+
log_entry.completed_at = timezone.now()
|
|
434
|
+
|
|
435
|
+
if duration_ms is not None:
|
|
436
|
+
log_entry.duration_ms = duration_ms
|
|
437
|
+
|
|
438
|
+
await log_entry.asave(
|
|
439
|
+
update_fields=[
|
|
440
|
+
"status",
|
|
441
|
+
"error_code",
|
|
442
|
+
"error_message",
|
|
443
|
+
"completed_at",
|
|
444
|
+
"duration_ms",
|
|
445
|
+
]
|
|
431
446
|
)
|
|
432
447
|
|
|
433
448
|
logger.error(
|
|
@@ -465,13 +480,27 @@ class CentrifugoLogger:
|
|
|
465
480
|
return
|
|
466
481
|
|
|
467
482
|
try:
|
|
468
|
-
from asgiref.sync import sync_to_async
|
|
469
483
|
from ..models import CentrifugoLog
|
|
470
484
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
485
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
486
|
+
log_entry.status = CentrifugoLog.StatusChoices.TIMEOUT
|
|
487
|
+
log_entry.acks_received = acks_received
|
|
488
|
+
log_entry.error_code = "timeout"
|
|
489
|
+
log_entry.error_message = f"Timeout after {log_entry.ack_timeout}s"
|
|
490
|
+
log_entry.completed_at = timezone.now()
|
|
491
|
+
|
|
492
|
+
if duration_ms is not None:
|
|
493
|
+
log_entry.duration_ms = duration_ms
|
|
494
|
+
|
|
495
|
+
await log_entry.asave(
|
|
496
|
+
update_fields=[
|
|
497
|
+
"status",
|
|
498
|
+
"acks_received",
|
|
499
|
+
"error_code",
|
|
500
|
+
"error_message",
|
|
501
|
+
"completed_at",
|
|
502
|
+
"duration_ms",
|
|
503
|
+
]
|
|
475
504
|
)
|
|
476
505
|
|
|
477
506
|
logger.warning(
|
|
@@ -12,6 +12,7 @@ from typing import Any, Dict
|
|
|
12
12
|
import httpx
|
|
13
13
|
import jwt
|
|
14
14
|
from django.conf import settings
|
|
15
|
+
from django.utils import timezone
|
|
15
16
|
from django_cfg.modules.django_logging import get_logger
|
|
16
17
|
from drf_spectacular.utils import extend_schema
|
|
17
18
|
from pydantic import BaseModel, Field
|
|
@@ -284,14 +285,23 @@ class CentrifugoTestingAPIViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
284
285
|
|
|
285
286
|
# Mark as failed
|
|
286
287
|
if log_entry:
|
|
287
|
-
from asgiref.sync import sync_to_async
|
|
288
288
|
from ..models import CentrifugoLog
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
290
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
291
|
+
log_entry.status = CentrifugoLog.StatusChoices.FAILED
|
|
292
|
+
log_entry.error_code = type(e).__name__
|
|
293
|
+
log_entry.error_message = str(e)
|
|
294
|
+
log_entry.completed_at = timezone.now()
|
|
295
|
+
log_entry.duration_ms = duration_ms
|
|
296
|
+
|
|
297
|
+
await log_entry.asave(
|
|
298
|
+
update_fields=[
|
|
299
|
+
"status",
|
|
300
|
+
"error_code",
|
|
301
|
+
"error_message",
|
|
302
|
+
"completed_at",
|
|
303
|
+
"duration_ms",
|
|
304
|
+
]
|
|
295
305
|
)
|
|
296
306
|
|
|
297
307
|
raise
|
|
@@ -12,6 +12,7 @@ from typing import Any, Dict
|
|
|
12
12
|
import httpx
|
|
13
13
|
from django.db import transaction
|
|
14
14
|
from django.http import JsonResponse
|
|
15
|
+
from django.utils import timezone
|
|
15
16
|
from django.utils.decorators import method_decorator
|
|
16
17
|
from django.views import View
|
|
17
18
|
from django.views.decorators.csrf import csrf_exempt
|
|
@@ -173,14 +174,23 @@ class PublishWrapperView(View):
|
|
|
173
174
|
|
|
174
175
|
# Mark as failed
|
|
175
176
|
if log_entry:
|
|
176
|
-
from asgiref.sync import sync_to_async
|
|
177
177
|
from ..models import CentrifugoLog
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
180
|
+
log_entry.status = CentrifugoLog.StatusChoices.FAILED
|
|
181
|
+
log_entry.error_code = type(e).__name__
|
|
182
|
+
log_entry.error_message = str(e)
|
|
183
|
+
log_entry.completed_at = timezone.now()
|
|
184
|
+
log_entry.duration_ms = duration_ms
|
|
185
|
+
|
|
186
|
+
await log_entry.asave(
|
|
187
|
+
update_fields=[
|
|
188
|
+
"status",
|
|
189
|
+
"error_code",
|
|
190
|
+
"error_message",
|
|
191
|
+
"completed_at",
|
|
192
|
+
"duration_ms",
|
|
193
|
+
]
|
|
184
194
|
)
|
|
185
195
|
|
|
186
196
|
raise
|
|
@@ -781,7 +781,8 @@ class Command(BaseCommand):
|
|
|
781
781
|
# Mark server as stopping (sync context - signal handlers are sync)
|
|
782
782
|
if server_status:
|
|
783
783
|
try:
|
|
784
|
-
|
|
784
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
785
|
+
asyncio.create_task(server_status.amark_stopping())
|
|
785
786
|
except Exception as e:
|
|
786
787
|
self.logger.warning(f"Could not mark server as stopping: {e}")
|
|
787
788
|
|
|
@@ -181,12 +181,29 @@ class ServiceRegistryManager:
|
|
|
181
181
|
>>> stats['success_rate']
|
|
182
182
|
96.67
|
|
183
183
|
"""
|
|
184
|
-
# Django 5.2
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
# Django 5.2+ async ORM: Use native async aggregate
|
|
185
|
+
stats = await (
|
|
186
|
+
GRPCRequestLog.objects.filter(service_name=service_name)
|
|
187
|
+
.recent(hours)
|
|
188
|
+
.aaggregate(
|
|
189
|
+
total=Count("id"),
|
|
190
|
+
successful=Count("id", filter=models.Q(status="success")),
|
|
191
|
+
errors=Count("id", filter=models.Q(status="error")),
|
|
192
|
+
avg_duration=Avg("duration_ms"),
|
|
193
|
+
)
|
|
194
|
+
)
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
total = stats["total"] or 0
|
|
197
|
+
successful = stats["successful"] or 0
|
|
198
|
+
success_rate = (successful / total * 100) if total > 0 else 0.0
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"total": total,
|
|
202
|
+
"successful": successful,
|
|
203
|
+
"errors": stats["errors"] or 0,
|
|
204
|
+
"success_rate": round(success_rate, 2),
|
|
205
|
+
"avg_duration_ms": round(stats["avg_duration"] or 0, 2),
|
|
206
|
+
}
|
|
190
207
|
|
|
191
208
|
def get_all_services_with_stats(self, hours: int = 24) -> List[Dict]:
|
|
192
209
|
"""
|
|
@@ -274,16 +291,15 @@ class ServiceRegistryManager:
|
|
|
274
291
|
services = self.get_all_services()
|
|
275
292
|
services_with_stats = []
|
|
276
293
|
|
|
277
|
-
# Django 5.2: Use
|
|
278
|
-
|
|
294
|
+
# Django 5.2+ async ORM: Use native async aggregate
|
|
295
|
+
for service in services:
|
|
296
|
+
service_name = service.get("name")
|
|
279
297
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def get_service_stats_sync(service_name: str):
|
|
283
|
-
return (
|
|
298
|
+
# Get stats from GRPCRequestLog using native async aggregate
|
|
299
|
+
stats = await (
|
|
284
300
|
GRPCRequestLog.objects.filter(service_name=service_name)
|
|
285
301
|
.recent(hours)
|
|
286
|
-
.
|
|
302
|
+
.aaggregate(
|
|
287
303
|
total=Count("id"),
|
|
288
304
|
successful=Count("id", filter=models.Q(status="success")),
|
|
289
305
|
avg_duration=Avg("duration_ms"),
|
|
@@ -291,12 +307,6 @@ class ServiceRegistryManager:
|
|
|
291
307
|
)
|
|
292
308
|
)
|
|
293
309
|
|
|
294
|
-
for service in services:
|
|
295
|
-
service_name = service.get("name")
|
|
296
|
-
|
|
297
|
-
# Get stats from GRPCRequestLog (async)
|
|
298
|
-
stats = await get_service_stats_sync(service_name)
|
|
299
|
-
|
|
300
310
|
# Calculate success rate
|
|
301
311
|
total = stats["total"] or 0
|
|
302
312
|
successful = stats["successful"] or 0
|
|
@@ -429,39 +439,30 @@ class ServiceRegistryManager:
|
|
|
429
439
|
if not service:
|
|
430
440
|
return []
|
|
431
441
|
|
|
432
|
-
# Django 5.2: Use
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
442
|
+
# Django 5.2+ async ORM: Use native async operations
|
|
443
|
+
methods_list = []
|
|
444
|
+
for method_name in service.get("methods", []):
|
|
445
|
+
# Get durations for percentile calculation using async list comprehension
|
|
446
|
+
durations = [
|
|
447
|
+
duration async for duration in
|
|
438
448
|
GRPCRequestLog.objects.filter(
|
|
439
|
-
service_name=
|
|
449
|
+
service_name=service_name,
|
|
440
450
|
method_name=method_name,
|
|
441
451
|
duration_ms__isnull=False,
|
|
442
452
|
).values_list("duration_ms", flat=True)
|
|
443
|
-
|
|
453
|
+
]
|
|
444
454
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
service_name=svc_name,
|
|
455
|
+
# Get aggregate stats using native async aggregate
|
|
456
|
+
stats = await GRPCRequestLog.objects.filter(
|
|
457
|
+
service_name=service_name,
|
|
449
458
|
method_name=method_name,
|
|
450
|
-
).
|
|
459
|
+
).aaggregate(
|
|
451
460
|
total=Count("id"),
|
|
452
461
|
successful=Count("id", filter=models.Q(status="success")),
|
|
453
462
|
errors=Count("id", filter=models.Q(status="error")),
|
|
454
463
|
avg_duration=Avg("duration_ms"),
|
|
455
464
|
)
|
|
456
465
|
|
|
457
|
-
methods_list = []
|
|
458
|
-
for method_name in service.get("methods", []):
|
|
459
|
-
# Get durations for percentile calculation (async)
|
|
460
|
-
durations = await get_method_durations(service_name, method_name)
|
|
461
|
-
|
|
462
|
-
# Get aggregate stats (async)
|
|
463
|
-
stats = await get_method_stats(service_name, method_name)
|
|
464
|
-
|
|
465
466
|
# Calculate percentiles
|
|
466
467
|
p50, p95, p99 = self._calculate_percentiles(durations)
|
|
467
468
|
|
|
@@ -143,6 +143,18 @@ class FilesGenerator:
|
|
|
143
143
|
description="Retry utilities with p-retry",
|
|
144
144
|
)
|
|
145
145
|
|
|
146
|
+
def generate_validation_events_file(self):
|
|
147
|
+
"""Generate validation-events.ts with browser CustomEvent integration."""
|
|
148
|
+
|
|
149
|
+
template = self.jinja_env.get_template('utils/validation-events.ts.jinja')
|
|
150
|
+
content = template.render()
|
|
151
|
+
|
|
152
|
+
return GeneratedFile(
|
|
153
|
+
path="validation-events.ts",
|
|
154
|
+
content=content,
|
|
155
|
+
description="Zod validation error events for browser integration",
|
|
156
|
+
)
|
|
157
|
+
|
|
146
158
|
def generate_api_instance_file(self):
|
|
147
159
|
"""Generate api-instance.ts with global singleton."""
|
|
148
160
|
|
|
@@ -98,6 +98,10 @@ class TypeScriptGenerator(BaseGenerator):
|
|
|
98
98
|
# Generate retry.ts with p-retry
|
|
99
99
|
files.append(self.files_gen.generate_retry_file())
|
|
100
100
|
|
|
101
|
+
# Generate validation-events.ts (browser CustomEvent for Zod errors)
|
|
102
|
+
if self.generate_zod_schemas:
|
|
103
|
+
files.append(self.files_gen.generate_validation_events_file())
|
|
104
|
+
|
|
101
105
|
# Generate api-instance.ts singleton (needed for fetchers/hooks)
|
|
102
106
|
if self.generate_fetchers:
|
|
103
107
|
files.append(self.files_gen.generate_api_instance_file())
|
|
@@ -146,6 +150,10 @@ class TypeScriptGenerator(BaseGenerator):
|
|
|
146
150
|
# Generate retry.ts with p-retry
|
|
147
151
|
files.append(self.files_gen.generate_retry_file())
|
|
148
152
|
|
|
153
|
+
# Generate validation-events.ts (browser CustomEvent for Zod errors)
|
|
154
|
+
if self.generate_zod_schemas:
|
|
155
|
+
files.append(self.files_gen.generate_validation_events_file())
|
|
156
|
+
|
|
149
157
|
# Generate api-instance.ts singleton (needed for fetchers/hooks)
|
|
150
158
|
if self.generate_fetchers:
|
|
151
159
|
files.append(self.files_gen.generate_api_instance_file())
|
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja
CHANGED
|
@@ -43,6 +43,28 @@ export async function {{ func_name }}(
|
|
|
43
43
|
|
|
44
44
|
consola.error('Response data:', response);
|
|
45
45
|
|
|
46
|
+
// Dispatch browser CustomEvent (only if window is defined)
|
|
47
|
+
if (typeof window !== 'undefined' && error instanceof Error && 'issues' in error) {
|
|
48
|
+
try {
|
|
49
|
+
const event = new CustomEvent('zod-validation-error', {
|
|
50
|
+
detail: {
|
|
51
|
+
operation: '{{ func_name }}',
|
|
52
|
+
path: '{{ operation.path }}',
|
|
53
|
+
method: '{{ operation.http_method }}',
|
|
54
|
+
error: error,
|
|
55
|
+
response: response,
|
|
56
|
+
timestamp: new Date(),
|
|
57
|
+
},
|
|
58
|
+
bubbles: true,
|
|
59
|
+
cancelable: false,
|
|
60
|
+
});
|
|
61
|
+
window.dispatchEvent(event);
|
|
62
|
+
} catch (eventError) {
|
|
63
|
+
// Silently fail - event dispatch should never crash the app
|
|
64
|
+
consola.warn('Failed to dispatch validation error event:', eventError);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
// Re-throw the error
|
|
47
69
|
throw error;
|
|
48
70
|
}
|
|
@@ -55,6 +55,10 @@ export * as Enums from "./enums";
|
|
|
55
55
|
|
|
56
56
|
// Re-export Zod schemas for runtime validation
|
|
57
57
|
export * as Schemas from "./_utils/schemas";
|
|
58
|
+
|
|
59
|
+
// Re-export Zod validation events for browser integration
|
|
60
|
+
export type { ValidationErrorDetail, ValidationErrorEvent } from "./validation-events";
|
|
61
|
+
export { dispatchValidationError, onValidationError, formatZodError } from "./validation-events";
|
|
58
62
|
{% endif %}
|
|
59
63
|
{% if generate_fetchers %}
|
|
60
64
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Validation Events - Browser CustomEvent integration
|
|
3
|
+
*
|
|
4
|
+
* Dispatches browser CustomEvents when Zod validation fails, allowing
|
|
5
|
+
* React/frontend apps to listen and handle validation errors globally.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // In your React app
|
|
10
|
+
* window.addEventListener('zod-validation-error', (event) => {
|
|
11
|
+
* const { operation, path, method, error, response } = event.detail;
|
|
12
|
+
* console.error(`Validation failed for ${method} ${path}`, error);
|
|
13
|
+
* // Show toast notification, log to Sentry, etc.
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ZodError } from 'zod'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation error event detail
|
|
22
|
+
*/
|
|
23
|
+
export interface ValidationErrorDetail {
|
|
24
|
+
/** Operation/function name that failed validation */
|
|
25
|
+
operation: string
|
|
26
|
+
/** API endpoint path */
|
|
27
|
+
path: string
|
|
28
|
+
/** HTTP method */
|
|
29
|
+
method: string
|
|
30
|
+
/** Zod validation error */
|
|
31
|
+
error: ZodError
|
|
32
|
+
/** Raw response data that failed validation */
|
|
33
|
+
response: any
|
|
34
|
+
/** Timestamp of the error */
|
|
35
|
+
timestamp: Date
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Custom event type for Zod validation errors
|
|
40
|
+
*/
|
|
41
|
+
export type ValidationErrorEvent = CustomEvent<ValidationErrorDetail>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Dispatch a Zod validation error event.
|
|
45
|
+
*
|
|
46
|
+
* Only dispatches in browser environment (when window is defined).
|
|
47
|
+
* Safe to call in Node.js/SSR - will be a no-op.
|
|
48
|
+
*
|
|
49
|
+
* @param detail - Validation error details
|
|
50
|
+
*/
|
|
51
|
+
export function dispatchValidationError(detail: ValidationErrorDetail): void {
|
|
52
|
+
// Check if running in browser
|
|
53
|
+
if (typeof window === 'undefined') {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const event = new CustomEvent<ValidationErrorDetail>('zod-validation-error', {
|
|
59
|
+
detail,
|
|
60
|
+
bubbles: true,
|
|
61
|
+
cancelable: false,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
window.dispatchEvent(event)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// Silently fail - validation event dispatch should never crash the app
|
|
67
|
+
console.warn('Failed to dispatch validation error event:', error)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Add a global listener for Zod validation errors.
|
|
73
|
+
*
|
|
74
|
+
* @param callback - Function to call when validation error occurs
|
|
75
|
+
* @returns Cleanup function to remove the listener
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const cleanup = onValidationError(({ operation, error }) => {
|
|
80
|
+
* toast.error(`Validation failed in ${operation}`);
|
|
81
|
+
* logToSentry(error);
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // Later, remove listener
|
|
85
|
+
* cleanup();
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function onValidationError(
|
|
89
|
+
callback: (detail: ValidationErrorDetail) => void
|
|
90
|
+
): () => void {
|
|
91
|
+
if (typeof window === 'undefined') {
|
|
92
|
+
// Return no-op cleanup function for SSR
|
|
93
|
+
return () => {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const handler = (event: Event) => {
|
|
97
|
+
if (event instanceof CustomEvent) {
|
|
98
|
+
callback(event.detail)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
window.addEventListener('zod-validation-error', handler)
|
|
103
|
+
|
|
104
|
+
// Return cleanup function
|
|
105
|
+
return () => {
|
|
106
|
+
window.removeEventListener('zod-validation-error', handler)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format Zod error for logging/display.
|
|
112
|
+
*
|
|
113
|
+
* @param error - Zod validation error
|
|
114
|
+
* @returns Formatted error message
|
|
115
|
+
*/
|
|
116
|
+
export function formatZodError(error: ZodError): string {
|
|
117
|
+
const issues = error.issues.map((issue, index) => {
|
|
118
|
+
const path = issue.path.join('.') || 'root'
|
|
119
|
+
const parts = [`${index + 1}. ${path}: ${issue.message}`]
|
|
120
|
+
|
|
121
|
+
if ('expected' in issue && issue.expected) {
|
|
122
|
+
parts.push(` Expected: ${issue.expected}`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if ('received' in issue && issue.received) {
|
|
126
|
+
parts.push(` Received: ${issue.received}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return parts.join('\n')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return issues.join('\n')
|
|
133
|
+
}
|
|
@@ -63,11 +63,44 @@ def get_openapi_urls() -> List[Any]:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
# Export urlpatterns for django.urls.include()
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
# CRITICAL: Use lazy evaluation to avoid importing DRF/drf-spectacular
|
|
67
|
+
# before Django settings are fully loaded. This prevents api_settings
|
|
68
|
+
# from being cached with wrong DEFAULT_SCHEMA_CLASS value.
|
|
69
|
+
class LazyURLPatterns:
|
|
70
|
+
"""Lazy URLpatterns that only initialize when accessed."""
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self._patterns = None
|
|
74
|
+
|
|
75
|
+
def _get_patterns(self):
|
|
76
|
+
if self._patterns is None:
|
|
77
|
+
if _is_django_configured():
|
|
78
|
+
self._patterns = get_openapi_urls()
|
|
79
|
+
else:
|
|
80
|
+
self._patterns = []
|
|
81
|
+
return self._patterns
|
|
82
|
+
|
|
83
|
+
def __iter__(self):
|
|
84
|
+
return iter(self._get_patterns())
|
|
85
|
+
|
|
86
|
+
def __getitem__(self, index):
|
|
87
|
+
return self._get_patterns()[index]
|
|
88
|
+
|
|
89
|
+
def __len__(self):
|
|
90
|
+
return len(self._get_patterns())
|
|
91
|
+
|
|
92
|
+
def clear(self):
|
|
93
|
+
"""Clear all patterns."""
|
|
94
|
+
patterns = self._get_patterns()
|
|
95
|
+
patterns.clear()
|
|
96
|
+
|
|
97
|
+
def extend(self, items):
|
|
98
|
+
"""Extend patterns with new items."""
|
|
99
|
+
patterns = self._get_patterns()
|
|
100
|
+
patterns.extend(items)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
urlpatterns = LazyURLPatterns()
|
|
71
104
|
|
|
72
105
|
|
|
73
106
|
__all__ = ["get_openapi_urls", "urlpatterns"]
|
|
@@ -111,7 +111,9 @@ class EmailOTPService(BaseTwilioService):
|
|
|
111
111
|
template_data: Optional[Dict[str, Any]] = None
|
|
112
112
|
) -> Tuple[bool, str, str]:
|
|
113
113
|
"""Async version of send_otp."""
|
|
114
|
-
|
|
114
|
+
# sync_to_async is appropriate here for external SendGrid API calls (not Django ORM)
|
|
115
|
+
# thread_sensitive=False for better performance since no database access occurs
|
|
116
|
+
return await sync_to_async(self.send_otp, thread_sensitive=False)(email, subject, template_data)
|
|
115
117
|
|
|
116
118
|
def _send_template_email(
|
|
117
119
|
self,
|
|
@@ -87,7 +87,9 @@ class SMSOTPService(BaseTwilioService):
|
|
|
87
87
|
|
|
88
88
|
async def asend_otp(self, phone_number: str) -> Tuple[bool, str]:
|
|
89
89
|
"""Async version of send_otp."""
|
|
90
|
-
|
|
90
|
+
# sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
|
|
91
|
+
# thread_sensitive=False for better performance since no database access occurs
|
|
92
|
+
return await sync_to_async(self.send_otp, thread_sensitive=False)(phone_number)
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
__all__ = [
|
|
@@ -105,7 +105,9 @@ class UnifiedOTPService(BaseTwilioService):
|
|
|
105
105
|
enable_fallback: bool = True
|
|
106
106
|
) -> Tuple[bool, str, TwilioChannelType]:
|
|
107
107
|
"""Async version of send_otp."""
|
|
108
|
-
|
|
108
|
+
# sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
|
|
109
|
+
# thread_sensitive=False for better performance since no database access occurs
|
|
110
|
+
return await sync_to_async(self.send_otp, thread_sensitive=False)(identifier, preferred_channel, enable_fallback)
|
|
109
111
|
|
|
110
112
|
def verify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
|
|
111
113
|
"""
|
|
@@ -129,7 +131,9 @@ class UnifiedOTPService(BaseTwilioService):
|
|
|
129
131
|
|
|
130
132
|
async def averify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
|
|
131
133
|
"""Async version of verify_otp."""
|
|
132
|
-
|
|
134
|
+
# sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
|
|
135
|
+
# thread_sensitive=False for better performance since no database access occurs
|
|
136
|
+
return await sync_to_async(self.verify_otp, thread_sensitive=False)(identifier, code)
|
|
133
137
|
|
|
134
138
|
def _get_available_channels(self, is_email: bool, config: TwilioConfig) -> List[TwilioChannelType]:
|
|
135
139
|
"""Get list of available channels based on configuration."""
|
|
@@ -101,7 +101,9 @@ class WhatsAppOTPService(BaseTwilioService):
|
|
|
101
101
|
|
|
102
102
|
async def asend_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
|
|
103
103
|
"""Async version of send_otp."""
|
|
104
|
-
|
|
104
|
+
# sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
|
|
105
|
+
# thread_sensitive=False for better performance since no database access occurs
|
|
106
|
+
return await sync_to_async(self.send_otp, thread_sensitive=False)(phone_number, fallback_to_sms)
|
|
105
107
|
|
|
106
108
|
def _send_sms_otp(self, phone_number: str, client: Client, verify_config: TwilioVerifyConfig) -> Tuple[bool, str]:
|
|
107
109
|
"""Internal SMS fallback method."""
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.31"
|
|
8
8
|
description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
|
|
Binary file
|
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
const authToken = localStorage.getItem('auth_token');
|
|
425
425
|
const refreshToken = localStorage.getItem('refresh_token');
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
console.log('[Django-CFG] sendAuth: authToken:', authToken ? authToken.substring(0, 20) + '...' : 'null', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
|
|
428
428
|
|
|
429
429
|
if (!authToken) {
|
|
430
430
|
console.warn('[Django-CFG] sendAuth: No auth token found in localStorage');
|
|
@@ -434,12 +434,12 @@
|
|
|
434
434
|
try {
|
|
435
435
|
// Use '*' in dev mode to handle localhost vs 127.0.0.1 mismatch
|
|
436
436
|
const targetOrigin = '*'; // In production, use specific origin for security
|
|
437
|
-
|
|
437
|
+
console.log('[Django-CFG] Sending parent-auth message to iframe:', this.iframe.id);
|
|
438
438
|
this.iframe.contentWindow.postMessage({
|
|
439
439
|
type: 'parent-auth',
|
|
440
440
|
data: { authToken, refreshToken }
|
|
441
441
|
}, targetOrigin);
|
|
442
|
-
|
|
442
|
+
console.log('[Django-CFG] parent-auth message sent successfully');
|
|
443
443
|
} catch (e) {
|
|
444
444
|
console.error('[Django-CFG] Failed to send auth:', e);
|
|
445
445
|
}
|
|
@@ -449,10 +449,10 @@
|
|
|
449
449
|
* Send all data (theme + auth)
|
|
450
450
|
*/
|
|
451
451
|
sendAllData() {
|
|
452
|
-
|
|
452
|
+
console.log('[Django-CFG] sendAllData called');
|
|
453
453
|
this.sendTheme();
|
|
454
454
|
this.sendAuth();
|
|
455
|
-
|
|
455
|
+
console.log('[Django-CFG] sendAllData completed');
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
/**
|
|
@@ -526,15 +526,15 @@
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
handleMessage(type, data) {
|
|
529
|
-
|
|
529
|
+
console.log('[Django-CFG] Received message from iframe:', type, data);
|
|
530
530
|
switch (type) {
|
|
531
531
|
case 'iframe-ready':
|
|
532
|
-
|
|
532
|
+
console.log('[Django-CFG] iframe-ready received, sending auth and theme data');
|
|
533
533
|
this.messageBridge.sendAllData();
|
|
534
534
|
break;
|
|
535
535
|
|
|
536
536
|
case 'iframe-auth-status':
|
|
537
|
-
|
|
537
|
+
console.log('[Django-CFG] iframe-auth-status:', data);
|
|
538
538
|
break;
|
|
539
539
|
|
|
540
540
|
case 'iframe-navigation':
|
|
@@ -640,26 +640,25 @@
|
|
|
640
640
|
* Setup ONE global message listener for ALL iframes
|
|
641
641
|
*/
|
|
642
642
|
_setupGlobalMessageListener() {
|
|
643
|
-
|
|
643
|
+
console.log('[Django-CFG] Setting up global message listener');
|
|
644
644
|
window.addEventListener('message', (event) => {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// });
|
|
645
|
+
console.log('[Django-CFG] Received message:', {
|
|
646
|
+
type: event.data?.type,
|
|
647
|
+
origin: event.origin,
|
|
648
|
+
source: event.source === window ? 'self' : 'iframe',
|
|
649
|
+
mapSize: this.iframeMap.size,
|
|
650
|
+
});
|
|
652
651
|
|
|
653
652
|
// Find which handler should handle this message
|
|
654
653
|
const handler = this.iframeMap.get(event.source);
|
|
655
654
|
|
|
656
655
|
if (handler) {
|
|
657
|
-
|
|
656
|
+
console.log('[Django-CFG] Routing message to handler:', handler.iframe.id);
|
|
658
657
|
const { type, data } = event.data || {};
|
|
659
658
|
handler.handleMessage(type, data);
|
|
660
659
|
} else {
|
|
661
660
|
// Message from unknown source (browser extension, etc.)
|
|
662
|
-
|
|
661
|
+
console.log('[Django-CFG] Ignoring message from unknown source, origin:', event.origin);
|
|
663
662
|
}
|
|
664
663
|
});
|
|
665
664
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.31
|
|
4
4
|
Summary: Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=D5Xv2whEAdJnyt2NXLAkpDOQ700x6HQ92QzLkYJRwUs,1822
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=ZqvSbFje8LJS5NtXTZsnUErgcUSCHa251LNfjEatZtY,2152
|
|
5
5
|
django_cfg/requirements.txt,sha256=NQjf3cPCjzPMaGFewhJxGNHnUdE9gE8yTXDEBIt0vuA,2682
|
|
@@ -426,7 +426,7 @@ django_cfg/apps/integrations/centrifugo/serializers/stats.py,sha256=OAoulxUY_8Lh
|
|
|
426
426
|
django_cfg/apps/integrations/centrifugo/services/__init__.py,sha256=q0Dzm_gIrKcCgCrRAPLxKY-n_FZUWHtrKMCgZ110ZVg,507
|
|
427
427
|
django_cfg/apps/integrations/centrifugo/services/config_helper.py,sha256=d2eT8AczcCRl-v2OqZUu0n-MvSt42GWJOD7tJTHKiSg,1583
|
|
428
428
|
django_cfg/apps/integrations/centrifugo/services/dashboard_notifier.py,sha256=uagZMbykw4GjX-mqlekCYjR6tgLMifWe2c9mwIjYrLU,4793
|
|
429
|
-
django_cfg/apps/integrations/centrifugo/services/logging.py,sha256=
|
|
429
|
+
django_cfg/apps/integrations/centrifugo/services/logging.py,sha256=_A07mXEz0X5hvtQUOFq5H-aBJbbvhQri9ceMutBz8WU,24434
|
|
430
430
|
django_cfg/apps/integrations/centrifugo/services/publisher.py,sha256=9p2vF03yJWZQOrMMpWFwmv29azsTIAI31it7ac37giU,11232
|
|
431
431
|
django_cfg/apps/integrations/centrifugo/services/token_generator.py,sha256=hjNKRg5neOYv1LytjrpOQBsBiBPx4wcPrbbglTJieh8,3375
|
|
432
432
|
django_cfg/apps/integrations/centrifugo/services/client/__init__.py,sha256=IH3RKQTnahybxlFp20X189A5MzWs-kxTnSSoN0Bt8pQ,1003
|
|
@@ -437,9 +437,9 @@ django_cfg/apps/integrations/centrifugo/services/client/exceptions.py,sha256=VsY
|
|
|
437
437
|
django_cfg/apps/integrations/centrifugo/views/__init__.py,sha256=ieaKBQSQeOi57BS3CUJltoqLDeAIgcixkT9YzsGK9c4,370
|
|
438
438
|
django_cfg/apps/integrations/centrifugo/views/admin_api.py,sha256=BBxyJo3c7ROivU191gjI7kiJojC4HwMTQsS3td7NSXI,13855
|
|
439
439
|
django_cfg/apps/integrations/centrifugo/views/monitoring.py,sha256=UM9tdgeFpcXYai3dYpf6lJT4jq8DkrT1OMjiuiTCjBE,13756
|
|
440
|
-
django_cfg/apps/integrations/centrifugo/views/testing_api.py,sha256=
|
|
440
|
+
django_cfg/apps/integrations/centrifugo/views/testing_api.py,sha256=HzLplNbKGJHyztkBseVJ9FIGE6fVLGu2E1WCmEvBlLI,14462
|
|
441
441
|
django_cfg/apps/integrations/centrifugo/views/token_api.py,sha256=j5_9DALkFzzOmiC_JTdIlppra67WzJq-E_OAzaxWeqQ,3499
|
|
442
|
-
django_cfg/apps/integrations/centrifugo/views/wrapper.py,sha256=
|
|
442
|
+
django_cfg/apps/integrations/centrifugo/views/wrapper.py,sha256=sf14OqS2VoJFJGsptGhcASP0hbLujgYNJLN57inp4GQ,9005
|
|
443
443
|
django_cfg/apps/integrations/grpc/__init__.py,sha256=mzsALD2x44rGQNNh3mIUFi7flQj6PvS1wPq-0TeBz3c,235
|
|
444
444
|
django_cfg/apps/integrations/grpc/apps.py,sha256=RF605ivjVMIVbQKIWhGKoDkYfJR9hk3Ou-hCQbBDfzY,3148
|
|
445
445
|
django_cfg/apps/integrations/grpc/urls.py,sha256=Hq1tpLQljoWifXIYudDDYytMeFO60m1FPE17gTAMlTg,1375
|
|
@@ -458,7 +458,7 @@ django_cfg/apps/integrations/grpc/management/__init__.py,sha256=qeaIQO1EY_-VkrUF
|
|
|
458
458
|
django_cfg/apps/integrations/grpc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
459
459
|
django_cfg/apps/integrations/grpc/management/commands/compile_proto.py,sha256=5978_OnFOz6j1m_XG1amlZ3U2dVw3w3395I5ktx2mIA,3440
|
|
460
460
|
django_cfg/apps/integrations/grpc/management/commands/generate_protos.py,sha256=zCGEncUf3kfT_PkvECbAOzBjrzzBx9iTYxq49tie9yg,6618
|
|
461
|
-
django_cfg/apps/integrations/grpc/management/commands/rungrpc.py,sha256=
|
|
461
|
+
django_cfg/apps/integrations/grpc/management/commands/rungrpc.py,sha256=PvQK-bRJjCRNuP4Ip9HJIuNM2ehbuEFKHM1rDh3JA8A,30974
|
|
462
462
|
django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py,sha256=lyKkjIUVNSmMgQdmd9Ko82hp3CZQgdVDCGU1NSxxvd8,2146
|
|
463
463
|
django_cfg/apps/integrations/grpc/management/proto/__init__.py,sha256=2esCFQpr2J0ERGvQaHsR-LOElHV1iXT1AK9McWlJ9g0,57
|
|
464
464
|
django_cfg/apps/integrations/grpc/management/proto/compiler.py,sha256=9qcIcPUeXqpJ-U0WdHYArCH0l0it_9nLNWt0GfK2E4E,5827
|
|
@@ -511,7 +511,7 @@ django_cfg/apps/integrations/grpc/services/commands/examples/start.py,sha256=9Yi
|
|
|
511
511
|
django_cfg/apps/integrations/grpc/services/commands/examples/stop.py,sha256=4q4RS9pmk4GbiYsmAvJ7GoQjtq9F05vVdp1f2PYcabg,2836
|
|
512
512
|
django_cfg/apps/integrations/grpc/services/discovery/__init__.py,sha256=FEC3x2zfJNelUqfulTlFpAxUDOTpTJVdW8dEoV3XOdc,914
|
|
513
513
|
django_cfg/apps/integrations/grpc/services/discovery/discovery.py,sha256=d1bDjVEdJEtrbXFuQV_zuQ5p3JfRGNfUAQQiVNYN7E8,20391
|
|
514
|
-
django_cfg/apps/integrations/grpc/services/discovery/registry.py,sha256=
|
|
514
|
+
django_cfg/apps/integrations/grpc/services/discovery/registry.py,sha256=3PH4pfxoKco_ZGad8Pn4HvQ3SFbFOfT7bpxG3PQ36VU,18305
|
|
515
515
|
django_cfg/apps/integrations/grpc/services/interceptors/__init__.py,sha256=Gp2cQ7PZ3VbpulsU-vB1VwF67DFY5JIURuZoRSopOoM,603
|
|
516
516
|
django_cfg/apps/integrations/grpc/services/interceptors/centrifugo.py,sha256=xhIH6JC6gwudPUfQrUX4MDz2mdUp9SYT455AvBGQ_3Q,19880
|
|
517
517
|
django_cfg/apps/integrations/grpc/services/interceptors/errors.py,sha256=rbhUhD_gwKESiGqFDsGR9bQ-x3zJdM0FxZ2ytb-rK_E,7968
|
|
@@ -870,7 +870,7 @@ django_cfg/modules/django_admin/widgets/registry.py,sha256=P2kQiB3eYXhjRiCXlcImH
|
|
|
870
870
|
django_cfg/modules/django_client/__init__.py,sha256=iHaGKbsyR2wMmVCWNsETC7cwB60fZudvnFMiK1bchW8,529
|
|
871
871
|
django_cfg/modules/django_client/apps.py,sha256=xfkw2aXy08xXlkFhbCiTFveMmRwlDk3SQOAWdqXraFM,1952
|
|
872
872
|
django_cfg/modules/django_client/pytest.ini,sha256=yC1PeQKS4nWQD-jVo5fWF9y1Uv6rywcH0mtHREsiAp0,668
|
|
873
|
-
django_cfg/modules/django_client/urls.py,sha256=
|
|
873
|
+
django_cfg/modules/django_client/urls.py,sha256=mvoeGUh7X3XcX82a3kAW0HD4rp05jGM4dqvyGs_dkdk,2751
|
|
874
874
|
django_cfg/modules/django_client/core/__init__.py,sha256=xAqXcFoY3H0oCBp8g7d9PzO99bOGIDdtHUKgVAOoS_c,1135
|
|
875
875
|
django_cfg/modules/django_client/core/archive/__init__.py,sha256=SKeOKjRnw_NsL7_fOU_2Neo-bcDj4Hv2sVFYztJYiSI,171
|
|
876
876
|
django_cfg/modules/django_client/core/archive/manager.py,sha256=2qSi1ymdjfjU-X0cWzl_hq2adt09EmOCz4z4pMQMAQw,4345
|
|
@@ -940,8 +940,8 @@ django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py
|
|
|
940
940
|
django_cfg/modules/django_client/core/generator/typescript/__init__.py,sha256=eHOZp7M65WZ9u3tA_xQlON5-oijZZiGXDhz22Bq73s0,371
|
|
941
941
|
django_cfg/modules/django_client/core/generator/typescript/client_generator.py,sha256=5CKeXuw6OvbRrq-ezdooqbpLpCrjYLmkL-kWkDdlD14,6068
|
|
942
942
|
django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py,sha256=nOaPFpSRb1izEpyXjJcnC2gdRcSLqhtmTPY0QWXUWkI,9184
|
|
943
|
-
django_cfg/modules/django_client/core/generator/typescript/files_generator.py,sha256=
|
|
944
|
-
django_cfg/modules/django_client/core/generator/typescript/generator.py,sha256=
|
|
943
|
+
django_cfg/modules/django_client/core/generator/typescript/files_generator.py,sha256=iX4WpiQdL6PnTToseEZWvhYrZiNKo27i2As50E1Pxfg,7512
|
|
944
|
+
django_cfg/modules/django_client/core/generator/typescript/generator.py,sha256=SajtFMQDu5Q47CLwX9COsGIQcB3sVC0YoMOHS3bsaSY,21268
|
|
945
945
|
django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py,sha256=0yT4cuUa1BP5LcF17jj3poeR7udJQ14A9TN0gB21d18,16002
|
|
946
946
|
django_cfg/modules/django_client/core/generator/typescript/models_generator.py,sha256=xhIaEn76_Jt9KeW_JE7PCBzdJMF7WZdE75T11Ih_ay0,9597
|
|
947
947
|
django_cfg/modules/django_client/core/generator/typescript/naming.py,sha256=kSZwPGqD42G2KBqoPGDiy4CoWjaQWseKAWbdJOF7VaU,2574
|
|
@@ -953,7 +953,7 @@ django_cfg/modules/django_client/core/generator/typescript/templates/api_instanc
|
|
|
953
953
|
django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja,sha256=gLsoYyEzKD6Gv64vsO9sQHMPiFMGdaB5XVufLHeRyvQ,62
|
|
954
954
|
django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja,sha256=LHUt72fO2eRNAHYEscIYvqVR69GC6mxqjcgSlUzeCtc,251
|
|
955
955
|
django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja,sha256=OtQxzqV_6SYvugk_oS0F9_WXty2tnKY_wl2n9-WeJqo,127
|
|
956
|
-
django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja,sha256=
|
|
956
|
+
django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja,sha256=9u30Lti9OPN9h6sliB5MyUUTjdi2dQSAP1-Nwagv2p0,7870
|
|
957
957
|
django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja,sha256=XOgZJG3twzWJ8gsZ5VZ_gQx7zBvrnwXy-yUFHfdyBBs,1280
|
|
958
958
|
django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja,sha256=QKbo6hYoVdRXrm7psRzBGStzAP5omQrnamSQT6b44gE,482
|
|
959
959
|
django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja,sha256=jaFN_QIQU2eyu-6uiwUjwACYNr06LI14XGJN0Dgv-9E,260
|
|
@@ -963,7 +963,7 @@ django_cfg/modules/django_client/core/generator/typescript/templates/client/main
|
|
|
963
963
|
django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja,sha256=z6GD-r7Y-S_yhDtlOAjMgDSL10AF3YrBLLNLPiCt8rE,1869
|
|
964
964
|
django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja,sha256=1rtFMqJO8ynjNNblhMPwCbVFhbSbLJJwiMhuJJYf9Lw,215
|
|
965
965
|
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja,sha256=vk-yQgNuS4jxnN6bqUl7gDrrK7_q5eLnWXiBvHVSvSQ,1291
|
|
966
|
-
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja,sha256=
|
|
966
|
+
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja,sha256=d3ImACQDM-KoqiU6ytElXSr82m7rY5Q_mRQxpMPaKOg,2352
|
|
967
967
|
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja,sha256=DoAVm8EoglJKtLrE917Fk7Na6rn5Bt9L1nI39o9AwzM,747
|
|
968
968
|
django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja,sha256=RdImlx61kBn1uCrrpqeTfxa1l-5a3AYU1ZKMVw1f6TU,870
|
|
969
969
|
django_cfg/modules/django_client/core/generator/typescript/templates/hooks/index.ts.jinja,sha256=c4Jru6DAKD-y8nzig1my0iE-BO3OwjSCENALbHvAp7E,694
|
|
@@ -980,6 +980,7 @@ django_cfg/modules/django_client/core/generator/typescript/templates/utils/logge
|
|
|
980
980
|
django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja,sha256=xv1wvOqyIITi7e-318Wxu_af9T72H68TWufVRsHiCqw,4080
|
|
981
981
|
django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja,sha256=KC8zJ6R91LtyeVW-dp9ljbwCTZVPyvf0glnRD8pJzAA,167
|
|
982
982
|
django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja,sha256=Sbszp1L7b1pN9EScq7c2WmltdC7yXrbqU55v4FmRdT0,4899
|
|
983
|
+
django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja,sha256=u5nVpO_TjPLvAX-t5bl-9MWEeAb-u7qAwQkEPZ9F6h0,3413
|
|
983
984
|
django_cfg/modules/django_client/core/groups/__init__.py,sha256=4n80S-cdywX4wITntbo9tsmVwv8iRQEkPKrtsaXz7jQ,226
|
|
984
985
|
django_cfg/modules/django_client/core/groups/detector.py,sha256=nCCqpXPGNznm57KzyuriL0D7hBJ2yGA3woL9upf4i5o,5543
|
|
985
986
|
django_cfg/modules/django_client/core/groups/manager.py,sha256=MBXVhHBb19antrrbwFWP47v9y2OMz0oQ1RAz406Yip4,11201
|
|
@@ -1124,16 +1125,16 @@ django_cfg/modules/django_twilio/README.md,sha256=Zmh_mG0loPTHlr4piqRfz7AWp-uSRt
|
|
|
1124
1125
|
django_cfg/modules/django_twilio/__init__.py,sha256=R51VI_lxA47h2nRx1jkcXnwdYrdsDfKJK7TSojYc5yI,2378
|
|
1125
1126
|
django_cfg/modules/django_twilio/_imports.py,sha256=qZo8urnj-K66z4HcIzaAZWhdAtqZU_lHVHL1ipqCU9A,782
|
|
1126
1127
|
django_cfg/modules/django_twilio/base.py,sha256=wizSK-AelOpH0oCzVgqAJHPzvxekBmIX-hKseWbhHJM,6426
|
|
1127
|
-
django_cfg/modules/django_twilio/email_otp.py,sha256=
|
|
1128
|
+
django_cfg/modules/django_twilio/email_otp.py,sha256=iGk7O8lGwp1bJqZnE2EQWgXB2VfQGryhW_7kacncpnc,7948
|
|
1128
1129
|
django_cfg/modules/django_twilio/exceptions.py,sha256=KWdiEc7mFYvxEwvOgLb14d_XmidJXtUVt-i2VNVWOwE,10498
|
|
1129
1130
|
django_cfg/modules/django_twilio/models.py,sha256=u7PHSiHfbkuTpfh9Z3WiPPBvV0Ps9ZW8ZxAUXcKMVMo,11599
|
|
1130
1131
|
django_cfg/modules/django_twilio/sendgrid_service.py,sha256=Xk0BAWKW2hgfKQ4-Y4oUNidqkcdwf-wgeFHoUTBqFUM,18296
|
|
1131
1132
|
django_cfg/modules/django_twilio/simple_service.py,sha256=zDV8hm7dVWqdL-Y0XF-T80wxJvIpe3JR1JpwdUFv-OY,9672
|
|
1132
|
-
django_cfg/modules/django_twilio/sms.py,sha256=
|
|
1133
|
+
django_cfg/modules/django_twilio/sms.py,sha256=EUIwOfhS8fDn8UGyANLu8PZCk5sFqIrecWSt-taymSI,2965
|
|
1133
1134
|
django_cfg/modules/django_twilio/twilio_service.py,sha256=PPjoemzZAgpEOZBENyHHC1F8s77NQh3gMN3EcH6iG-w,19299
|
|
1134
|
-
django_cfg/modules/django_twilio/unified.py,sha256=
|
|
1135
|
+
django_cfg/modules/django_twilio/unified.py,sha256=VnuauH9Ys5ZDtjNv2LX_VK_-ybKTgXsp2xb6GedrJoU,11776
|
|
1135
1136
|
django_cfg/modules/django_twilio/utils.py,sha256=6PSyvd0Jgvf3QuiHTHM8t0Sc4vYUXxAnJACd8WHWzQ8,4898
|
|
1136
|
-
django_cfg/modules/django_twilio/whatsapp.py,sha256=
|
|
1137
|
+
django_cfg/modules/django_twilio/whatsapp.py,sha256=AR2Ez0vsZIllhYaE_BVMI3NPkaA8exkK8zrODIbQyV8,5126
|
|
1137
1138
|
django_cfg/modules/django_twilio/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1138
1139
|
django_cfg/modules/django_twilio/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1139
1140
|
django_cfg/modules/django_twilio/management/commands/test_twilio.py,sha256=hFQjks4ZoZj1-K9rKCHdYFlwp71IUxNE6rYmJ8jEjHo,4118
|
|
@@ -1181,7 +1182,7 @@ django_cfg/static/admin/js/alpine/commands-section.js,sha256=8z2MQNwZF9Tx_2EK1AY
|
|
|
1181
1182
|
django_cfg/static/admin/js/alpine/dashboard-tabs.js,sha256=ob8Q_I9lFLDv_hFERXgTyvqMDBspAGfzCxI_7slRur4,1354
|
|
1182
1183
|
django_cfg/static/admin/js/alpine/system-metrics.js,sha256=m-Fg55K_vpHXToD46PXL9twl4OBF_V9MONvbSWbQqDw,440
|
|
1183
1184
|
django_cfg/static/admin/js/alpine/toggle-section.js,sha256=T141NFmy0fRJyGGuuaCJRjJXwPam-xxtQNW1hi8BJbc,672
|
|
1184
|
-
django_cfg/static/frontend/admin.zip,sha256=
|
|
1185
|
+
django_cfg/static/frontend/admin.zip,sha256=q-mvBZpSOujXj1ZyQeHUY8H4__JjfRR2Tp9D5kPS1MM,24319401
|
|
1185
1186
|
django_cfg/static/js/api-loader.mjs,sha256=boGqqRGnFR-Mzo_RQOjhAzNvsb7QxZddSwMKROzkk9Q,5163
|
|
1186
1187
|
django_cfg/static/js/api/base.mjs,sha256=KUxZHHdELAV8mNnACpwJRvaQhdJxp-n5LFEQ4oUZxBo,4707
|
|
1187
1188
|
django_cfg/static/js/api/index.mjs,sha256=_-Q04jjHcgwi4CGfiaLyiOR6NW7Yu1HBhJWp2J1cjpc,2538
|
|
@@ -1204,7 +1205,7 @@ django_cfg/static/js/api/tasks/client.mjs,sha256=tIy8K-finXzTUL9kOo_L4Q1kchDaHyu
|
|
|
1204
1205
|
django_cfg/static/js/api/tasks/index.mjs,sha256=yCY1GzdD-RtFZ3pAfk1l0msgO1epyo0lsGCjH0g1Afc,294
|
|
1205
1206
|
django_cfg/templates/__init__.py,sha256=IzLjt-a7VIJ0OutmAE1_-w0_LpL2u0MgGpnIabjZuW8,19
|
|
1206
1207
|
django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md,sha256=CL8E3K4rFpXeQiNgrYSMvCW1y-eFaoXxxsI58zPf9dY,17562
|
|
1207
|
-
django_cfg/templates/admin/index.html,sha256=
|
|
1208
|
+
django_cfg/templates/admin/index.html,sha256=MfDz35In0iOI_HjoHldTq0KbR9LIcp1FgQZTZmzhlJE,34826
|
|
1208
1209
|
django_cfg/templates/admin/constance/includes/results_list.html,sha256=Itzs1lGqOYg6ftJUjQ1jWmsbdXDKdov3cDtqMllxih8,3835
|
|
1209
1210
|
django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8G4L_GexPxvz6XQ,8836
|
|
1210
1211
|
django_cfg/templates/unfold/layouts/skeleton.html,sha256=2ArkcNZ34mFs30cOAsTQ1EZiDXcB0aVxkO71lJq9SLE,718
|
|
@@ -1218,9 +1219,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1218
1219
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1219
1220
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1220
1221
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1221
|
-
django_cfg/pyproject.toml,sha256=
|
|
1222
|
-
django_cfg-1.5.
|
|
1223
|
-
django_cfg-1.5.
|
|
1224
|
-
django_cfg-1.5.
|
|
1225
|
-
django_cfg-1.5.
|
|
1226
|
-
django_cfg-1.5.
|
|
1222
|
+
django_cfg/pyproject.toml,sha256=q2h83EyAumlN37F-_O6bas7tz-Dbtb1w-RcMZKsqtLg,8868
|
|
1223
|
+
django_cfg-1.5.31.dist-info/METADATA,sha256=2AFiJZJDolxFzpZQQNQEVnM7loaXthn6r0QEJFMpwRo,28377
|
|
1224
|
+
django_cfg-1.5.31.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1225
|
+
django_cfg-1.5.31.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1226
|
+
django_cfg-1.5.31.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1227
|
+
django_cfg-1.5.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|