django-cfg 1.2.31__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 (256) 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 -10
  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 +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  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 +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  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 +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  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 +172 -148
  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 -285
  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 +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  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 +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  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 +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  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 +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  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/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  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 -63
  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 -122
  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 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  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 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  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 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.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
- from django_cfg.modules.django_logger import get_logger
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
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
61
+
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
54
82
 
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)
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
115
-
116
- # Set next billing date
117
- if not subscription.next_billing:
118
- subscription.next_billing = timezone.now() + timedelta(days=30) # Monthly by default
130
+ from ..models import APIKey
119
131
 
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}")