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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +72 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/dashboard/sections/documentation.py +391 -0
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +238 -0
- django_cfg/modules/django_admin/management/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +188 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +767 -0
- django_cfg/modules/django_client/core/generator/python.py +751 -0
- django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
- django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
- django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/typescript.py +872 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/spectacular/__init__.py +9 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +16 -5
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
- {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.
|
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.
|
11
|
-
from django_cfg.
|
12
|
-
from django_cfg.
|
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.
|
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
|
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:
|
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={
|
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
|
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={
|
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={
|
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={
|
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={
|
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¤cies=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¤cy_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:
|
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
|
|