django-cfg 1.4.111__py3-none-any.whl → 1.4.113__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/serializers/__init__.py +10 -0
- django_cfg/apps/dashboard/serializers/crontab.py +84 -0
- django_cfg/apps/dashboard/serializers/overview.py +22 -11
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/apps/dashboard/services/crontab_service.py +210 -0
- django_cfg/apps/dashboard/services/system_health_service.py +72 -0
- django_cfg/apps/dashboard/urls.py +2 -0
- django_cfg/apps/dashboard/views/__init__.py +2 -0
- django_cfg/apps/dashboard/views/crontab_views.py +72 -0
- django_cfg/apps/dashboard/views/overview_views.py +16 -2
- django_cfg/config.py +3 -4
- django_cfg/core/base/config_model.py +7 -0
- django_cfg/core/builders/apps_builder.py +4 -0
- django_cfg/core/generation/integration_generators/__init__.py +3 -0
- django_cfg/core/generation/integration_generators/crontab.py +64 -0
- django_cfg/core/generation/orchestrator.py +13 -0
- django_cfg/core/integration/display/startup.py +2 -2
- django_cfg/core/integration/url_integration.py +2 -2
- django_cfg/models/__init__.py +3 -0
- django_cfg/models/django/__init__.py +3 -0
- django_cfg/models/django/crontab.py +303 -0
- django_cfg/modules/django_admin/utils/html_builder.py +50 -2
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +4 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +389 -166
- django_cfg/templatetags/django_cfg.py +8 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/METADATA +2 -1
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/RECORD +33 -28
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,9 +6,11 @@ Contains generators for third-party integrations and frameworks:
|
|
|
6
6
|
- External services (Telegram, Unfold, Constance)
|
|
7
7
|
- API frameworks (JWT, DRF, Spectacular, OpenAPI Client)
|
|
8
8
|
- Background tasks (ReArq)
|
|
9
|
+
- Crontab scheduling (django-crontab)
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
from .api import APIFrameworksGenerator
|
|
13
|
+
from .crontab import CrontabSettingsGenerator
|
|
12
14
|
from .sessions import SessionSettingsGenerator
|
|
13
15
|
from .tasks import TasksSettingsGenerator
|
|
14
16
|
from .third_party import ThirdPartyIntegrationsGenerator
|
|
@@ -18,4 +20,5 @@ __all__ = [
|
|
|
18
20
|
"ThirdPartyIntegrationsGenerator",
|
|
19
21
|
"APIFrameworksGenerator",
|
|
20
22
|
"TasksSettingsGenerator",
|
|
23
|
+
"CrontabSettingsGenerator",
|
|
21
24
|
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Crontab settings generator for django-cfg.
|
|
3
|
+
|
|
4
|
+
Generates django-crontab settings and handles INSTALLED_APPS integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict
|
|
8
|
+
|
|
9
|
+
from django_cfg.modules.django_logging import logger
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from django_cfg.models.django.crontab import CrontabConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CrontabSettingsGenerator:
|
|
16
|
+
"""
|
|
17
|
+
Generates crontab scheduling settings for django-crontab.
|
|
18
|
+
|
|
19
|
+
Automatically:
|
|
20
|
+
- Generates CRONJOBS configuration
|
|
21
|
+
- Adds django_crontab to INSTALLED_APPS if enabled
|
|
22
|
+
- Configures lock files and command prefixes
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, config: "CrontabConfig"):
|
|
26
|
+
"""
|
|
27
|
+
Initialize with crontab configuration.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config: CrontabConfig instance
|
|
31
|
+
"""
|
|
32
|
+
self.config = config
|
|
33
|
+
|
|
34
|
+
def generate(self) -> Dict[str, Any]:
|
|
35
|
+
"""
|
|
36
|
+
Generate crontab settings.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dictionary with crontab configuration
|
|
40
|
+
"""
|
|
41
|
+
if not self.config or not self.config.enabled:
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
settings = self.config.to_django_settings()
|
|
45
|
+
|
|
46
|
+
# Log configuration
|
|
47
|
+
enabled_jobs = self.config.get_enabled_jobs()
|
|
48
|
+
if enabled_jobs:
|
|
49
|
+
logger.info(
|
|
50
|
+
f"✓ Configured {len(enabled_jobs)} crontab job(s) "
|
|
51
|
+
f"[django-crontab integration]"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Log individual jobs in debug mode
|
|
55
|
+
for job in enabled_jobs:
|
|
56
|
+
logger.debug(
|
|
57
|
+
f" - {job.name}: {job.schedule} → "
|
|
58
|
+
f"{job.command if job.job_type == 'command' else job.callable_path}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return settings
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = ["CrontabSettingsGenerator"]
|
|
@@ -78,6 +78,7 @@ class SettingsOrchestrator:
|
|
|
78
78
|
settings.update(self._generate_third_party_settings())
|
|
79
79
|
settings.update(self._generate_api_settings())
|
|
80
80
|
settings.update(self._generate_tasks_settings())
|
|
81
|
+
settings.update(self._generate_crontab_settings())
|
|
81
82
|
settings.update(self._generate_tailwind_settings())
|
|
82
83
|
|
|
83
84
|
# Apply additional settings (user overrides)
|
|
@@ -223,6 +224,18 @@ class SettingsOrchestrator:
|
|
|
223
224
|
except Exception as e:
|
|
224
225
|
raise ConfigurationError(f"Failed to generate tasks settings: {e}") from e
|
|
225
226
|
|
|
227
|
+
def _generate_crontab_settings(self) -> Dict[str, Any]:
|
|
228
|
+
"""Generate crontab scheduling settings."""
|
|
229
|
+
if not hasattr(self.config, "crontab") or not self.config.crontab:
|
|
230
|
+
return {}
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
from .integration_generators.crontab import CrontabSettingsGenerator
|
|
234
|
+
generator = CrontabSettingsGenerator(self.config.crontab)
|
|
235
|
+
return generator.generate()
|
|
236
|
+
except Exception as e:
|
|
237
|
+
raise ConfigurationError(f"Failed to generate crontab settings: {e}") from e
|
|
238
|
+
|
|
226
239
|
def _generate_tailwind_settings(self) -> Dict[str, Any]:
|
|
227
240
|
"""Generate Tailwind CSS settings."""
|
|
228
241
|
try:
|
|
@@ -116,7 +116,7 @@ class StartupDisplayManager(BaseDisplayManager):
|
|
|
116
116
|
|
|
117
117
|
# Get library info
|
|
118
118
|
from django_cfg.config import (
|
|
119
|
-
|
|
119
|
+
LIB_SITE_URL,
|
|
120
120
|
LIB_GITHUB_URL,
|
|
121
121
|
LIB_SITE_URL,
|
|
122
122
|
LIB_SUPPORT_URL,
|
|
@@ -138,7 +138,7 @@ class StartupDisplayManager(BaseDisplayManager):
|
|
|
138
138
|
info_table.add_row("🔍 Env Source", env_source)
|
|
139
139
|
|
|
140
140
|
info_table.add_row("🌐 Site", LIB_SITE_URL)
|
|
141
|
-
info_table.add_row("📚 Docs",
|
|
141
|
+
info_table.add_row("📚 Docs", LIB_SITE_URL)
|
|
142
142
|
info_table.add_row("🐙 GitHub", LIB_GITHUB_URL)
|
|
143
143
|
info_table.add_row("🆘 Support", LIB_SUPPORT_URL)
|
|
144
144
|
|
|
@@ -176,7 +176,7 @@ def get_django_cfg_urls_info() -> dict:
|
|
|
176
176
|
Dictionary with complete URL integration info
|
|
177
177
|
"""
|
|
178
178
|
from django_cfg.config import (
|
|
179
|
-
|
|
179
|
+
LIB_SITE_URL,
|
|
180
180
|
LIB_GITHUB_URL,
|
|
181
181
|
LIB_HEALTH_URL,
|
|
182
182
|
LIB_NAME,
|
|
@@ -205,7 +205,7 @@ def get_django_cfg_urls_info() -> dict:
|
|
|
205
205
|
"prefix": "cfg/",
|
|
206
206
|
"description": LIB_NAME,
|
|
207
207
|
"site_url": LIB_SITE_URL,
|
|
208
|
-
"docs_url":
|
|
208
|
+
"docs_url": LIB_SITE_URL,
|
|
209
209
|
"github_url": LIB_GITHUB_URL,
|
|
210
210
|
"support_url": LIB_SUPPORT_URL,
|
|
211
211
|
"health_url": LIB_HEALTH_URL,
|
django_cfg/models/__init__.py
CHANGED
|
@@ -34,6 +34,7 @@ from .base.module import BaseCfgAutoModule
|
|
|
34
34
|
# Django-specific
|
|
35
35
|
from .django.axes import AxesConfig
|
|
36
36
|
from .django.constance import ConstanceConfig, ConstanceField
|
|
37
|
+
from .django.crontab import CrontabConfig, CrontabJobConfig
|
|
37
38
|
from .django.crypto_fields import CryptoFieldsConfig
|
|
38
39
|
from .django.environment import EnvironmentConfig
|
|
39
40
|
from .django.openapi import OpenAPIClientConfig
|
|
@@ -79,6 +80,8 @@ __all__ = [
|
|
|
79
80
|
"EnvironmentConfig",
|
|
80
81
|
"ConstanceConfig",
|
|
81
82
|
"ConstanceField",
|
|
83
|
+
"CrontabConfig",
|
|
84
|
+
"CrontabJobConfig",
|
|
82
85
|
"OpenAPIClientConfig",
|
|
83
86
|
"UnfoldConfig",
|
|
84
87
|
"AxesConfig",
|
|
@@ -6,6 +6,7 @@ Django integrations and extensions.
|
|
|
6
6
|
|
|
7
7
|
from .axes import AxesConfig
|
|
8
8
|
from .constance import ConstanceConfig, ConstanceField
|
|
9
|
+
from .crontab import CrontabConfig, CrontabJobConfig
|
|
9
10
|
from .environment import EnvironmentConfig
|
|
10
11
|
from .openapi import OpenAPIClientConfig
|
|
11
12
|
|
|
@@ -13,6 +14,8 @@ __all__ = [
|
|
|
13
14
|
"EnvironmentConfig",
|
|
14
15
|
"ConstanceConfig",
|
|
15
16
|
"ConstanceField",
|
|
17
|
+
"CrontabConfig",
|
|
18
|
+
"CrontabJobConfig",
|
|
16
19
|
"OpenAPIClientConfig",
|
|
17
20
|
"AxesConfig",
|
|
18
21
|
]
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Crontab Configuration for django-cfg.
|
|
3
|
+
|
|
4
|
+
Type-safe configuration for django-crontab with automatic
|
|
5
|
+
Django settings generation and support for management commands.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Type-safe crontab job definitions
|
|
9
|
+
- Django management command support
|
|
10
|
+
- Schedule validation
|
|
11
|
+
- Timezone support
|
|
12
|
+
- Lock file prevention of concurrent runs
|
|
13
|
+
- Command prefix configuration
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CrontabJobConfig(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Configuration for a single crontab job.
|
|
24
|
+
|
|
25
|
+
Supports both management commands and Python callables.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(
|
|
29
|
+
validate_assignment=True,
|
|
30
|
+
extra="forbid",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Job identification
|
|
34
|
+
name: str = Field(
|
|
35
|
+
...,
|
|
36
|
+
description="Human-readable job name (for identification and logging)",
|
|
37
|
+
min_length=1,
|
|
38
|
+
max_length=100,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Schedule definition (crontab format)
|
|
42
|
+
minute: str = Field(
|
|
43
|
+
default="*",
|
|
44
|
+
description="Crontab minute (0-59, *, */N, or comma-separated)",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
hour: str = Field(
|
|
48
|
+
default="*",
|
|
49
|
+
description="Crontab hour (0-23, *, */N, or comma-separated)",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
day_of_week: str = Field(
|
|
53
|
+
default="*",
|
|
54
|
+
description="Crontab day of week (0-6, *, */N, or comma-separated)",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
day_of_month: str = Field(
|
|
58
|
+
default="*",
|
|
59
|
+
description="Crontab day of month (1-31, *, */N, or comma-separated)",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
month_of_year: str = Field(
|
|
63
|
+
default="*",
|
|
64
|
+
description="Crontab month (1-12, *, */N, or comma-separated)",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Job execution configuration
|
|
68
|
+
job_type: Literal["command", "callable"] = Field(
|
|
69
|
+
default="command",
|
|
70
|
+
description="Job type: 'command' for Django management commands, 'callable' for Python functions",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# For management commands
|
|
74
|
+
command: Optional[str] = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="Django management command name (e.g., 'sync_account_balances')",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
command_args: List[str] = Field(
|
|
80
|
+
default_factory=list,
|
|
81
|
+
description="Command positional arguments",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
command_kwargs: Dict[str, Any] = Field(
|
|
85
|
+
default_factory=dict,
|
|
86
|
+
description="Command keyword arguments (e.g., {'verbosity': 0})",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# For Python callables
|
|
90
|
+
callable_path: Optional[str] = Field(
|
|
91
|
+
default=None,
|
|
92
|
+
description="Full Python path to callable (e.g., 'myapp.tasks.my_task')",
|
|
93
|
+
pattern=r"^[\w.]+$",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
callable_args: List[Any] = Field(
|
|
97
|
+
default_factory=list,
|
|
98
|
+
description="Callable positional arguments",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
callable_kwargs: Dict[str, Any] = Field(
|
|
102
|
+
default_factory=dict,
|
|
103
|
+
description="Callable keyword arguments",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Job options
|
|
107
|
+
enabled: bool = Field(
|
|
108
|
+
default=True,
|
|
109
|
+
description="Whether job is enabled",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
comment: Optional[str] = Field(
|
|
113
|
+
default=None,
|
|
114
|
+
description="Optional comment describing the job",
|
|
115
|
+
max_length=200,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@field_validator("minute", "hour", "day_of_week", "day_of_month", "month_of_year")
|
|
119
|
+
@classmethod
|
|
120
|
+
def validate_crontab_field(cls, v: str) -> str:
|
|
121
|
+
"""Validate crontab field format."""
|
|
122
|
+
if v == "*":
|
|
123
|
+
return v
|
|
124
|
+
|
|
125
|
+
# Allow */N (e.g., */15)
|
|
126
|
+
if v.startswith("*/"):
|
|
127
|
+
try:
|
|
128
|
+
int(v[2:])
|
|
129
|
+
return v
|
|
130
|
+
except ValueError:
|
|
131
|
+
raise ValueError(f"Invalid step value in crontab field: {v}")
|
|
132
|
+
|
|
133
|
+
# Allow ranges (e.g., 1-5)
|
|
134
|
+
if "-" in v:
|
|
135
|
+
parts = v.split("-")
|
|
136
|
+
if len(parts) != 2:
|
|
137
|
+
raise ValueError(f"Invalid range format in crontab field: {v}")
|
|
138
|
+
try:
|
|
139
|
+
int(parts[0])
|
|
140
|
+
int(parts[1])
|
|
141
|
+
return v
|
|
142
|
+
except ValueError:
|
|
143
|
+
raise ValueError(f"Invalid range values in crontab field: {v}")
|
|
144
|
+
|
|
145
|
+
# Allow comma-separated (e.g., 1,3,5)
|
|
146
|
+
if "," in v:
|
|
147
|
+
parts = v.split(",")
|
|
148
|
+
try:
|
|
149
|
+
for part in parts:
|
|
150
|
+
int(part)
|
|
151
|
+
return v
|
|
152
|
+
except ValueError:
|
|
153
|
+
raise ValueError(f"Invalid comma-separated values in crontab field: {v}")
|
|
154
|
+
|
|
155
|
+
# Allow single number
|
|
156
|
+
try:
|
|
157
|
+
int(v)
|
|
158
|
+
return v
|
|
159
|
+
except ValueError:
|
|
160
|
+
raise ValueError(f"Invalid crontab field format: {v}")
|
|
161
|
+
|
|
162
|
+
def model_post_init(self, __context: Any) -> None:
|
|
163
|
+
"""Validate job configuration after initialization."""
|
|
164
|
+
# Ensure either command or callable_path is set
|
|
165
|
+
if self.job_type == "command" and not self.command:
|
|
166
|
+
raise ValueError("'command' must be set when job_type is 'command'")
|
|
167
|
+
|
|
168
|
+
if self.job_type == "callable" and not self.callable_path:
|
|
169
|
+
raise ValueError("'callable_path' must be set when job_type is 'callable'")
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def schedule(self) -> str:
|
|
173
|
+
"""Get crontab schedule string."""
|
|
174
|
+
return f"{self.minute} {self.hour} {self.day_of_month} {self.month_of_year} {self.day_of_week}"
|
|
175
|
+
|
|
176
|
+
def to_django_crontab_format(self) -> tuple:
|
|
177
|
+
"""
|
|
178
|
+
Convert to django-crontab format.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Tuple for CRONJOBS list entry
|
|
182
|
+
"""
|
|
183
|
+
if self.job_type == "command":
|
|
184
|
+
# Format: (schedule, 'django.core.management.call_command', [command, *args], kwargs)
|
|
185
|
+
return (
|
|
186
|
+
self.schedule,
|
|
187
|
+
'django.core.management.call_command',
|
|
188
|
+
[self.command] + self.command_args,
|
|
189
|
+
self.command_kwargs,
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
# Format: (schedule, callable_path, args, kwargs)
|
|
193
|
+
return (
|
|
194
|
+
self.schedule,
|
|
195
|
+
self.callable_path,
|
|
196
|
+
self.callable_args,
|
|
197
|
+
self.callable_kwargs,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class CrontabConfig(BaseModel):
|
|
202
|
+
"""
|
|
203
|
+
Complete Crontab configuration container.
|
|
204
|
+
|
|
205
|
+
Integrates with django-crontab for scheduled task execution.
|
|
206
|
+
Automatically adds django_crontab to INSTALLED_APPS when enabled.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
model_config = ConfigDict(
|
|
210
|
+
validate_assignment=True,
|
|
211
|
+
extra="forbid",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
enabled: bool = Field(
|
|
215
|
+
default=True,
|
|
216
|
+
description="Enable crontab scheduling (auto-adds django_crontab to INSTALLED_APPS)",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
jobs: List[CrontabJobConfig] = Field(
|
|
220
|
+
default_factory=list,
|
|
221
|
+
description="List of scheduled jobs",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Django-crontab specific options
|
|
225
|
+
command_prefix: Optional[str] = Field(
|
|
226
|
+
default=None,
|
|
227
|
+
description="Command prefix for all cron jobs (e.g., 'DJANGO_SETTINGS_MODULE=api.settings')",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
command_suffix: Optional[str] = Field(
|
|
231
|
+
default=None,
|
|
232
|
+
description="Command suffix for all cron jobs",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
lock_jobs: bool = Field(
|
|
236
|
+
default=True,
|
|
237
|
+
description="Use lock files to prevent concurrent job execution",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
comment: str = Field(
|
|
241
|
+
default="django-crontab jobs",
|
|
242
|
+
description="Comment added to crontab file",
|
|
243
|
+
max_length=100,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def get_enabled_jobs(self) -> List[CrontabJobConfig]:
|
|
247
|
+
"""Get list of enabled jobs."""
|
|
248
|
+
return [job for job in self.jobs if job.enabled]
|
|
249
|
+
|
|
250
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
|
251
|
+
"""
|
|
252
|
+
Convert to Django settings dictionary.
|
|
253
|
+
|
|
254
|
+
Generates CRONJOBS and related settings for django-crontab.
|
|
255
|
+
"""
|
|
256
|
+
if not self.enabled:
|
|
257
|
+
return {}
|
|
258
|
+
|
|
259
|
+
settings = {}
|
|
260
|
+
|
|
261
|
+
# Build CRONJOBS list
|
|
262
|
+
enabled_jobs = self.get_enabled_jobs()
|
|
263
|
+
if enabled_jobs:
|
|
264
|
+
settings["CRONJOBS"] = [
|
|
265
|
+
job.to_django_crontab_format()
|
|
266
|
+
for job in enabled_jobs
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
# Add command prefix if configured
|
|
270
|
+
if self.command_prefix:
|
|
271
|
+
settings["CRONTAB_COMMAND_PREFIX"] = self.command_prefix
|
|
272
|
+
|
|
273
|
+
# Add command suffix if configured
|
|
274
|
+
if self.command_suffix:
|
|
275
|
+
settings["CRONTAB_COMMAND_SUFFIX"] = self.command_suffix
|
|
276
|
+
|
|
277
|
+
# Add lock jobs setting
|
|
278
|
+
settings["CRONTAB_LOCK_JOBS"] = self.lock_jobs
|
|
279
|
+
|
|
280
|
+
# Add comment
|
|
281
|
+
settings["CRONTAB_COMMENT"] = self.comment
|
|
282
|
+
|
|
283
|
+
return settings
|
|
284
|
+
|
|
285
|
+
def get_job_by_name(self, name: str) -> Optional[CrontabJobConfig]:
|
|
286
|
+
"""Get job by name."""
|
|
287
|
+
for job in self.jobs:
|
|
288
|
+
if job.name == name:
|
|
289
|
+
return job
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
def get_jobs_by_command(self, command: str) -> List[CrontabJobConfig]:
|
|
293
|
+
"""Get all jobs for a specific command."""
|
|
294
|
+
return [
|
|
295
|
+
job for job in self.jobs
|
|
296
|
+
if job.job_type == "command" and job.command == command
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
__all__ = [
|
|
301
|
+
"CrontabJobConfig",
|
|
302
|
+
"CrontabConfig",
|
|
303
|
+
]
|
|
@@ -4,6 +4,7 @@ Universal HTML builder for Django Admin display methods.
|
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, List, Optional, Union
|
|
7
|
+
import re
|
|
7
8
|
|
|
8
9
|
from django.utils.html import escape, format_html
|
|
9
10
|
from django.utils.safestring import SafeString
|
|
@@ -119,8 +120,17 @@ class HtmlBuilder:
|
|
|
119
120
|
if css_class:
|
|
120
121
|
classes += f" {css_class}"
|
|
121
122
|
|
|
122
|
-
#
|
|
123
|
-
|
|
123
|
+
# Convert items to strings, keeping SafeString as-is
|
|
124
|
+
from django.utils.safestring import SafeString, mark_safe
|
|
125
|
+
processed_items = []
|
|
126
|
+
for item in items:
|
|
127
|
+
if isinstance(item, (SafeString, str)):
|
|
128
|
+
processed_items.append(item)
|
|
129
|
+
else:
|
|
130
|
+
processed_items.append(escape(str(item)))
|
|
131
|
+
|
|
132
|
+
# Join with separator
|
|
133
|
+
joined = mark_safe(separator.join(str(item) for item in processed_items))
|
|
124
134
|
|
|
125
135
|
return format_html('<span class="{}">{}</span>', classes, joined)
|
|
126
136
|
|
|
@@ -320,6 +330,44 @@ class HtmlBuilder:
|
|
|
320
330
|
enable_plugins=enable_plugins
|
|
321
331
|
)
|
|
322
332
|
|
|
333
|
+
@staticmethod
|
|
334
|
+
def uuid_short(uuid_value: Any, length: int = 6, show_tooltip: bool = True) -> SafeString:
|
|
335
|
+
"""
|
|
336
|
+
Shorten UUID to first N characters with optional tooltip.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
uuid_value: UUID string or UUID object
|
|
340
|
+
length: Number of characters to show (default: 6)
|
|
341
|
+
show_tooltip: Show full UUID on hover (default: True)
|
|
342
|
+
|
|
343
|
+
Usage:
|
|
344
|
+
html.uuid_short(obj.id) # "a1b2c3..."
|
|
345
|
+
html.uuid_short(obj.id, length=8) # "a1b2c3d4..."
|
|
346
|
+
html.uuid_short(obj.id, show_tooltip=False) # Just short version
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
SafeString with shortened UUID
|
|
350
|
+
"""
|
|
351
|
+
uuid_str = str(uuid_value)
|
|
352
|
+
|
|
353
|
+
# Remove dashes for cleaner display
|
|
354
|
+
uuid_clean = uuid_str.replace('-', '')
|
|
355
|
+
|
|
356
|
+
# Take first N characters
|
|
357
|
+
short_uuid = uuid_clean[:length]
|
|
358
|
+
|
|
359
|
+
if show_tooltip:
|
|
360
|
+
return format_html(
|
|
361
|
+
'<code class="font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded cursor-help" title="{}">{}</code>',
|
|
362
|
+
uuid_str,
|
|
363
|
+
short_uuid
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return format_html(
|
|
367
|
+
'<code class="font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded">{}</code>',
|
|
368
|
+
short_uuid
|
|
369
|
+
)
|
|
370
|
+
|
|
323
371
|
@staticmethod
|
|
324
372
|
def markdown_docs(
|
|
325
373
|
content: Union[str, Path],
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.113"
|
|
8
8
|
description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
|
|
11
11
|
classifiers = [ "Development Status :: 4 - Beta", "Framework :: Django", "Framework :: Django :: 5.2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration", "Typing :: Typed",]
|
|
12
12
|
requires-python = ">=3.12,<3.14"
|
|
13
|
-
dependencies = [ "pydantic>=2.11.0,<3.0", "pydantic[email]>=2.11.0,<3.0", "PyYAML>=6.0,<7.0", "click>=8.2.0,<9.0", "questionary>=2.1.0,<3.0", "rich>=14.0.0,<15.0", "cloudflare>=4.3.0,<5.0", "loguru>=0.7.0,<1.0", "colorlog>=6.9.0,<7.0", "cachetools>=5.3.0,<7.0", "toml>=0.10.2,<0.11.0", "ngrok>=1.5.1; python_version>='3.12'", "psycopg[binary,pool]>=3.2.0,<4.0", "dj-database-url>=3.0.0,<4.0", "whitenoise>=6.8.0,<7.0", "django-cors-headers>=4.7.0,<5.0", "djangorestframework>=3.16.0,<4.0", "djangorestframework-simplejwt>=5.5.0,<6.0", "djangorestframework-simplejwt[token-blacklist]>=5.5.0,<6.0", "drf-nested-routers>=0.94.0,<1.0", "django-filter>=25.0,<26.0", "django-ratelimit>=4.1.0,<5.0.0", "drf-spectacular>=0.28.0,<1.0", "drf-spectacular-sidecar>=2025.8.0,<2026.0", "django-json-widget>=2.0.0,<3.0", "django-import-export>=4.3.0,<5.0", "django-extensions>=4.1.0,<5.0", "django-constance>=4.3.0,<5.0", "django-unfold>=0.64.0,<1.0", "django-redis>=6.0.0,<7.0", "redis>=6.4.0,<7.0", "hiredis>=2.0.0,<4.0", "rearq>=0.2.0,<1.0", "setuptools>=75.0.0; python_version>='3.13'", "pyTelegramBotAPI>=4.28.0,<5.0", "coolname>=2.2.0,<3.0", "django-admin-rangefilter>=0.13.0,<1.0", "python-json-logger>=3.3.0,<4.0", "requests>=2.32.0,<3.0", "tiktoken>=0.11.0,<1.0", "openai>=1.107.0,<2.0", "twilio>=9.8.0,<10.0", "sendgrid>=6.12.0,<7.0", "beautifulsoup4>=4.13.0,<5.0", "lxml>=6.0.0,<7.0", "pgvector>=0.4.0,<1.0", "tenacity>=9.1.2,<10.0.0", "mypy (>=1.18.2,<2.0.0)", "django-tailwind[reload] (>=4.2.0,<5.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "django-axes[ipware] (>=8.0.0,<9.0.0)", "pydantic-settings (>=2.11.0,<3.0.0)", "pytz>=2025.1", "httpx>=0.28.1,<1.0", "mistune>=3.1.4,<4.0",]
|
|
13
|
+
dependencies = [ "pydantic>=2.11.0,<3.0", "pydantic[email]>=2.11.0,<3.0", "PyYAML>=6.0,<7.0", "click>=8.2.0,<9.0", "questionary>=2.1.0,<3.0", "rich>=14.0.0,<15.0", "cloudflare>=4.3.0,<5.0", "loguru>=0.7.0,<1.0", "colorlog>=6.9.0,<7.0", "cachetools>=5.3.0,<7.0", "toml>=0.10.2,<0.11.0", "ngrok>=1.5.1; python_version>='3.12'", "psycopg[binary,pool]>=3.2.0,<4.0", "dj-database-url>=3.0.0,<4.0", "whitenoise>=6.8.0,<7.0", "django-cors-headers>=4.7.0,<5.0", "django-crontab>=0.7.1,<1.0", "djangorestframework>=3.16.0,<4.0", "djangorestframework-simplejwt>=5.5.0,<6.0", "djangorestframework-simplejwt[token-blacklist]>=5.5.0,<6.0", "drf-nested-routers>=0.94.0,<1.0", "django-filter>=25.0,<26.0", "django-ratelimit>=4.1.0,<5.0.0", "drf-spectacular>=0.28.0,<1.0", "drf-spectacular-sidecar>=2025.8.0,<2026.0", "django-json-widget>=2.0.0,<3.0", "django-import-export>=4.3.0,<5.0", "django-extensions>=4.1.0,<5.0", "django-constance>=4.3.0,<5.0", "django-unfold>=0.64.0,<1.0", "django-redis>=6.0.0,<7.0", "redis>=6.4.0,<7.0", "hiredis>=2.0.0,<4.0", "rearq>=0.2.0,<1.0", "setuptools>=75.0.0; python_version>='3.13'", "pyTelegramBotAPI>=4.28.0,<5.0", "coolname>=2.2.0,<3.0", "django-admin-rangefilter>=0.13.0,<1.0", "python-json-logger>=3.3.0,<4.0", "requests>=2.32.0,<3.0", "tiktoken>=0.11.0,<1.0", "openai>=1.107.0,<2.0", "twilio>=9.8.0,<10.0", "sendgrid>=6.12.0,<7.0", "beautifulsoup4>=4.13.0,<5.0", "lxml>=6.0.0,<7.0", "pgvector>=0.4.0,<1.0", "tenacity>=9.1.2,<10.0.0", "mypy (>=1.18.2,<2.0.0)", "django-tailwind[reload] (>=4.2.0,<5.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "django-axes[ipware] (>=8.0.0,<9.0.0)", "pydantic-settings (>=2.11.0,<3.0.0)", "pytz>=2025.1", "httpx>=0.28.1,<1.0", "mistune>=3.1.4,<4.0",]
|
|
14
14
|
[[project.authors]]
|
|
15
15
|
name = "Django-CFG Team"
|
|
16
16
|
email = "info@djangocfg.com"
|
django_cfg/registry/core.py
CHANGED
|
@@ -38,6 +38,10 @@ CORE_REGISTRY = {
|
|
|
38
38
|
# Security - Django Crypto Fields
|
|
39
39
|
"CryptoFieldsConfig": ("django_cfg.models.django.crypto_fields", "CryptoFieldsConfig"),
|
|
40
40
|
|
|
41
|
+
# Scheduling - Django Crontab
|
|
42
|
+
"CrontabConfig": ("django_cfg.models.django.crontab", "CrontabConfig"),
|
|
43
|
+
"CrontabJobConfig": ("django_cfg.models.django.crontab", "CrontabJobConfig"),
|
|
44
|
+
|
|
41
45
|
# Limits models
|
|
42
46
|
"LimitsConfig": ("django_cfg.models.api.limits", "LimitsConfig"),
|
|
43
47
|
|
|
Binary file
|