django-cfg 1.1.81__py3-none-any.whl → 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. django_cfg/__init__.py +20 -448
  2. django_cfg/apps/accounts/README.md +3 -3
  3. django_cfg/apps/accounts/admin/__init__.py +0 -2
  4. django_cfg/apps/accounts/admin/activity.py +2 -9
  5. django_cfg/apps/accounts/admin/filters.py +0 -42
  6. django_cfg/apps/accounts/admin/inlines.py +8 -8
  7. django_cfg/apps/accounts/admin/otp.py +5 -5
  8. django_cfg/apps/accounts/admin/registration_source.py +1 -8
  9. django_cfg/apps/accounts/admin/user.py +12 -20
  10. django_cfg/apps/accounts/managers/user_manager.py +2 -129
  11. django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
  12. django_cfg/apps/accounts/models.py +3 -123
  13. django_cfg/apps/accounts/serializers/otp.py +40 -44
  14. django_cfg/apps/accounts/serializers/profile.py +0 -2
  15. django_cfg/apps/accounts/services/otp_service.py +98 -186
  16. django_cfg/apps/accounts/signals.py +25 -15
  17. django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
  18. django_cfg/apps/accounts/views/otp.py +35 -36
  19. django_cfg/apps/agents/README.md +129 -0
  20. django_cfg/apps/agents/__init__.py +68 -0
  21. django_cfg/apps/agents/admin/__init__.py +17 -0
  22. django_cfg/apps/agents/admin/execution_admin.py +460 -0
  23. django_cfg/apps/agents/admin/registry_admin.py +360 -0
  24. django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
  25. django_cfg/apps/agents/apps.py +29 -0
  26. django_cfg/apps/agents/core/__init__.py +20 -0
  27. django_cfg/apps/agents/core/agent.py +281 -0
  28. django_cfg/apps/agents/core/dependencies.py +154 -0
  29. django_cfg/apps/agents/core/exceptions.py +66 -0
  30. django_cfg/apps/agents/core/models.py +106 -0
  31. django_cfg/apps/agents/core/orchestrator.py +391 -0
  32. django_cfg/apps/agents/examples/__init__.py +3 -0
  33. django_cfg/apps/agents/examples/simple_example.py +161 -0
  34. django_cfg/apps/agents/integration/__init__.py +14 -0
  35. django_cfg/apps/agents/integration/middleware.py +80 -0
  36. django_cfg/apps/agents/integration/registry.py +345 -0
  37. django_cfg/apps/agents/integration/signals.py +50 -0
  38. django_cfg/apps/agents/management/__init__.py +3 -0
  39. django_cfg/apps/agents/management/commands/__init__.py +3 -0
  40. django_cfg/apps/agents/management/commands/create_agent.py +365 -0
  41. django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
  42. django_cfg/apps/agents/managers/__init__.py +23 -0
  43. django_cfg/apps/agents/managers/execution.py +236 -0
  44. django_cfg/apps/agents/managers/registry.py +254 -0
  45. django_cfg/apps/agents/managers/toolsets.py +496 -0
  46. django_cfg/apps/agents/migrations/0001_initial.py +286 -0
  47. django_cfg/apps/agents/migrations/__init__.py +5 -0
  48. django_cfg/apps/agents/models/__init__.py +15 -0
  49. django_cfg/apps/agents/models/execution.py +215 -0
  50. django_cfg/apps/agents/models/registry.py +220 -0
  51. django_cfg/apps/agents/models/toolsets.py +305 -0
  52. django_cfg/apps/agents/patterns/__init__.py +24 -0
  53. django_cfg/apps/agents/patterns/content_agents.py +234 -0
  54. django_cfg/apps/agents/toolsets/__init__.py +15 -0
  55. django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
  56. django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
  57. django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
  58. django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
  59. django_cfg/apps/agents/urls.py +46 -0
  60. django_cfg/apps/knowbase/README.md +150 -0
  61. django_cfg/apps/knowbase/__init__.py +27 -0
  62. django_cfg/apps/knowbase/admin/__init__.py +23 -0
  63. django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
  64. django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
  65. django_cfg/apps/knowbase/admin/document_admin.py +650 -0
  66. django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
  67. django_cfg/apps/knowbase/apps.py +81 -0
  68. django_cfg/apps/knowbase/config/README.md +176 -0
  69. django_cfg/apps/knowbase/config/__init__.py +51 -0
  70. django_cfg/apps/knowbase/config/constance_fields.py +186 -0
  71. django_cfg/apps/knowbase/config/constance_settings.py +200 -0
  72. django_cfg/apps/knowbase/config/settings.py +444 -0
  73. django_cfg/apps/knowbase/examples/__init__.py +3 -0
  74. django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
  75. django_cfg/apps/knowbase/management/__init__.py +0 -0
  76. django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
  77. django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
  78. django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
  79. django_cfg/apps/knowbase/managers/__init__.py +22 -0
  80. django_cfg/apps/knowbase/managers/archive.py +426 -0
  81. django_cfg/apps/knowbase/managers/base.py +32 -0
  82. django_cfg/apps/knowbase/managers/chat.py +141 -0
  83. django_cfg/apps/knowbase/managers/document.py +203 -0
  84. django_cfg/apps/knowbase/managers/external_data.py +471 -0
  85. django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
  86. django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
  87. django_cfg/apps/knowbase/migrations/__init__.py +5 -0
  88. django_cfg/apps/knowbase/mixins/__init__.py +15 -0
  89. django_cfg/apps/knowbase/mixins/config.py +108 -0
  90. django_cfg/apps/knowbase/mixins/creator.py +81 -0
  91. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
  92. django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
  93. django_cfg/apps/knowbase/mixins/service.py +362 -0
  94. django_cfg/apps/knowbase/models/__init__.py +41 -0
  95. django_cfg/apps/knowbase/models/archive.py +599 -0
  96. django_cfg/apps/knowbase/models/base.py +58 -0
  97. django_cfg/apps/knowbase/models/chat.py +157 -0
  98. django_cfg/apps/knowbase/models/document.py +267 -0
  99. django_cfg/apps/knowbase/models/external_data.py +376 -0
  100. django_cfg/apps/knowbase/serializers/__init__.py +68 -0
  101. django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
  102. django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
  103. django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
  104. django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
  105. django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
  106. django_cfg/apps/knowbase/services/__init__.py +40 -0
  107. django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
  108. django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
  109. django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
  110. django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
  111. django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
  112. django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
  113. django_cfg/apps/knowbase/services/base.py +53 -0
  114. django_cfg/apps/knowbase/services/chat_service.py +239 -0
  115. django_cfg/apps/knowbase/services/document_service.py +144 -0
  116. django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
  117. django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
  118. django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
  119. django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
  120. django_cfg/apps/knowbase/services/embedding/models.py +229 -0
  121. django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
  122. django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
  123. django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
  124. django_cfg/apps/knowbase/services/search_service.py +293 -0
  125. django_cfg/apps/knowbase/signals/__init__.py +21 -0
  126. django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
  127. django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
  128. django_cfg/apps/knowbase/signals/document_signals.py +143 -0
  129. django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
  130. django_cfg/apps/knowbase/tasks/__init__.py +39 -0
  131. django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
  132. django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
  133. django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
  134. django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
  135. django_cfg/apps/knowbase/urls.py +43 -0
  136. django_cfg/apps/knowbase/utils/__init__.py +12 -0
  137. django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
  138. django_cfg/apps/knowbase/utils/text_processing.py +375 -0
  139. django_cfg/apps/knowbase/utils/validation.py +99 -0
  140. django_cfg/apps/knowbase/views/__init__.py +28 -0
  141. django_cfg/apps/knowbase/views/archive_views.py +469 -0
  142. django_cfg/apps/knowbase/views/base.py +49 -0
  143. django_cfg/apps/knowbase/views/chat_views.py +181 -0
  144. django_cfg/apps/knowbase/views/document_views.py +183 -0
  145. django_cfg/apps/knowbase/views/public_views.py +129 -0
  146. django_cfg/apps/leads/admin.py +70 -0
  147. django_cfg/apps/newsletter/admin.py +234 -0
  148. django_cfg/apps/newsletter/admin_filters.py +124 -0
  149. django_cfg/apps/support/admin.py +196 -0
  150. django_cfg/apps/support/admin_filters.py +71 -0
  151. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  152. django_cfg/apps/urls.py +5 -4
  153. django_cfg/cli/README.md +1 -1
  154. django_cfg/cli/commands/create_project.py +2 -2
  155. django_cfg/cli/commands/info.py +1 -1
  156. django_cfg/config.py +44 -0
  157. django_cfg/core/config.py +29 -82
  158. django_cfg/core/environment.py +1 -1
  159. django_cfg/core/generation.py +19 -107
  160. django_cfg/{integration.py → core/integration.py} +18 -16
  161. django_cfg/core/validation.py +1 -1
  162. django_cfg/management/__init__.py +1 -1
  163. django_cfg/management/commands/__init__.py +1 -1
  164. django_cfg/management/commands/auto_generate.py +482 -0
  165. django_cfg/management/commands/migrator.py +19 -101
  166. django_cfg/management/commands/test_email.py +1 -1
  167. django_cfg/middleware/README.md +0 -158
  168. django_cfg/middleware/__init__.py +0 -2
  169. django_cfg/middleware/user_activity.py +3 -3
  170. django_cfg/models/api.py +145 -0
  171. django_cfg/models/base.py +287 -0
  172. django_cfg/models/cache.py +4 -4
  173. django_cfg/models/constance.py +25 -88
  174. django_cfg/models/database.py +9 -9
  175. django_cfg/models/drf.py +3 -36
  176. django_cfg/models/email.py +163 -0
  177. django_cfg/models/environment.py +276 -0
  178. django_cfg/models/limits.py +1 -1
  179. django_cfg/models/logging.py +366 -0
  180. django_cfg/models/revolution.py +41 -2
  181. django_cfg/models/security.py +125 -0
  182. django_cfg/models/services.py +1 -1
  183. django_cfg/modules/__init__.py +2 -56
  184. django_cfg/modules/base.py +78 -52
  185. django_cfg/modules/django_currency/service.py +2 -2
  186. django_cfg/modules/django_email.py +2 -2
  187. django_cfg/modules/django_health.py +267 -0
  188. django_cfg/modules/django_llm/llm/client.py +79 -17
  189. django_cfg/modules/django_llm/translator/translator.py +2 -2
  190. django_cfg/modules/django_logger.py +2 -2
  191. django_cfg/modules/django_ngrok.py +2 -2
  192. django_cfg/modules/django_tasks.py +68 -3
  193. django_cfg/modules/django_telegram.py +3 -3
  194. django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
  195. django_cfg/modules/django_twilio/service.py +2 -2
  196. django_cfg/modules/django_twilio/simple_service.py +2 -2
  197. django_cfg/modules/django_twilio/templates/guide.md +266 -0
  198. django_cfg/modules/django_twilio/twilio_service.py +2 -2
  199. django_cfg/modules/django_unfold/__init__.py +69 -0
  200. django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
  201. django_cfg/modules/django_unfold/dashboard.py +278 -0
  202. django_cfg/modules/django_unfold/icons/README.md +145 -0
  203. django_cfg/modules/django_unfold/icons/__init__.py +12 -0
  204. django_cfg/modules/django_unfold/icons/constants.py +2851 -0
  205. django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
  206. django_cfg/modules/django_unfold/models/__init__.py +42 -0
  207. django_cfg/modules/django_unfold/models/config.py +601 -0
  208. django_cfg/modules/django_unfold/models/dashboard.py +206 -0
  209. django_cfg/modules/django_unfold/models/dropdown.py +40 -0
  210. django_cfg/modules/django_unfold/models/navigation.py +73 -0
  211. django_cfg/modules/django_unfold/models/tabs.py +25 -0
  212. django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
  213. django_cfg/modules/django_unfold/utils.py +140 -0
  214. django_cfg/registry/__init__.py +23 -0
  215. django_cfg/registry/core.py +61 -0
  216. django_cfg/registry/exceptions.py +11 -0
  217. django_cfg/registry/modules.py +12 -0
  218. django_cfg/registry/services.py +26 -0
  219. django_cfg/registry/third_party.py +52 -0
  220. django_cfg/routing/__init__.py +19 -0
  221. django_cfg/routing/callbacks.py +198 -0
  222. django_cfg/routing/routers.py +48 -0
  223. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
  224. django_cfg/templatetags/__init__.py +0 -0
  225. django_cfg/templatetags/django_cfg.py +33 -0
  226. django_cfg/urls.py +33 -0
  227. django_cfg/utils/path_resolution.py +1 -1
  228. django_cfg/utils/smart_defaults.py +7 -61
  229. django_cfg/utils/toolkit.py +663 -0
  230. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
  231. django_cfg-1.2.0.dist-info/RECORD +441 -0
  232. django_cfg/apps/tasks/@docs/README.md +0 -195
  233. django_cfg/archive/django_sample.zip +0 -0
  234. django_cfg/models/unfold.py +0 -271
  235. django_cfg/modules/unfold/__init__.py +0 -29
  236. django_cfg/modules/unfold/dashboard.py +0 -318
  237. django_cfg/pyproject.toml +0 -370
  238. django_cfg/routers.py +0 -83
  239. django_cfg-1.1.81.dist-info/RECORD +0 -278
  240. /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
  241. /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
  242. /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
  243. /django_cfg/{version_check.py → utils/version_check.py} +0 -0
  244. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,21 +5,14 @@ Registration Source admin configuration.
5
5
  from django.contrib import admin
6
6
  from django.contrib.humanize.templatetags.humanize import naturaltime
7
7
  from unfold.admin import ModelAdmin
8
- from import_export.admin import ImportExportModelAdmin
9
- from unfold.contrib.import_export.forms import ImportForm, ExportForm
10
8
 
11
9
  from ..models import RegistrationSource, UserRegistrationSource
12
10
  from .filters import RegistrationSourceStatusFilter
13
11
  from .inlines import RegistrationSourceInline
14
- from .resources import RegistrationSourceResource
15
12
 
16
13
 
17
14
  @admin.register(RegistrationSource)
18
- class RegistrationSourceAdmin(ModelAdmin, ImportExportModelAdmin):
19
- # Import/Export configuration
20
- resource_class = RegistrationSourceResource
21
- import_form_class = ImportForm
22
- export_form_class = ExportForm
15
+ class RegistrationSourceAdmin(ModelAdmin):
23
16
  list_display = ["name", "url", "status", "users_count", "created"]
24
17
  list_display_links = ["name", "url"]
25
18
  list_filter = [RegistrationSourceStatusFilter, "is_active", "created_at"]
@@ -12,26 +12,18 @@ from unfold.admin import ModelAdmin
12
12
  from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
13
13
  from unfold.decorators import action
14
14
  from unfold.enums import ActionVariant
15
- from import_export.admin import ImportExportModelAdmin
16
- from unfold.contrib.import_export.forms import ImportForm, ExportForm
17
15
 
18
16
  from ..models import CustomUser
19
17
  from .filters import UserStatusFilter
20
18
  from .inlines import UserRegistrationSourceInline, UserActivityInline, UserEmailLogInline, UserSupportTicketsInline
21
- from .resources import CustomUserResource
22
19
 
23
20
 
24
21
  @admin.register(CustomUser)
25
- class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
22
+ class CustomUserAdmin(BaseUserAdmin, ModelAdmin):
26
23
  # Forms loaded from `unfold.forms`
27
24
  form = UserChangeForm
28
25
  add_form = UserCreationForm
29
26
  change_password_form = AdminPasswordChangeForm
30
-
31
- # Import/Export configuration
32
- resource_class = CustomUserResource
33
- import_form_class = ImportForm
34
- export_form_class = ExportForm
35
27
 
36
28
  list_display = [
37
29
  "avatar",
@@ -56,8 +48,8 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
56
48
 
57
49
  # Add email log inline if newsletter app is enabled
58
50
  try:
59
- from django_cfg.modules.base import BaseModule
60
- base_module = BaseModule()
51
+ from django_cfg.modules.base import BaseCfgModule
52
+ base_module = BaseCfgModule()
61
53
  if base_module.is_newsletter_enabled():
62
54
  inlines.append(UserEmailLogInline)
63
55
  if base_module.is_support_enabled():
@@ -80,7 +72,7 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
80
72
  (
81
73
  "Contact Information",
82
74
  {
83
- "fields": ("company", "phone", "phone_verified", "position"),
75
+ "fields": ("company", "phone", "position"),
84
76
  },
85
77
  ),
86
78
  (
@@ -159,8 +151,8 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
159
151
  def emails_count(self, obj):
160
152
  """Show count of emails sent to user (if newsletter app is enabled)."""
161
153
  try:
162
- from django_cfg.modules.base import BaseModule
163
- base_module = BaseModule()
154
+ from django_cfg.modules.base import BaseCfgModule
155
+ base_module = BaseCfgModule()
164
156
 
165
157
  if not base_module.is_newsletter_enabled():
166
158
  return "—"
@@ -178,8 +170,8 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
178
170
  def tickets_count(self, obj):
179
171
  """Show count of support tickets for user (if support app is enabled)."""
180
172
  try:
181
- from django_cfg.modules.base import BaseModule
182
- base_module = BaseModule()
173
+ from django_cfg.modules.base import BaseCfgModule
174
+ base_module = BaseCfgModule()
183
175
 
184
176
  if not base_module.is_support_enabled():
185
177
  return "—"
@@ -241,8 +233,8 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
241
233
  return redirect(request.META.get('HTTP_REFERER', '/admin/'))
242
234
 
243
235
  # Check if newsletter app is enabled
244
- from django_cfg.modules.base import BaseModule
245
- base_module = BaseModule()
236
+ from django_cfg.modules.base import BaseCfgModule
237
+ base_module = BaseCfgModule()
246
238
 
247
239
  if not base_module.is_newsletter_enabled():
248
240
  self.message_user(
@@ -279,8 +271,8 @@ class CustomUserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
279
271
  return redirect(request.META.get('HTTP_REFERER', '/admin/'))
280
272
 
281
273
  # Check if support app is enabled
282
- from django_cfg.modules.base import BaseModule
283
- base_module = BaseModule()
274
+ from django_cfg.modules.base import BaseCfgModule
275
+ base_module = BaseCfgModule()
284
276
 
285
277
  if not base_module.is_support_enabled():
286
278
  self.message_user(
@@ -83,8 +83,8 @@ class UserManager(UserManager):
83
83
  f"Attempting to get_or_create user with email: {email}, username: {username}"
84
84
  )
85
85
 
86
- # Create or get user using self (the manager)
87
- user, created = self.get_or_create(
86
+ # Create or get user using self.model instead of importing CustomUser
87
+ user, created = self.model.objects.get_or_create(
88
88
  email=email, defaults=defaults
89
89
  )
90
90
 
@@ -287,130 +287,3 @@ class UserManager(UserManager):
287
287
  elif user.last_name:
288
288
  return user.last_name
289
289
  return user.email.split("@")[0].capitalize()
290
-
291
- # === NOTIFICATION-AWARE USER OPERATIONS ===
292
-
293
- def create_user_with_notifications(self, email, password=None, send_notifications=True, **extra_fields):
294
- """
295
- Create user with optional notifications.
296
-
297
- Args:
298
- email: User's email address
299
- password: User's password
300
- send_notifications: Whether to send email/telegram notifications
301
- **extra_fields: Additional fields for user creation
302
-
303
- Returns:
304
- User: Created user instance
305
- """
306
- # Create user normally
307
- user = self.create_user(email, password, **extra_fields)
308
-
309
- # Send notifications if requested
310
- if send_notifications:
311
- from ..utils.notifications import AccountNotifications
312
- AccountNotifications.send_welcome_email(user)
313
-
314
- return user
315
-
316
- def register_user_with_notifications(self, email, username=None, source_url=None, send_notifications=True, **extra_fields):
317
- """
318
- Register user with optional notifications (extends existing register_user method).
319
-
320
- Args:
321
- email: User's email address
322
- username: Optional username
323
- source_url: Optional source URL for tracking
324
- send_notifications: Whether to send notifications
325
- **extra_fields: Additional fields for user creation
326
-
327
- Returns:
328
- tuple: (user, created) where created is True if user was created
329
- """
330
- # Use existing register_user method
331
- user, created = self.register_user(email, username, source_url, **extra_fields)
332
-
333
- # Send notifications only for new users if requested
334
- if created and send_notifications:
335
- from ..utils.notifications import AccountNotifications
336
- AccountNotifications.send_welcome_email(user)
337
-
338
- return user, created
339
-
340
- def update_user_profile(self, user, send_notifications=True, **update_fields):
341
- """
342
- Update user profile with optional notifications.
343
-
344
- Args:
345
- user: User instance to update
346
- send_notifications: Whether to send notifications for important changes
347
- **update_fields: Fields to update
348
-
349
- Returns:
350
- User: Updated user instance
351
- """
352
- if not update_fields:
353
- return user
354
-
355
- # Track important changes for notifications
356
- important_changes = []
357
- old_values = {}
358
-
359
- if send_notifications:
360
- # Store old values for comparison
361
- for field in ['email', 'username', 'first_name', 'last_name']:
362
- if field in update_fields:
363
- old_values[field] = getattr(user, field)
364
-
365
- # Update user fields
366
- for field, value in update_fields.items():
367
- setattr(user, field, value)
368
-
369
- user.save()
370
-
371
- # Check for important changes and send notifications
372
- if send_notifications and old_values:
373
- if 'email' in old_values and old_values['email'] != user.email:
374
- important_changes.append("email address")
375
-
376
- if 'username' in old_values and old_values['username'] != user.username:
377
- important_changes.append("username")
378
-
379
- if (('first_name' in old_values and old_values['first_name'] != user.first_name) or
380
- ('last_name' in old_values and old_values['last_name'] != user.last_name)):
381
- important_changes.append("name")
382
-
383
- # Send notifications if there were important changes
384
- if important_changes:
385
- from ..utils.notifications import AccountNotifications
386
- AccountNotifications.send_profile_update_notification(user, important_changes)
387
-
388
- return user
389
-
390
- def update_user_status(self, user, is_active=None, send_notifications=True, reason=None):
391
- """
392
- Update user active status with optional notifications.
393
-
394
- Args:
395
- user: User instance to update
396
- is_active: New active status (True/False)
397
- send_notifications: Whether to send notifications
398
- reason: Reason for status change
399
-
400
- Returns:
401
- User: Updated user instance
402
- """
403
- if is_active is None:
404
- return user
405
-
406
- old_status = user.is_active
407
- user.is_active = is_active
408
- user.save()
409
-
410
- # Send notifications if status changed
411
- if send_notifications and old_status != is_active:
412
- from ..utils.notifications import AccountNotifications
413
- status_type = "activated" if is_active else "deactivated"
414
- AccountNotifications.send_account_status_change(user, status_type, reason)
415
-
416
- return user
@@ -0,0 +1,46 @@
1
+ # Generated by Django 5.2.6 on 2025-09-20 16:09
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('django_cfg_accounts', '0005_twilioresponse'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='twilioresponse',
15
+ name='otp_secret',
16
+ ),
17
+ migrations.RemoveIndex(
18
+ model_name='otpsecret',
19
+ name='django_cfg__recipie_f9f5aa_idx',
20
+ ),
21
+ migrations.RemoveField(
22
+ model_name='customuser',
23
+ name='phone_verified',
24
+ ),
25
+ migrations.RemoveField(
26
+ model_name='otpsecret',
27
+ name='channel_type',
28
+ ),
29
+ migrations.RemoveField(
30
+ model_name='otpsecret',
31
+ name='recipient',
32
+ ),
33
+ migrations.AddField(
34
+ model_name='otpsecret',
35
+ name='email',
36
+ field=models.EmailField(db_index=True, default=1, max_length=254),
37
+ preserve_default=False,
38
+ ),
39
+ migrations.AddIndex(
40
+ model_name='otpsecret',
41
+ index=models.Index(fields=['email', 'is_used', 'expires_at'], name='django_cfg__email_f595c8_idx'),
42
+ ),
43
+ migrations.DeleteModel(
44
+ name='TwilioResponse',
45
+ ),
46
+ ]
@@ -71,7 +71,6 @@ class CustomUser(AbstractUser):
71
71
  last_name = models.CharField(max_length=50, blank=True)
72
72
  company = models.CharField(max_length=100, blank=True)
73
73
  phone = models.CharField(max_length=20, blank=True)
74
- phone_verified = models.BooleanField(default=False, help_text="Whether the phone number has been verified")
75
74
  position = models.CharField(max_length=100, blank=True)
76
75
  avatar = models.ImageField(upload_to=user_avatar_path, blank=True, null=True)
77
76
 
@@ -83,12 +82,6 @@ class CustomUser(AbstractUser):
83
82
 
84
83
  USERNAME_FIELD = "email"
85
84
  REQUIRED_FIELDS = ["username"]
86
-
87
- def get_identifier_for_otp(self, channel='email'):
88
- """Get identifier for OTP based on channel."""
89
- if channel == 'phone':
90
- return self.phone
91
- return self.email
92
85
 
93
86
  def __str__(self):
94
87
  return self.email
@@ -135,16 +128,8 @@ class CustomUser(AbstractUser):
135
128
 
136
129
  class OTPSecret(models.Model):
137
130
  """Stores One-Time Passwords for authentication."""
138
-
139
- CHANNEL_CHOICES = [
140
- ('email', 'Email'),
141
- ('phone', 'Phone'),
142
- ]
143
131
 
144
- # Unified fields
145
- channel_type = models.CharField(max_length=10, choices=CHANNEL_CHOICES, default='email')
146
- recipient = models.CharField(max_length=255, db_index=True, blank=True, help_text="Email address or phone number")
147
-
132
+ email = models.EmailField(db_index=True)
148
133
  secret = models.CharField(max_length=6)
149
134
  created_at = models.DateTimeField(auto_now_add=True)
150
135
  expires_at = models.DateTimeField()
@@ -153,34 +138,12 @@ class OTPSecret(models.Model):
153
138
  def save(self, *args, **kwargs):
154
139
  if not self.expires_at:
155
140
  self.expires_at = timezone.now() + timedelta(minutes=10)
156
-
157
-
158
141
  super().save(*args, **kwargs)
159
142
 
160
143
  @staticmethod
161
144
  def generate_otp(length=6):
162
145
  """Generate random numeric OTP."""
163
146
  return "".join(random.choices(string.digits, k=length))
164
-
165
- @classmethod
166
- def create_for_email(cls, email, **kwargs):
167
- """Create OTP for email channel."""
168
- return cls.objects.create(
169
- channel_type='email',
170
- recipient=email,
171
- secret=cls.generate_otp(),
172
- **kwargs
173
- )
174
-
175
- @classmethod
176
- def create_for_phone(cls, phone, **kwargs):
177
- """Create OTP for phone channel."""
178
- return cls.objects.create(
179
- channel_type='phone',
180
- recipient=phone,
181
- secret=cls.generate_otp(),
182
- **kwargs
183
- )
184
147
 
185
148
  @property
186
149
  def is_valid(self):
@@ -193,13 +156,13 @@ class OTPSecret(models.Model):
193
156
  self.save(update_fields=["is_used"])
194
157
 
195
158
  def __str__(self):
196
- return f"OTP for {self.recipient} ({self.channel_type})"
159
+ return f"OTP for {self.email}"
197
160
 
198
161
  class Meta:
199
162
  app_label = 'django_cfg_accounts'
200
163
  ordering = ["-created_at"]
201
164
  indexes = [
202
- models.Index(fields=["recipient", "channel_type", "is_used", "expires_at"]),
165
+ models.Index(fields=["email", "is_used", "expires_at"]),
203
166
  ]
204
167
 
205
168
 
@@ -237,86 +200,3 @@ class UserActivity(models.Model):
237
200
 
238
201
  def __str__(self):
239
202
  return f"{self.user.username} - {self.get_activity_type_display()}"
240
-
241
-
242
- class TwilioResponse(models.Model):
243
- """Model for storing Twilio API responses and webhook data."""
244
-
245
- class ResponseType(models.TextChoices):
246
- API_SEND = 'api_send', 'API Send Request'
247
- API_VERIFY = 'api_verify', 'API Verify Request'
248
- WEBHOOK_STATUS = 'webhook_status', 'Webhook Status Update'
249
- WEBHOOK_DELIVERY = 'webhook_delivery', 'Webhook Delivery Report'
250
-
251
- class ServiceType(models.TextChoices):
252
- WHATSAPP = 'whatsapp', 'WhatsApp'
253
- SMS = 'sms', 'SMS'
254
- VOICE = 'voice', 'Voice'
255
- EMAIL = 'email', 'Email'
256
- VERIFY = 'verify', 'Verify API'
257
-
258
- # Response metadata
259
- response_type = models.CharField(max_length=20, choices=ResponseType.choices)
260
- service_type = models.CharField(max_length=10, choices=ServiceType.choices)
261
-
262
- # Twilio identifiers
263
- message_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Message SID")
264
- verification_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Verification SID")
265
-
266
- # Request/Response data
267
- request_data = models.JSONField(default=dict, help_text="Original request parameters")
268
- response_data = models.JSONField(default=dict, help_text="Twilio API response")
269
-
270
- # Status and error information
271
- status = models.CharField(max_length=20, blank=True, help_text="Message/Verification status")
272
- error_code = models.CharField(max_length=10, blank=True, help_text="Twilio error code")
273
- error_message = models.TextField(blank=True, help_text="Error description")
274
-
275
- # Contact information
276
- to_number = models.CharField(max_length=20, blank=True, help_text="Recipient phone/email")
277
- from_number = models.CharField(max_length=20, blank=True, help_text="Sender phone/email")
278
-
279
- # Pricing information
280
- price = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True)
281
- price_unit = models.CharField(max_length=3, blank=True, help_text="Currency code")
282
-
283
- # Timestamps
284
- created_at = models.DateTimeField(auto_now_add=True)
285
- updated_at = models.DateTimeField(auto_now=True)
286
- twilio_created_at = models.DateTimeField(null=True, blank=True, help_text="Timestamp from Twilio")
287
-
288
- # Relations
289
- otp_secret = models.ForeignKey(
290
- OTPSecret,
291
- on_delete=models.SET_NULL,
292
- null=True,
293
- blank=True,
294
- related_name='twilio_responses',
295
- help_text="Related OTP if applicable"
296
- )
297
-
298
- class Meta:
299
- app_label = 'django_cfg_accounts'
300
- verbose_name = 'Twilio Response'
301
- verbose_name_plural = 'Twilio Responses'
302
- ordering = ['-created_at']
303
- indexes = [
304
- models.Index(fields=['message_sid']),
305
- models.Index(fields=['verification_sid']),
306
- models.Index(fields=['status', 'created_at']),
307
- models.Index(fields=['response_type', 'service_type']),
308
- ]
309
-
310
- def __str__(self):
311
- identifier = self.message_sid or self.verification_sid or f"#{self.id}"
312
- return f"{self.get_service_type_display()} {self.get_response_type_display()} - {identifier}"
313
-
314
- @property
315
- def has_error(self):
316
- """Check if response has error."""
317
- return bool(self.error_code or self.error_message)
318
-
319
- @property
320
- def is_successful(self):
321
- """Check if response is successful."""
322
- return not self.has_error and self.status in ['sent', 'delivered', 'approved']
@@ -8,12 +8,12 @@ class OTPSerializer(serializers.ModelSerializer):
8
8
 
9
9
  class Meta:
10
10
  model = OTPSecret
11
- fields = ["recipient", "channel_type", "secret"]
11
+ fields = ["email", "secret"]
12
12
  read_only_fields = ["secret"]
13
13
 
14
14
 
15
15
  class OTPRequestSerializer(serializers.Serializer):
16
- """Serializer for OTP request supporting both email and phone."""
16
+ """Serializer for OTP request."""
17
17
 
18
18
  identifier = serializers.CharField(
19
19
  help_text="Email address or phone number for OTP delivery"
@@ -21,38 +21,36 @@ class OTPRequestSerializer(serializers.Serializer):
21
21
  channel = serializers.ChoiceField(
22
22
  choices=[('email', 'Email'), ('phone', 'Phone')],
23
23
  required=False,
24
- help_text="Delivery channel - auto-detected if not specified"
24
+ help_text="Delivery channel: 'email' or 'phone'. Auto-detected if not provided."
25
25
  )
26
26
  source_url = serializers.URLField(
27
27
  required=False,
28
28
  allow_blank=True,
29
- help_text="Source URL for tracking registration (e.g., https://unrealos.com)",
29
+ help_text="Source URL for tracking registration (e.g., https://dashboard.unrealon.com)",
30
30
  )
31
31
 
32
32
  def validate_identifier(self, value):
33
33
  """Validate identifier format."""
34
34
  if not value:
35
- raise serializers.ValidationError("Identifier (email or phone) is required.")
35
+ raise serializers.ValidationError("Identifier is required.")
36
36
 
37
- # Auto-detect if it's email
37
+ value = value.strip()
38
+ if not value:
39
+ raise serializers.ValidationError("Identifier cannot be empty.")
40
+
41
+ # Auto-detect if it's email or phone
38
42
  if '@' in value:
43
+ # Basic email validation
44
+ if not value.count('@') == 1:
45
+ raise serializers.ValidationError("Invalid email format.")
39
46
  return value.lower()
40
- return value
41
-
42
- def validate(self, attrs):
43
- """Auto-detect channel if not specified."""
44
- identifier = attrs.get('identifier')
45
-
46
- # Auto-detect channel if not specified
47
- if not attrs.get('channel'):
48
- if '@' in identifier:
49
- attrs['channel'] = 'email'
50
- elif identifier.startswith('+') or identifier.replace(' ', '').replace('-', '').isdigit():
51
- attrs['channel'] = 'phone'
52
- else:
53
- attrs['channel'] = 'email' # Default
54
-
55
- return attrs
47
+ else:
48
+ # Assume it's a phone number - basic validation
49
+ # Remove common phone number characters for validation
50
+ clean_phone = ''.join(c for c in value if c.isdigit() or c in '+')
51
+ if len(clean_phone) < 10:
52
+ raise serializers.ValidationError("Phone number must be at least 10 digits.")
53
+ return value
56
54
 
57
55
  def validate_source_url(self, value):
58
56
  """Validate source URL format."""
@@ -62,7 +60,7 @@ class OTPRequestSerializer(serializers.Serializer):
62
60
 
63
61
 
64
62
  class OTPVerifySerializer(serializers.Serializer):
65
- """Serializer for OTP verification supporting both email and phone."""
63
+ """Serializer for OTP verification."""
66
64
 
67
65
  identifier = serializers.CharField(
68
66
  help_text="Email address or phone number used for OTP request"
@@ -71,44 +69,42 @@ class OTPVerifySerializer(serializers.Serializer):
71
69
  channel = serializers.ChoiceField(
72
70
  choices=[('email', 'Email'), ('phone', 'Phone')],
73
71
  required=False,
74
- help_text="Delivery channel - auto-detected if not specified"
72
+ help_text="Delivery channel: 'email' or 'phone'. Auto-detected if not provided."
75
73
  )
76
74
  source_url = serializers.URLField(
77
75
  required=False,
78
76
  allow_blank=True,
79
- help_text="Source URL for tracking login (e.g., https://unrealos.com)",
77
+ help_text="Source URL for tracking login (e.g., https://dashboard.unrealon.com)",
80
78
  )
81
79
 
82
80
  def validate_identifier(self, value):
83
81
  """Validate identifier format."""
84
82
  if not value:
85
- raise serializers.ValidationError("Identifier (email or phone) is required.")
83
+ raise serializers.ValidationError("Identifier is required.")
86
84
 
87
- # Auto-detect if it's email
85
+ value = value.strip()
86
+ if not value:
87
+ raise serializers.ValidationError("Identifier cannot be empty.")
88
+
89
+ # Auto-detect if it's email or phone
88
90
  if '@' in value:
91
+ # Basic email validation
92
+ if not value.count('@') == 1:
93
+ raise serializers.ValidationError("Invalid email format.")
89
94
  return value.lower()
90
- return value
95
+ else:
96
+ # Assume it's a phone number - basic validation
97
+ # Remove common phone number characters for validation
98
+ clean_phone = ''.join(c for c in value if c.isdigit() or c in '+')
99
+ if len(clean_phone) < 10:
100
+ raise serializers.ValidationError("Phone number must be at least 10 digits.")
101
+ return value
91
102
 
92
103
  def validate_otp(self, value):
93
104
  """Validate OTP format."""
94
105
  if not value.isdigit():
95
106
  raise serializers.ValidationError("OTP must contain only digits.")
96
107
  return value
97
-
98
- def validate(self, attrs):
99
- """Auto-detect channel if not specified."""
100
- identifier = attrs.get('identifier')
101
-
102
- # Auto-detect channel if not specified
103
- if not attrs.get('channel'):
104
- if '@' in identifier:
105
- attrs['channel'] = 'email'
106
- elif identifier.startswith('+') or identifier.replace(' ', '').replace('-', '').isdigit():
107
- attrs['channel'] = 'phone'
108
- else:
109
- attrs['channel'] = 'email' # Default
110
-
111
- return attrs
112
108
 
113
109
  def validate_source_url(self, value):
114
110
  """Validate source URL format."""
@@ -134,4 +130,4 @@ class OTPRequestResponseSerializer(serializers.Serializer):
134
130
  class OTPErrorResponseSerializer(serializers.Serializer):
135
131
  """Error response for OTP operations."""
136
132
 
137
- error = serializers.CharField(help_text="Error message")
133
+ error = serializers.CharField(help_text="Error message")
@@ -23,7 +23,6 @@ class UserSerializer(serializers.ModelSerializer):
23
23
  "display_username",
24
24
  "company",
25
25
  "phone",
26
- "phone_verified",
27
26
  "position",
28
27
  "avatar",
29
28
  "is_staff",
@@ -35,7 +34,6 @@ class UserSerializer(serializers.ModelSerializer):
35
34
  read_only_fields = [
36
35
  "id",
37
36
  "email",
38
- "phone_verified",
39
37
  "is_staff",
40
38
  "is_superuser",
41
39
  "date_joined",