django-cfg 1.4.14__py3-none-any.whl → 1.4.17__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/leads/urls.py +2 -1
- django_cfg/apps/payments/urls.py +4 -4
- django_cfg/core/base/config_model.py +7 -7
- django_cfg/core/generation/core_generators/settings.py +2 -3
- django_cfg/core/generation/core_generators/static.py +9 -9
- django_cfg/core/generation/integration_generators/api.py +1 -1
- django_cfg/models/infrastructure/security.py +33 -2
- django_cfg/modules/django_client/core/generator/base.py +18 -20
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +56 -113
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +102 -232
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +13 -9
- django_cfg/modules/django_client/core/generator/typescript/naming.py +83 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +8 -4
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +3 -6
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +25 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/index.ts.jinja +29 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/mutation_hook.ts.jinja +25 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/query_hook.ts.jinja +21 -0
- django_cfg/modules/django_client/core/groups/manager.py +51 -26
- django_cfg/modules/django_client/spectacular/__init__.py +3 -2
- django_cfg/modules/django_client/spectacular/schema.py +50 -0
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.14.dist-info → django_cfg-1.4.17.dist-info}/METADATA +1 -1
- {django_cfg-1.4.14.dist-info → django_cfg-1.4.17.dist-info}/RECORD +28 -21
- {django_cfg-1.4.14.dist-info → django_cfg-1.4.17.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.14.dist-info → django_cfg-1.4.17.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.14.dist-info → django_cfg-1.4.17.dist-info}/licenses/LICENSE +0 -0
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
19
19
|
from jinja2 import Environment
|
20
20
|
from ..base import BaseGenerator, GeneratedFile
|
21
21
|
from ...ir import IROperationObject, IRContext
|
22
|
-
|
22
|
+
from .naming import operation_to_method_name
|
23
23
|
|
24
24
|
class HooksGenerator:
|
25
25
|
"""
|
@@ -39,7 +39,7 @@ class HooksGenerator:
|
|
39
39
|
|
40
40
|
def generate_query_hook(self, operation: IROperationObject) -> str:
|
41
41
|
"""
|
42
|
-
Generate useSWR hook for GET operation.
|
42
|
+
Generate useSWR hook for GET operation using Jinja2 template.
|
43
43
|
|
44
44
|
Examples:
|
45
45
|
>>> generate_query_hook(users_list)
|
@@ -65,44 +65,21 @@ class HooksGenerator:
|
|
65
65
|
# Get SWR key
|
66
66
|
swr_key = self._generate_swr_key(operation)
|
67
67
|
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
# Hook signature
|
81
|
-
if param_info['func_params']:
|
82
|
-
lines.append(f"export function {hook_name}({param_info['func_params']}) {{")
|
83
|
-
else:
|
84
|
-
lines.append(f"export function {hook_name}() {{")
|
85
|
-
|
86
|
-
# useSWR call
|
87
|
-
fetcher_params = param_info['fetcher_params']
|
88
|
-
if fetcher_params:
|
89
|
-
lines.append(f" return useSWR<{response_type}>(")
|
90
|
-
lines.append(f" {swr_key},")
|
91
|
-
lines.append(f" () => Fetchers.{fetcher_name}({fetcher_params})")
|
92
|
-
lines.append(" )")
|
93
|
-
else:
|
94
|
-
lines.append(f" return useSWR<{response_type}>(")
|
95
|
-
lines.append(f" {swr_key},")
|
96
|
-
lines.append(f" () => Fetchers.{fetcher_name}()")
|
97
|
-
lines.append(" )")
|
98
|
-
|
99
|
-
lines.append("}")
|
100
|
-
|
101
|
-
return "\n".join(lines)
|
68
|
+
# Render template
|
69
|
+
template = self.jinja_env.get_template('hooks/query_hook.ts.jinja')
|
70
|
+
return template.render(
|
71
|
+
operation=operation,
|
72
|
+
hook_name=hook_name,
|
73
|
+
fetcher_name=fetcher_name,
|
74
|
+
func_params=param_info['func_params'],
|
75
|
+
fetcher_params=param_info['fetcher_params'],
|
76
|
+
response_type=response_type,
|
77
|
+
swr_key=swr_key
|
78
|
+
)
|
102
79
|
|
103
80
|
def generate_mutation_hook(self, operation: IROperationObject) -> str:
|
104
81
|
"""
|
105
|
-
Generate mutation hook for POST/PUT/PATCH/DELETE.
|
82
|
+
Generate mutation hook for POST/PUT/PATCH/DELETE using Jinja2 template.
|
106
83
|
|
107
84
|
Examples:
|
108
85
|
>>> generate_mutation_hook(users_create)
|
@@ -131,114 +108,84 @@ class HooksGenerator:
|
|
131
108
|
# Get revalidation keys
|
132
109
|
revalidation_keys = self._get_revalidation_keys(operation)
|
133
110
|
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# Hook signature
|
147
|
-
lines.append(f"export function {hook_name}() {{")
|
148
|
-
lines.append(" const { mutate } = useSWRConfig()")
|
149
|
-
lines.append("")
|
150
|
-
|
151
|
-
# Return async function
|
152
|
-
if param_info['func_params']:
|
153
|
-
lines.append(f" return async ({param_info['func_params']}): Promise<{response_type}> => {{")
|
154
|
-
else:
|
155
|
-
lines.append(f" return async (): Promise<{response_type}> => {{")
|
156
|
-
|
157
|
-
# Call fetcher
|
158
|
-
fetcher_params = param_info['fetcher_params']
|
159
|
-
if fetcher_params:
|
160
|
-
lines.append(f" const result = await Fetchers.{fetcher_name}({fetcher_params})")
|
161
|
-
else:
|
162
|
-
lines.append(f" const result = await Fetchers.{fetcher_name}()")
|
163
|
-
|
164
|
-
# Revalidate
|
165
|
-
if revalidation_keys:
|
166
|
-
lines.append("")
|
167
|
-
lines.append(" // Revalidate related queries")
|
168
|
-
for key in revalidation_keys:
|
169
|
-
lines.append(f" mutate('{key}')")
|
170
|
-
|
171
|
-
lines.append("")
|
172
|
-
lines.append(" return result")
|
173
|
-
lines.append(" }")
|
174
|
-
lines.append("}")
|
175
|
-
|
176
|
-
return "\n".join(lines)
|
111
|
+
# Render template
|
112
|
+
template = self.jinja_env.get_template('hooks/mutation_hook.ts.jinja')
|
113
|
+
return template.render(
|
114
|
+
operation=operation,
|
115
|
+
hook_name=hook_name,
|
116
|
+
fetcher_name=fetcher_name,
|
117
|
+
func_params=param_info['func_params'],
|
118
|
+
fetcher_params=param_info['fetcher_params'],
|
119
|
+
response_type=response_type,
|
120
|
+
revalidation_keys=revalidation_keys
|
121
|
+
)
|
177
122
|
|
178
123
|
def _operation_to_hook_name(self, operation: IROperationObject) -> str:
|
179
124
|
"""
|
180
125
|
Convert operation to hook name.
|
181
|
-
|
126
|
+
|
127
|
+
Hooks are organized into tag-specific files but also exported globally,
|
128
|
+
so we include the tag in the name to avoid collisions.
|
129
|
+
|
182
130
|
Examples:
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
users_update (PUT) -> useUpdateUsers
|
187
|
-
users_partial_update (PATCH) -> usePartialUpdateUsers
|
188
|
-
users_destroy (DELETE) -> useDeleteUsers
|
131
|
+
cfg_support_tickets_list -> useSupportTicketsList
|
132
|
+
cfg_health_drf_retrieve -> useHealthDrf
|
133
|
+
cfg_accounts_otp_request_create -> useCreateAccountsOtpRequest
|
189
134
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
if
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
elif
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
resource = op_id.removesuffix("_destroy")
|
211
|
-
return f"useDelete{self._to_pascal_case(resource)}"
|
135
|
+
|
136
|
+
# Remove cfg_ prefix but keep tag + resource for uniqueness (same as fetchers)
|
137
|
+
operation_id = operation.operation_id
|
138
|
+
if operation_id.startswith('django_cfg_'):
|
139
|
+
operation_id = operation_id.replace('django_cfg_', '', 1)
|
140
|
+
elif operation_id.startswith('cfg_'):
|
141
|
+
operation_id = operation_id.replace('cfg_', '', 1)
|
142
|
+
|
143
|
+
# Determine prefix based on HTTP method
|
144
|
+
if operation.http_method == 'GET':
|
145
|
+
prefix = 'use'
|
146
|
+
elif operation.http_method == 'POST':
|
147
|
+
prefix = 'useCreate'
|
148
|
+
elif operation.http_method in ('PUT', 'PATCH'):
|
149
|
+
if '_partial_update' in operation_id:
|
150
|
+
prefix = 'usePartialUpdate'
|
151
|
+
else:
|
152
|
+
prefix = 'useUpdate'
|
153
|
+
elif operation.http_method == 'DELETE':
|
154
|
+
prefix = 'useDelete'
|
212
155
|
else:
|
213
|
-
|
214
|
-
|
156
|
+
prefix = 'use'
|
157
|
+
|
158
|
+
# For hooks, path is not critical but pass for consistency
|
159
|
+
return operation_to_method_name(operation_id, operation.http_method, prefix, self.base, operation.path)
|
215
160
|
|
216
161
|
def _operation_to_fetcher_name(self, operation: IROperationObject) -> str:
|
217
162
|
"""Get corresponding fetcher function name (must match fetchers_generator logic)."""
|
218
|
-
|
219
|
-
|
220
|
-
#
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
elif
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
elif
|
238
|
-
|
239
|
-
return f"delete{self._to_pascal_case(resource)}"
|
163
|
+
|
164
|
+
|
165
|
+
# Remove cfg_ prefix but keep tag + resource (must match fetchers_generator exactly)
|
166
|
+
operation_id = operation.operation_id
|
167
|
+
if operation_id.startswith('django_cfg_'):
|
168
|
+
operation_id = operation_id.replace('django_cfg_', '', 1)
|
169
|
+
elif operation_id.startswith('cfg_'):
|
170
|
+
operation_id = operation_id.replace('cfg_', '', 1)
|
171
|
+
|
172
|
+
# Determine prefix (must match fetchers_generator exactly)
|
173
|
+
if operation.http_method == 'GET':
|
174
|
+
prefix = 'get'
|
175
|
+
elif operation.http_method == 'POST':
|
176
|
+
prefix = 'create'
|
177
|
+
elif operation.http_method in ('PUT', 'PATCH'):
|
178
|
+
if '_partial_update' in operation_id:
|
179
|
+
prefix = 'partialUpdate'
|
180
|
+
else:
|
181
|
+
prefix = 'update'
|
182
|
+
elif operation.http_method == 'DELETE':
|
183
|
+
prefix = 'delete'
|
240
184
|
else:
|
241
|
-
|
185
|
+
prefix = ''
|
186
|
+
|
187
|
+
# Must match fetchers exactly, including path
|
188
|
+
return operation_to_method_name(operation_id, operation.http_method, prefix, self.base, operation.path)
|
242
189
|
|
243
190
|
def _get_param_info(self, operation: IROperationObject) -> dict:
|
244
191
|
"""
|
@@ -385,22 +332,13 @@ class HooksGenerator:
|
|
385
332
|
|
386
333
|
return keys
|
387
334
|
|
388
|
-
def _to_pascal_case(self, snake_str: str) -> str:
|
389
|
-
"""Convert snake_case to PascalCase."""
|
390
|
-
return ''.join(word.capitalize() for word in snake_str.split('_'))
|
391
|
-
|
392
|
-
def _to_camel_case(self, snake_str: str) -> str:
|
393
|
-
"""Convert snake_case to camelCase."""
|
394
|
-
components = snake_str.split('_')
|
395
|
-
return components[0] + ''.join(x.capitalize() for x in components[1:])
|
396
|
-
|
397
335
|
def generate_tag_hooks_file(
|
398
336
|
self,
|
399
337
|
tag: str,
|
400
338
|
operations: list[IROperationObject],
|
401
339
|
) -> GeneratedFile:
|
402
340
|
"""
|
403
|
-
Generate hooks file for a specific tag/resource.
|
341
|
+
Generate hooks file for a specific tag/resource using Jinja2 template.
|
404
342
|
|
405
343
|
Args:
|
406
344
|
tag: Tag name (e.g., "shop_products")
|
@@ -410,8 +348,7 @@ class HooksGenerator:
|
|
410
348
|
GeneratedFile with hooks
|
411
349
|
"""
|
412
350
|
# Separate queries and mutations & collect schema names
|
413
|
-
|
414
|
-
mutation_hooks = []
|
351
|
+
hooks = []
|
415
352
|
schema_names = set()
|
416
353
|
|
417
354
|
for operation in operations:
|
@@ -430,69 +367,28 @@ class HooksGenerator:
|
|
430
367
|
|
431
368
|
# Generate hook
|
432
369
|
if operation.http_method == "GET":
|
433
|
-
|
370
|
+
hooks.append(self.generate_query_hook(operation))
|
434
371
|
else:
|
435
|
-
|
372
|
+
hooks.append(self.generate_mutation_hook(operation))
|
436
373
|
|
437
374
|
# Get display name for documentation
|
438
375
|
tag_display_name = self.base.tag_to_display_name(tag)
|
439
|
-
|
440
|
-
#
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
lines.append(" * configureAPI({ baseUrl: 'https://api.example.com' })")
|
454
|
-
lines.append(" * ```")
|
455
|
-
lines.append(" *")
|
456
|
-
lines.append(" * Usage:")
|
457
|
-
lines.append(" * ```typescript")
|
458
|
-
lines.append(" * // Query hook")
|
459
|
-
lines.append(" * const { data, error, mutate } = useShopProducts({ page: 1 })")
|
460
|
-
lines.append(" *")
|
461
|
-
lines.append(" * // Mutation hook")
|
462
|
-
lines.append(" * const createProduct = useCreateShopProduct()")
|
463
|
-
lines.append(" * await createProduct({ name: 'Product', price: 99 })")
|
464
|
-
lines.append(" * ```")
|
465
|
-
lines.append(" */")
|
466
|
-
|
467
|
-
# Import types from schemas
|
468
|
-
for schema_name in sorted(schema_names):
|
469
|
-
lines.append(f"import type {{ {schema_name} }} from '../schemas/{schema_name}.schema'")
|
470
|
-
|
471
|
-
lines.append("import useSWR from 'swr'")
|
472
|
-
lines.append("import { useSWRConfig } from 'swr'")
|
473
|
-
lines.append("import * as Fetchers from '../fetchers'")
|
474
|
-
lines.append("")
|
475
|
-
|
476
|
-
# Query hooks
|
477
|
-
if query_hooks:
|
478
|
-
lines.append("// ===== Query Hooks (GET) =====")
|
479
|
-
lines.append("")
|
480
|
-
for hook in query_hooks:
|
481
|
-
lines.append(hook)
|
482
|
-
lines.append("")
|
483
|
-
|
484
|
-
# Mutation hooks
|
485
|
-
if mutation_hooks:
|
486
|
-
lines.append("// ===== Mutation Hooks (POST/PUT/PATCH/DELETE) =====")
|
487
|
-
lines.append("")
|
488
|
-
for hook in mutation_hooks:
|
489
|
-
lines.append(hook)
|
490
|
-
lines.append("")
|
491
|
-
|
492
|
-
content = "\n".join(lines)
|
376
|
+
|
377
|
+
# Get tag file name for fetchers import
|
378
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
379
|
+
tag_file = folder_name
|
380
|
+
|
381
|
+
# Render template
|
382
|
+
template = self.jinja_env.get_template('hooks/hooks.ts.jinja')
|
383
|
+
content = template.render(
|
384
|
+
tag_display_name=tag_display_name,
|
385
|
+
tag_file=tag_file,
|
386
|
+
has_schemas=bool(schema_names),
|
387
|
+
schema_names=sorted(schema_names),
|
388
|
+
hooks=hooks
|
389
|
+
)
|
493
390
|
|
494
391
|
# Get file path (use same naming as APIClient)
|
495
|
-
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
496
392
|
file_path = f"_utils/hooks/{folder_name}.ts"
|
497
393
|
|
498
394
|
return GeneratedFile(
|
@@ -502,35 +398,9 @@ class HooksGenerator:
|
|
502
398
|
)
|
503
399
|
|
504
400
|
def generate_hooks_index_file(self, module_names: list[str]) -> GeneratedFile:
|
505
|
-
"""Generate index.ts for hooks folder."""
|
506
|
-
|
507
|
-
|
508
|
-
lines.append("/**")
|
509
|
-
lines.append(" * SWR Hooks - React hooks for data fetching")
|
510
|
-
lines.append(" *")
|
511
|
-
lines.append(" * Auto-generated from OpenAPI specification.")
|
512
|
-
lines.append(" * These hooks use SWR for data fetching and caching.")
|
513
|
-
lines.append(" *")
|
514
|
-
lines.append(" * Usage:")
|
515
|
-
lines.append(" * ```typescript")
|
516
|
-
lines.append(" * import { useShopProducts } from './_utils/hooks'")
|
517
|
-
lines.append(" *")
|
518
|
-
lines.append(" * function ProductsPage() {")
|
519
|
-
lines.append(" * const { data, error } = useShopProducts({ page: 1 })")
|
520
|
-
lines.append(" * if (error) return <Error />")
|
521
|
-
lines.append(" * if (!data) return <Loading />")
|
522
|
-
lines.append(" * return <ProductList products={data.results} />")
|
523
|
-
lines.append(" * }")
|
524
|
-
lines.append(" * ```")
|
525
|
-
lines.append(" */")
|
526
|
-
lines.append("")
|
527
|
-
|
528
|
-
for module_name in module_names:
|
529
|
-
lines.append(f"export * from './{module_name}'")
|
530
|
-
|
531
|
-
lines.append("")
|
532
|
-
|
533
|
-
content = "\n".join(lines)
|
401
|
+
"""Generate index.ts for hooks folder using Jinja2 template."""
|
402
|
+
template = self.jinja_env.get_template('hooks/index.ts.jinja')
|
403
|
+
content = template.render(modules=module_names)
|
534
404
|
|
535
405
|
return GeneratedFile(
|
536
406
|
path="_utils/hooks/index.ts",
|
@@ -140,35 +140,39 @@ class ModelsGenerator:
|
|
140
140
|
Examples:
|
141
141
|
id: number;
|
142
142
|
username: string;
|
143
|
-
email?: string
|
143
|
+
email?: string;
|
144
144
|
status: Enums.StatusEnum;
|
145
145
|
"""
|
146
146
|
# Check if this field is an enum
|
147
147
|
if schema.enum and schema.name:
|
148
148
|
# Use enum type from shared enums (sanitized)
|
149
149
|
ts_type = f"Enums.{self.base.sanitize_enum_name(schema.name)}"
|
150
|
-
|
151
|
-
ts_type = f"{ts_type} | null"
|
150
|
+
# Don't add | null for nullable - use optional marker instead
|
152
151
|
# Check if this field is a reference to an enum (via $ref)
|
153
152
|
elif schema.ref and schema.ref in self.context.schemas:
|
154
153
|
ref_schema = self.context.schemas[schema.ref]
|
155
154
|
if ref_schema.enum:
|
156
155
|
# This is a reference to an enum component (sanitized to PascalCase)
|
157
156
|
ts_type = f"Enums.{self.base.sanitize_enum_name(schema.ref)}"
|
158
|
-
|
159
|
-
ts_type = f"{ts_type} | null"
|
157
|
+
# Don't add | null for nullable - use optional marker instead
|
160
158
|
else:
|
161
|
-
# Regular reference
|
159
|
+
# Regular reference - get base type without | null
|
162
160
|
ts_type = schema.typescript_type
|
161
|
+
# Remove | null suffix if present (we'll use optional marker instead)
|
162
|
+
if ts_type.endswith(" | null"):
|
163
|
+
ts_type = ts_type[:-7] # Remove " | null"
|
163
164
|
else:
|
164
|
-
# Get TypeScript type
|
165
|
+
# Get TypeScript type and remove | null suffix if present
|
165
166
|
ts_type = schema.typescript_type
|
167
|
+
if ts_type.endswith(" | null"):
|
168
|
+
ts_type = ts_type[:-7] # Remove " | null"
|
166
169
|
|
167
170
|
# Check if required
|
168
171
|
is_required = name in required_fields
|
169
172
|
|
170
|
-
# Optional marker
|
171
|
-
|
173
|
+
# Optional marker - use for both non-required AND nullable fields
|
174
|
+
# This converts Django's nullable=True to TypeScript's optional (undefined)
|
175
|
+
optional_marker = "" if is_required and not schema.nullable else "?"
|
172
176
|
|
173
177
|
# Comment
|
174
178
|
if schema.description:
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"""
|
2
|
+
Simple naming strategy for TypeScript code generation.
|
3
|
+
|
4
|
+
Strategy: Use full operation_id, remove tag prefix, convert to camelCase/PascalCase.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import re
|
8
|
+
|
9
|
+
|
10
|
+
def to_camel_case(s: str) -> str:
|
11
|
+
"""Convert snake_case or kebab-case to camelCase."""
|
12
|
+
s = s.replace('-', '_')
|
13
|
+
parts = s.split('_')
|
14
|
+
if not parts:
|
15
|
+
return ''
|
16
|
+
return parts[0].lower() + ''.join(p.capitalize() for p in parts[1:])
|
17
|
+
|
18
|
+
|
19
|
+
def to_pascal_case(s: str) -> str:
|
20
|
+
"""Convert snake_case or kebab-case to PascalCase."""
|
21
|
+
s = s.replace('-', '_')
|
22
|
+
return ''.join(p.capitalize() for p in s.split('_'))
|
23
|
+
|
24
|
+
|
25
|
+
def remove_tag_prefix(operation_id: str) -> str:
|
26
|
+
"""
|
27
|
+
Remove common tag prefixes from operation_id.
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
cfg_newsletter_campaigns_list -> newsletter_campaigns_list
|
31
|
+
django_cfg_accounts_login -> accounts_login
|
32
|
+
newsletter_campaigns_send_create -> newsletter_campaigns_send_create
|
33
|
+
"""
|
34
|
+
# Remove cfg_ or django_cfg_ prefix
|
35
|
+
if operation_id.startswith('django_cfg_'):
|
36
|
+
return operation_id[11:] # len('django_cfg_')
|
37
|
+
elif operation_id.startswith('cfg_'):
|
38
|
+
return operation_id[4:] # len('cfg_')
|
39
|
+
return operation_id
|
40
|
+
|
41
|
+
|
42
|
+
def operation_to_method_name(
|
43
|
+
operation_id: str,
|
44
|
+
http_method: str,
|
45
|
+
prefix: str,
|
46
|
+
base_generator,
|
47
|
+
path: str = ''
|
48
|
+
) -> str:
|
49
|
+
"""
|
50
|
+
Simple naming: remove tag prefix, convert to camelCase/PascalCase.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
operation_id: Full operation ID from OpenAPI
|
54
|
+
http_method: HTTP method (GET, POST, PUT, PATCH, DELETE)
|
55
|
+
prefix: Function prefix ('', 'get', 'create', 'use', etc.)
|
56
|
+
base_generator: Base generator instance (unused)
|
57
|
+
path: URL path (unused)
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
Method name in camelCase (for client) or PascalCase (for fetchers/hooks)
|
61
|
+
|
62
|
+
Examples:
|
63
|
+
# Client methods (prefix='')
|
64
|
+
cfg_newsletter_campaigns_list -> newsletterCampaignsList
|
65
|
+
cfg_newsletter_campaigns_send_create -> newsletterCampaignsSendCreate
|
66
|
+
cfg_accounts_otp_request_create -> accountsOtpRequestCreate
|
67
|
+
|
68
|
+
# Fetchers (prefix='get')
|
69
|
+
cfg_newsletter_campaigns_list -> getNewsletterCampaignsList
|
70
|
+
|
71
|
+
# Hooks (prefix='use')
|
72
|
+
cfg_newsletter_campaigns_list -> useNewsletterCampaignsList
|
73
|
+
"""
|
74
|
+
# Remove tag prefix
|
75
|
+
clean_id = remove_tag_prefix(operation_id)
|
76
|
+
|
77
|
+
# For client methods (no prefix): camelCase
|
78
|
+
if not prefix:
|
79
|
+
return to_camel_case(clean_id)
|
80
|
+
|
81
|
+
# For fetchers/hooks (with prefix): prefix + PascalCase
|
82
|
+
return f"{prefix}{to_pascal_case(clean_id)}"
|
83
|
+
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
6
6
|
|
7
7
|
from jinja2 import Environment
|
8
8
|
from ...ir import IROperationObject
|
9
|
-
|
9
|
+
from .naming import operation_to_method_name
|
10
10
|
|
11
11
|
class OperationsGenerator:
|
12
12
|
"""Generates TypeScript async operation methods."""
|
@@ -18,15 +18,19 @@ class OperationsGenerator:
|
|
18
18
|
|
19
19
|
def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False, in_subclient: bool = False) -> str:
|
20
20
|
"""Generate async method for operation."""
|
21
|
-
|
21
|
+
|
22
|
+
|
23
|
+
# Get method name using universal logic
|
24
|
+
# For client methods, we use empty prefix to get short names: list, create, retrieve
|
22
25
|
operation_id = operation.operation_id
|
23
26
|
if remove_tag_prefix and operation.tags:
|
24
27
|
# Remove tag prefix using base class method
|
25
28
|
tag = operation.tags[0]
|
26
29
|
operation_id = self.base.remove_tag_prefix(operation_id, tag)
|
27
30
|
|
28
|
-
#
|
29
|
-
|
31
|
+
# Use universal naming function with empty prefix for client methods
|
32
|
+
# Pass path to distinguish custom actions
|
33
|
+
method_name = operation_to_method_name(operation_id, operation.http_method, '', self.base, operation.path)
|
30
34
|
|
31
35
|
# Request method prefix
|
32
36
|
request_prefix = "this.client" if in_subclient else "this"
|
@@ -117,14 +117,11 @@ class SchemasGenerator:
|
|
117
117
|
# Check if required
|
118
118
|
is_required = name in required_fields
|
119
119
|
|
120
|
-
# Handle optional fields
|
121
|
-
|
120
|
+
# Handle optional fields - use .optional() for both non-required AND nullable
|
121
|
+
# This converts Django's nullable=True to TypeScript's optional (undefined)
|
122
|
+
if not is_required or schema.nullable:
|
122
123
|
zod_type = f"{zod_type}.optional()"
|
123
124
|
|
124
|
-
# Handle nullable fields
|
125
|
-
if schema.nullable:
|
126
|
-
zod_type = f"{zod_type}.nullable()"
|
127
|
-
|
128
125
|
return f"{name}: {zod_type}"
|
129
126
|
|
130
127
|
def _map_type_to_zod(self, schema: IRSchemaObject) -> str:
|
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
/**
|
2
|
+
* {{ operation.summary or 'API operation' }}
|
3
|
+
*
|
4
|
+
* @method {{ operation.http_method }}
|
5
|
+
* @path {{ operation.path }}
|
6
|
+
*/
|
7
|
+
export async function {{ func_name }}(
|
8
|
+
{%- if func_params %}
|
9
|
+
{{ func_params }},
|
10
|
+
{%- endif %}
|
11
|
+
client?: API
|
12
|
+
): Promise<{{ response_type }}> {
|
13
|
+
const api = client || getAPIInstance()
|
14
|
+
{% if api_call_params %}
|
15
|
+
const response = await {{ api_call }}({{ api_call_params }})
|
16
|
+
{% else %}
|
17
|
+
const response = await {{ api_call }}()
|
18
|
+
{% endif %}
|
19
|
+
{% if response_schema %}
|
20
|
+
return {{ response_schema }}.parse(response)
|
21
|
+
{% else %}
|
22
|
+
return response
|
23
|
+
{% endif %}
|
24
|
+
}
|
25
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
/**
|
2
|
+
* SWR Hooks for {{ tag_display_name }}
|
3
|
+
*
|
4
|
+
* React hooks powered by SWR for data fetching with automatic caching,
|
5
|
+
* revalidation, and optimistic updates.
|
6
|
+
*
|
7
|
+
* Usage:
|
8
|
+
* ```typescript
|
9
|
+
* // Query hooks (GET)
|
10
|
+
* const { data, error, isLoading } = useUsers({ page: 1 })
|
11
|
+
*
|
12
|
+
* // Mutation hooks (POST/PUT/PATCH/DELETE)
|
13
|
+
* const createUser = useCreateUser()
|
14
|
+
* await createUser({ name: 'John', email: 'john@example.com' })
|
15
|
+
* ```
|
16
|
+
*/
|
17
|
+
import useSWR from 'swr'
|
18
|
+
import { useSWRConfig } from 'swr'
|
19
|
+
import * as Fetchers from '../fetchers/{{ tag_file }}'
|
20
|
+
{% if has_schemas %}
|
21
|
+
{% for schema_name in schema_names %}
|
22
|
+
import type { {{ schema_name }} } from '../schemas/{{ schema_name }}.schema'
|
23
|
+
{% endfor %}
|
24
|
+
{% endif %}
|
25
|
+
|
26
|
+
{% for hook in hooks %}
|
27
|
+
{{ hook }}
|
28
|
+
|
29
|
+
{% endfor %}
|
30
|
+
|