django-cfg 1.4.10__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 (181) 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/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +72 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/dashboard/sections/documentation.py +391 -0
  20. django_cfg/management/commands/check_endpoints.py +11 -160
  21. django_cfg/management/commands/check_settings.py +13 -348
  22. django_cfg/management/commands/clear_constance.py +13 -201
  23. django_cfg/management/commands/create_token.py +13 -321
  24. django_cfg/management/commands/generate_clients.py +23 -0
  25. django_cfg/management/commands/list_urls.py +13 -306
  26. django_cfg/management/commands/migrate_all.py +13 -126
  27. django_cfg/management/commands/migrator.py +13 -396
  28. django_cfg/management/commands/rundramatiq.py +15 -247
  29. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  30. django_cfg/management/commands/runserver_ngrok.py +15 -160
  31. django_cfg/management/commands/script.py +12 -488
  32. django_cfg/management/commands/show_config.py +12 -215
  33. django_cfg/management/commands/show_urls.py +12 -342
  34. django_cfg/management/commands/superuser.py +15 -295
  35. django_cfg/management/commands/task_clear.py +14 -217
  36. django_cfg/management/commands/task_status.py +13 -248
  37. django_cfg/management/commands/test_email.py +15 -86
  38. django_cfg/management/commands/test_telegram.py +14 -61
  39. django_cfg/management/commands/test_twilio.py +15 -105
  40. django_cfg/management/commands/tree.py +13 -383
  41. django_cfg/management/commands/validate_openapi.py +10 -0
  42. django_cfg/middleware/README.md +1 -1
  43. django_cfg/middleware/user_activity.py +3 -3
  44. django_cfg/models/__init__.py +2 -2
  45. django_cfg/models/api/drf/spectacular.py +6 -6
  46. django_cfg/models/django/__init__.py +2 -2
  47. django_cfg/models/django/openapi.py +238 -0
  48. django_cfg/modules/django_admin/management/__init__.py +0 -0
  49. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  50. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  51. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  52. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  53. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  54. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  55. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  56. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  57. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  58. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  59. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  60. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  61. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  62. django_cfg/modules/django_client/__init__.py +20 -0
  63. django_cfg/modules/django_client/apps.py +35 -0
  64. django_cfg/modules/django_client/core/__init__.py +56 -0
  65. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  66. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  67. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  68. django_cfg/modules/django_client/core/cli/main.py +235 -0
  69. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  70. django_cfg/modules/django_client/core/config/config.py +188 -0
  71. django_cfg/modules/django_client/core/config/group.py +101 -0
  72. django_cfg/modules/django_client/core/config/service.py +209 -0
  73. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  74. django_cfg/modules/django_client/core/generator/base.py +767 -0
  75. django_cfg/modules/django_client/core/generator/python.py +751 -0
  76. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  77. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  78. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  79. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  80. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  81. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  82. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  83. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  84. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  85. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  86. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  87. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  94. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  95. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  96. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  97. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  98. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  99. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  100. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  102. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  103. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  104. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  105. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  112. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  113. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  114. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  115. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  116. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  117. django_cfg/modules/django_client/core/ir/context.py +387 -0
  118. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  119. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  120. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  121. django_cfg/modules/django_client/core/parser/base.py +648 -0
  122. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  123. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  124. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  125. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  126. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  127. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  128. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  129. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  130. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  131. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  132. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  133. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  134. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  135. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  136. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  137. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  138. django_cfg/modules/django_client/management/__init__.py +3 -0
  139. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  140. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  141. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  142. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  143. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  144. django_cfg/modules/django_client/urls.py +72 -0
  145. django_cfg/modules/django_email/management/__init__.py +0 -0
  146. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  147. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  148. django_cfg/modules/django_logging/django_logger.py +6 -6
  149. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  150. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  151. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  152. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  153. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  154. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  155. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  156. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  157. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  158. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  159. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  160. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  161. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  162. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  164. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  165. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  166. django_cfg/pyproject.toml +2 -6
  167. django_cfg/registry/third_party.py +5 -7
  168. django_cfg/routing/callbacks.py +1 -1
  169. django_cfg/static/admin/css/prose-unfold.css +666 -0
  170. django_cfg/templates/admin/index.html +8 -0
  171. django_cfg/templates/admin/index_new.html +13 -0
  172. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  173. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  174. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  175. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  176. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
  177. django_cfg/management/commands/generate.py +0 -107
  178. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  179. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.10.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):
@@ -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
 
@@ -17,9 +17,16 @@ from rest_framework.decorators import api_view, permission_classes
17
17
  from rest_framework.permissions import AllowAny
18
18
  from rest_framework.response import Response
19
19
  from rest_framework.views import APIView
20
+ from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
20
21
 
21
22
  from ...services.core.webhook_service import WebhookService
22
23
  from ...services.types import WebhookValidationRequest, WebhookProcessingResult
24
+ from ..serializers.webhooks import (
25
+ WebhookResponseSerializer,
26
+ WebhookHealthSerializer,
27
+ WebhookStatsSerializer,
28
+ SupportedProvidersSerializer,
29
+ )
23
30
  from django_cfg.modules.django_logging import get_logger
24
31
 
25
32
  logger = get_logger("webhook_views")
@@ -29,7 +36,7 @@ logger = get_logger("webhook_views")
29
36
  class UniversalWebhookView(APIView):
30
37
  """
31
38
  Universal webhook handler for all payment providers.
32
-
39
+
33
40
  Features:
34
41
  - Provider-agnostic webhook processing
35
42
  - Signature validation and security
@@ -37,13 +44,29 @@ class UniversalWebhookView(APIView):
37
44
  - Comprehensive logging and monitoring
38
45
  - Integration with ngrok for development
39
46
  """
40
-
47
+
41
48
  permission_classes = [AllowAny] # Webhooks don't use standard auth
49
+ serializer_class = WebhookResponseSerializer # For OpenAPI schema generation
42
50
 
43
51
  def __init__(self, **kwargs):
44
52
  super().__init__(**kwargs)
45
53
  self.webhook_service = WebhookService()
46
-
54
+
55
+ @extend_schema(
56
+ summary="Process Webhook",
57
+ description="Process incoming webhook from payment provider",
58
+ parameters=[
59
+ OpenApiParameter(
60
+ name='provider',
61
+ description='Payment provider name (nowpayments, stripe, etc.)',
62
+ required=True,
63
+ type=OpenApiTypes.STR,
64
+ location=OpenApiParameter.PATH
65
+ )
66
+ ],
67
+ responses={200: WebhookResponseSerializer},
68
+ tags=["Webhooks"]
69
+ )
47
70
  def post(self, request: HttpRequest, provider: str) -> JsonResponse:
48
71
  """
49
72
  Handle incoming webhook from any payment provider.
@@ -144,10 +167,25 @@ class UniversalWebhookView(APIView):
144
167
  request_id
145
168
  )
146
169
 
170
+ @extend_schema(
171
+ summary="Webhook Endpoint Info",
172
+ description="Get webhook endpoint information for debugging and configuration",
173
+ parameters=[
174
+ OpenApiParameter(
175
+ name='provider',
176
+ description='Payment provider name',
177
+ required=True,
178
+ type=OpenApiTypes.STR,
179
+ location=OpenApiParameter.PATH
180
+ )
181
+ ],
182
+ responses={200: WebhookResponseSerializer},
183
+ tags=["Webhooks"]
184
+ )
147
185
  def get(self, request: HttpRequest, provider: str) -> JsonResponse:
148
186
  """
149
187
  Handle GET requests for webhook endpoint info.
150
-
188
+
151
189
  Useful for debugging and provider configuration verification.
152
190
  """
153
191
 
@@ -314,12 +352,18 @@ class UniversalWebhookView(APIView):
314
352
  return JsonResponse(response_data, status=status_code)
315
353
 
316
354
 
355
+ @extend_schema(
356
+ summary="Webhook Health Check",
357
+ description="Check webhook service health status and recent activity metrics",
358
+ responses={200: WebhookHealthSerializer},
359
+ tags=["Webhooks"]
360
+ )
317
361
  @api_view(['GET'])
318
362
  @permission_classes([AllowAny])
319
363
  def webhook_health_check(request):
320
364
  """
321
365
  Health check endpoint for webhook service.
322
-
366
+
323
367
  Returns service status and recent activity metrics.
324
368
  """
325
369
 
@@ -362,12 +406,27 @@ def webhook_health_check(request):
362
406
  }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
363
407
 
364
408
 
409
+ @extend_schema(
410
+ summary="Webhook Statistics",
411
+ description="Get webhook processing statistics for a given time period",
412
+ parameters=[
413
+ OpenApiParameter(
414
+ name='days',
415
+ description='Number of days to analyze (1-365)',
416
+ required=False,
417
+ type=OpenApiTypes.INT,
418
+ default=30
419
+ )
420
+ ],
421
+ responses={200: WebhookStatsSerializer},
422
+ tags=["Webhooks"]
423
+ )
365
424
  @api_view(['GET'])
366
425
  @permission_classes([AllowAny])
367
426
  def webhook_stats(request):
368
427
  """
369
428
  Get webhook processing statistics.
370
-
429
+
371
430
  Query parameters:
372
431
  - days: Number of days to analyze (default: 30)
373
432
  """
@@ -408,12 +467,18 @@ def webhook_stats(request):
408
467
  }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
409
468
 
410
469
 
470
+ @extend_schema(
471
+ summary="Supported Webhook Providers",
472
+ description="Get list of supported webhook providers with configuration details",
473
+ responses={200: SupportedProvidersSerializer},
474
+ tags=["Webhooks"]
475
+ )
411
476
  @api_view(['GET'])
412
477
  @permission_classes([AllowAny])
413
478
  def supported_providers(request):
414
479
  """
415
480
  Get list of supported webhook providers.
416
-
481
+
417
482
  Returns provider information and webhook URLs.
418
483
  """
419
484
 
@@ -201,5 +201,38 @@ class TimePeriodSerializer(serializers.Serializer):
201
201
  ('90d', 'Last 90 days'),
202
202
  ('1y', 'Last year'),
203
203
  ]
204
-
204
+
205
205
  period = serializers.ChoiceField(choices=PERIOD_CHOICES, default='30d')
206
+
207
+
208
+ class CurrencyAnalyticsItemSerializer(serializers.Serializer):
209
+ """
210
+ Analytics data for a single currency
211
+ """
212
+ currency_code = serializers.CharField(help_text="Currency code (e.g., BTC)")
213
+ currency_name = serializers.CharField(help_text="Currency name (e.g., Bitcoin)")
214
+ total_payments = serializers.IntegerField(help_text="Total number of payments")
215
+ total_amount = serializers.FloatField(help_text="Total amount in USD")
216
+ completed_payments = serializers.IntegerField(help_text="Number of completed payments")
217
+ average_amount = serializers.FloatField(help_text="Average payment amount in USD")
218
+ success_rate = serializers.FloatField(help_text="Success rate percentage")
219
+
220
+
221
+ class ProviderAnalyticsItemSerializer(serializers.Serializer):
222
+ """
223
+ Analytics data for a single payment provider
224
+ """
225
+ provider = serializers.CharField(help_text="Provider code")
226
+ provider_display = serializers.CharField(help_text="Provider display name")
227
+ total_payments = serializers.IntegerField(help_text="Total number of payments")
228
+ total_amount = serializers.FloatField(help_text="Total amount in USD")
229
+ completed_payments = serializers.IntegerField(help_text="Number of completed payments")
230
+ success_rate = serializers.FloatField(help_text="Success rate percentage")
231
+
232
+
233
+ class PaymentAnalyticsResponseSerializer(serializers.Serializer):
234
+ """
235
+ Payment analytics response with currency and provider breakdown
236
+ """
237
+ currency_analytics = CurrencyAnalyticsItemSerializer(many=True, help_text="Analytics by currency")
238
+ provider_analytics = ProviderAnalyticsItemSerializer(many=True, help_text="Analytics by provider")
@@ -26,6 +26,7 @@ from .serializers import (
26
26
  BalanceOverviewSerializer,
27
27
  SubscriptionOverviewSerializer,
28
28
  APIKeysOverviewSerializer,
29
+ PaymentAnalyticsResponseSerializer,
29
30
  )
30
31
 
31
32
 
@@ -95,7 +96,7 @@ from .serializers import (
95
96
  default=10
96
97
  )
97
98
  ],
98
- responses={200: 'object'} # Generic object since it's a dict with currency_analytics and provider_analytics
99
+ responses={200: PaymentAnalyticsResponseSerializer}
99
100
  ),
100
101
  balance_overview=extend_schema(
101
102
  summary="Balance Overview",
@@ -27,7 +27,7 @@ class PaymentListSerializer(serializers.ModelSerializer):
27
27
  status_display = serializers.CharField(source='get_status_display', read_only=True)
28
28
  amount_display = serializers.SerializerMethodField(read_only=True)
29
29
 
30
- def get_amount_display(self, obj):
30
+ def get_amount_display(self, obj) -> str:
31
31
  """Get formatted amount display."""
32
32
  return f"${obj.amount_usd:.2f}"
33
33
 
@@ -64,23 +64,23 @@ class PaymentSerializer(serializers.ModelSerializer):
64
64
  is_failed = serializers.SerializerMethodField(read_only=True)
65
65
  is_expired = serializers.SerializerMethodField(read_only=True)
66
66
 
67
- def get_amount_display(self, obj):
67
+ def get_amount_display(self, obj) -> str:
68
68
  """Get formatted amount display."""
69
69
  return f"${obj.amount_usd:.2f}"
70
70
 
71
- def get_is_pending(self, obj):
71
+ def get_is_pending(self, obj) -> bool:
72
72
  """Check if payment is pending."""
73
73
  return obj.status == obj.PaymentStatus.PENDING
74
74
 
75
- def get_is_completed(self, obj):
75
+ def get_is_completed(self, obj) -> bool:
76
76
  """Check if payment is completed."""
77
77
  return obj.status == obj.PaymentStatus.COMPLETED
78
78
 
79
- def get_is_failed(self, obj):
79
+ def get_is_failed(self, obj) -> bool:
80
80
  """Check if payment is failed."""
81
81
  return obj.status == obj.PaymentStatus.FAILED
82
82
 
83
- def get_is_expired(self, obj):
83
+ def get_is_expired(self, obj) -> bool:
84
84
  """Check if payment is expired."""
85
85
  return obj.status == obj.PaymentStatus.EXPIRED
86
86