django-cfg 1.4.9__py3-none-any.whl → 1.4.11__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 (193) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/middleware/api_access.py +6 -2
  8. django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
  9. django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
  10. django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
  11. django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
  12. django_cfg/apps/payments/services/core/balance_service.py +5 -5
  13. django_cfg/apps/payments/services/core/subscription_service.py +1 -2
  14. django_cfg/apps/payments/views/api/balances.py +8 -7
  15. django_cfg/apps/payments/views/api/base.py +10 -6
  16. django_cfg/apps/payments/views/api/currencies.py +53 -10
  17. django_cfg/apps/payments/views/api/payments.py +3 -1
  18. django_cfg/apps/payments/views/api/subscriptions.py +2 -5
  19. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  20. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  21. django_cfg/apps/payments/views/overview/views.py +2 -1
  22. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  23. django_cfg/apps/urls.py +106 -45
  24. django_cfg/core/base/config_model.py +2 -2
  25. django_cfg/core/constants.py +1 -1
  26. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  27. django_cfg/core/generation/integration_generators/api.py +82 -41
  28. django_cfg/core/integration/display/startup.py +30 -22
  29. django_cfg/core/integration/url_integration.py +15 -16
  30. django_cfg/dashboard/sections/documentation.py +391 -0
  31. django_cfg/management/commands/check_endpoints.py +11 -160
  32. django_cfg/management/commands/check_settings.py +13 -265
  33. django_cfg/management/commands/clear_constance.py +13 -201
  34. django_cfg/management/commands/create_token.py +13 -321
  35. django_cfg/management/commands/generate_clients.py +23 -0
  36. django_cfg/management/commands/list_urls.py +13 -306
  37. django_cfg/management/commands/migrate_all.py +13 -126
  38. django_cfg/management/commands/migrator.py +13 -396
  39. django_cfg/management/commands/rundramatiq.py +15 -247
  40. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  41. django_cfg/management/commands/runserver_ngrok.py +15 -160
  42. django_cfg/management/commands/script.py +12 -488
  43. django_cfg/management/commands/show_config.py +12 -215
  44. django_cfg/management/commands/show_urls.py +12 -342
  45. django_cfg/management/commands/superuser.py +15 -295
  46. django_cfg/management/commands/task_clear.py +14 -217
  47. django_cfg/management/commands/task_status.py +13 -248
  48. django_cfg/management/commands/test_email.py +15 -86
  49. django_cfg/management/commands/test_telegram.py +14 -61
  50. django_cfg/management/commands/test_twilio.py +15 -105
  51. django_cfg/management/commands/tree.py +13 -383
  52. django_cfg/management/commands/validate_openapi.py +10 -0
  53. django_cfg/middleware/README.md +1 -1
  54. django_cfg/middleware/user_activity.py +3 -3
  55. django_cfg/models/__init__.py +2 -2
  56. django_cfg/models/api/drf/spectacular.py +6 -6
  57. django_cfg/models/django/__init__.py +2 -2
  58. django_cfg/models/django/openapi.py +238 -0
  59. django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
  60. django_cfg/modules/django_admin/management/__init__.py +0 -0
  61. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  62. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  63. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  64. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  65. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  66. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  67. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  68. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  69. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  70. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  71. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  72. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  73. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  74. django_cfg/modules/django_client/__init__.py +20 -0
  75. django_cfg/modules/django_client/apps.py +35 -0
  76. django_cfg/modules/django_client/core/__init__.py +56 -0
  77. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  78. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  79. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  80. django_cfg/modules/django_client/core/cli/main.py +235 -0
  81. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  82. django_cfg/modules/django_client/core/config/config.py +188 -0
  83. django_cfg/modules/django_client/core/config/group.py +101 -0
  84. django_cfg/modules/django_client/core/config/service.py +209 -0
  85. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  86. django_cfg/modules/django_client/core/generator/base.py +767 -0
  87. django_cfg/modules/django_client/core/generator/python.py +751 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  94. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  95. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  96. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  97. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  98. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  99. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  100. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  102. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  103. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  104. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  105. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  112. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  113. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  114. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  115. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  116. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  117. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  118. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  119. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  120. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  121. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  122. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  123. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  124. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  125. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  126. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  127. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  128. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  129. django_cfg/modules/django_client/core/ir/context.py +387 -0
  130. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  131. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  132. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  133. django_cfg/modules/django_client/core/parser/base.py +648 -0
  134. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  135. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  136. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  137. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  138. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  139. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  140. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  141. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  142. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  143. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  144. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  145. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  146. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  147. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  148. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  149. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  150. django_cfg/modules/django_client/management/__init__.py +3 -0
  151. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  152. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  153. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  154. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  155. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  156. django_cfg/modules/django_client/urls.py +72 -0
  157. django_cfg/modules/django_email/management/__init__.py +0 -0
  158. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  159. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  160. django_cfg/modules/django_logging/django_logger.py +6 -6
  161. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  162. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  164. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  165. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  166. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  167. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  168. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  169. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  170. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  171. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  172. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  173. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  174. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  176. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  177. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  178. django_cfg/modules/django_unfold/dashboard.py +1 -1
  179. django_cfg/pyproject.toml +2 -6
  180. django_cfg/registry/third_party.py +5 -7
  181. django_cfg/routing/callbacks.py +1 -1
  182. django_cfg/static/admin/css/prose-unfold.css +666 -0
  183. django_cfg/templates/admin/index.html +8 -0
  184. django_cfg/templates/admin/index_new.html +13 -0
  185. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  186. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  187. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  188. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  189. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
  190. django_cfg/management/commands/generate.py +0 -107
  191. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  192. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  193. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ import asyncio
6
6
  from django.core.management.base import BaseCommand, CommandError
7
7
  from django.contrib.auth.models import User
8
8
 
9
- from django_cfg.modules.django_orchestrator.models.registry import AgentDefinition
9
+ from django_cfg.apps.agents.models.registry import AgentDefinition
10
10
 
11
11
 
12
12
  class Command(BaseCommand):
@@ -7,9 +7,9 @@ from django.core.management.base import BaseCommand
7
7
  from django.utils import timezone
8
8
  from datetime import timedelta
9
9
 
10
- from django_cfg.modules.django_orchestrator.integration.registry import get_registry
11
- from django_cfg.modules.django_orchestrator.models.execution import AgentExecution, WorkflowExecution
12
- from django_cfg.modules.django_orchestrator.models.registry import AgentDefinition
10
+ from django_cfg.apps.agents.integration.registry import get_registry
11
+ from django_cfg.apps.agents.models.execution import AgentExecution, WorkflowExecution
12
+ from django_cfg.apps.agents.models.registry import AgentDefinition
13
13
 
14
14
 
15
15
  class Command(BaseCommand):
@@ -12,8 +12,8 @@ User = get_user_model()
12
12
 
13
13
  class NewsletterSerializer(serializers.ModelSerializer):
14
14
  """Serializer for Newsletter model."""
15
-
16
- subscribers_count = serializers.ReadOnlyField()
15
+
16
+ subscribers_count = serializers.IntegerField(read_only=True)
17
17
 
18
18
  class Meta:
19
19
  model = Newsletter
@@ -102,7 +102,7 @@ class EmailLogSerializer(serializers.ModelSerializer):
102
102
 
103
103
  class BulkEmailSerializer(serializers.Serializer):
104
104
  """Simple serializer for bulk email."""
105
-
105
+
106
106
  recipients = serializers.ListField(
107
107
  child=serializers.EmailField(),
108
108
  min_length=1,
@@ -115,3 +115,40 @@ class BulkEmailSerializer(serializers.Serializer):
115
115
  button_text = serializers.CharField(max_length=100, required=False, allow_blank=True)
116
116
  button_url = serializers.URLField(required=False, allow_blank=True)
117
117
  secondary_text = serializers.CharField(required=False, allow_blank=True)
118
+
119
+
120
+ # Response serializers
121
+ class SuccessResponseSerializer(serializers.Serializer):
122
+ """Generic success response."""
123
+ success = serializers.BooleanField()
124
+ message = serializers.CharField()
125
+
126
+
127
+ class SubscribeResponseSerializer(serializers.Serializer):
128
+ """Response for subscription."""
129
+ success = serializers.BooleanField()
130
+ message = serializers.CharField()
131
+ subscription_id = serializers.IntegerField(required=False)
132
+
133
+
134
+ class ErrorResponseSerializer(serializers.Serializer):
135
+ """Generic error response."""
136
+ success = serializers.BooleanField(default=False)
137
+ message = serializers.CharField()
138
+
139
+
140
+ class BulkEmailResponseSerializer(serializers.Serializer):
141
+ """Response for bulk email sending."""
142
+ success = serializers.BooleanField()
143
+ sent_count = serializers.IntegerField()
144
+ failed_count = serializers.IntegerField()
145
+ total_recipients = serializers.IntegerField()
146
+ error = serializers.CharField(required=False, allow_blank=True)
147
+
148
+
149
+ class SendCampaignResponseSerializer(serializers.Serializer):
150
+ """Response for sending campaign."""
151
+ success = serializers.BooleanField()
152
+ message = serializers.CharField(required=False)
153
+ sent_count = serializers.IntegerField(required=False)
154
+ error = serializers.CharField(required=False)
@@ -8,7 +8,12 @@ from rest_framework.permissions import IsAuthenticated
8
8
  from drf_spectacular.utils import extend_schema
9
9
 
10
10
  from ..models import NewsletterCampaign
11
- from ..serializers import NewsletterCampaignSerializer, SendCampaignSerializer
11
+ from ..serializers import (
12
+ NewsletterCampaignSerializer,
13
+ SendCampaignSerializer,
14
+ SendCampaignResponseSerializer,
15
+ ErrorResponseSerializer,
16
+ )
12
17
 
13
18
 
14
19
  class NewsletterCampaignListView(generics.ListCreateAPIView):
@@ -67,7 +72,7 @@ class NewsletterCampaignDetailView(generics.RetrieveUpdateDestroyAPIView):
67
72
  @extend_schema(
68
73
  summary="Delete Campaign",
69
74
  description="Delete a newsletter campaign.",
70
- responses={204: "No Content"},
75
+ responses={204: None},
71
76
  tags=["Campaigns"]
72
77
  )
73
78
  def delete(self, request, *args, **kwargs):
@@ -84,7 +89,11 @@ class SendCampaignView(generics.CreateAPIView):
84
89
  summary="Send Newsletter Campaign",
85
90
  description="Send a newsletter campaign to all subscribers.",
86
91
  request=SendCampaignSerializer,
87
- responses={200: "Success", 400: "Bad Request", 404: "Campaign not found"},
92
+ responses={
93
+ 200: SendCampaignResponseSerializer,
94
+ 400: ErrorResponseSerializer,
95
+ 404: ErrorResponseSerializer,
96
+ },
88
97
  tags=["Campaigns"]
89
98
  )
90
99
  def post(self, request, *args, **kwargs):
@@ -9,7 +9,12 @@ from drf_spectacular.utils import extend_schema
9
9
 
10
10
  from ..models import EmailLog
11
11
  from ..services.email_service import NewsletterEmailService
12
- from ..serializers import TestEmailSerializer, BulkEmailSerializer, EmailLogSerializer
12
+ from ..serializers import (
13
+ TestEmailSerializer,
14
+ BulkEmailSerializer,
15
+ EmailLogSerializer,
16
+ BulkEmailResponseSerializer,
17
+ )
13
18
 
14
19
 
15
20
  class TestEmailView(generics.CreateAPIView):
@@ -22,7 +27,10 @@ class TestEmailView(generics.CreateAPIView):
22
27
  summary="Test Email Sending",
23
28
  description="Send a test email to verify mailer configuration.",
24
29
  request=TestEmailSerializer,
25
- responses={200: "Success", 400: "Bad Request"},
30
+ responses={
31
+ 200: BulkEmailResponseSerializer,
32
+ 400: BulkEmailResponseSerializer,
33
+ },
26
34
  tags=["Testing"]
27
35
  )
28
36
  def post(self, request, *args, **kwargs):
@@ -67,7 +75,10 @@ class BulkEmailView(generics.CreateAPIView):
67
75
  summary="Send Bulk Email",
68
76
  description="Send bulk emails to multiple recipients using base email template.",
69
77
  request=BulkEmailSerializer,
70
- responses={200: "Success", 400: "Bad Request"},
78
+ responses={
79
+ 200: BulkEmailResponseSerializer,
80
+ 400: BulkEmailResponseSerializer,
81
+ },
71
82
  tags=["Bulk Email"]
72
83
  )
73
84
  def post(self, request, *args, **kwargs):
@@ -12,6 +12,9 @@ from ..serializers import (
12
12
  NewsletterSubscriptionSerializer,
13
13
  SubscribeSerializer,
14
14
  UnsubscribeSerializer,
15
+ SubscribeResponseSerializer,
16
+ SuccessResponseSerializer,
17
+ ErrorResponseSerializer,
15
18
  )
16
19
 
17
20
 
@@ -25,7 +28,11 @@ class SubscribeView(generics.CreateAPIView):
25
28
  summary="Subscribe to Newsletter",
26
29
  description="Subscribe an email address to a newsletter.",
27
30
  request=SubscribeSerializer,
28
- responses={201: "Created", 400: "Bad Request"},
31
+ responses={
32
+ 201: SubscribeResponseSerializer,
33
+ 400: ErrorResponseSerializer,
34
+ 404: ErrorResponseSerializer,
35
+ },
29
36
  tags=["Subscriptions"]
30
37
  )
31
38
  def post(self, request, *args, **kwargs):
@@ -83,7 +90,10 @@ class UnsubscribeView(generics.UpdateAPIView):
83
90
  summary="Unsubscribe from Newsletter",
84
91
  description="Unsubscribe from a newsletter using subscription ID.",
85
92
  request=UnsubscribeSerializer,
86
- responses={200: "Success", 404: "Subscription not found"},
93
+ responses={
94
+ 200: SuccessResponseSerializer,
95
+ 404: ErrorResponseSerializer,
96
+ },
87
97
  tags=["Subscriptions"]
88
98
  )
89
99
  def post(self, request, *args, **kwargs):
@@ -96,12 +96,16 @@ class APIAccessMiddleware(MiddlewareMixin):
96
96
  def process_request(self, request: HttpRequest) -> Optional[JsonResponse]:
97
97
  """
98
98
  Process incoming request for API access control.
99
-
99
+
100
100
  Returns JsonResponse if access should be denied, None to continue.
101
101
  """
102
102
  if not self.enabled:
103
103
  return None
104
-
104
+
105
+ # Check if this is a django-cfg internal endpoint check (bypass API key validation)
106
+ if request.META.get('HTTP_X_DJANGO_CFG_INTERNAL_CHECK') == 'true':
107
+ return None
108
+
105
109
  # Check if this path is protected (whitelist approach)
106
110
  if not self._is_protected_path(request.path):
107
111
  return None
@@ -56,6 +56,7 @@ class RateLimitingMiddleware(MiddlewareMixin):
56
56
  '/admin/',
57
57
  '/static/',
58
58
  '/media/',
59
+ '/schema/', # Exempt schema generation endpoints (Spectacular)
59
60
  ]
60
61
 
61
62
  except Exception as e:
@@ -74,7 +75,7 @@ class RateLimitingMiddleware(MiddlewareMixin):
74
75
  self.burst_allowance = 0.5
75
76
  self.window_size = 60
76
77
  self.window_precision = 10
77
- self.exempt_paths = ['/api/health/', '/cfg/', '/admin/']
78
+ self.exempt_paths = ['/api/health/', '/cfg/', '/admin/', '/schema/']
78
79
  self.cache_timeout = 300
79
80
 
80
81
  logger.info(f"Rate Limiting Middleware initialized", extra={
@@ -70,7 +70,11 @@ class UsageTrackingMiddleware(MiddlewareMixin):
70
70
  """
71
71
  if not self.enabled:
72
72
  return None
73
-
73
+
74
+ # Check if this is a django-cfg internal endpoint check (skip tracking)
75
+ if request.META.get('HTTP_X_DJANGO_CFG_INTERNAL_CHECK') == 'true':
76
+ return None
77
+
74
78
  # Check if we should track this request
75
79
  if not self._should_track_request(request):
76
80
  return None
@@ -319,7 +319,6 @@ class APIKeyManager(models.Manager):
319
319
  # Usage statistics
320
320
  usage_stats = queryset.aggregate(
321
321
  total_requests=models.Sum('total_requests'),
322
- avg_requests=models.Avg('total_requests'),
323
322
  max_requests=models.Max('total_requests')
324
323
  )
325
324
  stats.update(usage_stats)
@@ -196,7 +196,6 @@ class SubscriptionQuerySet(models.QuerySet):
196
196
  """Get usage statistics."""
197
197
  return self.aggregate(
198
198
  total_requests=models.Sum('total_requests'),
199
- avg_requests=models.Avg('total_requests'),
200
199
  max_requests=models.Max('total_requests'),
201
200
  active_users=models.Count('user', distinct=True)
202
201
  )
@@ -340,16 +340,16 @@ class BalanceService(BaseService):
340
340
  created_at__gte=since
341
341
  ).aggregate(
342
342
  total_transactions=models.Count('id'),
343
- total_volume=models.Sum('amount'),
343
+ total_volume=models.Sum('amount_usd'),
344
344
  deposits=models.Sum(
345
- 'amount',
345
+ 'amount_usd',
346
346
  filter=models.Q(transaction_type='deposit')
347
347
  ),
348
348
  withdrawals=models.Sum(
349
- 'amount',
349
+ 'amount_usd',
350
350
  filter=models.Q(transaction_type='withdrawal')
351
351
  ),
352
- avg_transaction=models.Avg('amount')
352
+ avg_transaction=models.Avg('amount_usd')
353
353
  )
354
354
 
355
355
  # Transaction type breakdown
@@ -357,7 +357,7 @@ class BalanceService(BaseService):
357
357
  created_at__gte=since
358
358
  ).values('transaction_type').annotate(
359
359
  count=models.Count('id'),
360
- volume=models.Sum('amount')
360
+ volume=models.Sum('amount_usd')
361
361
  ).order_by('-count')
362
362
 
363
363
  stats = {
@@ -473,8 +473,7 @@ class SubscriptionService(BaseService):
473
473
  created_at__gte=since
474
474
  ).aggregate(
475
475
  new_subscriptions=models.Count('id'),
476
- total_requests=models.Sum('total_requests'),
477
- avg_requests=models.Avg('total_requests')
476
+ total_requests=models.Sum('total_requests')
478
477
  )
479
478
 
480
479
  stats = {
@@ -10,6 +10,7 @@ from rest_framework.response import Response
10
10
  from django_filters.rest_framework import DjangoFilterBackend
11
11
  from django.contrib.auth import get_user_model
12
12
  from django.db import models
13
+ from django.utils import timezone
13
14
 
14
15
  from .base import PaymentBaseViewSet, NestedPaymentViewSet, ReadOnlyPaymentViewSet
15
16
  from ...models import UserBalance, Transaction
@@ -245,12 +246,12 @@ class TransactionViewSet(ReadOnlyPaymentViewSet):
245
246
  'total_transactions': type_transactions.count(),
246
247
  'total_amount': float(
247
248
  type_transactions.aggregate(
248
- total=models.Sum('amount')
249
+ total=models.Sum('amount_usd')
249
250
  )['total'] or 0
250
251
  ),
251
252
  'average_amount': float(
252
253
  type_transactions.aggregate(
253
- avg=models.Avg('amount')
254
+ avg=models.Avg('amount_usd')
254
255
  )['avg'] or 0
255
256
  ),
256
257
  }
@@ -344,14 +345,14 @@ class UserTransactionViewSet(NestedPaymentViewSet):
344
345
  summary = queryset.aggregate(
345
346
  total_transactions=models.Count('id'),
346
347
  total_credits=models.Sum(
347
- 'amount',
348
- filter=models.Q(amount__gt=0)
348
+ 'amount_usd',
349
+ filter=models.Q(amount_usd__gt=0)
349
350
  ),
350
351
  total_debits=models.Sum(
351
- 'amount',
352
- filter=models.Q(amount__lt=0)
352
+ 'amount_usd',
353
+ filter=models.Q(amount_usd__lt=0)
353
354
  ),
354
- net_amount=models.Sum('amount'),
355
+ net_amount=models.Sum('amount_usd'),
355
356
  )
356
357
 
357
358
  # Get type breakdown
@@ -82,10 +82,10 @@ class PaymentBaseViewSet(viewsets.ModelViewSet):
82
82
  return context
83
83
 
84
84
  @action(detail=False, methods=['get'])
85
- def stats(self, request):
85
+ def stats(self, request, **kwargs):
86
86
  """
87
87
  Get statistics for the current queryset.
88
-
88
+
89
89
  Returns counts, aggregates, and breakdowns.
90
90
  """
91
91
  try:
@@ -120,10 +120,10 @@ class PaymentBaseViewSet(viewsets.ModelViewSet):
120
120
  )
121
121
 
122
122
  @action(detail=False, methods=['get'])
123
- def health(self, request):
123
+ def health(self, request, **kwargs):
124
124
  """
125
125
  Health check for the ViewSet and related services.
126
-
126
+
127
127
  Returns service status and basic metrics.
128
128
  """
129
129
  try:
@@ -264,12 +264,16 @@ class NestedPaymentViewSet(PaymentBaseViewSet):
264
264
  def get_queryset(self):
265
265
  """Filter queryset by parent object from URL."""
266
266
  queryset = super().get_queryset()
267
-
267
+
268
268
  parent_id = self.kwargs.get(self.parent_lookup_field)
269
269
  if parent_id:
270
+ # Skip filtering for schema generation placeholders
271
+ if str(parent_id).lower() in ['test', 'string', 'example']:
272
+ return queryset.none()
273
+
270
274
  filter_kwargs = {self.parent_model_field + '_id': parent_id}
271
275
  queryset = queryset.filter(**filter_kwargs)
272
-
276
+
273
277
  return queryset
274
278
 
275
279
  def perform_create(self, serializer):
@@ -9,6 +9,8 @@ from rest_framework.decorators import action
9
9
  from rest_framework.response import Response
10
10
  from django_filters.rest_framework import DjangoFilterBackend
11
11
  from django.utils import timezone
12
+ from drf_spectacular.utils import extend_schema, OpenApiParameter
13
+ from drf_spectacular.types import OpenApiTypes
12
14
 
13
15
  from .base import ReadOnlyPaymentViewSet
14
16
  from ...models import Currency, Network, ProviderCurrency
@@ -157,34 +159,75 @@ class CurrencyViewSet(ReadOnlyPaymentViewSet):
157
159
 
158
160
  return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
159
161
 
162
+ @extend_schema(
163
+ parameters=[
164
+ OpenApiParameter(
165
+ name='base_currency',
166
+ type=OpenApiTypes.STR,
167
+ location=OpenApiParameter.QUERY,
168
+ description='Base currency code (e.g., USD)',
169
+ required=True,
170
+ ),
171
+ OpenApiParameter(
172
+ name='currencies',
173
+ type=OpenApiTypes.STR,
174
+ location=OpenApiParameter.QUERY,
175
+ description='Comma-separated list of target currency codes (e.g., BTC,ETH,USDT)',
176
+ required=True,
177
+ ),
178
+ ],
179
+ summary='Get exchange rates',
180
+ description='Get current exchange rates for specified currencies',
181
+ )
160
182
  @action(detail=False, methods=['get'])
161
183
  def rates(self, request):
162
184
  """
163
185
  Get current exchange rates.
164
-
186
+
165
187
  GET /api/currencies/rates/?base_currency=USD&currencies=BTC,ETH
166
188
  """
167
189
  serializer = CurrencyRatesSerializer(data=request.query_params)
168
-
190
+
169
191
  if serializer.is_valid():
170
192
  result = serializer.save()
171
193
  return Response(result)
172
-
194
+
173
195
  return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
174
196
 
197
+ @extend_schema(
198
+ parameters=[
199
+ OpenApiParameter(
200
+ name='provider',
201
+ type=OpenApiTypes.STR,
202
+ location=OpenApiParameter.QUERY,
203
+ description='Payment provider name (e.g., nowpayments)',
204
+ required=False,
205
+ ),
206
+ OpenApiParameter(
207
+ name='currency_type',
208
+ type=OpenApiTypes.STR,
209
+ location=OpenApiParameter.QUERY,
210
+ description='Currency type filter: crypto, fiat, or stablecoin',
211
+ required=False,
212
+ enum=['crypto', 'fiat', 'stablecoin'],
213
+ ),
214
+ ],
215
+ summary='Get supported currencies',
216
+ description='Get list of supported currencies from payment providers',
217
+ )
175
218
  @action(detail=False, methods=['get'])
176
219
  def supported(self, request):
177
220
  """
178
221
  Get supported currencies from providers.
179
-
222
+
180
223
  GET /api/currencies/supported/?provider=nowpayments&currency_type=crypto
181
224
  """
182
225
  serializer = SupportedCurrenciesSerializer(data=request.query_params)
183
-
226
+
184
227
  if serializer.is_valid():
185
228
  result = serializer.save()
186
229
  return Response(result)
187
-
230
+
188
231
  return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
189
232
 
190
233
 
@@ -204,7 +247,7 @@ class NetworkViewSet(ReadOnlyPaymentViewSet):
204
247
 
205
248
  def get_queryset(self):
206
249
  """Optimize queryset with related objects."""
207
- return super().get_queryset().select_related('currency')
250
+ return super().get_queryset().select_related('native_currency')
208
251
 
209
252
  @action(detail=False, methods=['get'])
210
253
  def by_currency(self, request):
@@ -218,11 +261,11 @@ class NetworkViewSet(ReadOnlyPaymentViewSet):
218
261
 
219
262
  networks_by_currency = {}
220
263
  for network in queryset:
221
- currency_code = network.currency.code
222
-
264
+ currency_code = network.native_currency.code
265
+
223
266
  if currency_code not in networks_by_currency:
224
267
  networks_by_currency[currency_code] = {
225
- 'currency': CurrencyListSerializer(network.currency).data,
268
+ 'currency': CurrencyListSerializer(network.native_currency).data,
226
269
  'networks': []
227
270
  }
228
271
 
@@ -10,6 +10,8 @@ from rest_framework.response import Response
10
10
  from django_filters.rest_framework import DjangoFilterBackend
11
11
  from django.contrib.auth import get_user_model
12
12
  from django.shortcuts import get_object_or_404
13
+ from django.utils import timezone
14
+ from django.db import models
13
15
 
14
16
  from .base import PaymentBaseViewSet, NestedPaymentViewSet
15
17
  from ...models import UniversalPayment
@@ -371,7 +373,7 @@ class PaymentStatusView(generics.RetrieveAPIView):
371
373
  queryset = UniversalPayment.objects.all()
372
374
  serializer_class = PaymentSerializer
373
375
  permission_classes = [permissions.IsAuthenticated]
374
- lookup_field = 'id'
376
+ lookup_field = 'pk' # URL uses <uuid:pk>
375
377
 
376
378
  def get_object(self):
377
379
  """Get payment with permission check."""
@@ -344,7 +344,6 @@ class UserSubscriptionViewSet(NestedPaymentViewSet):
344
344
  filter=models.Q(expires_at__lt=timezone.now())
345
345
  ),
346
346
  total_requests=models.Sum('total_requests'),
347
- requests_used=models.Sum('requests_used'),
348
347
  )
349
348
 
350
349
  return Response({
@@ -352,8 +351,6 @@ class UserSubscriptionViewSet(NestedPaymentViewSet):
352
351
  'summary': {
353
352
  **summary,
354
353
  'total_requests': summary['total_requests'] or 0,
355
- 'requests_used': summary['requests_used'] or 0,
356
- 'requests_remaining': (summary['total_requests'] or 0) - (summary['requests_used'] or 0),
357
354
  },
358
355
  'generated_at': timezone.now().isoformat()
359
356
  })
@@ -430,7 +427,7 @@ class TariffViewSet(ReadOnlyPaymentViewSet):
430
427
 
431
428
  GET /api/tariffs/free/
432
429
  """
433
- free_tariffs = self.get_queryset().filter(monthly_price=0)
430
+ free_tariffs = self.get_queryset().filter(monthly_price_usd=0)
434
431
  serializer = self.get_serializer(free_tariffs, many=True)
435
432
 
436
433
  return Response({
@@ -447,7 +444,7 @@ class TariffViewSet(ReadOnlyPaymentViewSet):
447
444
 
448
445
  GET /api/tariffs/paid/
449
446
  """
450
- paid_tariffs = self.get_queryset().filter(monthly_price__gt=0)
447
+ paid_tariffs = self.get_queryset().filter(monthly_price_usd__gt=0)
451
448
  serializer = self.get_serializer(paid_tariffs, many=True)
452
449
 
453
450
  return Response({