django-cfg 1.2.13__py3-none-any.whl → 1.2.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/core/config.py +8 -0
- django_cfg/core/generation.py +52 -18
- django_cfg/middleware/pagination.py +258 -0
- django_cfg/models/drf.py +16 -3
- django_cfg/models/tasks.py +55 -1
- django_cfg/modules/django_tasks.py +41 -3
- django_cfg/registry/core.py +7 -0
- django_cfg/templates/rest_framework/api.html +12 -0
- django_cfg/templatetags/django_cfg.py +16 -0
- {django_cfg-1.2.13.dist-info → django_cfg-1.2.15.dist-info}/METADATA +1 -1
- {django_cfg-1.2.13.dist-info → django_cfg-1.2.15.dist-info}/RECORD +15 -13
- {django_cfg-1.2.13.dist-info → django_cfg-1.2.15.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.13.dist-info → django_cfg-1.2.15.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.13.dist-info → django_cfg-1.2.15.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
django_cfg/core/config.py
CHANGED
@@ -20,6 +20,7 @@ from django_cfg import (
|
|
20
20
|
DatabaseConfig, CacheConfig, EmailConfig, TelegramConfig,
|
21
21
|
UnfoldConfig, DRFConfig, SpectacularConfig, LimitsConfig
|
22
22
|
)
|
23
|
+
from django_cfg.models.tasks import TaskConfig
|
23
24
|
|
24
25
|
# Default apps
|
25
26
|
DEFAULT_APPS = [
|
@@ -244,6 +245,12 @@ class DjangoConfig(BaseModel):
|
|
244
245
|
description="Unfold admin interface configuration",
|
245
246
|
)
|
246
247
|
|
248
|
+
# === Background Task Processing ===
|
249
|
+
tasks: Optional[TaskConfig] = Field(
|
250
|
+
default=None,
|
251
|
+
description="Background task processing configuration (Dramatiq)",
|
252
|
+
)
|
253
|
+
|
247
254
|
# === API Configuration ===
|
248
255
|
# Note: DRF base configuration is handled by django-revolution
|
249
256
|
# These fields provide additional/extended settings on top of Revolution
|
@@ -353,6 +360,7 @@ class DjangoConfig(BaseModel):
|
|
353
360
|
if "default" not in self.databases:
|
354
361
|
raise ConfigurationError("'default' database is required", context={"available_databases": list(self.databases.keys())}, suggestions=["Add a database with alias 'default'"])
|
355
362
|
|
363
|
+
|
356
364
|
# Validate database routing consistency - check migrate_to references
|
357
365
|
referenced_databases = set()
|
358
366
|
for alias, db_config in self.databases.items():
|
django_cfg/core/generation.py
CHANGED
@@ -418,11 +418,17 @@ class SettingsGenerator:
|
|
418
418
|
|
419
419
|
# Check for Tasks/Dramatiq configuration
|
420
420
|
try:
|
421
|
+
from django_cfg.models.tasks import TaskConfig
|
421
422
|
from django_cfg.modules.django_tasks import generate_dramatiq_settings_from_config
|
422
|
-
|
423
|
-
if
|
424
|
-
|
425
|
-
|
423
|
+
|
424
|
+
# Auto-initialize TaskConfig if needed and generate settings
|
425
|
+
task_config = TaskConfig.auto_initialize_if_needed()
|
426
|
+
if task_config is not None:
|
427
|
+
dramatiq_settings = generate_dramatiq_settings_from_config()
|
428
|
+
if dramatiq_settings:
|
429
|
+
settings.update(dramatiq_settings)
|
430
|
+
integrations.append("dramatiq")
|
431
|
+
|
426
432
|
except ImportError as e:
|
427
433
|
logger.warning(f"Failed to import django_tasks module: {e}")
|
428
434
|
except Exception as e:
|
@@ -467,24 +473,52 @@ class SettingsGenerator:
|
|
467
473
|
except Exception as e:
|
468
474
|
logger.warning(f"Could not generate DRF config from Revolution: {e}")
|
469
475
|
|
470
|
-
# Apply django-cfg DRF/Spectacular extensions
|
476
|
+
# Apply django-cfg DRF/Spectacular extensions
|
471
477
|
try:
|
472
|
-
if
|
473
|
-
|
474
|
-
if config.
|
475
|
-
|
476
|
-
|
477
|
-
settings["REST_FRAMEWORK"].update(drf_extensions)
|
478
|
-
logger.info("🔧 Extended REST_FRAMEWORK settings with django-cfg DRF config")
|
479
|
-
|
480
|
-
# Extend SPECTACULAR_SETTINGS if config.spectacular is provided
|
481
|
-
if config.spectacular and "SPECTACULAR_SETTINGS" in settings:
|
482
|
-
spectacular_extensions = config.spectacular.get_spectacular_settings()
|
483
|
-
# Merge with existing settings (django-cfg extensions take priority)
|
478
|
+
# Always apply project name to Spectacular settings if they exist
|
479
|
+
if "SPECTACULAR_SETTINGS" in settings:
|
480
|
+
if config.spectacular:
|
481
|
+
# User provided explicit spectacular config
|
482
|
+
spectacular_extensions = config.spectacular.get_spectacular_settings(project_name=config.project_name)
|
484
483
|
settings["SPECTACULAR_SETTINGS"].update(spectacular_extensions)
|
485
484
|
logger.info("🔧 Extended SPECTACULAR_SETTINGS with django-cfg Spectacular config")
|
486
|
-
|
485
|
+
else:
|
486
|
+
# Auto-create minimal spectacular config to set project name
|
487
|
+
from django_cfg.models.drf import SpectacularConfig
|
488
|
+
auto_spectacular = SpectacularConfig()
|
489
|
+
spectacular_extensions = auto_spectacular.get_spectacular_settings(project_name=config.project_name)
|
490
|
+
settings["SPECTACULAR_SETTINGS"].update(spectacular_extensions)
|
491
|
+
logger.info(f"🚀 Auto-configured API title as '{config.project_name} API'")
|
492
|
+
|
487
493
|
integrations.append("drf_spectacular_extended")
|
494
|
+
|
495
|
+
# Always apply django-cfg DRF settings (create REST_FRAMEWORK if needed)
|
496
|
+
if config.drf:
|
497
|
+
# User provided explicit DRF config
|
498
|
+
drf_extensions = config.drf.get_rest_framework_settings()
|
499
|
+
if "REST_FRAMEWORK" in settings:
|
500
|
+
settings["REST_FRAMEWORK"].update(drf_extensions)
|
501
|
+
else:
|
502
|
+
settings["REST_FRAMEWORK"] = drf_extensions
|
503
|
+
logger.info("🔧 Extended REST_FRAMEWORK settings with django-cfg DRF config")
|
504
|
+
else:
|
505
|
+
# Auto-create minimal DRF config to set default pagination
|
506
|
+
from django_cfg.models.drf import DRFConfig
|
507
|
+
auto_drf = DRFConfig()
|
508
|
+
drf_extensions = auto_drf.get_rest_framework_settings()
|
509
|
+
|
510
|
+
if "REST_FRAMEWORK" in settings:
|
511
|
+
# Only apply pagination and page_size, don't override other settings
|
512
|
+
pagination_settings = {
|
513
|
+
'DEFAULT_PAGINATION_CLASS': drf_extensions['DEFAULT_PAGINATION_CLASS'],
|
514
|
+
'PAGE_SIZE': drf_extensions['PAGE_SIZE'],
|
515
|
+
}
|
516
|
+
settings["REST_FRAMEWORK"].update(pagination_settings)
|
517
|
+
else:
|
518
|
+
# Create new REST_FRAMEWORK settings with our defaults
|
519
|
+
settings["REST_FRAMEWORK"] = drf_extensions
|
520
|
+
|
521
|
+
logger.info(f"🚀 Auto-configured default pagination: {drf_extensions['DEFAULT_PAGINATION_CLASS']}")
|
488
522
|
|
489
523
|
except Exception as e:
|
490
524
|
logger.warning(f"Could not apply DRF/Spectacular extensions from django-cfg: {e}")
|
@@ -0,0 +1,258 @@
|
|
1
|
+
"""
|
2
|
+
Django CFG Default Pagination Classes
|
3
|
+
|
4
|
+
Provides enhanced pagination classes with better response format and schema support.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from rest_framework.pagination import PageNumberPagination
|
8
|
+
from rest_framework.response import Response
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
from django.core.paginator import InvalidPage
|
11
|
+
from rest_framework.exceptions import NotFound
|
12
|
+
|
13
|
+
|
14
|
+
class DefaultPagination(PageNumberPagination):
|
15
|
+
"""
|
16
|
+
Enhanced default pagination class for django-cfg projects.
|
17
|
+
|
18
|
+
Features:
|
19
|
+
- Configurable page size via query parameter
|
20
|
+
- Enhanced response format with detailed pagination info
|
21
|
+
- Better OpenAPI schema support
|
22
|
+
- Consistent error handling
|
23
|
+
"""
|
24
|
+
|
25
|
+
# Page size configuration
|
26
|
+
page_size = 100
|
27
|
+
page_size_query_param = 'page_size'
|
28
|
+
max_page_size = 1000
|
29
|
+
|
30
|
+
# Page number configuration
|
31
|
+
page_query_param = 'page'
|
32
|
+
|
33
|
+
# Template for invalid page messages
|
34
|
+
invalid_page_message = 'Invalid page "{page_number}": {message}.'
|
35
|
+
|
36
|
+
def paginate_queryset(self, queryset, request, view=None):
|
37
|
+
"""
|
38
|
+
Paginate a queryset if required, either returning a page object,
|
39
|
+
or `None` if pagination is not configured for this view.
|
40
|
+
"""
|
41
|
+
try:
|
42
|
+
return super().paginate_queryset(queryset, request, view)
|
43
|
+
except InvalidPage as exc:
|
44
|
+
msg = self.invalid_page_message.format(
|
45
|
+
page_number=request.query_params.get(self.page_query_param, 1),
|
46
|
+
message=str(exc)
|
47
|
+
)
|
48
|
+
raise NotFound(msg)
|
49
|
+
|
50
|
+
def get_paginated_response(self, data):
|
51
|
+
"""
|
52
|
+
Return a paginated style `Response` object with enhanced format.
|
53
|
+
|
54
|
+
Response format:
|
55
|
+
{
|
56
|
+
"count": 150, # Total number of items
|
57
|
+
"page": 2, # Current page number
|
58
|
+
"pages": 15, # Total number of pages
|
59
|
+
"page_size": 10, # Items per page
|
60
|
+
"has_next": true, # Whether there is a next page
|
61
|
+
"has_previous": true, # Whether there is a previous page
|
62
|
+
"next_page": 3, # Next page number (null if no next)
|
63
|
+
"previous_page": 1, # Previous page number (null if no previous)
|
64
|
+
"results": [...] # Actual data
|
65
|
+
}
|
66
|
+
"""
|
67
|
+
return Response({
|
68
|
+
'count': self.page.paginator.count,
|
69
|
+
'page': self.page.number,
|
70
|
+
'pages': self.page.paginator.num_pages,
|
71
|
+
'page_size': self.page.paginator.per_page,
|
72
|
+
'has_next': self.page.has_next(),
|
73
|
+
'has_previous': self.page.has_previous(),
|
74
|
+
'next_page': self.page.next_page_number() if self.page.has_next() else None,
|
75
|
+
'previous_page': self.page.previous_page_number() if self.page.has_previous() else None,
|
76
|
+
'results': data,
|
77
|
+
})
|
78
|
+
|
79
|
+
def get_paginated_response_schema(self, schema):
|
80
|
+
"""
|
81
|
+
Return the OpenAPI schema for paginated responses.
|
82
|
+
|
83
|
+
This ensures proper API documentation generation.
|
84
|
+
"""
|
85
|
+
return {
|
86
|
+
'type': 'object',
|
87
|
+
'required': ['count', 'page', 'pages', 'page_size', 'has_next', 'has_previous', 'results'],
|
88
|
+
'properties': {
|
89
|
+
'count': {
|
90
|
+
'type': 'integer',
|
91
|
+
'description': 'Total number of items across all pages',
|
92
|
+
'example': 150
|
93
|
+
},
|
94
|
+
'page': {
|
95
|
+
'type': 'integer',
|
96
|
+
'description': 'Current page number (1-based)',
|
97
|
+
'example': 2
|
98
|
+
},
|
99
|
+
'pages': {
|
100
|
+
'type': 'integer',
|
101
|
+
'description': 'Total number of pages',
|
102
|
+
'example': 15
|
103
|
+
},
|
104
|
+
'page_size': {
|
105
|
+
'type': 'integer',
|
106
|
+
'description': 'Number of items per page',
|
107
|
+
'example': 10
|
108
|
+
},
|
109
|
+
'has_next': {
|
110
|
+
'type': 'boolean',
|
111
|
+
'description': 'Whether there is a next page',
|
112
|
+
'example': True
|
113
|
+
},
|
114
|
+
'has_previous': {
|
115
|
+
'type': 'boolean',
|
116
|
+
'description': 'Whether there is a previous page',
|
117
|
+
'example': True
|
118
|
+
},
|
119
|
+
'next_page': {
|
120
|
+
'type': 'integer',
|
121
|
+
'nullable': True,
|
122
|
+
'description': 'Next page number (null if no next page)',
|
123
|
+
'example': 3
|
124
|
+
},
|
125
|
+
'previous_page': {
|
126
|
+
'type': 'integer',
|
127
|
+
'nullable': True,
|
128
|
+
'description': 'Previous page number (null if no previous page)',
|
129
|
+
'example': 1
|
130
|
+
},
|
131
|
+
'results': {
|
132
|
+
**schema,
|
133
|
+
'description': 'Array of items for current page'
|
134
|
+
},
|
135
|
+
},
|
136
|
+
}
|
137
|
+
|
138
|
+
def get_html_context(self):
|
139
|
+
"""
|
140
|
+
Return context for HTML template rendering (browsable API).
|
141
|
+
"""
|
142
|
+
base_context = super().get_html_context()
|
143
|
+
base_context.update({
|
144
|
+
'page_size': self.page.paginator.per_page,
|
145
|
+
'total_pages': self.page.paginator.num_pages,
|
146
|
+
})
|
147
|
+
return base_context
|
148
|
+
|
149
|
+
|
150
|
+
class LargePagination(DefaultPagination):
|
151
|
+
"""
|
152
|
+
Pagination class for large datasets.
|
153
|
+
|
154
|
+
Uses larger page sizes for better performance with big collections.
|
155
|
+
"""
|
156
|
+
page_size = 500
|
157
|
+
max_page_size = 2000
|
158
|
+
|
159
|
+
|
160
|
+
class SmallPagination(DefaultPagination):
|
161
|
+
"""
|
162
|
+
Pagination class for small datasets or detailed views.
|
163
|
+
|
164
|
+
Uses smaller page sizes for better user experience.
|
165
|
+
"""
|
166
|
+
page_size = 20
|
167
|
+
max_page_size = 100
|
168
|
+
|
169
|
+
|
170
|
+
class NoPagination(PageNumberPagination):
|
171
|
+
"""
|
172
|
+
Pagination class that effectively disables pagination.
|
173
|
+
|
174
|
+
Returns all results in a single page. Use with caution on large datasets.
|
175
|
+
"""
|
176
|
+
page_size = None
|
177
|
+
page_size_query_param = None
|
178
|
+
max_page_size = None
|
179
|
+
|
180
|
+
def paginate_queryset(self, queryset, request, view=None):
|
181
|
+
"""
|
182
|
+
Don't paginate the queryset, return None to indicate no pagination.
|
183
|
+
"""
|
184
|
+
return None
|
185
|
+
|
186
|
+
def get_paginated_response(self, data):
|
187
|
+
"""
|
188
|
+
Return a non-paginated response.
|
189
|
+
"""
|
190
|
+
return Response(data)
|
191
|
+
|
192
|
+
|
193
|
+
class CursorPaginationEnhanced(PageNumberPagination):
|
194
|
+
"""
|
195
|
+
Enhanced cursor-based pagination for large datasets.
|
196
|
+
|
197
|
+
Better performance for large datasets but doesn't support jumping to arbitrary pages.
|
198
|
+
"""
|
199
|
+
page_size = 100
|
200
|
+
page_size_query_param = 'page_size'
|
201
|
+
max_page_size = 1000
|
202
|
+
cursor_query_param = 'cursor'
|
203
|
+
ordering = '-created_at' # Default ordering, should be overridden
|
204
|
+
|
205
|
+
def get_paginated_response(self, data):
|
206
|
+
"""
|
207
|
+
Return cursor-paginated response with enhanced format.
|
208
|
+
"""
|
209
|
+
return Response({
|
210
|
+
'next': self.get_next_link(),
|
211
|
+
'previous': self.get_previous_link(),
|
212
|
+
'page_size': self.page_size,
|
213
|
+
'results': data,
|
214
|
+
})
|
215
|
+
|
216
|
+
def get_paginated_response_schema(self, schema):
|
217
|
+
"""
|
218
|
+
Return the OpenAPI schema for cursor-paginated responses.
|
219
|
+
"""
|
220
|
+
return {
|
221
|
+
'type': 'object',
|
222
|
+
'required': ['results'],
|
223
|
+
'properties': {
|
224
|
+
'next': {
|
225
|
+
'type': 'string',
|
226
|
+
'nullable': True,
|
227
|
+
'format': 'uri',
|
228
|
+
'description': 'URL to next page of results',
|
229
|
+
'example': 'http://api.example.org/accounts/?cursor=cD0yMDIzLTEyLTE1KzAyJTNBMDA%3D'
|
230
|
+
},
|
231
|
+
'previous': {
|
232
|
+
'type': 'string',
|
233
|
+
'nullable': True,
|
234
|
+
'format': 'uri',
|
235
|
+
'description': 'URL to previous page of results',
|
236
|
+
'example': 'http://api.example.org/accounts/?cursor=bD0yMDIzLTEyLTEzKzAyJTNBMDA%3D'
|
237
|
+
},
|
238
|
+
'page_size': {
|
239
|
+
'type': 'integer',
|
240
|
+
'description': 'Number of items per page',
|
241
|
+
'example': 100
|
242
|
+
},
|
243
|
+
'results': {
|
244
|
+
**schema,
|
245
|
+
'description': 'Array of items for current page'
|
246
|
+
},
|
247
|
+
},
|
248
|
+
}
|
249
|
+
|
250
|
+
|
251
|
+
# Export all pagination classes
|
252
|
+
__all__ = [
|
253
|
+
'DefaultPagination',
|
254
|
+
'LargePagination',
|
255
|
+
'SmallPagination',
|
256
|
+
'NoPagination',
|
257
|
+
'CursorPaginationEnhanced',
|
258
|
+
]
|
django_cfg/models/drf.py
CHANGED
@@ -133,12 +133,15 @@ class SpectacularConfig(BaseModel):
|
|
133
133
|
description="Enum name overrides"
|
134
134
|
)
|
135
135
|
|
136
|
-
def get_spectacular_settings(self) -> Dict[str, Any]:
|
136
|
+
def get_spectacular_settings(self, project_name: Optional[str] = None) -> Dict[str, Any]:
|
137
137
|
"""
|
138
138
|
Get django-cfg Spectacular extensions.
|
139
139
|
|
140
140
|
NOTE: This extends Revolution's base settings, not replaces them.
|
141
141
|
Only include settings that are unique to django-cfg or critical fixes.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
project_name: Project name from DjangoConfig to use as API title
|
142
145
|
"""
|
143
146
|
settings = {
|
144
147
|
# django-cfg specific UI enhancements
|
@@ -156,6 +159,16 @@ class SpectacularConfig(BaseModel):
|
|
156
159
|
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
|
157
160
|
}
|
158
161
|
|
162
|
+
# Use project_name as API title if provided and title is default
|
163
|
+
if project_name and self.title == "API Documentation":
|
164
|
+
settings["TITLE"] = f"{project_name} API"
|
165
|
+
elif self.title != "API Documentation":
|
166
|
+
settings["TITLE"] = self.title
|
167
|
+
|
168
|
+
# Always set description and version
|
169
|
+
settings["DESCRIPTION"] = self.description
|
170
|
+
settings["VERSION"] = self.version
|
171
|
+
|
159
172
|
# Add optional fields if present
|
160
173
|
if self.terms_of_service:
|
161
174
|
settings["TERMS_OF_SERVICE"] = self.terms_of_service
|
@@ -205,10 +218,10 @@ class DRFConfig(BaseModel):
|
|
205
218
|
|
206
219
|
# Pagination
|
207
220
|
pagination_class: str = Field(
|
208
|
-
default='
|
221
|
+
default='django_cfg.middleware.pagination.DefaultPagination',
|
209
222
|
description="Default pagination class"
|
210
223
|
)
|
211
|
-
page_size: int = Field(default=
|
224
|
+
page_size: int = Field(default=100, description="Default page size")
|
212
225
|
|
213
226
|
# Schema
|
214
227
|
schema_class: str = Field(
|
django_cfg/models/tasks.py
CHANGED
@@ -11,6 +11,7 @@ from typing import Optional, List, Literal, Dict, Any
|
|
11
11
|
from enum import Enum
|
12
12
|
import os
|
13
13
|
import logging
|
14
|
+
from django_cfg.models.cfg import BaseCfgAutoModule
|
14
15
|
|
15
16
|
logger = logging.getLogger(__name__)
|
16
17
|
|
@@ -239,7 +240,7 @@ class WorkerConfig(BaseModel):
|
|
239
240
|
)
|
240
241
|
|
241
242
|
|
242
|
-
class TaskConfig(BaseModel):
|
243
|
+
class TaskConfig(BaseModel, BaseCfgAutoModule):
|
243
244
|
"""
|
244
245
|
High-level task system configuration.
|
245
246
|
|
@@ -257,6 +258,12 @@ class TaskConfig(BaseModel):
|
|
257
258
|
description="Task processing backend"
|
258
259
|
)
|
259
260
|
|
261
|
+
def __init__(self, **data):
|
262
|
+
"""Initialize TaskConfig with BaseCfgAutoModule support."""
|
263
|
+
super().__init__(**data)
|
264
|
+
# Initialize _config attribute for BaseCfgAutoModule
|
265
|
+
self._config = None
|
266
|
+
|
260
267
|
# === Backend-Specific Configuration ===
|
261
268
|
dramatiq: DramatiqConfig = Field(
|
262
269
|
default_factory=DramatiqConfig,
|
@@ -371,6 +378,53 @@ class TaskConfig(BaseModel):
|
|
371
378
|
"DRAMATIQ_QUEUES": self.dramatiq.queues,
|
372
379
|
}
|
373
380
|
|
381
|
+
def get_smart_defaults(self):
|
382
|
+
"""Get smart default configuration for this module."""
|
383
|
+
config = self.get_config()
|
384
|
+
debug = getattr(config, 'debug', False) if config else False
|
385
|
+
return get_default_task_config(debug=debug)
|
386
|
+
|
387
|
+
def get_module_config(self):
|
388
|
+
"""Get the final configuration for this module."""
|
389
|
+
return self
|
390
|
+
|
391
|
+
@classmethod
|
392
|
+
def auto_initialize_if_needed(cls) -> Optional['TaskConfig']:
|
393
|
+
"""
|
394
|
+
Auto-initialize TaskConfig if needed based on config flags.
|
395
|
+
|
396
|
+
Returns:
|
397
|
+
TaskConfig instance if should be initialized, None otherwise
|
398
|
+
"""
|
399
|
+
# Get config through BaseCfgModule
|
400
|
+
from django_cfg.modules import BaseCfgModule
|
401
|
+
base_module = BaseCfgModule()
|
402
|
+
config = base_module.get_config()
|
403
|
+
|
404
|
+
if not config:
|
405
|
+
return None
|
406
|
+
|
407
|
+
# Check if TaskConfig already exists
|
408
|
+
if hasattr(config, 'tasks') and config.tasks is not None:
|
409
|
+
# Set config reference and return existing
|
410
|
+
config.tasks.set_config(config)
|
411
|
+
return config.tasks
|
412
|
+
|
413
|
+
# Check if tasks should be enabled
|
414
|
+
if base_module.is_tasks_enabled():
|
415
|
+
# Auto-initialize with smart defaults
|
416
|
+
task_config = cls().get_smart_defaults()
|
417
|
+
task_config.set_config(config)
|
418
|
+
config.tasks = task_config
|
419
|
+
|
420
|
+
import logging
|
421
|
+
logger = logging.getLogger(__name__)
|
422
|
+
logger.info("🚀 Auto-initialized TaskConfig (enabled by knowbase/agents/tasks flags)")
|
423
|
+
|
424
|
+
return task_config
|
425
|
+
|
426
|
+
return None
|
427
|
+
|
374
428
|
|
375
429
|
# === Utility Functions ===
|
376
430
|
|
@@ -9,16 +9,14 @@ from typing import Optional, Dict, Any, List
|
|
9
9
|
import logging
|
10
10
|
from urllib.parse import urlparse
|
11
11
|
|
12
|
-
from
|
12
|
+
from . import BaseCfgModule
|
13
13
|
from django_cfg.models.tasks import TaskConfig, validate_task_config
|
14
14
|
from django_cfg.models.constance import ConstanceField
|
15
15
|
|
16
16
|
# Django imports (will be available when Django is configured)
|
17
17
|
try:
|
18
|
-
from django.conf import settings
|
19
18
|
from django.apps import apps
|
20
19
|
except ImportError:
|
21
|
-
settings = None
|
22
20
|
apps = None
|
23
21
|
|
24
22
|
# Optional imports
|
@@ -426,6 +424,46 @@ def extend_constance_config_with_tasks():
|
|
426
424
|
|
427
425
|
# === Exports ===
|
428
426
|
|
427
|
+
def generate_dramatiq_settings_from_config() -> Optional[Dict[str, Any]]:
|
428
|
+
"""
|
429
|
+
Generate Dramatiq settings from auto-discovered DjangoConfig.
|
430
|
+
|
431
|
+
Returns:
|
432
|
+
Dictionary of Dramatiq settings or None if tasks disabled
|
433
|
+
"""
|
434
|
+
try:
|
435
|
+
# Get config through BaseCfgModule
|
436
|
+
base_module = BaseCfgModule()
|
437
|
+
config = base_module.get_config()
|
438
|
+
|
439
|
+
if not config or not hasattr(config, 'tasks') or not config.tasks or not config.tasks.enabled:
|
440
|
+
return None
|
441
|
+
|
442
|
+
# Get Redis URL from cache config or environment
|
443
|
+
redis_url = None
|
444
|
+
if hasattr(config, 'cache_default') and config.cache_default:
|
445
|
+
redis_url = getattr(config.cache_default, 'redis_url', None)
|
446
|
+
|
447
|
+
if not redis_url:
|
448
|
+
# Fallback to environment or default
|
449
|
+
import os
|
450
|
+
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379/1')
|
451
|
+
|
452
|
+
# Generate Dramatiq settings
|
453
|
+
dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
|
454
|
+
|
455
|
+
# Ensure we only use Redis broker (no RabbitMQ)
|
456
|
+
if 'DRAMATIQ_BROKER' in dramatiq_settings:
|
457
|
+
dramatiq_settings['DRAMATIQ_BROKER']['BROKER'] = 'dramatiq.brokers.redis.RedisBroker'
|
458
|
+
|
459
|
+
logger.info(f"✅ Generated Dramatiq settings with Redis broker and {len(config.tasks.dramatiq.queues)} queues")
|
460
|
+
return dramatiq_settings
|
461
|
+
|
462
|
+
except Exception as e:
|
463
|
+
logger.error(f"Failed to generate Dramatiq settings: {e}")
|
464
|
+
return None
|
465
|
+
|
466
|
+
|
429
467
|
__all__ = [
|
430
468
|
"DjangoTasks",
|
431
469
|
"get_task_service",
|
django_cfg/registry/core.py
CHANGED
@@ -41,6 +41,13 @@ CORE_REGISTRY = {
|
|
41
41
|
"TaskConfig": ("django_cfg.models.tasks", "TaskConfig"),
|
42
42
|
"DramatiqConfig": ("django_cfg.models.tasks", "DramatiqConfig"),
|
43
43
|
|
44
|
+
# Pagination classes
|
45
|
+
"DefaultPagination": ("django_cfg.middleware.pagination", "DefaultPagination"),
|
46
|
+
"LargePagination": ("django_cfg.middleware.pagination", "LargePagination"),
|
47
|
+
"SmallPagination": ("django_cfg.middleware.pagination", "SmallPagination"),
|
48
|
+
"NoPagination": ("django_cfg.middleware.pagination", "NoPagination"),
|
49
|
+
"CursorPaginationEnhanced": ("django_cfg.middleware.pagination", "CursorPaginationEnhanced"),
|
50
|
+
|
44
51
|
# Utils
|
45
52
|
"version_check": ("django_cfg.utils.version_check", "version_check"),
|
46
53
|
"toolkit": ("django_cfg.utils.toolkit", "toolkit"),
|
@@ -0,0 +1,12 @@
|
|
1
|
+
{% extends "rest_framework/base.html" %}
|
2
|
+
{% load django_cfg %}
|
3
|
+
|
4
|
+
{% block title %}
|
5
|
+
{% if view.get_view_name %}{{ view.get_view_name }}{% else %}API{% endif %} - {% project_name %}
|
6
|
+
{% endblock %}
|
7
|
+
|
8
|
+
{% block branding %}
|
9
|
+
<a class='navbar-brand' rel="nofollow" href='{% url "api-root" %}'>
|
10
|
+
{% project_name %}
|
11
|
+
</a>
|
12
|
+
{% endblock %}
|
@@ -32,3 +32,19 @@ def lib_health_url():
|
|
32
32
|
# Lazy import to avoid AppRegistryNotReady error
|
33
33
|
from django_cfg.config import LIB_HEALTH_URL
|
34
34
|
return LIB_HEALTH_URL
|
35
|
+
|
36
|
+
|
37
|
+
@register.simple_tag
|
38
|
+
def project_name():
|
39
|
+
"""Get the project name from current config."""
|
40
|
+
# Lazy import to avoid AppRegistryNotReady error
|
41
|
+
from django_cfg.core.config import get_current_config
|
42
|
+
from django_cfg.config import LIB_NAME
|
43
|
+
|
44
|
+
# Try to get project name from current config
|
45
|
+
config = get_current_config()
|
46
|
+
if config and hasattr(config, 'project_name'):
|
47
|
+
return config.project_name
|
48
|
+
|
49
|
+
# Fallback to library name
|
50
|
+
return LIB_NAME
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-cfg
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.15
|
4
4
|
Summary: 🚀 Next-gen Django configuration: type-safety, AI features, blazing-fast setup, and automated best practices — all in one.
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
6
6
|
Project-URL: Documentation, https://docs.djangocfg.com
|
@@ -1,5 +1,5 @@
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
django_cfg/__init__.py,sha256=
|
2
|
+
django_cfg/__init__.py,sha256=WO5eQC0F4xNgcT1pvxC4u4-1_fGdLq1oolFLlGmuRKE,1631
|
3
3
|
django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
|
4
4
|
django_cfg/config.py,sha256=0cuRJVEnf03WzvEqhwzLvn9Zi1805C5KG1yk27ekABA,1190
|
5
5
|
django_cfg/urls.py,sha256=bpRFjMonQuk4UCUMxx4ueBX3YDNB7HXKFwEghQ3KR3o,793
|
@@ -285,10 +285,10 @@ django_cfg/cli/commands/__init__.py,sha256=EKLXDAx-QttnGmdjsmVANAfhxWplxl2V_2I0S
|
|
285
285
|
django_cfg/cli/commands/create_project.py,sha256=iuf965j8Yg7zxHcPb0GtFHEj73CYXC45ZJRmd6RbA9E,21025
|
286
286
|
django_cfg/cli/commands/info.py,sha256=o4S1xPJSHv2oEVqmH0X9RTF5f-8Wy9579yHkyd_PC3E,4923
|
287
287
|
django_cfg/core/__init__.py,sha256=eVK57qFOok9kTeHoNEMQ1BplkUOaQ7NB9kP9eQK1vg0,358
|
288
|
-
django_cfg/core/config.py,sha256=
|
288
|
+
django_cfg/core/config.py,sha256=y0rvqiO5l_MMqDhQCiyA9c3GHYJlJwRpdOgvN_MfcWU,26536
|
289
289
|
django_cfg/core/environment.py,sha256=MAoEPqIPsLVhSANT2Bz4nnus2wmbMW0RCOQxhQfDrDc,9106
|
290
290
|
django_cfg/core/exceptions.py,sha256=RTQEoU3PfR8lqqNNv5ayd_HY2yJLs3eioqUy8VM6AG4,10378
|
291
|
-
django_cfg/core/generation.py,sha256=
|
291
|
+
django_cfg/core/generation.py,sha256=zSVb3VIv588wzqSF1-wxOb15CW5hX2tf9fq_FRXSOzk,24990
|
292
292
|
django_cfg/core/integration.py,sha256=5kzkZSd45ij0rfrBdeNUYnDalObqvK__XpoP31xFYKo,5224
|
293
293
|
django_cfg/core/validation.py,sha256=PhwDBTs24nuM3xqfIT3hXmN88VrhMT-Bk8Yb8FxjmPk,6590
|
294
294
|
django_cfg/management/__init__.py,sha256=NrLAhiS59hqjy-bipOC1abNuRiNm5BpKXmjN05VzKbM,28
|
@@ -316,6 +316,7 @@ django_cfg/management/commands/tree.py,sha256=rBeDOouqntFZjZGI5uYLOCpInJgSzb9Fz7
|
|
316
316
|
django_cfg/management/commands/validate_config.py,sha256=2V8M6ZCOfrXA-tZ8WoXRxgnb6gDE0xXymAbBzUJ4gic,6940
|
317
317
|
django_cfg/middleware/README.md,sha256=7XukH2HREqEnNJ9KDXhAcSw6lfpZ3gvKKAqGYvSivo4,3886
|
318
318
|
django_cfg/middleware/__init__.py,sha256=IdcooCQLd8rNt-_8PXa2qXLMBnhsvkfdfuhI9CmgABA,195
|
319
|
+
django_cfg/middleware/pagination.py,sha256=RfLka1DbmLD-zznSAqayGD08x95ikcnkzp9LT5Jj5Bs,8655
|
319
320
|
django_cfg/middleware/public_endpoints.py,sha256=EHJKzlYwakeU3hSPBJ53FZn8Ub-jwENCVSfFDJt3kAk,6915
|
320
321
|
django_cfg/middleware/user_activity.py,sha256=7XVSnnIVfz-Hucx-YWRODcXd3qDH8Y8E__-6zYWLxqI,6153
|
321
322
|
django_cfg/models/__init__.py,sha256=du24ZdqKdvc3VNXjgufEz_6B6gxdHtlqVHG4PJWaOQM,419
|
@@ -326,7 +327,7 @@ django_cfg/models/cfg.py,sha256=P6YowmE-VOqp_L25Ijxj2hjjNhB9xtlm8G35DHWqTfk,2702
|
|
326
327
|
django_cfg/models/constance.py,sha256=BqwoPvUx5vfrZT6qF2KR0prpsSM5VEYIYJUVlB50ewk,8936
|
327
328
|
django_cfg/models/cors.py,sha256=lozVoQJVehhNL1qzOhvAkd4ZpgwAdOJ-OUX7wkJp5W4,3567
|
328
329
|
django_cfg/models/database.py,sha256=LpcmEljjr-2pnR8UVSziUNSnucsojfh8PcgayhrWqK4,16875
|
329
|
-
django_cfg/models/drf.py,sha256=
|
330
|
+
django_cfg/models/drf.py,sha256=o7p1dijT9NbQIHDp_WbWJsi1Kl5n2WS0wr5iWYuxNF0,10324
|
330
331
|
django_cfg/models/email.py,sha256=y97W9I-Mm3TyWWLCLRh_2GO0u2PBVT2olMInqOyBiq8,4843
|
331
332
|
django_cfg/models/environment.py,sha256=jM_KPXrxQHTFOhbaJTkkZL0Yml8bAM2Q7DJ69QsedX8,9438
|
332
333
|
django_cfg/models/jwt.py,sha256=3R_dpLmVZIcH4zdtwA4qKnuCB8RZQACrgsbbgWY2q4c,9025
|
@@ -336,14 +337,14 @@ django_cfg/models/ngrok.py,sha256=MVgcKWx0DRSW0QcwhiSx2vVwTSG49vbVrzPkZqDK-zw,35
|
|
336
337
|
django_cfg/models/revolution.py,sha256=gaAhdicd1ji-XiJPqUFQaUmDcgfwFHofSYS3JtLcz4A,7540
|
337
338
|
django_cfg/models/security.py,sha256=Xv19ZVOIenB_-f0wB6fm-Ap4j9kA43bSFaT2XenpSqc,4685
|
338
339
|
django_cfg/models/services.py,sha256=fj9JjrJFrlL4DMnMbx_D8JiiZpz4E5uBqmhquAxau7c,13159
|
339
|
-
django_cfg/models/tasks.py,sha256=
|
340
|
+
django_cfg/models/tasks.py,sha256=G06kEMaJ9mZhW525y-r33q4rHswAwnvPgMt7lpbgiak,15526
|
340
341
|
django_cfg/modules/__init__.py,sha256=Ip9WMpzImEwIAywpFwU056_v0O9oIGG7nCT1YSArxkw,316
|
341
342
|
django_cfg/modules/base.py,sha256=ALlsylzO1rYsrLNwL3MhEK_dqYSZV0BBZAObR6wlGEA,5343
|
342
343
|
django_cfg/modules/django_email.py,sha256=2XXlIKzD6Jao3CT4_zIE2eaM9Cc9ROA1tjp2bJ9z5Lo,16592
|
343
344
|
django_cfg/modules/django_health.py,sha256=7QzuQ6WyjWYj6lecd4auwRvEyrMUL7N6hiAp-tLyoY4,8923
|
344
345
|
django_cfg/modules/django_logger.py,sha256=3oP9jev0lOcFUJ1tYcpbFnK524zIGA2xIOrrAiTwpb8,6331
|
345
346
|
django_cfg/modules/django_ngrok.py,sha256=LjlAIJprbFhFYwDG93acas8XaB6iFQGu4fWvZiDNhyQ,10482
|
346
|
-
django_cfg/modules/django_tasks.py,sha256=
|
347
|
+
django_cfg/modules/django_tasks.py,sha256=SBas2juL6RfDL7ttm4xAxJ7bvS052WmUzixNHPlzJVo,17328
|
347
348
|
django_cfg/modules/django_telegram.py,sha256=qz3EOJnCR_YTJVIu2-Iq8--cla6mU-WPohquQ26eHDQ,16367
|
348
349
|
django_cfg/modules/dramatiq_setup.py,sha256=Jke4aO_L1t6F3OAc4pl12zppKzL0gb1p6ilfQ3zUIZ8,454
|
349
350
|
django_cfg/modules/logger.py,sha256=4_zeasNehr8LGz8r_ckv15-fQS63zCodiqD4CYIEyFI,10546
|
@@ -406,7 +407,7 @@ django_cfg/modules/django_unfold/models/dropdown.py,sha256=bbRYl0iuzBrwgmqUMTDw9
|
|
406
407
|
django_cfg/modules/django_unfold/models/navigation.py,sha256=xrP2szoe1MSiTVAfc0Hd2dS32HhCp8XHBJinF5yc_U8,2575
|
407
408
|
django_cfg/modules/django_unfold/models/tabs.py,sha256=AHV7lmaRTjOluf9mgZvXISG5D7Re1QPdU2v-iYoCaIQ,848
|
408
409
|
django_cfg/registry/__init__.py,sha256=q6z9hGcBdtzbVLeDeQtP-A31z-1V7myZqjlZyTs1uo0,540
|
409
|
-
django_cfg/registry/core.py,sha256=
|
410
|
+
django_cfg/registry/core.py,sha256=FGe86oEGm66fv9ypAEIFXP_XkFog1EaE5vYggALgQ_4,2703
|
410
411
|
django_cfg/registry/exceptions.py,sha256=b4pIakeRxhT1-ST3lbAnmAQaASRBARCoah_w6vb3VF0,399
|
411
412
|
django_cfg/registry/modules.py,sha256=e8lCG5C-yhRgvs8El_-6Xbdrte-enZF_Xb3B4opO-Hs,1135
|
412
413
|
django_cfg/registry/services.py,sha256=wpGSgSCUr4e9pUh5-rwN1AyPLGazK6KD-Ky4vz-elIw,1219
|
@@ -455,15 +456,16 @@ django_cfg/templates/admin/snippets/tabs/stats_tab.html,sha256=jIqSnp4WTo2jAzzMq
|
|
455
456
|
django_cfg/templates/admin/snippets/tabs/users_tab.html,sha256=04D05VSeidn2qJnQmktVI7TNlm7tNcXYXJAsk32U5hY,2813
|
456
457
|
django_cfg/templates/admin/snippets/zones/zones_table.html,sha256=Hg4hT61yX0um00j4HkbkANOt0ehKwYIMT-r2IzTOpVo,12358
|
457
458
|
django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8G4L_GexPxvz6XQ,8836
|
459
|
+
django_cfg/templates/rest_framework/api.html,sha256=M5t75SPfZOgHDmLBNVXxyZrmCWgJKv45Ybxw8Iempj0,343
|
458
460
|
django_cfg/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
459
|
-
django_cfg/templatetags/django_cfg.py,sha256=
|
461
|
+
django_cfg/templatetags/django_cfg.py,sha256=11_3YX0jAVuOd_fVcDixAxyDMG4V7A98SPqv8Lbfpxc,1308
|
460
462
|
django_cfg/utils/__init__.py,sha256=64wwXJuXytvwt8Ze_erSR2HmV07nGWJ6DV5wloRBvYE,435
|
461
463
|
django_cfg/utils/path_resolution.py,sha256=C9As6p4Q9l3VeoVkFDRPQWGrzAWf8O8UxLVkaI3ToVM,13899
|
462
464
|
django_cfg/utils/smart_defaults.py,sha256=b6A1z7VO1NJGq0oUQXN5P97c3k_Ssgw6qUi0mK-4TlM,19786
|
463
465
|
django_cfg/utils/toolkit.py,sha256=Td8_iXNaftonF_xdZP4Y3uO65nuA_4_zditn5Q_Pfcw,23310
|
464
466
|
django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaNpucs,4800
|
465
|
-
django_cfg-1.2.
|
466
|
-
django_cfg-1.2.
|
467
|
-
django_cfg-1.2.
|
468
|
-
django_cfg-1.2.
|
469
|
-
django_cfg-1.2.
|
467
|
+
django_cfg-1.2.15.dist-info/METADATA,sha256=QcWw3jNJ4GU7JwFC9Ya3UdOT-9UFo2Au_7XeCUT3KLE,36406
|
468
|
+
django_cfg-1.2.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
469
|
+
django_cfg-1.2.15.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
470
|
+
django_cfg-1.2.15.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
471
|
+
django_cfg-1.2.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|