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,146 +1,165 @@
1
1
  """
2
- Subscription models for the universal payments system.
2
+ Subscription models for the Universal Payment System v2.0.
3
+
4
+ Handles user subscriptions and API access control.
3
5
  """
4
6
 
5
7
  from django.db import models
6
8
  from django.contrib.auth import get_user_model
7
- from django.core.validators import MinValueValidator
9
+ from django.core.validators import MinValueValidator, MaxValueValidator
10
+ from django.core.exceptions import ValidationError
8
11
  from django.utils import timezone
9
12
  from datetime import timedelta
10
- from .base import UUIDTimestampedModel, TimestampedModel
13
+ from .base import UUIDTimestampedModel
11
14
 
12
15
  User = get_user_model()
13
16
 
14
17
 
15
- class EndpointGroup(TimestampedModel):
16
- """API endpoint groups for subscription management."""
18
+ class EndpointGroup(models.Model):
19
+ """
20
+ API endpoint group for subscription management.
21
+
22
+ Groups related API endpoints for subscription-based access control.
23
+ """
17
24
 
18
25
  name = models.CharField(
19
26
  max_length=100,
20
27
  unique=True,
21
- help_text="Endpoint group name"
28
+ help_text="Endpoint group name (e.g., 'Payment API', 'Balance API')"
22
29
  )
23
- display_name = models.CharField(
24
- max_length=200,
25
- help_text="Human-readable name"
30
+
31
+ code = models.CharField(
32
+ max_length=50,
33
+ unique=True,
34
+ help_text="Endpoint group code (e.g., 'payments', 'balance')"
26
35
  )
36
+
27
37
  description = models.TextField(
28
38
  blank=True,
29
- help_text="Group description"
39
+ help_text="Description of what this endpoint group provides"
30
40
  )
31
41
 
32
- # Pricing tiers
33
- basic_price = models.FloatField(
34
- default=0.0,
35
- validators=[MinValueValidator(0.0)],
36
- help_text="Basic tier monthly price"
37
- )
38
- premium_price = models.FloatField(
39
- default=0.0,
40
- validators=[MinValueValidator(0.0)],
41
- help_text="Premium tier monthly price"
42
- )
43
- enterprise_price = models.FloatField(
44
- default=0.0,
45
- validators=[MinValueValidator(0.0)],
46
- help_text="Enterprise tier monthly price"
47
- )
48
-
49
- # Usage limits per tier
50
- basic_limit = models.PositiveIntegerField(
51
- default=1000,
52
- help_text="Basic tier monthly usage limit"
53
- )
54
- premium_limit = models.PositiveIntegerField(
55
- default=10000,
56
- help_text="Premium tier monthly usage limit"
57
- )
58
- enterprise_limit = models.PositiveIntegerField(
59
- default=0, # 0 = unlimited
60
- help_text="Enterprise tier monthly usage limit (0 = unlimited)"
42
+ # Access control
43
+ is_enabled = models.BooleanField(
44
+ default=True,
45
+ help_text="Whether this endpoint group is available"
61
46
  )
62
47
 
63
- # Settings
64
- is_active = models.BooleanField(
48
+ requires_subscription = models.BooleanField(
65
49
  default=True,
66
- help_text="Is this endpoint group active"
50
+ help_text="Whether access requires an active subscription"
67
51
  )
68
- require_api_key = models.BooleanField(
69
- default=True,
70
- help_text="Require API key for access"
52
+
53
+ # Rate limiting defaults
54
+ default_rate_limit = models.PositiveIntegerField(
55
+ default=1000,
56
+ help_text="Default requests per hour for this endpoint group"
71
57
  )
72
58
 
73
- # Import and assign manager
74
- from ..managers import EndpointGroupManager
75
- objects = EndpointGroupManager()
59
+ # Timestamps
60
+ created_at = models.DateTimeField(auto_now_add=True)
61
+ updated_at = models.DateTimeField(auto_now=True)
76
62
 
77
63
  class Meta:
78
- db_table = 'endpoint_groups'
79
- verbose_name = "Endpoint Group"
80
- verbose_name_plural = "Endpoint Groups"
81
- indexes = [
82
- models.Index(fields=['name']),
83
- models.Index(fields=['is_active']),
84
- ]
64
+ db_table = 'payments_endpoint_groups'
65
+ verbose_name = 'Endpoint Group'
66
+ verbose_name_plural = 'Endpoint Groups'
85
67
  ordering = ['name']
86
68
 
87
69
  def __str__(self):
88
- return self.display_name
89
-
90
- def get_price_for_tier(self, tier: str) -> float:
91
- """Get price for specific tier."""
92
- tier_prices = {
93
- 'basic': self.basic_price,
94
- 'premium': self.premium_price,
95
- 'enterprise': self.enterprise_price,
96
- }
97
- return tier_prices.get(tier, 0.0)
98
-
99
- def get_limit_for_tier(self, tier: str) -> int:
100
- """Get usage limit for specific tier."""
101
- tier_limits = {
102
- 'basic': self.basic_limit,
103
- 'premium': self.premium_limit,
104
- 'enterprise': self.enterprise_limit,
105
- }
106
- return tier_limits.get(tier, 0)
70
+ return f"{self.name} ({self.code})"
71
+
72
+ def clean(self):
73
+ """Validate endpoint group data."""
74
+ if self.code:
75
+ self.code = self.code.lower().replace(' ', '_')
76
+
77
+
78
+ class SubscriptionQuerySet(models.QuerySet):
79
+ """Optimized queryset for subscription operations."""
80
+
81
+ def optimized(self):
82
+ """Prevent N+1 queries."""
83
+ return self.select_related('user').prefetch_related('endpoint_groups')
84
+
85
+ def active(self):
86
+ """Get active subscriptions."""
87
+ return self.filter(
88
+ status=Subscription.SubscriptionStatus.ACTIVE,
89
+ expires_at__gt=timezone.now()
90
+ )
91
+
92
+ def expired(self):
93
+ """Get expired subscriptions."""
94
+ return self.filter(expires_at__lte=timezone.now())
95
+
96
+ def by_tier(self, tier):
97
+ """Filter by subscription tier."""
98
+ return self.filter(tier=tier)
99
+
100
+ def by_user(self, user):
101
+ """Filter by user."""
102
+ return self.filter(user=user)
103
+
104
+
105
+ class SubscriptionManager(models.Manager):
106
+ """Manager for subscription operations."""
107
+
108
+ def get_queryset(self):
109
+ """Return optimized queryset by default."""
110
+ return SubscriptionQuerySet(self.model, using=self._db)
111
+
112
+ def optimized(self):
113
+ """Get optimized queryset."""
114
+ return self.get_queryset().optimized()
115
+
116
+ def active(self):
117
+ """Get active subscriptions."""
118
+ return self.get_queryset().active()
119
+
120
+ def expired(self):
121
+ """Get expired subscriptions."""
122
+ return self.get_queryset().expired()
123
+
124
+ def by_tier(self, tier):
125
+ """Get subscriptions by tier."""
126
+ return self.get_queryset().by_tier(tier)
107
127
 
108
128
 
109
129
  class Subscription(UUIDTimestampedModel):
110
- """User subscriptions to endpoint groups."""
130
+ """
131
+ User subscription model for API access control.
132
+
133
+ Manages user subscriptions with different tiers and access levels.
134
+ """
111
135
 
112
136
  class SubscriptionStatus(models.TextChoices):
113
137
  ACTIVE = "active", "Active"
114
138
  INACTIVE = "inactive", "Inactive"
115
- EXPIRED = "expired", "Expired"
116
- CANCELLED = "cancelled", "Cancelled"
117
139
  SUSPENDED = "suspended", "Suspended"
140
+ CANCELLED = "cancelled", "Cancelled"
141
+ EXPIRED = "expired", "Expired"
118
142
 
119
143
  class SubscriptionTier(models.TextChoices):
120
- BASIC = "basic", "Basic"
121
- PREMIUM = "premium", "Premium"
122
- ENTERPRISE = "enterprise", "Enterprise"
144
+ FREE = "free", "Free Tier"
145
+ BASIC = "basic", "Basic Tier"
146
+ PRO = "pro", "Pro Tier"
147
+ ENTERPRISE = "enterprise", "Enterprise Tier"
123
148
 
124
149
  user = models.ForeignKey(
125
150
  User,
126
151
  on_delete=models.CASCADE,
127
- related_name='subscriptions',
128
- help_text="Subscriber"
129
- )
130
- endpoint_group = models.ForeignKey(
131
- EndpointGroup,
132
- on_delete=models.CASCADE,
133
- related_name='subscriptions',
134
- help_text="Endpoint group"
152
+ related_name='payment_subscriptions',
153
+ help_text="User who owns this subscription"
135
154
  )
136
155
 
137
- # Subscription details
138
156
  tier = models.CharField(
139
157
  max_length=20,
140
158
  choices=SubscriptionTier.choices,
141
- default=SubscriptionTier.BASIC,
159
+ default=SubscriptionTier.FREE,
142
160
  help_text="Subscription tier"
143
161
  )
162
+
144
163
  status = models.CharField(
145
164
  max_length=20,
146
165
  choices=SubscriptionStatus.choices,
@@ -148,123 +167,184 @@ class Subscription(UUIDTimestampedModel):
148
167
  help_text="Subscription status"
149
168
  )
150
169
 
151
- # Pricing
152
- monthly_price = models.FloatField(
153
- validators=[MinValueValidator(0.0)],
154
- help_text="Monthly subscription price"
170
+ # Access control
171
+ endpoint_groups = models.ManyToManyField(
172
+ EndpointGroup,
173
+ related_name='subscriptions',
174
+ blank=True,
175
+ help_text="Endpoint groups accessible with this subscription"
155
176
  )
156
177
 
157
- # Usage tracking
158
- usage_limit = models.PositiveIntegerField(
178
+ # Rate limiting
179
+ requests_per_hour = models.PositiveIntegerField(
180
+ default=100,
181
+ validators=[MinValueValidator(1), MaxValueValidator(100000)],
182
+ help_text="API requests allowed per hour"
183
+ )
184
+
185
+ requests_per_day = models.PositiveIntegerField(
159
186
  default=1000,
160
- help_text="Monthly usage limit (0 = unlimited)"
187
+ validators=[MinValueValidator(1), MaxValueValidator(1000000)],
188
+ help_text="API requests allowed per day"
161
189
  )
162
- usage_current = models.PositiveIntegerField(
163
- default=0,
164
- help_text="Current month usage"
190
+
191
+ # Subscription period
192
+ starts_at = models.DateTimeField(
193
+ default=timezone.now,
194
+ help_text="When this subscription starts"
165
195
  )
166
196
 
167
- # Billing
168
- last_billed = models.DateTimeField(
169
- null=True,
170
- blank=True,
171
- help_text="Last billing date"
197
+ expires_at = models.DateTimeField(
198
+ help_text="When this subscription expires"
172
199
  )
173
- next_billing = models.DateTimeField(
174
- null=True,
175
- blank=True,
176
- help_text="Next billing date"
200
+
201
+ # Billing information
202
+ monthly_cost_usd = models.FloatField(
203
+ default=0.0,
204
+ validators=[MinValueValidator(0.0)],
205
+ help_text="Monthly cost in USD"
177
206
  )
178
207
 
179
- # Lifecycle
180
- expires_at = models.DateTimeField(
181
- null=True,
182
- blank=True,
183
- help_text="Subscription expiration"
208
+ # Usage tracking
209
+ total_requests = models.PositiveIntegerField(
210
+ default=0,
211
+ help_text="Total API requests made with this subscription"
184
212
  )
185
- cancelled_at = models.DateTimeField(
213
+
214
+ last_request_at = models.DateTimeField(
186
215
  null=True,
187
216
  blank=True,
188
- help_text="Cancellation date"
217
+ help_text="When the last API request was made"
189
218
  )
190
219
 
191
- # Metadata
192
- metadata = models.JSONField(
193
- default=dict,
194
- help_text="Additional subscription metadata"
220
+ # Auto-renewal
221
+ auto_renew = models.BooleanField(
222
+ default=False,
223
+ help_text="Whether to automatically renew this subscription"
195
224
  )
196
225
 
197
- # Import and assign manager
198
- from ..managers import SubscriptionManager
226
+ # Manager
227
+ from .managers.subscription_managers import SubscriptionManager
199
228
  objects = SubscriptionManager()
200
229
 
201
230
  class Meta:
202
- db_table = 'user_subscriptions'
203
- verbose_name = "Subscription"
204
- verbose_name_plural = "Subscriptions"
231
+ db_table = 'payments_subscriptions'
232
+ verbose_name = 'Subscription'
233
+ verbose_name_plural = 'Subscriptions'
234
+ ordering = ['-created_at']
205
235
  indexes = [
206
236
  models.Index(fields=['user', 'status']),
207
- models.Index(fields=['endpoint_group', 'status']),
208
237
  models.Index(fields=['status', 'expires_at']),
209
- models.Index(fields=['next_billing']),
210
- models.Index(fields=['created_at']),
238
+ models.Index(fields=['tier', 'status']),
239
+ ]
240
+ constraints = [
241
+ models.UniqueConstraint(
242
+ fields=['user'],
243
+ condition=models.Q(status='active'),
244
+ name='one_active_subscription_per_user'
245
+ ),
211
246
  ]
212
- unique_together = [['user', 'endpoint_group']] # One subscription per user per group
213
- ordering = ['-created_at']
214
247
 
215
248
  def __str__(self):
216
- return f"{self.user.email} - {self.endpoint_group.name} ({self.tier})"
249
+ return f"{self.user.username} - {self.tier} ({self.status})"
217
250
 
218
- def is_active(self) -> bool:
219
- """Check if subscription is currently active."""
220
- now = timezone.now()
251
+ def save(self, *args, **kwargs):
252
+ """Override save to set default expiration."""
253
+ if not self.expires_at:
254
+ # Default to 30 days from start
255
+ self.expires_at = self.starts_at + timedelta(days=30)
221
256
 
257
+ super().save(*args, **kwargs)
258
+
259
+ def clean(self):
260
+ """Validate subscription data."""
261
+ if self.expires_at and self.starts_at and self.expires_at <= self.starts_at:
262
+ raise ValidationError("Expiration date must be after start date")
263
+
264
+ if self.requests_per_day < self.requests_per_hour:
265
+ raise ValidationError("Daily limit cannot be less than hourly limit")
266
+
267
+ @property
268
+ def is_active(self) -> bool:
269
+ """Check if subscription is active and not expired."""
222
270
  return (
223
271
  self.status == self.SubscriptionStatus.ACTIVE and
224
- (self.expires_at is None or self.expires_at > now)
272
+ self.expires_at > timezone.now()
225
273
  )
226
274
 
227
- def is_usage_exceeded(self) -> bool:
228
- """Check if usage limit is exceeded."""
229
- return self.usage_limit > 0 and self.usage_current >= self.usage_limit
275
+ @property
276
+ def is_expired(self) -> bool:
277
+ """Check if subscription is expired."""
278
+ return timezone.now() > self.expires_at
230
279
 
231
- def get_usage_percentage(self) -> float:
232
- """Get usage as percentage (0-100)."""
233
- if self.usage_limit == 0:
234
- return 0.0 # Unlimited
235
-
236
- return min((self.usage_current / self.usage_limit) * 100, 100.0)
237
-
238
- def can_use_api(self) -> bool:
239
- """Check if user can use API (active and not exceeded)."""
240
- return self.is_active() and not self.is_usage_exceeded()
241
-
242
- def increment_usage(self, count: int = 1):
243
- """Increment usage counter."""
244
- self.usage_current += count
245
- self.save(update_fields=['usage_current'])
246
-
247
- def reset_usage(self):
248
- """Reset usage counter (for new billing period)."""
249
- self.usage_current = 0
250
- self.save(update_fields=['usage_current'])
251
-
252
- def cancel(self):
253
- """Cancel subscription."""
254
- self.status = self.SubscriptionStatus.CANCELLED
255
- self.cancelled_at = timezone.now()
256
- self.save(update_fields=['status', 'cancelled_at'])
257
-
258
- def extend_billing_period(self):
259
- """Extend billing period by one month."""
260
- if self.next_billing:
261
- self.next_billing += timedelta(days=30)
262
- else:
263
- self.next_billing = timezone.now() + timedelta(days=30)
264
-
265
- if self.expires_at:
266
- self.expires_at += timedelta(days=30)
267
- else:
268
- self.expires_at = timezone.now() + timedelta(days=30)
280
+ @property
281
+ def days_remaining(self) -> int:
282
+ """Get days remaining until expiration."""
283
+ if self.is_expired:
284
+ return 0
285
+ delta = self.expires_at - timezone.now()
286
+ return max(0, delta.days)
287
+
288
+ @property
289
+ def usage_percentage(self) -> float:
290
+ """Get usage percentage for current period."""
291
+ # This would need to be calculated based on actual usage tracking
292
+ # For now, return 0.0 as placeholder
293
+ return 0.0
294
+
295
+ @property
296
+ def tier_display(self) -> str:
297
+ """Get display name for tier."""
298
+ return self.get_tier_display()
299
+
300
+ @property
301
+ def status_color(self) -> str:
302
+ """Get color for status display."""
303
+ colors = {
304
+ self.SubscriptionStatus.ACTIVE: 'success',
305
+ self.SubscriptionStatus.INACTIVE: 'secondary',
306
+ self.SubscriptionStatus.SUSPENDED: 'warning',
307
+ self.SubscriptionStatus.CANCELLED: 'danger',
308
+ self.SubscriptionStatus.EXPIRED: 'danger',
309
+ }
310
+ return colors.get(self.status, 'secondary')
311
+
312
+ def activate(self):
313
+ """Activate subscription (delegates to manager)."""
314
+ return self.__class__.objects.activate_subscription(self)
315
+
316
+ def suspend(self, reason=None):
317
+ """Suspend subscription (delegates to manager)."""
318
+ return self.__class__.objects.suspend_subscription(self, reason)
319
+
320
+ def cancel(self, reason=None):
321
+ """Cancel subscription (delegates to manager)."""
322
+ return self.__class__.objects.cancel_subscription(self, reason)
323
+
324
+ def renew(self, duration_days: int = 30):
325
+ """Renew subscription (delegates to manager)."""
326
+ return self.__class__.objects.renew_subscription(self, duration_days)
327
+
328
+ def has_access_to_endpoint_group(self, endpoint_group_code: str) -> bool:
329
+ """Check if subscription has access to specific endpoint group."""
330
+ if not self.is_active:
331
+ return False
269
332
 
270
- self.save(update_fields=['next_billing', 'expires_at'])
333
+ return self.endpoint_groups.filter(
334
+ code=endpoint_group_code,
335
+ is_enabled=True
336
+ ).exists()
337
+
338
+ def increment_usage(self):
339
+ """Increment usage counter (delegates to manager)."""
340
+ return self.__class__.objects.increment_subscription_usage(self)
341
+
342
+ @classmethod
343
+ def get_active_for_user(cls, user: User) -> 'Subscription':
344
+ """Get active subscription for user (delegates to manager)."""
345
+ return cls.objects.get_active_for_user(user)
346
+
347
+ @classmethod
348
+ def create_free_subscription(cls, user: User) -> 'Subscription':
349
+ """Create free tier subscription for user (delegates to manager)."""
350
+ return cls.objects.create_free_subscription(user)