django-cfg 1.2.29__py3-none-any.whl → 1.3.1__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.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,604 +15,256 @@ from django_cfg.models.services import EmailConfig
16
15
  from django_cfg.core.exceptions import ConfigurationError
17
16
 
18
17
 
18
+ def get_log_filename() -> str:
19
+ """
20
+ Determine the correct log filename based on project type.
21
+
22
+ Returns:
23
+ - 'django-cfg.log' for django-cfg projects
24
+ - 'django.log' for regular Django projects
25
+ """
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'
33
+
34
+ # Default to regular Django log
35
+ return 'django.log'
36
+
37
+ except Exception:
38
+ # Fallback to django-cfg filename (since we're in django-cfg module)
39
+ return 'django-cfg.log'
40
+
41
+
19
42
  class SmartDefaults:
20
43
  """
21
44
  Environment-aware smart defaults for Django configuration.
22
45
 
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
46
+ Provides intelligent defaults based on environment detection
47
+ with comprehensive type safety and validation.
28
48
  """
29
49
 
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.
39
-
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
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
+ return {
54
+ 'ENGINE': 'django.db.backends.sqlite3',
55
+ 'NAME': Path('db') / 'db.sqlite3',
56
+ 'ATOMIC_REQUESTS': True,
57
+ 'CONN_MAX_AGE': 60,
58
+ 'OPTIONS': {
59
+ 'timeout': 20,
60
+ }
61
+ }
95
62
 
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.
105
-
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'})
63
+ @staticmethod
64
+ def get_cache_defaults() -> Dict[str, Any]:
65
+ """Get cache configuration defaults."""
66
+ return {
67
+ 'default': {
68
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
69
+ 'LOCATION': 'default-cache',
70
+ 'TIMEOUT': 300,
71
+ 'OPTIONS': {
72
+ 'MAX_ENTRIES': 1000,
162
73
  }
163
- ) from e
74
+ }
75
+ }
164
76
 
165
- @classmethod
77
+ @staticmethod
166
78
  def get_security_defaults(
79
+ security_domains=None,
80
+ environment: str = "development",
81
+ debug: bool = False,
82
+ ssl_redirect=None,
83
+ cors_allow_headers=None
84
+ ) -> Dict[str, Any]:
85
+ """Get security configuration defaults."""
86
+ return {
87
+ 'USE_TZ': True,
88
+ 'USE_I18N': True,
89
+ 'USE_L10N': True,
90
+ 'SECURE_BROWSER_XSS_FILTER': not debug,
91
+ 'SECURE_CONTENT_TYPE_NOSNIFF': not debug,
92
+ 'X_FRAME_OPTIONS': 'DENY' if not debug else 'SAMEORIGIN',
93
+ 'SECURE_HSTS_SECONDS': 31536000 if not debug else 0,
94
+ 'SECURE_HSTS_INCLUDE_SUBDOMAINS': not debug,
95
+ 'SECURE_HSTS_PRELOAD': not debug,
96
+ }
97
+
98
+ @classmethod
99
+ def get_logging_defaults(
167
100
  cls,
168
- domains: List[str],
169
101
  environment: Optional[str] = None,
170
- debug: bool = False,
171
- ssl_redirect: Optional[bool] = None,
172
- cors_allow_headers: Optional[List[str]] = None
102
+ debug: bool = False
173
103
  ) -> Dict[str, Any]:
174
104
  """
175
- Get security defaults based on environment and domains.
105
+ Simple logging configuration.
106
+
107
+ NOTE: Real logging setup is handled by django_cfg.modules.django_logger
108
+ This provides minimal fallback configuration only.
176
109
 
177
110
  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
111
+ environment: Environment name (ignored - for compatibility)
112
+ debug: Debug mode (ignored - for compatibility)
183
113
 
184
114
  Returns:
185
- Security settings dictionary
115
+ Minimal Django logging configuration
186
116
  """
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
219
-
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)
117
+ # Minimal fallback - actual logging is configured by django_logger module
118
+ return {
119
+ 'version': 1,
120
+ 'disable_existing_loggers': False,
121
+ 'handlers': {
122
+ 'console': {
123
+ 'class': 'logging.StreamHandler',
124
+ },
125
+ },
126
+ 'root': {
127
+ 'handlers': ['console'],
128
+ 'level': 'INFO',
129
+ },
231
130
  }
232
-
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
131
 
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}
253
-
254
- return {}
255
-
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',
270
- }
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",
132
+ @staticmethod
133
+ def get_middleware_defaults() -> List[str]:
134
+ """Get middleware configuration defaults."""
135
+ return [
136
+ 'corsheaders.middleware.CorsMiddleware',
137
+ 'django.middleware.security.SecurityMiddleware',
138
+ 'django.contrib.sessions.middleware.SessionMiddleware',
139
+ 'django.middleware.common.CommonMiddleware',
140
+ 'django.middleware.csrf.CsrfViewMiddleware',
141
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
142
+ 'django.contrib.messages.middleware.MessageMiddleware',
143
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
312
144
  ]
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
145
 
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}")
146
+ @staticmethod
147
+ def get_installed_apps_defaults() -> List[str]:
148
+ """Get default installed apps."""
149
+ return [
150
+ # Unfold admin
151
+ "unfold",
152
+ "unfold.contrib.filters",
153
+ "unfold.contrib.forms",
154
+ "unfold.contrib.inlines",
155
+ "import_export",
156
+ "unfold.contrib.import_export",
157
+ "unfold.contrib.guardian",
158
+ "unfold.contrib.simple_history",
159
+ "unfold.contrib.location_field",
160
+ "unfold.contrib.constance",
343
161
 
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
162
+ # Django core
163
+ "django.contrib.admin",
164
+ "django.contrib.auth",
165
+ "django.contrib.contenttypes",
166
+ "django.contrib.sessions",
167
+ "django.contrib.messages",
168
+ "django.contrib.staticfiles",
169
+ "django.contrib.humanize",
170
+
171
+ # Third-party
172
+ "corsheaders",
173
+ "rest_framework",
174
+ "rest_framework.authtoken",
175
+ "rest_framework_simplejwt",
176
+ "rest_framework_simplejwt.token_blacklist",
177
+ "rest_framework_nested",
178
+ "rangefilter",
179
+ "django_filters",
180
+ "drf_spectacular",
181
+ "drf_spectacular_sidecar",
182
+ "django_json_widget",
183
+ "django_extensions",
184
+ "constance",
185
+ "constance.backends.database",
186
+
187
+ # Django CFG
188
+ "django_cfg",
189
+ ]
349
190
 
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
191
+ @staticmethod
192
+ def get_templates_defaults() -> List[Dict[str, Any]]:
193
+ """Get templates configuration defaults."""
194
+ return [
195
+ {
196
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
197
+ 'DIRS': ['templates'],
198
+ 'APP_DIRS': True,
199
+ 'OPTIONS': {
200
+ 'context_processors': [
201
+ 'django.template.context_processors.debug',
202
+ 'django.template.context_processors.request',
203
+ 'django.contrib.auth.context_processors.auth',
204
+ 'django.contrib.messages.context_processors.messages',
205
+ ],
206
+ },
207
+ },
208
+ ]
377
209
 
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
210
+ @staticmethod
211
+ def get_static_files_defaults() -> Dict[str, Any]:
212
+ """Get static files configuration defaults."""
213
+ return {
214
+ 'STATIC_URL': '/static/',
215
+ 'STATIC_ROOT': Path('staticfiles'),
216
+ 'STATICFILES_DIRS': [
217
+ Path('static'),
218
+ ],
219
+ 'MEDIA_URL': '/media/',
220
+ 'MEDIA_ROOT': Path('media'),
221
+ }
392
222
 
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.')
223
+ @staticmethod
224
+ def get_rest_framework_defaults() -> Dict[str, Any]:
225
+ """Get Django REST Framework defaults."""
226
+ return {
227
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
228
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
229
+ 'rest_framework.authentication.SessionAuthentication',
230
+ ],
231
+ 'DEFAULT_PERMISSION_CLASSES': [
232
+ 'rest_framework.permissions.IsAuthenticated',
233
+ ],
234
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
235
+ 'PAGE_SIZE': 20,
236
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
237
+ }
412
238
 
413
- @classmethod
414
- def get_database_defaults(
415
- cls,
416
- environment: Optional[str] = None,
417
- debug: bool = False,
418
- engine: Optional[str] = None
419
- ) -> 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
239
+ @staticmethod
240
+ def get_cors_defaults() -> Dict[str, Any]:
241
+ """Get CORS configuration defaults."""
242
+ return {
243
+ 'CORS_ALLOWED_ORIGINS': [
244
+ "http://localhost:3000",
245
+ "http://127.0.0.1:3000",
246
+ ],
247
+ 'CORS_ALLOW_CREDENTIALS': True,
248
+ 'CORS_ALLOW_ALL_ORIGINS': False,
249
+ }
499
250
 
500
- @classmethod
501
- def get_logging_defaults(
502
- cls,
503
- environment: Optional[str] = None,
504
- debug: bool = False
505
- ) -> Dict[str, Any]:
506
- """
507
- Get logging defaults based on environment.
508
-
509
- Args:
510
- environment: Current environment
511
- debug: Django DEBUG setting
512
-
513
- Returns:
514
- Logging configuration dictionary
515
- """
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
- }
602
-
603
- # Fallback
604
- return {}
605
-
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
251
+ @staticmethod
252
+ def configure_cache_backend(cache_config, environment: str, debug: bool):
253
+ """Configure cache backend - simplified."""
254
+ if cache_config is None:
255
+ return CacheConfig()
256
+ return cache_config
257
+
258
+ @staticmethod
259
+ def configure_email_backend(email_config, environment: str, debug: bool):
260
+ """Configure email backend - simplified."""
261
+ if email_config is None:
262
+ return EmailConfig()
263
+ return email_config
614
264
 
615
265
 
616
266
  # Export the main class
617
267
  __all__ = [
618
268
  "SmartDefaults",
269
+ "get_log_filename",
619
270
  ]