django-cfg 1.3.1__py3-none-any.whl → 1.3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
  3. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
  4. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
  5. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
  6. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
  7. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
  8. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
  9. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
  10. django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
  11. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
  12. django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
  13. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
  14. django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
  23. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
  24. django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
  25. django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
  26. django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
  27. django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
  28. django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
  29. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
  30. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
  31. django_cfg/apps/payments/admin_interface/views/base.py +114 -0
  32. django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
  33. django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
  34. django_cfg/apps/payments/config/helpers.py +2 -2
  35. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
  36. django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
  37. django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
  38. django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
  39. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  40. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  41. django_cfg/apps/payments/middleware/api_access.py +35 -34
  42. django_cfg/apps/payments/migrations/0001_initial.py +1 -1
  43. django_cfg/apps/payments/models/balance.py +5 -2
  44. django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
  45. django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
  46. django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
  47. django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
  48. django_cfg/apps/payments/models/subscriptions.py +0 -24
  49. django_cfg/apps/payments/services/cache/__init__.py +1 -1
  50. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  51. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  52. django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
  53. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  54. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  55. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  56. django_cfg/apps/payments/services/core/balance_service.py +13 -2
  57. django_cfg/apps/payments/services/core/payment_service.py +49 -22
  58. django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
  59. django_cfg/apps/payments/services/providers/registry.py +20 -0
  60. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  61. django_cfg/apps/payments/signals/balance_signals.py +8 -5
  62. django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
  63. django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
  64. django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
  65. django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
  66. django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
  67. django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
  68. django_cfg/apps/payments/urls.py +4 -0
  69. django_cfg/apps/payments/urls_admin.py +37 -18
  70. django_cfg/apps/payments/views/api/api_keys.py +14 -0
  71. django_cfg/apps/payments/views/api/base.py +1 -0
  72. django_cfg/apps/payments/views/api/currencies.py +2 -2
  73. django_cfg/apps/payments/views/api/payments.py +11 -5
  74. django_cfg/apps/payments/views/api/subscriptions.py +36 -31
  75. django_cfg/apps/payments/views/overview/__init__.py +40 -0
  76. django_cfg/apps/payments/views/overview/serializers.py +205 -0
  77. django_cfg/apps/payments/views/overview/services.py +439 -0
  78. django_cfg/apps/payments/views/overview/urls.py +27 -0
  79. django_cfg/apps/payments/views/overview/views.py +231 -0
  80. django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
  81. django_cfg/apps/payments/views/serializers/balances.py +5 -8
  82. django_cfg/apps/payments/views/serializers/currencies.py +2 -6
  83. django_cfg/apps/payments/views/serializers/payments.py +37 -32
  84. django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
  85. django_cfg/apps/urls.py +2 -1
  86. django_cfg/core/config.py +25 -15
  87. django_cfg/core/generation.py +12 -12
  88. django_cfg/core/integration/display/startup.py +1 -1
  89. django_cfg/core/validation.py +4 -4
  90. django_cfg/management/commands/show_config.py +2 -2
  91. django_cfg/management/commands/tree.py +1 -3
  92. django_cfg/middleware/__init__.py +2 -0
  93. django_cfg/middleware/static_nocache.py +55 -0
  94. django_cfg/models/payments.py +13 -15
  95. django_cfg/models/security.py +15 -0
  96. django_cfg/modules/django_ngrok.py +6 -0
  97. django_cfg/modules/django_unfold/dashboard.py +1 -3
  98. django_cfg/utils/smart_defaults.py +51 -5
  99. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
  100. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
  101. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
  102. django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
  103. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
  104. django_cfg/apps/payments/services/cache/cache_service.py +0 -235
  105. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
  106. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
  107. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
  108. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
  109. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
  110. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
  111. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
  112. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
  113. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
  114. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
  115. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -23,8 +23,8 @@ class APIKeyListSerializer(serializers.ModelSerializer):
23
23
  """
24
24
 
25
25
  user = serializers.StringRelatedField(read_only=True)
26
- is_expired = serializers.BooleanField(source='is_expired', read_only=True)
27
- is_valid = serializers.BooleanField(source='is_valid', read_only=True)
26
+ is_expired = serializers.BooleanField(read_only=True)
27
+ is_valid = serializers.BooleanField(read_only=True)
28
28
 
29
29
  class Meta:
30
30
  model = APIKey
@@ -51,10 +51,10 @@ class APIKeySerializer(serializers.ModelSerializer):
51
51
  """
52
52
 
53
53
  user = serializers.StringRelatedField(read_only=True)
54
- key_preview = serializers.CharField(source='key_preview', read_only=True)
55
- is_expired = serializers.BooleanField(source='is_expired', read_only=True)
56
- is_valid = serializers.BooleanField(source='is_valid', read_only=True)
57
- days_until_expiry = serializers.IntegerField(source='days_until_expiry', read_only=True)
54
+ key_preview = serializers.CharField(read_only=True)
55
+ is_expired = serializers.BooleanField(read_only=True)
56
+ is_valid = serializers.BooleanField(read_only=True)
57
+ days_until_expiry = serializers.IntegerField(read_only=True)
58
58
 
59
59
  class Meta:
60
60
  model = APIKey
@@ -361,6 +361,20 @@ class APIKeyValidationSerializer(serializers.Serializer):
361
361
  }
362
362
 
363
363
 
364
+ class APIKeyValidationResponseSerializer(serializers.Serializer):
365
+ """
366
+ API key validation response serializer.
367
+
368
+ Defines the structure of API key validation response for OpenAPI schema.
369
+ """
370
+ success = serializers.BooleanField(help_text="Whether the validation was successful")
371
+ valid = serializers.BooleanField(help_text="Whether the API key is valid")
372
+ api_key = APIKeySerializer(allow_null=True, help_text="API key details if valid")
373
+ message = serializers.CharField(help_text="Validation message")
374
+ error = serializers.CharField(required=False, help_text="Error message if validation failed")
375
+ error_code = serializers.CharField(required=False, help_text="Error code if validation failed")
376
+
377
+
364
378
  class APIKeyStatsSerializer(serializers.Serializer):
365
379
  """
366
380
  API key statistics serializer.
@@ -25,9 +25,6 @@ class UserBalanceSerializer(serializers.ModelSerializer):
25
25
  """
26
26
 
27
27
  user = serializers.StringRelatedField(read_only=True)
28
- balance_display = serializers.CharField(source='balance_display', read_only=True)
29
- is_empty = serializers.BooleanField(source='is_empty', read_only=True)
30
- has_transactions = serializers.BooleanField(source='has_transactions', read_only=True)
31
28
 
32
29
  class Meta:
33
30
  model = UserBalance
@@ -51,17 +48,17 @@ class TransactionSerializer(serializers.ModelSerializer):
51
48
  """
52
49
 
53
50
  user = serializers.StringRelatedField(read_only=True)
54
- amount_display = serializers.CharField(source='amount_display', read_only=True)
55
- type_color = serializers.CharField(source='type_color', read_only=True)
56
- is_credit = serializers.BooleanField(source='is_credit', read_only=True)
57
- is_debit = serializers.BooleanField(source='is_debit', read_only=True)
51
+ amount_display = serializers.CharField(read_only=True)
52
+ type_color = serializers.CharField(read_only=True)
53
+ is_credit = serializers.BooleanField(read_only=True)
54
+ is_debit = serializers.BooleanField(read_only=True)
58
55
 
59
56
  class Meta:
60
57
  model = Transaction
61
58
  fields = [
62
59
  'id',
63
60
  'user',
64
- 'amount',
61
+ 'amount_usd',
65
62
  'amount_display',
66
63
  'transaction_type',
67
64
  'type_color',
@@ -22,9 +22,6 @@ class CurrencySerializer(serializers.ModelSerializer):
22
22
  """
23
23
 
24
24
  type_display = serializers.CharField(source='get_currency_type_display', read_only=True)
25
- is_crypto = serializers.BooleanField(source='is_crypto', read_only=True)
26
- is_fiat = serializers.BooleanField(source='is_fiat', read_only=True)
27
- is_stable = serializers.BooleanField(source='is_stable', read_only=True)
28
25
 
29
26
  class Meta:
30
27
  model = Currency
@@ -35,11 +32,10 @@ class CurrencySerializer(serializers.ModelSerializer):
35
32
  'symbol',
36
33
  'currency_type',
37
34
  'type_display',
38
- 'decimals',
35
+ 'decimal_places',
39
36
  'is_active',
40
37
  'is_crypto',
41
38
  'is_fiat',
42
- 'is_stable',
43
39
  'created_at',
44
40
  'updated_at',
45
41
  ]
@@ -113,7 +109,7 @@ class ProviderCurrencySerializer(serializers.ModelSerializer):
113
109
  'min_amount',
114
110
  'max_amount',
115
111
  'fee_percentage',
116
- 'is_active',
112
+ 'is_enabled',
117
113
  'created_at',
118
114
  'updated_at',
119
115
  ]
@@ -25,21 +25,22 @@ class PaymentListSerializer(serializers.ModelSerializer):
25
25
  """
26
26
 
27
27
  status_display = serializers.CharField(source='get_status_display', read_only=True)
28
- amount_display = serializers.CharField(source='amount_display', read_only=True)
29
- crypto_amount_display = serializers.CharField(source='crypto_amount_display', read_only=True)
28
+ amount_display = serializers.SerializerMethodField(read_only=True)
29
+
30
+ def get_amount_display(self, obj):
31
+ """Get formatted amount display."""
32
+ return f"${obj.amount_usd:.2f}"
30
33
 
31
34
  class Meta:
32
35
  model = UniversalPayment
33
36
  fields = [
34
37
  'id',
35
38
  'amount_usd',
36
- 'crypto_amount',
37
- 'currency_code',
39
+ 'currency',
38
40
  'provider',
39
41
  'status',
40
42
  'status_display',
41
43
  'amount_display',
42
- 'crypto_amount_display',
43
44
  'created_at',
44
45
  'expires_at',
45
46
  ]
@@ -55,17 +56,33 @@ class PaymentSerializer(serializers.ModelSerializer):
55
56
 
56
57
  user = serializers.StringRelatedField(read_only=True)
57
58
  status_display = serializers.CharField(source='get_status_display', read_only=True)
58
- status_color = serializers.CharField(source='status_color', read_only=True)
59
- amount_display = serializers.CharField(source='amount_display', read_only=True)
60
- crypto_amount_display = serializers.CharField(source='crypto_amount_display', read_only=True)
59
+ amount_display = serializers.SerializerMethodField(read_only=True)
61
60
 
62
61
  # Status check methods
63
- is_pending = serializers.BooleanField(source='is_pending', read_only=True)
64
- is_completed = serializers.BooleanField(source='is_completed', read_only=True)
65
- is_failed = serializers.BooleanField(source='is_failed', read_only=True)
66
- is_expired = serializers.BooleanField(source='is_expired', read_only=True)
67
- can_be_cancelled = serializers.BooleanField(source='can_be_cancelled', read_only=True)
68
- can_be_refunded = serializers.BooleanField(source='can_be_refunded', read_only=True)
62
+ is_pending = serializers.SerializerMethodField(read_only=True)
63
+ is_completed = serializers.SerializerMethodField(read_only=True)
64
+ is_failed = serializers.SerializerMethodField(read_only=True)
65
+ is_expired = serializers.SerializerMethodField(read_only=True)
66
+
67
+ def get_amount_display(self, obj):
68
+ """Get formatted amount display."""
69
+ return f"${obj.amount_usd:.2f}"
70
+
71
+ def get_is_pending(self, obj):
72
+ """Check if payment is pending."""
73
+ return obj.status == obj.PaymentStatus.PENDING
74
+
75
+ def get_is_completed(self, obj):
76
+ """Check if payment is completed."""
77
+ return obj.status == obj.PaymentStatus.COMPLETED
78
+
79
+ def get_is_failed(self, obj):
80
+ """Check if payment is failed."""
81
+ return obj.status == obj.PaymentStatus.FAILED
82
+
83
+ def get_is_expired(self, obj):
84
+ """Check if payment is expired."""
85
+ return obj.status == obj.PaymentStatus.EXPIRED
69
86
 
70
87
  class Meta:
71
88
  model = UniversalPayment
@@ -73,24 +90,20 @@ class PaymentSerializer(serializers.ModelSerializer):
73
90
  'id',
74
91
  'user',
75
92
  'amount_usd',
76
- 'crypto_amount',
77
- 'currency_code',
93
+ 'currency',
94
+ 'network',
78
95
  'provider',
79
96
  'status',
80
97
  'status_display',
81
- 'status_color',
82
98
  'amount_display',
83
- 'crypto_amount_display',
84
99
  'provider_payment_id',
85
100
  'payment_url',
86
- 'qr_code_url',
87
- 'wallet_address',
101
+ 'pay_address',
88
102
  'callback_url',
89
103
  'cancel_url',
90
104
  'description',
91
- 'metadata',
92
105
  'transaction_hash',
93
- 'confirmation_blocks',
106
+ 'confirmations_count',
94
107
  'created_at',
95
108
  'updated_at',
96
109
  'expires_at',
@@ -100,32 +113,24 @@ class PaymentSerializer(serializers.ModelSerializer):
100
113
  'is_completed',
101
114
  'is_failed',
102
115
  'is_expired',
103
- 'can_be_cancelled',
104
- 'can_be_refunded',
105
116
  ]
106
117
  read_only_fields = [
107
118
  'id',
108
119
  'user',
109
- 'crypto_amount',
110
120
  'provider_payment_id',
111
121
  'payment_url',
112
- 'qr_code_url',
113
- 'wallet_address',
122
+ 'pay_address',
114
123
  'transaction_hash',
115
- 'confirmation_blocks',
124
+ 'confirmations_count',
116
125
  'created_at',
117
126
  'updated_at',
118
127
  'completed_at',
119
128
  'status_display',
120
- 'status_color',
121
129
  'amount_display',
122
- 'crypto_amount_display',
123
130
  'is_pending',
124
131
  'is_completed',
125
132
  'is_failed',
126
133
  'is_expired',
127
- 'can_be_cancelled',
128
- 'can_be_refunded',
129
134
  ]
130
135
 
131
136
 
@@ -29,7 +29,7 @@ class EndpointGroupSerializer(serializers.ModelSerializer):
29
29
  'id',
30
30
  'name',
31
31
  'description',
32
- 'is_active',
32
+ 'is_enabled',
33
33
  'created_at',
34
34
  'updated_at',
35
35
  ]
@@ -52,9 +52,9 @@ class TariffSerializer(serializers.ModelSerializer):
52
52
  'id',
53
53
  'name',
54
54
  'description',
55
- 'monthly_price',
55
+ 'monthly_price_usd',
56
56
  'requests_per_month',
57
- 'requests_per_minute',
57
+ 'requests_per_hour',
58
58
  'is_active',
59
59
  'endpoint_groups',
60
60
  'endpoint_groups_count',
@@ -74,8 +74,6 @@ class SubscriptionListSerializer(serializers.ModelSerializer):
74
74
  user = serializers.StringRelatedField(read_only=True)
75
75
  tariff_name = serializers.CharField(source='tariff.name', read_only=True)
76
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
77
 
80
78
  class Meta:
81
79
  model = Subscription
@@ -104,18 +102,8 @@ class SubscriptionSerializer(serializers.ModelSerializer):
104
102
  tariff = TariffSerializer(read_only=True)
105
103
  endpoint_group = EndpointGroupSerializer(read_only=True)
106
104
  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
105
 
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)
106
+ # Only keep fields that actually exist or are needed
119
107
 
120
108
  class Meta:
121
109
  model = Subscription
@@ -129,16 +117,11 @@ class SubscriptionSerializer(serializers.ModelSerializer):
129
117
  'status_color',
130
118
  'tier',
131
119
  'total_requests',
132
- 'requests_used',
133
- 'requests_remaining',
134
120
  'usage_percentage',
135
121
  'last_request_at',
136
122
  'expires_at',
137
123
  'is_active',
138
124
  'is_expired',
139
- 'is_trial',
140
- 'can_be_renewed',
141
- 'can_be_cancelled',
142
125
  'created_at',
143
126
  'updated_at',
144
127
  ]
@@ -146,8 +129,6 @@ class SubscriptionSerializer(serializers.ModelSerializer):
146
129
  'id',
147
130
  'user',
148
131
  'total_requests',
149
- 'requests_used',
150
- 'requests_remaining',
151
132
  'usage_percentage',
152
133
  'last_request_at',
153
134
  'created_at',
@@ -156,9 +137,6 @@ class SubscriptionSerializer(serializers.ModelSerializer):
156
137
  'status_color',
157
138
  'is_active',
158
139
  'is_expired',
159
- 'is_trial',
160
- 'can_be_renewed',
161
- 'can_be_cancelled',
162
140
  ]
163
141
 
164
142
 
django_cfg/apps/urls.py CHANGED
@@ -52,7 +52,8 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
52
52
  # if base_module.is_maintenance_enabled():
53
53
  # patterns.append(path('admin/django_cfg_maintenance/', include('django_cfg.apps.maintenance.urls_admin')))
54
54
 
55
- if base_module.is_payments_enabled():
55
+ # if base_module.is_payments_enabled():
56
+ if True:
56
57
  patterns.append(path('admin/django_cfg_payments/admin/', include('django_cfg.apps.payments.urls_admin')))
57
58
 
58
59
  except Exception:
django_cfg/core/config.py CHANGED
@@ -336,7 +336,6 @@ class DjangoConfig(BaseModel):
336
336
  )
337
337
 
338
338
  # === Internal State ===
339
- _environment: Optional[str] = PrivateAttr(default=None)
340
339
  _base_dir: Optional[Path] = PrivateAttr(default=None)
341
340
  _django_settings: Optional[Dict[str, Any]] = PrivateAttr(default=None)
342
341
 
@@ -353,7 +352,7 @@ class DjangoConfig(BaseModel):
353
352
  super().__init__(**data)
354
353
 
355
354
  # Initialize internal state
356
- self._detect_environment()
355
+ self._auto_detect_env_mode()
357
356
  self._resolve_paths()
358
357
  self._apply_smart_defaults()
359
358
  self._load_environment_config()
@@ -448,14 +447,22 @@ class DjangoConfig(BaseModel):
448
447
  """Check if running in test mode."""
449
448
  return self.env_mode == EnvironmentMode.TEST
450
449
 
451
- def _detect_environment(self) -> None:
452
- """Detect current environment from various sources."""
453
- from django_cfg.core.environment import EnvironmentDetector
454
-
455
- try:
456
- self._environment = EnvironmentDetector.detect_environment()
457
- except Exception as e:
458
- raise EnvironmentError(f"Failed to detect environment: {e}", suggestions=["Set DJANGO_ENV environment variable explicitly"]) from e
450
+ def _auto_detect_env_mode(self) -> None:
451
+ """Auto-detect environment mode from various sources."""
452
+ import os
453
+
454
+ # Check environment variables first
455
+ env_var = os.getenv("DJANGO_ENV", "").lower()
456
+ if env_var in ["development", "dev"]:
457
+ self.env_mode = EnvironmentMode.DEVELOPMENT
458
+ elif env_var in ["production", "prod"]:
459
+ self.env_mode = EnvironmentMode.PRODUCTION
460
+ elif env_var in ["test", "testing"]:
461
+ self.env_mode = EnvironmentMode.TEST
462
+ elif hasattr(self, 'debug') and self.debug:
463
+ # Auto-detect from debug flag if no env var set
464
+ self.env_mode = EnvironmentMode.DEVELOPMENT
465
+ # Otherwise keep the default (PRODUCTION)
459
466
 
460
467
  @property
461
468
  def base_dir(self) -> Path:
@@ -509,17 +516,17 @@ class DjangoConfig(BaseModel):
509
516
  try:
510
517
  # Apply cache defaults
511
518
  if self.cache_default:
512
- self.cache_default = SmartDefaults.configure_cache_backend(self.cache_default, self._environment, self.debug)
519
+ self.cache_default = SmartDefaults.configure_cache_backend(self.cache_default, self.env_mode, self.debug)
513
520
 
514
521
  if self.cache_sessions:
515
- self.cache_sessions = SmartDefaults.configure_cache_backend(self.cache_sessions, self._environment, self.debug)
522
+ self.cache_sessions = SmartDefaults.configure_cache_backend(self.cache_sessions, self.env_mode, self.debug)
516
523
 
517
524
  # Apply email defaults
518
525
  if self.email:
519
- self.email = SmartDefaults.configure_email_backend(self.email, self._environment, self.debug)
526
+ self.email = SmartDefaults.configure_email_backend(self.email, self.env_mode, self.debug)
520
527
 
521
528
  except Exception as e:
522
- raise ConfigurationError(f"Failed to apply smart defaults: {e}", context={"environment": self._environment, "debug": self.debug}) from e
529
+ raise ConfigurationError(f"Failed to apply smart defaults: {e}", context={"environment": self.env_mode, "debug": self.debug}) from e
523
530
 
524
531
  def _load_environment_config(self) -> None:
525
532
  """Load environment-specific configuration from YAML files."""
@@ -775,6 +782,9 @@ class DjangoConfig(BaseModel):
775
782
  if self.enable_accounts:
776
783
  middleware.append("django_cfg.middleware.UserActivityMiddleware")
777
784
 
785
+ # Add static no-cache middleware (auto-detects development mode)
786
+ middleware.append("django_cfg.middleware.StaticNoCacheMiddleware")
787
+
778
788
  # Add payments middleware if enabled
779
789
  if self.payments and self.payments.enabled:
780
790
  middleware.extend(self.payments.get_middleware_classes())
@@ -854,7 +864,7 @@ class DjangoConfig(BaseModel):
854
864
  Returns:
855
865
  Model data with internal fields excluded
856
866
  """
857
- return self.model_dump(exclude={"_environment", "_base_dir", "_django_settings"}, exclude_none=True)
867
+ return self.model_dump(exclude={"_base_dir", "_django_settings"}, exclude_none=True)
858
868
 
859
869
 
860
870
  # Global config instance for access from other modules
@@ -179,7 +179,7 @@ class SettingsGenerator:
179
179
  # Apply database defaults for each database based on its engine
180
180
  for alias, db_config in config.databases.items():
181
181
  db_defaults = SmartDefaults.get_database_defaults(
182
- config._environment,
182
+ config.env_mode,
183
183
  config.debug,
184
184
  db_config.engine
185
185
  )
@@ -219,17 +219,17 @@ class SettingsGenerator:
219
219
 
220
220
  # Default cache - always provide one
221
221
  if config.cache_default:
222
- caches["default"] = config.cache_default.to_django_config(config._environment, config.debug, "default")
222
+ caches["default"] = config.cache_default.to_django_config(config.env_mode, config.debug, "default")
223
223
  else:
224
224
  # Create default cache backend
225
225
  from django_cfg.models.cache import CacheConfig
226
226
 
227
227
  default_cache = CacheConfig()
228
- caches["default"] = default_cache.to_django_config(config._environment, config.debug, "default")
228
+ caches["default"] = default_cache.to_django_config(config.env_mode, config.debug, "default")
229
229
 
230
230
  # Sessions cache
231
231
  if config.cache_sessions:
232
- caches["sessions"] = config.cache_sessions.to_django_config(config._environment, config.debug, "sessions")
232
+ caches["sessions"] = config.cache_sessions.to_django_config(config.env_mode, config.debug, "sessions")
233
233
 
234
234
  # Configure Django to use cache for sessions (can be overridden)
235
235
  settings["SESSION_ENGINE"] = "django.contrib.sessions.backends.cache"
@@ -241,7 +241,7 @@ class SettingsGenerator:
241
241
  cache_obj = getattr(config, attr_name)
242
242
  if hasattr(cache_obj, "to_django_config"):
243
243
  cache_alias = attr_name.replace("cache_", "")
244
- caches[cache_alias] = cache_obj.to_django_config(config._environment, config.debug, cache_alias)
244
+ caches[cache_alias] = cache_obj.to_django_config(config.env_mode, config.debug, cache_alias)
245
245
 
246
246
  if caches:
247
247
  settings["CACHES"] = caches
@@ -261,7 +261,7 @@ class SettingsGenerator:
261
261
  if config.security_domains or config.ssl_redirect is not None:
262
262
  security_defaults = SmartDefaults.get_security_defaults(
263
263
  config.security_domains,
264
- config._environment,
264
+ config.env_mode,
265
265
  config.debug,
266
266
  config.ssl_redirect,
267
267
  config.cors_allow_headers
@@ -275,12 +275,12 @@ class SettingsGenerator:
275
275
  pass
276
276
 
277
277
  # Additional security settings for production
278
- if config._environment == "production":
278
+ if config.env_mode == "production":
279
279
  settings.update(
280
280
  {
281
- "SESSION_COOKIE_AGE": 86400, # 24 hours
281
+ "SESSION_COOKIE_AGE": 2592000, # 30 days (30 * 24 * 60 * 60)
282
282
  "SESSION_SAVE_EVERY_REQUEST": True,
283
- "SESSION_EXPIRE_AT_BROWSER_CLOSE": True,
283
+ "SESSION_EXPIRE_AT_BROWSER_CLOSE": False, # Allow persistent sessions
284
284
  }
285
285
  )
286
286
 
@@ -296,7 +296,7 @@ class SettingsGenerator:
296
296
  settings = {}
297
297
 
298
298
  if config.email:
299
- email_settings = config.email.to_django_config(config._environment, config.debug)
299
+ email_settings = config.email.to_django_config(config.env_mode, config.debug)
300
300
  settings.update(email_settings)
301
301
 
302
302
  return settings
@@ -311,7 +311,7 @@ class SettingsGenerator:
311
311
  settings = {}
312
312
 
313
313
  # Generate logging defaults
314
- logging_defaults = SmartDefaults.get_logging_defaults(config._environment, config.debug)
314
+ logging_defaults = SmartDefaults.get_logging_defaults(config.env_mode, config.debug)
315
315
 
316
316
  if logging_defaults:
317
317
  settings["LOGGING"] = logging_defaults
@@ -370,7 +370,7 @@ class SettingsGenerator:
370
370
  }
371
371
 
372
372
  # Adjust for different environments
373
- if config._environment == "development":
373
+ if config.env_mode == "development":
374
374
  settings["USE_L10N"] = True # Deprecated but sometimes needed
375
375
 
376
376
  return settings
@@ -122,7 +122,7 @@ class StartupDisplayManager(BaseDisplayManager):
122
122
  info_table.add_row("šŸ—ļø Project", self.config.project_name)
123
123
 
124
124
  # Add environment source
125
- env_source = getattr(self.config, '_environment', 'default_fallback')
125
+ env_source = getattr(self.config, 'env_mode', 'default_fallback')
126
126
  info_table.add_row("šŸ” Env Source", env_source)
127
127
 
128
128
  info_table.add_row("🌐 Site", LIB_SITE_URL)
@@ -81,7 +81,7 @@ class ConfigurationValidator:
81
81
  errors.append("SECRET_KEY must be at least 50 characters long")
82
82
  else:
83
83
  # Check for insecure patterns in production
84
- if config._environment == 'production':
84
+ if config.env_mode == 'production':
85
85
  insecure_patterns = [
86
86
  'django-insecure',
87
87
  'change-me',
@@ -110,7 +110,7 @@ class ConfigurationValidator:
110
110
  errors.append(f"Invalid hostname in ALLOWED_HOSTS[{i}]: '{host}'")
111
111
 
112
112
  # Production-specific validation
113
- if config._environment == 'production' and '*' in allowed_hosts:
113
+ if config.env_mode == 'production' and '*' in allowed_hosts:
114
114
  errors.append("Wildcard '*' in ALLOWED_HOSTS is not recommended for production")
115
115
 
116
116
  return errors
@@ -143,7 +143,7 @@ class ConfigurationValidator:
143
143
  errors = []
144
144
 
145
145
  # Environment-specific security validation
146
- if config._environment == 'production':
146
+ if config.env_mode == 'production':
147
147
  # Allow DEBUG=True in production for development purposes
148
148
  pass
149
149
 
@@ -161,7 +161,7 @@ class ConfigurationValidator:
161
161
  """Validate environment-specific consistency."""
162
162
  errors = []
163
163
 
164
- environment = config._environment
164
+ environment = config.env_mode
165
165
 
166
166
  if environment == 'production':
167
167
  # Production requirements - allow DEBUG=True for development
@@ -100,7 +100,7 @@ class Command(BaseCommand):
100
100
  self.stdout.write(self.style.SUCCESS('\nšŸŒ Environment'))
101
101
  self.stdout.write('-' * 40)
102
102
  env_data = [
103
- ('Environment', getattr(config, '_environment', 'auto-detected')),
103
+ ('Environment', getattr(config, 'env_mode', 'auto-detected')),
104
104
  ('Debug Mode', config.debug),
105
105
  ('Security Domains', ', '.join(config.security_domains) if config.security_domains else 'None'),
106
106
  ]
@@ -186,7 +186,7 @@ class Command(BaseCommand):
186
186
  'description': getattr(config, 'project_description', None),
187
187
  },
188
188
  'environment': {
189
- 'environment': getattr(config, '_environment', 'auto-detected'),
189
+ 'environment': getattr(config, 'env_mode', 'auto-detected'),
190
190
  'debug': config.debug,
191
191
  'allowed_hosts': config.allowed_hosts,
192
192
  },
@@ -97,9 +97,7 @@ class Command(BaseCommand):
97
97
 
98
98
  # Try to get environment info
99
99
  try:
100
- env_info = getattr(config, 'environment', 'unknown')
101
- if hasattr(config, '_environment'):
102
- env_info = config._environment or 'unknown'
100
+ env_info = getattr(config, 'env_mode', 'unknown')
103
101
  self.stdout.write(
104
102
  self.style.HTTP_INFO(f"šŸ”§ Environment: {env_info}")
105
103
  )
@@ -5,7 +5,9 @@ Provides middleware components for Django CFG applications.
5
5
  """
6
6
 
7
7
  from .user_activity import UserActivityMiddleware
8
+ from .static_nocache import StaticNoCacheMiddleware
8
9
 
9
10
  __all__ = [
10
11
  'UserActivityMiddleware',
12
+ 'StaticNoCacheMiddleware',
11
13
  ]
@@ -0,0 +1,55 @@
1
+ """
2
+ Static files no-cache middleware for django-cfg.
3
+
4
+ Automatically disables caching for static files in development environments
5
+ to prevent browser caching issues during development.
6
+ """
7
+
8
+ from django.conf import settings
9
+ from django_cfg.core.config import EnvironmentMode
10
+
11
+
12
+ class StaticNoCacheMiddleware:
13
+ """
14
+ Middleware to disable caching for static files in development.
15
+
16
+ This ensures that JavaScript and CSS files are always fresh during development,
17
+ preventing browser caching issues when files are updated.
18
+
19
+ Automatically detects development mode based on:
20
+ - DEBUG setting
21
+ - ENV_MODE environment variable
22
+ """
23
+
24
+ def __init__(self, get_response):
25
+ self.get_response = get_response
26
+
27
+ # Determine if we should disable caching
28
+ self.should_disable_cache = self._should_disable_cache()
29
+
30
+ def _should_disable_cache(self):
31
+ """Determine if caching should be disabled based on environment."""
32
+ # Always disable in DEBUG mode
33
+ if settings.DEBUG:
34
+ return True
35
+
36
+ # Check ENV_MODE if available
37
+ env_mode = getattr(settings, 'ENV_MODE', None)
38
+ if env_mode == EnvironmentMode.DEVELOPMENT or env_mode == EnvironmentMode.TEST:
39
+ return True
40
+
41
+ return False
42
+
43
+ def __call__(self, request):
44
+ response = self.get_response(request)
45
+
46
+ # Apply no-cache headers for static files in development
47
+ if self.should_disable_cache and request.path.startswith('/static/'):
48
+ response['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
49
+ response['Pragma'] = 'no-cache'
50
+ response['Expires'] = '0'
51
+ # Add ETag removal to prevent conditional requests
52
+ if 'ETag' in response:
53
+ del response['ETag']
54
+
55
+ return response