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.

Files changed (33) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +10 -0
  3. django_cfg/apps/dashboard/serializers/crontab.py +84 -0
  4. django_cfg/apps/dashboard/serializers/overview.py +22 -11
  5. django_cfg/apps/dashboard/services/__init__.py +2 -0
  6. django_cfg/apps/dashboard/services/crontab_service.py +210 -0
  7. django_cfg/apps/dashboard/services/system_health_service.py +72 -0
  8. django_cfg/apps/dashboard/urls.py +2 -0
  9. django_cfg/apps/dashboard/views/__init__.py +2 -0
  10. django_cfg/apps/dashboard/views/crontab_views.py +72 -0
  11. django_cfg/apps/dashboard/views/overview_views.py +16 -2
  12. django_cfg/config.py +3 -4
  13. django_cfg/core/base/config_model.py +7 -0
  14. django_cfg/core/builders/apps_builder.py +4 -0
  15. django_cfg/core/generation/integration_generators/__init__.py +3 -0
  16. django_cfg/core/generation/integration_generators/crontab.py +64 -0
  17. django_cfg/core/generation/orchestrator.py +13 -0
  18. django_cfg/core/integration/display/startup.py +2 -2
  19. django_cfg/core/integration/url_integration.py +2 -2
  20. django_cfg/models/__init__.py +3 -0
  21. django_cfg/models/django/__init__.py +3 -0
  22. django_cfg/models/django/crontab.py +303 -0
  23. django_cfg/modules/django_admin/utils/html_builder.py +50 -2
  24. django_cfg/pyproject.toml +2 -2
  25. django_cfg/registry/core.py +4 -0
  26. django_cfg/static/frontend/admin.zip +0 -0
  27. django_cfg/templates/admin/index.html +389 -166
  28. django_cfg/templatetags/django_cfg.py +8 -0
  29. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/METADATA +2 -1
  30. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/RECORD +33 -28
  31. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/WHEEL +0 -0
  32. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/entry_points.txt +0 -0
  33. {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
- LIB_DOCS_URL,
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", LIB_DOCS_URL)
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
- LIB_DOCS_URL,
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": LIB_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,
@@ -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
- # Join items with separator
123
- joined = format_html(separator.join(['{}'] * len(items)), *items)
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.111"
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"
@@ -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