django-cfg 1.1.81__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.
Files changed (246) hide show
  1. django_cfg/__init__.py +20 -448
  2. django_cfg/apps/accounts/README.md +3 -3
  3. django_cfg/apps/accounts/admin/__init__.py +0 -2
  4. django_cfg/apps/accounts/admin/activity.py +2 -9
  5. django_cfg/apps/accounts/admin/filters.py +0 -42
  6. django_cfg/apps/accounts/admin/inlines.py +8 -8
  7. django_cfg/apps/accounts/admin/otp.py +5 -5
  8. django_cfg/apps/accounts/admin/registration_source.py +1 -8
  9. django_cfg/apps/accounts/admin/user.py +12 -20
  10. django_cfg/apps/accounts/managers/user_manager.py +2 -129
  11. django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
  12. django_cfg/apps/accounts/models.py +3 -123
  13. django_cfg/apps/accounts/serializers/otp.py +40 -44
  14. django_cfg/apps/accounts/serializers/profile.py +0 -2
  15. django_cfg/apps/accounts/services/otp_service.py +98 -186
  16. django_cfg/apps/accounts/signals.py +25 -15
  17. django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
  18. django_cfg/apps/accounts/views/otp.py +35 -36
  19. django_cfg/apps/agents/README.md +129 -0
  20. django_cfg/apps/agents/__init__.py +68 -0
  21. django_cfg/apps/agents/admin/__init__.py +17 -0
  22. django_cfg/apps/agents/admin/execution_admin.py +460 -0
  23. django_cfg/apps/agents/admin/registry_admin.py +360 -0
  24. django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
  25. django_cfg/apps/agents/apps.py +29 -0
  26. django_cfg/apps/agents/core/__init__.py +20 -0
  27. django_cfg/apps/agents/core/agent.py +281 -0
  28. django_cfg/apps/agents/core/dependencies.py +154 -0
  29. django_cfg/apps/agents/core/exceptions.py +66 -0
  30. django_cfg/apps/agents/core/models.py +106 -0
  31. django_cfg/apps/agents/core/orchestrator.py +391 -0
  32. django_cfg/apps/agents/examples/__init__.py +3 -0
  33. django_cfg/apps/agents/examples/simple_example.py +161 -0
  34. django_cfg/apps/agents/integration/__init__.py +14 -0
  35. django_cfg/apps/agents/integration/middleware.py +80 -0
  36. django_cfg/apps/agents/integration/registry.py +345 -0
  37. django_cfg/apps/agents/integration/signals.py +50 -0
  38. django_cfg/apps/agents/management/__init__.py +3 -0
  39. django_cfg/apps/agents/management/commands/__init__.py +3 -0
  40. django_cfg/apps/agents/management/commands/create_agent.py +365 -0
  41. django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
  42. django_cfg/apps/agents/managers/__init__.py +23 -0
  43. django_cfg/apps/agents/managers/execution.py +236 -0
  44. django_cfg/apps/agents/managers/registry.py +254 -0
  45. django_cfg/apps/agents/managers/toolsets.py +496 -0
  46. django_cfg/apps/agents/migrations/0001_initial.py +286 -0
  47. django_cfg/apps/agents/migrations/__init__.py +5 -0
  48. django_cfg/apps/agents/models/__init__.py +15 -0
  49. django_cfg/apps/agents/models/execution.py +215 -0
  50. django_cfg/apps/agents/models/registry.py +220 -0
  51. django_cfg/apps/agents/models/toolsets.py +305 -0
  52. django_cfg/apps/agents/patterns/__init__.py +24 -0
  53. django_cfg/apps/agents/patterns/content_agents.py +234 -0
  54. django_cfg/apps/agents/toolsets/__init__.py +15 -0
  55. django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
  56. django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
  57. django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
  58. django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
  59. django_cfg/apps/agents/urls.py +46 -0
  60. django_cfg/apps/knowbase/README.md +150 -0
  61. django_cfg/apps/knowbase/__init__.py +27 -0
  62. django_cfg/apps/knowbase/admin/__init__.py +23 -0
  63. django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
  64. django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
  65. django_cfg/apps/knowbase/admin/document_admin.py +650 -0
  66. django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
  67. django_cfg/apps/knowbase/apps.py +81 -0
  68. django_cfg/apps/knowbase/config/README.md +176 -0
  69. django_cfg/apps/knowbase/config/__init__.py +51 -0
  70. django_cfg/apps/knowbase/config/constance_fields.py +186 -0
  71. django_cfg/apps/knowbase/config/constance_settings.py +200 -0
  72. django_cfg/apps/knowbase/config/settings.py +444 -0
  73. django_cfg/apps/knowbase/examples/__init__.py +3 -0
  74. django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
  75. django_cfg/apps/knowbase/management/__init__.py +0 -0
  76. django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
  77. django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
  78. django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
  79. django_cfg/apps/knowbase/managers/__init__.py +22 -0
  80. django_cfg/apps/knowbase/managers/archive.py +426 -0
  81. django_cfg/apps/knowbase/managers/base.py +32 -0
  82. django_cfg/apps/knowbase/managers/chat.py +141 -0
  83. django_cfg/apps/knowbase/managers/document.py +203 -0
  84. django_cfg/apps/knowbase/managers/external_data.py +471 -0
  85. django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
  86. django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
  87. django_cfg/apps/knowbase/migrations/__init__.py +5 -0
  88. django_cfg/apps/knowbase/mixins/__init__.py +15 -0
  89. django_cfg/apps/knowbase/mixins/config.py +108 -0
  90. django_cfg/apps/knowbase/mixins/creator.py +81 -0
  91. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
  92. django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
  93. django_cfg/apps/knowbase/mixins/service.py +362 -0
  94. django_cfg/apps/knowbase/models/__init__.py +41 -0
  95. django_cfg/apps/knowbase/models/archive.py +599 -0
  96. django_cfg/apps/knowbase/models/base.py +58 -0
  97. django_cfg/apps/knowbase/models/chat.py +157 -0
  98. django_cfg/apps/knowbase/models/document.py +267 -0
  99. django_cfg/apps/knowbase/models/external_data.py +376 -0
  100. django_cfg/apps/knowbase/serializers/__init__.py +68 -0
  101. django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
  102. django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
  103. django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
  104. django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
  105. django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
  106. django_cfg/apps/knowbase/services/__init__.py +40 -0
  107. django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
  108. django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
  109. django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
  110. django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
  111. django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
  112. django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
  113. django_cfg/apps/knowbase/services/base.py +53 -0
  114. django_cfg/apps/knowbase/services/chat_service.py +239 -0
  115. django_cfg/apps/knowbase/services/document_service.py +144 -0
  116. django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
  117. django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
  118. django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
  119. django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
  120. django_cfg/apps/knowbase/services/embedding/models.py +229 -0
  121. django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
  122. django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
  123. django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
  124. django_cfg/apps/knowbase/services/search_service.py +293 -0
  125. django_cfg/apps/knowbase/signals/__init__.py +21 -0
  126. django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
  127. django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
  128. django_cfg/apps/knowbase/signals/document_signals.py +143 -0
  129. django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
  130. django_cfg/apps/knowbase/tasks/__init__.py +39 -0
  131. django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
  132. django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
  133. django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
  134. django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
  135. django_cfg/apps/knowbase/urls.py +43 -0
  136. django_cfg/apps/knowbase/utils/__init__.py +12 -0
  137. django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
  138. django_cfg/apps/knowbase/utils/text_processing.py +375 -0
  139. django_cfg/apps/knowbase/utils/validation.py +99 -0
  140. django_cfg/apps/knowbase/views/__init__.py +28 -0
  141. django_cfg/apps/knowbase/views/archive_views.py +469 -0
  142. django_cfg/apps/knowbase/views/base.py +49 -0
  143. django_cfg/apps/knowbase/views/chat_views.py +181 -0
  144. django_cfg/apps/knowbase/views/document_views.py +183 -0
  145. django_cfg/apps/knowbase/views/public_views.py +129 -0
  146. django_cfg/apps/leads/admin.py +70 -0
  147. django_cfg/apps/newsletter/admin.py +234 -0
  148. django_cfg/apps/newsletter/admin_filters.py +124 -0
  149. django_cfg/apps/support/admin.py +196 -0
  150. django_cfg/apps/support/admin_filters.py +71 -0
  151. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  152. django_cfg/apps/urls.py +5 -4
  153. django_cfg/cli/README.md +1 -1
  154. django_cfg/cli/commands/create_project.py +2 -2
  155. django_cfg/cli/commands/info.py +1 -1
  156. django_cfg/config.py +44 -0
  157. django_cfg/core/config.py +29 -82
  158. django_cfg/core/environment.py +1 -1
  159. django_cfg/core/generation.py +19 -107
  160. django_cfg/{integration.py → core/integration.py} +18 -16
  161. django_cfg/core/validation.py +1 -1
  162. django_cfg/management/__init__.py +1 -1
  163. django_cfg/management/commands/__init__.py +1 -1
  164. django_cfg/management/commands/auto_generate.py +482 -0
  165. django_cfg/management/commands/migrator.py +19 -101
  166. django_cfg/management/commands/test_email.py +1 -1
  167. django_cfg/middleware/README.md +0 -158
  168. django_cfg/middleware/__init__.py +0 -2
  169. django_cfg/middleware/user_activity.py +3 -3
  170. django_cfg/models/api.py +145 -0
  171. django_cfg/models/base.py +287 -0
  172. django_cfg/models/cache.py +4 -4
  173. django_cfg/models/constance.py +25 -88
  174. django_cfg/models/database.py +9 -9
  175. django_cfg/models/drf.py +3 -36
  176. django_cfg/models/email.py +163 -0
  177. django_cfg/models/environment.py +276 -0
  178. django_cfg/models/limits.py +1 -1
  179. django_cfg/models/logging.py +366 -0
  180. django_cfg/models/revolution.py +41 -2
  181. django_cfg/models/security.py +125 -0
  182. django_cfg/models/services.py +1 -1
  183. django_cfg/modules/__init__.py +2 -56
  184. django_cfg/modules/base.py +78 -52
  185. django_cfg/modules/django_currency/service.py +2 -2
  186. django_cfg/modules/django_email.py +2 -2
  187. django_cfg/modules/django_health.py +267 -0
  188. django_cfg/modules/django_llm/llm/client.py +79 -17
  189. django_cfg/modules/django_llm/translator/translator.py +2 -2
  190. django_cfg/modules/django_logger.py +2 -2
  191. django_cfg/modules/django_ngrok.py +2 -2
  192. django_cfg/modules/django_tasks.py +68 -3
  193. django_cfg/modules/django_telegram.py +3 -3
  194. django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
  195. django_cfg/modules/django_twilio/service.py +2 -2
  196. django_cfg/modules/django_twilio/simple_service.py +2 -2
  197. django_cfg/modules/django_twilio/templates/guide.md +266 -0
  198. django_cfg/modules/django_twilio/twilio_service.py +2 -2
  199. django_cfg/modules/django_unfold/__init__.py +69 -0
  200. django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
  201. django_cfg/modules/django_unfold/dashboard.py +278 -0
  202. django_cfg/modules/django_unfold/icons/README.md +145 -0
  203. django_cfg/modules/django_unfold/icons/__init__.py +12 -0
  204. django_cfg/modules/django_unfold/icons/constants.py +2851 -0
  205. django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
  206. django_cfg/modules/django_unfold/models/__init__.py +42 -0
  207. django_cfg/modules/django_unfold/models/config.py +601 -0
  208. django_cfg/modules/django_unfold/models/dashboard.py +206 -0
  209. django_cfg/modules/django_unfold/models/dropdown.py +40 -0
  210. django_cfg/modules/django_unfold/models/navigation.py +73 -0
  211. django_cfg/modules/django_unfold/models/tabs.py +25 -0
  212. django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
  213. django_cfg/modules/django_unfold/utils.py +140 -0
  214. django_cfg/registry/__init__.py +23 -0
  215. django_cfg/registry/core.py +61 -0
  216. django_cfg/registry/exceptions.py +11 -0
  217. django_cfg/registry/modules.py +12 -0
  218. django_cfg/registry/services.py +26 -0
  219. django_cfg/registry/third_party.py +52 -0
  220. django_cfg/routing/__init__.py +19 -0
  221. django_cfg/routing/callbacks.py +198 -0
  222. django_cfg/routing/routers.py +48 -0
  223. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
  224. django_cfg/templatetags/__init__.py +0 -0
  225. django_cfg/templatetags/django_cfg.py +33 -0
  226. django_cfg/urls.py +33 -0
  227. django_cfg/utils/path_resolution.py +1 -1
  228. django_cfg/utils/smart_defaults.py +7 -61
  229. django_cfg/utils/toolkit.py +663 -0
  230. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
  231. django_cfg-1.2.0.dist-info/RECORD +441 -0
  232. django_cfg/apps/tasks/@docs/README.md +0 -195
  233. django_cfg/archive/django_sample.zip +0 -0
  234. django_cfg/models/unfold.py +0 -271
  235. django_cfg/modules/unfold/__init__.py +0 -29
  236. django_cfg/modules/unfold/dashboard.py +0 -318
  237. django_cfg/pyproject.toml +0 -370
  238. django_cfg/routers.py +0 -83
  239. django_cfg-1.1.81.dist-info/RECORD +0 -278
  240. /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
  241. /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
  242. /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
  243. /django_cfg/{version_check.py → utils/version_check.py} +0 -0
  244. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.1.81.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
+ }
@@ -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
+ }