django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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 (252) 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 +258 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +171 -461
  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 +105 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
  48. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  49. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
  50. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  51. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  52. django_cfg/apps/payments/middleware/api_access.py +32 -6
  53. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
  54. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
  55. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
  56. django_cfg/apps/payments/models/balance.py +12 -0
  57. django_cfg/apps/payments/models/currencies.py +106 -32
  58. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  59. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  60. django_cfg/apps/payments/services/core/payment_service.py +1 -1
  61. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  62. django_cfg/apps/payments/services/providers/base.py +95 -39
  63. django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
  64. django_cfg/apps/payments/services/providers/models/base.py +122 -0
  65. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  66. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  67. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  68. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  69. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  70. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  71. django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
  72. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  73. django_cfg/apps/payments/services/providers/registry.py +4 -32
  74. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  75. django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
  76. django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
  77. django_cfg/apps/payments/tasks/__init__.py +39 -0
  78. django_cfg/apps/payments/tasks/types.py +73 -0
  79. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  80. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  81. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  82. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  83. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  84. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  85. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  86. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  87. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  88. django_cfg/apps/payments/urls_admin.py +1 -1
  89. django_cfg/apps/payments/views/api/currencies.py +5 -5
  90. django_cfg/apps/payments/views/overview/services.py +2 -2
  91. django_cfg/apps/payments/views/serializers/currencies.py +4 -3
  92. django_cfg/apps/support/admin/__init__.py +10 -1
  93. django_cfg/apps/support/admin/support_admin.py +338 -141
  94. django_cfg/apps/tasks/admin/__init__.py +11 -0
  95. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  96. django_cfg/apps/urls.py +1 -2
  97. django_cfg/config.py +1 -1
  98. django_cfg/core/config.py +10 -5
  99. django_cfg/core/generation.py +1 -1
  100. django_cfg/management/commands/__init__.py +13 -1
  101. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  102. django_cfg/management/commands/app_agent_generate.py +342 -0
  103. django_cfg/management/commands/app_agent_info.py +308 -0
  104. django_cfg/management/commands/migrate_all.py +9 -3
  105. django_cfg/management/commands/migrator.py +11 -6
  106. django_cfg/management/commands/rundramatiq.py +3 -2
  107. django_cfg/middleware/__init__.py +0 -2
  108. django_cfg/models/api_keys.py +115 -0
  109. django_cfg/modules/django_admin/__init__.py +64 -0
  110. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  111. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  112. django_cfg/modules/django_admin/decorators/display.py +106 -0
  113. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  114. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  115. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  116. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  117. django_cfg/modules/django_admin/models/__init__.py +20 -0
  118. django_cfg/modules/django_admin/models/action_models.py +33 -0
  119. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  120. django_cfg/modules/django_admin/models/base.py +26 -0
  121. django_cfg/modules/django_admin/models/display_models.py +31 -0
  122. django_cfg/modules/django_admin/utils/badges.py +159 -0
  123. django_cfg/modules/django_admin/utils/displays.py +247 -0
  124. django_cfg/modules/django_app_agent/__init__.py +87 -0
  125. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  126. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  127. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  128. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  129. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  130. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  135. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  136. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  137. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  138. django_cfg/modules/django_app_agent/core/config.py +300 -0
  139. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  140. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  141. django_cfg/modules/django_app_agent/models/base.py +283 -0
  142. django_cfg/modules/django_app_agent/models/context.py +496 -0
  143. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  144. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  145. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  146. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  147. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  153. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  154. django_cfg/modules/django_app_agent/services/base.py +437 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  160. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  165. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  171. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  172. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  188. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  195. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  196. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  197. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  198. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  199. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  200. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  201. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  202. django_cfg/modules/django_currency/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  204. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  205. django_cfg/modules/django_currency/core/converter.py +12 -12
  206. django_cfg/modules/django_currency/database/__init__.py +2 -2
  207. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  208. django_cfg/modules/django_llm/llm/client.py +10 -2
  209. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  210. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  211. django_cfg/modules/django_unfold/dashboard.py +14 -13
  212. django_cfg/modules/django_unfold/models/config.py +1 -1
  213. django_cfg/registry/core.py +3 -0
  214. django_cfg/registry/third_party.py +2 -2
  215. django_cfg/template_archive/django_sample.zip +0 -0
  216. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  217. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
  218. django_cfg/apps/accounts/admin/activity.py +0 -96
  219. django_cfg/apps/accounts/admin/group.py +0 -17
  220. django_cfg/apps/accounts/admin/otp.py +0 -59
  221. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  222. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  223. django_cfg/apps/accounts/admin/user.py +0 -300
  224. django_cfg/apps/agents/core/agent.py +0 -281
  225. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  226. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  227. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  228. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  229. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  230. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  231. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  232. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  236. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  237. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  239. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  242. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  243. django_cfg/apps/tasks/admin.py +0 -320
  244. django_cfg/middleware/static_nocache.py +0 -55
  245. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  249. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  250. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  251. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  252. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,115 @@
1
+ """
2
+ OTP admin interface using Django Admin Utilities.
3
+
4
+ Enhanced OTP management with status indicators and time displays.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from unfold.admin import ModelAdmin
9
+
10
+ from django_cfg.modules.django_admin import (
11
+ OptimizedModelAdmin,
12
+ DisplayMixin,
13
+ DateTimeDisplayConfig,
14
+ StatusBadgeConfig,
15
+ Icons,
16
+ display
17
+ )
18
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
19
+
20
+ from ..models import OTPSecret
21
+ from .filters import OTPStatusFilter
22
+
23
+
24
+ @admin.register(OTPSecret)
25
+ class OTPSecretAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
26
+ """Enhanced OTP admin using Django Admin Utilities."""
27
+
28
+ list_display = [
29
+ 'email_display',
30
+ 'secret_display',
31
+ 'status_display',
32
+ 'created_display',
33
+ 'expires_display'
34
+ ]
35
+ list_display_links = ['email_display', 'secret_display']
36
+ list_filter = [OTPStatusFilter, 'is_used', 'created_at']
37
+ search_fields = ['email', 'secret']
38
+ readonly_fields = ['created_at', 'expires_at']
39
+ ordering = ['-created_at']
40
+
41
+ fieldsets = (
42
+ (
43
+ "OTP Details",
44
+ {
45
+ "fields": ("email", "secret", "is_used"),
46
+ },
47
+ ),
48
+ (
49
+ "Timestamps",
50
+ {
51
+ "fields": ("created_at", "expires_at"),
52
+ "classes": ("collapse",),
53
+ },
54
+ ),
55
+ )
56
+
57
+ @display(description="Email")
58
+ def email_display(self, obj):
59
+ """Email display with email icon."""
60
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.EMAIL)
61
+ return StatusBadge.create(
62
+ text=obj.email,
63
+ variant="info",
64
+ config=config
65
+ )
66
+
67
+ @display(description="Secret")
68
+ def secret_display(self, obj):
69
+ """Secret display with key icon."""
70
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
71
+ return StatusBadge.create(
72
+ text=obj.secret,
73
+ variant="secondary",
74
+ config=config
75
+ )
76
+
77
+ @display(description="Status", label=True)
78
+ def status_display(self, obj):
79
+ """Enhanced OTP status with appropriate icons and colors."""
80
+ if obj.is_used:
81
+ status = "Used"
82
+ icon = Icons.CHECK_CIRCLE
83
+ variant = "secondary"
84
+ elif obj.is_valid:
85
+ status = "Valid"
86
+ icon = Icons.VERIFIED
87
+ variant = "success"
88
+ else:
89
+ status = "Expired"
90
+ icon = Icons.SCHEDULE
91
+ variant = "warning"
92
+
93
+ config = StatusBadgeConfig(
94
+ show_icons=True,
95
+ icon=icon,
96
+ custom_mappings={status: variant}
97
+ )
98
+
99
+ return self.display_status_auto(
100
+ type('obj', (), {'status': status})(),
101
+ 'status',
102
+ config
103
+ )
104
+
105
+ @display(description="Created")
106
+ def created_display(self, obj):
107
+ """Created time with relative display."""
108
+ config = DateTimeDisplayConfig(show_relative=True)
109
+ return self.display_datetime_relative(obj, 'created_at', config)
110
+
111
+ @display(description="Expires")
112
+ def expires_display(self, obj):
113
+ """Expires time with relative display."""
114
+ config = DateTimeDisplayConfig(show_relative=True)
115
+ return self.display_datetime_relative(obj, 'expires_at', config)
@@ -0,0 +1,173 @@
1
+ """
2
+ Registration admin interfaces using Django Admin Utilities.
3
+
4
+ Enhanced registration source management with Material Icons and optimized queries.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from unfold.admin import ModelAdmin
9
+
10
+ from django_cfg.modules.django_admin import (
11
+ OptimizedModelAdmin,
12
+ DisplayMixin,
13
+ UserDisplayConfig,
14
+ DateTimeDisplayConfig,
15
+ StatusBadgeConfig,
16
+ Icons,
17
+ display
18
+ )
19
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
20
+
21
+ from ..models import RegistrationSource, UserRegistrationSource
22
+
23
+
24
+ @admin.register(RegistrationSource)
25
+ class RegistrationSourceAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
26
+ """Enhanced admin for RegistrationSource model using Django Admin Utilities."""
27
+
28
+ list_display = [
29
+ 'name_display',
30
+ 'description_display',
31
+ 'is_active_display',
32
+ 'users_count_display',
33
+ 'created_at_display'
34
+ ]
35
+ list_display_links = ['name_display']
36
+ list_filter = ['is_active', 'created_at']
37
+ search_fields = ['name', 'description']
38
+ readonly_fields = ['created_at', 'updated_at']
39
+ ordering = ['name']
40
+
41
+ fieldsets = (
42
+ ('Source Details', {
43
+ 'fields': ('name', 'description', 'is_active')
44
+ }),
45
+ ('Timestamps', {
46
+ 'fields': ('created_at', 'updated_at'),
47
+ 'classes': ('collapse',)
48
+ }),
49
+ )
50
+
51
+ @display(description="Name")
52
+ def name_display(self, obj):
53
+ """Name display with source icon."""
54
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.SOURCE)
55
+ return StatusBadge.create(
56
+ text=obj.name,
57
+ variant="primary",
58
+ config=config
59
+ )
60
+
61
+ @display(description="Description")
62
+ def description_display(self, obj):
63
+ """Description display with info icon."""
64
+ if not obj.description:
65
+ return "—"
66
+
67
+ # Truncate long descriptions
68
+ description = obj.description
69
+ if len(description) > 50:
70
+ description = f"{description[:47]}..."
71
+
72
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.INFO)
73
+ return StatusBadge.create(
74
+ text=description,
75
+ variant="info",
76
+ config=config
77
+ )
78
+
79
+ @display(description="Status", label=True)
80
+ def is_active_display(self, obj):
81
+ """Active status display."""
82
+ status = "Active" if obj.is_active else "Inactive"
83
+ icon = Icons.CHECK_CIRCLE if obj.is_active else Icons.CANCEL
84
+ variant = "success" if obj.is_active else "secondary"
85
+
86
+ config = StatusBadgeConfig(
87
+ show_icons=True,
88
+ icon=icon,
89
+ custom_mappings={status: variant}
90
+ )
91
+
92
+ return self.display_status_auto(
93
+ type('obj', (), {'status': status})(),
94
+ 'status',
95
+ config
96
+ )
97
+
98
+ @display(description="Users")
99
+ def users_count_display(self, obj):
100
+ """Count of users from this source."""
101
+ count = obj.user_registration_sources.count()
102
+ if count == 0:
103
+ return "—"
104
+
105
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.PEOPLE)
106
+ return StatusBadge.create(
107
+ text=f"{count} user{'s' if count != 1 else ''}",
108
+ variant="info",
109
+ config=config
110
+ )
111
+
112
+ @display(description="Created")
113
+ def created_at_display(self, obj):
114
+ """Created time with relative display."""
115
+ config = DateTimeDisplayConfig(show_relative=True)
116
+ return self.display_datetime_relative(obj, 'created_at', config)
117
+
118
+
119
+ @admin.register(UserRegistrationSource)
120
+ class UserRegistrationSourceAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
121
+ """Enhanced admin for UserRegistrationSource model using Django Admin Utilities."""
122
+
123
+ # Performance optimization
124
+ select_related_fields = ['user', 'source']
125
+
126
+ list_display = [
127
+ 'user_display',
128
+ 'source_display',
129
+ 'registration_date_display'
130
+ ]
131
+ list_display_links = ['user_display', 'source_display']
132
+ list_filter = ['source', 'registration_date']
133
+ search_fields = ['user__email', 'user__first_name', 'user__last_name', 'source__name']
134
+ readonly_fields = ['registration_date']
135
+ date_hierarchy = 'registration_date'
136
+ ordering = ['-registration_date']
137
+
138
+ fieldsets = (
139
+ ('Registration Details', {
140
+ 'fields': ('user', 'source', 'first_registration')
141
+ }),
142
+ ('Timestamp', {
143
+ 'fields': ('registration_date',)
144
+ }),
145
+ )
146
+
147
+ @display(description="User")
148
+ def user_display(self, obj):
149
+ """User display with avatar."""
150
+ config = UserDisplayConfig(
151
+ show_avatar=True,
152
+ avatar_size=20,
153
+ show_email=True
154
+ )
155
+ return self.display_user_simple(obj.user, config)
156
+
157
+ @display(description="Source")
158
+ def source_display(self, obj):
159
+ """Source display with source icon."""
160
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.SOURCE)
161
+ variant = "success" if obj.source.is_active else "secondary"
162
+
163
+ return StatusBadge.create(
164
+ text=obj.source.name,
165
+ variant=variant,
166
+ config=config
167
+ )
168
+
169
+ @display(description="Registered")
170
+ def registration_date_display(self, obj):
171
+ """Registration date with relative display."""
172
+ config = DateTimeDisplayConfig(show_relative=True)
173
+ return self.display_datetime_relative(obj, 'registration_date', config)
@@ -1,16 +1,20 @@
1
1
  """
2
2
  Import/Export resources for Accounts app.
3
+
4
+ Enhanced resources with better data validation and export optimization.
3
5
  """
4
6
 
5
7
  from import_export import resources, fields
6
8
  from import_export.widgets import ForeignKeyWidget, DateTimeWidget, BooleanWidget, ManyToManyWidget
7
9
  from django.contrib.auth.models import Group
10
+ from django.utils import timezone
11
+ from datetime import timedelta
8
12
 
9
13
  from ..models import CustomUser, UserActivity, RegistrationSource, TwilioResponse
10
14
 
11
15
 
12
16
  class CustomUserResource(resources.ModelResource):
13
- """Resource for importing/exporting users."""
17
+ """Enhanced resource for importing/exporting users."""
14
18
 
15
19
  # Custom fields for better export/import
16
20
  full_name = fields.Field(
@@ -54,6 +58,17 @@ class CustomUserResource(resources.ModelResource):
54
58
  attribute='phone_verified',
55
59
  widget=BooleanWidget()
56
60
  )
61
+
62
+ # Additional computed fields
63
+ registration_sources = fields.Field(
64
+ column_name='registration_sources',
65
+ readonly=True
66
+ )
67
+
68
+ activities_count = fields.Field(
69
+ column_name='activities_count',
70
+ readonly=True
71
+ )
57
72
 
58
73
  class Meta:
59
74
  model = CustomUser
@@ -71,6 +86,8 @@ class CustomUserResource(resources.ModelResource):
71
86
  'is_staff',
72
87
  'is_superuser',
73
88
  'groups',
89
+ 'registration_sources',
90
+ 'activities_count',
74
91
  'last_login',
75
92
  'date_joined',
76
93
  )
@@ -79,11 +96,28 @@ class CustomUserResource(resources.ModelResource):
79
96
  skip_unchanged = True
80
97
  report_skipped = True
81
98
 
99
+ def dehydrate_registration_sources(self, user):
100
+ """Get registration sources for export."""
101
+ sources = user.user_registration_sources.select_related('source').all()
102
+ return '|'.join([source.source.name for source in sources])
103
+
104
+ def dehydrate_activities_count(self, user):
105
+ """Get activities count for export."""
106
+ return user.activities.count()
107
+
82
108
  def before_import_row(self, row, **kwargs):
83
- """Process row before import."""
84
- # Ensure email is lowercase
109
+ """Process row before import with enhanced validation."""
110
+ # Ensure email is lowercase and valid
85
111
  if 'email' in row:
86
- row['email'] = row['email'].lower().strip()
112
+ email = row['email'].lower().strip()
113
+ if '@' not in email:
114
+ raise ValueError(f"Invalid email format: {email}")
115
+ row['email'] = email
116
+
117
+ # Clean phone number
118
+ if 'phone' in row and row['phone']:
119
+ phone = ''.join(filter(str.isdigit, str(row['phone'])))
120
+ row['phone'] = phone if phone else None
87
121
 
88
122
  def skip_row(self, instance, original, row, import_validation_errors=None):
89
123
  """Skip rows with validation errors."""
@@ -93,7 +127,7 @@ class CustomUserResource(resources.ModelResource):
93
127
 
94
128
 
95
129
  class UserActivityResource(resources.ModelResource):
96
- """Resource for exporting user activity (export only)."""
130
+ """Enhanced resource for exporting user activity (export only)."""
97
131
 
98
132
  user_email = fields.Field(
99
133
  column_name='user_email',
@@ -118,6 +152,12 @@ class UserActivityResource(resources.ModelResource):
118
152
  attribute='created_at',
119
153
  widget=DateTimeWidget(format='%Y-%m-%d %H:%M:%S')
120
154
  )
155
+
156
+ # Additional context fields
157
+ user_agent_browser = fields.Field(
158
+ column_name='user_agent_browser',
159
+ readonly=True
160
+ )
121
161
 
122
162
  class Meta:
123
163
  model = UserActivity
@@ -130,12 +170,30 @@ class UserActivityResource(resources.ModelResource):
130
170
  'description',
131
171
  'ip_address',
132
172
  'user_agent',
173
+ 'user_agent_browser',
133
174
  'object_id',
134
175
  'object_type',
135
176
  'created_at',
136
177
  )
137
178
  export_order = fields
138
179
  # No import - this is export only
180
+
181
+ def dehydrate_user_agent_browser(self, activity):
182
+ """Extract browser info from user agent."""
183
+ if not activity.user_agent:
184
+ return "Unknown"
185
+
186
+ user_agent = activity.user_agent.lower()
187
+ if 'chrome' in user_agent:
188
+ return "Chrome"
189
+ elif 'firefox' in user_agent:
190
+ return "Firefox"
191
+ elif 'safari' in user_agent:
192
+ return "Safari"
193
+ elif 'edge' in user_agent:
194
+ return "Edge"
195
+ else:
196
+ return "Other"
139
197
 
140
198
  def get_queryset(self):
141
199
  """Optimize queryset for export."""
@@ -143,7 +201,7 @@ class UserActivityResource(resources.ModelResource):
143
201
 
144
202
 
145
203
  class RegistrationSourceResource(resources.ModelResource):
146
- """Resource for importing/exporting registration sources."""
204
+ """Enhanced resource for importing/exporting registration sources."""
147
205
 
148
206
  is_active = fields.Field(
149
207
  column_name='is_active',
@@ -167,45 +225,56 @@ class RegistrationSourceResource(resources.ModelResource):
167
225
  column_name='users_count',
168
226
  readonly=True
169
227
  )
228
+
229
+ recent_registrations = fields.Field(
230
+ column_name='recent_registrations_7d',
231
+ readonly=True
232
+ )
170
233
 
171
234
  class Meta:
172
235
  model = RegistrationSource
173
236
  fields = (
174
237
  'id',
175
- 'url',
176
238
  'name',
177
239
  'description',
178
240
  'is_active',
179
241
  'users_count',
242
+ 'recent_registrations',
180
243
  'created_at',
181
244
  'updated_at',
182
245
  )
183
246
  export_order = fields
184
- import_id_fields = ('url',) # Use URL as unique identifier
247
+ import_id_fields = ('name',) # Use name as unique identifier
185
248
  skip_unchanged = True
186
249
  report_skipped = True
187
250
 
188
251
  def dehydrate_users_count(self, registration_source):
189
- """Calculate users count for export."""
252
+ """Calculate total users count for export."""
190
253
  return registration_source.user_registration_sources.count()
254
+
255
+ def dehydrate_recent_registrations(self, registration_source):
256
+ """Calculate recent registrations count."""
257
+
258
+ week_ago = timezone.now() - timedelta(days=7)
259
+ return registration_source.user_registration_sources.filter(
260
+ created_at__gte=week_ago
261
+ ).count()
191
262
 
192
263
  def before_import_row(self, row, **kwargs):
193
- """Process row before import."""
194
- # Ensure URL is properly formatted
195
- if 'url' in row and row['url']:
196
- url = row['url'].strip()
197
- if not url.startswith(('http://', 'https://')):
198
- row['url'] = f'https://{url}'
199
- else:
200
- row['url'] = url
264
+ """Process row before import with validation."""
265
+ # Clean and validate name
266
+ if 'name' in row and row['name']:
267
+ row['name'] = row['name'].strip()
268
+ if len(row['name']) < 2:
269
+ raise ValueError(f"Registration source name too short: {row['name']}")
201
270
 
202
271
 
203
272
  class TwilioResponseResource(resources.ModelResource):
204
- """Resource for exporting Twilio responses (export only)."""
273
+ """Enhanced resource for exporting Twilio responses (export only)."""
205
274
 
206
275
  otp_recipient = fields.Field(
207
276
  column_name='otp_recipient',
208
- attribute='otp_secret__recipient',
277
+ attribute='otp_secret__email',
209
278
  readonly=True
210
279
  )
211
280
 
@@ -240,6 +309,17 @@ class TwilioResponseResource(resources.ModelResource):
240
309
  widget=BooleanWidget(),
241
310
  readonly=True
242
311
  )
312
+
313
+ # Additional computed fields
314
+ response_time_ms = fields.Field(
315
+ column_name='response_time_ms',
316
+ readonly=True
317
+ )
318
+
319
+ masked_recipient = fields.Field(
320
+ column_name='masked_recipient',
321
+ readonly=True
322
+ )
243
323
 
244
324
  class Meta:
245
325
  model = TwilioResponse
@@ -251,12 +331,14 @@ class TwilioResponseResource(resources.ModelResource):
251
331
  'message_sid',
252
332
  'verification_sid',
253
333
  'to_number',
334
+ 'masked_recipient',
254
335
  'from_number',
255
336
  'otp_recipient',
256
337
  'error_code',
257
338
  'error_message',
258
339
  'price',
259
340
  'price_unit',
341
+ 'response_time_ms',
260
342
  'has_error',
261
343
  'is_successful',
262
344
  'created_at',
@@ -265,6 +347,28 @@ class TwilioResponseResource(resources.ModelResource):
265
347
  )
266
348
  export_order = fields
267
349
  # No import - this is export only
350
+
351
+ def dehydrate_response_time_ms(self, twilio_response):
352
+ """Calculate response time if available."""
353
+ if twilio_response.twilio_created_at and twilio_response.created_at:
354
+ delta = twilio_response.twilio_created_at - twilio_response.created_at
355
+ return int(delta.total_seconds() * 1000)
356
+ return None
357
+
358
+ def dehydrate_masked_recipient(self, twilio_response):
359
+ """Mask recipient for privacy."""
360
+ if not twilio_response.to_number:
361
+ return "—"
362
+
363
+ recipient = twilio_response.to_number
364
+ if '@' in recipient:
365
+ # Email masking
366
+ local, domain = recipient.split('@', 1)
367
+ masked_local = local[:2] + '*' * (len(local) - 2) if len(local) > 2 else local
368
+ return f"{masked_local}@{domain}"
369
+ else:
370
+ # Phone masking
371
+ return f"***{recipient[-4:]}" if len(recipient) > 4 else "***"
268
372
 
269
373
  def get_queryset(self):
270
374
  """Optimize queryset for export."""