django-cfg 1.2.29__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,196 +1,248 @@
1
1
  """
2
- 🔄 Universal Subscription Signals
2
+ Subscription Signals for the Universal Payment System v2.0.
3
3
 
4
- Automatic subscription management and lifecycle handling via Django signals.
4
+ Minimal signals focused on cache invalidation and notifications.
5
+ Business logic stays in SubscriptionManager.
5
6
  """
6
7
 
7
- from django.db.models.signals import post_save, pre_save, post_delete
8
+ from django.db.models.signals import post_save, post_delete, pre_save
8
9
  from django.dispatch import receiver
9
- from django.db import transaction
10
+ from django.core.cache import cache
10
11
  from django.utils import timezone
11
- from datetime import timedelta
12
- import logging
13
12
 
14
- from ..models import Subscription, EndpointGroup, UserBalance, Transaction
15
- from ..services.cache import SimpleCache
13
+ from ..models import Subscription
14
+ from django_cfg.modules.django_logger import get_logger
16
15
 
17
- logger = logging.getLogger(__name__)
16
+ logger = get_logger("subscription_signals")
18
17
 
19
18
 
20
19
  @receiver(pre_save, sender=Subscription)
21
- def store_original_subscription_status(sender, instance, **kwargs):
22
- """Store original subscription status for change detection."""
20
+ def store_original_subscription_data(sender, instance: Subscription, **kwargs):
21
+ """Store original subscription data for change detection."""
23
22
  if instance.pk:
24
23
  try:
25
- old_instance = Subscription.objects.get(pk=instance.pk)
26
- instance._original_status = old_instance.status
27
- instance._original_expires_at = old_instance.expires_at
24
+ original = Subscription.objects.get(pk=instance.pk)
25
+ instance._original_status = original.status
26
+ instance._original_tier = original.tier
27
+ instance._original_expires_at = original.expires_at
28
28
  except Subscription.DoesNotExist:
29
29
  instance._original_status = None
30
+ instance._original_tier = None
30
31
  instance._original_expires_at = None
32
+ else:
33
+ instance._original_status = None
34
+ instance._original_tier = None
35
+ instance._original_expires_at = None
31
36
 
32
37
 
33
38
  @receiver(post_save, sender=Subscription)
34
- def process_subscription_status_changes(sender, instance, created, **kwargs):
35
- """Process subscription status changes and handle lifecycle events."""
36
- if created:
37
- logger.info(
38
- f"New subscription created: {instance.endpoint_group.name} "
39
- f"for user {instance.user.email} (expires: {instance.expires_at})"
40
- )
41
- _clear_user_cache(instance.user.id)
42
- return
39
+ def handle_subscription_changes(sender, instance: Subscription, created: bool, **kwargs):
40
+ """
41
+ Handle subscription changes - only cache clearing and notifications.
43
42
 
44
- # Check if status changed
45
- if hasattr(instance, '_original_status'):
46
- old_status = instance._original_status
47
- new_status = instance.status
48
-
49
- if old_status != new_status:
50
- logger.info(
51
- f"Subscription status changed: {instance.endpoint_group.name} "
52
- f"for user {instance.user.email} - {old_status} → {new_status}"
53
- )
43
+ Business logic (API key management, etc.) stays in managers.
44
+ """
45
+ if created:
46
+ logger.info(f"New subscription created", extra={
47
+ 'subscription_id': str(instance.id),
48
+ 'user_id': instance.user.id,
49
+ 'tier': instance.tier,
50
+ 'status': instance.status
51
+ })
52
+
53
+ # Create default API key for new subscription (delegate to manager)
54
+ _create_default_api_key(instance)
55
+
56
+ else:
57
+ # Check for status changes
58
+ if hasattr(instance, '_original_status'):
59
+ old_status = instance._original_status
60
+ new_status = instance.status
54
61
 
55
- # Handle specific status changes
56
- if new_status == Subscription.SubscriptionStatus.ACTIVE:
57
- _handle_subscription_activation(instance)
58
- elif new_status == Subscription.SubscriptionStatus.CANCELLED:
59
- _handle_subscription_cancellation(instance)
60
- elif new_status == Subscription.SubscriptionStatus.EXPIRED:
61
- _handle_subscription_expiration(instance)
62
+ if old_status != new_status:
63
+ logger.info(f"Subscription status changed", extra={
64
+ 'subscription_id': str(instance.id),
65
+ 'user_id': instance.user.id,
66
+ 'old_status': old_status,
67
+ 'new_status': new_status
68
+ })
69
+
70
+ # Handle activation
71
+ if new_status == 'active' and old_status != 'active':
72
+ _handle_subscription_activated(instance)
73
+
74
+ # Handle suspension/cancellation
75
+ elif new_status in ['suspended', 'cancelled'] and old_status not in ['suspended', 'cancelled']:
76
+ _handle_subscription_deactivated(instance)
77
+
78
+ # Check for tier changes
79
+ if hasattr(instance, '_original_tier'):
80
+ old_tier = instance._original_tier
81
+ new_tier = instance.tier
82
+
83
+ if old_tier != new_tier:
84
+ logger.info(f"Subscription tier changed", extra={
85
+ 'subscription_id': str(instance.id),
86
+ 'user_id': instance.user.id,
87
+ 'old_tier': old_tier,
88
+ 'new_tier': new_tier
89
+ })
90
+
91
+ _handle_tier_change(instance, old_tier, new_tier)
92
+
93
+ # Check for expiration changes
94
+ if hasattr(instance, '_original_expires_at'):
95
+ old_expires = instance._original_expires_at
96
+ new_expires = instance.expires_at
62
97
 
63
- _clear_user_cache(instance.user.id)
98
+ if old_expires != new_expires:
99
+ logger.info(f"Subscription expiration changed", extra={
100
+ 'subscription_id': str(instance.id),
101
+ 'user_id': instance.user.id,
102
+ 'old_expires': old_expires.isoformat() if old_expires else None,
103
+ 'new_expires': new_expires.isoformat() if new_expires else None
104
+ })
105
+
106
+ # Clear subscription-related caches
107
+ _clear_subscription_caches(instance)
64
108
 
65
109
 
66
- @receiver(post_save, sender=Subscription)
67
- def handle_subscription_renewal(sender, instance, created, **kwargs):
68
- """Handle subscription renewal and billing."""
69
- if created or not hasattr(instance, '_original_expires_at'):
70
- return
71
-
72
- old_expires_at = instance._original_expires_at
73
- new_expires_at = instance.expires_at
110
+ @receiver(post_delete, sender=Subscription)
111
+ def handle_subscription_deletion(sender, instance: Subscription, **kwargs):
112
+ """Handle subscription deletion."""
113
+ logger.warning(f"Subscription deleted", extra={
114
+ 'subscription_id': str(instance.id),
115
+ 'user_id': instance.user.id,
116
+ 'tier': instance.tier,
117
+ 'status': instance.status,
118
+ 'deletion_timestamp': timezone.now().isoformat()
119
+ })
74
120
 
75
- # Check if subscription was renewed (expires_at extended)
76
- if old_expires_at and new_expires_at and new_expires_at > old_expires_at:
77
- logger.info(
78
- f"Subscription renewed: {instance.endpoint_group.name} "
79
- f"for user {instance.user.email} - extended to {new_expires_at}"
80
- )
81
- _clear_user_cache(instance.user.id)
121
+ # Clear caches
122
+ _clear_subscription_caches(instance)
82
123
 
83
124
 
84
- @receiver(post_delete, sender=Subscription)
85
- def log_subscription_deletion(sender, instance, **kwargs):
86
- """Log subscription deletions for audit purposes."""
87
- logger.warning(
88
- f"Subscription deleted: {instance.endpoint_group.name} "
89
- f"for user {instance.user.email} - Status was: {instance.status}"
90
- )
91
- _clear_user_cache(instance.user.id)
92
-
93
-
94
- @receiver(post_save, sender=EndpointGroup)
95
- def log_endpoint_group_changes(sender, instance, created, **kwargs):
96
- """Log endpoint group changes that may affect subscriptions."""
97
- if created:
98
- logger.info(f"New endpoint group created: {instance.name}")
99
- else:
100
- # Check if important fields changed
101
- if instance.tracker.has_changed('is_active'):
102
- logger.warning(
103
- f"Endpoint group activity changed: {instance.name} "
104
- f"- active: {instance.is_active}"
105
- )
106
- # Clear cache for all users with subscriptions to this group
107
- _clear_endpoint_group_cache(instance)
108
-
109
-
110
- def _handle_subscription_activation(subscription: Subscription):
111
- """Handle subscription activation logic."""
125
+ # Helper functions (notifications and delegations only)
126
+
127
+ def _create_default_api_key(subscription: Subscription):
128
+ """Create default API key for new subscription (delegate to manager)."""
112
129
  try:
113
- # Reset usage counters
114
- subscription.usage_current = 0
130
+ from ..models import APIKey
115
131
 
116
- # Set next billing date
117
- if not subscription.next_billing:
118
- subscription.next_billing = timezone.now() + timedelta(days=30) # Monthly by default
119
-
120
- subscription.save(update_fields=['usage_current', 'next_billing'])
132
+ # Use manager method which has all the business logic
133
+ api_key = APIKey.objects.create_api_key_for_user(
134
+ user=subscription.user,
135
+ name=f"Default API Key ({subscription.tier})",
136
+ expires_in_days=None # No expiration for default key
137
+ )
121
138
 
122
- logger.info(f"Subscription activated: {subscription.endpoint_group.name} for {subscription.user.email}")
139
+ logger.info(f"Created default API key for subscription", extra={
140
+ 'subscription_id': str(subscription.id),
141
+ 'user_id': subscription.user.id,
142
+ 'api_key_id': str(api_key.id)
143
+ })
123
144
 
124
145
  except Exception as e:
125
- logger.error(f"Error handling subscription activation: {e}")
146
+ logger.error(f"Failed to create default API key", extra={
147
+ 'subscription_id': str(subscription.id),
148
+ 'user_id': subscription.user.id,
149
+ 'error': str(e)
150
+ })
126
151
 
127
152
 
128
- def _handle_subscription_cancellation(subscription: Subscription):
129
- """Handle subscription cancellation logic."""
153
+ def _handle_subscription_activated(subscription: Subscription):
154
+ """Handle subscription activation (notification only)."""
130
155
  try:
131
- # Mark as cancelled
132
- subscription.cancelled_at = timezone.now()
133
- subscription.save(update_fields=['cancelled_at'])
156
+ logger.info(f"Subscription activated", extra={
157
+ 'subscription_id': str(subscription.id),
158
+ 'user_id': subscription.user.id,
159
+ 'tier': subscription.tier
160
+ })
134
161
 
135
- logger.info(f"Subscription cancelled: {subscription.endpoint_group.name} for {subscription.user.email}")
162
+ # Set activation notification in cache
163
+ cache.set(
164
+ f"subscription_activated:{subscription.user.id}",
165
+ {
166
+ 'subscription_id': str(subscription.id),
167
+ 'tier': subscription.tier,
168
+ 'timestamp': timezone.now().isoformat()
169
+ },
170
+ timeout=86400 # 24 hours
171
+ )
136
172
 
137
173
  except Exception as e:
138
- logger.error(f"Error handling subscription cancellation: {e}")
174
+ logger.error(f"Failed to handle subscription activation: {e}")
139
175
 
140
176
 
141
- def _handle_subscription_expiration(subscription: Subscription):
142
- """Handle subscription expiration logic."""
177
+ def _handle_subscription_deactivated(subscription: Subscription):
178
+ """Handle subscription deactivation (notification only)."""
143
179
  try:
144
- # Mark as expired
145
- subscription.expired_at = timezone.now()
146
- subscription.save(update_fields=['expired_at'])
180
+ logger.warning(f"Subscription deactivated", extra={
181
+ 'subscription_id': str(subscription.id),
182
+ 'user_id': subscription.user.id,
183
+ 'status': subscription.status,
184
+ 'tier': subscription.tier
185
+ })
147
186
 
148
- logger.info(f"Subscription expired: {subscription.endpoint_group.name} for {subscription.user.email}")
187
+ # Set deactivation notification in cache
188
+ cache.set(
189
+ f"subscription_deactivated:{subscription.user.id}",
190
+ {
191
+ 'subscription_id': str(subscription.id),
192
+ 'status': subscription.status,
193
+ 'tier': subscription.tier,
194
+ 'timestamp': timezone.now().isoformat()
195
+ },
196
+ timeout=86400 * 7 # 7 days
197
+ )
149
198
 
150
199
  except Exception as e:
151
- logger.error(f"Error handling subscription expiration: {e}")
200
+ logger.error(f"Failed to handle subscription deactivation: {e}")
152
201
 
153
202
 
154
- def _clear_user_cache(user_id: int):
155
- """Clear cache for specific user."""
203
+ def _handle_tier_change(subscription: Subscription, old_tier: str, new_tier: str):
204
+ """Handle subscription tier change (notification only)."""
156
205
  try:
157
- cache = SimpleCache("subscriptions")
158
- cache_keys = [
159
- f"access:{user_id}",
160
- f"subscriptions:{user_id}",
161
- f"user_summary:{user_id}",
162
- ]
206
+ logger.info(f"Subscription tier changed", extra={
207
+ 'subscription_id': str(subscription.id),
208
+ 'user_id': subscription.user.id,
209
+ 'old_tier': old_tier,
210
+ 'new_tier': new_tier
211
+ })
212
+
213
+ # Set tier change notification in cache
214
+ cache.set(
215
+ f"tier_changed:{subscription.user.id}",
216
+ {
217
+ 'subscription_id': str(subscription.id),
218
+ 'old_tier': old_tier,
219
+ 'new_tier': new_tier,
220
+ 'timestamp': timezone.now().isoformat()
221
+ },
222
+ timeout=86400 # 24 hours
223
+ )
163
224
 
164
- for key in cache_keys:
165
- cache.delete(key)
166
-
167
225
  except Exception as e:
168
- logger.warning(f"Failed to clear cache for user {user_id}: {e}")
226
+ logger.error(f"Failed to handle tier change: {e}")
169
227
 
170
228
 
171
- def _clear_endpoint_group_cache(endpoint_group: EndpointGroup):
172
- """Clear cache for all users with subscriptions to this endpoint group."""
229
+ def _clear_subscription_caches(subscription: Subscription):
230
+ """Clear subscription-related cache entries."""
173
231
  try:
174
- # Get all users with active subscriptions to this group
175
- user_ids = Subscription.objects.filter(
176
- endpoint_group=endpoint_group,
177
- status=Subscription.SubscriptionStatus.ACTIVE
178
- ).values_list('user_id', flat=True)
179
-
180
- for user_id in user_ids:
181
- _clear_user_cache(user_id)
182
-
232
+ cache_keys = [
233
+ f"user_subscription:{subscription.user.id}",
234
+ f"subscription_access:{subscription.user.id}",
235
+ f"subscription_stats:{subscription.user.id}",
236
+ f"tier_limits:{subscription.tier}",
237
+ ]
238
+
239
+ cache.delete_many(cache_keys)
240
+
241
+ logger.debug(f"Cleared subscription caches", extra={
242
+ 'subscription_id': str(subscription.id),
243
+ 'user_id': subscription.user.id,
244
+ 'cache_keys_cleared': len(cache_keys)
245
+ })
246
+
183
247
  except Exception as e:
184
- logger.warning(f"Failed to clear cache for endpoint group {endpoint_group.name}: {e}")
185
-
186
-
187
- @receiver(post_save, sender=Subscription)
188
- def update_usage_statistics(sender, instance, created, **kwargs):
189
- """Update usage statistics when subscription is modified."""
190
- if not created and hasattr(instance, '_original_status'):
191
- # Only update stats if usage-related fields might have changed
192
- if instance.usage_current != getattr(instance, '_original_usage_current', instance.usage_current):
193
- logger.debug(
194
- f"Usage updated for subscription {instance.endpoint_group.name}: "
195
- f"{instance.usage_current} requests"
196
- )
248
+ logger.warning(f"Failed to clear subscription caches: {e}")