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.
- 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/middleware/api_access.py +6 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
- django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
- django_cfg/apps/payments/services/core/balance_service.py +5 -5
- django_cfg/apps/payments/services/core/subscription_service.py +1 -2
- django_cfg/apps/payments/views/api/balances.py +8 -7
- django_cfg/apps/payments/views/api/base.py +10 -6
- django_cfg/apps/payments/views/api/currencies.py +53 -10
- django_cfg/apps/payments/views/api/payments.py +3 -1
- django_cfg/apps/payments/views/api/subscriptions.py +2 -5
- 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 +82 -41
- 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 -265
- 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/models/django/{revolution.py → revolution_legacy.py} +8 -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/modules/django_unfold/dashboard.py +1 -1
- 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.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
- django_cfg/management/commands/generate.py +0 -107
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
- {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.
|
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):
|
@@ -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('
|
343
|
+
total_volume=models.Sum('amount_usd'),
|
344
344
|
deposits=models.Sum(
|
345
|
-
'
|
345
|
+
'amount_usd',
|
346
346
|
filter=models.Q(transaction_type='deposit')
|
347
347
|
),
|
348
348
|
withdrawals=models.Sum(
|
349
|
-
'
|
349
|
+
'amount_usd',
|
350
350
|
filter=models.Q(transaction_type='withdrawal')
|
351
351
|
),
|
352
|
-
avg_transaction=models.Avg('
|
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('
|
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('
|
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('
|
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
|
-
'
|
348
|
-
filter=models.Q(
|
348
|
+
'amount_usd',
|
349
|
+
filter=models.Q(amount_usd__gt=0)
|
349
350
|
),
|
350
351
|
total_debits=models.Sum(
|
351
|
-
'
|
352
|
-
filter=models.Q(
|
352
|
+
'amount_usd',
|
353
|
+
filter=models.Q(amount_usd__lt=0)
|
353
354
|
),
|
354
|
-
net_amount=models.Sum('
|
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¤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
|
|
@@ -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('
|
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.
|
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.
|
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 = '
|
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(
|
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(
|
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({
|