django-cfg 1.4.113__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 (32) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +10 -8
  3. django_cfg/apps/dashboard/serializers/django_q2.py +50 -0
  4. django_cfg/apps/dashboard/services/__init__.py +2 -2
  5. django_cfg/apps/dashboard/services/django_q2_service.py +159 -0
  6. django_cfg/apps/dashboard/services/system_health_service.py +39 -26
  7. django_cfg/apps/dashboard/urls.py +2 -2
  8. django_cfg/apps/dashboard/views/__init__.py +2 -2
  9. django_cfg/apps/dashboard/views/django_q2_views.py +79 -0
  10. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +16 -0
  11. django_cfg/core/base/config_model.py +15 -5
  12. django_cfg/core/builders/apps_builder.py +3 -3
  13. django_cfg/core/generation/data_generators/cache.py +28 -2
  14. django_cfg/core/generation/integration_generators/__init__.py +4 -3
  15. django_cfg/core/generation/integration_generators/django_q2.py +133 -0
  16. django_cfg/core/generation/orchestrator.py +7 -7
  17. django_cfg/models/__init__.py +3 -3
  18. django_cfg/models/django/__init__.py +3 -3
  19. django_cfg/models/django/django_q2.py +491 -0
  20. django_cfg/pyproject.toml +2 -2
  21. django_cfg/registry/core.py +3 -3
  22. django_cfg/static/frontend/admin.zip +0 -0
  23. {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/METADATA +3 -2
  24. {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/RECORD +27 -26
  25. django_cfg/apps/dashboard/serializers/crontab.py +0 -84
  26. django_cfg/apps/dashboard/services/crontab_service.py +0 -210
  27. django_cfg/apps/dashboard/views/crontab_views.py +0 -72
  28. django_cfg/core/generation/integration_generators/crontab.py +0 -64
  29. django_cfg/models/django/crontab.py +0 -303
  30. {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/WHEEL +0 -0
  31. {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/entry_points.txt +0 -0
  32. {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,133 @@
1
+ """
2
+ Django-Q2 settings generator for django-cfg.
3
+
4
+ Generates django-q2 settings and handles INSTALLED_APPS integration.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Any, Dict, Optional
8
+
9
+ from django_cfg.modules.django_logging import logger
10
+
11
+ if TYPE_CHECKING:
12
+ from django_cfg.models.django.django_q import DjangoQ2Config
13
+
14
+
15
+ class DjangoQ2SettingsGenerator:
16
+ """
17
+ Generates task scheduling settings for django-q2.
18
+
19
+ Automatically:
20
+ - Generates Q_CLUSTER configuration
21
+ - Adds django_q to INSTALLED_APPS if enabled
22
+ - Configures broker, workers, and task settings
23
+ - Provides schedule management via Django ORM
24
+
25
+ Django-Q2 vs django-crontab:
26
+ - No need to run 'crontab add' - schedules in database
27
+ - Built-in admin interface for monitoring
28
+ - Support for both cron and interval scheduling
29
+ - Task retries and hooks
30
+ - Async task support
31
+ """
32
+
33
+ def __init__(self, config: "DjangoQ2Config", parent_config: Optional[Any] = None):
34
+ """
35
+ Initialize with Django-Q2 configuration.
36
+
37
+ Args:
38
+ config: DjangoQ2Config instance
39
+ parent_config: Optional parent DjangoConfig for accessing redis_url
40
+ """
41
+ self.config = config
42
+ self.parent_config = parent_config
43
+
44
+ def generate(self) -> Dict[str, Any]:
45
+ """
46
+ Generate Django-Q2 settings.
47
+
48
+ Returns:
49
+ Dictionary with Q_CLUSTER configuration
50
+ """
51
+ if not self.config or not self.config.enabled:
52
+ return {}
53
+
54
+ settings = self.config.to_django_settings(parent_config=self.parent_config)
55
+
56
+ # Log configuration
57
+ logger.info(
58
+ f"✓ Configured Django-Q2 task queue "
59
+ f"[broker: {self.config.broker_class}, workers: {self.config.workers}]"
60
+ )
61
+
62
+ enabled_schedules = self.config.get_enabled_schedules()
63
+ if enabled_schedules:
64
+ logger.info(
65
+ f"✓ Found {len(enabled_schedules)} scheduled task(s) "
66
+ f"[use admin or management commands to create schedules]"
67
+ )
68
+
69
+ # Log individual schedules in debug mode
70
+ for schedule in enabled_schedules:
71
+ logger.debug(
72
+ f" - {schedule.name}: {schedule.schedule_type} → "
73
+ f"{schedule.command if schedule.command else schedule.func}"
74
+ )
75
+
76
+ logger.info(
77
+ "📝 To create schedules: python manage.py qcluster "
78
+ "or use Django admin at /admin/django_q/schedule/"
79
+ )
80
+
81
+ return settings
82
+
83
+ def generate_schedule_creation_code(self) -> str:
84
+ """
85
+ Generate Python code to create schedules programmatically.
86
+
87
+ Returns:
88
+ Python code string for creating schedules
89
+
90
+ Example:
91
+ ```python
92
+ code = generator.generate_schedule_creation_code()
93
+ # Use in management command or startup script
94
+ ```
95
+ """
96
+ if not self.config or not self.config.enabled:
97
+ return ""
98
+
99
+ enabled_schedules = self.config.get_enabled_schedules()
100
+ if not enabled_schedules:
101
+ return ""
102
+
103
+ lines = [
104
+ "# Auto-generated Django-Q2 schedule creation",
105
+ "from django_q.models import Schedule",
106
+ "",
107
+ "# Create or update schedules",
108
+ ]
109
+
110
+ for schedule in enabled_schedules:
111
+ schedule_dict = schedule.to_django_q_format()
112
+
113
+ lines.append("")
114
+ lines.append(f"# {schedule.name}")
115
+ lines.append("Schedule.objects.update_or_create(")
116
+ lines.append(f" name='{schedule.name}',")
117
+ lines.append(" defaults={")
118
+
119
+ for key, value in schedule_dict.items():
120
+ if key == "name":
121
+ continue
122
+ if isinstance(value, str):
123
+ lines.append(f" '{key}': '{value}',")
124
+ else:
125
+ lines.append(f" '{key}': {value},")
126
+
127
+ lines.append(" }")
128
+ lines.append(")")
129
+
130
+ return "\n".join(lines)
131
+
132
+
133
+ __all__ = ["DjangoQ2SettingsGenerator"]
@@ -78,7 +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
+ settings.update(self._generate_django_q2_settings())
82
82
  settings.update(self._generate_tailwind_settings())
83
83
 
84
84
  # Apply additional settings (user overrides)
@@ -224,17 +224,17 @@ class SettingsOrchestrator:
224
224
  except Exception as e:
225
225
  raise ConfigurationError(f"Failed to generate tasks settings: {e}") from e
226
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:
227
+ def _generate_django_q2_settings(self) -> Dict[str, Any]:
228
+ """Generate Django-Q2 task scheduling settings."""
229
+ if not hasattr(self.config, "django_q2") or not self.config.django_q2:
230
230
  return {}
231
231
 
232
232
  try:
233
- from .integration_generators.crontab import CrontabSettingsGenerator
234
- generator = CrontabSettingsGenerator(self.config.crontab)
233
+ from .integration_generators.django_q2 import DjangoQ2SettingsGenerator
234
+ generator = DjangoQ2SettingsGenerator(self.config.django_q2, parent_config=self.config)
235
235
  return generator.generate()
236
236
  except Exception as e:
237
- raise ConfigurationError(f"Failed to generate crontab settings: {e}") from e
237
+ raise ConfigurationError(f"Failed to generate Django-Q2 settings: {e}") from e
238
238
 
239
239
  def _generate_tailwind_settings(self) -> Dict[str, Any]:
240
240
  """Generate Tailwind CSS settings."""
@@ -34,8 +34,8 @@ 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
38
37
  from .django.crypto_fields import CryptoFieldsConfig
38
+ from .django.django_q2 import DjangoQ2Config, DjangoQ2ScheduleConfig
39
39
  from .django.environment import EnvironmentConfig
40
40
  from .django.openapi import OpenAPIClientConfig
41
41
  from .infrastructure.cache import CacheConfig
@@ -80,8 +80,8 @@ __all__ = [
80
80
  "EnvironmentConfig",
81
81
  "ConstanceConfig",
82
82
  "ConstanceField",
83
- "CrontabConfig",
84
- "CrontabJobConfig",
83
+ "DjangoQ2Config",
84
+ "DjangoQ2ScheduleConfig",
85
85
  "OpenAPIClientConfig",
86
86
  "UnfoldConfig",
87
87
  "AxesConfig",
@@ -6,7 +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
+ from .django_q2 import DjangoQ2Config, DjangoQ2ScheduleConfig
10
10
  from .environment import EnvironmentConfig
11
11
  from .openapi import OpenAPIClientConfig
12
12
 
@@ -14,8 +14,8 @@ __all__ = [
14
14
  "EnvironmentConfig",
15
15
  "ConstanceConfig",
16
16
  "ConstanceField",
17
- "CrontabConfig",
18
- "CrontabJobConfig",
17
+ "DjangoQ2Config",
18
+ "DjangoQ2ScheduleConfig",
19
19
  "OpenAPIClientConfig",
20
20
  "AxesConfig",
21
21
  ]
@@ -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
+ ]