django-cfg 1.4.13__py3-none-any.whl → 1.4.15__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/urls.py CHANGED
@@ -5,130 +5,142 @@ Built-in API endpoints for django_cfg functionality.
5
5
  """
6
6
 
7
7
  from django.urls import path, include
8
- from typing import List, Dict
9
- from django.urls import URLPattern
8
+ from typing import List
10
9
 
11
10
 
12
- def _register_group_urls(patterns: List[URLPattern], groups: Dict) -> None:
11
+ def get_enabled_cfg_apps() -> List[str]:
13
12
  """
14
- Auto-register URLs from OpenAPI groups using convention.
15
-
16
- Convention: cfg_{app} → /cfg/{app}/
17
-
18
- Args:
19
- patterns: URL patterns list to append to
20
- groups: OpenAPI groups dict
21
- """
22
- for group_name in groups.keys():
23
- # Only django-cfg apps (convention: cfg_*)
24
- if not group_name.startswith('cfg_'):
25
- continue
26
-
27
- # Extract app name: cfg_payments → payments
28
- app_name = group_name[4:]
29
-
30
- # Register main URLs: /cfg/{app}/
31
- try:
32
- patterns.append(
33
- path(f'cfg/{app_name}/', include(f'django_cfg.apps.{app_name}.urls'))
34
- )
35
- except ImportError:
36
- pass # URL module doesn't exist
37
-
38
- # Register admin URLs: /cfg/{app}/admin/ (if exists)
39
- try:
40
- patterns.append(
41
- path(f'cfg/{app_name}/admin/', include(f'django_cfg.apps.{app_name}.urls_admin'))
42
- )
43
- except ImportError:
44
- pass # Admin URL module doesn't exist
45
-
46
-
47
- def _register_apps_fallback(patterns: List[URLPattern]) -> None:
48
- """
49
- Fallback: Register apps when OpenAPI is disabled.
50
-
51
- Uses BaseCfgModule checks to determine which apps are enabled.
52
-
53
- Args:
54
- patterns: URL patterns list to append to
13
+ Get list of enabled django-cfg apps based on configuration.
14
+
15
+ Returns:
16
+ List of enabled app paths (e.g., ['django_cfg.apps.accounts', ...])
55
17
  """
56
18
  from django_cfg.modules.base import BaseCfgModule
19
+
57
20
  base_module = BaseCfgModule()
58
-
59
- # Business logic apps
60
- if base_module.is_support_enabled():
61
- patterns.append(path('cfg/support/', include('django_cfg.apps.support.urls')))
62
-
21
+ enabled_apps = []
22
+
63
23
  if base_module.is_accounts_enabled():
64
- patterns.append(path('cfg/accounts/', include('django_cfg.apps.accounts.urls')))
65
-
24
+ enabled_apps.append("django_cfg.apps.accounts")
25
+
26
+ if base_module.is_support_enabled():
27
+ enabled_apps.append("django_cfg.apps.support")
28
+
66
29
  if base_module.is_newsletter_enabled():
67
- patterns.append(path('cfg/newsletter/', include('django_cfg.apps.newsletter.urls')))
68
-
30
+ enabled_apps.append("django_cfg.apps.newsletter")
31
+
69
32
  if base_module.is_leads_enabled():
70
- patterns.append(path('cfg/leads/', include('django_cfg.apps.leads.urls')))
71
-
33
+ enabled_apps.append("django_cfg.apps.leads")
34
+
72
35
  if base_module.is_knowbase_enabled():
73
- patterns.append(path('cfg/knowbase/', include('django_cfg.apps.knowbase.urls')))
74
-
36
+ enabled_apps.append("django_cfg.apps.knowbase")
37
+
75
38
  if base_module.is_agents_enabled():
76
- patterns.append(path('cfg/agents/', include('django_cfg.apps.agents.urls')))
77
-
39
+ enabled_apps.append("django_cfg.apps.agents")
40
+
78
41
  if base_module.should_enable_tasks():
79
- patterns.append(path('cfg/tasks/', include('django_cfg.apps.tasks.urls')))
80
- patterns.append(path('cfg/tasks/admin/', include('django_cfg.apps.tasks.urls_admin')))
81
-
42
+ enabled_apps.append("django_cfg.apps.tasks")
43
+
82
44
  if base_module.is_payments_enabled():
83
- patterns.append(path('cfg/payments/', include('django_cfg.apps.payments.urls')))
84
- patterns.append(path('cfg/payments/admin/', include('django_cfg.apps.payments.urls_admin')))
85
-
86
- # Standalone apps
87
- if base_module.is_maintenance_enabled():
88
- patterns.append(
89
- path('admin/django_cfg_maintenance/', include('django_cfg.apps.maintenance.urls_admin'))
90
- )
91
-
92
- if base_module.is_rpc_enabled():
93
- patterns.append(path('rpc/', include('django_cfg.modules.django_ipc_client.dashboard.urls')))
94
- patterns.append(path('admin/rpc/', include('django_cfg.modules.django_ipc_client.dashboard.urls_admin')))
45
+ enabled_apps.append("django_cfg.apps.payments")
46
+
47
+ return enabled_apps
95
48
 
96
49
 
97
- def get_django_cfg_urlpatterns() -> List[URLPattern]:
50
+ def get_default_cfg_group():
98
51
  """
99
- Get Django CFG URL patterns based on OpenAPI groups.
100
-
52
+ Returns default OpenAPIGroupConfig for enabled django-cfg apps.
53
+
54
+ Only includes apps that are enabled in the current configuration.
55
+
56
+ This can be imported and added to your project's OpenAPIClientConfig groups:
57
+
58
+ ```python
59
+ from django_cfg.apps.urls import get_default_cfg_group
60
+
61
+ openapi_client = OpenAPIClientConfig(
62
+ groups=[
63
+ get_default_cfg_group(),
64
+ # ... your custom groups
65
+ ]
66
+ )
67
+ ```
68
+
101
69
  Returns:
102
- List of URL patterns for django_cfg
70
+ OpenAPIGroupConfig with enabled django-cfg apps
103
71
  """
104
- patterns = [
105
- # Core APIs (always enabled)
106
- path('health/', include('django_cfg.apps.api.health.urls')),
107
- path('endpoints/', include('django_cfg.apps.api.endpoints.urls')),
108
- path('commands/', include('django_cfg.apps.api.commands.urls')),
109
-
110
- # OpenAPI schemas (if enabled)
111
- # Provides /openapi/{group}/schema/
112
- path('openapi/', include('django_cfg.modules.django_client.urls')),
113
- ]
114
-
72
+ from django_cfg.modules.django_client.core.config import OpenAPIGroupConfig
73
+
74
+ return OpenAPIGroupConfig(
75
+ name="cfg",
76
+ apps=get_enabled_cfg_apps(),
77
+ title="Django-CFG API",
78
+ description="Authentication (OTP), Support, Newsletter, Leads, Knowledge Base, AI Agents, Tasks, Payments",
79
+ version="1.0.0",
80
+ )
81
+
82
+
83
+ def _safe_include(pattern_path: str, module_path: str):
84
+ """Helper to safely include URL module if it exists."""
115
85
  try:
116
- # Auto-register from OpenAPI groups (preferred)
117
- from django_cfg.modules.django_client.core import get_openapi_service
118
- service = get_openapi_service()
119
-
120
- if service and service.is_enabled():
121
- _register_group_urls(patterns, service.get_groups())
122
- else:
123
- # Fallback: Use BaseCfgModule when OpenAPI disabled
124
- _register_apps_fallback(patterns)
125
-
126
- except Exception:
127
- # Last resort fallback
128
- _register_apps_fallback(patterns)
129
-
130
- return patterns
131
-
132
-
133
- # Generate URL patterns dynamically
134
- urlpatterns = get_django_cfg_urlpatterns()
86
+ return path(pattern_path, include(module_path))
87
+ except ImportError:
88
+ return None
89
+
90
+
91
+ # Core API endpoints (always enabled)
92
+ # Note: All prefixes are explicit here (cfg/, health/, etc.)
93
+ urlpatterns = [
94
+ path('cfg/health/', include('django_cfg.apps.api.health.urls')),
95
+ path('cfg/endpoints/', include('django_cfg.apps.api.endpoints.urls')),
96
+ path('cfg/commands/', include('django_cfg.apps.api.commands.urls')),
97
+ path('cfg/openapi/', include('django_cfg.modules.django_client.urls')),
98
+ ]
99
+
100
+ # Django-CFG apps - conditionally registered based on config
101
+ # Map app paths to URL patterns (with cfg/ prefix from add_django_cfg_urls)
102
+ APP_URL_MAP = {
103
+ "django_cfg.apps.accounts": [
104
+ ("cfg/accounts/", "django_cfg.apps.accounts.urls"),
105
+ ],
106
+ "django_cfg.apps.support": [
107
+ ("cfg/support/", "django_cfg.apps.support.urls"),
108
+ ],
109
+ "django_cfg.apps.newsletter": [
110
+ ("cfg/newsletter/", "django_cfg.apps.newsletter.urls"),
111
+ ],
112
+ "django_cfg.apps.leads": [
113
+ ("cfg/leads/", "django_cfg.apps.leads.urls"),
114
+ ],
115
+ "django_cfg.apps.knowbase": [
116
+ ("cfg/knowbase/", "django_cfg.apps.knowbase.urls"),
117
+ ],
118
+ "django_cfg.apps.agents": [
119
+ ("cfg/agents/", "django_cfg.apps.agents.urls"),
120
+ ],
121
+ "django_cfg.apps.tasks": [
122
+ ("cfg/tasks/", "django_cfg.apps.tasks.urls"),
123
+ ("cfg/tasks/admin/", "django_cfg.apps.tasks.urls_admin"),
124
+ ],
125
+ "django_cfg.apps.payments": [
126
+ ("cfg/payments/", "django_cfg.apps.payments.urls"),
127
+ ("cfg/payments/admin/", "django_cfg.apps.payments.urls_admin"),
128
+ ],
129
+ }
130
+
131
+ # Register URLs for enabled apps only
132
+ enabled_apps = get_enabled_cfg_apps()
133
+ cfg_app_urls = []
134
+
135
+ for app_path in enabled_apps:
136
+ if app_path in APP_URL_MAP:
137
+ for url_pattern, url_module in APP_URL_MAP[app_path]:
138
+ cfg_app_urls.append(_safe_include(url_pattern, url_module))
139
+
140
+ # Maintenance (special case - admin only)
141
+ from django_cfg.modules.base import BaseCfgModule
142
+ if BaseCfgModule().is_maintenance_enabled():
143
+ cfg_app_urls.append(_safe_include('admin/django_cfg_maintenance/', 'django_cfg.apps.maintenance.urls_admin'))
144
+
145
+ # Add only successfully imported URLs
146
+ urlpatterns.extend([url for url in cfg_app_urls if url is not None])
@@ -10,18 +10,17 @@ from django_cfg.core.environment import EnvironmentDetector
10
10
  from django.conf import settings
11
11
 
12
12
 
13
- def add_django_cfg_urls(urlpatterns: List[URLPattern], cfg_prefix: str = "cfg/") -> List[URLPattern]:
13
+ def add_django_cfg_urls(urlpatterns: List[URLPattern]) -> List[URLPattern]:
14
14
  """
15
15
  Automatically add django_cfg URLs and all integrations to the main URL configuration.
16
16
 
17
17
  This function adds:
18
- - Django CFG management URLs (cfg/)
18
+ - Django CFG management URLs
19
19
  - Django Client URLs (if available)
20
20
  - Startup information display (based on config)
21
21
 
22
22
  Args:
23
23
  urlpatterns: Existing URL patterns list
24
- cfg_prefix: URL prefix for django_cfg endpoints (default: "cfg/")
25
24
 
26
25
  Returns:
27
26
  Updated URL patterns list with all URLs added
@@ -35,17 +34,13 @@ def add_django_cfg_urls(urlpatterns: List[URLPattern], cfg_prefix: str = "cfg/")
35
34
  path("admin/", admin.site.urls),
36
35
  ]
37
36
 
38
- # Automatically adds:
39
- # - path("cfg/", include("django_cfg.apps.urls"))
40
- # - Django Client URLs (if available)
41
- # - Startup info display (based on config.startup_info_mode)
37
+ # Automatically adds django_cfg URLs with proper prefixes
42
38
  urlpatterns = add_django_cfg_urls(urlpatterns)
43
39
  """
44
40
  # Add django_cfg API URLs
45
- # Note: Django Client URLs are included in django_cfg.apps.urls
46
- # at /cfg/openapi/{group}/schema/ to avoid conflicts
41
+ # Note: URL prefixes (cfg/, health/, etc.) are defined in django_cfg.apps.urls
47
42
  new_patterns = urlpatterns + [
48
- path(cfg_prefix, include("django_cfg.apps.urls")),
43
+ path("", include("django_cfg.apps.urls")),
49
44
  ]
50
45
 
51
46
  # Add django-browser-reload URLs in development (if installed)
@@ -57,8 +57,8 @@ class OpenAPIClientConfig(OpenAPIConfig):
57
57
  description="Schema path prefix for DRF Spectacular"
58
58
  )
59
59
  drf_enable_browsable_api: bool = Field(
60
- default=False,
61
- description="Enable DRF browsable API"
60
+ default=True,
61
+ description="Enable DRF browsable API with Tailwind theme"
62
62
  )
63
63
  drf_enable_throttling: bool = Field(
64
64
  default=False,
@@ -104,6 +104,8 @@ class OpenAPIClientConfig(OpenAPIConfig):
104
104
  def get_groups_with_defaults(self) -> Dict[str, OpenAPIGroupConfig]:
105
105
  """
106
106
  Get groups with django-cfg default groups automatically added.
107
+
108
+ Automatically adds the 'cfg' group with all django-cfg apps if not explicitly defined.
107
109
 
108
110
  Returns:
109
111
  Dict of groups including default django-cfg groups
@@ -111,52 +113,13 @@ class OpenAPIClientConfig(OpenAPIConfig):
111
113
  # Convert list to dict for compatibility
112
114
  groups_dict = {group.name: group for group in self.groups}
113
115
 
114
- # Add default django-cfg groups if enabled
115
- try:
116
- from django_cfg.modules.base import BaseCfgModule
117
- base_module = BaseCfgModule()
118
-
119
- support_enabled = base_module.is_support_enabled()
120
- accounts_enabled = base_module.is_accounts_enabled()
121
- newsletter_enabled = base_module.is_newsletter_enabled()
122
- leads_enabled = base_module.is_leads_enabled()
123
- knowbase_enabled = base_module.is_knowbase_enabled()
124
- agents_enabled = base_module.is_agents_enabled()
125
- tasks_enabled = base_module.should_enable_tasks()
126
- payments_enabled = base_module.is_payments_enabled()
127
-
128
- # Collect all enabled django-cfg apps for unified group
129
- enabled_cfg_apps = []
130
- if support_enabled:
131
- enabled_cfg_apps.append("django_cfg.apps.support")
132
- if accounts_enabled:
133
- enabled_cfg_apps.append("django_cfg.apps.accounts")
134
- if newsletter_enabled:
135
- enabled_cfg_apps.append("django_cfg.apps.newsletter")
136
- if leads_enabled:
137
- enabled_cfg_apps.append("django_cfg.apps.leads")
138
- if knowbase_enabled:
139
- enabled_cfg_apps.append("django_cfg.apps.knowbase")
140
- if agents_enabled:
141
- enabled_cfg_apps.append("django_cfg.apps.agents")
142
- if tasks_enabled:
143
- enabled_cfg_apps.append("django_cfg.apps.tasks")
144
- if payments_enabled:
145
- enabled_cfg_apps.append("django_cfg.apps.payments")
146
-
147
- # Add unified 'cfg' group with all enabled apps
148
- if enabled_cfg_apps and 'cfg' not in groups_dict:
149
- groups_dict['cfg'] = OpenAPIGroupConfig(
150
- name="cfg",
151
- apps=enabled_cfg_apps,
152
- title="Django-CFG API",
153
- description="All django-cfg built-in applications",
154
- )
155
-
156
- return groups_dict
157
-
158
- except Exception:
159
- pass
116
+ # Add default 'cfg' group if not explicitly defined
117
+ if 'cfg' not in groups_dict:
118
+ try:
119
+ from django_cfg.apps.urls import get_default_cfg_group
120
+ groups_dict['cfg'] = get_default_cfg_group()
121
+ except Exception:
122
+ pass
160
123
 
161
124
  return groups_dict
162
125
 
@@ -110,24 +110,24 @@ class FetchersGenerator:
110
110
  Convert operation to function name.
111
111
 
112
112
  Examples:
113
- users_list (GET) -> getUsers
114
- users_retrieve (GET) -> getUser
115
- users_create (POST) -> createUser
116
- users_update (PUT) -> updateUser
117
- users_partial_update (PATCH) -> updateUser
118
- users_destroy (DELETE) -> deleteUser
113
+ users_list (GET) -> getUsersList
114
+ users_retrieve (GET) -> getUsersById
115
+ users_create (POST) -> createUsers
116
+ users_update (PUT) -> updateUsers
117
+ users_partial_update (PATCH) -> partialUpdateUsers
118
+ users_destroy (DELETE) -> deleteUsers
119
119
  """
120
120
  # Remove tag prefix from operation_id
121
121
  op_id = operation.operation_id
122
122
 
123
- # Handle common patterns (remove only suffix, not all occurrences)
123
+ # Handle common patterns - keep full resource name for uniqueness
124
124
  if op_id.endswith("_list"):
125
125
  resource = op_id.removesuffix("_list")
126
- return f"get{self._to_pascal_case(resource)}"
126
+ return f"get{self._to_pascal_case(resource)}List"
127
127
  elif op_id.endswith("_retrieve"):
128
128
  resource = op_id.removesuffix("_retrieve")
129
- # Singular
130
- return f"get{self._to_pascal_case(resource).rstrip('s')}"
129
+ # Add ById suffix to distinguish from list
130
+ return f"get{self._to_pascal_case(resource)}ById"
131
131
  elif op_id.endswith("_create"):
132
132
  resource = op_id.removesuffix("_create")
133
133
  return f"create{self._to_pascal_case(resource)}"
@@ -180,24 +180,23 @@ class HooksGenerator:
180
180
  Convert operation to hook name.
181
181
 
182
182
  Examples:
183
- users_list (GET) -> useUsers
184
- users_retrieve (GET) -> useUser
185
- users_create (POST) -> useCreateUser
186
- users_update (PUT) -> useUpdateUser
187
- users_partial_update (PATCH) -> useUpdateUser
188
- users_destroy (DELETE) -> useDeleteUser
183
+ users_list (GET) -> useUsersList
184
+ users_retrieve (GET) -> useUsersById
185
+ users_create (POST) -> useCreateUsers
186
+ users_update (PUT) -> useUpdateUsers
187
+ users_partial_update (PATCH) -> usePartialUpdateUsers
188
+ users_destroy (DELETE) -> useDeleteUsers
189
189
  """
190
190
  op_id = operation.operation_id
191
191
 
192
+ # Keep full resource name and add suffixes for uniqueness
192
193
  if op_id.endswith("_list"):
193
- resource = op_id.replace("_list", "")
194
- # Plural form
195
- return f"use{self._to_pascal_case(resource)}"
194
+ resource = op_id.removesuffix("_list")
195
+ return f"use{self._to_pascal_case(resource)}List"
196
196
  elif op_id.endswith("_retrieve"):
197
- resource = op_id.replace("_retrieve", "")
198
- # Singular form (remove trailing 's')
199
- resource_singular = resource.rstrip('s') if resource.endswith('s') and len(resource) > 1 else resource
200
- return f"use{self._to_pascal_case(resource_singular)}"
197
+ resource = op_id.removesuffix("_retrieve")
198
+ # Add ById suffix to distinguish from list
199
+ return f"use{self._to_pascal_case(resource)}ById"
201
200
  elif op_id.endswith("_create"):
202
201
  resource = op_id.removesuffix("_create")
203
202
  return f"useCreate{self._to_pascal_case(resource)}"
@@ -215,18 +214,17 @@ class HooksGenerator:
215
214
  return f"use{self._to_pascal_case(op_id)}"
216
215
 
217
216
  def _operation_to_fetcher_name(self, operation: IROperationObject) -> str:
218
- """Get corresponding fetcher function name."""
217
+ """Get corresponding fetcher function name (must match fetchers_generator logic)."""
219
218
  op_id = operation.operation_id
220
219
 
221
- # Remove only suffix, not all occurrences (same logic as fetchers_generator)
220
+ # Must match fetchers_generator._operation_to_function_name() exactly
222
221
  if op_id.endswith("_list"):
223
222
  resource = op_id.removesuffix("_list")
224
- return f"get{self._to_pascal_case(resource)}"
223
+ return f"get{self._to_pascal_case(resource)}List"
225
224
  elif op_id.endswith("_retrieve"):
226
225
  resource = op_id.removesuffix("_retrieve")
227
- # Singular
228
- resource_singular = resource.rstrip('s') if resource.endswith('s') else resource
229
- return f"get{self._to_pascal_case(resource_singular)}"
226
+ # Add ById suffix to match fetchers_generator
227
+ return f"get{self._to_pascal_case(resource)}ById"
230
228
  elif op_id.endswith("_create"):
231
229
  resource = op_id.removesuffix("_create")
232
230
  return f"create{self._to_pascal_case(resource)}"
@@ -240,7 +238,7 @@ class HooksGenerator:
240
238
  resource = op_id.removesuffix("_destroy")
241
239
  return f"delete{self._to_pascal_case(resource)}"
242
240
  else:
243
- return f"{operation.http_method.lower()}{self._to_pascal_case(op_id)}"
241
+ return self._to_camel_case(op_id)
244
242
 
245
243
  def _get_param_info(self, operation: IROperationObject) -> dict:
246
244
  """
@@ -391,6 +389,11 @@ class HooksGenerator:
391
389
  """Convert snake_case to PascalCase."""
392
390
  return ''.join(word.capitalize() for word in snake_str.split('_'))
393
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
+
394
397
  def generate_tag_hooks_file(
395
398
  self,
396
399
  tag: str,
@@ -140,35 +140,39 @@ class ModelsGenerator:
140
140
  Examples:
141
141
  id: number;
142
142
  username: string;
143
- email?: string | null;
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
- if schema.nullable:
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
- if schema.nullable:
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
- optional_marker = "" if is_required else "?"
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:
@@ -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
- if not is_required:
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:
@@ -79,7 +79,7 @@ class DashboardManager(BaseCfgModule):
79
79
  if self.should_enable_tasks():
80
80
  operations_items.extend([
81
81
  NavigationItem(title="Background Tasks", icon=Icons.TASK, link="/admin/django_dramatiq/task/"),
82
- NavigationItem(title="Task Dashboard", icon=Icons.SETTINGS_APPLICATIONS, link="/cfg/admin/django_cfg_tasks/admin/dashboard/"),
82
+ NavigationItem(title="Task Dashboard", icon=Icons.SETTINGS_APPLICATIONS, link="/cfg/tasks/admin/dashboard/"),
83
83
  ])
84
84
 
85
85
  # Maintenance Mode (if enabled)
@@ -194,7 +194,7 @@ class DashboardManager(BaseCfgModule):
194
194
 
195
195
  # Main dashboard (always show if payments app enabled)
196
196
  payments_items.append(
197
- NavigationItem(title="Payment Dashboard", icon=Icons.DASHBOARD, link="/cfg/admin/django_cfg_payments/admin/")
197
+ NavigationItem(title="Payment Dashboard", icon=Icons.DASHBOARD, link="/cfg/payments/admin/")
198
198
  )
199
199
 
200
200
  # Always show basic admin models (even if payments functionality is disabled)
@@ -208,13 +208,13 @@ class DashboardManager(BaseCfgModule):
208
208
  # Add advanced features only if payments functionality is enabled
209
209
  if config.enabled:
210
210
  # payments_items.append(
211
- # NavigationItem(title="Webhook Dashboard", icon=Icons.WEBHOOK, link="/cfg/admin/django_cfg_payments/admin/webhooks/")
211
+ # NavigationItem(title="Webhook Dashboard", icon=Icons.WEBHOOK, link="/cfg/payments/admin/webhooks/")
212
212
  # )
213
213
  # payments_items.append(
214
- # NavigationItem(title="Create Payment", icon=Icons.ADD, link="/cfg/admin/django_cfg_payments/admin/payments/create/")
214
+ # NavigationItem(title="Create Payment", icon=Icons.ADD, link="/cfg/payments/admin/payments/create/")
215
215
  # )
216
216
  # payments_items.append(
217
- # NavigationItem(title="Currency Converter", icon=Icons.CURRENCY_EXCHANGE, link="/cfg/admin/django_cfg_payments/admin/tools/converter/")
217
+ # NavigationItem(title="Currency Converter", icon=Icons.CURRENCY_EXCHANGE, link="/cfg/payments/admin/tools/converter/")
218
218
  # )
219
219
 
220
220
  # Show subscription features only if enabled
@@ -246,7 +246,7 @@ class DashboardManager(BaseCfgModule):
246
246
  except Exception:
247
247
  # Fallback
248
248
  payments_items = [
249
- NavigationItem(title="Payment Dashboard", icon=Icons.DASHBOARD, link="/cfg/admin/django_cfg_payments/admin/"),
249
+ NavigationItem(title="Payment Dashboard", icon=Icons.DASHBOARD, link="/cfg/payments/admin/"),
250
250
  NavigationItem(title="Universal Payments", icon=Icons.ACCOUNT_BALANCE, link="/admin/payments/universalpayment/"),
251
251
  ]
252
252
 
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.13"
7
+ version = "1.4.15"
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.13
3
+ Version: 1.4.15
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