django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) 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/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.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}")