django-cfg 1.1.82__py3-none-any.whl โ 1.2.0__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 +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +444 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py โ core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +79 -17
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold โ django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold โ django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.82.dist-info โ django_cfg-1.2.0.dist-info}/METADATA +83 -86
- django_cfg-1.2.0.dist-info/RECORD +441 -0
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.82.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py โ core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold โ django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold โ django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py โ utils/version_check.py} +0 -0
- {django_cfg-1.1.82.dist-info โ django_cfg-1.2.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info โ django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.82.dist-info โ django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
django_cfg/middleware/README.md
CHANGED
@@ -5,7 +5,6 @@ Custom Django middleware components for Django CFG applications.
|
|
5
5
|
## ๐ Contents
|
6
6
|
|
7
7
|
- [UserActivityMiddleware](#useractivitymiddleware) - User activity tracking
|
8
|
-
- [PublicEndpointsMiddleware](#publicendpointsmiddleware) - Ignore invalid JWT tokens on public endpoints
|
9
8
|
|
10
9
|
## UserActivityMiddleware
|
11
10
|
|
@@ -159,160 +158,3 @@ class MyProjectConfig(DjangoConfig):
|
|
159
158
|
# GET /api/users/?format=json
|
160
159
|
# PUT /cfg/newsletter/subscribe/
|
161
160
|
```
|
162
|
-
|
163
|
-
## PublicEndpointsMiddleware
|
164
|
-
|
165
|
-
Middleware that temporarily removes invalid JWT tokens from public endpoints to prevent authentication errors.
|
166
|
-
|
167
|
-
### โจ Features
|
168
|
-
|
169
|
-
- โ
**Automatic activation** - No configuration needed, works out of the box
|
170
|
-
- โ
**Smart endpoint detection** - Configurable regex patterns for public endpoints
|
171
|
-
- โ
**JWT token detection** - Only processes requests with Bearer tokens
|
172
|
-
- โ
**Temporary removal** - Auth headers are restored after request processing
|
173
|
-
- โ
**Performance optimized** - Compiled regex patterns for fast matching
|
174
|
-
- โ
**Detailed logging** - Debug information for troubleshooting
|
175
|
-
- โ
**Statistics tracking** - Monitor middleware usage and effectiveness
|
176
|
-
|
177
|
-
### ๐ฏ Problem Solved
|
178
|
-
|
179
|
-
When a frontend sends an invalid/expired JWT token to a public endpoint (like OTP request), Django's authentication middleware tries to authenticate the user and fails with "User not found" errors, even though the endpoint has `AllowAny` permissions.
|
180
|
-
|
181
|
-
This middleware temporarily removes the `Authorization` header for public endpoints, allowing them to work without authentication errors.
|
182
|
-
|
183
|
-
### ๐ Automatic Integration
|
184
|
-
|
185
|
-
The middleware is **automatically included** in all Django CFG projects:
|
186
|
-
|
187
|
-
```python
|
188
|
-
class MyConfig(DjangoConfig):
|
189
|
-
# No configuration needed - PublicEndpointsMiddleware is always active
|
190
|
-
pass
|
191
|
-
```
|
192
|
-
|
193
|
-
### ๐ฏ Default Public Endpoints
|
194
|
-
|
195
|
-
The middleware protects these endpoints by default:
|
196
|
-
|
197
|
-
```python
|
198
|
-
DEFAULT_PUBLIC_PATTERNS = [
|
199
|
-
r'^/api/accounts/otp/', # OTP endpoints (request, verify)
|
200
|
-
r'^/cfg/accounts/otp/', # CFG OTP endpoints
|
201
|
-
r'^/api/accounts/token/refresh/', # Token refresh
|
202
|
-
r'^/cfg/accounts/token/refresh/', # CFG Token refresh
|
203
|
-
r'^/api/health/', # Health check endpoints
|
204
|
-
r'^/cfg/api/health/', # CFG Health check endpoints
|
205
|
-
r'^/admin/login/', # Django admin login
|
206
|
-
r'^/api/schema/', # API schema endpoints
|
207
|
-
r'^/api/docs/', # API documentation
|
208
|
-
]
|
209
|
-
```
|
210
|
-
|
211
|
-
### โ๏ธ Custom Configuration
|
212
|
-
|
213
|
-
You can customize public endpoint patterns in your Django settings:
|
214
|
-
|
215
|
-
```python
|
216
|
-
# settings.py (optional)
|
217
|
-
PUBLIC_ENDPOINT_PATTERNS = [
|
218
|
-
r'^/api/accounts/otp/',
|
219
|
-
r'^/api/public/',
|
220
|
-
r'^/api/webhooks/',
|
221
|
-
# Add your custom patterns here
|
222
|
-
]
|
223
|
-
```
|
224
|
-
|
225
|
-
### ๐ How It Works
|
226
|
-
|
227
|
-
1. **Request Processing**: Middleware checks if the request path matches public endpoint patterns
|
228
|
-
2. **Token Detection**: If a Bearer token is present, it's temporarily removed
|
229
|
-
3. **Request Handling**: Django processes the request without authentication
|
230
|
-
4. **Token Restoration**: The original Authorization header is restored after processing
|
231
|
-
|
232
|
-
### ๐ Statistics
|
233
|
-
|
234
|
-
Get middleware statistics for monitoring:
|
235
|
-
|
236
|
-
```python
|
237
|
-
from django_cfg.middleware import PublicEndpointsMiddleware
|
238
|
-
|
239
|
-
# In your view or management command
|
240
|
-
middleware = PublicEndpointsMiddleware()
|
241
|
-
stats = middleware.get_stats()
|
242
|
-
|
243
|
-
print(stats)
|
244
|
-
# {
|
245
|
-
# 'requests_processed': 1250,
|
246
|
-
# 'tokens_ignored': 45,
|
247
|
-
# 'public_endpoints_hit': 120,
|
248
|
-
# 'public_patterns_count': 9,
|
249
|
-
# 'middleware_active': True
|
250
|
-
# }
|
251
|
-
```
|
252
|
-
|
253
|
-
### ๐ Logging
|
254
|
-
|
255
|
-
The middleware logs activity at DEBUG level:
|
256
|
-
|
257
|
-
```python
|
258
|
-
# settings.py
|
259
|
-
LOGGING = {
|
260
|
-
'loggers': {
|
261
|
-
'django_cfg.middleware.public_endpoints': {
|
262
|
-
'level': 'DEBUG',
|
263
|
-
'handlers': ['console'],
|
264
|
-
},
|
265
|
-
},
|
266
|
-
}
|
267
|
-
```
|
268
|
-
|
269
|
-
### ๐๏ธ Manual Integration
|
270
|
-
|
271
|
-
If you need to include the middleware manually (not recommended):
|
272
|
-
|
273
|
-
```python
|
274
|
-
# settings.py
|
275
|
-
MIDDLEWARE = [
|
276
|
-
'django.middleware.security.SecurityMiddleware',
|
277
|
-
'corsheaders.middleware.CorsMiddleware',
|
278
|
-
'django_cfg.middleware.PublicEndpointsMiddleware', # Add early in stack
|
279
|
-
# ... other middleware
|
280
|
-
]
|
281
|
-
```
|
282
|
-
|
283
|
-
### ๐จ Important Notes
|
284
|
-
|
285
|
-
1. **Always Active**: Middleware is included by default in all Django CFG projects
|
286
|
-
2. **Performance**: Uses compiled regex patterns for fast endpoint matching
|
287
|
-
3. **Safety**: Only removes Authorization headers temporarily, restores them after processing
|
288
|
-
4. **Logging**: All actions are logged for debugging and monitoring
|
289
|
-
|
290
|
-
### ๐ก Usage Examples
|
291
|
-
|
292
|
-
The middleware works automatically with no configuration needed:
|
293
|
-
|
294
|
-
```python
|
295
|
-
# Your DjangoConfig
|
296
|
-
class MyProjectConfig(DjangoConfig):
|
297
|
-
# PublicEndpointsMiddleware is automatically active
|
298
|
-
pass
|
299
|
-
|
300
|
-
# These requests will work even with invalid tokens:
|
301
|
-
# POST /api/accounts/otp/request/ (with expired Bearer token)
|
302
|
-
# POST /cfg/accounts/otp/verify/ (with invalid Bearer token)
|
303
|
-
# GET /api/health/ (with any Bearer token)
|
304
|
-
```
|
305
|
-
|
306
|
-
### ๐ง Frontend Integration
|
307
|
-
|
308
|
-
Perfect companion to frontend error handling:
|
309
|
-
|
310
|
-
```typescript
|
311
|
-
// Frontend automatically clears invalid tokens on 401/403
|
312
|
-
// Middleware ensures public endpoints work during token cleanup
|
313
|
-
const response = await api.requestOTP({
|
314
|
-
identifier: "user@example.com",
|
315
|
-
channel: "email"
|
316
|
-
});
|
317
|
-
// โ
Works even if localStorage has invalid token
|
318
|
-
```
|
@@ -9,12 +9,12 @@ import logging
|
|
9
9
|
from django.utils import timezone
|
10
10
|
from django.contrib.auth import get_user_model
|
11
11
|
from django.utils.deprecation import MiddlewareMixin
|
12
|
-
from django_cfg.modules.base import
|
12
|
+
from django_cfg.modules.base import BaseCfgModule
|
13
13
|
|
14
14
|
logger = logging.getLogger(__name__)
|
15
15
|
|
16
16
|
|
17
|
-
class UserActivityMiddleware(MiddlewareMixin,
|
17
|
+
class UserActivityMiddleware(MiddlewareMixin, BaseCfgModule):
|
18
18
|
"""
|
19
19
|
Middleware to track user activity via last_login field.
|
20
20
|
|
@@ -31,7 +31,7 @@ class UserActivityMiddleware(MiddlewareMixin, BaseModule):
|
|
31
31
|
def __init__(self, get_response=None):
|
32
32
|
"""Initialize the middleware."""
|
33
33
|
super().__init__(get_response)
|
34
|
-
|
34
|
+
BaseCfgModule.__init__(self)
|
35
35
|
self.get_response = get_response
|
36
36
|
|
37
37
|
# Cache for tracking last update times (in memory)
|
django_cfg/models/api.py
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
"""
|
2
|
+
API Configuration Model
|
3
|
+
|
4
|
+
Django REST Framework and API settings with Pydantic 2.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, List
|
8
|
+
from pydantic import Field, field_validator
|
9
|
+
from .base import BaseConfig
|
10
|
+
|
11
|
+
|
12
|
+
class APIConfig(BaseConfig):
|
13
|
+
"""
|
14
|
+
๐ API Configuration - REST Framework and API settings
|
15
|
+
|
16
|
+
Configures Django REST Framework, pagination, throttling,
|
17
|
+
and API documentation settings.
|
18
|
+
"""
|
19
|
+
|
20
|
+
# Pagination settings
|
21
|
+
page_size: int = Field(
|
22
|
+
default=20,
|
23
|
+
ge=1,
|
24
|
+
le=100,
|
25
|
+
description="Default API pagination page size"
|
26
|
+
)
|
27
|
+
|
28
|
+
max_page_size: int = Field(
|
29
|
+
default=100,
|
30
|
+
ge=1,
|
31
|
+
le=1000,
|
32
|
+
description="Maximum API pagination page size"
|
33
|
+
)
|
34
|
+
|
35
|
+
# Throttling settings
|
36
|
+
rate_limit_enabled: bool = Field(
|
37
|
+
default=True,
|
38
|
+
description="Enable API rate limiting"
|
39
|
+
)
|
40
|
+
|
41
|
+
rate_limit_anon: str = Field(
|
42
|
+
default="100/hour",
|
43
|
+
description="Rate limit for anonymous users"
|
44
|
+
)
|
45
|
+
|
46
|
+
rate_limit_user: str = Field(
|
47
|
+
default="1000/hour",
|
48
|
+
description="Rate limit for authenticated users"
|
49
|
+
)
|
50
|
+
|
51
|
+
# Documentation settings
|
52
|
+
docs_enabled: bool = Field(
|
53
|
+
default=True,
|
54
|
+
description="Enable API documentation"
|
55
|
+
)
|
56
|
+
|
57
|
+
docs_title: str = Field(
|
58
|
+
default="API Documentation",
|
59
|
+
description="API documentation title"
|
60
|
+
)
|
61
|
+
|
62
|
+
docs_version: str = Field(
|
63
|
+
default="v1",
|
64
|
+
description="API version"
|
65
|
+
)
|
66
|
+
|
67
|
+
# CORS settings for API
|
68
|
+
api_cors_origins: List[str] = Field(
|
69
|
+
default_factory=list,
|
70
|
+
description="CORS origins specifically for API endpoints"
|
71
|
+
)
|
72
|
+
|
73
|
+
@field_validator('page_size', 'max_page_size')
|
74
|
+
@classmethod
|
75
|
+
def validate_page_sizes(cls, v: int) -> int:
|
76
|
+
"""Validate pagination sizes."""
|
77
|
+
if v <= 0:
|
78
|
+
raise ValueError("Page size must be positive")
|
79
|
+
return v
|
80
|
+
|
81
|
+
@field_validator('rate_limit_anon', 'rate_limit_user')
|
82
|
+
@classmethod
|
83
|
+
def validate_rate_limits(cls, v: str) -> str:
|
84
|
+
"""Validate rate limit format."""
|
85
|
+
import re
|
86
|
+
pattern = r'^\d+/(second|minute|hour|day)$'
|
87
|
+
if not re.match(pattern, v):
|
88
|
+
raise ValueError("Rate limit must be in format: number/(second|minute|hour|day)")
|
89
|
+
return v
|
90
|
+
|
91
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
92
|
+
"""Convert to Django REST Framework settings."""
|
93
|
+
settings = {
|
94
|
+
'REST_FRAMEWORK': {
|
95
|
+
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
96
|
+
'PAGE_SIZE': self.page_size,
|
97
|
+
'MAX_PAGE_SIZE': self.max_page_size,
|
98
|
+
|
99
|
+
'DEFAULT_AUTHENTICATION_CLASSES': [
|
100
|
+
'rest_framework.authentication.SessionAuthentication',
|
101
|
+
'rest_framework.authentication.TokenAuthentication',
|
102
|
+
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
103
|
+
],
|
104
|
+
|
105
|
+
'DEFAULT_PERMISSION_CLASSES': [
|
106
|
+
'rest_framework.permissions.IsAuthenticated',
|
107
|
+
],
|
108
|
+
|
109
|
+
'DEFAULT_RENDERER_CLASSES': [
|
110
|
+
'rest_framework.renderers.JSONRenderer',
|
111
|
+
'rest_framework.renderers.BrowsableAPIRenderer',
|
112
|
+
],
|
113
|
+
|
114
|
+
'DEFAULT_FILTER_BACKENDS': [
|
115
|
+
'django_filters.rest_framework.DjangoFilterBackend',
|
116
|
+
'rest_framework.filters.OrderingFilter',
|
117
|
+
'rest_framework.filters.SearchFilter',
|
118
|
+
],
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
# Add throttling if enabled
|
123
|
+
if self.rate_limit_enabled:
|
124
|
+
settings['REST_FRAMEWORK']['DEFAULT_THROTTLE_CLASSES'] = [
|
125
|
+
'rest_framework.throttling.AnonRateThrottle',
|
126
|
+
'rest_framework.throttling.UserRateThrottle',
|
127
|
+
]
|
128
|
+
settings['REST_FRAMEWORK']['DEFAULT_THROTTLE_RATES'] = {
|
129
|
+
'anon': self.rate_limit_anon,
|
130
|
+
'user': self.rate_limit_user,
|
131
|
+
}
|
132
|
+
|
133
|
+
# API documentation settings
|
134
|
+
if self.docs_enabled:
|
135
|
+
settings.update({
|
136
|
+
'SPECTACULAR_SETTINGS': {
|
137
|
+
'TITLE': self.docs_title,
|
138
|
+
'DESCRIPTION': 'API for Django application',
|
139
|
+
'VERSION': self.docs_version,
|
140
|
+
'SERVE_INCLUDE_SCHEMA': False,
|
141
|
+
'SCHEMA_PATH_PREFIX': '/api/',
|
142
|
+
}
|
143
|
+
})
|
144
|
+
|
145
|
+
return settings
|
@@ -0,0 +1,287 @@
|
|
1
|
+
"""
|
2
|
+
Base Configuration Model
|
3
|
+
|
4
|
+
Foundation for all Django configuration models using Pydantic 2.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
12
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
13
|
+
from pydantic_settings.sources import DotEnvSettingsSource
|
14
|
+
|
15
|
+
|
16
|
+
class ConfigValidationError(Exception):
|
17
|
+
"""Configuration validation error with helpful developer messages."""
|
18
|
+
|
19
|
+
def __init__(self, field: str, value: Any, message: str):
|
20
|
+
self.field = field
|
21
|
+
self.value = value
|
22
|
+
self.message = message
|
23
|
+
super().__init__(f"โ Config error in '{field}': {message} (got: {value})")
|
24
|
+
|
25
|
+
|
26
|
+
class BaseConfig(BaseSettings):
|
27
|
+
"""
|
28
|
+
๐ฅ Base configuration model with amazing developer experience
|
29
|
+
|
30
|
+
Features:
|
31
|
+
- Automatic .env file detection (.env.dev, .env.prod, etc.)
|
32
|
+
- Nested environment variables with __ delimiter
|
33
|
+
- Type-safe validation with Pydantic 2
|
34
|
+
- Helpful error messages for developers
|
35
|
+
- Environment-specific configuration
|
36
|
+
"""
|
37
|
+
|
38
|
+
model_config = SettingsConfigDict(
|
39
|
+
# Environment file settings
|
40
|
+
env_file='.env',
|
41
|
+
env_file_encoding='utf-8',
|
42
|
+
env_nested_delimiter='__',
|
43
|
+
case_sensitive=False,
|
44
|
+
|
45
|
+
# Validation settings
|
46
|
+
validate_assignment=True,
|
47
|
+
validate_default=True,
|
48
|
+
extra='ignore',
|
49
|
+
|
50
|
+
# Performance settings
|
51
|
+
frozen=False,
|
52
|
+
arbitrary_types_allowed=False,
|
53
|
+
use_enum_values=True,
|
54
|
+
)
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def settings_customise_sources(
|
58
|
+
cls,
|
59
|
+
settings_cls,
|
60
|
+
init_settings,
|
61
|
+
env_settings,
|
62
|
+
dotenv_settings,
|
63
|
+
file_secret_settings,
|
64
|
+
):
|
65
|
+
"""Custom settings sources with automatic env file detection."""
|
66
|
+
env_file = cls._detect_env_file()
|
67
|
+
if env_file:
|
68
|
+
dotenv_settings = DotEnvSettingsSource(
|
69
|
+
settings_cls,
|
70
|
+
env_file=env_file,
|
71
|
+
env_file_encoding='utf-8'
|
72
|
+
)
|
73
|
+
|
74
|
+
return (
|
75
|
+
init_settings,
|
76
|
+
env_settings,
|
77
|
+
dotenv_settings,
|
78
|
+
file_secret_settings,
|
79
|
+
)
|
80
|
+
|
81
|
+
def __init__(self, **kwargs):
|
82
|
+
"""Initialize with smart .env file detection."""
|
83
|
+
try:
|
84
|
+
super().__init__(**kwargs)
|
85
|
+
except Exception as e:
|
86
|
+
self._handle_validation_error(e)
|
87
|
+
raise
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def _detect_env_file(cls) -> Optional[str]:
|
91
|
+
"""
|
92
|
+
๐ Smart environment file detection
|
93
|
+
|
94
|
+
Priority order:
|
95
|
+
1. .env.local (local overrides, should be git-ignored)
|
96
|
+
2. .env.dev / .env.development (development)
|
97
|
+
3. .env.prod / .env.production (production)
|
98
|
+
4. config.env.dev / config.env.prod (project-specific)
|
99
|
+
5. .env (fallback)
|
100
|
+
"""
|
101
|
+
env_files = [
|
102
|
+
'.env.local',
|
103
|
+
'.env.dev',
|
104
|
+
'.env.development',
|
105
|
+
'.env.prod',
|
106
|
+
'.env.production',
|
107
|
+
'config.env.dev',
|
108
|
+
'config.env.prod',
|
109
|
+
'.env'
|
110
|
+
]
|
111
|
+
|
112
|
+
for env_file in env_files:
|
113
|
+
if Path(env_file).exists():
|
114
|
+
return env_file
|
115
|
+
|
116
|
+
return None
|
117
|
+
|
118
|
+
def _show_debug(self) -> bool:
|
119
|
+
"""Check if we should show debug info."""
|
120
|
+
return os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
|
121
|
+
|
122
|
+
def _handle_validation_error(self, error: Exception):
|
123
|
+
"""Provide helpful validation error messages for developers."""
|
124
|
+
if hasattr(error, 'errors'):
|
125
|
+
print("โ Django Configuration Validation Errors:")
|
126
|
+
print("=" * 50)
|
127
|
+
|
128
|
+
for err in error.errors():
|
129
|
+
field = '.'.join(str(x) for x in err['loc'])
|
130
|
+
message = err['msg']
|
131
|
+
input_val = err.get('input', 'N/A')
|
132
|
+
|
133
|
+
print(f"๐ด Field: {field}")
|
134
|
+
print(f" Error: {message}")
|
135
|
+
print(f" Value: {input_val}")
|
136
|
+
print(f" ๐ก Fix: Check your .env file or environment variables")
|
137
|
+
print()
|
138
|
+
|
139
|
+
print("๐ Documentation: https://django-config-toolkit.readthedocs.io/")
|
140
|
+
print("=" * 50)
|
141
|
+
|
142
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
143
|
+
"""Convert configuration to Django-compatible settings dictionary."""
|
144
|
+
return self.model_dump(exclude_none=True, by_alias=True)
|
145
|
+
|
146
|
+
def get_field_info(self) -> Dict[str, Dict[str, Any]]:
|
147
|
+
"""Get detailed field information for developers."""
|
148
|
+
field_info = {}
|
149
|
+
|
150
|
+
for field_name, field in self.model_fields.items():
|
151
|
+
field_info[field_name] = {
|
152
|
+
'description': field.description or f"Configuration for {field_name}",
|
153
|
+
'type': str(field.annotation) if hasattr(field, 'annotation') else 'Any',
|
154
|
+
'default': field.default if field.default is not ... else None,
|
155
|
+
'required': field.is_required(),
|
156
|
+
'env_var': field_name.upper(),
|
157
|
+
}
|
158
|
+
|
159
|
+
return field_info
|
160
|
+
|
161
|
+
def print_field_help(self):
|
162
|
+
"""Print helpful field information for developers."""
|
163
|
+
print(f"๐ {self.__class__.__name__} Configuration Fields:")
|
164
|
+
print("=" * 60)
|
165
|
+
|
166
|
+
for field_name, info in self.get_field_info().items():
|
167
|
+
current_value = getattr(self, field_name, None)
|
168
|
+
|
169
|
+
# Hide sensitive values
|
170
|
+
if any(word in field_name.lower() for word in ['secret', 'password', 'key', 'token']):
|
171
|
+
display_value = "***HIDDEN***" if current_value else "Not set"
|
172
|
+
else:
|
173
|
+
display_value = current_value
|
174
|
+
|
175
|
+
print(f"๐ง {field_name}:")
|
176
|
+
print(f" ๐ {info['description']}")
|
177
|
+
print(f" ๐ท๏ธ Type: {info['type']}")
|
178
|
+
print(f" ๐ Env: {info['env_var']}")
|
179
|
+
print(f" ๐พ Current: {display_value}")
|
180
|
+
if info['default'] is not None:
|
181
|
+
print(f" ๐ฏ Default: {info['default']}")
|
182
|
+
if info['required']:
|
183
|
+
print(f" โ ๏ธ Required: Yes")
|
184
|
+
print()
|
185
|
+
|
186
|
+
@classmethod
|
187
|
+
def create_env_example(cls, filename: str = ".env.example") -> None:
|
188
|
+
"""
|
189
|
+
๐ Create example .env file for developers
|
190
|
+
|
191
|
+
This generates a complete .env.example file with all fields,
|
192
|
+
descriptions, and example values.
|
193
|
+
"""
|
194
|
+
lines = [
|
195
|
+
"# ๐ Django Configuration Environment Variables",
|
196
|
+
"# Generated by Django Config Toolkit",
|
197
|
+
"# Copy this file to .env and customize your settings",
|
198
|
+
"",
|
199
|
+
f"# === {cls.__name__} Configuration ===",
|
200
|
+
"",
|
201
|
+
]
|
202
|
+
|
203
|
+
# Create temporary instance to get field info
|
204
|
+
try:
|
205
|
+
temp_instance = cls()
|
206
|
+
field_info = temp_instance.get_field_info()
|
207
|
+
except:
|
208
|
+
# Fallback if instance creation fails
|
209
|
+
field_info = {}
|
210
|
+
for field_name, field in cls.model_fields.items():
|
211
|
+
field_info[field_name] = {
|
212
|
+
'description': field.description or f"Configure {field_name}",
|
213
|
+
'default': field.default if field.default is not ... else None,
|
214
|
+
'env_var': field_name.upper(),
|
215
|
+
}
|
216
|
+
|
217
|
+
for field_name, info in field_info.items():
|
218
|
+
# Add description
|
219
|
+
lines.append(f"# {info['description']}")
|
220
|
+
|
221
|
+
# Generate example value
|
222
|
+
default_val = info.get('default')
|
223
|
+
if default_val is not None:
|
224
|
+
example_value = default_val
|
225
|
+
elif 'secret' in field_name.lower() or 'key' in field_name.lower():
|
226
|
+
example_value = "your-secret-key-change-this-to-something-secure"
|
227
|
+
elif 'password' in field_name.lower():
|
228
|
+
example_value = "your-secure-password"
|
229
|
+
elif 'url' in field_name.lower():
|
230
|
+
if 'database' in field_name.lower():
|
231
|
+
example_value = "postgresql://user:password@localhost:5432/dbname"
|
232
|
+
elif 'redis' in field_name.lower():
|
233
|
+
example_value = "redis://localhost:6379/0"
|
234
|
+
else:
|
235
|
+
example_value = "https://example.com"
|
236
|
+
elif 'debug' in field_name.lower():
|
237
|
+
example_value = "true"
|
238
|
+
elif 'port' in field_name.lower():
|
239
|
+
example_value = "5432"
|
240
|
+
elif 'timeout' in field_name.lower():
|
241
|
+
example_value = "30"
|
242
|
+
else:
|
243
|
+
example_value = "change-me"
|
244
|
+
|
245
|
+
# Add environment variable
|
246
|
+
lines.append(f"{info['env_var']}={example_value}")
|
247
|
+
lines.append("")
|
248
|
+
|
249
|
+
# Write file
|
250
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
251
|
+
f.write('\n'.join(lines))
|
252
|
+
|
253
|
+
print(f"โ
Created example environment file: {filename}")
|
254
|
+
print(f"๐ก Copy it to .env and customize your settings!")
|
255
|
+
print(f"๐ More info: https://django-config-toolkit.readthedocs.io/")
|
256
|
+
|
257
|
+
def validate_for_environment(self, environment: str = "development") -> bool:
|
258
|
+
"""
|
259
|
+
๐งช Validate configuration for specific environment
|
260
|
+
|
261
|
+
Args:
|
262
|
+
environment: Target environment (development/production/testing)
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
True if configuration is valid for the environment
|
266
|
+
"""
|
267
|
+
try:
|
268
|
+
# Perform environment-specific validation
|
269
|
+
if environment == "production":
|
270
|
+
return self._validate_production()
|
271
|
+
elif environment == "development":
|
272
|
+
return self._validate_development()
|
273
|
+
else:
|
274
|
+
return True
|
275
|
+
except Exception as e:
|
276
|
+
print(f"โ Validation failed for {environment}: {e}")
|
277
|
+
return False
|
278
|
+
|
279
|
+
def _validate_production(self) -> bool:
|
280
|
+
"""Validate production-specific requirements."""
|
281
|
+
# Override in subclasses for specific validation
|
282
|
+
return True
|
283
|
+
|
284
|
+
def _validate_development(self) -> bool:
|
285
|
+
"""Validate development-specific requirements."""
|
286
|
+
# Override in subclasses for specific validation
|
287
|
+
return True
|
django_cfg/models/cache.py
CHANGED
@@ -12,10 +12,10 @@ from typing import Dict, Optional, Any, Literal, Union
|
|
12
12
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
13
13
|
from urllib.parse import urlparse
|
14
14
|
|
15
|
-
from django_cfg.exceptions import CacheError, ValidationError
|
15
|
+
from django_cfg.core.exceptions import CacheError, ValidationError
|
16
16
|
|
17
17
|
|
18
|
-
class
|
18
|
+
class CacheConfig(BaseModel):
|
19
19
|
"""
|
20
20
|
Type-safe cache backend configuration.
|
21
21
|
|
@@ -160,7 +160,7 @@ class CacheBackend(BaseModel):
|
|
160
160
|
return v
|
161
161
|
|
162
162
|
@model_validator(mode='after')
|
163
|
-
def validate_configuration_consistency(self) -> '
|
163
|
+
def validate_configuration_consistency(self) -> 'CacheConfig':
|
164
164
|
"""Validate cache configuration consistency."""
|
165
165
|
# Warn about compression with JSON serializer
|
166
166
|
if self.compress and self.serializer == "json":
|
@@ -336,5 +336,5 @@ class CacheBackend(BaseModel):
|
|
336
336
|
|
337
337
|
# Export all models
|
338
338
|
__all__ = [
|
339
|
-
"
|
339
|
+
"CacheConfig",
|
340
340
|
]
|