django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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 (246) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,115 @@
1
+ """
2
+ API Keys configuration models for django-cfg.
3
+
4
+ Simple model for OpenAI and OpenRouter API keys.
5
+ Following CRITICAL_REQUIREMENTS.md:
6
+ - No raw Dict/Any usage - everything through Pydantic models
7
+ - Proper type annotations for all fields
8
+ - No mutable default arguments
9
+ """
10
+
11
+ from typing import Optional
12
+ from pydantic import BaseModel, Field, field_validator, SecretStr
13
+
14
+
15
+ class ApiKeys(BaseModel):
16
+ """
17
+ API keys configuration for LLM services.
18
+
19
+ Simple model for storing OpenAI and OpenRouter API keys.
20
+
21
+ Example:
22
+ ```python
23
+ api_keys = ApiKeys(
24
+ openai="${OPENAI_API_KEY}",
25
+ openrouter="${OPENROUTER_API_KEY}"
26
+ )
27
+ ```
28
+ """
29
+
30
+ model_config = {
31
+ "validate_assignment": True,
32
+ "extra": "forbid",
33
+ "str_strip_whitespace": True,
34
+ "validate_default": True,
35
+ }
36
+
37
+ # === LLM Provider Keys ===
38
+ openai: Optional[SecretStr] = Field(
39
+ default=None,
40
+ description="OpenAI API key for GPT models and embeddings"
41
+ )
42
+
43
+ openrouter: Optional[SecretStr] = Field(
44
+ default=None,
45
+ description="OpenRouter API key for access to multiple LLM providers"
46
+ )
47
+
48
+ @field_validator("openai")
49
+ @classmethod
50
+ def validate_openai_key(cls, v: Optional[SecretStr]) -> Optional[SecretStr]:
51
+ """Validate OpenAI API key format."""
52
+ if v is None:
53
+ return v
54
+
55
+ key_str = v.get_secret_value()
56
+ if not key_str.startswith(("sk-", "sk-proj-")):
57
+ raise ValueError("OpenAI API key must start with 'sk-' or 'sk-proj-'")
58
+
59
+ if len(key_str) < 20:
60
+ raise ValueError("OpenAI API key appears to be too short")
61
+
62
+ return v
63
+
64
+ @field_validator("openrouter")
65
+ @classmethod
66
+ def validate_openrouter_key(cls, v: Optional[SecretStr]) -> Optional[SecretStr]:
67
+ """Validate OpenRouter API key format."""
68
+ if v is None:
69
+ return v
70
+
71
+ key_str = v.get_secret_value()
72
+ if not key_str.startswith(("sk-or-", "sk-proj-")):
73
+ raise ValueError("OpenRouter API key must start with 'sk-or-' or 'sk-proj-'")
74
+
75
+ if len(key_str) < 20:
76
+ raise ValueError("OpenRouter API key appears to be too short")
77
+
78
+ return v
79
+
80
+ def get_openai_key(self) -> Optional[str]:
81
+ """Get OpenAI API key as string."""
82
+ return self.openai.get_secret_value() if self.openai else None
83
+
84
+ def get_openrouter_key(self) -> Optional[str]:
85
+ """Get OpenRouter API key as string."""
86
+ return self.openrouter.get_secret_value() if self.openrouter else None
87
+
88
+ def has_openai(self) -> bool:
89
+ """Check if OpenAI key is configured."""
90
+ return self.openai is not None
91
+
92
+ def has_openrouter(self) -> bool:
93
+ """Check if OpenRouter key is configured."""
94
+ return self.openrouter is not None
95
+
96
+ def get_preferred_provider(self) -> Optional[str]:
97
+ """
98
+ Get preferred provider based on availability.
99
+
100
+ Priority: OpenRouter (default) > OpenAI
101
+
102
+ Returns:
103
+ "openrouter" or "openai" or None
104
+ """
105
+ if self.has_openrouter():
106
+ return "openrouter"
107
+ elif self.has_openai():
108
+ return "openai"
109
+ return None
110
+
111
+
112
+ # Export the main class
113
+ __all__ = [
114
+ "ApiKeys",
115
+ ]
@@ -181,17 +181,6 @@ class ConstanceConfig(BaseModel, BaseCfgAutoModule):
181
181
  except (ImportError, Exception):
182
182
  pass
183
183
 
184
- # Get fields from payments app (only if enabled)
185
- if config and config.payments and config.payments.enabled:
186
- try:
187
- from django_cfg.apps.payments.config import get_django_cfg_payments_constance_fields
188
- payments_fields = get_django_cfg_payments_constance_fields()
189
- app_fields.extend(payments_fields)
190
- print(f"✅ Added {len(payments_fields)} payments fields to Constance")
191
- except (ImportError, Exception) as e:
192
- print(f"❌ Failed to load payments constance fields: {e}")
193
- traceback.print_exc()
194
-
195
184
  # Cache the result
196
185
  self._app_fields_cache = app_fields
197
186
  return app_fields
@@ -6,14 +6,110 @@ Simple, clean configuration following django-cfg philosophy.
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
  from typing import List, Dict, Optional
9
+ from django_cfg.models.cfg import BaseCfgAutoModule
9
10
 
10
11
 
11
- class PaymentsConfig(BaseModel):
12
+ class BaseProviderConfig(BaseModel):
13
+ """Base configuration for payment providers."""
14
+
15
+ provider_name: str = Field(..., description="Provider name")
16
+ enabled: bool = Field(default=True, description="Whether provider is enabled")
17
+
18
+ def get_provider_config(self) -> Dict[str, any]:
19
+ """Get provider-specific configuration."""
20
+ return {
21
+ 'provider_name': self.provider_name,
22
+ 'enabled': self.enabled,
23
+ }
24
+
25
+
26
+ class NowPaymentsProviderConfig(BaseProviderConfig):
27
+ """NowPayments provider configuration."""
28
+
29
+ provider_name: str = Field(default="nowpayments", description="Provider name")
30
+ api_key: str = Field(default="", description="NowPayments API key")
31
+ ipn_secret: str = Field(default="", description="NowPayments IPN secret for webhook validation")
32
+ sandbox_mode: bool = Field(default=True, description="NowPayments sandbox mode")
33
+
34
+ def get_provider_config(self) -> Dict[str, any]:
35
+ """Get NowPayments-specific configuration."""
36
+ return {
37
+ 'provider_name': self.provider_name,
38
+ 'enabled': self.enabled and bool(self.api_key.strip()),
39
+ 'api_key': self.api_key,
40
+ 'ipn_secret': self.ipn_secret,
41
+ 'sandbox_mode': self.sandbox_mode,
42
+ }
43
+
44
+
45
+ # Future provider configs (commented out for now)
46
+ # class StripeProviderConfig(BaseProviderConfig):
47
+ # """Stripe provider configuration."""
48
+ #
49
+ # provider_name: str = Field(default="stripe", description="Provider name")
50
+ # api_key: str = Field(default="", description="Stripe API key")
51
+ # webhook_secret: str = Field(default="", description="Stripe webhook secret")
52
+ #
53
+ # def get_provider_config(self) -> Dict[str, any]:
54
+ # return {
55
+ # 'provider_name': self.provider_name,
56
+ # 'enabled': self.enabled and bool(self.api_key.strip()),
57
+ # 'api_key': self.api_key,
58
+ # 'webhook_secret': self.webhook_secret,
59
+ # }
60
+
61
+
62
+ class ProviderAPIKeysConfig(BaseModel):
63
+ """
64
+ API keys configuration for payment providers.
65
+
66
+ Stores list of provider configurations.
67
+ """
68
+
69
+ providers: List[BaseProviderConfig] = Field(
70
+ default_factory=list,
71
+ description="List of provider configurations"
72
+ )
73
+
74
+ def add_provider(self, provider_config: BaseProviderConfig):
75
+ """Add a provider configuration."""
76
+ # Remove existing provider with same name
77
+ self.providers = [p for p in self.providers if p.provider_name != provider_config.provider_name]
78
+ self.providers.append(provider_config)
79
+
80
+ def get_provider_config(self, provider: str) -> Dict[str, any]:
81
+ """Get provider-specific configuration."""
82
+ provider_lower = provider.lower()
83
+
84
+ for provider_config in self.providers:
85
+ if provider_config.provider_name.lower() == provider_lower:
86
+ return provider_config.get_provider_config()
87
+
88
+ return {'enabled': False, 'provider_name': provider}
89
+
90
+ def get_enabled_providers(self) -> List[str]:
91
+ """Get list of enabled providers."""
92
+ enabled = []
93
+ for provider_config in self.providers:
94
+ config = provider_config.get_provider_config()
95
+ if config.get('enabled', False):
96
+ enabled.append(provider_config.provider_name)
97
+ return enabled
98
+
99
+ def get_provider_by_name(self, provider_name: str) -> Optional[BaseProviderConfig]:
100
+ """Get provider configuration by name."""
101
+ for provider_config in self.providers:
102
+ if provider_config.provider_name.lower() == provider_name.lower():
103
+ return provider_config
104
+ return None
105
+
106
+
107
+ class PaymentsConfig(BaseModel, BaseCfgAutoModule):
12
108
  """
13
109
  Payments app configuration for django-cfg.
14
110
 
15
- Static configuration that doesn't change at runtime.
16
- Dynamic settings (API keys, secrets) are handled by Constance.
111
+ Includes both static configuration and API keys.
112
+ API keys are now managed through BaseCfgAutoModule instead of Constance.
17
113
  """
18
114
 
19
115
  # Core settings
@@ -22,6 +118,12 @@ class PaymentsConfig(BaseModel):
22
118
  description="Enable payments app"
23
119
  )
24
120
 
121
+ # API keys configuration
122
+ api_keys: ProviderAPIKeysConfig = Field(
123
+ default_factory=ProviderAPIKeysConfig,
124
+ description="API keys and secrets for payment providers"
125
+ )
126
+
25
127
  # Middleware settings
26
128
  middleware_enabled: bool = Field(
27
129
  default=True,
@@ -207,6 +309,29 @@ class PaymentsConfig(BaseModel):
207
309
 
208
310
  return items
209
311
 
312
+ # API Keys access methods
313
+ def get_provider_api_config(self, provider: str) -> Dict[str, any]:
314
+ """Get provider-specific API configuration."""
315
+ return self.api_keys.get_provider_config(provider)
316
+
317
+ def get_enabled_providers(self) -> List[str]:
318
+ """Get list of enabled providers (those with API keys configured)."""
319
+ return self.api_keys.get_enabled_providers()
320
+
321
+ def is_provider_enabled(self, provider: str) -> bool:
322
+ """Check if a specific provider is enabled (has API keys configured)."""
323
+ config = self.api_keys.get_provider_config(provider)
324
+ return config.get('enabled', False)
325
+
326
+ # BaseCfgAutoModule implementation
327
+ def get_smart_defaults(self):
328
+ """Get smart default configuration for this module."""
329
+ return PaymentsConfig()
330
+
331
+ def get_module_config(self):
332
+ """Get the final configuration for this module."""
333
+ return self
334
+
210
335
  @classmethod
211
336
  def get_current_config(cls) -> 'PaymentsConfig':
212
337
  """
@@ -220,3 +345,12 @@ class PaymentsConfig(BaseModel):
220
345
  return PaymentsConfigManager.get_payments_config_safe()
221
346
  except Exception:
222
347
  return cls()
348
+
349
+
350
+ # Export all payment configuration classes
351
+ __all__ = [
352
+ "BaseProviderConfig",
353
+ "NowPaymentsProviderConfig",
354
+ "ProviderAPIKeysConfig",
355
+ "PaymentsConfig",
356
+ ]
@@ -0,0 +1,64 @@
1
+ """
2
+ Django Admin Utilities - Universal HTML Builder System
3
+
4
+ Clean, type-safe admin utilities with no HTML duplication.
5
+ """
6
+
7
+ # Core utilities
8
+ from .utils.displays import UserDisplay, MoneyDisplay, StatusDisplay, DateTimeDisplay
9
+ from .utils.badges import StatusBadge, ProgressBadge, CounterBadge
10
+
11
+ # Icons
12
+ from .icons import Icons, IconCategories
13
+
14
+ # Admin mixins
15
+ from .mixins.display_mixin import DisplayMixin
16
+ from .mixins.optimization_mixin import OptimizedModelAdmin
17
+ from .mixins.standalone_actions_mixin import StandaloneActionsMixin, standalone_action
18
+
19
+ # Configuration models
20
+ from .models.display_models import UserDisplayConfig, MoneyDisplayConfig, DateTimeDisplayConfig
21
+ from .models.badge_models import BadgeConfig, BadgeVariant, StatusBadgeConfig
22
+ from .models.action_models import ActionVariant, ActionConfig
23
+
24
+ # Decorators
25
+ from .decorators import display, action
26
+
27
+ __version__ = "1.0.0"
28
+
29
+ __all__ = [
30
+ # Display utilities
31
+ "UserDisplay",
32
+ "MoneyDisplay",
33
+ "StatusDisplay",
34
+ "DateTimeDisplay",
35
+
36
+ # Badge utilities
37
+ "StatusBadge",
38
+ "ProgressBadge",
39
+ "CounterBadge",
40
+
41
+ # Icons
42
+ "Icons",
43
+ "IconCategories",
44
+
45
+ # Admin mixins
46
+ "OptimizedModelAdmin",
47
+ "DisplayMixin",
48
+ "StandaloneActionsMixin",
49
+ "standalone_action",
50
+
51
+ # Configuration models
52
+ "UserDisplayConfig",
53
+ "MoneyDisplayConfig",
54
+ "DateTimeDisplayConfig",
55
+ "BadgeConfig",
56
+ "BadgeVariant",
57
+ "StatusBadgeConfig",
58
+ "ActionVariant",
59
+ "ActionConfig",
60
+
61
+ # Decorators
62
+ "display",
63
+ "action",
64
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ Django Admin Decorators - Wrappers for Unfold decorators.
3
+
4
+ Provides consistent, type-safe decorators with our admin utilities integration.
5
+ """
6
+
7
+ from .display import display
8
+ from .actions import action
9
+
10
+ __all__ = [
11
+ 'display',
12
+ 'action',
13
+ ]
@@ -0,0 +1,106 @@
1
+ """
2
+ Action decorator wrapper for Unfold integration.
3
+
4
+ Provides type-safe action decorators with consistent styling.
5
+ """
6
+
7
+ from typing import Optional, Callable, Any
8
+ from functools import wraps
9
+ from unfold.decorators import action as unfold_action
10
+ from unfold.enums import ActionVariant as UnfoldActionVariant
11
+
12
+ from django_cfg.modules.django_logger import get_logger
13
+ from ..models.action_models import ActionVariant
14
+
15
+
16
+ logger = get_logger("django_admin.decorators.actions")
17
+
18
+ def action(
19
+ description: str,
20
+ variant: Optional[ActionVariant] = None,
21
+ icon: Optional[str] = None,
22
+ permissions: Optional[list] = None,
23
+ url_path: Optional[str] = None,
24
+ attrs: Optional[dict] = None
25
+ ) -> Callable:
26
+ """
27
+ Enhanced action decorator with Django Admin Utilities integration.
28
+
29
+ Args:
30
+ description: Action description shown in admin
31
+ variant: Action style variant (ActionVariant enum)
32
+ icon: Material icon name
33
+ permissions: Required permissions list
34
+ url_path: URL path for standalone action buttons (creates separate button)
35
+ attrs: Additional attributes for the action
36
+
37
+ Usage:
38
+ # Bulk action (works on selected items)
39
+ @action(description="Activate items", variant=ActionVariant.SUCCESS)
40
+ def activate_items(self, request, queryset):
41
+ updated = queryset.update(is_active=True)
42
+ self.message_user(request, f"Activated {updated} items.")
43
+
44
+ # Standalone action button (url_path creates separate button)
45
+ @action(
46
+ description="Update Rates",
47
+ variant=ActionVariant.SUCCESS,
48
+ url_path="update-rates",
49
+ icon="sync"
50
+ )
51
+ def update_rates(self, request):
52
+ # Standalone action logic (no queryset parameter)
53
+ pass
54
+ """
55
+ def decorator(func: Callable) -> Callable:
56
+ @wraps(func)
57
+ def wrapper(self, request: Any, *args, **kwargs) -> Any:
58
+ try:
59
+ # For url_path actions, there's no queryset parameter
60
+ if url_path:
61
+ return func(self, request, *args, **kwargs)
62
+ else:
63
+ # For bulk actions, pass queryset as second parameter
64
+ queryset = args[0] if args else kwargs.get('queryset')
65
+ return func(self, request, queryset, *args[1:], **kwargs)
66
+ except Exception as e:
67
+ # Log error and show user message
68
+ logger.error(f"Error in action {func.__name__}: {e}")
69
+
70
+ self.message_user(
71
+ request,
72
+ f"Error executing action: {str(e)}",
73
+ level='ERROR'
74
+ )
75
+
76
+ # Convert our ActionVariant to Unfold ActionVariant
77
+ action_variant = None
78
+ if variant:
79
+ # Direct mapping since values are the same
80
+ unfold_variant_mapping = {
81
+ ActionVariant.DEFAULT: UnfoldActionVariant.DEFAULT,
82
+ ActionVariant.PRIMARY: UnfoldActionVariant.PRIMARY,
83
+ ActionVariant.SUCCESS: UnfoldActionVariant.SUCCESS,
84
+ ActionVariant.INFO: UnfoldActionVariant.INFO,
85
+ ActionVariant.WARNING: UnfoldActionVariant.WARNING,
86
+ ActionVariant.DANGER: UnfoldActionVariant.DANGER,
87
+ }
88
+ action_variant = unfold_variant_mapping.get(variant, UnfoldActionVariant.DEFAULT)
89
+
90
+ # Build decorator kwargs
91
+ decorator_kwargs = {'description': description}
92
+ if action_variant:
93
+ decorator_kwargs['variant'] = action_variant
94
+ if icon:
95
+ decorator_kwargs['icon'] = icon
96
+ if permissions:
97
+ decorator_kwargs['permissions'] = permissions
98
+ if url_path:
99
+ decorator_kwargs['url_path'] = url_path
100
+ if attrs:
101
+ decorator_kwargs['attrs'] = attrs
102
+
103
+ # Apply Unfold decorator
104
+ return unfold_action(**decorator_kwargs)(wrapper)
105
+
106
+ return decorator
@@ -0,0 +1,106 @@
1
+ """
2
+ Display decorator wrapper for Unfold integration.
3
+
4
+ Provides type-safe display decorators with our admin utilities.
5
+ """
6
+
7
+ from typing import Optional, Callable, Any, Union
8
+ from functools import wraps
9
+ from django.utils.safestring import SafeString, mark_safe
10
+ from unfold.decorators import display as unfold_display
11
+ from django_cfg.modules.django_logger import get_logger
12
+
13
+ logger = get_logger('django_admin.decorators.display')
14
+
15
+ def display(
16
+ function: Optional[Callable] = None,
17
+ *,
18
+ boolean: Optional[bool] = None,
19
+ image: Optional[bool] = None,
20
+ ordering: Optional[str] = None,
21
+ description: Optional[str] = None,
22
+ empty_value: Optional[str] = None,
23
+ dropdown: Optional[bool] = None,
24
+ label: Union[bool, str, dict, None] = None,
25
+ header: Optional[bool] = None
26
+ ) -> Callable:
27
+ """
28
+ Enhanced display decorator with Django Admin Utilities integration.
29
+
30
+ This decorator wraps unfold.decorators.display with additional features:
31
+ - Automatic HTML safety detection and marking
32
+ - Empty value handling with customizable fallback
33
+ - Error handling with logging
34
+
35
+ Args:
36
+ function: Function to decorate (for direct usage)
37
+ boolean: Show as boolean icon (True/False icons)
38
+ image: Show as image thumbnail
39
+ ordering: Field name for sorting
40
+ description: Column header text
41
+ empty_value: Default value for empty/None fields
42
+ dropdown: Show as dropdown menu
43
+ label: Show as label badge (bool, str, or dict for styling)
44
+ header: Show as header with avatar
45
+
46
+ Usage:
47
+ @display(description="User", header=True)
48
+ def user_display(self, obj):
49
+ return self.display_user_with_avatar(obj, 'user')
50
+
51
+ @display(description="Status", label=True)
52
+ def status_display(self, obj):
53
+ return self.display_status_auto(obj, 'status')
54
+
55
+ @display(description="Has Embedding", boolean=True)
56
+ def has_embedding_display(self, obj):
57
+ return obj.has_embedding
58
+
59
+ @display(description="Avatar", image=True)
60
+ def avatar_display(self, obj):
61
+ return obj.avatar.url if obj.avatar else None
62
+ """
63
+ def decorator(func: Callable) -> Callable:
64
+ @wraps(func)
65
+ def wrapper(self, obj: Any) -> Any:
66
+ try:
67
+ result = func(self, obj)
68
+
69
+ # Handle empty values (use our default if unfold's empty_value is None)
70
+ if result is None or result == "":
71
+ return empty_value if empty_value is not None else "—"
72
+
73
+ # Auto-mark HTML as safe if it contains HTML tags
74
+ if isinstance(result, str) and ('<' in result and '>' in result):
75
+ return mark_safe(result)
76
+
77
+ # SafeString is already safe
78
+ if isinstance(result, SafeString):
79
+ return result
80
+
81
+ return result
82
+ except Exception as e:
83
+ # Log error and return safe fallback
84
+ logger.error(f"Error in display method {func.__name__}: {e}")
85
+ return empty_value if empty_value is not None else "—"
86
+
87
+ # Apply Unfold decorator with all parameters
88
+ return unfold_display(
89
+ function=None, # We handle the function ourselves
90
+ boolean=boolean,
91
+ image=image,
92
+ ordering=ordering,
93
+ description=description,
94
+ empty_value=empty_value,
95
+ dropdown=dropdown,
96
+ label=label,
97
+ header=header
98
+ )(wrapper)
99
+
100
+ # Support both @display and @display(...) usage
101
+ if function is not None:
102
+ # Direct usage: @display
103
+ return decorator(function)
104
+ else:
105
+ # Parametrized usage: @display(...)
106
+ return decorator
@@ -0,0 +1,14 @@
1
+ """
2
+ Admin mixins for easy integration.
3
+ """
4
+
5
+ from .display_mixin import DisplayMixin
6
+ from .optimization_mixin import OptimizedModelAdmin
7
+ from .standalone_actions_mixin import StandaloneActionsMixin, standalone_action
8
+
9
+ __all__ = [
10
+ "DisplayMixin",
11
+ "OptimizedModelAdmin",
12
+ "StandaloneActionsMixin",
13
+ "standalone_action",
14
+ ]
@@ -0,0 +1,81 @@
1
+ """
2
+ Display mixin for convenient wrapper methods.
3
+ """
4
+
5
+ from typing import Optional, Any
6
+ from django.utils.safestring import SafeString
7
+
8
+ from ..utils.displays import UserDisplay, MoneyDisplay, StatusDisplay, DateTimeDisplay
9
+ from ..utils.badges import StatusBadge, CounterBadge
10
+ from ..models.display_models import UserDisplayConfig, MoneyDisplayConfig, DateTimeDisplayConfig
11
+ from ..models.badge_models import StatusBadgeConfig
12
+
13
+
14
+ class DisplayMixin:
15
+ """Mixin for Django ModelAdmin classes with convenient display methods."""
16
+
17
+ def display_user_with_avatar(self, obj: Any, user_field: str = 'user',
18
+ config: Optional[UserDisplayConfig] = None) -> list:
19
+ """Display user with avatar for @display(header=True)."""
20
+ user = getattr(obj, user_field, None)
21
+ return UserDisplay.with_avatar(user, config)
22
+
23
+ def display_user_simple(self, obj: Any, user_field: str = 'user',
24
+ config: Optional[UserDisplayConfig] = None) -> SafeString:
25
+ """Simple user display."""
26
+ user = getattr(obj, user_field, None)
27
+ return UserDisplay.simple(user, config)
28
+
29
+ def display_money_amount(self, obj: Any, amount_field: str,
30
+ config: Optional[MoneyDisplayConfig] = None) -> SafeString:
31
+ """Display money amount."""
32
+ amount = getattr(obj, amount_field, None)
33
+ return MoneyDisplay.amount(amount, config)
34
+
35
+ def display_money_breakdown(self, obj: Any, main_field: str, breakdown_fields: dict,
36
+ config: Optional[MoneyDisplayConfig] = None) -> SafeString:
37
+ """Display money with breakdown."""
38
+ main_amount = getattr(obj, main_field, 0)
39
+
40
+ breakdown_items = []
41
+ for label, field_name in breakdown_fields.items():
42
+ amount = getattr(obj, field_name, 0)
43
+ breakdown_items.append({
44
+ 'label': label,
45
+ 'amount': amount,
46
+ 'color': 'warning' if amount > 0 else 'secondary'
47
+ })
48
+
49
+ return MoneyDisplay.with_breakdown(main_amount, breakdown_items, config)
50
+
51
+ def display_status_auto(self, obj: Any, status_field: str = 'status',
52
+ config: Optional[StatusBadgeConfig] = None) -> SafeString:
53
+ """Display status with auto color mapping."""
54
+ status = getattr(obj, status_field, '')
55
+ return StatusBadge.auto(status, config)
56
+
57
+ def display_datetime_relative(self, obj: Any, datetime_field: str,
58
+ config: Optional[DateTimeDisplayConfig] = None) -> SafeString:
59
+ """Display datetime with relative time."""
60
+ dt = getattr(obj, datetime_field, None)
61
+ return DateTimeDisplay.relative(dt, config)
62
+
63
+ def display_datetime_compact(self, obj: Any, datetime_field: str,
64
+ config: Optional[DateTimeDisplayConfig] = None) -> SafeString:
65
+ """Display datetime compact."""
66
+ dt = getattr(obj, datetime_field, None)
67
+ return DateTimeDisplay.compact(dt, config)
68
+
69
+ def display_count_simple(self, obj: Any, count_field: str, label: str = None) -> SafeString:
70
+ """Display count as badge."""
71
+ count = getattr(obj, count_field, 0)
72
+ return CounterBadge.simple(count, label)
73
+
74
+ def display_related_count(self, obj: Any, related_name: str, label: str = None) -> SafeString:
75
+ """Display count of related objects."""
76
+ try:
77
+ related_manager = getattr(obj, related_name)
78
+ count = related_manager.count()
79
+ return CounterBadge.simple(count, label)
80
+ except AttributeError:
81
+ return CounterBadge.simple(0, label)