django-cfg 1.2.21__py3-none-any.whl → 1.2.22__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/__init__.py +1 -1
- django_cfg/apps/newsletter/signals.py +9 -8
- django_cfg/apps/payments/__init__.py +8 -0
- django_cfg/apps/payments/apps.py +22 -0
- django_cfg/apps/payments/managers/__init__.py +22 -0
- django_cfg/apps/payments/managers/api_key_manager.py +35 -0
- django_cfg/apps/payments/managers/balance_manager.py +361 -0
- django_cfg/apps/payments/managers/currency_manager.py +32 -0
- django_cfg/apps/payments/managers/payment_manager.py +44 -0
- django_cfg/apps/payments/managers/subscription_manager.py +37 -0
- django_cfg/apps/payments/managers/tariff_manager.py +29 -0
- django_cfg/apps/payments/middleware/__init__.py +13 -0
- django_cfg/apps/payments/migrations/0001_initial.py +982 -0
- django_cfg/apps/payments/migrations/__init__.py +1 -0
- django_cfg/apps/payments/models/__init__.py +49 -0
- django_cfg/apps/payments/models/api_keys.py +96 -0
- django_cfg/apps/payments/models/balance.py +209 -0
- django_cfg/apps/payments/models/base.py +14 -0
- django_cfg/apps/payments/models/currencies.py +138 -0
- django_cfg/apps/payments/models/events.py +73 -0
- django_cfg/apps/payments/models/payments.py +301 -0
- django_cfg/apps/payments/models/subscriptions.py +270 -0
- django_cfg/apps/payments/models/tariffs.py +102 -0
- django_cfg/apps/payments/serializers/__init__.py +56 -0
- django_cfg/apps/payments/serializers/api_keys.py +51 -0
- django_cfg/apps/payments/serializers/balance.py +59 -0
- django_cfg/apps/payments/serializers/currencies.py +55 -0
- django_cfg/apps/payments/serializers/payments.py +62 -0
- django_cfg/apps/payments/serializers/subscriptions.py +71 -0
- django_cfg/apps/payments/serializers/tariffs.py +56 -0
- django_cfg/apps/payments/services/__init__.py +14 -0
- django_cfg/apps/payments/services/base.py +68 -0
- django_cfg/apps/payments/services/nowpayments.py +78 -0
- django_cfg/apps/payments/services/providers.py +77 -0
- django_cfg/apps/payments/services/redis_service.py +215 -0
- django_cfg/apps/payments/urls.py +78 -0
- django_cfg/apps/payments/views/__init__.py +62 -0
- django_cfg/apps/payments/views/api_key_views.py +164 -0
- django_cfg/apps/payments/views/balance_views.py +75 -0
- django_cfg/apps/payments/views/currency_views.py +111 -0
- django_cfg/apps/payments/views/payment_views.py +111 -0
- django_cfg/apps/payments/views/subscription_views.py +135 -0
- django_cfg/apps/payments/views/tariff_views.py +131 -0
- django_cfg/core/config.py +6 -0
- django_cfg/models/revolution.py +14 -0
- django_cfg/modules/base.py +9 -0
- {django_cfg-1.2.21.dist-info → django_cfg-1.2.22.dist-info}/METADATA +1 -1
- {django_cfg-1.2.21.dist-info → django_cfg-1.2.22.dist-info}/RECORD +51 -10
- {django_cfg-1.2.21.dist-info → django_cfg-1.2.22.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.21.dist-info → django_cfg-1.2.22.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.21.dist-info → django_cfg-1.2.22.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
"""
|
2
|
+
Currency ViewSets.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rest_framework import viewsets, permissions, generics
|
6
|
+
from rest_framework.decorators import action
|
7
|
+
from rest_framework.response import Response
|
8
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
+
from ..models import Currency, CurrencyNetwork
|
10
|
+
from ..serializers import (
|
11
|
+
CurrencySerializer, CurrencyNetworkSerializer, CurrencyListSerializer
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class CurrencyViewSet(viewsets.ReadOnlyModelViewSet):
|
16
|
+
"""Currency ViewSet: /currencies/"""
|
17
|
+
|
18
|
+
queryset = Currency.objects.filter(is_active=True)
|
19
|
+
serializer_class = CurrencySerializer
|
20
|
+
permission_classes = [permissions.IsAuthenticated]
|
21
|
+
filter_backends = [DjangoFilterBackend]
|
22
|
+
filterset_fields = ['currency_type', 'is_active']
|
23
|
+
|
24
|
+
def get_serializer_class(self):
|
25
|
+
"""Use list serializer for list action."""
|
26
|
+
if self.action == 'list':
|
27
|
+
return CurrencyListSerializer
|
28
|
+
return CurrencySerializer
|
29
|
+
|
30
|
+
@action(detail=False, methods=['get'])
|
31
|
+
def crypto(self, request):
|
32
|
+
"""Get only cryptocurrencies."""
|
33
|
+
cryptos = self.get_queryset().filter(currency_type='crypto')
|
34
|
+
serializer = CurrencyListSerializer(cryptos, many=True)
|
35
|
+
return Response(serializer.data)
|
36
|
+
|
37
|
+
@action(detail=False, methods=['get'])
|
38
|
+
def fiat(self, request):
|
39
|
+
"""Get only fiat currencies."""
|
40
|
+
fiats = self.get_queryset().filter(currency_type='fiat')
|
41
|
+
serializer = CurrencyListSerializer(fiats, many=True)
|
42
|
+
return Response(serializer.data)
|
43
|
+
|
44
|
+
@action(detail=True, methods=['get'])
|
45
|
+
def networks(self, request, pk=None):
|
46
|
+
"""Get networks for specific currency."""
|
47
|
+
currency = self.get_object()
|
48
|
+
networks = CurrencyNetwork.objects.filter(
|
49
|
+
currency=currency,
|
50
|
+
is_active=True
|
51
|
+
)
|
52
|
+
serializer = CurrencyNetworkSerializer(networks, many=True)
|
53
|
+
return Response(serializer.data)
|
54
|
+
|
55
|
+
|
56
|
+
class CurrencyNetworkViewSet(viewsets.ReadOnlyModelViewSet):
|
57
|
+
"""Currency Network ViewSet: /currency-networks/"""
|
58
|
+
|
59
|
+
queryset = CurrencyNetwork.objects.filter(is_active=True)
|
60
|
+
serializer_class = CurrencyNetworkSerializer
|
61
|
+
permission_classes = [permissions.IsAuthenticated]
|
62
|
+
filter_backends = [DjangoFilterBackend]
|
63
|
+
filterset_fields = ['currency', 'network_code', 'is_active']
|
64
|
+
|
65
|
+
@action(detail=False, methods=['get'])
|
66
|
+
def by_currency(self, request):
|
67
|
+
"""Get networks grouped by currency."""
|
68
|
+
currency_code = request.query_params.get('currency')
|
69
|
+
if not currency_code:
|
70
|
+
return Response({'error': 'currency parameter required'}, status=400)
|
71
|
+
|
72
|
+
try:
|
73
|
+
currency = Currency.objects.get(code=currency_code, is_active=True)
|
74
|
+
networks = self.get_queryset().filter(currency=currency)
|
75
|
+
serializer = self.get_serializer(networks, many=True)
|
76
|
+
return Response(serializer.data)
|
77
|
+
except Currency.DoesNotExist:
|
78
|
+
return Response({'error': 'Currency not found'}, status=404)
|
79
|
+
|
80
|
+
|
81
|
+
# Generic views for specific use cases
|
82
|
+
class SupportedCurrenciesView(generics.ListAPIView):
|
83
|
+
"""Generic view to list supported currencies."""
|
84
|
+
|
85
|
+
serializer_class = CurrencyListSerializer
|
86
|
+
permission_classes = [permissions.IsAuthenticated]
|
87
|
+
|
88
|
+
def get_queryset(self):
|
89
|
+
"""Get active currencies."""
|
90
|
+
return Currency.objects.filter(is_active=True).order_by('code')
|
91
|
+
|
92
|
+
|
93
|
+
class CurrencyRatesView(generics.GenericAPIView):
|
94
|
+
"""Generic view to get currency exchange rates."""
|
95
|
+
|
96
|
+
serializer_class = CurrencySerializer # For schema generation
|
97
|
+
permission_classes = [permissions.IsAuthenticated]
|
98
|
+
|
99
|
+
def get(self, request):
|
100
|
+
"""Get current exchange rates."""
|
101
|
+
currencies = Currency.objects.filter(is_active=True)
|
102
|
+
|
103
|
+
rates = {}
|
104
|
+
for currency in currencies:
|
105
|
+
rates[currency.code] = {
|
106
|
+
'usd_rate': currency.usd_rate,
|
107
|
+
'updated_at': currency.rate_updated_at,
|
108
|
+
'type': currency.currency_type,
|
109
|
+
}
|
110
|
+
|
111
|
+
return Response(rates)
|
@@ -0,0 +1,111 @@
|
|
1
|
+
"""
|
2
|
+
Payment ViewSets with nested routing.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rest_framework import viewsets, permissions, status, generics
|
6
|
+
from rest_framework.decorators import action
|
7
|
+
from rest_framework.response import Response
|
8
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
+
from django.contrib.auth import get_user_model
|
10
|
+
from ..models import UniversalPayment
|
11
|
+
from ..serializers import (
|
12
|
+
UniversalPaymentSerializer, PaymentCreateSerializer, PaymentListSerializer
|
13
|
+
)
|
14
|
+
|
15
|
+
User = get_user_model()
|
16
|
+
|
17
|
+
|
18
|
+
class UserPaymentViewSet(viewsets.ModelViewSet):
|
19
|
+
"""Nested ViewSet for user payments: /users/{user_id}/payments/"""
|
20
|
+
|
21
|
+
serializer_class = UniversalPaymentSerializer
|
22
|
+
permission_classes = [permissions.IsAuthenticated]
|
23
|
+
filter_backends = [DjangoFilterBackend]
|
24
|
+
filterset_fields = ['status', 'provider', 'currency_code']
|
25
|
+
|
26
|
+
def get_queryset(self):
|
27
|
+
"""Filter by user from URL."""
|
28
|
+
user_id = self.kwargs.get('user_pk')
|
29
|
+
return UniversalPayment.objects.filter(user_id=user_id).order_by('-created_at')
|
30
|
+
|
31
|
+
def get_serializer_class(self):
|
32
|
+
"""Use different serializers for different actions."""
|
33
|
+
if self.action == 'create':
|
34
|
+
return PaymentCreateSerializer
|
35
|
+
elif self.action == 'list':
|
36
|
+
return PaymentListSerializer
|
37
|
+
return UniversalPaymentSerializer
|
38
|
+
|
39
|
+
def perform_create(self, serializer):
|
40
|
+
"""Set user from URL when creating."""
|
41
|
+
user_id = self.kwargs.get('user_pk')
|
42
|
+
user = User.objects.get(id=user_id)
|
43
|
+
serializer.save(user=user)
|
44
|
+
|
45
|
+
@action(detail=True, methods=['post'])
|
46
|
+
def check_status(self, request, user_pk=None, pk=None):
|
47
|
+
"""Check payment status via provider API."""
|
48
|
+
payment = self.get_object()
|
49
|
+
# TODO: Implement provider status check
|
50
|
+
return Response({'status': payment.status})
|
51
|
+
|
52
|
+
@action(detail=False, methods=['get'])
|
53
|
+
def summary(self, request, user_pk=None):
|
54
|
+
"""Get payment summary for user."""
|
55
|
+
queryset = self.get_queryset()
|
56
|
+
|
57
|
+
total_payments = queryset.count()
|
58
|
+
total_amount = sum(p.amount_usd for p in queryset if p.status == 'completed')
|
59
|
+
pending_amount = sum(p.amount_usd for p in queryset if p.status == 'pending')
|
60
|
+
|
61
|
+
return Response({
|
62
|
+
'total_payments': total_payments,
|
63
|
+
'total_amount_usd': total_amount,
|
64
|
+
'pending_amount_usd': pending_amount,
|
65
|
+
'completed_payments': queryset.filter(status='completed').count(),
|
66
|
+
'pending_payments': queryset.filter(status='pending').count(),
|
67
|
+
})
|
68
|
+
|
69
|
+
|
70
|
+
class UniversalPaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
71
|
+
"""Global payment ViewSet: /payments/"""
|
72
|
+
|
73
|
+
queryset = UniversalPayment.objects.all()
|
74
|
+
serializer_class = UniversalPaymentSerializer
|
75
|
+
permission_classes = [permissions.IsAuthenticated]
|
76
|
+
filter_backends = [DjangoFilterBackend]
|
77
|
+
filterset_fields = ['status', 'provider', 'currency_code']
|
78
|
+
|
79
|
+
def get_queryset(self):
|
80
|
+
"""Filter by current user for security."""
|
81
|
+
return UniversalPayment.objects.filter(user=self.request.user).order_by('-created_at')
|
82
|
+
|
83
|
+
def get_serializer_class(self):
|
84
|
+
"""Use list serializer for list action."""
|
85
|
+
if self.action == 'list':
|
86
|
+
return PaymentListSerializer
|
87
|
+
return UniversalPaymentSerializer
|
88
|
+
|
89
|
+
|
90
|
+
# Generic views for specific use cases
|
91
|
+
class PaymentCreateView(generics.CreateAPIView):
|
92
|
+
"""Generic view to create payment."""
|
93
|
+
|
94
|
+
serializer_class = PaymentCreateSerializer
|
95
|
+
permission_classes = [permissions.IsAuthenticated]
|
96
|
+
|
97
|
+
def perform_create(self, serializer):
|
98
|
+
"""Set current user when creating."""
|
99
|
+
serializer.save(user=self.request.user)
|
100
|
+
|
101
|
+
|
102
|
+
class PaymentStatusView(generics.RetrieveAPIView):
|
103
|
+
"""Generic view to check payment status."""
|
104
|
+
|
105
|
+
serializer_class = UniversalPaymentSerializer
|
106
|
+
permission_classes = [permissions.IsAuthenticated]
|
107
|
+
lookup_field = 'internal_payment_id'
|
108
|
+
|
109
|
+
def get_queryset(self):
|
110
|
+
"""Filter by current user."""
|
111
|
+
return UniversalPayment.objects.filter(user=self.request.user)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
"""
|
2
|
+
Subscription ViewSets with nested routing.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rest_framework import viewsets, permissions, status, generics
|
6
|
+
from rest_framework.decorators import action
|
7
|
+
from rest_framework.response import Response
|
8
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
+
from django.contrib.auth import get_user_model
|
10
|
+
from ..models import Subscription, EndpointGroup
|
11
|
+
from ..serializers import (
|
12
|
+
SubscriptionSerializer, SubscriptionCreateSerializer, SubscriptionListSerializer,
|
13
|
+
EndpointGroupSerializer
|
14
|
+
)
|
15
|
+
|
16
|
+
User = get_user_model()
|
17
|
+
|
18
|
+
|
19
|
+
class UserSubscriptionViewSet(viewsets.ModelViewSet):
|
20
|
+
"""Nested ViewSet for user subscriptions: /users/{user_id}/subscriptions/"""
|
21
|
+
|
22
|
+
serializer_class = SubscriptionSerializer
|
23
|
+
permission_classes = [permissions.IsAuthenticated]
|
24
|
+
filter_backends = [DjangoFilterBackend]
|
25
|
+
filterset_fields = ['status', 'tier', 'endpoint_group']
|
26
|
+
|
27
|
+
def get_queryset(self):
|
28
|
+
"""Filter by user from URL."""
|
29
|
+
user_id = self.kwargs.get('user_pk')
|
30
|
+
return Subscription.objects.filter(user_id=user_id).order_by('-created_at')
|
31
|
+
|
32
|
+
def get_serializer_class(self):
|
33
|
+
"""Use different serializers for different actions."""
|
34
|
+
if self.action == 'create':
|
35
|
+
return SubscriptionCreateSerializer
|
36
|
+
elif self.action == 'list':
|
37
|
+
return SubscriptionListSerializer
|
38
|
+
return SubscriptionSerializer
|
39
|
+
|
40
|
+
def perform_create(self, serializer):
|
41
|
+
"""Set user from URL when creating."""
|
42
|
+
user_id = self.kwargs.get('user_pk')
|
43
|
+
user = User.objects.get(id=user_id)
|
44
|
+
serializer.save(user=user)
|
45
|
+
|
46
|
+
@action(detail=True, methods=['post'])
|
47
|
+
def cancel(self, request, user_pk=None, pk=None):
|
48
|
+
"""Cancel subscription."""
|
49
|
+
subscription = self.get_object()
|
50
|
+
subscription.status = 'cancelled'
|
51
|
+
subscription.save()
|
52
|
+
|
53
|
+
serializer = self.get_serializer(subscription)
|
54
|
+
return Response(serializer.data)
|
55
|
+
|
56
|
+
@action(detail=True, methods=['post'])
|
57
|
+
def renew(self, request, user_pk=None, pk=None):
|
58
|
+
"""Renew subscription."""
|
59
|
+
subscription = self.get_object()
|
60
|
+
# TODO: Implement renewal logic
|
61
|
+
return Response({'message': 'Subscription renewed'})
|
62
|
+
|
63
|
+
@action(detail=False, methods=['get'])
|
64
|
+
def active(self, request, user_pk=None):
|
65
|
+
"""Get active subscriptions for user."""
|
66
|
+
queryset = self.get_queryset().filter(status='active')
|
67
|
+
serializer = SubscriptionListSerializer(queryset, many=True)
|
68
|
+
return Response(serializer.data)
|
69
|
+
|
70
|
+
|
71
|
+
class SubscriptionViewSet(viewsets.ReadOnlyModelViewSet):
|
72
|
+
"""Global subscription ViewSet: /subscriptions/"""
|
73
|
+
|
74
|
+
queryset = Subscription.objects.all()
|
75
|
+
serializer_class = SubscriptionSerializer
|
76
|
+
permission_classes = [permissions.IsAuthenticated]
|
77
|
+
filter_backends = [DjangoFilterBackend]
|
78
|
+
filterset_fields = ['status', 'tier', 'endpoint_group']
|
79
|
+
|
80
|
+
def get_queryset(self):
|
81
|
+
"""Filter by current user for security."""
|
82
|
+
return Subscription.objects.filter(user=self.request.user).order_by('-created_at')
|
83
|
+
|
84
|
+
def get_serializer_class(self):
|
85
|
+
"""Use list serializer for list action."""
|
86
|
+
if self.action == 'list':
|
87
|
+
return SubscriptionListSerializer
|
88
|
+
return SubscriptionSerializer
|
89
|
+
|
90
|
+
|
91
|
+
class EndpointGroupViewSet(viewsets.ReadOnlyModelViewSet):
|
92
|
+
"""Endpoint groups ViewSet: /endpoint-groups/"""
|
93
|
+
|
94
|
+
queryset = EndpointGroup.objects.filter(is_active=True)
|
95
|
+
serializer_class = EndpointGroupSerializer
|
96
|
+
permission_classes = [permissions.IsAuthenticated]
|
97
|
+
|
98
|
+
@action(detail=True, methods=['get'])
|
99
|
+
def pricing(self, request, pk=None):
|
100
|
+
"""Get pricing for endpoint group."""
|
101
|
+
endpoint_group = self.get_object()
|
102
|
+
return Response({
|
103
|
+
'basic_price': endpoint_group.basic_price,
|
104
|
+
'premium_price': endpoint_group.premium_price,
|
105
|
+
'enterprise_price': endpoint_group.enterprise_price,
|
106
|
+
'basic_limit': endpoint_group.basic_limit,
|
107
|
+
'premium_limit': endpoint_group.premium_limit,
|
108
|
+
'enterprise_limit': endpoint_group.enterprise_limit,
|
109
|
+
})
|
110
|
+
|
111
|
+
|
112
|
+
# Generic views for specific use cases
|
113
|
+
class SubscriptionCreateView(generics.CreateAPIView):
|
114
|
+
"""Generic view to create subscription."""
|
115
|
+
|
116
|
+
serializer_class = SubscriptionCreateSerializer
|
117
|
+
permission_classes = [permissions.IsAuthenticated]
|
118
|
+
|
119
|
+
def perform_create(self, serializer):
|
120
|
+
"""Set current user when creating."""
|
121
|
+
serializer.save(user=self.request.user)
|
122
|
+
|
123
|
+
|
124
|
+
class ActiveSubscriptionsView(generics.ListAPIView):
|
125
|
+
"""Generic view to list active subscriptions."""
|
126
|
+
|
127
|
+
serializer_class = SubscriptionListSerializer
|
128
|
+
permission_classes = [permissions.IsAuthenticated]
|
129
|
+
|
130
|
+
def get_queryset(self):
|
131
|
+
"""Get active subscriptions for current user."""
|
132
|
+
return Subscription.objects.filter(
|
133
|
+
user=self.request.user,
|
134
|
+
status='active'
|
135
|
+
).order_by('-created_at')
|
@@ -0,0 +1,131 @@
|
|
1
|
+
"""
|
2
|
+
Tariff ViewSets.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rest_framework import viewsets, permissions, generics
|
6
|
+
from rest_framework.decorators import action
|
7
|
+
from rest_framework.response import Response
|
8
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
+
from ..models import Tariff, TariffEndpointGroup
|
10
|
+
from ..serializers import (
|
11
|
+
TariffSerializer, TariffEndpointGroupSerializer, TariffListSerializer
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class TariffViewSet(viewsets.ReadOnlyModelViewSet):
|
16
|
+
"""Tariff ViewSet: /tariffs/"""
|
17
|
+
|
18
|
+
queryset = Tariff.objects.filter(is_active=True)
|
19
|
+
serializer_class = TariffSerializer
|
20
|
+
permission_classes = [permissions.IsAuthenticated]
|
21
|
+
filter_backends = [DjangoFilterBackend]
|
22
|
+
filterset_fields = ['is_active']
|
23
|
+
|
24
|
+
def get_serializer_class(self):
|
25
|
+
"""Use list serializer for list action."""
|
26
|
+
if self.action == 'list':
|
27
|
+
return TariffListSerializer
|
28
|
+
return TariffSerializer
|
29
|
+
|
30
|
+
def get_queryset(self):
|
31
|
+
"""Order by price."""
|
32
|
+
return super().get_queryset().order_by('monthly_price')
|
33
|
+
|
34
|
+
@action(detail=False, methods=['get'])
|
35
|
+
def free(self, request):
|
36
|
+
"""Get free tariffs."""
|
37
|
+
free_tariffs = self.get_queryset().filter(monthly_price=0)
|
38
|
+
serializer = TariffListSerializer(free_tariffs, many=True)
|
39
|
+
return Response(serializer.data)
|
40
|
+
|
41
|
+
@action(detail=False, methods=['get'])
|
42
|
+
def paid(self, request):
|
43
|
+
"""Get paid tariffs."""
|
44
|
+
paid_tariffs = self.get_queryset().filter(monthly_price__gt=0)
|
45
|
+
serializer = TariffListSerializer(paid_tariffs, many=True)
|
46
|
+
return Response(serializer.data)
|
47
|
+
|
48
|
+
@action(detail=True, methods=['get'])
|
49
|
+
def endpoint_groups(self, request, pk=None):
|
50
|
+
"""Get endpoint groups for tariff."""
|
51
|
+
tariff = self.get_object()
|
52
|
+
endpoint_groups = TariffEndpointGroup.objects.filter(
|
53
|
+
tariff=tariff,
|
54
|
+
is_enabled=True
|
55
|
+
)
|
56
|
+
serializer = TariffEndpointGroupSerializer(endpoint_groups, many=True)
|
57
|
+
return Response(serializer.data)
|
58
|
+
|
59
|
+
|
60
|
+
class TariffEndpointGroupViewSet(viewsets.ReadOnlyModelViewSet):
|
61
|
+
"""Tariff Endpoint Group ViewSet: /tariff-endpoint-groups/"""
|
62
|
+
|
63
|
+
queryset = TariffEndpointGroup.objects.filter(is_enabled=True)
|
64
|
+
serializer_class = TariffEndpointGroupSerializer
|
65
|
+
permission_classes = [permissions.IsAuthenticated]
|
66
|
+
filter_backends = [DjangoFilterBackend]
|
67
|
+
filterset_fields = ['tariff', 'endpoint_group', 'is_enabled']
|
68
|
+
|
69
|
+
@action(detail=False, methods=['get'])
|
70
|
+
def by_tariff(self, request):
|
71
|
+
"""Get endpoint groups by tariff."""
|
72
|
+
tariff_id = request.query_params.get('tariff_id')
|
73
|
+
if not tariff_id:
|
74
|
+
return Response({'error': 'tariff_id parameter required'}, status=400)
|
75
|
+
|
76
|
+
groups = self.get_queryset().filter(tariff_id=tariff_id)
|
77
|
+
serializer = self.get_serializer(groups, many=True)
|
78
|
+
return Response(serializer.data)
|
79
|
+
|
80
|
+
@action(detail=False, methods=['get'])
|
81
|
+
def by_endpoint_group(self, request):
|
82
|
+
"""Get tariffs by endpoint group."""
|
83
|
+
endpoint_group_id = request.query_params.get('endpoint_group_id')
|
84
|
+
if not endpoint_group_id:
|
85
|
+
return Response({'error': 'endpoint_group_id parameter required'}, status=400)
|
86
|
+
|
87
|
+
groups = self.get_queryset().filter(endpoint_group_id=endpoint_group_id)
|
88
|
+
serializer = self.get_serializer(groups, many=True)
|
89
|
+
return Response(serializer.data)
|
90
|
+
|
91
|
+
|
92
|
+
# Generic views for specific use cases
|
93
|
+
class AvailableTariffsView(generics.ListAPIView):
|
94
|
+
"""Generic view to list available tariffs."""
|
95
|
+
|
96
|
+
serializer_class = TariffListSerializer
|
97
|
+
permission_classes = [permissions.IsAuthenticated]
|
98
|
+
|
99
|
+
def get_queryset(self):
|
100
|
+
"""Get active tariffs ordered by price."""
|
101
|
+
return Tariff.objects.filter(is_active=True).order_by('monthly_price')
|
102
|
+
|
103
|
+
|
104
|
+
class TariffComparisonView(generics.GenericAPIView):
|
105
|
+
"""Generic view to compare tariffs."""
|
106
|
+
|
107
|
+
serializer_class = TariffSerializer # For schema generation
|
108
|
+
permission_classes = [permissions.IsAuthenticated]
|
109
|
+
|
110
|
+
def get(self, request):
|
111
|
+
"""Get tariff comparison data."""
|
112
|
+
tariffs = Tariff.objects.filter(is_active=True).order_by('monthly_price')
|
113
|
+
|
114
|
+
comparison = []
|
115
|
+
for tariff in tariffs:
|
116
|
+
endpoint_groups_count = TariffEndpointGroup.objects.filter(
|
117
|
+
tariff=tariff,
|
118
|
+
is_enabled=True
|
119
|
+
).count()
|
120
|
+
|
121
|
+
comparison.append({
|
122
|
+
'id': tariff.id,
|
123
|
+
'name': tariff.name,
|
124
|
+
'display_name': tariff.display_name,
|
125
|
+
'monthly_price': tariff.monthly_price,
|
126
|
+
'request_limit': tariff.request_limit,
|
127
|
+
'is_free': tariff.is_free,
|
128
|
+
'endpoint_groups_count': endpoint_groups_count,
|
129
|
+
})
|
130
|
+
|
131
|
+
return Response(comparison)
|
django_cfg/core/config.py
CHANGED
@@ -158,6 +158,10 @@ class DjangoConfig(BaseModel):
|
|
158
158
|
default=False,
|
159
159
|
description="Enable django-cfg Maintenance application (multi-site maintenance mode with Cloudflare)",
|
160
160
|
)
|
161
|
+
enable_payments: bool = Field(
|
162
|
+
default=False,
|
163
|
+
description="Enable django-cfg Payments application (universal payment system, subscriptions, API keys, billing)",
|
164
|
+
)
|
161
165
|
|
162
166
|
# === URLs ===
|
163
167
|
site_url: str = Field(default="http://localhost:3000", description="Frontend site URL")
|
@@ -593,6 +597,8 @@ class DjangoConfig(BaseModel):
|
|
593
597
|
apps.append("django_cfg.apps.agents")
|
594
598
|
if self.enable_maintenance:
|
595
599
|
apps.append("django_cfg.apps.maintenance")
|
600
|
+
if self.enable_payments:
|
601
|
+
apps.append("django_cfg.apps.payments")
|
596
602
|
|
597
603
|
# Auto-enable tasks if needed
|
598
604
|
if self.should_enable_tasks():
|
django_cfg/models/revolution.py
CHANGED
@@ -93,6 +93,7 @@ class ExtendedRevolutionConfig(BaseDjangoRevolutionConfig):
|
|
93
93
|
knowbase_enabled = base_module.is_knowbase_enabled()
|
94
94
|
agents_enabled = base_module.is_agents_enabled()
|
95
95
|
tasks_enabled = base_module.should_enable_tasks()
|
96
|
+
payments_enabled = base_module.enable_payments()
|
96
97
|
|
97
98
|
# Add Support zone if enabled
|
98
99
|
default_support_zone = 'cfg_support'
|
@@ -177,6 +178,19 @@ class ExtendedRevolutionConfig(BaseDjangoRevolutionConfig):
|
|
177
178
|
auth_required=True,
|
178
179
|
version="v1",
|
179
180
|
)
|
181
|
+
|
182
|
+
# Add Payments zone if enabled
|
183
|
+
default_payments_zone = 'cfg_payments'
|
184
|
+
if payments_enabled and default_payments_zone not in zones:
|
185
|
+
zones[default_payments_zone] = ZoneConfig(
|
186
|
+
apps=["django_cfg.apps.payments"],
|
187
|
+
title="Payments API",
|
188
|
+
description="Payments, subscriptions, and billing API",
|
189
|
+
public=False,
|
190
|
+
auth_required=True,
|
191
|
+
version="v1",
|
192
|
+
)
|
193
|
+
|
180
194
|
except Exception:
|
181
195
|
pass
|
182
196
|
|
django_cfg/modules/base.py
CHANGED
@@ -175,6 +175,15 @@ class BaseCfgModule(ABC):
|
|
175
175
|
"""
|
176
176
|
return self._get_config_key('enable_maintenance', False)
|
177
177
|
|
178
|
+
def enable_payments(self) -> bool:
|
179
|
+
"""
|
180
|
+
Check if django-cfg Payments is enabled.
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
True if Payments is enabled, False otherwise
|
184
|
+
"""
|
185
|
+
return self._get_config_key('enable_payments', False)
|
186
|
+
|
178
187
|
|
179
188
|
# Export the base class
|
180
189
|
__all__ = [
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-cfg
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.22
|
4
4
|
Summary: 🚀 Next-gen Django configuration: type-safety, AI features, blazing-fast setup, and automated best practices — all in one.
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
6
6
|
Project-URL: Documentation, https://docs.djangocfg.com
|
@@ -1,5 +1,5 @@
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
django_cfg/__init__.py,sha256=
|
2
|
+
django_cfg/__init__.py,sha256=Xr1TpxIGCz8PGP-3n7OZqU3lCSjl9yNS4gakIXX7gqo,1631
|
3
3
|
django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
|
4
4
|
django_cfg/config.py,sha256=ME-JKaVzcdmaGhuc1YTkEWoMKSaUasNf1SBlNz-NfrM,1399
|
5
5
|
django_cfg/urls.py,sha256=bpRFjMonQuk4UCUMxx4ueBX3YDNB7HXKFwEghQ3KR3o,793
|
@@ -246,7 +246,7 @@ django_cfg/apps/newsletter/admin_filters.py,sha256=lPMtHULDOisTsN9_2BMoGu23ICkFE
|
|
246
246
|
django_cfg/apps/newsletter/apps.py,sha256=TXrULqE2HbmEIHaLomw7z9bkC19le1rKwlUWhK1xrVo,566
|
247
247
|
django_cfg/apps/newsletter/models.py,sha256=tc5IEUJZUAdYbQOhT_XvfiGL86FIb5LvPAuq7TOPrAM,8930
|
248
248
|
django_cfg/apps/newsletter/serializers.py,sha256=Nse8ZmBDNdhzfgZu53LNp_JOR327rI1fSHpgdJtCoV8,4133
|
249
|
-
django_cfg/apps/newsletter/signals.py,sha256=
|
249
|
+
django_cfg/apps/newsletter/signals.py,sha256=kbjSvnMIy4kwmE_r02YFMneCrHy0oRuc_spjki976dM,3003
|
250
250
|
django_cfg/apps/newsletter/urls.py,sha256=Zfbgj0NZ06dMu503JOn4GGgmKpQ3tOjTIyUmEslaJdE,1609
|
251
251
|
django_cfg/apps/newsletter/admin/__init__.py,sha256=dXCym_3qp9HxT0sUV_LBGgEkKv1k_Mj3a0TTZYnH2yI,290
|
252
252
|
django_cfg/apps/newsletter/admin/filters.py,sha256=lPMtHULDOisTsN9_2BMoGu23ICkFEJDZst5Fhj5Sv5c,3675
|
@@ -267,6 +267,47 @@ django_cfg/apps/newsletter/views/emails.py,sha256=w-W_egdfFbsbgfW8WjqT0DBJl596lJ
|
|
267
267
|
django_cfg/apps/newsletter/views/newsletters.py,sha256=gb8skjDYxHd8R2DAVUhSe6z4lnKDCS_e9zbyyHok4CI,1370
|
268
268
|
django_cfg/apps/newsletter/views/subscriptions.py,sha256=marHLbRT6a6e8tMjKVtKG_0K6G_gcjKCU9LmMGihKwg,4774
|
269
269
|
django_cfg/apps/newsletter/views/tracking.py,sha256=EJlAltpASzc_0nslVPJt-6mnIqI_QCEmz3MQEGPKzmU,2481
|
270
|
+
django_cfg/apps/payments/__init__.py,sha256=Qb8viLJBKCclV9uuoXjxWXeDevXlbdJ76mV8g5hK7tk,237
|
271
|
+
django_cfg/apps/payments/apps.py,sha256=FZgbLQFMeOJPs_892bM6X-3Edt3YPD2CP50HMekH7Sk,551
|
272
|
+
django_cfg/apps/payments/urls.py,sha256=aJ4hC3quZvn1c_JJHVWd305DLFAHhtkBHGS-i7XfYCw,3508
|
273
|
+
django_cfg/apps/payments/managers/__init__.py,sha256=uYiETCw-K5GXQyAwpcOa2Qxzof8sbtTNc_SghyW45YU,672
|
274
|
+
django_cfg/apps/payments/managers/api_key_manager.py,sha256=xhViRKCLC_eR68hankMZQTcbzMgiRJdyr8KYKk01_dQ,928
|
275
|
+
django_cfg/apps/payments/managers/balance_manager.py,sha256=Sm_EETlHO7LtlrWIqF65vFPS4FpPSNYo5oTgQwTFAZ4,13279
|
276
|
+
django_cfg/apps/payments/managers/currency_manager.py,sha256=-iq_Nhd3cj_n1aUzObBIMhRRHtpOv50Z0phdwOcKv6w,876
|
277
|
+
django_cfg/apps/payments/managers/payment_manager.py,sha256=udiTFIIVXsjqmlwzFwcT0ZTkYJZ9HVOSLLFTvAodY3w,1467
|
278
|
+
django_cfg/apps/payments/managers/subscription_manager.py,sha256=LHrw5INc1nSqfpw76bn7W2poEA-29FXfF5uhaxmq7l8,968
|
279
|
+
django_cfg/apps/payments/managers/tariff_manager.py,sha256=Kor_0ApvROcH0UKlp1-9rYChtHWDeOFgUc1GG3IepxY,799
|
280
|
+
django_cfg/apps/payments/middleware/__init__.py,sha256=ryn3emWBq15-kE8eJxX19gd-eTrk7JlpXqV2Cg8N_Wg,294
|
281
|
+
django_cfg/apps/payments/migrations/0001_initial.py,sha256=u4as4J5YycFDAs4acQz9girjg2Hp9uTn8Hy7WZEUI6U,39911
|
282
|
+
django_cfg/apps/payments/migrations/__init__.py,sha256=OHlzxEGDJYKZz82orZXnh77xy-Okv6FcG3EKMfg9LzU,21
|
283
|
+
django_cfg/apps/payments/models/__init__.py,sha256=UV1xEm1osEkWA5XmvBJSS5YU2fddqkvSum6-_15IBH0,882
|
284
|
+
django_cfg/apps/payments/models/api_keys.py,sha256=6V9hOSPQT27qEWaygWrtmQPqCeZpgHZv1KAfsgXnS-Q,2476
|
285
|
+
django_cfg/apps/payments/models/balance.py,sha256=wn63yjOVIbLbirAjKLAZm03ooXeOe3yOqDGVr_hiLZY,7026
|
286
|
+
django_cfg/apps/payments/models/base.py,sha256=T8mQKFXlpLc05xZDmSdOfrOAc8s_mGnKqoSgEhaY9Kk,349
|
287
|
+
django_cfg/apps/payments/models/currencies.py,sha256=lz6axNRmfoQa3od-EV78vbZQv3vh2FznxHzGBi20qIU,4106
|
288
|
+
django_cfg/apps/payments/models/events.py,sha256=xIrAj8nyBq0NTc0YA-SolJSxG9Mr3ut4gyARsY-QMRM,2364
|
289
|
+
django_cfg/apps/payments/models/payments.py,sha256=7CLgRFWPg8K5ajjXjG5_xy8wpACzTAMxOzOufbyIaAI,9540
|
290
|
+
django_cfg/apps/payments/models/subscriptions.py,sha256=RuINi31j2DIhI_V_-HNvhdq8qa-woAGrUDeWOJOq3so,8167
|
291
|
+
django_cfg/apps/payments/models/tariffs.py,sha256=nqM3RPtu4itAHRvT6jAgZgQEdEIN1KgKnEljkaHGFDc,2749
|
292
|
+
django_cfg/apps/payments/serializers/__init__.py,sha256=4l7Tu1NXztXtqceVbC3OxtWJir4n3_dFpVEgRgQ9J4k,1416
|
293
|
+
django_cfg/apps/payments/serializers/api_keys.py,sha256=5qG3PEwIVfoNSC6XpbOBbC3l4umgld3kPJC5l_vsYV0,1396
|
294
|
+
django_cfg/apps/payments/serializers/balance.py,sha256=QzR1MTRQ_4uTJBRefRB6X-z-0QOkOuHmCyNTojYj_8w,2025
|
295
|
+
django_cfg/apps/payments/serializers/currencies.py,sha256=SskSl3rjZBxxLnY_zsaJiFCGfbkz9WyWFHLHiJbbTIM,1821
|
296
|
+
django_cfg/apps/payments/serializers/payments.py,sha256=BDhcD9DCea50Cz1PumLNVntgH40dOR55NzCXv4I0YOQ,2258
|
297
|
+
django_cfg/apps/payments/serializers/subscriptions.py,sha256=16OileC0yxM90ERK3Uo5Jqe9DeyNf3tBwK-ZqDtEbPw,2795
|
298
|
+
django_cfg/apps/payments/serializers/tariffs.py,sha256=54N5tFpc5NBIMyXuN7Re6pDNoQLJc2yZBIb5uVRhn-I,1698
|
299
|
+
django_cfg/apps/payments/services/__init__.py,sha256=GigyUFB3pT1qpN1uV1-LZUXVQFPMNkbjh6i6sV4iZsw,280
|
300
|
+
django_cfg/apps/payments/services/base.py,sha256=2xX42CnTDObNNc0atXwEWpozI1fAbh7Y4uOgBAoHGzY,2302
|
301
|
+
django_cfg/apps/payments/services/nowpayments.py,sha256=K31_KFM9KvkKidbbgYBgnIwg7I49dFBTSkD9upyYkO8,2878
|
302
|
+
django_cfg/apps/payments/services/providers.py,sha256=7t335fOtBwwqMa_HLpvuJ-dW3Zyi8AjkKApSurhidTY,2337
|
303
|
+
django_cfg/apps/payments/services/redis_service.py,sha256=xarjV3ndkuXXTHy0N9yuLfEvcg0it4pNNQ-yghNwIqo,8193
|
304
|
+
django_cfg/apps/payments/views/__init__.py,sha256=00HRO1sDErjnFq2dedFAjBIyIhZZyZmv4DK98lHmx2Q,1613
|
305
|
+
django_cfg/apps/payments/views/api_key_views.py,sha256=fG68roqOF9FShde68Qp_CBf0KJuf1chwK41RU0VqUJk,5542
|
306
|
+
django_cfg/apps/payments/views/balance_views.py,sha256=rA_OJFizP_UBBr8OdWLQZQ51qlPFYoeJCYUzNXSw4S0,2509
|
307
|
+
django_cfg/apps/payments/views/currency_views.py,sha256=B1rjOKDV3KEYRQI6NuPEyEGF8VNumaraoZdI8Qqdu6s,4096
|
308
|
+
django_cfg/apps/payments/views/payment_views.py,sha256=zfdHiHJhGby6KHpTe5S9UVeC-pFrJD-RTMhHhkSCl5s,4122
|
309
|
+
django_cfg/apps/payments/views/subscription_views.py,sha256=WqBDsaPUQGP6ZCmbheIdRupy-qESPpdj8IgKkaLp3V8,5005
|
310
|
+
django_cfg/apps/payments/views/tariff_views.py,sha256=i0uOg6SBk_1sct2M2iwKMw-5YHMr5mhVvm_ljpFRwPQ,4907
|
270
311
|
django_cfg/apps/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
271
312
|
django_cfg/apps/support/admin.py,sha256=-fcegtqoCcwPiBXwyNLMftcYCTTbTRRBpWItyeNNkDY,8827
|
272
313
|
django_cfg/apps/support/admin_filters.py,sha256=ZpKtetRxppRAMwIr-pwDbXAyh7qouDfTCEZoo1YJfFs,2179
|
@@ -324,7 +365,7 @@ django_cfg/cli/commands/__init__.py,sha256=EKLXDAx-QttnGmdjsmVANAfhxWplxl2V_2I0S
|
|
324
365
|
django_cfg/cli/commands/create_project.py,sha256=iuf965j8Yg7zxHcPb0GtFHEj73CYXC45ZJRmd6RbA9E,21025
|
325
366
|
django_cfg/cli/commands/info.py,sha256=o4S1xPJSHv2oEVqmH0X9RTF5f-8Wy9579yHkyd_PC3E,4923
|
326
367
|
django_cfg/core/__init__.py,sha256=eVK57qFOok9kTeHoNEMQ1BplkUOaQ7NB9kP9eQK1vg0,358
|
327
|
-
django_cfg/core/config.py,sha256=
|
368
|
+
django_cfg/core/config.py,sha256=TS78BFOtzaErZMdEQgZvY2cnMq5PGaC74rOR8dttRuY,28404
|
328
369
|
django_cfg/core/environment.py,sha256=MAoEPqIPsLVhSANT2Bz4nnus2wmbMW0RCOQxhQfDrDc,9106
|
329
370
|
django_cfg/core/exceptions.py,sha256=RTQEoU3PfR8lqqNNv5ayd_HY2yJLs3eioqUy8VM6AG4,10378
|
330
371
|
django_cfg/core/generation.py,sha256=Oa9bnEPsxwEaa0RP71zNl_z2p9NNljHnoCTeRu72X-0,25421
|
@@ -373,12 +414,12 @@ django_cfg/models/jwt.py,sha256=3R_dpLmVZIcH4zdtwA4qKnuCB8RZQACrgsbbgWY2q4c,9025
|
|
373
414
|
django_cfg/models/limits.py,sha256=nUfvyPykwKR38ZOlIFqlNGRmfV8RO5hiUrhDH6FCHJw,7449
|
374
415
|
django_cfg/models/logging.py,sha256=4vZF-G9rPmXMxxtUx_ad7Esvgbe8a_5Dl692Yg0fL4A,10636
|
375
416
|
django_cfg/models/ngrok.py,sha256=MVgcKWx0DRSW0QcwhiSx2vVwTSG49vbVrzPkZqDK-zw,3575
|
376
|
-
django_cfg/models/revolution.py,sha256=
|
417
|
+
django_cfg/models/revolution.py,sha256=7Rj-EYvNK4EEttMqtE95ejLWzvucz8rFelWNBNTKk3s,8130
|
377
418
|
django_cfg/models/security.py,sha256=Xv19ZVOIenB_-f0wB6fm-Ap4j9kA43bSFaT2XenpSqc,4685
|
378
419
|
django_cfg/models/services.py,sha256=fj9JjrJFrlL4DMnMbx_D8JiiZpz4E5uBqmhquAxau7c,13159
|
379
420
|
django_cfg/models/tasks.py,sha256=2T3apcUFf8zevYJmapbFrh6bWv5PTJPeXR6Bc5vlj4c,15524
|
380
421
|
django_cfg/modules/__init__.py,sha256=Ip9WMpzImEwIAywpFwU056_v0O9oIGG7nCT1YSArxkw,316
|
381
|
-
django_cfg/modules/base.py,sha256=
|
422
|
+
django_cfg/modules/base.py,sha256=_agj4rt9CrEfrDVL29A1vgFYIwZXXg6Wf7aCNFFdXo0,5732
|
382
423
|
django_cfg/modules/django_email.py,sha256=2XXlIKzD6Jao3CT4_zIE2eaM9Cc9ROA1tjp2bJ9z5Lo,16592
|
383
424
|
django_cfg/modules/django_health.py,sha256=7QzuQ6WyjWYj6lecd4auwRvEyrMUL7N6hiAp-tLyoY4,8923
|
384
425
|
django_cfg/modules/django_logger.py,sha256=3oP9jev0lOcFUJ1tYcpbFnK524zIGA2xIOrrAiTwpb8,6331
|
@@ -503,8 +544,8 @@ django_cfg/utils/path_resolution.py,sha256=C9As6p4Q9l3VeoVkFDRPQWGrzAWf8O8UxLVka
|
|
503
544
|
django_cfg/utils/smart_defaults.py,sha256=MxbUZwn_xbh48li7uLI6W4D9WCD2P2WO48dv85Fra5E,23057
|
504
545
|
django_cfg/utils/toolkit.py,sha256=Td8_iXNaftonF_xdZP4Y3uO65nuA_4_zditn5Q_Pfcw,23310
|
505
546
|
django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaNpucs,4800
|
506
|
-
django_cfg-1.2.
|
507
|
-
django_cfg-1.2.
|
508
|
-
django_cfg-1.2.
|
509
|
-
django_cfg-1.2.
|
510
|
-
django_cfg-1.2.
|
547
|
+
django_cfg-1.2.22.dist-info/METADATA,sha256=VGD-7Y0LSzV1UdNyMYvkZ2w8WIO14V_uk1T0Xe0WbIg,38434
|
548
|
+
django_cfg-1.2.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
549
|
+
django_cfg-1.2.22.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
550
|
+
django_cfg-1.2.22.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
551
|
+
django_cfg-1.2.22.dist-info/RECORD,,
|
File without changes
|