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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,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)