django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,10 @@
1
1
  """
2
2
  Smart defaults system for django_cfg.
3
3
 
4
- Following CRITICAL_REQUIREMENTS.md:
5
- - No raw Dict/Any usage
6
- - Proper type annotations
7
- - Environment-aware configuration
8
- - No mutable default arguments
4
+ Following KISS principle:
5
+ - Simple, clear configuration
6
+ - No complex environment logic
7
+ - Logging handled by django_logger module
9
8
  """
10
9
 
11
10
  from typing import Dict, Any, Optional, List
@@ -16,486 +15,91 @@ from django_cfg.models.services import EmailConfig
16
15
  from django_cfg.core.exceptions import ConfigurationError
17
16
 
18
17
 
19
- class SmartDefaults:
18
+ def get_log_filename() -> str:
20
19
  """
21
- Environment-aware smart defaults for Django configuration.
20
+ Determine the correct log filename based on project type.
22
21
 
23
- Automatically selects appropriate defaults based on:
24
- - Environment (development, production, testing, staging)
25
- - DEBUG flag
26
- - Available services (Redis, SMTP, etc.)
27
- - Security requirements
22
+ Returns:
23
+ - 'django-cfg.log' for django-cfg projects
24
+ - 'django.log' for regular Django projects
28
25
  """
29
-
30
- @classmethod
31
- def configure_cache_backend(
32
- cls,
33
- cache_config: CacheConfig,
34
- environment: Optional[str] = None,
35
- debug: bool = False
36
- ) -> CacheConfig:
37
- """
38
- Configure cache backend with environment-aware defaults.
26
+ try:
27
+ # Check for django-cfg in installed apps
28
+ from django.conf import settings
29
+ if hasattr(settings, 'INSTALLED_APPS'):
30
+ for app in settings.INSTALLED_APPS:
31
+ if 'django_cfg' in app:
32
+ return 'django-cfg.log'
39
33
 
40
- Args:
41
- cache_config: Base cache configuration
42
- environment: Current environment
43
- debug: Django DEBUG setting
44
-
45
- Returns:
46
- Configured cache backend
47
-
48
- Raises:
49
- ConfigurationError: If configuration cannot be applied
50
- """
51
- try:
52
- # Create a copy to avoid modifying the original
53
- config: CacheConfig = cache_config.model_copy()
54
-
55
- # Environment-specific adjustments
56
- if environment == "testing":
57
- # Testing: Use dummy cache with very short timeouts
58
- config.backend_override = "django.core.cache.backends.dummy.DummyCache"
59
- config.timeout = min(config.timeout, 1) # Max 1 second for tests
60
-
61
- elif environment == "development" or debug:
62
- # Development: Prefer memory cache, shorter timeouts
63
- if not config.redis_url:
64
- # No Redis configured - use memory cache
65
- config.timeout = min(config.timeout, 300) # Max 5 minutes
66
- else:
67
- # Redis available - use it but with shorter timeouts
68
- config.timeout = min(config.timeout, 600) # Max 10 minutes
69
- config.max_connections = min(config.max_connections, 20) # Fewer connections
70
-
71
- elif environment in ("production", "staging"):
72
- # Production: Use Redis if available, longer timeouts
73
- if config.redis_url:
74
- config.timeout = max(config.timeout, 300) # Min 5 minutes
75
- config.max_connections = max(config.max_connections, 50) # More connections
76
- else:
77
- # Production without Redis - warn but use file cache
78
- config.backend_override = "django.core.cache.backends.filebased.FileBasedCache"
79
-
80
- # Apply compression for production
81
- if environment == "production" and config.redis_url:
82
- config.compress = True
83
-
84
- return config
85
-
86
- except Exception as e:
87
- raise ConfigurationError(
88
- f"Failed to configure cache backend: {e}",
89
- context={
90
- 'environment': environment,
91
- 'debug': debug,
92
- 'cache_config': cache_config.model_dump()
93
- }
94
- ) from e
95
-
96
- @classmethod
97
- def configure_email_backend(
98
- cls,
99
- email_config: EmailConfig,
100
- environment: Optional[str] = None,
101
- debug: bool = False
102
- ) -> EmailConfig:
103
- """
104
- Configure email backend with environment-aware defaults.
34
+ # Default to regular Django log
35
+ return 'django.log'
105
36
 
106
- Args:
107
- email_config: Base email configuration
108
- environment: Current environment
109
- debug: Django DEBUG setting
110
-
111
- Returns:
112
- Configured email backend
113
-
114
- Raises:
115
- ConfigurationError: If configuration cannot be applied
116
- """
117
- try:
118
- # Create a copy to avoid modifying the original
119
- config = email_config.model_copy()
120
-
121
- # Environment-specific adjustments
122
- if environment == "testing":
123
- # Testing: Use in-memory backend
124
- config.backend_override = "django.core.mail.backends.locmem.EmailBackend"
125
- config.timeout = min(config.timeout, 5) # Very short timeout
126
-
127
- elif environment == "development":
128
- # Development: Use SMTP if configured (allow real email sending in dev)
129
- if config.username and config.password:
130
- # SMTP configured - use SMTP backend
131
- config.backend_override = "django.core.mail.backends.smtp.EmailBackend"
132
- # Note: No fallback to console - let user decide via backend setting
133
-
134
- elif environment in ("production", "staging"):
135
- # Production: Use SMTP if properly configured
136
- if config.username and config.password:
137
- config.backend_override = "django.core.mail.backends.smtp.EmailBackend"
138
-
139
- # Production email security
140
- if config.port == 587:
141
- config.use_tls = True
142
- config.use_ssl = False
143
- elif config.port == 465:
144
- config.use_tls = False
145
- config.use_ssl = True
146
-
147
- # Longer timeout for production
148
- config.timeout = max(config.timeout, 30)
149
- else:
150
- # Production without SMTP - fallback to console with warning
151
- config.backend_override = "django.core.mail.backends.console.EmailBackend"
152
-
153
- return config
154
-
155
- except Exception as e:
156
- raise ConfigurationError(
157
- f"Failed to configure email backend: {e}",
158
- context={
159
- 'environment': environment,
160
- 'debug': debug,
161
- 'email_config': email_config.model_dump(exclude={'password'})
162
- }
163
- ) from e
37
+ except Exception:
38
+ # Fallback to django-cfg filename (since we're in django-cfg module)
39
+ return 'django-cfg.log'
40
+
41
+
42
+ class SmartDefaults:
43
+ """
44
+ Environment-aware smart defaults for Django configuration.
164
45
 
165
- @classmethod
166
- def get_security_defaults(
167
- cls,
168
- domains: List[str],
169
- environment: Optional[str] = None,
170
- debug: bool = False,
171
- ssl_redirect: Optional[bool] = None,
172
- cors_allow_headers: Optional[List[str]] = None
173
- ) -> Dict[str, Any]:
174
- """
175
- Get security defaults based on environment and domains.
176
-
177
- Args:
178
- domains: List of domains for CORS/security configuration
179
- environment: Current environment
180
- debug: Django DEBUG setting
181
- ssl_redirect: Force SSL redirect on/off (None = auto based on domains)
182
- cors_allow_headers: Additional CORS headers to extend defaults
183
-
184
- Returns:
185
- Security settings dictionary
186
- """
187
- try:
188
- settings = {}
189
-
190
- if not domains:
191
- return settings
192
-
193
- is_dev = environment == "development" or debug
194
-
195
- # Generate CORS settings
196
- cors_settings = cls._generate_cors_settings(domains, is_dev, cors_allow_headers)
197
- settings.update(cors_settings)
198
-
199
- # Generate CSRF trusted origins
200
- csrf_settings = cls._generate_csrf_settings(domains, is_dev)
201
- settings.update(csrf_settings)
202
-
203
- # Generate security headers for production
204
- if environment == "production":
205
- security_headers = cls._generate_security_headers(domains, ssl_redirect)
206
- settings.update(security_headers)
207
-
208
- return settings
209
-
210
- except Exception as e:
211
- raise ConfigurationError(
212
- f"Failed to generate security defaults: {e}",
213
- context={
214
- 'domains': domains,
215
- 'environment': environment,
216
- 'debug': debug
217
- }
218
- ) from e
46
+ Provides intelligent defaults based on environment detection
47
+ with comprehensive type safety and validation.
48
+ """
219
49
 
220
- @classmethod
221
- def _generate_cors_settings(
222
- cls,
223
- domains: List[str],
224
- is_dev: bool,
225
- cors_allow_headers: Optional[List[str]] = None
226
- ) -> Dict[str, Any]:
227
- """Generate CORS-specific settings."""
228
- settings = {
229
- 'CORS_ALLOW_CREDENTIALS': True,
230
- 'CORS_ALLOW_HEADERS': cls._get_cors_headers(cors_allow_headers)
50
+ @staticmethod
51
+ def get_database_defaults(environment: str = "development", debug: bool = False, engine: str = "sqlite3") -> Dict[str, Any]:
52
+ """Get database configuration defaults."""
53
+ defaults = {
54
+ 'ENGINE': 'django.db.backends.sqlite3',
55
+ 'NAME': Path('db') / 'db.sqlite3',
56
+ 'ATOMIC_REQUESTS': True,
57
+ 'CONN_MAX_AGE': 60,
58
+ 'OPTIONS': {}
231
59
  }
232
60
 
233
- if is_dev:
234
- # Development: Allow all origins for convenience
235
- settings['CORS_ALLOW_ALL_ORIGINS'] = True
236
- else:
237
- # Production: Restrict to specified domains
238
- settings['CORS_ALLOWED_ORIGINS'] = cls._build_allowed_origins(domains)
239
-
240
- return settings
241
-
242
- @classmethod
243
- def _generate_csrf_settings(cls, domains: List[str], is_dev: bool) -> Dict[str, Any]:
244
- """Generate CSRF trusted origins."""
245
- if is_dev:
246
- csrf_origins = cls._build_dev_csrf_origins(domains)
247
- else:
248
- csrf_origins = cls._build_prod_csrf_origins(domains)
249
-
250
- # Only return CSRF settings if we have origins to set
251
- if csrf_origins:
252
- return {'CSRF_TRUSTED_ORIGINS': csrf_origins}
61
+ # Add engine-specific options
62
+ if engine == "django.db.backends.postgresql":
63
+ defaults['OPTIONS']['connect_timeout'] = 20
64
+ elif engine == "django.db.backends.sqlite3":
65
+ defaults['OPTIONS']['timeout'] = 20 # SQLite uses 'timeout'
253
66
 
254
- return {}
67
+ return defaults
255
68
 
256
- @classmethod
257
- def _generate_security_headers(
258
- cls,
259
- domains: List[str],
260
- ssl_redirect: Optional[bool] = None
261
- ) -> Dict[str, Any]:
262
- """Generate security headers for production."""
263
- settings = {
264
- 'SECURE_BROWSER_XSS_FILTER': True,
265
- 'SECURE_CONTENT_TYPE_NOSNIFF': True,
266
- 'SECURE_HSTS_SECONDS': 31536000, # 1 year
267
- 'SECURE_HSTS_INCLUDE_SUBDOMAINS': True,
268
- 'SECURE_HSTS_PRELOAD': True,
269
- 'X_FRAME_OPTIONS': 'DENY',
69
+ @staticmethod
70
+ def get_cache_defaults() -> Dict[str, Any]:
71
+ """Get cache configuration defaults."""
72
+ return {
73
+ 'default': {
74
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
75
+ 'LOCATION': 'default-cache',
76
+ 'TIMEOUT': 300,
77
+ 'OPTIONS': {
78
+ 'MAX_ENTRIES': 1000,
79
+ }
80
+ }
270
81
  }
271
-
272
- # SSL settings - configurable or auto-detect based on domains
273
- should_use_ssl = ssl_redirect if ssl_redirect is not None else bool(domains)
274
-
275
- if should_use_ssl:
276
- settings.update({
277
- 'SECURE_SSL_REDIRECT': True,
278
- 'SESSION_COOKIE_SECURE': True,
279
- 'CSRF_COOKIE_SECURE': True,
280
- })
281
- elif ssl_redirect is False:
282
- # Explicitly disable SSL redirect
283
- settings.update({
284
- 'SECURE_SSL_REDIRECT': False,
285
- 'SESSION_COOKIE_SECURE': False,
286
- 'CSRF_COOKIE_SECURE': False,
287
- })
288
-
289
- return settings
290
-
291
- @classmethod
292
- def _get_cors_headers(cls, cors_allow_headers: Optional[List[str]] = None) -> List[str]:
293
- """
294
- Get CORS headers with defaults extended by custom headers.
295
-
296
- Note: The default headers are defined in DjangoConfig.cors_allow_headers.
297
- This method should be consistent with those defaults.
298
- """
299
- # Default headers - should match DjangoConfig.cors_allow_headers defaults
300
- default_headers = [
301
- "accept",
302
- "accept-encoding",
303
- "authorization",
304
- "content-type",
305
- "dnt",
306
- "origin",
307
- "user-agent",
308
- "x-csrftoken",
309
- "x-requested-with",
310
- "x-api-key",
311
- "x-api-token",
312
- ]
313
-
314
- if not cors_allow_headers:
315
- return default_headers
316
-
317
- # Extend with custom headers and remove duplicates while preserving order
318
- return cls._merge_headers(default_headers, cors_allow_headers)
319
-
320
- @classmethod
321
- def _merge_headers(cls, default_headers: List[str], custom_headers: List[str]) -> List[str]:
322
- """Merge header lists removing duplicates while preserving order."""
323
- all_headers = default_headers + custom_headers
324
- seen = set()
325
- unique_headers = []
326
-
327
- for header in all_headers:
328
- header_lower = header.lower()
329
- if header_lower not in seen:
330
- seen.add(header_lower)
331
- unique_headers.append(header)
332
-
333
- return unique_headers
334
-
335
- @classmethod
336
- def _build_allowed_origins(cls, domains: List[str]) -> List[str]:
337
- """Build CORS allowed origins for production."""
338
- allowed_origins = []
339
-
340
- for domain in domains:
341
- # Add HTTPS version
342
- allowed_origins.append(f"https://{domain}")
343
-
344
- # Add www. version if applicable
345
- if cls._should_add_www_variant(domain):
346
- allowed_origins.append(f"https://www.{domain}")
347
-
348
- return allowed_origins
349
82
 
350
- @classmethod
351
- def _build_dev_csrf_origins(cls, domains: List[str]) -> List[str]:
352
- """Build CSRF trusted origins for development."""
353
- csrf_origins = []
354
-
355
- for domain in domains:
356
- if domain.startswith(('localhost', '127.0.0.1', '0.0.0.0')):
357
- # Local domains: add HTTP with common ports
358
- csrf_origins.extend([
359
- f"http://{domain}",
360
- f"http://{domain}:8000",
361
- f"http://{domain}:3000",
362
- ])
363
- elif domain.endswith('.local'):
364
- # .local domains: add both HTTP and HTTPS
365
- csrf_origins.extend([
366
- f"http://{domain}",
367
- f"https://{domain}",
368
- ])
369
- else:
370
- # External domains: add both for dev flexibility
371
- csrf_origins.extend([
372
- f"https://{domain}",
373
- f"http://{domain}",
374
- ])
375
-
376
- return csrf_origins
377
-
378
- @classmethod
379
- def _build_prod_csrf_origins(cls, domains: List[str]) -> List[str]:
380
- """Build CSRF trusted origins for production."""
381
- csrf_origins = []
382
-
383
- for domain in domains:
384
- # Add HTTPS version
385
- csrf_origins.append(f"https://{domain}")
386
-
387
- # Add www. version if applicable
388
- if cls._should_add_www_variant(domain):
389
- csrf_origins.append(f"https://www.{domain}")
390
-
391
- return csrf_origins
392
-
393
- @classmethod
394
- def _should_add_www_variant(cls, domain: str) -> bool:
395
- """
396
- Check if domain should get a www. variant added.
397
-
398
- Simple rule: add www. variant only if domain doesn't already start with www.
399
- Let users handle complex subdomain logic themselves by providing exact domains they want.
400
-
401
- Examples:
402
- - example.com -> True (add www.example.com)
403
- - www.example.com -> False (already has www)
404
- - api.example.com -> True (add www.api.example.com - let user decide)
405
- - localhost -> False (no dot)
406
- """
407
- if not domain or '.' not in domain:
408
- return False
409
-
410
- # Don't add www if domain already starts with www
411
- return not domain.startswith('www.')
412
-
413
- @classmethod
414
- def get_database_defaults(
415
- cls,
416
- environment: Optional[str] = None,
83
+ @staticmethod
84
+ def get_security_defaults(
85
+ security_domains=None,
86
+ environment: str = "development",
417
87
  debug: bool = False,
418
- engine: Optional[str] = None
88
+ ssl_redirect=None,
89
+ cors_allow_headers=None
419
90
  ) -> Dict[str, Any]:
420
- """
421
- Get database defaults based on environment and engine.
422
-
423
- Args:
424
- environment: Current environment
425
- debug: Django DEBUG setting
426
- engine: Database engine (to determine which options to apply)
427
-
428
- Returns:
429
- Database settings dictionary
430
- """
431
- try:
432
- defaults = {}
433
-
434
- if environment == "testing":
435
- # Testing: Use in-memory SQLite
436
- defaults = {
437
- 'ENGINE': 'django.db.backends.sqlite3',
438
- 'NAME': ':memory:',
439
- 'OPTIONS': {
440
- 'timeout': 1, # Very short timeout for tests
441
- }
442
- }
443
-
444
- elif environment == "development" or debug:
445
- # Development: Only add PostgreSQL/MySQL specific options
446
- if engine and engine in ("django.db.backends.postgresql", "django.db.backends.mysql"):
447
- defaults = {
448
- 'OPTIONS': {
449
- 'connect_timeout': 5, # Short timeout for dev
450
- }
451
- }
452
- # Add sslmode only for PostgreSQL
453
- if engine == "django.db.backends.postgresql":
454
- defaults['OPTIONS']['sslmode'] = 'prefer'
455
- else:
456
- # For SQLite, no special options needed
457
- defaults = {}
458
-
459
- elif environment in ("production", "staging"):
460
- # Production: Only add PostgreSQL/MySQL specific options
461
- if engine and engine in ("django.db.backends.postgresql", "django.db.backends.mysql"):
462
- if engine == "django.db.backends.postgresql":
463
- # psycopg3 supports connection pooling with proper parameters
464
- defaults = {
465
- 'OPTIONS': {
466
- 'connect_timeout': 10,
467
- # psycopg3 connection pool parameters
468
- 'pool': {
469
- 'min_size': 1,
470
- 'max_size': 20,
471
- 'timeout': 30.0,
472
- }
473
- }
474
- }
475
- else:
476
- # MySQL
477
- defaults = {
478
- 'OPTIONS': {
479
- 'connect_timeout': 10,
480
- }
481
- }
482
- # Add sslmode only for PostgreSQL
483
- if engine == "django.db.backends.postgresql":
484
- defaults['OPTIONS']['sslmode'] = 'require' # Require SSL in production
485
- else:
486
- # For SQLite, no special options needed
487
- defaults = {}
488
-
489
- return defaults
490
-
491
- except Exception as e:
492
- raise ConfigurationError(
493
- f"Failed to generate database defaults: {e}",
494
- context={
495
- 'environment': environment,
496
- 'debug': debug
497
- }
498
- ) from e
91
+ """Get security configuration defaults."""
92
+ return {
93
+ 'USE_TZ': True,
94
+ 'USE_I18N': True,
95
+ 'USE_L10N': True,
96
+ 'SECURE_BROWSER_XSS_FILTER': not debug,
97
+ 'SECURE_CONTENT_TYPE_NOSNIFF': not debug,
98
+ 'X_FRAME_OPTIONS': 'DENY' if not debug else 'SAMEORIGIN',
99
+ 'SECURE_HSTS_SECONDS': 31536000 if not debug else 0,
100
+ 'SECURE_HSTS_INCLUDE_SUBDOMAINS': not debug,
101
+ 'SECURE_HSTS_PRELOAD': not debug,
102
+ }
499
103
 
500
104
  @classmethod
501
105
  def get_logging_defaults(
@@ -504,116 +108,169 @@ class SmartDefaults:
504
108
  debug: bool = False
505
109
  ) -> Dict[str, Any]:
506
110
  """
507
- Get logging defaults based on environment.
111
+ Simple logging configuration.
112
+
113
+ NOTE: Real logging setup is handled by django_cfg.modules.django_logger
114
+ This provides minimal fallback configuration only.
508
115
 
509
116
  Args:
510
- environment: Current environment
511
- debug: Django DEBUG setting
117
+ environment: Environment name (ignored - for compatibility)
118
+ debug: Debug mode (ignored - for compatibility)
512
119
 
513
120
  Returns:
514
- Logging configuration dictionary
121
+ Minimal Django logging configuration
515
122
  """
516
- try:
517
- if environment == "testing":
518
- # Testing: Minimal logging
519
- return {
520
- 'version': 1,
521
- 'disable_existing_loggers': False,
522
- 'handlers': {
523
- 'null': {
524
- 'class': 'logging.NullHandler',
525
- },
526
- },
527
- 'root': {
528
- 'handlers': ['null'],
529
- },
530
- }
531
-
532
- elif environment == "development" or debug:
533
- # Development: Console logging with colors
534
- return {
535
- 'version': 1,
536
- 'disable_existing_loggers': False,
537
- 'formatters': {
538
- 'verbose': {
539
- 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
540
- 'style': '{',
541
- },
542
- },
543
- 'handlers': {
544
- 'console': {
545
- 'class': 'logging.StreamHandler',
546
- 'formatter': 'verbose',
547
- },
548
- },
549
- 'root': {
550
- 'handlers': ['console'],
551
- 'level': 'DEBUG',
552
- },
553
- 'loggers': {
554
- 'django': {
555
- 'handlers': ['console'],
556
- 'level': 'INFO',
557
- 'propagate': False,
558
- },
559
- },
560
- }
561
-
562
- elif environment in ("production", "staging"):
563
- # Production: File and structured logging
564
- return {
565
- 'version': 1,
566
- 'disable_existing_loggers': False,
567
- 'formatters': {
568
- 'json': {
569
- 'class': 'pythonjsonlogger.jsonlogger.JsonFormatter',
570
- 'format': '%(asctime)s %(name)s %(levelname)s %(message)s'
571
- },
572
- 'standard': {
573
- 'format': '{levelname} {asctime} {name} {message}',
574
- 'style': '{',
575
- },
576
- },
577
- 'handlers': {
578
- 'file': {
579
- 'class': 'logging.handlers.RotatingFileHandler',
580
- 'filename': 'logs/django.log',
581
- 'maxBytes': 1024*1024*10, # 10MB
582
- 'backupCount': 5,
583
- 'formatter': 'json' if environment == "production" else 'standard',
584
- },
585
- 'console': {
586
- 'class': 'logging.StreamHandler',
587
- 'formatter': 'standard',
588
- },
589
- },
590
- 'root': {
591
- 'handlers': ['file', 'console'],
592
- 'level': 'WARNING' if environment == "production" else 'INFO',
593
- },
594
- 'loggers': {
595
- 'django': {
596
- 'handlers': ['file'],
597
- 'level': 'INFO',
598
- 'propagate': False,
599
- },
600
- },
601
- }
123
+ # Minimal fallback - actual logging is configured by django_logger module
124
+ return {
125
+ 'version': 1,
126
+ 'disable_existing_loggers': False,
127
+ 'handlers': {
128
+ 'console': {
129
+ 'class': 'logging.StreamHandler',
130
+ },
131
+ },
132
+ 'root': {
133
+ 'handlers': ['console'],
134
+ 'level': 'INFO',
135
+ },
136
+ }
137
+
138
+ @staticmethod
139
+ def get_middleware_defaults() -> List[str]:
140
+ """Get middleware configuration defaults."""
141
+ return [
142
+ 'corsheaders.middleware.CorsMiddleware',
143
+ 'django.middleware.security.SecurityMiddleware',
144
+ 'django.contrib.sessions.middleware.SessionMiddleware',
145
+ 'django.middleware.common.CommonMiddleware',
146
+ 'django.middleware.csrf.CsrfViewMiddleware',
147
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
148
+ 'django.contrib.messages.middleware.MessageMiddleware',
149
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
150
+ ]
151
+
152
+ @staticmethod
153
+ def get_installed_apps_defaults() -> List[str]:
154
+ """Get default installed apps."""
155
+ return [
156
+ # Unfold admin
157
+ "unfold",
158
+ "unfold.contrib.filters",
159
+ "unfold.contrib.forms",
160
+ "unfold.contrib.inlines",
161
+ "import_export",
162
+ "unfold.contrib.import_export",
163
+ "unfold.contrib.guardian",
164
+ "unfold.contrib.simple_history",
165
+ "unfold.contrib.location_field",
166
+ "unfold.contrib.constance",
602
167
 
603
- # Fallback
604
- return {}
168
+ # Django core
169
+ "django.contrib.admin",
170
+ "django.contrib.auth",
171
+ "django.contrib.contenttypes",
172
+ "django.contrib.sessions",
173
+ "django.contrib.messages",
174
+ "django.contrib.staticfiles",
175
+ "django.contrib.humanize",
605
176
 
606
- except Exception as e:
607
- raise ConfigurationError(
608
- f"Failed to generate logging defaults: {e}",
609
- context={
610
- 'environment': environment,
611
- 'debug': debug
612
- }
613
- ) from e
177
+ # Third-party
178
+ "corsheaders",
179
+ "rest_framework",
180
+ "rest_framework.authtoken",
181
+ "rest_framework_simplejwt",
182
+ "rest_framework_simplejwt.token_blacklist",
183
+ "rest_framework_nested",
184
+ "rangefilter",
185
+ "django_filters",
186
+ "drf_spectacular",
187
+ "drf_spectacular_sidecar",
188
+ "django_json_widget",
189
+ "django_extensions",
190
+ "constance",
191
+ "constance.backends.database",
192
+
193
+ # Django CFG
194
+ "django_cfg",
195
+ ]
196
+
197
+ @staticmethod
198
+ def get_templates_defaults() -> List[Dict[str, Any]]:
199
+ """Get templates configuration defaults."""
200
+ return [
201
+ {
202
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
203
+ 'DIRS': ['templates'],
204
+ 'APP_DIRS': True,
205
+ 'OPTIONS': {
206
+ 'context_processors': [
207
+ 'django.template.context_processors.debug',
208
+ 'django.template.context_processors.request',
209
+ 'django.contrib.auth.context_processors.auth',
210
+ 'django.contrib.messages.context_processors.messages',
211
+ ],
212
+ },
213
+ },
214
+ ]
215
+
216
+ @staticmethod
217
+ def get_static_files_defaults() -> Dict[str, Any]:
218
+ """Get static files configuration defaults."""
219
+ return {
220
+ 'STATIC_URL': '/static/',
221
+ 'STATIC_ROOT': Path('staticfiles'),
222
+ 'STATICFILES_DIRS': [
223
+ Path('static'),
224
+ ],
225
+ 'MEDIA_URL': '/media/',
226
+ 'MEDIA_ROOT': Path('media'),
227
+ }
228
+
229
+ @staticmethod
230
+ def get_rest_framework_defaults() -> Dict[str, Any]:
231
+ """Get Django REST Framework defaults."""
232
+ return {
233
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
234
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
235
+ 'rest_framework.authentication.SessionAuthentication',
236
+ ],
237
+ 'DEFAULT_PERMISSION_CLASSES': [
238
+ 'rest_framework.permissions.IsAuthenticated',
239
+ ],
240
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
241
+ 'PAGE_SIZE': 20,
242
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
243
+ }
244
+
245
+ @staticmethod
246
+ def get_cors_defaults() -> Dict[str, Any]:
247
+ """Get CORS configuration defaults."""
248
+ return {
249
+ 'CORS_ALLOWED_ORIGINS': [
250
+ "http://localhost:3000",
251
+ "http://127.0.0.1:3000",
252
+ ],
253
+ 'CORS_ALLOW_CREDENTIALS': True,
254
+ 'CORS_ALLOW_ALL_ORIGINS': False,
255
+ }
256
+
257
+ @staticmethod
258
+ def configure_cache_backend(cache_config, environment: str, debug: bool):
259
+ """Configure cache backend - simplified."""
260
+ if cache_config is None:
261
+ return CacheConfig()
262
+ return cache_config
263
+
264
+ @staticmethod
265
+ def configure_email_backend(email_config, environment: str, debug: bool):
266
+ """Configure email backend - simplified."""
267
+ if email_config is None:
268
+ return EmailConfig()
269
+ return email_config
614
270
 
615
271
 
616
272
  # Export the main class
617
273
  __all__ = [
618
274
  "SmartDefaults",
275
+ "get_log_filename",
619
276
  ]