django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -10
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
- django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
- django_cfg/apps/payments/management/commands/test_providers.py +434 -0
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +153 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +172 -148
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -285
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
- django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
- django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
- django_cfg/apps/payments/services/cache_service/keys.py +49 -0
- django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
- django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +371 -465
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +40 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +227 -570
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -146
- django_cfg/apps/payments/managers/__init__.py +0 -23
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -63
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -461
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -63
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -122
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -66
- django_cfg/core/integration.py +0 -160
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,429 @@
|
|
1
|
+
"""
|
2
|
+
Subscription serializers for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
DRF serializers for subscription operations with service integration.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from rest_framework import serializers
|
8
|
+
from typing import Dict, Any
|
9
|
+
from django.contrib.auth import get_user_model
|
10
|
+
|
11
|
+
from ...models import Subscription, EndpointGroup, Tariff
|
12
|
+
from ...services import get_subscription_service, SubscriptionCreateRequest, SubscriptionUpdateRequest
|
13
|
+
from django_cfg.modules.django_logger import get_logger
|
14
|
+
|
15
|
+
User = get_user_model()
|
16
|
+
logger = get_logger("subscription_serializers")
|
17
|
+
|
18
|
+
|
19
|
+
class EndpointGroupSerializer(serializers.ModelSerializer):
|
20
|
+
"""
|
21
|
+
Endpoint group serializer for API access management.
|
22
|
+
|
23
|
+
Used for subscription endpoint group configuration.
|
24
|
+
"""
|
25
|
+
|
26
|
+
class Meta:
|
27
|
+
model = EndpointGroup
|
28
|
+
fields = [
|
29
|
+
'id',
|
30
|
+
'name',
|
31
|
+
'description',
|
32
|
+
'is_active',
|
33
|
+
'created_at',
|
34
|
+
'updated_at',
|
35
|
+
]
|
36
|
+
read_only_fields = fields
|
37
|
+
|
38
|
+
|
39
|
+
class TariffSerializer(serializers.ModelSerializer):
|
40
|
+
"""
|
41
|
+
Tariff serializer for subscription pricing.
|
42
|
+
|
43
|
+
Used for tariff information and selection.
|
44
|
+
"""
|
45
|
+
|
46
|
+
endpoint_groups = EndpointGroupSerializer(many=True, read_only=True)
|
47
|
+
endpoint_groups_count = serializers.IntegerField(source='endpoint_groups.count', read_only=True)
|
48
|
+
|
49
|
+
class Meta:
|
50
|
+
model = Tariff
|
51
|
+
fields = [
|
52
|
+
'id',
|
53
|
+
'name',
|
54
|
+
'description',
|
55
|
+
'monthly_price',
|
56
|
+
'requests_per_month',
|
57
|
+
'requests_per_minute',
|
58
|
+
'is_active',
|
59
|
+
'endpoint_groups',
|
60
|
+
'endpoint_groups_count',
|
61
|
+
'created_at',
|
62
|
+
'updated_at',
|
63
|
+
]
|
64
|
+
read_only_fields = fields
|
65
|
+
|
66
|
+
|
67
|
+
class SubscriptionListSerializer(serializers.ModelSerializer):
|
68
|
+
"""
|
69
|
+
Lightweight subscription serializer for lists.
|
70
|
+
|
71
|
+
Optimized for subscription lists with minimal data.
|
72
|
+
"""
|
73
|
+
|
74
|
+
user = serializers.StringRelatedField(read_only=True)
|
75
|
+
tariff_name = serializers.CharField(source='tariff.name', read_only=True)
|
76
|
+
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
77
|
+
is_active = serializers.BooleanField(source='is_active', read_only=True)
|
78
|
+
is_expired = serializers.BooleanField(source='is_expired', read_only=True)
|
79
|
+
|
80
|
+
class Meta:
|
81
|
+
model = Subscription
|
82
|
+
fields = [
|
83
|
+
'id',
|
84
|
+
'user',
|
85
|
+
'tariff_name',
|
86
|
+
'status',
|
87
|
+
'status_display',
|
88
|
+
'is_active',
|
89
|
+
'is_expired',
|
90
|
+
'expires_at',
|
91
|
+
'created_at',
|
92
|
+
]
|
93
|
+
read_only_fields = fields
|
94
|
+
|
95
|
+
|
96
|
+
class SubscriptionSerializer(serializers.ModelSerializer):
|
97
|
+
"""
|
98
|
+
Complete subscription serializer with full details.
|
99
|
+
|
100
|
+
Used for subscription detail views and updates.
|
101
|
+
"""
|
102
|
+
|
103
|
+
user = serializers.StringRelatedField(read_only=True)
|
104
|
+
tariff = TariffSerializer(read_only=True)
|
105
|
+
endpoint_group = EndpointGroupSerializer(read_only=True)
|
106
|
+
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
107
|
+
status_color = serializers.CharField(source='status_color', read_only=True)
|
108
|
+
|
109
|
+
# Status check methods
|
110
|
+
is_active = serializers.BooleanField(source='is_active', read_only=True)
|
111
|
+
is_expired = serializers.BooleanField(source='is_expired', read_only=True)
|
112
|
+
is_trial = serializers.BooleanField(source='is_trial', read_only=True)
|
113
|
+
can_be_renewed = serializers.BooleanField(source='can_be_renewed', read_only=True)
|
114
|
+
can_be_cancelled = serializers.BooleanField(source='can_be_cancelled', read_only=True)
|
115
|
+
|
116
|
+
# Usage statistics
|
117
|
+
usage_percentage = serializers.FloatField(source='usage_percentage', read_only=True)
|
118
|
+
requests_remaining = serializers.IntegerField(source='requests_remaining', read_only=True)
|
119
|
+
|
120
|
+
class Meta:
|
121
|
+
model = Subscription
|
122
|
+
fields = [
|
123
|
+
'id',
|
124
|
+
'user',
|
125
|
+
'tariff',
|
126
|
+
'endpoint_group',
|
127
|
+
'status',
|
128
|
+
'status_display',
|
129
|
+
'status_color',
|
130
|
+
'tier',
|
131
|
+
'total_requests',
|
132
|
+
'requests_used',
|
133
|
+
'requests_remaining',
|
134
|
+
'usage_percentage',
|
135
|
+
'last_request_at',
|
136
|
+
'expires_at',
|
137
|
+
'is_active',
|
138
|
+
'is_expired',
|
139
|
+
'is_trial',
|
140
|
+
'can_be_renewed',
|
141
|
+
'can_be_cancelled',
|
142
|
+
'created_at',
|
143
|
+
'updated_at',
|
144
|
+
]
|
145
|
+
read_only_fields = [
|
146
|
+
'id',
|
147
|
+
'user',
|
148
|
+
'total_requests',
|
149
|
+
'requests_used',
|
150
|
+
'requests_remaining',
|
151
|
+
'usage_percentage',
|
152
|
+
'last_request_at',
|
153
|
+
'created_at',
|
154
|
+
'updated_at',
|
155
|
+
'status_display',
|
156
|
+
'status_color',
|
157
|
+
'is_active',
|
158
|
+
'is_expired',
|
159
|
+
'is_trial',
|
160
|
+
'can_be_renewed',
|
161
|
+
'can_be_cancelled',
|
162
|
+
]
|
163
|
+
|
164
|
+
|
165
|
+
class SubscriptionCreateSerializer(serializers.Serializer):
|
166
|
+
"""
|
167
|
+
Subscription creation serializer with service integration.
|
168
|
+
|
169
|
+
Validates input and delegates to SubscriptionService.
|
170
|
+
"""
|
171
|
+
|
172
|
+
tariff_id = serializers.IntegerField(
|
173
|
+
min_value=1,
|
174
|
+
help_text="Tariff ID for the subscription"
|
175
|
+
)
|
176
|
+
endpoint_group_id = serializers.IntegerField(
|
177
|
+
required=False,
|
178
|
+
allow_null=True,
|
179
|
+
min_value=1,
|
180
|
+
help_text="Endpoint group ID (optional)"
|
181
|
+
)
|
182
|
+
duration_days = serializers.IntegerField(
|
183
|
+
default=30,
|
184
|
+
min_value=1,
|
185
|
+
max_value=365,
|
186
|
+
help_text="Subscription duration in days"
|
187
|
+
)
|
188
|
+
|
189
|
+
def validate_tariff_id(self, value: int) -> int:
|
190
|
+
"""Validate tariff exists and is active."""
|
191
|
+
if not Tariff.objects.filter(id=value, is_active=True).exists():
|
192
|
+
raise serializers.ValidationError(f"Tariff {value} not found or inactive")
|
193
|
+
return value
|
194
|
+
|
195
|
+
def validate_endpoint_group_id(self, value: int) -> int:
|
196
|
+
"""Validate endpoint group exists and is active."""
|
197
|
+
if value and not EndpointGroup.objects.filter(id=value, is_active=True).exists():
|
198
|
+
raise serializers.ValidationError(f"Endpoint group {value} not found or inactive")
|
199
|
+
return value
|
200
|
+
|
201
|
+
def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
|
202
|
+
"""Validate subscription creation data."""
|
203
|
+
try:
|
204
|
+
# Get user from context
|
205
|
+
user_id = self.context.get('user_pk') or self.context['request'].user.id
|
206
|
+
|
207
|
+
# Create Pydantic request for validation
|
208
|
+
request = SubscriptionCreateRequest(
|
209
|
+
user_id=user_id,
|
210
|
+
**attrs
|
211
|
+
)
|
212
|
+
|
213
|
+
# Store validated request for create method
|
214
|
+
self._validated_request = request
|
215
|
+
return attrs
|
216
|
+
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(f"Subscription validation failed: {e}")
|
219
|
+
raise serializers.ValidationError(f"Invalid subscription data: {e}")
|
220
|
+
|
221
|
+
def create(self, validated_data: Dict[str, Any]) -> Subscription:
|
222
|
+
"""Create subscription using SubscriptionService."""
|
223
|
+
try:
|
224
|
+
subscription_service = get_subscription_service()
|
225
|
+
result = subscription_service.create_subscription(self._validated_request)
|
226
|
+
|
227
|
+
if result.success:
|
228
|
+
# Get the created subscription from database
|
229
|
+
subscription = Subscription.objects.get(id=result.subscription_id)
|
230
|
+
|
231
|
+
logger.info(f"Subscription created successfully", extra={
|
232
|
+
'subscription_id': result.subscription_id,
|
233
|
+
'user_id': self._validated_request.user_id,
|
234
|
+
'tariff_id': self._validated_request.tariff_id
|
235
|
+
})
|
236
|
+
|
237
|
+
return subscription
|
238
|
+
else:
|
239
|
+
logger.error(f"Subscription creation failed: {result.message}")
|
240
|
+
raise serializers.ValidationError(result.message)
|
241
|
+
|
242
|
+
except Exception as e:
|
243
|
+
logger.error(f"Subscription creation error: {e}")
|
244
|
+
raise serializers.ValidationError(f"Subscription creation failed: {e}")
|
245
|
+
|
246
|
+
def to_representation(self, instance: Subscription) -> Dict[str, Any]:
|
247
|
+
"""Return full subscription data after creation."""
|
248
|
+
return SubscriptionSerializer(instance, context=self.context).data
|
249
|
+
|
250
|
+
|
251
|
+
class SubscriptionUpdateSerializer(serializers.Serializer):
|
252
|
+
"""
|
253
|
+
Subscription update serializer with service integration.
|
254
|
+
|
255
|
+
Handles subscription modifications through SubscriptionService.
|
256
|
+
"""
|
257
|
+
|
258
|
+
action = serializers.ChoiceField(
|
259
|
+
choices=[
|
260
|
+
('activate', 'Activate'),
|
261
|
+
('suspend', 'Suspend'),
|
262
|
+
('cancel', 'Cancel'),
|
263
|
+
('renew', 'Renew'),
|
264
|
+
],
|
265
|
+
help_text="Action to perform on subscription"
|
266
|
+
)
|
267
|
+
reason = serializers.CharField(
|
268
|
+
required=False,
|
269
|
+
allow_blank=True,
|
270
|
+
max_length=500,
|
271
|
+
help_text="Reason for the action"
|
272
|
+
)
|
273
|
+
duration_days = serializers.IntegerField(
|
274
|
+
required=False,
|
275
|
+
min_value=1,
|
276
|
+
max_value=365,
|
277
|
+
help_text="Duration for renewal (required for renew action)"
|
278
|
+
)
|
279
|
+
|
280
|
+
def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
|
281
|
+
"""Validate subscription update data."""
|
282
|
+
action = attrs.get('action')
|
283
|
+
duration_days = attrs.get('duration_days')
|
284
|
+
|
285
|
+
if action == 'renew' and not duration_days:
|
286
|
+
raise serializers.ValidationError("duration_days is required for renew action")
|
287
|
+
|
288
|
+
return attrs
|
289
|
+
|
290
|
+
def save(self) -> Dict[str, Any]:
|
291
|
+
"""Update subscription using SubscriptionService."""
|
292
|
+
try:
|
293
|
+
subscription_id = self.context.get('subscription_id')
|
294
|
+
if not subscription_id:
|
295
|
+
raise serializers.ValidationError("Subscription ID is required")
|
296
|
+
|
297
|
+
subscription_service = get_subscription_service()
|
298
|
+
action = self.validated_data['action']
|
299
|
+
reason = self.validated_data.get('reason')
|
300
|
+
duration_days = self.validated_data.get('duration_days')
|
301
|
+
|
302
|
+
# Call appropriate service method based on action
|
303
|
+
if action == 'activate':
|
304
|
+
result = subscription_service.activate_subscription(subscription_id)
|
305
|
+
elif action == 'suspend':
|
306
|
+
result = subscription_service.suspend_subscription(subscription_id, reason)
|
307
|
+
elif action == 'cancel':
|
308
|
+
result = subscription_service.cancel_subscription(subscription_id, reason)
|
309
|
+
elif action == 'renew':
|
310
|
+
result = subscription_service.renew_subscription(subscription_id, duration_days)
|
311
|
+
else:
|
312
|
+
raise serializers.ValidationError(f"Unknown action: {action}")
|
313
|
+
|
314
|
+
if result.success:
|
315
|
+
# Get updated subscription
|
316
|
+
subscription = Subscription.objects.get(id=result.subscription_id)
|
317
|
+
|
318
|
+
return {
|
319
|
+
'success': True,
|
320
|
+
'message': result.message,
|
321
|
+
'subscription': SubscriptionSerializer(subscription, context=self.context).data
|
322
|
+
}
|
323
|
+
else:
|
324
|
+
return {
|
325
|
+
'success': False,
|
326
|
+
'error': result.message,
|
327
|
+
'error_code': result.error_code
|
328
|
+
}
|
329
|
+
|
330
|
+
except Exception as e:
|
331
|
+
logger.error(f"Subscription update error: {e}")
|
332
|
+
return {
|
333
|
+
'success': False,
|
334
|
+
'error': f"Subscription update failed: {e}",
|
335
|
+
'error_code': 'subscription_update_error'
|
336
|
+
}
|
337
|
+
|
338
|
+
|
339
|
+
class SubscriptionUsageSerializer(serializers.Serializer):
|
340
|
+
"""
|
341
|
+
Subscription usage tracking serializer.
|
342
|
+
|
343
|
+
Used for incrementing usage counters.
|
344
|
+
"""
|
345
|
+
|
346
|
+
endpoint = serializers.CharField(
|
347
|
+
required=False,
|
348
|
+
allow_blank=True,
|
349
|
+
max_length=200,
|
350
|
+
help_text="API endpoint used"
|
351
|
+
)
|
352
|
+
|
353
|
+
def save(self) -> Dict[str, Any]:
|
354
|
+
"""Increment subscription usage using SubscriptionService."""
|
355
|
+
try:
|
356
|
+
subscription_id = self.context.get('subscription_id')
|
357
|
+
if not subscription_id:
|
358
|
+
raise serializers.ValidationError("Subscription ID is required")
|
359
|
+
|
360
|
+
subscription_service = get_subscription_service()
|
361
|
+
result = subscription_service.increment_usage(
|
362
|
+
subscription_id=subscription_id,
|
363
|
+
endpoint=self.validated_data.get('endpoint')
|
364
|
+
)
|
365
|
+
|
366
|
+
if result.success:
|
367
|
+
return {
|
368
|
+
'success': True,
|
369
|
+
'message': result.message,
|
370
|
+
'usage': result.data
|
371
|
+
}
|
372
|
+
else:
|
373
|
+
return {
|
374
|
+
'success': False,
|
375
|
+
'error': result.message,
|
376
|
+
'error_code': result.error_code
|
377
|
+
}
|
378
|
+
|
379
|
+
except Exception as e:
|
380
|
+
logger.error(f"Usage increment error: {e}")
|
381
|
+
return {
|
382
|
+
'success': False,
|
383
|
+
'error': f"Usage increment failed: {e}",
|
384
|
+
'error_code': 'usage_increment_error'
|
385
|
+
}
|
386
|
+
|
387
|
+
|
388
|
+
class SubscriptionStatsSerializer(serializers.Serializer):
|
389
|
+
"""
|
390
|
+
Subscription statistics serializer.
|
391
|
+
|
392
|
+
Used for subscription analytics and reporting.
|
393
|
+
"""
|
394
|
+
|
395
|
+
days = serializers.IntegerField(
|
396
|
+
default=30,
|
397
|
+
min_value=1,
|
398
|
+
max_value=365,
|
399
|
+
help_text="Number of days to analyze"
|
400
|
+
)
|
401
|
+
|
402
|
+
def save(self) -> Dict[str, Any]:
|
403
|
+
"""Get subscription statistics using SubscriptionService."""
|
404
|
+
try:
|
405
|
+
subscription_service = get_subscription_service()
|
406
|
+
result = subscription_service.get_subscription_stats(
|
407
|
+
days=self.validated_data['days']
|
408
|
+
)
|
409
|
+
|
410
|
+
if result.success:
|
411
|
+
return {
|
412
|
+
'success': True,
|
413
|
+
'stats': result.data,
|
414
|
+
'message': result.message
|
415
|
+
}
|
416
|
+
else:
|
417
|
+
return {
|
418
|
+
'success': False,
|
419
|
+
'error': result.message,
|
420
|
+
'error_code': result.error_code
|
421
|
+
}
|
422
|
+
|
423
|
+
except Exception as e:
|
424
|
+
logger.error(f"Subscription stats error: {e}")
|
425
|
+
return {
|
426
|
+
'success': False,
|
427
|
+
'error': f"Stats generation failed: {e}",
|
428
|
+
'error_code': 'stats_error'
|
429
|
+
}
|
@@ -0,0 +1,137 @@
|
|
1
|
+
"""
|
2
|
+
Webhook serializers for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
DRF serializers for webhook endpoints and data validation.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from rest_framework import serializers
|
8
|
+
from django_cfg.modules.django_logger import get_logger
|
9
|
+
|
10
|
+
logger = get_logger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class WebhookSerializer(serializers.Serializer):
|
14
|
+
"""
|
15
|
+
Generic webhook serializer.
|
16
|
+
|
17
|
+
Base serializer for all webhook types.
|
18
|
+
"""
|
19
|
+
|
20
|
+
provider = serializers.CharField(max_length=50, help_text="Payment provider name")
|
21
|
+
success = serializers.BooleanField(help_text="Processing success status")
|
22
|
+
message = serializers.CharField(max_length=500, help_text="Processing message")
|
23
|
+
|
24
|
+
class Meta:
|
25
|
+
fields = ['provider', 'success', 'message']
|
26
|
+
|
27
|
+
|
28
|
+
class WebhookDataSerializer(serializers.Serializer):
|
29
|
+
"""
|
30
|
+
Serializer for incoming webhook data.
|
31
|
+
|
32
|
+
Generic webhook data structure for all providers.
|
33
|
+
"""
|
34
|
+
|
35
|
+
provider = serializers.CharField(max_length=50, help_text="Payment provider name")
|
36
|
+
payment_id = serializers.CharField(max_length=256, help_text="Provider payment ID")
|
37
|
+
status = serializers.CharField(max_length=50, help_text="Payment status")
|
38
|
+
amount = serializers.DecimalField(max_digits=20, decimal_places=8, required=False, help_text="Payment amount")
|
39
|
+
currency = serializers.CharField(max_length=10, required=False, help_text="Payment currency")
|
40
|
+
transaction_hash = serializers.CharField(max_length=256, required=False, help_text="Blockchain transaction hash")
|
41
|
+
confirmations = serializers.IntegerField(required=False, help_text="Number of confirmations")
|
42
|
+
|
43
|
+
# Raw webhook data
|
44
|
+
raw_data = serializers.JSONField(help_text="Raw webhook payload")
|
45
|
+
|
46
|
+
class Meta:
|
47
|
+
fields = [
|
48
|
+
'provider', 'payment_id', 'status', 'amount',
|
49
|
+
'currency', 'transaction_hash', 'confirmations', 'raw_data'
|
50
|
+
]
|
51
|
+
|
52
|
+
|
53
|
+
class WebhookResponseSerializer(serializers.Serializer):
|
54
|
+
"""
|
55
|
+
Serializer for webhook processing response.
|
56
|
+
|
57
|
+
Standard response format for all webhook endpoints.
|
58
|
+
"""
|
59
|
+
|
60
|
+
success = serializers.BooleanField(help_text="Whether webhook was processed successfully")
|
61
|
+
message = serializers.CharField(max_length=500, help_text="Processing result message")
|
62
|
+
payment_id = serializers.CharField(max_length=256, required=False, help_text="Internal payment ID")
|
63
|
+
provider_payment_id = serializers.CharField(max_length=256, required=False, help_text="Provider payment ID")
|
64
|
+
processed_at = serializers.DateTimeField(required=False, help_text="Processing timestamp")
|
65
|
+
|
66
|
+
class Meta:
|
67
|
+
fields = ['success', 'message', 'payment_id', 'provider_payment_id', 'processed_at']
|
68
|
+
|
69
|
+
|
70
|
+
class WebhookHealthSerializer(serializers.Serializer):
|
71
|
+
"""
|
72
|
+
Serializer for webhook health check response.
|
73
|
+
"""
|
74
|
+
|
75
|
+
status = serializers.CharField(max_length=20, help_text="Health status")
|
76
|
+
timestamp = serializers.DateTimeField(help_text="Check timestamp")
|
77
|
+
providers = serializers.JSONField(help_text="Provider health status")
|
78
|
+
|
79
|
+
class Meta:
|
80
|
+
fields = ['status', 'timestamp', 'providers']
|
81
|
+
|
82
|
+
|
83
|
+
class WebhookStatsSerializer(serializers.Serializer):
|
84
|
+
"""
|
85
|
+
Serializer for webhook statistics response.
|
86
|
+
"""
|
87
|
+
|
88
|
+
total_webhooks = serializers.IntegerField(help_text="Total webhooks processed")
|
89
|
+
successful_webhooks = serializers.IntegerField(help_text="Successfully processed webhooks")
|
90
|
+
failed_webhooks = serializers.IntegerField(help_text="Failed webhook processing attempts")
|
91
|
+
success_rate = serializers.FloatField(help_text="Success rate percentage")
|
92
|
+
providers = serializers.JSONField(help_text="Per-provider statistics")
|
93
|
+
|
94
|
+
class Meta:
|
95
|
+
fields = ['total_webhooks', 'successful_webhooks', 'failed_webhooks', 'success_rate', 'providers']
|
96
|
+
|
97
|
+
|
98
|
+
class SupportedProvidersSerializer(serializers.Serializer):
|
99
|
+
"""
|
100
|
+
Serializer for supported providers response.
|
101
|
+
"""
|
102
|
+
|
103
|
+
success = serializers.BooleanField(help_text="Request success status")
|
104
|
+
providers = serializers.JSONField(help_text="List of supported providers")
|
105
|
+
total_count = serializers.IntegerField(help_text="Total number of providers")
|
106
|
+
timestamp = serializers.DateTimeField(help_text="Response timestamp")
|
107
|
+
|
108
|
+
class Meta:
|
109
|
+
fields = ['success', 'providers', 'total_count', 'timestamp']
|
110
|
+
|
111
|
+
|
112
|
+
class NowPaymentsWebhookSerializer(serializers.Serializer):
|
113
|
+
"""
|
114
|
+
Serializer for NowPayments webhook data.
|
115
|
+
|
116
|
+
Specific to NowPayments IPN format.
|
117
|
+
"""
|
118
|
+
|
119
|
+
payment_id = serializers.CharField(max_length=256, help_text="NowPayments payment ID")
|
120
|
+
payment_status = serializers.CharField(max_length=50, help_text="Payment status")
|
121
|
+
pay_address = serializers.CharField(max_length=256, required=False, help_text="Payment address")
|
122
|
+
price_amount = serializers.DecimalField(max_digits=20, decimal_places=8, required=False, help_text="Price amount")
|
123
|
+
price_currency = serializers.CharField(max_length=10, required=False, help_text="Price currency")
|
124
|
+
pay_amount = serializers.DecimalField(max_digits=20, decimal_places=8, required=False, help_text="Pay amount")
|
125
|
+
pay_currency = serializers.CharField(max_length=10, required=False, help_text="Pay currency")
|
126
|
+
order_id = serializers.CharField(max_length=256, required=False, help_text="Order ID")
|
127
|
+
order_description = serializers.CharField(max_length=500, required=False, help_text="Order description")
|
128
|
+
purchase_id = serializers.CharField(max_length=256, required=False, help_text="Purchase ID")
|
129
|
+
outcome_amount = serializers.DecimalField(max_digits=20, decimal_places=8, required=False, help_text="Outcome amount")
|
130
|
+
outcome_currency = serializers.CharField(max_length=10, required=False, help_text="Outcome currency")
|
131
|
+
|
132
|
+
class Meta:
|
133
|
+
fields = [
|
134
|
+
'payment_id', 'payment_status', 'pay_address', 'price_amount', 'price_currency',
|
135
|
+
'pay_amount', 'pay_currency', 'order_id', 'order_description', 'purchase_id',
|
136
|
+
'outcome_amount', 'outcome_currency'
|
137
|
+
]
|
django_cfg/config.py
CHANGED
@@ -10,7 +10,7 @@ from .modules.django_unfold.models.dropdown import SiteDropdownItem
|
|
10
10
|
|
11
11
|
|
12
12
|
# Library configuration
|
13
|
-
LIB_NAME = "
|
13
|
+
LIB_NAME = "django-cfg"
|
14
14
|
LIB_SITE_URL = "https://djangocfg.com"
|
15
15
|
LIB_DOCS_URL = "https://docs.djangocfg.com"
|
16
16
|
LIB_GITHUB_URL = "https://github.com/django-cfg/django-cfg"
|
django_cfg/core/config.py
CHANGED
@@ -79,6 +79,13 @@ class EnvironmentMode(str, Enum):
|
|
79
79
|
return cls.DEVELOPMENT if debug else cls.PRODUCTION
|
80
80
|
|
81
81
|
|
82
|
+
class StartupInfoMode(str, Enum):
|
83
|
+
"""Startup information display mode."""
|
84
|
+
NONE = "none" # Minimal info only (version, environment, critical errors)
|
85
|
+
SHORT = "short" # Essential info (apps, endpoints, status, updates)
|
86
|
+
FULL = "full" # Complete info (everything from old system)
|
87
|
+
|
88
|
+
|
82
89
|
class DjangoConfig(BaseModel):
|
83
90
|
"""
|
84
91
|
Base configuration class for Django projects.
|
@@ -150,6 +157,11 @@ class DjangoConfig(BaseModel):
|
|
150
157
|
)
|
151
158
|
|
152
159
|
# === Django CFG Features ===
|
160
|
+
startup_info_mode: StartupInfoMode = Field(
|
161
|
+
default=StartupInfoMode.FULL,
|
162
|
+
description="Startup information display mode: none (minimal), short (essential), full (complete)",
|
163
|
+
)
|
164
|
+
|
153
165
|
enable_support: bool = Field(
|
154
166
|
default=True,
|
155
167
|
description="Enable django-cfg Support application (tickets, messages, chat interface)",
|
@@ -601,16 +613,40 @@ class DjangoConfig(BaseModel):
|
|
601
613
|
if self.enable_knowbase or self.enable_agents:
|
602
614
|
return True
|
603
615
|
|
604
|
-
# Check if payments module requires tasks
|
605
|
-
if self.payments and self.payments.should_enable_tasks():
|
606
|
-
return True
|
607
|
-
|
608
616
|
# Check if agents module requires tasks
|
609
617
|
if self.enable_agents:
|
610
618
|
return True
|
611
619
|
|
612
620
|
return False
|
613
621
|
|
622
|
+
def get_enabled_apps(self) -> List[str]:
|
623
|
+
"""
|
624
|
+
Get list of enabled django-cfg apps.
|
625
|
+
"""
|
626
|
+
|
627
|
+
apps = [
|
628
|
+
"django_cfg.apps.api.health",
|
629
|
+
"django_cfg.apps.api.commands",
|
630
|
+
]
|
631
|
+
|
632
|
+
if self.enable_support:
|
633
|
+
apps.append("django_cfg.apps.support")
|
634
|
+
if self.enable_accounts:
|
635
|
+
apps.append("django_cfg.apps.accounts")
|
636
|
+
if self.enable_newsletter:
|
637
|
+
apps.append("django_cfg.apps.newsletter")
|
638
|
+
if self.enable_leads:
|
639
|
+
apps.append("django_cfg.apps.leads")
|
640
|
+
if self.enable_knowbase:
|
641
|
+
apps.append("django_cfg.apps.knowbase")
|
642
|
+
if self.enable_agents:
|
643
|
+
apps.append("django_cfg.apps.agents")
|
644
|
+
if self.enable_maintenance:
|
645
|
+
apps.append("django_cfg.apps.maintenance")
|
646
|
+
if self.payments and self.payments.enabled:
|
647
|
+
apps.append("django_cfg.apps.payments")
|
648
|
+
return apps
|
649
|
+
|
614
650
|
def get_installed_apps(self) -> List[str]:
|
615
651
|
"""
|
616
652
|
Get complete list of installed Django apps.
|
django_cfg/core/generation.py
CHANGED
@@ -114,13 +114,34 @@ class SettingsGenerator:
|
|
114
114
|
|
115
115
|
# Add templates configuration
|
116
116
|
django_cfg_templates = Path(__file__).parent.parent / "templates"
|
117
|
+
|
118
|
+
# Collect all django-cfg template directories
|
119
|
+
template_dirs = [
|
120
|
+
config.base_dir / "templates",
|
121
|
+
django_cfg_templates, # Add django_cfg templates
|
122
|
+
]
|
123
|
+
|
124
|
+
# Auto-discover app template directories
|
125
|
+
django_cfg_dir = Path(__file__).parent.parent
|
126
|
+
apps_dir = django_cfg_dir / 'apps'
|
127
|
+
if apps_dir.exists():
|
128
|
+
for app_dir in apps_dir.iterdir():
|
129
|
+
if app_dir.is_dir() and not app_dir.name.startswith(('@', '_', '.')):
|
130
|
+
# Look for common template directory patterns
|
131
|
+
possible_template_dirs = [
|
132
|
+
app_dir / 'templates',
|
133
|
+
app_dir / 'admin_interface' / 'templates',
|
134
|
+
app_dir / 'frontend' / 'templates',
|
135
|
+
]
|
136
|
+
|
137
|
+
for template_dir in possible_template_dirs:
|
138
|
+
if template_dir.exists():
|
139
|
+
template_dirs.append(template_dir)
|
140
|
+
|
117
141
|
settings["TEMPLATES"] = [
|
118
142
|
{
|
119
143
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
120
|
-
"DIRS":
|
121
|
-
config.base_dir / "templates",
|
122
|
-
django_cfg_templates, # Add django_cfg templates
|
123
|
-
],
|
144
|
+
"DIRS": template_dirs,
|
124
145
|
"APP_DIRS": True,
|
125
146
|
"OPTIONS": {
|
126
147
|
"context_processors": [
|