django-cfg 1.4.111__py3-none-any.whl → 1.4.114__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 (35) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +12 -0
  3. django_cfg/apps/dashboard/serializers/django_q2.py +50 -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/django_q2_service.py +159 -0
  7. django_cfg/apps/dashboard/services/system_health_service.py +85 -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/django_q2_views.py +79 -0
  11. django_cfg/apps/dashboard/views/overview_views.py +16 -2
  12. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +16 -0
  13. django_cfg/config.py +3 -4
  14. django_cfg/core/base/config_model.py +18 -1
  15. django_cfg/core/builders/apps_builder.py +4 -0
  16. django_cfg/core/generation/data_generators/cache.py +28 -2
  17. django_cfg/core/generation/integration_generators/__init__.py +4 -0
  18. django_cfg/core/generation/integration_generators/django_q2.py +133 -0
  19. django_cfg/core/generation/orchestrator.py +13 -0
  20. django_cfg/core/integration/display/startup.py +2 -2
  21. django_cfg/core/integration/url_integration.py +2 -2
  22. django_cfg/models/__init__.py +3 -0
  23. django_cfg/models/django/__init__.py +3 -0
  24. django_cfg/models/django/django_q2.py +491 -0
  25. django_cfg/modules/django_admin/utils/html_builder.py +50 -2
  26. django_cfg/pyproject.toml +2 -2
  27. django_cfg/registry/core.py +4 -0
  28. django_cfg/static/frontend/admin.zip +0 -0
  29. django_cfg/templates/admin/index.html +389 -166
  30. django_cfg/templatetags/django_cfg.py +8 -0
  31. {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/METADATA +3 -1
  32. {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/RECORD +35 -29
  33. {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/WHEEL +0 -0
  34. {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/entry_points.txt +0 -0
  35. {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,491 @@
1
+ """
2
+ Django-Q2 Configuration for django-cfg.
3
+
4
+ Type-safe configuration for django-q2 (modern fork of django-q) with automatic
5
+ Django settings generation and support for scheduled tasks.
6
+
7
+ Django-Q2 is the actively maintained fork: https://github.com/django-q2/django-q2
8
+
9
+ Features:
10
+ - Type-safe scheduled task definitions
11
+ - Django management command support
12
+ - Cron-style and interval-based scheduling
13
+ - Redis/database broker configuration
14
+ - Queue management
15
+ - Task result storage
16
+ - Monitoring and logging
17
+ - Built-in admin interface
18
+ - Python 3.8+ and Django 3.2+ support
19
+
20
+ Migration from django-crontab to django-q2:
21
+ 1. Replace django-crontab with django-q2 in requirements
22
+ 2. Convert CrontabConfig to DjangoQ2Config
23
+ 3. Cron expressions stay the same
24
+ 4. Add additional features like intervals, hooks, retries
25
+ 5. Built-in admin interface for monitoring
26
+ 6. No need to run 'crontab add' - uses Django's own scheduler
27
+
28
+ Example:
29
+ ```python
30
+ # Old django-crontab
31
+ crontab_config = CrontabConfig(
32
+ jobs=[
33
+ CrontabJobConfig(
34
+ name="Sync balances",
35
+ minute="0",
36
+ hour="*/1", # Every hour
37
+ command="sync_account_balances",
38
+ )
39
+ ]
40
+ )
41
+
42
+ # New django-q2
43
+ django_q2_config = DjangoQ2Config(
44
+ schedules=[
45
+ DjangoQ2ScheduleConfig(
46
+ name="Sync balances",
47
+ schedule_type="hourly", # Simpler!
48
+ command="sync_account_balances",
49
+ )
50
+ ]
51
+ )
52
+ ```
53
+ """
54
+
55
+ from typing import Any, Dict, List, Literal, Optional, Union
56
+
57
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
58
+
59
+
60
+ class DjangoQ2ScheduleConfig(BaseModel):
61
+ """
62
+ Configuration for a single Django-Q2 scheduled task.
63
+
64
+ Supports both cron-style and interval-based scheduling.
65
+
66
+ Schedule types:
67
+ - cron: Traditional cron expression (e.g., "0 0 * * *")
68
+ - minutes: Every N minutes (e.g., minutes=15)
69
+ - hourly: Every hour (at minute 0)
70
+ - daily: Every day (at midnight)
71
+ - weekly: Every week (Sunday at midnight)
72
+ - monthly: Every month (1st at midnight)
73
+ - yearly: Every year (Jan 1st at midnight)
74
+ - once: Run once at next scheduled time
75
+ """
76
+
77
+ model_config = ConfigDict(
78
+ validate_assignment=True,
79
+ extra="forbid",
80
+ )
81
+
82
+ # Task identification
83
+ name: str = Field(
84
+ ...,
85
+ description="Human-readable task name (unique identifier)",
86
+ min_length=1,
87
+ max_length=100,
88
+ )
89
+
90
+ # Schedule type
91
+ schedule_type: Literal["cron", "minutes", "hourly", "daily", "weekly", "monthly", "yearly", "once"] = Field(
92
+ default="cron",
93
+ description="Schedule type: cron, minutes, hourly, daily, weekly, monthly, yearly, once",
94
+ )
95
+
96
+ # Cron-style schedule (when schedule_type="cron")
97
+ cron: Optional[str] = Field(
98
+ default=None,
99
+ description="Cron expression (e.g., '0 0 * * *' for daily at midnight)",
100
+ pattern=r"^[\d\*\-\,\/\s]+$",
101
+ )
102
+
103
+ # Interval-based schedule (when schedule_type="minutes")
104
+ minutes: Optional[int] = Field(
105
+ default=None,
106
+ ge=1,
107
+ le=525600, # Max 1 year in minutes
108
+ description="Run every N minutes (when schedule_type='minutes')",
109
+ )
110
+
111
+ # Task execution configuration
112
+ func: Optional[str] = Field(
113
+ default=None,
114
+ description="Function path (e.g., 'django.core.management.call_command' or 'myapp.tasks.my_task'). Auto-set when 'command' is provided.",
115
+ )
116
+
117
+ # Function arguments
118
+ args: Optional[List[Any]] = Field(
119
+ default=None,
120
+ description="Positional arguments for the function",
121
+ )
122
+
123
+ kwargs: Optional[Dict[str, Any]] = Field(
124
+ default=None,
125
+ description="Keyword arguments for the function",
126
+ )
127
+
128
+ # Management command support (shortcut)
129
+ command: Optional[str] = Field(
130
+ default=None,
131
+ description="Django management command name (auto-sets func to call_command)",
132
+ )
133
+
134
+ command_args: Optional[List[str]] = Field(
135
+ default=None,
136
+ description="Management command arguments",
137
+ )
138
+
139
+ command_kwargs: Optional[Dict[str, Any]] = Field(
140
+ default=None,
141
+ description="Management command keyword arguments",
142
+ )
143
+
144
+ # Task options
145
+ enabled: bool = Field(
146
+ default=True,
147
+ description="Whether task is enabled",
148
+ )
149
+
150
+ queue: Optional[str] = Field(
151
+ default=None,
152
+ description="Queue name (None = default queue)",
153
+ )
154
+
155
+ timeout: Optional[int] = Field(
156
+ default=None,
157
+ ge=1,
158
+ le=86400, # Max 24 hours
159
+ description="Task timeout in seconds",
160
+ )
161
+
162
+ repeats: int = Field(
163
+ default=-1,
164
+ description="Number of times to repeat (-1 = infinite)",
165
+ )
166
+
167
+ hook: Optional[str] = Field(
168
+ default=None,
169
+ description="Hook function to call after task completion",
170
+ )
171
+
172
+ cluster: Optional[str] = Field(
173
+ default=None,
174
+ description="Cluster name (for multi-cluster setups)",
175
+ )
176
+
177
+ def model_post_init(self, __context: Any) -> None:
178
+ """Validate and setup configuration after initialization."""
179
+ # Validate that either func or command is provided
180
+ if not self.func and not self.command:
181
+ raise ValueError("Either 'func' or 'command' must be provided")
182
+
183
+ # Setup management command
184
+ if self.command:
185
+ self.func = "django.core.management.call_command"
186
+ args = [self.command]
187
+ if self.command_args:
188
+ args.extend(self.command_args)
189
+ self.args = args
190
+ if self.command_kwargs:
191
+ self.kwargs = self.command_kwargs
192
+
193
+ # Validate schedule configuration
194
+ if self.schedule_type == "cron" and not self.cron:
195
+ raise ValueError("'cron' must be set when schedule_type is 'cron'")
196
+
197
+ if self.schedule_type == "minutes" and not self.minutes:
198
+ raise ValueError("'minutes' must be set when schedule_type is 'minutes'")
199
+
200
+ def to_django_q_format(self) -> Dict[str, Any]:
201
+ """
202
+ Convert to Django-Q Schedule format.
203
+
204
+ Returns:
205
+ Dictionary for ORM.create() or schedule creation
206
+ """
207
+ # Map our schedule types to Django-Q2 constants
208
+ type_mapping = {
209
+ "once": "O",
210
+ "minutes": "I",
211
+ "hourly": "H",
212
+ "daily": "D",
213
+ "weekly": "W",
214
+ "monthly": "M",
215
+ "yearly": "Y",
216
+ "cron": "C",
217
+ }
218
+
219
+ config = {
220
+ "name": self.name,
221
+ "func": self.func,
222
+ "schedule_type": type_mapping.get(self.schedule_type, self.schedule_type.upper()),
223
+ }
224
+
225
+ if self.args:
226
+ config["args"] = str(self.args)
227
+
228
+ if self.kwargs:
229
+ config["kwargs"] = str(self.kwargs)
230
+
231
+ if self.schedule_type == "cron" and self.cron:
232
+ config["cron"] = self.cron
233
+
234
+ if self.schedule_type == "minutes" and self.minutes:
235
+ config["minutes"] = self.minutes
236
+
237
+ if self.queue:
238
+ config["queue"] = self.queue
239
+
240
+ if self.timeout:
241
+ config["timeout"] = self.timeout
242
+
243
+ if self.repeats != -1:
244
+ config["repeats"] = self.repeats
245
+
246
+ if self.hook:
247
+ config["hook"] = self.hook
248
+
249
+ if self.cluster:
250
+ config["cluster"] = self.cluster
251
+
252
+ return config
253
+
254
+
255
+ class DjangoQ2Config(BaseModel):
256
+ """
257
+ Complete Django-Q2 configuration container.
258
+
259
+ Integrates with django-q2 (modern fork) for scheduled and async task execution.
260
+ Automatically adds django_q to INSTALLED_APPS when enabled.
261
+
262
+ Installation:
263
+ pip install django-q2[redis]
264
+
265
+ Example:
266
+ ```python
267
+ # MAGIC: broker_url automatically uses config.redis_url! 🎉
268
+ # Just set redis_url once in your DjangoConfig:
269
+ # redis_url: Optional[str] = env.redis_url
270
+
271
+ django_q2_config = DjangoQ2Config(
272
+ enabled=True,
273
+ # broker_url is auto-detected from config.redis_url!
274
+ schedules=[
275
+ DjangoQ2ScheduleConfig(
276
+ name="Sync balances every hour",
277
+ schedule_type="hourly",
278
+ command="sync_account_balances",
279
+ ),
280
+ DjangoQ2ScheduleConfig(
281
+ name="Cleanup old data daily",
282
+ schedule_type="cron",
283
+ cron="0 2 * * *", # 2 AM daily
284
+ command="cleanup_old_data",
285
+ command_kwargs={"days": 30},
286
+ ),
287
+ DjangoQ2ScheduleConfig(
288
+ name="Quick check every 5 minutes",
289
+ schedule_type="minutes",
290
+ minutes=5,
291
+ command="health_check",
292
+ ),
293
+ ],
294
+ )
295
+ ```
296
+
297
+ Admin interface:
298
+ - Visit /admin/django_q/ to view tasks and schedules
299
+ - Monitor task execution, failures, and performance
300
+ - Manually trigger scheduled tasks
301
+ - View task results and logs
302
+ """
303
+
304
+ model_config = ConfigDict(
305
+ validate_assignment=True,
306
+ extra="forbid",
307
+ )
308
+
309
+ enabled: bool = Field(
310
+ default=True,
311
+ description="Enable Django-Q (auto-adds django_q to INSTALLED_APPS)",
312
+ )
313
+
314
+ schedules: List[DjangoQ2ScheduleConfig] = Field(
315
+ default_factory=list,
316
+ description="List of scheduled tasks",
317
+ )
318
+
319
+ # Django-Q broker configuration
320
+ broker_url: str = Field(
321
+ default="redis://localhost:6379/0",
322
+ description="Broker URL (Redis recommended for production)",
323
+ )
324
+
325
+ broker_class: Literal["redis", "orm"] = Field(
326
+ default="redis",
327
+ description="Broker backend class (redis or orm)",
328
+ )
329
+
330
+ # Queue configuration
331
+ queue_limit: Optional[int] = Field(
332
+ default=50,
333
+ ge=1,
334
+ description="Maximum tasks in queue before rejecting new ones",
335
+ )
336
+
337
+ workers: int = Field(
338
+ default=4,
339
+ ge=1,
340
+ le=32,
341
+ description="Number of worker processes",
342
+ )
343
+
344
+ timeout: int = Field(
345
+ default=300,
346
+ ge=1,
347
+ le=86400, # Max 24 hours
348
+ description="Default task timeout in seconds",
349
+ )
350
+
351
+ retry: int = Field(
352
+ default=3600,
353
+ ge=0,
354
+ description="Seconds to wait before retrying failed tasks (0 = no retry)",
355
+ )
356
+
357
+ # Task result configuration
358
+ save_limit: int = Field(
359
+ default=250,
360
+ ge=0,
361
+ description="Maximum number of successful tasks to save (0 = unlimited)",
362
+ )
363
+
364
+ cached: int = Field(
365
+ default=500,
366
+ ge=0,
367
+ description="Maximum number of tasks to cache (0 = disabled)",
368
+ )
369
+
370
+ # Monitoring
371
+ monitor_interval: int = Field(
372
+ default=30,
373
+ ge=1,
374
+ description="Seconds between monitor checks",
375
+ )
376
+
377
+ # Logging
378
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
379
+ default="INFO",
380
+ description="Django-Q log level",
381
+ )
382
+
383
+ # Advanced options
384
+ compress: bool = Field(
385
+ default=False,
386
+ description="Compress task data",
387
+ )
388
+
389
+ catch_up: bool = Field(
390
+ default=True,
391
+ description="Run missed scheduled tasks immediately",
392
+ )
393
+
394
+ sync: bool = Field(
395
+ default=False,
396
+ description="Run tasks synchronously (for testing)",
397
+ )
398
+
399
+ def get_enabled_schedules(self) -> List[DjangoQ2ScheduleConfig]:
400
+ """Get list of enabled schedules."""
401
+ return [schedule for schedule in self.schedules if schedule.enabled]
402
+
403
+ def to_django_settings(self, parent_config: Optional[Any] = None) -> Dict[str, Any]:
404
+ """
405
+ Convert to Django settings dictionary.
406
+
407
+ Generates Q_CLUSTER configuration for Django-Q2.
408
+
409
+ Args:
410
+ parent_config: Optional parent DjangoConfig for accessing redis_url
411
+
412
+ Note: Schedules are created via Django ORM, not settings.
413
+ Use management command: python manage.py qcluster
414
+ """
415
+ if not self.enabled:
416
+ return {}
417
+
418
+ # Auto-detect redis_url from parent config if not explicitly set
419
+ broker_url = self.broker_url
420
+ if broker_url == "redis://localhost:6379/0" and parent_config:
421
+ # Use redis_url from parent config if available
422
+ if hasattr(parent_config, 'redis_url') and parent_config.redis_url:
423
+ broker_url = parent_config.redis_url
424
+
425
+ # Map short broker names to full class paths
426
+ broker_class_map = {
427
+ "redis": "django_q.brokers.redis_broker.Redis",
428
+ "orm": "django_q.brokers.orm.ORM",
429
+ }
430
+
431
+ cluster_config = {
432
+ # Broker
433
+ "name": "django_cfg_cluster",
434
+ "broker": broker_url,
435
+ "broker_class": broker_class_map.get(self.broker_class, self.broker_class),
436
+
437
+ # Queue
438
+ "queue_limit": self.queue_limit,
439
+ "workers": self.workers,
440
+ "timeout": self.timeout,
441
+ "retry": self.retry,
442
+
443
+ # Results
444
+ "save_limit": self.save_limit,
445
+ "cached": self.cached,
446
+
447
+ # Monitoring
448
+ "monitor": self.monitor_interval,
449
+
450
+ # Logging
451
+ "log_level": self.log_level,
452
+
453
+ # Advanced
454
+ "compress": self.compress,
455
+ "catch_up": self.catch_up,
456
+ "sync": self.sync,
457
+
458
+ # Django integration
459
+ "orm": "default",
460
+ }
461
+
462
+ # Only add django_redis if broker is actually Redis
463
+ if self.broker_class == "redis":
464
+ # Don't set django_redis - let Django-Q2 connect directly via broker URL
465
+ pass
466
+
467
+ settings = {
468
+ "Q_CLUSTER": cluster_config
469
+ }
470
+
471
+ return settings
472
+
473
+ def get_schedule_by_name(self, name: str) -> Optional[DjangoQ2ScheduleConfig]:
474
+ """Get schedule by name."""
475
+ for schedule in self.schedules:
476
+ if schedule.name == name:
477
+ return schedule
478
+ return None
479
+
480
+ def get_schedules_by_command(self, command: str) -> List[DjangoQ2ScheduleConfig]:
481
+ """Get all schedules for a specific command."""
482
+ return [
483
+ schedule for schedule in self.schedules
484
+ if schedule.command == command
485
+ ]
486
+
487
+
488
+ __all__ = [
489
+ "DjangoQ2ScheduleConfig",
490
+ "DjangoQ2Config",
491
+ ]
@@ -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.114"
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-q2==1.8.0", "croniter>=6.0.0,<7.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-Q2
42
+ "DjangoQ2Config": ("django_cfg.models.django.django_q2", "DjangoQ2Config"),
43
+ "DjangoQ2ScheduleConfig": ("django_cfg.models.django.django_q2", "DjangoQ2ScheduleConfig"),
44
+
41
45
  # Limits models
42
46
  "LimitsConfig": ("django_cfg.models.api.limits", "LimitsConfig"),
43
47
 
Binary file