django-cfg 1.4.19__py3-none-any.whl → 1.4.21__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.
- django_cfg/apps/knowbase/migrations/0003_alter_documentarchive_archive_type.py +29 -0
- django_cfg/apps/knowbase/models/archive.py +2 -2
- django_cfg/apps/knowbase/urls.py +3 -24
- django_cfg/apps/knowbase/urls_admin.py +23 -0
- django_cfg/apps/knowbase/urls_system.py +26 -0
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/payments/signals/api_key_signals.py +3 -3
- django_cfg/apps/support/views/api.py +2 -4
- django_cfg/apps/urls.py +5 -3
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +57 -11
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +21 -12
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +6 -2
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +54 -7
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +16 -5
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.19.dist-info → django_cfg-1.4.21.dist-info}/METADATA +1 -1
- {django_cfg-1.4.19.dist-info → django_cfg-1.4.21.dist-info}/RECORD +20 -17
- {django_cfg-1.4.19.dist-info → django_cfg-1.4.21.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.19.dist-info → django_cfg-1.4.21.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.19.dist-info → django_cfg-1.4.21.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-09 15:24
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
dependencies = [
|
8
|
+
(
|
9
|
+
"django_cfg_knowbase",
|
10
|
+
"0002_archiveitem_archiveitemchunk_documentarchive_and_more",
|
11
|
+
),
|
12
|
+
]
|
13
|
+
|
14
|
+
operations = [
|
15
|
+
migrations.AlterField(
|
16
|
+
model_name="documentarchive",
|
17
|
+
name="archive_type",
|
18
|
+
field=models.CharField(
|
19
|
+
choices=[
|
20
|
+
("zip", "ZIP"),
|
21
|
+
("tar", "TAR"),
|
22
|
+
("tar.gz", "TAR GZ"),
|
23
|
+
("tar.bz2", "TAR BZ2"),
|
24
|
+
],
|
25
|
+
help_text="Archive format",
|
26
|
+
max_length=20,
|
27
|
+
),
|
28
|
+
),
|
29
|
+
]
|
@@ -20,8 +20,8 @@ class ArchiveType(models.TextChoices):
|
|
20
20
|
"""Supported archive formats."""
|
21
21
|
ZIP = "zip", "ZIP"
|
22
22
|
TAR = "tar", "TAR"
|
23
|
-
TAR_GZ = "tar.gz", "TAR
|
24
|
-
TAR_BZ2 = "tar.bz2", "TAR
|
23
|
+
TAR_GZ = "tar.gz", "TAR GZ"
|
24
|
+
TAR_BZ2 = "tar.bz2", "TAR BZ2"
|
25
25
|
|
26
26
|
|
27
27
|
class ContentType(models.TextChoices):
|
django_cfg/apps/knowbase/urls.py
CHANGED
@@ -4,24 +4,8 @@ Knowledge Base URL Configuration
|
|
4
4
|
|
5
5
|
from django.urls import path, include
|
6
6
|
from rest_framework.routers import DefaultRouter
|
7
|
-
from .views import (
|
8
|
-
DocumentViewSet, ChatViewSet, ChatSessionViewSet,
|
9
|
-
DocumentArchiveViewSet, ArchiveItemViewSet, ArchiveItemChunkViewSet
|
10
|
-
)
|
11
7
|
from .views.public_views import PublicDocumentViewSet, PublicCategoryViewSet
|
12
8
|
|
13
|
-
# Create router and register viewsets
|
14
|
-
router = DefaultRouter()
|
15
|
-
router.register(r'documents', DocumentViewSet, basename='document')
|
16
|
-
router.register(r'chat', ChatViewSet, basename='chat')
|
17
|
-
router.register(r'sessions', ChatSessionViewSet, basename='session')
|
18
|
-
|
19
|
-
# Archive router for authenticated users
|
20
|
-
archive_router = DefaultRouter()
|
21
|
-
archive_router.register(r'archives', DocumentArchiveViewSet, basename='archive')
|
22
|
-
archive_router.register(r'items', ArchiveItemViewSet, basename='archive-item')
|
23
|
-
archive_router.register(r'chunks', ArchiveItemChunkViewSet, basename='archive-chunk')
|
24
|
-
|
25
9
|
# Public router for client access
|
26
10
|
public_router = DefaultRouter()
|
27
11
|
public_router.register(r'documents', PublicDocumentViewSet, basename='public-document')
|
@@ -29,15 +13,10 @@ public_router.register(r'categories', PublicCategoryViewSet, basename='public-ca
|
|
29
13
|
|
30
14
|
# URL patterns
|
31
15
|
urlpatterns = [
|
32
|
-
|
33
|
-
path('admin/', include(router.urls)),
|
34
|
-
|
35
|
-
# Archive API endpoints (require authentication)
|
36
|
-
path('', include(archive_router.urls)),
|
37
|
-
|
16
|
+
|
38
17
|
# Public API endpoints (no authentication required)
|
39
|
-
path('
|
18
|
+
path('', include(public_router.urls)),
|
40
19
|
]
|
41
20
|
|
42
21
|
# Add app name for namespacing
|
43
|
-
app_name = '
|
22
|
+
app_name = 'cfg_knowbase'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"""
|
2
|
+
Knowledge Base URL Configuration
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.urls import path, include
|
6
|
+
from rest_framework.routers import DefaultRouter
|
7
|
+
from .views import (
|
8
|
+
DocumentViewSet, ChatViewSet, ChatSessionViewSet,
|
9
|
+
)
|
10
|
+
|
11
|
+
# Create router and register viewsets
|
12
|
+
router = DefaultRouter()
|
13
|
+
router.register(r'documents', DocumentViewSet, basename='document')
|
14
|
+
router.register(r'chat', ChatViewSet, basename='chat')
|
15
|
+
router.register(r'sessions', ChatSessionViewSet, basename='session')
|
16
|
+
|
17
|
+
# URL patterns
|
18
|
+
urlpatterns = [
|
19
|
+
# Admin API endpoints (require authentication + admin rights)
|
20
|
+
path('', include(router.urls)),
|
21
|
+
]
|
22
|
+
|
23
|
+
app_name = 'cfg_knowbase_admin'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Knowledge Base URL Configuration
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.urls import path, include
|
6
|
+
from rest_framework.routers import DefaultRouter
|
7
|
+
from .views import (
|
8
|
+
DocumentArchiveViewSet, ArchiveItemViewSet, ArchiveItemChunkViewSet
|
9
|
+
)
|
10
|
+
|
11
|
+
# Archive router for authenticated users
|
12
|
+
archive_router = DefaultRouter()
|
13
|
+
archive_router.register(r'archives', DocumentArchiveViewSet, basename='archive')
|
14
|
+
archive_router.register(r'items', ArchiveItemViewSet, basename='archive-item')
|
15
|
+
archive_router.register(r'chunks', ArchiveItemChunkViewSet, basename='archive-chunk')
|
16
|
+
|
17
|
+
# URL patterns
|
18
|
+
urlpatterns = [
|
19
|
+
|
20
|
+
# Archive API endpoints (require authentication)
|
21
|
+
path('', include(archive_router.urls)),
|
22
|
+
|
23
|
+
]
|
24
|
+
|
25
|
+
# Add app name for namespacing
|
26
|
+
app_name = 'cfg_knowbase_system'
|
@@ -111,6 +111,9 @@ class ChatSessionViewSet(BaseKnowledgeViewSet):
|
|
111
111
|
class ChatViewSet(BaseKnowledgeViewSet):
|
112
112
|
"""Chat query endpoints."""
|
113
113
|
|
114
|
+
# This ViewSet doesn't use standard CRUD operations
|
115
|
+
# It only has custom actions (query, history)
|
116
|
+
queryset = ChatSession.objects.none() # Empty queryset since we don't use list/retrieve/etc
|
114
117
|
service_class = ChatService
|
115
118
|
serializer_class = ChatResponseSerializer
|
116
119
|
|
@@ -100,7 +100,7 @@ def handle_api_key_deletion(sender, instance: APIKey, **kwargs):
|
|
100
100
|
logger.warning(f"API key deleted", extra={
|
101
101
|
'api_key_id': str(instance.id),
|
102
102
|
'user_id': instance.user.id,
|
103
|
-
'
|
103
|
+
'key_name': instance.name,
|
104
104
|
'total_requests': instance.total_requests,
|
105
105
|
'deletion_timestamp': timezone.now().isoformat()
|
106
106
|
})
|
@@ -129,7 +129,7 @@ def _handle_api_key_activated(api_key: APIKey):
|
|
129
129
|
logger.info(f"API key activated", extra={
|
130
130
|
'api_key_id': str(api_key.id),
|
131
131
|
'user_id': api_key.user.id,
|
132
|
-
'
|
132
|
+
'key_name': api_key.name
|
133
133
|
})
|
134
134
|
|
135
135
|
# Set activation notification in cache
|
@@ -153,7 +153,7 @@ def _handle_api_key_deactivated(api_key: APIKey):
|
|
153
153
|
logger.warning(f"API key deactivated", extra={
|
154
154
|
'api_key_id': str(api_key.id),
|
155
155
|
'user_id': api_key.user.id,
|
156
|
-
'
|
156
|
+
'key_name': api_key.name,
|
157
157
|
'total_requests': api_key.total_requests
|
158
158
|
})
|
159
159
|
|
@@ -12,10 +12,9 @@ from ..serializers import TicketSerializer, MessageSerializer, MessageCreateSeri
|
|
12
12
|
|
13
13
|
class TicketViewSet(viewsets.ModelViewSet):
|
14
14
|
"""ViewSet for managing support tickets."""
|
15
|
-
|
15
|
+
|
16
16
|
serializer_class = TicketSerializer
|
17
17
|
permission_classes = [permissions.IsAuthenticated]
|
18
|
-
pagination_class = None
|
19
18
|
lookup_field = 'uuid'
|
20
19
|
lookup_url_kwarg = 'uuid'
|
21
20
|
|
@@ -34,10 +33,9 @@ class TicketViewSet(viewsets.ModelViewSet):
|
|
34
33
|
|
35
34
|
class MessageViewSet(viewsets.ModelViewSet):
|
36
35
|
"""ViewSet for managing support messages."""
|
37
|
-
|
36
|
+
|
38
37
|
serializer_class = MessageSerializer
|
39
38
|
permission_classes = [permissions.IsAuthenticated]
|
40
|
-
pagination_class = None
|
41
39
|
lookup_field = 'uuid'
|
42
40
|
lookup_url_kwarg = 'uuid'
|
43
41
|
|
django_cfg/apps/urls.py
CHANGED
@@ -22,6 +22,9 @@ def get_enabled_cfg_apps() -> List[str]:
|
|
22
22
|
|
23
23
|
if base_module.is_accounts_enabled():
|
24
24
|
enabled_apps.append("django_cfg.apps.accounts")
|
25
|
+
|
26
|
+
if base_module.is_knowbase_enabled():
|
27
|
+
enabled_apps.append("django_cfg.apps.knowbase")
|
25
28
|
|
26
29
|
if base_module.is_support_enabled():
|
27
30
|
enabled_apps.append("django_cfg.apps.support")
|
@@ -32,9 +35,6 @@ def get_enabled_cfg_apps() -> List[str]:
|
|
32
35
|
if base_module.is_leads_enabled():
|
33
36
|
enabled_apps.append("django_cfg.apps.leads")
|
34
37
|
|
35
|
-
if base_module.is_knowbase_enabled():
|
36
|
-
enabled_apps.append("django_cfg.apps.knowbase")
|
37
|
-
|
38
38
|
if base_module.is_agents_enabled():
|
39
39
|
enabled_apps.append("django_cfg.apps.agents")
|
40
40
|
|
@@ -114,6 +114,8 @@ APP_URL_MAP = {
|
|
114
114
|
],
|
115
115
|
"django_cfg.apps.knowbase": [
|
116
116
|
("cfg/knowbase/", "django_cfg.apps.knowbase.urls"),
|
117
|
+
("cfg/knowbase/admin/", "django_cfg.apps.knowbase.urls_admin"),
|
118
|
+
("cfg/knowbase/system/", "django_cfg.apps.knowbase.urls_system"),
|
117
119
|
],
|
118
120
|
"django_cfg.apps.agents": [
|
119
121
|
("cfg/agents/", "django_cfg.apps.agents.urls"),
|
@@ -159,7 +159,58 @@ class FetchersGenerator:
|
|
159
159
|
func_params.append(f"{param.name}: {param_type}")
|
160
160
|
api_call_params.append(param.name)
|
161
161
|
|
162
|
+
# Request body (passed as data or unpacked for multipart)
|
163
|
+
# NOTE: This must come BEFORE query params to match client method signature order!
|
164
|
+
if operation.request_body:
|
165
|
+
# Check if this is a file upload operation
|
166
|
+
is_multipart = operation.request_body.content_type == "multipart/form-data"
|
167
|
+
|
168
|
+
if is_multipart:
|
169
|
+
# For multipart, unpack data properties to match client signature
|
170
|
+
schema_name = operation.request_body.schema_name
|
171
|
+
if schema_name and schema_name in self.context.schemas:
|
172
|
+
schema = self.context.schemas[schema_name]
|
173
|
+
# Add data parameter in func signature (keeps API simple)
|
174
|
+
func_params.append(f"data: {schema_name}")
|
175
|
+
# But unpack when calling client (which expects individual params)
|
176
|
+
# IMPORTANT: Order must match client - required first, then optional
|
177
|
+
required_props = []
|
178
|
+
optional_props = []
|
179
|
+
|
180
|
+
for prop_name, prop in schema.properties.items():
|
181
|
+
if prop_name in schema.required:
|
182
|
+
required_props.append(prop_name)
|
183
|
+
else:
|
184
|
+
optional_props.append(prop_name)
|
185
|
+
|
186
|
+
# Add required first, then optional (matches client signature)
|
187
|
+
for prop_name in required_props + optional_props:
|
188
|
+
api_call_params.append(f"data.{prop_name}")
|
189
|
+
else:
|
190
|
+
# Inline schema - use data as-is
|
191
|
+
func_params.append(f"data: FormData")
|
192
|
+
api_call_params.append("data")
|
193
|
+
else:
|
194
|
+
# JSON request body - pass data object
|
195
|
+
schema_name = operation.request_body.schema_name
|
196
|
+
if schema_name and schema_name in self.context.schemas:
|
197
|
+
body_type = schema_name
|
198
|
+
else:
|
199
|
+
body_type = "any"
|
200
|
+
func_params.append(f"data: {body_type}")
|
201
|
+
api_call_params.append("data")
|
202
|
+
elif operation.patch_request_body:
|
203
|
+
# PATCH request body (optional)
|
204
|
+
schema_name = operation.patch_request_body.schema_name
|
205
|
+
if schema_name and schema_name in self.context.schemas:
|
206
|
+
func_params.append(f"data?: {schema_name}")
|
207
|
+
api_call_params.append("data")
|
208
|
+
else:
|
209
|
+
func_params.append(f"data?: any")
|
210
|
+
api_call_params.append("data")
|
211
|
+
|
162
212
|
# Query parameters (passed as params object, but unpacked when calling API)
|
213
|
+
# NOTE: This must come AFTER request body to match client method signature order!
|
163
214
|
if operation.query_parameters:
|
164
215
|
query_fields = []
|
165
216
|
# params is required only if all parameters are required
|
@@ -177,17 +228,6 @@ class FetchersGenerator:
|
|
177
228
|
params_optional = "" if all_required else "?"
|
178
229
|
func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
|
179
230
|
|
180
|
-
# Request body (passed as data)
|
181
|
-
if operation.request_body:
|
182
|
-
schema_name = operation.request_body.schema_name
|
183
|
-
# Use schema only if it exists as a component (not inline)
|
184
|
-
if schema_name and schema_name in self.context.schemas:
|
185
|
-
body_type = schema_name
|
186
|
-
else:
|
187
|
-
body_type = "any"
|
188
|
-
func_params.append(f"data: {body_type}")
|
189
|
-
api_call_params.append("data")
|
190
|
-
|
191
231
|
return {
|
192
232
|
'func_params': ", ".join(func_params) if func_params else "",
|
193
233
|
'api_call_params': ", ".join(api_call_params) if api_call_params else ""
|
@@ -338,6 +378,12 @@ class FetchersGenerator:
|
|
338
378
|
# Only add if schema exists in components (not inline)
|
339
379
|
if operation.request_body.schema_name in self.context.schemas:
|
340
380
|
schema_names.add(operation.request_body.schema_name)
|
381
|
+
|
382
|
+
# Add patch request body schemas
|
383
|
+
if operation.patch_request_body and operation.patch_request_body.schema_name:
|
384
|
+
# Only add if schema exists in components (not inline)
|
385
|
+
if operation.patch_request_body.schema_name in self.context.schemas:
|
386
|
+
schema_names.add(operation.patch_request_body.schema_name)
|
341
387
|
|
342
388
|
# Get display name and folder name (use same naming as APIClient)
|
343
389
|
tag_display_name = self.base.tag_to_display_name(tag)
|
@@ -207,7 +207,27 @@ class HooksGenerator:
|
|
207
207
|
func_params.append(f"{param.name}: {param_type}")
|
208
208
|
fetcher_params.append(param.name)
|
209
209
|
|
210
|
-
#
|
210
|
+
# Request body (must come BEFORE query params to match fetcher signature!)
|
211
|
+
if operation.request_body:
|
212
|
+
schema_name = operation.request_body.schema_name
|
213
|
+
# Use schema only if it exists as a component (not inline)
|
214
|
+
if schema_name and schema_name in self.context.schemas:
|
215
|
+
body_type = schema_name
|
216
|
+
else:
|
217
|
+
body_type = "any"
|
218
|
+
func_params.append(f"data: {body_type}")
|
219
|
+
fetcher_params.append("data")
|
220
|
+
elif operation.patch_request_body:
|
221
|
+
# PATCH request body (optional)
|
222
|
+
schema_name = operation.patch_request_body.schema_name
|
223
|
+
if schema_name and schema_name in self.context.schemas:
|
224
|
+
func_params.append(f"data?: {schema_name}")
|
225
|
+
fetcher_params.append("data")
|
226
|
+
else:
|
227
|
+
func_params.append(f"data?: any")
|
228
|
+
fetcher_params.append("data")
|
229
|
+
|
230
|
+
# Query parameters (must come AFTER request body to match fetcher signature!)
|
211
231
|
if operation.query_parameters:
|
212
232
|
query_fields = []
|
213
233
|
all_required = all(param.required for param in operation.query_parameters)
|
@@ -222,17 +242,6 @@ class HooksGenerator:
|
|
222
242
|
func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
|
223
243
|
fetcher_params.append("params")
|
224
244
|
|
225
|
-
# Request body
|
226
|
-
if operation.request_body:
|
227
|
-
schema_name = operation.request_body.schema_name
|
228
|
-
# Use schema only if it exists as a component (not inline)
|
229
|
-
if schema_name and schema_name in self.context.schemas:
|
230
|
-
body_type = schema_name
|
231
|
-
else:
|
232
|
-
body_type = "any"
|
233
|
-
func_params.append(f"data: {body_type}")
|
234
|
-
fetcher_params.append("data")
|
235
|
-
|
236
245
|
return {
|
237
246
|
'func_params': ", ".join(func_params) if func_params else "",
|
238
247
|
'fetcher_params': ", ".join(fetcher_params) if fetcher_params else ""
|
@@ -208,10 +208,14 @@ class ModelsGenerator:
|
|
208
208
|
if not var_name or (isinstance(value, str) and value == ''):
|
209
209
|
continue
|
210
210
|
|
211
|
+
# Sanitize var_name: replace dots and spaces with underscores, convert to UPPER_CASE
|
212
|
+
# "TAR.GZ" -> "TAR_GZ", "TAR GZ" -> "TAR_GZ"
|
213
|
+
sanitized_var_name = var_name.replace('.', '_').replace(' ', '_').upper()
|
214
|
+
|
211
215
|
if isinstance(value, str):
|
212
|
-
member_lines.append(f'{
|
216
|
+
member_lines.append(f'{sanitized_var_name} = "{value}",')
|
213
217
|
else:
|
214
|
-
member_lines.append(f"{
|
218
|
+
member_lines.append(f"{sanitized_var_name} = {value},")
|
215
219
|
|
216
220
|
# Build enum
|
217
221
|
lines = []
|
@@ -56,17 +56,31 @@ class OperationsGenerator:
|
|
56
56
|
schema_name = operation.request_body.schema_name
|
57
57
|
if schema_name and schema_name in self.context.schemas:
|
58
58
|
schema = self.context.schemas[schema_name]
|
59
|
+
# Add required params first, then optional (TypeScript requirement)
|
60
|
+
required_params = []
|
61
|
+
optional_params = []
|
62
|
+
|
59
63
|
for prop_name, prop in schema.properties.items():
|
60
64
|
# Check if it's a file field (format: binary)
|
61
65
|
if prop.format == "binary":
|
62
|
-
|
66
|
+
param_str = f"{prop_name}: File | Blob"
|
63
67
|
else:
|
64
68
|
# Regular field in multipart
|
65
69
|
prop_type = self._map_param_type(prop.type)
|
66
70
|
if prop_name in schema.required:
|
67
|
-
|
71
|
+
param_str = f"{prop_name}: {prop_type}"
|
68
72
|
else:
|
69
|
-
|
73
|
+
param_str = f"{prop_name}?: {prop_type}"
|
74
|
+
|
75
|
+
# Separate required and optional
|
76
|
+
if prop_name in schema.required:
|
77
|
+
required_params.append(param_str)
|
78
|
+
else:
|
79
|
+
optional_params.append(param_str)
|
80
|
+
|
81
|
+
# Add required first, then optional
|
82
|
+
params.extend(required_params)
|
83
|
+
params.extend(optional_params)
|
70
84
|
else:
|
71
85
|
# Inline schema - use FormData
|
72
86
|
params.append("data: FormData")
|
@@ -153,15 +167,37 @@ class OperationsGenerator:
|
|
153
167
|
if use_rest_params and query_params_list:
|
154
168
|
# Extract parameters from args array
|
155
169
|
path_params_count = len(operation.path_parameters)
|
156
|
-
|
170
|
+
|
171
|
+
# For multipart, body params are unpacked as individual fields
|
172
|
+
if is_multipart and operation.request_body:
|
173
|
+
schema_name = operation.request_body.schema_name
|
174
|
+
if schema_name and schema_name in self.context.schemas:
|
175
|
+
schema = self.context.schemas[schema_name]
|
176
|
+
body_params_count = len(schema.properties)
|
177
|
+
else:
|
178
|
+
body_params_count = 1 # data: FormData
|
179
|
+
else:
|
180
|
+
body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
|
181
|
+
|
157
182
|
first_query_pos = path_params_count + body_params_count
|
158
183
|
|
159
184
|
# Extract path parameters
|
160
185
|
for i, param in enumerate(operation.path_parameters):
|
161
186
|
body_lines.append(f"const {param.name} = args[{i}];")
|
162
187
|
|
163
|
-
# Extract body/data parameter
|
164
|
-
if
|
188
|
+
# Extract body/data parameter(s)
|
189
|
+
if is_multipart and operation.request_body:
|
190
|
+
schema_name = operation.request_body.schema_name
|
191
|
+
if schema_name and schema_name in self.context.schemas:
|
192
|
+
schema = self.context.schemas[schema_name]
|
193
|
+
# Extract each property as separate variable
|
194
|
+
arg_idx = path_params_count
|
195
|
+
for prop_name in schema.properties.keys():
|
196
|
+
body_lines.append(f"const {prop_name} = args[{arg_idx}];")
|
197
|
+
arg_idx += 1
|
198
|
+
else:
|
199
|
+
body_lines.append(f"const data = args[{path_params_count}];")
|
200
|
+
elif operation.request_body or operation.patch_request_body:
|
165
201
|
body_lines.append(f"const data = args[{path_params_count}];")
|
166
202
|
|
167
203
|
# Check if first query arg is object (params style) or primitive (old style)
|
@@ -187,7 +223,18 @@ class OperationsGenerator:
|
|
187
223
|
if use_rest_params:
|
188
224
|
# Extract params from args array - handle both calling styles
|
189
225
|
path_params_count = len(operation.path_parameters)
|
190
|
-
|
226
|
+
|
227
|
+
# For multipart, body params are unpacked as individual fields
|
228
|
+
if is_multipart and operation.request_body:
|
229
|
+
schema_name = operation.request_body.schema_name
|
230
|
+
if schema_name and schema_name in self.context.schemas:
|
231
|
+
schema = self.context.schemas[schema_name]
|
232
|
+
body_params_count = len(schema.properties)
|
233
|
+
else:
|
234
|
+
body_params_count = 1 # data: FormData
|
235
|
+
else:
|
236
|
+
body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
|
237
|
+
|
191
238
|
first_query_pos = path_params_count + body_params_count
|
192
239
|
|
193
240
|
body_lines.append("let params;")
|
@@ -74,7 +74,7 @@ class SchemasGenerator:
|
|
74
74
|
# Generate fields
|
75
75
|
if schema.properties:
|
76
76
|
for prop_name, prop_schema in schema.properties.items():
|
77
|
-
field_code = self._generate_field(prop_name, prop_schema, schema.required)
|
77
|
+
field_code = self._generate_field(prop_name, prop_schema, schema.required, parent_schema=schema)
|
78
78
|
lines.append(f" {field_code},")
|
79
79
|
|
80
80
|
lines.append("})")
|
@@ -86,6 +86,7 @@ class SchemasGenerator:
|
|
86
86
|
name: str,
|
87
87
|
schema: IRSchemaObject,
|
88
88
|
required_fields: list[str],
|
89
|
+
parent_schema: IRSchemaObject | None = None,
|
89
90
|
) -> str:
|
90
91
|
"""
|
91
92
|
Generate Zod field validation.
|
@@ -112,7 +113,7 @@ class SchemasGenerator:
|
|
112
113
|
zod_type = f"{schema.ref}Schema"
|
113
114
|
else:
|
114
115
|
# Map TypeScript type to Zod type
|
115
|
-
zod_type = self._map_type_to_zod(schema)
|
116
|
+
zod_type = self._map_type_to_zod(schema, parent_schema=parent_schema)
|
116
117
|
|
117
118
|
# Check if required
|
118
119
|
is_required = name in required_fields
|
@@ -128,12 +129,13 @@ class SchemasGenerator:
|
|
128
129
|
|
129
130
|
return f"{name}: {zod_type}"
|
130
131
|
|
131
|
-
def _map_type_to_zod(self, schema: IRSchemaObject) -> str:
|
132
|
+
def _map_type_to_zod(self, schema: IRSchemaObject, parent_schema: IRSchemaObject | None = None) -> str:
|
132
133
|
"""
|
133
134
|
Map OpenAPI/TypeScript type to Zod validation.
|
134
135
|
|
135
136
|
Args:
|
136
137
|
schema: IRSchemaObject with type information
|
138
|
+
parent_schema: Parent schema (for context about patch models, etc.)
|
137
139
|
|
138
140
|
Returns:
|
139
141
|
Zod validation code
|
@@ -151,6 +153,15 @@ class SchemasGenerator:
|
|
151
153
|
schema_type = schema.type
|
152
154
|
schema_format = schema.format
|
153
155
|
|
156
|
+
# Binary type (File/Blob for file uploads)
|
157
|
+
# NOTE: Skip binary validation for PATCH models (they use JSON, not multipart)
|
158
|
+
# PATCH models typically don't allow file uploads
|
159
|
+
parent_is_patch = parent_schema and parent_schema.is_patch_model if parent_schema else False
|
160
|
+
if schema.is_binary and not parent_is_patch:
|
161
|
+
# For multipart/form-data file uploads, use z.instanceof()
|
162
|
+
# This works for both File and Blob in browser/React Native
|
163
|
+
return "z.union([z.instanceof(File), z.instanceof(Blob)])"
|
164
|
+
|
154
165
|
# String types with format validation
|
155
166
|
if schema_type == "string":
|
156
167
|
base_type = "z.string()"
|
@@ -176,8 +187,8 @@ class SchemasGenerator:
|
|
176
187
|
|
177
188
|
# Add pattern validation
|
178
189
|
if schema.pattern:
|
179
|
-
# Escape
|
180
|
-
escaped_pattern = schema.pattern.replace('
|
190
|
+
# Escape forward slashes for JS regex literal
|
191
|
+
escaped_pattern = schema.pattern.replace('/', r'\/')
|
181
192
|
base_type = f"{base_type}.regex(/{escaped_pattern}/)"
|
182
193
|
|
183
194
|
return base_type
|
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.4.
|
7
|
+
version = "1.4.21"
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
9
9
|
readme = "README.md"
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-cfg
|
3
|
-
Version: 1.4.
|
3
|
+
Version: 1.4.21
|
4
4
|
Summary: Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django.
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
@@ -3,7 +3,7 @@ django_cfg/__init__.py,sha256=4dZgnuTlq8YmLISTJAqnPrr080kxPfmPKUjhiWhkEDc,1630
|
|
3
3
|
django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
|
4
4
|
django_cfg/config.py,sha256=3hX5bOCbOWdUvtD9Z5qEHEOEyWzY1-4CsvFs_EO7VSw,1398
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
6
|
-
django_cfg/apps/urls.py,sha256=
|
6
|
+
django_cfg/apps/urls.py,sha256=nQgaDWlr33h-fXawz6PA_KF5k_jfbfDVuinTPxCqcsw,4775
|
7
7
|
django_cfg/apps/accounts/README.md,sha256=YkUYJ3iKMYTmm9ALK2PDnX75SDqZxgnkzNLCD5efxRs,8227
|
8
8
|
django_cfg/apps/accounts/__init__.py,sha256=osecEQhMJVP8ejhZzElNsAqA1fX-GPD3K5_yNwDk6IE,100
|
9
9
|
django_cfg/apps/accounts/__models.py,sha256=65AomWYd78ptQ60drPbodxf0Ue310vmJQpQOPHL6V3E,10161
|
@@ -117,7 +117,9 @@ django_cfg/apps/api/health/views.py,sha256=D_HL7P4CrfV5tUqyto59MBCAKDoFG8x7PrQUb
|
|
117
117
|
django_cfg/apps/knowbase/README.md,sha256=HXt_J6WCN-LsMhA7p9mdvih07_vp_r_hkPdmqHhNEeo,3965
|
118
118
|
django_cfg/apps/knowbase/__init__.py,sha256=cfGnxDQwjajPhUoleKkgvdabJcB0LdXEglnsBojKkPo,1045
|
119
119
|
django_cfg/apps/knowbase/apps.py,sha256=FNvRmaUkZ64TierGSabWNb9IzzbDFMndscSldxUn1dU,3426
|
120
|
-
django_cfg/apps/knowbase/urls.py,sha256=
|
120
|
+
django_cfg/apps/knowbase/urls.py,sha256=lOMuEoNDIBE4Fhhr3aQ0RxWInNS1Ri4NYPgVLijsPuU,642
|
121
|
+
django_cfg/apps/knowbase/urls_admin.py,sha256=WN6Tm5lGlisrGGBK10PHCjPhztqQ2GotW6-WVUE5UHM,630
|
122
|
+
django_cfg/apps/knowbase/urls_system.py,sha256=c4zl7sMTJE0p-esoT2UC2V3qouVWJhi35z1Id1z4o4g,747
|
121
123
|
django_cfg/apps/knowbase/admin/__init__.py,sha256=NGYMzpLec7e8jYeQg3Gz5x-N-nMUePN6DHqzMHC4dAQ,499
|
122
124
|
django_cfg/apps/knowbase/admin/archive_admin.py,sha256=uQtK-6XRyiFR8H0polOwCKvufJE_pninjZL6sIlP-30,21239
|
123
125
|
django_cfg/apps/knowbase/admin/chat_admin.py,sha256=5iG5NEmTzQRlKjpy6wBjY8LLJkJNj1ZN9gT41dArtHY,16655
|
@@ -148,6 +150,7 @@ django_cfg/apps/knowbase/managers/document.py,sha256=Cj-e5r53jyZyNMXkygfVZERf7PV
|
|
148
150
|
django_cfg/apps/knowbase/managers/external_data.py,sha256=8Suu-_BXet08i8g7-2shZGL9XV6brrYKkzwHvDqV8bM,16111
|
149
151
|
django_cfg/apps/knowbase/migrations/0001_initial.py,sha256=SPL3_xB7uqmG3QRef24tPbfV4K-78bPRK7tD-f6i-ys,15817
|
150
152
|
django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py,sha256=memHiuUFiRVCri897eo-9_4X5S_wChR_xmTPvF-ys88,25960
|
153
|
+
django_cfg/apps/knowbase/migrations/0003_alter_documentarchive_archive_type.py,sha256=zadldeSIpEoSXSb5aIrh6fzMgeDuwsfQ0_3FPNYV_QU,760
|
151
154
|
django_cfg/apps/knowbase/migrations/__init__.py,sha256=IDW4uBY-1QLUKzBPzhiMkwg4K4YR79dwe9u_5vWFAdc,91
|
152
155
|
django_cfg/apps/knowbase/mixins/__init__.py,sha256=23W6yOJhnI64ZwojTRTfl2Nv1Ry86ucBTusfRRhxdOk,778
|
153
156
|
django_cfg/apps/knowbase/mixins/config.py,sha256=Lptwhneyg2sNv15BS2EO9d2Syrz0spC9rspzUGsmt-c,2960
|
@@ -163,7 +166,7 @@ django_cfg/apps/knowbase/mixins/generators/content_generator.py,sha256=gBog40cfL
|
|
163
166
|
django_cfg/apps/knowbase/mixins/generators/field_analyzer.py,sha256=-r1ovN1LwAJLjuT8fVwf0BKtoHJDl7NsFBESzPyh35c,2759
|
164
167
|
django_cfg/apps/knowbase/mixins/generators/metadata_generator.py,sha256=gfF9SoWtKuCHfoVPtWsH9vAyYntJ4git2tMmIJg77OE,4476
|
165
168
|
django_cfg/apps/knowbase/models/__init__.py,sha256=MnMzbOZTB3dlo0IU1VsgQVY1uPOgU6FtQFiAUyaK6AU,742
|
166
|
-
django_cfg/apps/knowbase/models/archive.py,sha256=
|
169
|
+
django_cfg/apps/knowbase/models/archive.py,sha256=CiXfTRacHe9PuaF6_W78uKKWg2TBm468i80Y6tkBF_k,18600
|
167
170
|
django_cfg/apps/knowbase/models/base.py,sha256=ojkeEige-Z4W4HV96FSL3phjipVUwbUzKjRzWl_28vM,1478
|
168
171
|
django_cfg/apps/knowbase/models/chat.py,sha256=HBiYxwLpve5VTIJkhHuGWCK_OEZZKWzmMKSe1jHxSpU,4578
|
169
172
|
django_cfg/apps/knowbase/models/document.py,sha256=NVVQIaZHJFPgyAVQe8sJK-faUVWrymKagV6AIJUZA-0,8368
|
@@ -224,7 +227,7 @@ django_cfg/apps/knowbase/utils/validation.py,sha256=VYU91B3S3np-GzeY6yRH9ElHfsqR
|
|
224
227
|
django_cfg/apps/knowbase/views/__init__.py,sha256=9Ra0MqUmcqClN58BJkrXchgX5dvLToVfeWN23kYGjXc,505
|
225
228
|
django_cfg/apps/knowbase/views/archive_views.py,sha256=38pwzop2T3irr2yiApjpvNaRoT7H5-Tx5ye7-WquDG4,17575
|
226
229
|
django_cfg/apps/knowbase/views/base.py,sha256=qkzq_ugpCqpcEWGCCpDnllPP4SoWiyXbVYgEZfUiLps,1709
|
227
|
-
django_cfg/apps/knowbase/views/chat_views.py,sha256=
|
230
|
+
django_cfg/apps/knowbase/views/chat_views.py,sha256=jD5RpzS8LNsWzmRSKrmDovAAnJVuuLXbbCrjRfYJhv4,6446
|
228
231
|
django_cfg/apps/knowbase/views/document_views.py,sha256=zg0zzGvQtr7bWpieF37rSu9XP1Z5nhWLxninEQFz-vw,6392
|
229
232
|
django_cfg/apps/knowbase/views/public_views.py,sha256=eNiDodCxzyl6NdPjnYxj8M0jsJ0NkqAmp5wt1pJuIoE,4506
|
230
233
|
django_cfg/apps/leads/README.md,sha256=QjYHj_33zQyKzOtPeUqbkTy0fvLgIexKH7JxnzxEKTY,3444
|
@@ -434,7 +437,7 @@ django_cfg/apps/payments/services/types/requests.py,sha256=WLZgPz07lSq-qTeQAThm9
|
|
434
437
|
django_cfg/apps/payments/services/types/responses.py,sha256=3FdyNOGQEoI8uZNjBPhhw1XtXqs-2lo02qZ5TQg-CqY,7947
|
435
438
|
django_cfg/apps/payments/services/types/webhooks.py,sha256=y7R1CoAZBRn71y9Mc8k6VVj5rjpbQ1ZHJkNHD1ZyK3M,10095
|
436
439
|
django_cfg/apps/payments/signals/__init__.py,sha256=Lg8kfwNWqRQN8p_CxdsdsAIfhX39ieB9jK1dn3n7N_A,934
|
437
|
-
django_cfg/apps/payments/signals/api_key_signals.py,sha256=
|
440
|
+
django_cfg/apps/payments/signals/api_key_signals.py,sha256=_24HixaZmq-d4_WB5O-VSIMAFnqAKycwUViB8W0pVd8,8600
|
438
441
|
django_cfg/apps/payments/signals/balance_signals.py,sha256=jOaWrsC0KMHbJiZ3HU_MbRkEcsVb-wkgQyRUSj0G_-Q,6258
|
439
442
|
django_cfg/apps/payments/signals/payment_signals.py,sha256=j5k8lZ3v8Ko47s-xQKPTUfffwzIk-IxhD7hweR8xOBs,6788
|
440
443
|
django_cfg/apps/payments/signals/subscription_signals.py,sha256=x_gk6E5G7KvWf58mF8lAljAh6Cob-au9M3LnrYk7pQQ,9062
|
@@ -501,7 +504,7 @@ django_cfg/apps/support/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
|
|
501
504
|
django_cfg/apps/support/utils/support_email_service.py,sha256=Zq37yEeHYICsvHF4kkYIA3mZDi2iOSWaHv7vaO-AHUE,5569
|
502
505
|
django_cfg/apps/support/views/__init__.py,sha256=E-Iqiy888SncfBTiSyXyGCjaNrMa5Klvapzb3ItYPXc,448
|
503
506
|
django_cfg/apps/support/views/admin.py,sha256=dEKJsvuSWEtBOQF5Nat0uJosZ0cMAqk0NNcy2nyIDhI,1623
|
504
|
-
django_cfg/apps/support/views/api.py,sha256=
|
507
|
+
django_cfg/apps/support/views/api.py,sha256=JkJzgJgD-gVKUNXATu8mE0rSrV6WwngJAGiVvhU4r2s,5340
|
505
508
|
django_cfg/apps/support/views/chat.py,sha256=pDSVDxndArqQ6cp1GeAXgIz4QKsItJ8eX6kq0V47UH0,3369
|
506
509
|
django_cfg/apps/tasks/__init__.py,sha256=nY0Mp7b64LfuMB1Pbv_C6SBEdueMIYdfFvbORM81OQM,85
|
507
510
|
django_cfg/apps/tasks/apps.py,sha256=WXQvQjFVBuiO7s1ikd4Sf15YKRTvruVzMTo82uNXY08,383
|
@@ -760,14 +763,14 @@ django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.
|
|
760
763
|
django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja,sha256=xrnhAc-hNk6DigxHmTvhR3dyDFI9ocnkhL-_Hz9hCs8,270
|
761
764
|
django_cfg/modules/django_client/core/generator/typescript/__init__.py,sha256=eHOZp7M65WZ9u3tA_xQlON5-oijZZiGXDhz22Bq73s0,371
|
762
765
|
django_cfg/modules/django_client/core/generator/typescript/client_generator.py,sha256=7ql-m59YVt6zGKfVBCxy1OR3CNy6C9lkaMEUqexiRvo,5878
|
763
|
-
django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py,sha256=
|
766
|
+
django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py,sha256=2Gd-AIKx5m_0q5bVPr-PbQQjqAeRwL-dJgfbnIHAFHs,16445
|
764
767
|
django_cfg/modules/django_client/core/generator/typescript/files_generator.py,sha256=faRdhVVf9GQ-0esVz94dsaQMB56zK3csyNkhEHL4al4,7044
|
765
768
|
django_cfg/modules/django_client/core/generator/typescript/generator.py,sha256=oBM7CKlsD7arzIt0dSeow7Ujuo6RWl6h5to221Q19Gc,16984
|
766
|
-
django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py,sha256=
|
767
|
-
django_cfg/modules/django_client/core/generator/typescript/models_generator.py,sha256=
|
769
|
+
django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py,sha256=woTohitYEyo6R7s-XqO6o9KC5u-nbo2lAd1jAchvT-U,15694
|
770
|
+
django_cfg/modules/django_client/core/generator/typescript/models_generator.py,sha256=dvfBsXBds_SvGBXrmcIcODMgQMUYAMsHuqBo9Z5F80c,9131
|
768
771
|
django_cfg/modules/django_client/core/generator/typescript/naming.py,sha256=uUh2XVmorQ8gzfdzZJB8KNPng6UI4axe3lEaFtTqYks,2592
|
769
|
-
django_cfg/modules/django_client/core/generator/typescript/operations_generator.py,sha256=
|
770
|
-
django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py,sha256=
|
772
|
+
django_cfg/modules/django_client/core/generator/typescript/operations_generator.py,sha256=Nwcx0m4Z0fnMEiwbsZJW9m4Ut6XBD-Hw4WPxXuPIEVg,16354
|
773
|
+
django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py,sha256=DnattUopGrSazcLIIFbRC-BG2aD1DrZ_D0osp9Z8D1E,12041
|
771
774
|
django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja,sha256=OPUjnz6Dk3kY97UFIRcgvxkEIKd6fUGqBzXJWOXKykE,2906
|
772
775
|
django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja,sha256=gLsoYyEzKD6Gv64vsO9sQHMPiFMGdaB5XVufLHeRyvQ,62
|
773
776
|
django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja,sha256=LHUt72fO2eRNAHYEscIYvqVR69GC6mxqjcgSlUzeCtc,251
|
@@ -1100,9 +1103,9 @@ django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaN
|
|
1100
1103
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
1101
1104
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
1102
1105
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
1103
|
-
django_cfg/pyproject.toml,sha256=
|
1104
|
-
django_cfg-1.4.
|
1105
|
-
django_cfg-1.4.
|
1106
|
-
django_cfg-1.4.
|
1107
|
-
django_cfg-1.4.
|
1108
|
-
django_cfg-1.4.
|
1106
|
+
django_cfg/pyproject.toml,sha256=j1l8H5W_LuQyzUF6eJPq42OTdpvk9QQfwniyOUAJuXQ,8210
|
1107
|
+
django_cfg-1.4.21.dist-info/METADATA,sha256=S5uhOrdaGJxz1XPOkJRPfJ4pjKv4DAEknWemC3Ht4J8,22533
|
1108
|
+
django_cfg-1.4.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
1109
|
+
django_cfg-1.4.21.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
1110
|
+
django_cfg-1.4.21.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
1111
|
+
django_cfg-1.4.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|