django-cfg 1.3.11__py3-none-any.whl → 1.4.0__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.
Files changed (439) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/inlines.py +11 -5
  3. django_cfg/apps/accounts/admin/user_admin.py +39 -16
  4. django_cfg/apps/accounts/serializers/profile.py +1 -1
  5. django_cfg/apps/accounts/services/otp_service.py +18 -11
  6. django_cfg/apps/accounts/signals.py +15 -24
  7. django_cfg/apps/accounts/utils/notifications.py +217 -358
  8. django_cfg/apps/accounts/views/otp.py +2 -2
  9. django_cfg/apps/accounts/views/webhook.py +1 -1
  10. django_cfg/apps/agents/core/django_agent.py +1 -1
  11. django_cfg/apps/api/commands/views.py +66 -83
  12. django_cfg/apps/api/health/drf_views.py +269 -0
  13. django_cfg/apps/api/health/serializers.py +45 -0
  14. django_cfg/apps/api/health/urls.py +6 -1
  15. django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
  16. django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
  17. django_cfg/apps/knowbase/admin/document_admin.py +136 -270
  18. django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
  19. django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
  20. django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
  21. django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
  22. django_cfg/apps/knowbase/config/constance_fields.py +1 -1
  23. django_cfg/apps/knowbase/config/settings.py +2 -2
  24. django_cfg/apps/knowbase/mixins/__init__.py +19 -2
  25. django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
  26. django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
  27. django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
  28. django_cfg/apps/knowbase/mixins/creator.py +10 -10
  29. django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
  30. django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
  31. django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
  32. django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
  33. django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
  34. django_cfg/apps/knowbase/mixins/service.py +2 -2
  35. django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
  36. django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
  37. django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
  38. django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
  39. django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
  40. django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
  41. django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
  42. django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
  43. django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
  44. django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
  45. django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
  46. django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
  47. django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
  48. django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
  49. django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
  50. django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
  51. django_cfg/apps/knowbase/services/embedding/models.py +18 -14
  52. django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
  53. django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
  54. django_cfg/apps/leads/tests.py +1 -1
  55. django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
  56. django_cfg/apps/payments/admin/balance_admin.py +1 -1
  57. django_cfg/apps/payments/admin/currencies_admin.py +1 -1
  58. django_cfg/apps/payments/admin/payments_admin.py +1 -1
  59. django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
  60. django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
  61. django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
  62. django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
  63. django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
  64. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
  65. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
  66. django_cfg/apps/payments/admin_interface/views/base.py +29 -2
  67. django_cfg/apps/payments/apps.py +1 -1
  68. django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
  69. django_cfg/apps/payments/config/helpers.py +3 -2
  70. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
  71. django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
  72. django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
  73. django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
  74. django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
  75. django_cfg/apps/payments/management/commands/test_providers.py +1 -1
  76. django_cfg/apps/payments/middleware/api_access.py +1 -1
  77. django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
  78. django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
  79. django_cfg/apps/payments/models/balance.py +2 -2
  80. django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
  81. django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
  82. django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
  83. django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
  84. django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
  85. django_cfg/apps/payments/models/payments.py +2 -2
  86. django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
  87. django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
  88. django_cfg/apps/payments/services/core/base.py +1 -1
  89. django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
  90. django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
  91. django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
  92. django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
  93. django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
  94. django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
  95. django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
  96. django_cfg/apps/payments/services/core/payment_service.py +124 -612
  97. django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
  98. django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
  99. django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
  100. django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
  101. django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
  102. django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
  103. django_cfg/apps/payments/services/providers/base.py +1 -1
  104. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
  105. django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
  106. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
  107. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
  108. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
  109. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
  110. django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
  111. django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
  112. django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
  113. django_cfg/apps/payments/services/providers/registry.py +1 -1
  114. django_cfg/apps/payments/services/providers/sync_service.py +1 -1
  115. django_cfg/apps/payments/signals/__init__.py +1 -1
  116. django_cfg/apps/payments/signals/api_key_signals.py +1 -1
  117. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  118. django_cfg/apps/payments/signals/payment_signals.py +1 -1
  119. django_cfg/apps/payments/signals/subscription_signals.py +1 -1
  120. django_cfg/apps/payments/views/api/api_keys.py +1 -1
  121. django_cfg/apps/payments/views/api/balances.py +1 -1
  122. django_cfg/apps/payments/views/api/base.py +1 -1
  123. django_cfg/apps/payments/views/api/currencies.py +1 -1
  124. django_cfg/apps/payments/views/api/payments.py +1 -1
  125. django_cfg/apps/payments/views/api/subscriptions.py +1 -1
  126. django_cfg/apps/payments/views/api/webhooks.py +1 -1
  127. django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
  128. django_cfg/apps/payments/views/serializers/balances.py +1 -1
  129. django_cfg/apps/payments/views/serializers/currencies.py +1 -1
  130. django_cfg/apps/payments/views/serializers/payments.py +1 -1
  131. django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
  132. django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
  133. django_cfg/apps/support/admin/support_admin.py +21 -13
  134. django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
  135. django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
  136. django_cfg/apps/support/utils/support_email_service.py +1 -1
  137. django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
  138. django_cfg/apps/tasks/utils/simulator.py +1 -1
  139. django_cfg/apps/tasks/views/dashboard.py +33 -3
  140. django_cfg/apps/urls.py +5 -1
  141. django_cfg/cli/README.md +57 -471
  142. django_cfg/cli/commands/create_project.py +140 -529
  143. django_cfg/cli/main.py +13 -10
  144. django_cfg/core/__init__.py +63 -6
  145. django_cfg/core/base/__init__.py +5 -0
  146. django_cfg/core/base/config_model.py +652 -0
  147. django_cfg/core/builders/__init__.py +11 -0
  148. django_cfg/core/builders/apps_builder.py +258 -0
  149. django_cfg/core/builders/middleware_builder.py +115 -0
  150. django_cfg/core/builders/security_builder.py +96 -0
  151. django_cfg/core/config.py +20 -892
  152. django_cfg/core/constants.py +69 -0
  153. django_cfg/core/environment/__init__.py +9 -0
  154. django_cfg/core/exceptions.py +45 -298
  155. django_cfg/core/generation/__init__.py +51 -0
  156. django_cfg/core/generation/core_generators/__init__.py +0 -0
  157. django_cfg/core/generation/core_generators/settings.py +90 -0
  158. django_cfg/core/generation/core_generators/static.py +82 -0
  159. django_cfg/core/generation/core_generators/templates.py +141 -0
  160. django_cfg/core/generation/data_generators/__init__.py +15 -0
  161. django_cfg/core/generation/data_generators/cache.py +132 -0
  162. django_cfg/core/generation/data_generators/database.py +117 -0
  163. django_cfg/core/generation/generation.py +92 -0
  164. django_cfg/core/generation/integration_generators/__init__.py +21 -0
  165. django_cfg/core/generation/integration_generators/api.py +237 -0
  166. django_cfg/core/generation/integration_generators/sessions.py +65 -0
  167. django_cfg/core/generation/integration_generators/tailwind.py +54 -0
  168. django_cfg/core/generation/integration_generators/tasks.py +92 -0
  169. django_cfg/core/generation/integration_generators/third_party.py +144 -0
  170. django_cfg/core/generation/orchestrator.py +285 -0
  171. django_cfg/core/generation/protocols.py +30 -0
  172. django_cfg/core/generation/security_generators/__init__.py +0 -0
  173. django_cfg/core/generation/utility_generators/__init__.py +24 -0
  174. django_cfg/core/generation/utility_generators/email.py +58 -0
  175. django_cfg/core/generation/utility_generators/i18n.py +66 -0
  176. django_cfg/core/generation/utility_generators/limits.py +58 -0
  177. django_cfg/core/generation/utility_generators/logging.py +66 -0
  178. django_cfg/core/generation/utility_generators/security.py +101 -0
  179. django_cfg/core/generation/utils/__init__.py +0 -0
  180. django_cfg/core/generation/utils/helpers.py +32 -0
  181. django_cfg/core/integration/__init__.py +18 -25
  182. django_cfg/core/integration/display/startup.py +146 -133
  183. django_cfg/core/integration/url_integration.py +13 -2
  184. django_cfg/core/services/__init__.py +5 -0
  185. django_cfg/core/services/config_service.py +121 -0
  186. django_cfg/core/state/__init__.py +9 -0
  187. django_cfg/core/state/registry.py +84 -0
  188. django_cfg/core/types/__init__.py +15 -0
  189. django_cfg/core/types/aliases.py +15 -0
  190. django_cfg/core/types/enums.py +49 -0
  191. django_cfg/dashboard/DEBUG_README.md +105 -0
  192. django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
  193. django_cfg/dashboard/__init__.py +24 -0
  194. django_cfg/dashboard/components.py +308 -0
  195. django_cfg/dashboard/debug.py +176 -0
  196. django_cfg/dashboard/management/__init__.py +0 -0
  197. django_cfg/dashboard/management/commands/__init__.py +0 -0
  198. django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
  199. django_cfg/dashboard/sections/__init__.py +1 -0
  200. django_cfg/dashboard/sections/base.py +128 -0
  201. django_cfg/dashboard/sections/commands.py +32 -0
  202. django_cfg/dashboard/sections/overview.py +394 -0
  203. django_cfg/dashboard/sections/stats.py +48 -0
  204. django_cfg/dashboard/sections/system.py +73 -0
  205. django_cfg/management/commands/check_settings.py +6 -2
  206. django_cfg/management/commands/clear_constance.py +6 -1
  207. django_cfg/management/commands/create_token.py +5 -4
  208. django_cfg/management/commands/generate.py +5 -0
  209. django_cfg/management/commands/list_urls.py +7 -2
  210. django_cfg/management/commands/migrate_all.py +6 -2
  211. django_cfg/management/commands/migrator.py +6 -1
  212. django_cfg/management/commands/rundramatiq.py +6 -1
  213. django_cfg/management/commands/rundramatiq_simulator.py +11 -4
  214. django_cfg/management/commands/runserver_ngrok.py +9 -7
  215. django_cfg/management/commands/script.py +25 -21
  216. django_cfg/management/commands/show_config.py +6 -1
  217. django_cfg/management/commands/show_urls.py +8 -3
  218. django_cfg/management/commands/superuser.py +5 -4
  219. django_cfg/management/commands/task_clear.py +8 -3
  220. django_cfg/management/commands/task_status.py +8 -3
  221. django_cfg/management/commands/test_email.py +6 -1
  222. django_cfg/management/commands/test_telegram.py +6 -1
  223. django_cfg/management/commands/test_twilio.py +6 -1
  224. django_cfg/management/commands/tree.py +7 -4
  225. django_cfg/models/__init__.py +88 -3
  226. django_cfg/models/api/__init__.py +27 -0
  227. django_cfg/models/{api.py → api/config.py} +1 -1
  228. django_cfg/models/api/drf/__init__.py +21 -0
  229. django_cfg/models/api/drf/config.py +101 -0
  230. django_cfg/models/api/drf/redoc.py +31 -0
  231. django_cfg/models/api/drf/spectacular.py +129 -0
  232. django_cfg/models/api/drf/swagger.py +59 -0
  233. django_cfg/models/{api_keys.py → api/keys.py} +16 -6
  234. django_cfg/models/{limits.py → api/limits.py} +0 -1
  235. django_cfg/models/base/__init__.py +14 -0
  236. django_cfg/models/django/__init__.py +16 -0
  237. django_cfg/models/{constance.py → django/constance.py} +1 -1
  238. django_cfg/models/{environment.py → django/environment.py} +1 -1
  239. django_cfg/models/infrastructure/__init__.py +17 -0
  240. django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
  241. django_cfg/models/infrastructure/database/__init__.py +22 -0
  242. django_cfg/models/infrastructure/database/config.py +265 -0
  243. django_cfg/models/infrastructure/database/converters.py +91 -0
  244. django_cfg/models/infrastructure/database/parsers.py +96 -0
  245. django_cfg/models/infrastructure/database/routing.py +85 -0
  246. django_cfg/models/infrastructure/database/validators.py +170 -0
  247. django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
  248. django_cfg/models/{security.py → infrastructure/security.py} +2 -2
  249. django_cfg/models/ngrok/__init__.py +11 -0
  250. django_cfg/models/ngrok/auth.py +37 -0
  251. django_cfg/models/ngrok/config.py +77 -0
  252. django_cfg/models/ngrok/tunnel.py +35 -0
  253. django_cfg/models/payments/__init__.py +20 -0
  254. django_cfg/models/payments/api_keys.py +57 -0
  255. django_cfg/models/{payments.py → payments/config.py} +56 -154
  256. django_cfg/models/payments/providers/__init__.py +15 -0
  257. django_cfg/models/payments/providers/base.py +25 -0
  258. django_cfg/models/payments/providers/nowpayments.py +48 -0
  259. django_cfg/models/services/__init__.py +18 -0
  260. django_cfg/models/services/base.py +65 -0
  261. django_cfg/models/{email.py → services/email.py} +1 -1
  262. django_cfg/models/services/telegram.py +172 -0
  263. django_cfg/models/tasks/__init__.py +51 -0
  264. django_cfg/models/tasks/backends.py +250 -0
  265. django_cfg/models/tasks/config.py +314 -0
  266. django_cfg/models/tasks/utils.py +174 -0
  267. django_cfg/modules/base.py +18 -3
  268. django_cfg/modules/django_admin/decorators/actions.py +1 -1
  269. django_cfg/modules/django_admin/decorators/display.py +1 -1
  270. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
  271. django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
  272. django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
  273. django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
  274. django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
  275. django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
  276. django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
  277. django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
  278. django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
  279. django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
  280. django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
  281. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
  282. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
  283. django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
  284. django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
  285. django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
  286. django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
  287. django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
  288. django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
  289. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
  290. django_cfg/modules/django_drf_theme/README.md +207 -0
  291. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
  292. django_cfg/modules/django_drf_theme/__init__.py +23 -0
  293. django_cfg/modules/django_drf_theme/apps.py +15 -0
  294. django_cfg/modules/django_drf_theme/renderers.py +58 -0
  295. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
  296. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
  297. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
  298. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
  299. django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
  300. django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
  301. django_cfg/modules/django_email/__init__.py +14 -0
  302. django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
  303. django_cfg/modules/django_email/utils.py +40 -0
  304. django_cfg/modules/django_health/__init__.py +9 -0
  305. django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
  306. django_cfg/modules/django_llm/llm/client.py +155 -550
  307. django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
  308. django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
  309. django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
  310. django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
  311. django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
  312. django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
  313. django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
  314. django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
  315. django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
  316. django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
  317. django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
  318. django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
  319. django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
  320. django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
  321. django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
  322. django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
  323. django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
  324. django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
  325. django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
  326. django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
  327. django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
  328. django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
  329. django_cfg/modules/django_llm/translator/translator.py +150 -603
  330. django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
  331. django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
  332. django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
  333. django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
  334. django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
  335. django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
  336. django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
  337. django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
  338. django_cfg/modules/django_logging/__init__.py +14 -0
  339. django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
  340. django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
  341. django_cfg/modules/django_ngrok/__init__.py +39 -0
  342. django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
  343. django_cfg/modules/django_rpc_old/POETRY.md +344 -0
  344. django_cfg/modules/django_rpc_old/README.md +397 -0
  345. django_cfg/modules/django_rpc_old/TESTING.md +358 -0
  346. django_cfg/modules/django_rpc_old/__init__.py +39 -0
  347. django_cfg/modules/django_rpc_old/client.py +531 -0
  348. django_cfg/modules/django_rpc_old/config.py +279 -0
  349. django_cfg/modules/django_rpc_old/exceptions.py +172 -0
  350. django_cfg/modules/django_tailwind/README.md +478 -0
  351. django_cfg/modules/django_tailwind/__init__.py +7 -0
  352. django_cfg/modules/django_tailwind/apps.py +10 -0
  353. django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
  354. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
  355. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
  356. django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
  357. django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
  358. django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
  359. django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
  360. django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
  361. django_cfg/modules/django_tasks/__init__.py +29 -0
  362. django_cfg/modules/django_tasks/factory.py +127 -0
  363. django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
  364. django_cfg/modules/django_tasks/settings.py +107 -0
  365. django_cfg/modules/django_telegram/__init__.py +29 -0
  366. django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
  367. django_cfg/modules/django_telegram/utils.py +62 -0
  368. django_cfg/modules/django_twilio/__init__.py +54 -107
  369. django_cfg/modules/django_twilio/_imports.py +30 -0
  370. django_cfg/modules/django_twilio/base.py +192 -0
  371. django_cfg/modules/django_twilio/email_otp.py +227 -0
  372. django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
  373. django_cfg/modules/django_twilio/simple_service.py +1 -2
  374. django_cfg/modules/django_twilio/sms.py +94 -0
  375. django_cfg/modules/django_twilio/twilio_service.py +2 -3
  376. django_cfg/modules/django_twilio/unified.py +310 -0
  377. django_cfg/modules/django_twilio/utils.py +190 -0
  378. django_cfg/modules/django_twilio/whatsapp.py +137 -0
  379. django_cfg/modules/django_unfold/callbacks/base.py +198 -7
  380. django_cfg/modules/django_unfold/callbacks/main.py +102 -10
  381. django_cfg/modules/django_unfold/dashboard.py +65 -43
  382. django_cfg/modules/django_unfold/models/config.py +13 -12
  383. django_cfg/modules/django_unfold/models/navigation.py +8 -3
  384. django_cfg/modules/django_unfold/models/tabs.py +2 -2
  385. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
  386. django_cfg/registry/core.py +24 -26
  387. django_cfg/registry/modules.py +5 -2
  388. django_cfg/registry/services.py +20 -3
  389. django_cfg/registry/third_party.py +8 -8
  390. django_cfg/static/admin/css/dashboard.css +260 -0
  391. django_cfg/static/admin/js/commands.js +171 -0
  392. django_cfg/static/admin/js/dashboard.js +126 -0
  393. django_cfg/templates/admin/components/management_commands.js +375 -0
  394. django_cfg/templates/admin/components/progress_bar.html +18 -23
  395. django_cfg/templates/admin/index.html +48 -20
  396. django_cfg/templates/admin/index_new.html +106 -0
  397. django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
  398. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
  399. django_cfg/templates/admin/sections/commands_section.html +626 -0
  400. django_cfg/templates/admin/sections/overview_section.html +112 -0
  401. django_cfg/templates/admin/sections/stats_section.html +35 -0
  402. django_cfg/templates/admin/sections/system_section.html +99 -0
  403. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
  404. django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
  405. django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
  406. django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
  407. django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
  408. django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
  409. django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
  410. django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
  411. django_cfg/templatetags/django_cfg.py +7 -1
  412. django_cfg/utils/smart_defaults.py +4 -4
  413. django_cfg-1.4.0.dist-info/METADATA +920 -0
  414. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/RECORD +425 -196
  415. django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
  416. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
  417. django_cfg/core/generation.py +0 -621
  418. django_cfg/management/commands/validate_config.py +0 -189
  419. django_cfg/models/database.py +0 -480
  420. django_cfg/models/drf.py +0 -272
  421. django_cfg/models/ngrok.py +0 -122
  422. django_cfg/models/services.py +0 -440
  423. django_cfg/models/tasks.py +0 -550
  424. django_cfg/modules/django_twilio/service.py +0 -942
  425. django_cfg/template_archive/django_sample.zip +0 -0
  426. django_cfg/templates/rest_framework/api.html +0 -12
  427. django_cfg/utils/toolkit.py +0 -703
  428. django_cfg-1.3.11.dist-info/METADATA +0 -1029
  429. /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
  430. /django_cfg/core/{environment.py → environment/detector.py} +0 -0
  431. /django_cfg/models/{cors.py → api/cors.py} +0 -0
  432. /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
  433. /django_cfg/models/{base.py → base/config.py} +0 -0
  434. /django_cfg/models/{cfg.py → base/module.py} +0 -0
  435. /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
  436. /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
  437. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
  438. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
  439. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,938 @@
1
+ {% load static rest_framework tailwind_tags %}
2
+ <!DOCTYPE html>
3
+ <html lang="en" class="{{ html_class }}" x-data="drfApp()" x-init="init()" @keydown.window="handleKeyboard($event)">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="robots" content="NONE,NOARCHIVE">
8
+
9
+ <title>{% if name %}{{ name }} – {% endif %}{{ project_name|default:"API" }}</title>
10
+
11
+ {# CSRF Token for AJAX requests #}
12
+ {% csrf_token %}
13
+ <meta name="csrf-token" content="{{ csrf_token }}">
14
+
15
+ {# DRF CSRF data for ajax-form.js #}
16
+ <script id="drf_csrf" type="application/json">
17
+ {
18
+ "csrfToken": "{{ csrf_token }}",
19
+ "csrfHeaderName": "X-CSRFToken"
20
+ }
21
+ </script>
22
+
23
+ {# Apply theme immediately to prevent flash #}
24
+ <script>
25
+ (function() {
26
+ const theme = '{{ theme|default:"auto" }}';
27
+ const html = document.documentElement;
28
+ let isDark = false;
29
+
30
+ if (theme === 'dark') {
31
+ html.classList.add('dark');
32
+ isDark = true;
33
+ } else if (theme === 'light') {
34
+ html.classList.remove('dark');
35
+ isDark = false;
36
+ } else {
37
+ // Auto: use system preference
38
+ isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
39
+ if (isDark) {
40
+ html.classList.add('dark');
41
+ } else {
42
+ html.classList.remove('dark');
43
+ }
44
+ }
45
+
46
+ // Store for Prism.js theme switching
47
+ window.__drfThemeIsDark = isDark;
48
+ })();
49
+ </script>
50
+
51
+ {# Tailwind CSS #}
52
+ <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,container-queries"></script>
53
+
54
+ {# Override Tailwind CDN's media-query dark mode with class-based dark mode #}
55
+ <style>
56
+ /* Force light mode colors when NOT in dark mode - override media queries */
57
+ html:not(.dark) body,
58
+ html:not(.dark) .dark\:bg-gray-900,
59
+ html:not(.dark) .dark\:bg-gray-800,
60
+ html:not(.dark) .dark\:bg-black {
61
+ background-color: rgb(249 250 251) !important; /* bg-gray-50 */
62
+ }
63
+
64
+ html:not(.dark) .dark\:bg-gray-700 {
65
+ background-color: rgb(243 244 246) !important; /* bg-gray-100 */
66
+ }
67
+
68
+ html:not(.dark) .dark\:text-gray-100,
69
+ html:not(.dark) .dark\:text-white,
70
+ html:not(.dark) .dark\:text-gray-300 {
71
+ color: rgb(17 24 39) !important; /* text-gray-900 */
72
+ }
73
+
74
+ html:not(.dark) .dark\:text-gray-400 {
75
+ color: rgb(75 85 99) !important; /* text-gray-600 */
76
+ }
77
+
78
+ html:not(.dark) .dark\:border-gray-700,
79
+ html:not(.dark) .dark\:border-gray-600 {
80
+ border-color: rgb(229 231 235) !important; /* border-gray-200 */
81
+ }
82
+
83
+ /* Enforce dark mode colors when IN dark mode */
84
+ html.dark body,
85
+ html.dark .dark\:bg-gray-900 {
86
+ background-color: rgb(17 24 39) !important;
87
+ }
88
+
89
+ html.dark .dark\:bg-gray-800 {
90
+ background-color: rgb(31 41 55) !important;
91
+ }
92
+
93
+ html.dark .dark\:bg-black {
94
+ background-color: rgb(0 0 0) !important;
95
+ }
96
+
97
+ html.dark .dark\:text-gray-100 {
98
+ color: rgb(243 244 246) !important;
99
+ }
100
+
101
+ html.dark .dark\:text-white {
102
+ color: rgb(255 255 255) !important;
103
+ }
104
+ </style>
105
+
106
+ {# Prism.js for syntax highlighting - loaded dynamically based on theme #}
107
+ <link rel="stylesheet" id="prism-light" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" disabled>
108
+ <link rel="stylesheet" id="prism-dark" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" disabled>
109
+ <script>
110
+ // Enable the correct Prism theme immediately
111
+ if (window.__drfThemeIsDark) {
112
+ document.getElementById('prism-dark').disabled = false;
113
+ } else {
114
+ document.getElementById('prism-light').disabled = false;
115
+ }
116
+ </script>
117
+
118
+ {# Alpine.js for interactivity #}
119
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
120
+
121
+ {# Custom styles #}
122
+ <style>
123
+ /* Glass morphism */
124
+ .glass {
125
+ background: rgba(255, 255, 255, 0.9);
126
+ backdrop-filter: blur(10px);
127
+ -webkit-backdrop-filter: blur(10px);
128
+ }
129
+ .dark .glass {
130
+ background: rgba(17, 24, 39, 0.9);
131
+ }
132
+
133
+ /* Smooth transitions */
134
+ * {
135
+ transition-property: background-color, border-color, color, fill, stroke;
136
+ transition-duration: 200ms;
137
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
138
+ }
139
+
140
+ /* Custom scrollbar */
141
+ ::-webkit-scrollbar {
142
+ width: 8px;
143
+ height: 8px;
144
+ }
145
+ ::-webkit-scrollbar-track {
146
+ @apply bg-gray-100 dark:bg-gray-800;
147
+ }
148
+ ::-webkit-scrollbar-thumb {
149
+ @apply bg-gray-300 dark:bg-gray-600 rounded-full;
150
+ }
151
+ ::-webkit-scrollbar-thumb:hover {
152
+ @apply bg-gray-400 dark:bg-gray-500;
153
+ }
154
+
155
+ /* Shimmer animation for loading */
156
+ @keyframes shimmer {
157
+ 0% { background-position: -1000px 0; }
158
+ 100% { background-position: 1000px 0; }
159
+ }
160
+ .shimmer {
161
+ animation: shimmer 2s infinite;
162
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
163
+ background-size: 1000px 100%;
164
+ }
165
+
166
+ /* Pygments dark theme overrides */
167
+ .dark .highlight { background: #1f2937 !important; }
168
+ .dark .highlight .c { color: #6ee7b7; } /* Comment */
169
+ .dark .highlight .k { color: #c084fc; } /* Keyword */
170
+ .dark .highlight .o { color: #9ca3af; } /* Operator */
171
+ .dark .highlight .s { color: #fca5a5; } /* String */
172
+ .dark .highlight .na { color: #fca5a5; } /* Name.Attribute */
173
+ .dark .highlight .nb { color: #c084fc; } /* Name.Builtin */
174
+ .dark .highlight .nc { color: #60a5fa; } /* Name.Class */
175
+ .dark .highlight .nf { color: #34d399; } /* Name.Function */
176
+ .dark .highlight .nn { color: #60a5fa; } /* Name.Namespace */
177
+ .dark .highlight .nt { color: #34d399; } /* Name.Tag */
178
+ .dark .highlight .m,
179
+ .dark .highlight .mi,
180
+ .dark .highlight .mf { color: #fb923c; } /* Numbers */
181
+
182
+ /* Simple badges */
183
+ .badge {
184
+ display: inline-flex;
185
+ align-items: center;
186
+ padding: 0.125rem 0.625rem;
187
+ border-radius: 0.25rem;
188
+ font-size: 0.75rem;
189
+ font-weight: 500;
190
+ text-transform: uppercase;
191
+ letter-spacing: 0.05em;
192
+ }
193
+
194
+ .badge-get {
195
+ background-color: #3b82f6;
196
+ color: white;
197
+ }
198
+
199
+ .badge-post {
200
+ background-color: #10b981;
201
+ color: white;
202
+ }
203
+
204
+ .badge-put {
205
+ background-color: #f97316;
206
+ color: white;
207
+ }
208
+
209
+ .badge-patch {
210
+ background-color: #a855f7;
211
+ color: white;
212
+ }
213
+
214
+ .badge-delete {
215
+ background-color: #ef4444;
216
+ color: white;
217
+ }
218
+
219
+ /* Status badges */
220
+ .status-success {
221
+ background-color: #dcfce7;
222
+ color: #166534;
223
+ }
224
+ .dark .status-success {
225
+ background-color: rgba(34, 197, 94, 0.3);
226
+ color: #86efac;
227
+ }
228
+
229
+ .status-info {
230
+ background-color: #dbeafe;
231
+ color: #1e40af;
232
+ }
233
+ .dark .status-info {
234
+ background-color: rgba(59, 130, 246, 0.3);
235
+ color: #93c5fd;
236
+ }
237
+
238
+ .status-warning {
239
+ background-color: #fef3c7;
240
+ color: #92400e;
241
+ }
242
+ .dark .status-warning {
243
+ background-color: rgba(234, 179, 8, 0.3);
244
+ color: #fde047;
245
+ }
246
+
247
+ .status-error {
248
+ background-color: #fee2e2;
249
+ color: #991b1b;
250
+ }
251
+ .dark .status-error {
252
+ background-color: rgba(239, 68, 68, 0.3);
253
+ color: #fca5a5;
254
+ }
255
+
256
+ /* Cards */
257
+ .card {
258
+ background-color: white;
259
+ border-radius: 0.5rem;
260
+ border: 1px solid #e5e7eb;
261
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
262
+ }
263
+ .dark .card {
264
+ background-color: #1f2937;
265
+ border-color: #374151;
266
+ }
267
+
268
+ /* Buttons */
269
+ .btn-icon {
270
+ padding: 0.5rem;
271
+ border-radius: 0.5rem;
272
+ }
273
+ .btn-icon:hover {
274
+ background-color: #f3f4f6;
275
+ }
276
+ .dark .btn-icon:hover {
277
+ background-color: #374151;
278
+ }
279
+
280
+ /* Base HTML elements styling */
281
+ input[type="text"],
282
+ input[type="email"],
283
+ input[type="password"],
284
+ input[type="number"],
285
+ input[type="url"],
286
+ input[type="search"],
287
+ textarea,
288
+ select {
289
+ width: 100%;
290
+ padding: 0.5rem 0.75rem;
291
+ background-color: white;
292
+ border: 1px solid #d1d5db;
293
+ border-radius: 0.5rem;
294
+ color: #111827;
295
+ }
296
+ .dark input[type="text"],
297
+ .dark input[type="email"],
298
+ .dark input[type="password"],
299
+ .dark input[type="number"],
300
+ .dark input[type="url"],
301
+ .dark input[type="search"],
302
+ .dark textarea,
303
+ .dark select {
304
+ background-color: #1f2937;
305
+ border-color: #4b5563;
306
+ color: white;
307
+ }
308
+
309
+ input:focus,
310
+ textarea:focus,
311
+ select:focus {
312
+ outline: none;
313
+ ring: 2px;
314
+ ring-color: #3b82f6;
315
+ border-color: #3b82f6;
316
+ }
317
+
318
+ button[type="submit"],
319
+ .btn-primary {
320
+ padding: 0.5rem 1rem;
321
+ background-color: #2563eb;
322
+ color: white;
323
+ border-radius: 0.5rem;
324
+ font-weight: 500;
325
+ }
326
+ button[type="submit"]:hover,
327
+ .btn-primary:hover {
328
+ background-color: #1d4ed8;
329
+ }
330
+
331
+ .content a {
332
+ color: #2563eb;
333
+ text-decoration: underline;
334
+ }
335
+ .dark .content a {
336
+ color: #60a5fa;
337
+ }
338
+
339
+ table {
340
+ width: 100%;
341
+ border-collapse: collapse;
342
+ }
343
+
344
+ th {
345
+ padding: 0.5rem 1rem;
346
+ background-color: #f3f4f6;
347
+ border: 1px solid #e5e7eb;
348
+ text-align: left;
349
+ font-weight: 500;
350
+ }
351
+ .dark th {
352
+ background-color: #1f2937;
353
+ border-color: #374151;
354
+ }
355
+
356
+ td {
357
+ padding: 0.5rem 1rem;
358
+ border: 1px solid #e5e7eb;
359
+ }
360
+ .dark td {
361
+ border-color: #374151;
362
+ }
363
+
364
+ pre {
365
+ background-color: #f3f4f6;
366
+ padding: 1rem;
367
+ border-radius: 0.5rem;
368
+ overflow-x: auto;
369
+ }
370
+ .dark pre {
371
+ background-color: #111827;
372
+ }
373
+
374
+ code {
375
+ background-color: #f3f4f6;
376
+ padding: 0.125rem 0.375rem;
377
+ border-radius: 0.25rem;
378
+ font-size: 0.875rem;
379
+ font-family: monospace;
380
+ }
381
+ .dark code {
382
+ background-color: #1f2937;
383
+ }
384
+
385
+ pre code {
386
+ background: transparent;
387
+ padding: 0;
388
+ }
389
+
390
+ h1, h2, h3, h4, h5, h6 {
391
+ font-weight: bold;
392
+ color: #111827;
393
+ }
394
+ .dark h1, .dark h2, .dark h3, .dark h4, .dark h5, .dark h6 {
395
+ color: white;
396
+ }
397
+
398
+ h1 { font-size: 1.875rem; }
399
+ h2 { font-size: 1.5rem; }
400
+ h3 { font-size: 1.25rem; }
401
+ h4 { font-size: 1.125rem; }
402
+
403
+ label {
404
+ display: block;
405
+ font-size: 0.875rem;
406
+ font-weight: 500;
407
+ color: #374151;
408
+ margin-bottom: 0.25rem;
409
+ }
410
+ .dark label {
411
+ color: #d1d5db;
412
+ }
413
+
414
+ .form-group {
415
+ margin-bottom: 1rem;
416
+ }
417
+
418
+ .alert {
419
+ padding: 1rem;
420
+ border-radius: 0.5rem;
421
+ border: 1px solid;
422
+ }
423
+
424
+ .alert-error {
425
+ background-color: #fef2f2;
426
+ border-color: #fecaca;
427
+ color: #991b1b;
428
+ }
429
+ .dark .alert-error {
430
+ background-color: rgba(239, 68, 68, 0.2);
431
+ border-color: #991b1b;
432
+ color: #fca5a5;
433
+ }
434
+
435
+ .alert-success {
436
+ background-color: #f0fdf4;
437
+ border-color: #bbf7d0;
438
+ color: #166534;
439
+ }
440
+ .dark .alert-success {
441
+ background-color: rgba(34, 197, 94, 0.2);
442
+ border-color: #166534;
443
+ color: #86efac;
444
+ }
445
+
446
+ .alert-info {
447
+ background-color: #eff6ff;
448
+ border-color: #bfdbfe;
449
+ color: #1e40af;
450
+ }
451
+ .dark .alert-info {
452
+ background-color: rgba(59, 130, 246, 0.2);
453
+ border-color: #1e40af;
454
+ color: #93c5fd;
455
+ }
456
+ </style>
457
+
458
+ {% if code_style %}<style>{{ code_style }}</style>{% endif %}
459
+ </head>
460
+
461
+ <body class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
462
+
463
+ {# Glass morphism navbar #}
464
+ <nav class="sticky top-0 z-50 glass border-b border-gray-200/50 dark:border-gray-700/50 shadow-sm">
465
+ <div class="px-6 py-3">
466
+ <div class="flex items-center justify-between">
467
+
468
+ {# Left: Branding + HTTP Method Badge #}
469
+ <div class="flex items-center space-x-4">
470
+ <a href="/" class="text-xl font-bold text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-all">
471
+ {% block branding %}{{ project_name|default:"API" }}{% endblock %}
472
+ </a>
473
+
474
+ {% if request.method %}
475
+ <span class="badge
476
+ {% if request.method == 'GET' %}badge-get
477
+ {% elif request.method == 'POST' %}badge-post
478
+ {% elif request.method == 'PUT' %}badge-put
479
+ {% elif request.method == 'PATCH' %}badge-patch
480
+ {% elif request.method == 'DELETE' %}badge-delete
481
+ {% else %}bg-gradient-to-r from-gray-500 to-gray-600 text-white{% endif %}">
482
+ {{ request.method }}
483
+ </span>
484
+ {% endif %}
485
+ </div>
486
+
487
+ {# Right: Actions #}
488
+ <div class="flex items-center space-x-2">
489
+
490
+ {# Command Palette Trigger #}
491
+ <button @click="commandPaletteOpen = true"
492
+ class="hidden md:flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-sm transition-all group">
493
+ <svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
494
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16l2.879-2.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242zM21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
495
+ </svg>
496
+ <span class="text-gray-600 dark:text-gray-400">Search</span>
497
+ <kbd class="px-2 py-0.5 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘K</kbd>
498
+ </button>
499
+
500
+ {# Theme Toggle #}
501
+ <div class="relative" x-data="{ open: false }">
502
+ <button @click="open = !open"
503
+ class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-all">
504
+ <svg x-show="theme === 'light'" class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
505
+ <path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
506
+ </svg>
507
+ <svg x-show="theme === 'dark'" class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
508
+ <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
509
+ </svg>
510
+ <svg x-show="theme === 'auto'" class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
511
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
512
+ </svg>
513
+ </button>
514
+
515
+ {# Theme dropdown #}
516
+ <div x-show="open"
517
+ @click.away="open = false"
518
+ x-transition
519
+ class="absolute right-0 mt-2 w-40 glass rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-1">
520
+ <button @click="setTheme('light'); open = false"
521
+ class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
522
+ <svg class="w-4 h-4 mr-2 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
523
+ <path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
524
+ </svg>
525
+ Light
526
+ </button>
527
+ <button @click="setTheme('dark'); open = false"
528
+ class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
529
+ <svg class="w-4 h-4 mr-2 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
530
+ <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
531
+ </svg>
532
+ Dark
533
+ </button>
534
+ <button @click="setTheme('auto'); open = false"
535
+ class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
536
+ <svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
537
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3"/>
538
+ </svg>
539
+ Auto
540
+ </button>
541
+ </div>
542
+ </div>
543
+
544
+ {# User Menu #}
545
+ <div class="flex items-center space-x-3 pl-3 border-l border-gray-200 dark:border-gray-700">
546
+ {% if user.is_authenticated %}
547
+ <div class="flex items-center space-x-3">
548
+ <div class="flex items-center space-x-2">
549
+ <div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-semibold text-sm">
550
+ {{ user.username|first|upper }}
551
+ </div>
552
+ <span class="hidden md:block text-sm font-medium text-gray-900 dark:text-white">{{ user.username }}</span>
553
+ </div>
554
+ {% if api_settings.LOGOUT_URL %}
555
+ <form action="{% url api_settings.LOGOUT_URL %}" method="post">
556
+ {% csrf_token %}
557
+ <button type="submit" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
558
+ Logout
559
+ </button>
560
+ </form>
561
+ {% endif %}
562
+ </div>
563
+ {% else %}
564
+ {% optional_login request %}
565
+ {% endif %}
566
+ </div>
567
+ </div>
568
+ </div>
569
+ </div>
570
+ </nav>
571
+
572
+ {# Main Content #}
573
+ <main class="container mx-auto px-4 py-6 max-w-7xl">
574
+
575
+ {# Breadcrumbs with animation #}
576
+ {% if breadcrumblist %}
577
+ <nav class="mb-6 animate-fade-in">
578
+ <ol class="flex items-center space-x-2 text-sm">
579
+ {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
580
+ {% if not forloop.last %}
581
+ <li class="flex items-center">
582
+ <a href="{{ breadcrumb_url }}"
583
+ class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline">
584
+ {{ breadcrumb_name }}
585
+ </a>
586
+ <svg class="w-4 h-4 mx-2 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
587
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
588
+ </svg>
589
+ </li>
590
+ {% else %}
591
+ <li class="text-gray-900 dark:text-gray-100 font-medium">{{ breadcrumb_name }}</li>
592
+ {% endif %}
593
+ {% endfor %}
594
+ </ol>
595
+ </nav>
596
+ {% endif %}
597
+
598
+ {# Content Block #}
599
+ <div class="space-y-6">
600
+ {% block content %}{% endblock %}
601
+ </div>
602
+
603
+ {# Footer #}
604
+ {% load django_cfg %}
605
+ <footer class="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
606
+ <div class="text-center">
607
+ <p class="text-sm text-gray-600 dark:text-gray-400">
608
+ Powered by
609
+ <a href="{% lib_site_url %}"
610
+ class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors"
611
+ target="_blank" rel="noopener noreferrer">
612
+ {% lib_name %}
613
+ </a>
614
+ </p>
615
+ <p class="mt-2 text-xs text-gray-500 dark:text-gray-500">
616
+ {% lib_subtitle %}
617
+ </p>
618
+ </div>
619
+ </footer>
620
+ </main>
621
+
622
+ {# Command Palette #}
623
+ <div x-show="commandPaletteOpen"
624
+ x-transition.opacity
625
+ @click="commandPaletteOpen = false"
626
+ class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50">
627
+
628
+ <div @click.stop
629
+ class="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-2xl glass rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
630
+
631
+ {# Search Input #}
632
+ <div class="p-4 border-b border-gray-200 dark:border-gray-700">
633
+ <div class="relative">
634
+ <svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
635
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
636
+ </svg>
637
+ <input type="text"
638
+ x-model="commandSearch"
639
+ @keydown.escape="commandPaletteOpen = false"
640
+ placeholder="Search actions, endpoints..."
641
+ class="w-full pl-10 pr-4 py-3 bg-transparent border-0 focus:outline-none text-lg">
642
+ </div>
643
+ </div>
644
+
645
+ {# Quick Actions #}
646
+ <div class="p-2 max-h-96 overflow-y-auto">
647
+ <div class="space-y-1">
648
+ <button @click="copyCurrentURL(); commandPaletteOpen = false"
649
+ class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
650
+ <svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
651
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
652
+ </svg>
653
+ <div class="flex-1">
654
+ <div class="font-medium">Copy Current URL</div>
655
+ <div class="text-sm text-gray-500">Copy this endpoint URL to clipboard</div>
656
+ </div>
657
+ <kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘C</kbd>
658
+ </button>
659
+
660
+ <button @click="toggleTheme(); commandPaletteOpen = false"
661
+ class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
662
+ <svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
663
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
664
+ </svg>
665
+ <div class="flex-1">
666
+ <div class="font-medium">Toggle Theme</div>
667
+ <div class="text-sm text-gray-500">Switch between light and dark mode</div>
668
+ </div>
669
+ <kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘D</kbd>
670
+ </button>
671
+
672
+ <button @click="showShortcuts(); commandPaletteOpen = false"
673
+ class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
674
+ <svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
675
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
676
+ </svg>
677
+ <div class="flex-1">
678
+ <div class="font-medium">Keyboard Shortcuts</div>
679
+ <div class="text-sm text-gray-500">View all available shortcuts</div>
680
+ </div>
681
+ <kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">?</kbd>
682
+ </button>
683
+ </div>
684
+ </div>
685
+ </div>
686
+ </div>
687
+
688
+ {# Toast Notifications #}
689
+ <div class="fixed bottom-4 right-4 z-50 space-y-2">
690
+ <template x-for="(toast, index) in toasts" :key="index">
691
+ <div x-show="toast.show"
692
+ x-transition:enter="transition ease-out duration-300"
693
+ x-transition:enter-start="opacity-0 translate-y-4"
694
+ x-transition:enter-end="opacity-100 translate-y-0"
695
+ x-transition:leave="transition ease-in duration-200"
696
+ x-transition:leave-start="opacity-100 translate-y-0"
697
+ x-transition:leave-end="opacity-0 translate-y-4"
698
+ class="glass rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 p-4 flex items-center space-x-3 max-w-sm">
699
+
700
+ <svg x-show="toast.type === 'success'" class="w-5 h-5 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
701
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
702
+ </svg>
703
+
704
+ <svg x-show="toast.type === 'error'" class="w-5 h-5 text-red-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
705
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
706
+ </svg>
707
+
708
+ <p class="flex-1 text-sm" x-text="toast.message"></p>
709
+
710
+ <button @click="removeToast(index)" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
711
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
712
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
713
+ </svg>
714
+ </button>
715
+ </div>
716
+ </template>
717
+ </div>
718
+
719
+ {# Scripts #}
720
+ {# jQuery required for DRF ajax-form.js #}
721
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
722
+
723
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
724
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script>
725
+ <script src="{% static 'rest_framework/js/csrf.js' %}"></script>
726
+ <script src="{% static 'rest_framework/js/ajax-form.js' %}"></script>
727
+
728
+ <script>
729
+ function drfApp() {
730
+ return {
731
+ theme: '{{ theme|default:"auto" }}',
732
+ commandPaletteOpen: false,
733
+ commandSearch: '',
734
+ toasts: [],
735
+
736
+ init() {
737
+ this.applyTheme();
738
+ this.listenForSystemTheme();
739
+ },
740
+
741
+ applyTheme() {
742
+ let isDark = false;
743
+
744
+ if (this.theme === 'dark') {
745
+ document.documentElement.classList.add('dark');
746
+ isDark = true;
747
+ } else if (this.theme === 'light') {
748
+ document.documentElement.classList.remove('dark');
749
+ isDark = false;
750
+ } else {
751
+ // Auto: use system preference
752
+ isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
753
+ if (isDark) {
754
+ document.documentElement.classList.add('dark');
755
+ } else {
756
+ document.documentElement.classList.remove('dark');
757
+ }
758
+ }
759
+
760
+ // Switch Prism.js theme
761
+ const prismLight = document.getElementById('prism-light');
762
+ const prismDark = document.getElementById('prism-dark');
763
+ if (prismLight && prismDark) {
764
+ prismLight.disabled = isDark;
765
+ prismDark.disabled = !isDark;
766
+ }
767
+ },
768
+
769
+ setTheme(newTheme) {
770
+ this.theme = newTheme;
771
+ this.applyTheme();
772
+ document.cookie = `theme=${newTheme}; path=/; max-age=31536000; SameSite=Lax`;
773
+ this.showToast(`Theme changed to ${newTheme}`, 'success');
774
+ },
775
+
776
+ toggleTheme() {
777
+ const themes = ['light', 'dark', 'auto'];
778
+ const currentIndex = themes.indexOf(this.theme);
779
+ const nextTheme = themes[(currentIndex + 1) % themes.length];
780
+ this.setTheme(nextTheme);
781
+ },
782
+
783
+ listenForSystemTheme() {
784
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
785
+ if (this.theme === 'auto') {
786
+ this.applyTheme();
787
+ }
788
+ });
789
+ },
790
+
791
+ handleKeyboard(event) {
792
+ // Command Palette (Cmd/Ctrl + K)
793
+ if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
794
+ event.preventDefault();
795
+ this.commandPaletteOpen = !this.commandPaletteOpen;
796
+ }
797
+
798
+ // Toggle theme (Cmd/Ctrl + D)
799
+ if ((event.metaKey || event.ctrlKey) && event.key === 'd') {
800
+ event.preventDefault();
801
+ this.toggleTheme();
802
+ }
803
+
804
+ // Copy URL (Cmd/Ctrl + C when not in input and no text selected)
805
+ if ((event.metaKey || event.ctrlKey) && event.key === 'c' && !['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
806
+ // Only intercept if no text is selected
807
+ const selection = window.getSelection();
808
+ if (!selection || selection.toString().length === 0) {
809
+ event.preventDefault();
810
+ this.copyCurrentURL();
811
+ }
812
+ }
813
+
814
+ // Show shortcuts (?)
815
+ if (event.key === '?' && !['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
816
+ event.preventDefault();
817
+ this.showShortcuts();
818
+ }
819
+
820
+ // Close command palette (Escape)
821
+ if (event.key === 'Escape') {
822
+ this.commandPaletteOpen = false;
823
+ }
824
+ },
825
+
826
+ copyCurrentURL() {
827
+ navigator.clipboard.writeText(window.location.href).then(() => {
828
+ this.showToast('URL copied to clipboard!', 'success');
829
+ }).catch(() => {
830
+ this.showToast('Failed to copy URL', 'error');
831
+ });
832
+ },
833
+
834
+ showShortcuts() {
835
+ const shortcuts = `
836
+ Keyboard Shortcuts:
837
+
838
+ ⌘K or Ctrl+K - Open command palette
839
+ ⌘D or Ctrl+D - Toggle theme
840
+ ⌘C or Ctrl+C - Copy current URL
841
+ ? - Show this help
842
+ Esc - Close dialogs
843
+ `.trim();
844
+
845
+ alert(shortcuts);
846
+ },
847
+
848
+ showToast(message, type = 'success') {
849
+ const toast = { message, type, show: true };
850
+ this.toasts.push(toast);
851
+
852
+ setTimeout(() => {
853
+ const index = this.toasts.indexOf(toast);
854
+ if (index > -1) {
855
+ this.toasts[index].show = false;
856
+ setTimeout(() => this.removeToast(index), 300);
857
+ }
858
+ }, 3000);
859
+ },
860
+
861
+ removeToast(index) {
862
+ this.toasts.splice(index, 1);
863
+ }
864
+ }
865
+ }
866
+
867
+ // Global JSON rendering function
868
+ function renderJSON(data, level = 0) {
869
+ if (data === null) return '<span class="text-gray-500 dark:text-gray-400">null</span>';
870
+ if (data === undefined) return '<span class="text-gray-500 dark:text-gray-400">undefined</span>';
871
+ if (typeof data === 'boolean') return `<span class="text-purple-600 dark:text-purple-400">${data}</span>`;
872
+ if (typeof data === 'number') return `<span class="text-orange-600 dark:text-orange-400">${data}</span>`;
873
+ if (typeof data === 'string') return `<span class="text-green-600 dark:text-green-400">"${escapeHtml(data)}"</span>`;
874
+
875
+ const indent = ' '.repeat(level);
876
+ const childIndent = ' '.repeat(level + 1);
877
+
878
+ if (Array.isArray(data)) {
879
+ if (data.length === 0) return '<span class="text-gray-600 dark:text-gray-400">[]</span>';
880
+
881
+ const id = 'json-' + Math.random().toString(36).substr(2, 9);
882
+ let html = `<div class="json-node">
883
+ <span class="json-toggle cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-700 px-1 rounded"
884
+ onclick="this.dataset.expanded = this.dataset.expanded === 'true' ? 'false' : 'true';
885
+ this.textContent = this.dataset.expanded === 'true' ? '▼' : '▶';
886
+ document.getElementById('${id}').style.display = this.dataset.expanded === 'true' ? 'block' : 'none'"
887
+ data-expanded="false">▶</span>
888
+ <span class="text-gray-600 dark:text-gray-400">[</span>
889
+ <span class="text-xs text-gray-500 dark:text-gray-500 ml-2">${data.length} items</span>
890
+ <div id="${id}" class="json-content ml-4" style="display: none;">`;
891
+
892
+ data.forEach((item, i) => {
893
+ html += `<div class="py-0.5">${childIndent}${renderJSON(item, level + 1)}${i < data.length - 1 ? '<span class="text-gray-600 dark:text-gray-400">,</span>' : ''}</div>`;
894
+ });
895
+
896
+ html += `</div>${indent}<span class="text-gray-600 dark:text-gray-400">]</span></div>`;
897
+ return html;
898
+ }
899
+
900
+ if (typeof data === 'object') {
901
+ const keys = Object.keys(data);
902
+ if (keys.length === 0) return '<span class="text-gray-600 dark:text-gray-400">{}</span>';
903
+
904
+ const id = 'json-' + Math.random().toString(36).substr(2, 9);
905
+ let html = `<div class="json-node">
906
+ <span class="json-toggle cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-700 px-1 rounded"
907
+ onclick="this.dataset.expanded = this.dataset.expanded === 'true' ? 'false' : 'true';
908
+ this.textContent = this.dataset.expanded === 'true' ? '▼' : '▶';
909
+ document.getElementById('${id}').style.display = this.dataset.expanded === 'true' ? 'block' : 'none'"
910
+ data-expanded="false">▶</span>
911
+ <span class="text-gray-600 dark:text-gray-400">{</span>
912
+ <span class="text-xs text-gray-500 dark:text-gray-500 ml-2">${keys.length} keys</span>
913
+ <div id="${id}" class="json-content ml-4" style="display: none;">`;
914
+
915
+ keys.forEach((key, i) => {
916
+ html += `<div class="py-0.5">${childIndent}<span class="text-blue-600 dark:text-blue-400">"${escapeHtml(key)}"</span><span class="text-gray-600 dark:text-gray-400">: </span>${renderJSON(data[key], level + 1)}${i < keys.length - 1 ? '<span class="text-gray-600 dark:text-gray-400">,</span>' : ''}</div>`;
917
+ });
918
+
919
+ html += `</div>${indent}<span class="text-gray-600 dark:text-gray-400">}</span></div>`;
920
+ return html;
921
+ }
922
+
923
+ return String(data);
924
+ }
925
+
926
+ function escapeHtml(text) {
927
+ const div = document.createElement('div');
928
+ div.textContent = text;
929
+ return div.innerHTML;
930
+ }
931
+ </script>
932
+
933
+ {% if django_browser_reload_enabled %}
934
+ <script src="{% static 'django_browser_reload/reload-listener.js' %}"></script>
935
+ {% endif %}
936
+
937
+ </body>
938
+ </html>