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
@@ -0,0 +1,276 @@
|
|
1
|
+
"""
|
2
|
+
Environment Configuration Model
|
3
|
+
|
4
|
+
Core Django environment settings with Pydantic 2.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import List, Dict, Any
|
10
|
+
from pydantic import Field, field_validator, computed_field
|
11
|
+
from .base import BaseConfig
|
12
|
+
|
13
|
+
|
14
|
+
class EnvironmentConfig(BaseConfig):
|
15
|
+
"""
|
16
|
+
🌍 Environment Configuration - Core Django Settings
|
17
|
+
|
18
|
+
Handles all core Django environment settings with smart defaults
|
19
|
+
and automatic environment detection.
|
20
|
+
"""
|
21
|
+
|
22
|
+
# Core Django settings
|
23
|
+
debug: bool = Field(
|
24
|
+
default=False, description="Enable Django debug mode (True for development)"
|
25
|
+
)
|
26
|
+
|
27
|
+
secret_key: str = Field(
|
28
|
+
min_length=50,
|
29
|
+
description="Django secret key (minimum 50 characters for security)",
|
30
|
+
)
|
31
|
+
|
32
|
+
allowed_hosts: List[str] = Field(
|
33
|
+
default_factory=list, description="List of allowed hosts for Django"
|
34
|
+
)
|
35
|
+
|
36
|
+
# Environment detection
|
37
|
+
environment: str = Field(
|
38
|
+
default="development",
|
39
|
+
description="Environment name (development/production/staging/testing)",
|
40
|
+
)
|
41
|
+
|
42
|
+
# Path settings
|
43
|
+
base_dir: Path = Field(
|
44
|
+
default_factory=lambda: Path.cwd(), description="Application base directory"
|
45
|
+
)
|
46
|
+
|
47
|
+
# Django User Model
|
48
|
+
auth_user_model: str = Field(
|
49
|
+
default="django.contrib.auth.models.User",
|
50
|
+
description="Django user model to use (e.g., 'accounts.CustomUser')",
|
51
|
+
)
|
52
|
+
|
53
|
+
@field_validator("secret_key")
|
54
|
+
@classmethod
|
55
|
+
def validate_secret_key(cls, v: str) -> str:
|
56
|
+
"""Ensure secret key is secure."""
|
57
|
+
if len(v) < 50:
|
58
|
+
raise ValueError(
|
59
|
+
"Secret key must be at least 50 characters long for security"
|
60
|
+
)
|
61
|
+
|
62
|
+
# Check for common insecure values
|
63
|
+
insecure_keys = [
|
64
|
+
"change-me",
|
65
|
+
"your-secret-key-here",
|
66
|
+
"dev-secret-key",
|
67
|
+
"django-insecure-",
|
68
|
+
"your-secret-key-change-this",
|
69
|
+
]
|
70
|
+
|
71
|
+
for insecure in insecure_keys:
|
72
|
+
if insecure in v.lower():
|
73
|
+
raise ValueError(
|
74
|
+
f"Please change the secret key from default/example value"
|
75
|
+
)
|
76
|
+
|
77
|
+
return v
|
78
|
+
|
79
|
+
@field_validator("allowed_hosts", mode="before")
|
80
|
+
@classmethod
|
81
|
+
def set_default_allowed_hosts(cls, v, info):
|
82
|
+
"""Set smart default allowed hosts based on debug mode."""
|
83
|
+
if not v: # If empty list or None
|
84
|
+
# Try to get debug from field being validated
|
85
|
+
debug = info.data.get("debug", False)
|
86
|
+
|
87
|
+
if debug:
|
88
|
+
return ["localhost", "127.0.0.1", "0.0.0.0", "*"]
|
89
|
+
else:
|
90
|
+
return []
|
91
|
+
return v
|
92
|
+
|
93
|
+
@field_validator("environment")
|
94
|
+
@classmethod
|
95
|
+
def validate_environment(cls, v: str) -> str:
|
96
|
+
"""Validate environment name."""
|
97
|
+
allowed = ["development", "production", "testing", "staging"]
|
98
|
+
if v not in allowed:
|
99
|
+
raise ValueError(f"Environment must be one of: {allowed}")
|
100
|
+
return v
|
101
|
+
|
102
|
+
# Computed properties for easy access
|
103
|
+
@computed_field
|
104
|
+
@property
|
105
|
+
def is_production(self) -> bool:
|
106
|
+
"""True if running in production environment."""
|
107
|
+
return self.environment == "production" or not self.debug
|
108
|
+
|
109
|
+
@computed_field
|
110
|
+
@property
|
111
|
+
def is_development(self) -> bool:
|
112
|
+
"""True if running in development environment."""
|
113
|
+
return self.environment == "development" or self.debug
|
114
|
+
|
115
|
+
@computed_field
|
116
|
+
@property
|
117
|
+
def is_testing(self) -> bool:
|
118
|
+
"""True if running in testing environment."""
|
119
|
+
return self.environment == "testing"
|
120
|
+
|
121
|
+
@computed_field
|
122
|
+
@property
|
123
|
+
def is_staging(self) -> bool:
|
124
|
+
"""True if running in staging environment."""
|
125
|
+
return self.environment == "staging"
|
126
|
+
|
127
|
+
@computed_field
|
128
|
+
@property
|
129
|
+
def is_docker(self) -> bool:
|
130
|
+
"""True if running in Docker container."""
|
131
|
+
return os.path.exists("/.dockerenv")
|
132
|
+
|
133
|
+
# Computed paths
|
134
|
+
@computed_field
|
135
|
+
@property
|
136
|
+
def static_dir(self) -> Path:
|
137
|
+
"""Static files directory."""
|
138
|
+
return self.base_dir / "static"
|
139
|
+
|
140
|
+
@computed_field
|
141
|
+
@property
|
142
|
+
def media_dir(self) -> Path:
|
143
|
+
"""Media files directory."""
|
144
|
+
return self.base_dir / "media"
|
145
|
+
|
146
|
+
@computed_field
|
147
|
+
@property
|
148
|
+
def templates_dir(self) -> Path:
|
149
|
+
"""Templates directory."""
|
150
|
+
return self.base_dir / "templates"
|
151
|
+
|
152
|
+
@computed_field
|
153
|
+
@property
|
154
|
+
def logs_dir(self) -> Path:
|
155
|
+
"""Logs directory."""
|
156
|
+
return self.base_dir / "logs"
|
157
|
+
|
158
|
+
def _validate_production(self) -> bool:
|
159
|
+
"""Validate production-specific requirements."""
|
160
|
+
errors = []
|
161
|
+
|
162
|
+
# Check secret key security
|
163
|
+
if len(self.secret_key) < 50:
|
164
|
+
errors.append("Secret key must be at least 50 characters in production")
|
165
|
+
|
166
|
+
# Check debug is disabled
|
167
|
+
if self.debug:
|
168
|
+
errors.append("Debug mode must be disabled in production")
|
169
|
+
|
170
|
+
# Check allowed hosts
|
171
|
+
if "*" in self.allowed_hosts:
|
172
|
+
errors.append("Wildcard '*' in ALLOWED_HOSTS is not secure for production")
|
173
|
+
|
174
|
+
if errors:
|
175
|
+
print("❌ Production validation errors:")
|
176
|
+
for error in errors:
|
177
|
+
print(f" - {error}")
|
178
|
+
return False
|
179
|
+
|
180
|
+
return True
|
181
|
+
|
182
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
183
|
+
"""Convert to Django settings with smart defaults."""
|
184
|
+
return {
|
185
|
+
# Core Django settings
|
186
|
+
"DEBUG": self.debug,
|
187
|
+
"SECRET_KEY": self.secret_key,
|
188
|
+
"ALLOWED_HOSTS": self.allowed_hosts,
|
189
|
+
# Paths
|
190
|
+
"BASE_DIR": self.base_dir,
|
191
|
+
# URL and WSGI/ASGI configuration
|
192
|
+
"ROOT_URLCONF": "api.urls",
|
193
|
+
"WSGI_APPLICATION": "api.wsgi.application",
|
194
|
+
"ASGI_APPLICATION": "api.asgi.application",
|
195
|
+
# Static files configuration
|
196
|
+
"STATIC_URL": "/static/",
|
197
|
+
"STATIC_ROOT": self.static_dir,
|
198
|
+
"STATICFILES_STORAGE": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
199
|
+
"WHITENOISE_USE_FINDERS": True,
|
200
|
+
"WHITENOISE_AUTOREFRESH": self.debug,
|
201
|
+
"WHITENOISE_MAX_AGE": 31536000, # 1 year
|
202
|
+
# Media files configuration
|
203
|
+
"MEDIA_URL": "/media/",
|
204
|
+
"MEDIA_ROOT": self.media_dir,
|
205
|
+
# Basic Django apps (can be extended by other configs)
|
206
|
+
"INSTALLED_APPS": [
|
207
|
+
"django.contrib.admin",
|
208
|
+
"django.contrib.auth",
|
209
|
+
"django.contrib.contenttypes",
|
210
|
+
"django.contrib.sessions",
|
211
|
+
"django.contrib.messages",
|
212
|
+
"django.contrib.staticfiles",
|
213
|
+
"django.contrib.humanize",
|
214
|
+
"django.contrib.sites",
|
215
|
+
"django.contrib.sitemaps",
|
216
|
+
],
|
217
|
+
# User model
|
218
|
+
"AUTH_USER_MODEL": self.auth_user_model,
|
219
|
+
# Basic middleware (can be extended by other configs)
|
220
|
+
"MIDDLEWARE": [
|
221
|
+
"django.middleware.security.SecurityMiddleware",
|
222
|
+
"whitenoise.middleware.WhiteNoiseMiddleware",
|
223
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
224
|
+
"django.middleware.common.CommonMiddleware",
|
225
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
226
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
227
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
228
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
229
|
+
],
|
230
|
+
# Templates configuration
|
231
|
+
"TEMPLATES": [
|
232
|
+
{
|
233
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
234
|
+
"DIRS": [self.templates_dir],
|
235
|
+
"APP_DIRS": True,
|
236
|
+
"OPTIONS": {
|
237
|
+
"context_processors": [
|
238
|
+
"django.template.context_processors.debug",
|
239
|
+
"django.template.context_processors.request",
|
240
|
+
"django.contrib.auth.context_processors.auth",
|
241
|
+
"django.contrib.messages.context_processors.messages",
|
242
|
+
],
|
243
|
+
},
|
244
|
+
},
|
245
|
+
],
|
246
|
+
# Auth configuration
|
247
|
+
"AUTH_PASSWORD_VALIDATORS": (
|
248
|
+
[]
|
249
|
+
if self.debug
|
250
|
+
else [
|
251
|
+
{
|
252
|
+
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
253
|
+
},
|
254
|
+
{
|
255
|
+
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
256
|
+
"OPTIONS": {"min_length": 8},
|
257
|
+
},
|
258
|
+
{
|
259
|
+
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
260
|
+
},
|
261
|
+
{
|
262
|
+
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
263
|
+
},
|
264
|
+
]
|
265
|
+
),
|
266
|
+
# Internationalization
|
267
|
+
"LANGUAGE_CODE": "en-us",
|
268
|
+
"TIME_ZONE": "UTC",
|
269
|
+
"USE_I18N": True,
|
270
|
+
"USE_L10N": True,
|
271
|
+
"USE_TZ": True,
|
272
|
+
# Miscellaneous
|
273
|
+
"DEFAULT_AUTO_FIELD": "django.db.models.BigAutoField",
|
274
|
+
"DATA_UPLOAD_MAX_NUMBER_FIELDS": 10000,
|
275
|
+
"SITE_ID": 1,
|
276
|
+
}
|
django_cfg/models/limits.py
CHANGED
@@ -10,7 +10,7 @@ Following CRITICAL_REQUIREMENTS.md:
|
|
10
10
|
|
11
11
|
from typing import Optional, Dict, Any, List
|
12
12
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
13
|
-
from django_cfg.exceptions import ConfigurationError
|
13
|
+
from django_cfg.core.exceptions import ConfigurationError
|
14
14
|
|
15
15
|
|
16
16
|
class LimitsConfig(BaseModel):
|
@@ -0,0 +1,366 @@
|
|
1
|
+
"""
|
2
|
+
Logging Configuration Model
|
3
|
+
|
4
|
+
Django logging settings with Pydantic 2.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, List, Optional
|
8
|
+
from pathlib import Path
|
9
|
+
from pydantic import Field, field_validator
|
10
|
+
from .base import BaseConfig
|
11
|
+
|
12
|
+
|
13
|
+
class LoggerConfig(BaseConfig):
|
14
|
+
"""Configuration for a single logger."""
|
15
|
+
|
16
|
+
name: str = Field(
|
17
|
+
description="Logger name"
|
18
|
+
)
|
19
|
+
|
20
|
+
level: str = Field(
|
21
|
+
default="INFO",
|
22
|
+
description="Log level"
|
23
|
+
)
|
24
|
+
|
25
|
+
handlers: List[str] = Field(
|
26
|
+
default_factory=list,
|
27
|
+
description="Handler names for this logger"
|
28
|
+
)
|
29
|
+
|
30
|
+
propagate: bool = Field(
|
31
|
+
default=True,
|
32
|
+
description="Whether to propagate to parent loggers"
|
33
|
+
)
|
34
|
+
|
35
|
+
@field_validator('level')
|
36
|
+
@classmethod
|
37
|
+
def validate_level(cls, v: str) -> str:
|
38
|
+
"""Validate log level."""
|
39
|
+
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
40
|
+
if v.upper() not in valid_levels:
|
41
|
+
raise ValueError(f"Log level must be one of: {valid_levels}")
|
42
|
+
return v.upper()
|
43
|
+
|
44
|
+
|
45
|
+
class HandlerConfig(BaseConfig):
|
46
|
+
"""Configuration for a log handler."""
|
47
|
+
|
48
|
+
name: str = Field(
|
49
|
+
description="Handler name"
|
50
|
+
)
|
51
|
+
|
52
|
+
class_name: str = Field(
|
53
|
+
description="Handler class"
|
54
|
+
)
|
55
|
+
|
56
|
+
level: str = Field(
|
57
|
+
default="INFO",
|
58
|
+
description="Handler log level"
|
59
|
+
)
|
60
|
+
|
61
|
+
formatter: str = Field(
|
62
|
+
default="verbose",
|
63
|
+
description="Formatter name"
|
64
|
+
)
|
65
|
+
|
66
|
+
filename: Optional[str] = Field(
|
67
|
+
default=None,
|
68
|
+
description="Log file path (for file handlers)"
|
69
|
+
)
|
70
|
+
|
71
|
+
max_bytes: int = Field(
|
72
|
+
default=10485760, # 10MB
|
73
|
+
description="Max bytes for rotating file handler"
|
74
|
+
)
|
75
|
+
|
76
|
+
backup_count: int = Field(
|
77
|
+
default=5,
|
78
|
+
description="Backup count for rotating file handler"
|
79
|
+
)
|
80
|
+
|
81
|
+
@field_validator('level')
|
82
|
+
@classmethod
|
83
|
+
def validate_level(cls, v: str) -> str:
|
84
|
+
"""Validate log level."""
|
85
|
+
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
86
|
+
if v.upper() not in valid_levels:
|
87
|
+
raise ValueError(f"Log level must be one of: {valid_levels}")
|
88
|
+
return v.upper()
|
89
|
+
|
90
|
+
|
91
|
+
class FormatterConfig(BaseConfig):
|
92
|
+
"""Configuration for a log formatter."""
|
93
|
+
|
94
|
+
name: str = Field(
|
95
|
+
description="Formatter name"
|
96
|
+
)
|
97
|
+
|
98
|
+
format_string: str = Field(
|
99
|
+
description="Log format string"
|
100
|
+
)
|
101
|
+
|
102
|
+
date_format: Optional[str] = Field(
|
103
|
+
default=None,
|
104
|
+
description="Date format string"
|
105
|
+
)
|
106
|
+
|
107
|
+
|
108
|
+
class LoggingConfig(BaseConfig):
|
109
|
+
"""
|
110
|
+
📝 Logging Configuration - Structured Logging
|
111
|
+
|
112
|
+
Complete logging configuration with formatters, handlers,
|
113
|
+
and loggers for Django applications.
|
114
|
+
"""
|
115
|
+
|
116
|
+
# Global settings
|
117
|
+
version: int = Field(
|
118
|
+
default=1,
|
119
|
+
description="Logging config version"
|
120
|
+
)
|
121
|
+
|
122
|
+
disable_existing_loggers: bool = Field(
|
123
|
+
default=False,
|
124
|
+
description="Disable existing loggers"
|
125
|
+
)
|
126
|
+
|
127
|
+
# Log level settings
|
128
|
+
root_level: str = Field(
|
129
|
+
default="INFO",
|
130
|
+
description="Root logger level"
|
131
|
+
)
|
132
|
+
|
133
|
+
django_level: str = Field(
|
134
|
+
default="INFO",
|
135
|
+
description="Django logger level"
|
136
|
+
)
|
137
|
+
|
138
|
+
# File settings
|
139
|
+
log_dir: Optional[str] = Field(
|
140
|
+
default=None,
|
141
|
+
description="Directory for log files"
|
142
|
+
)
|
143
|
+
|
144
|
+
# Console settings
|
145
|
+
console_enabled: bool = Field(
|
146
|
+
default=True,
|
147
|
+
description="Enable console logging"
|
148
|
+
)
|
149
|
+
|
150
|
+
# File logging settings
|
151
|
+
file_enabled: bool = Field(
|
152
|
+
default=True,
|
153
|
+
description="Enable file logging"
|
154
|
+
)
|
155
|
+
|
156
|
+
# Rotating file settings
|
157
|
+
rotating_enabled: bool = Field(
|
158
|
+
default=True,
|
159
|
+
description="Enable rotating file logging"
|
160
|
+
)
|
161
|
+
|
162
|
+
max_file_size: int = Field(
|
163
|
+
default=10485760, # 10MB
|
164
|
+
description="Maximum log file size in bytes"
|
165
|
+
)
|
166
|
+
|
167
|
+
backup_count: int = Field(
|
168
|
+
default=5,
|
169
|
+
description="Number of backup log files to keep"
|
170
|
+
)
|
171
|
+
|
172
|
+
# Custom loggers, handlers, formatters
|
173
|
+
custom_loggers: List[LoggerConfig] = Field(
|
174
|
+
default_factory=list,
|
175
|
+
description="Custom logger configurations"
|
176
|
+
)
|
177
|
+
|
178
|
+
custom_handlers: List[HandlerConfig] = Field(
|
179
|
+
default_factory=list,
|
180
|
+
description="Custom handler configurations"
|
181
|
+
)
|
182
|
+
|
183
|
+
custom_formatters: List[FormatterConfig] = Field(
|
184
|
+
default_factory=list,
|
185
|
+
description="Custom formatter configurations"
|
186
|
+
)
|
187
|
+
|
188
|
+
@field_validator('root_level', 'django_level')
|
189
|
+
@classmethod
|
190
|
+
def validate_levels(cls, v: str) -> str:
|
191
|
+
"""Validate log levels."""
|
192
|
+
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
193
|
+
if v.upper() not in valid_levels:
|
194
|
+
raise ValueError(f"Log level must be one of: {valid_levels}")
|
195
|
+
return v.upper()
|
196
|
+
|
197
|
+
def get_log_directory(self) -> Path:
|
198
|
+
"""Get log directory path."""
|
199
|
+
if self.log_dir:
|
200
|
+
return Path(self.log_dir)
|
201
|
+
|
202
|
+
# Default to logs/ in current directory
|
203
|
+
return Path.cwd() / "logs"
|
204
|
+
|
205
|
+
def get_default_formatters(self) -> Dict[str, Any]:
|
206
|
+
"""Get default log formatters."""
|
207
|
+
formatters = {
|
208
|
+
'verbose': {
|
209
|
+
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
|
210
|
+
'style': '{',
|
211
|
+
'datefmt': '%Y-%m-%d %H:%M:%S',
|
212
|
+
},
|
213
|
+
'simple': {
|
214
|
+
'format': '{levelname} {message}',
|
215
|
+
'style': '{',
|
216
|
+
},
|
217
|
+
'django.server': {
|
218
|
+
'format': '{asctime} {message}',
|
219
|
+
'style': '{',
|
220
|
+
'datefmt': '%Y-%m-%d %H:%M:%S',
|
221
|
+
},
|
222
|
+
}
|
223
|
+
|
224
|
+
# Add custom formatters
|
225
|
+
for formatter in self.custom_formatters:
|
226
|
+
formatters[formatter.name] = {
|
227
|
+
'format': formatter.format_string,
|
228
|
+
'style': '{',
|
229
|
+
}
|
230
|
+
if formatter.date_format:
|
231
|
+
formatters[formatter.name]['datefmt'] = formatter.date_format
|
232
|
+
|
233
|
+
return formatters
|
234
|
+
|
235
|
+
def get_default_handlers(self) -> Dict[str, Any]:
|
236
|
+
"""Get default log handlers."""
|
237
|
+
handlers = {}
|
238
|
+
|
239
|
+
# Console handler
|
240
|
+
if self.console_enabled:
|
241
|
+
handlers['console'] = {
|
242
|
+
'level': 'INFO',
|
243
|
+
'class': 'logging.StreamHandler',
|
244
|
+
'formatter': 'simple',
|
245
|
+
}
|
246
|
+
|
247
|
+
# File handler
|
248
|
+
if self.file_enabled:
|
249
|
+
log_dir = self.get_log_directory()
|
250
|
+
log_dir.mkdir(exist_ok=True)
|
251
|
+
|
252
|
+
handlers['file'] = {
|
253
|
+
'level': 'INFO',
|
254
|
+
'class': 'logging.FileHandler',
|
255
|
+
'filename': str(log_dir / 'django.log'),
|
256
|
+
'formatter': 'verbose',
|
257
|
+
}
|
258
|
+
|
259
|
+
# Rotating file handler
|
260
|
+
if self.rotating_enabled:
|
261
|
+
log_dir = self.get_log_directory()
|
262
|
+
log_dir.mkdir(exist_ok=True)
|
263
|
+
|
264
|
+
handlers['rotating_file'] = {
|
265
|
+
'level': 'INFO',
|
266
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
267
|
+
'filename': str(log_dir / 'django_rotating.log'),
|
268
|
+
'maxBytes': self.max_file_size,
|
269
|
+
'backupCount': self.backup_count,
|
270
|
+
'formatter': 'verbose',
|
271
|
+
}
|
272
|
+
|
273
|
+
# Error file handler
|
274
|
+
if self.file_enabled:
|
275
|
+
log_dir = self.get_log_directory()
|
276
|
+
log_dir.mkdir(exist_ok=True)
|
277
|
+
|
278
|
+
handlers['error_file'] = {
|
279
|
+
'level': 'ERROR',
|
280
|
+
'class': 'logging.FileHandler',
|
281
|
+
'filename': str(log_dir / 'django_errors.log'),
|
282
|
+
'formatter': 'verbose',
|
283
|
+
}
|
284
|
+
|
285
|
+
# Add custom handlers
|
286
|
+
for handler in self.custom_handlers:
|
287
|
+
handler_config = {
|
288
|
+
'level': handler.level,
|
289
|
+
'class': handler.class_name,
|
290
|
+
'formatter': handler.formatter,
|
291
|
+
}
|
292
|
+
|
293
|
+
if handler.filename:
|
294
|
+
handler_config['filename'] = handler.filename
|
295
|
+
|
296
|
+
if 'RotatingFileHandler' in handler.class_name:
|
297
|
+
handler_config['maxBytes'] = handler.max_bytes
|
298
|
+
handler_config['backupCount'] = handler.backup_count
|
299
|
+
|
300
|
+
handlers[handler.name] = handler_config
|
301
|
+
|
302
|
+
return handlers
|
303
|
+
|
304
|
+
def get_default_loggers(self) -> Dict[str, Any]:
|
305
|
+
"""Get default logger configurations."""
|
306
|
+
# Determine available handlers
|
307
|
+
available_handlers = []
|
308
|
+
if self.console_enabled:
|
309
|
+
available_handlers.append('console')
|
310
|
+
if self.file_enabled:
|
311
|
+
available_handlers.extend(['file', 'error_file'])
|
312
|
+
if self.rotating_enabled:
|
313
|
+
available_handlers.append('rotating_file')
|
314
|
+
|
315
|
+
loggers = {
|
316
|
+
'django': {
|
317
|
+
'handlers': available_handlers,
|
318
|
+
'level': self.django_level,
|
319
|
+
'propagate': False,
|
320
|
+
},
|
321
|
+
'django.request': {
|
322
|
+
'handlers': available_handlers,
|
323
|
+
'level': 'ERROR',
|
324
|
+
'propagate': False,
|
325
|
+
},
|
326
|
+
'django.server': {
|
327
|
+
'handlers': ['console'] if self.console_enabled else [],
|
328
|
+
'level': 'INFO',
|
329
|
+
'propagate': False,
|
330
|
+
},
|
331
|
+
}
|
332
|
+
|
333
|
+
# Add custom loggers
|
334
|
+
for logger in self.custom_loggers:
|
335
|
+
loggers[logger.name] = {
|
336
|
+
'handlers': logger.handlers,
|
337
|
+
'level': logger.level,
|
338
|
+
'propagate': logger.propagate,
|
339
|
+
}
|
340
|
+
|
341
|
+
return loggers
|
342
|
+
|
343
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
344
|
+
"""Convert to Django LOGGING setting."""
|
345
|
+
# Determine root handlers
|
346
|
+
root_handlers = []
|
347
|
+
if self.console_enabled:
|
348
|
+
root_handlers.append('console')
|
349
|
+
if self.file_enabled:
|
350
|
+
root_handlers.append('file')
|
351
|
+
|
352
|
+
logging_config = {
|
353
|
+
'version': self.version,
|
354
|
+
'disable_existing_loggers': self.disable_existing_loggers,
|
355
|
+
'formatters': self.get_default_formatters(),
|
356
|
+
'handlers': self.get_default_handlers(),
|
357
|
+
'root': {
|
358
|
+
'level': self.root_level,
|
359
|
+
'handlers': root_handlers,
|
360
|
+
},
|
361
|
+
'loggers': self.get_default_loggers(),
|
362
|
+
}
|
363
|
+
|
364
|
+
return {
|
365
|
+
'LOGGING': logging_config
|
366
|
+
}
|