django-cfg 1.5.1__py3-none-any.whl → 1.5.2__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/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +6 -7
- django_cfg/core/base/config_model.py +10 -26
- django_cfg/core/builders/apps_builder.py +4 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/orchestrator.py +9 -19
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +4 -6
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/html/composition.py +9 -2
- django_cfg/modules/django_unfold/navigation.py +1 -26
- django_cfg/pyproject.toml +4 -4
- django_cfg/registry/core.py +4 -7
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/METADATA +5 -6
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/RECORD +77 -82
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/config.py +0 -98
- django_cfg/apps/tasks/admin/task_log.py +0 -238
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/migrations/__init__.py +0 -0
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Main task configuration models.
|
|
3
|
-
|
|
4
|
-
Contains TaskConfig class (main entry point) and related enums.
|
|
5
|
-
Size: ~250 lines (focused on main configuration)
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
import os
|
|
10
|
-
from enum import Enum
|
|
11
|
-
from typing import Any, Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field, field_validator
|
|
14
|
-
|
|
15
|
-
from django_cfg.models.base import BaseCfgAutoModule
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TaskBackend(str, Enum):
|
|
21
|
-
"""Supported task backends."""
|
|
22
|
-
REARQ = "rearq"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class QueuePriority(str, Enum):
|
|
26
|
-
"""Standard queue priorities."""
|
|
27
|
-
CRITICAL = "critical"
|
|
28
|
-
HIGH = "high"
|
|
29
|
-
DEFAULT = "default"
|
|
30
|
-
LOW = "low"
|
|
31
|
-
BACKGROUND = "background"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
35
|
-
"""
|
|
36
|
-
High-level task system configuration.
|
|
37
|
-
|
|
38
|
-
Main entry point for configuring background task processing in Django-CFG.
|
|
39
|
-
Provides environment-aware defaults and automatic Redis integration.
|
|
40
|
-
|
|
41
|
-
Example:
|
|
42
|
-
```python
|
|
43
|
-
from django_cfg.models.tasks import TaskConfig
|
|
44
|
-
|
|
45
|
-
tasks = TaskConfig(
|
|
46
|
-
enabled=True,
|
|
47
|
-
backend=TaskBackend.DRAMATIQ,
|
|
48
|
-
)
|
|
49
|
-
```
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
# === Core Settings ===
|
|
53
|
-
enabled: bool = Field(
|
|
54
|
-
default=True,
|
|
55
|
-
description="Enable background task processing"
|
|
56
|
-
)
|
|
57
|
-
backend: TaskBackend = Field(
|
|
58
|
-
default=TaskBackend.REARQ,
|
|
59
|
-
description="Task processing backend"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
def __init__(self, **data):
|
|
63
|
-
"""Initialize TaskConfig with BaseCfgAutoModule support."""
|
|
64
|
-
super().__init__(**data)
|
|
65
|
-
# Initialize _config attribute for BaseCfgAutoModule
|
|
66
|
-
self._config = None
|
|
67
|
-
|
|
68
|
-
# === Backend-Specific Configuration ===
|
|
69
|
-
rearq: 'RearqConfig' = Field(
|
|
70
|
-
default_factory=lambda: None,
|
|
71
|
-
description="ReArq-specific configuration"
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
def model_post_init(self, __context: Any) -> None:
|
|
75
|
-
"""Initialize backend configs with defaults after model creation."""
|
|
76
|
-
if self.rearq is None:
|
|
77
|
-
from .backends import RearqConfig
|
|
78
|
-
self.rearq = RearqConfig()
|
|
79
|
-
|
|
80
|
-
# === Environment-Specific Overrides ===
|
|
81
|
-
dev_processes: Optional[int] = Field(
|
|
82
|
-
default=2,
|
|
83
|
-
description="Number of processes in development environment"
|
|
84
|
-
)
|
|
85
|
-
prod_processes: Optional[int] = Field(
|
|
86
|
-
default=None,
|
|
87
|
-
description="Number of processes in production environment"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# === Auto-Configuration ===
|
|
91
|
-
auto_discover_tasks: bool = Field(
|
|
92
|
-
default=True,
|
|
93
|
-
description="Automatically discover tasks in Django apps"
|
|
94
|
-
)
|
|
95
|
-
task_modules: List[str] = Field(
|
|
96
|
-
default=["tasks"],
|
|
97
|
-
description="Module names to search for tasks"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
@field_validator("enabled")
|
|
101
|
-
@classmethod
|
|
102
|
-
def validate_enabled_with_environment(cls, v: bool) -> bool:
|
|
103
|
-
"""Validate task system can be enabled in current environment."""
|
|
104
|
-
if v:
|
|
105
|
-
# Check if we're in a test environment
|
|
106
|
-
if os.getenv("DJANGO_SETTINGS_MODULE", "").endswith("test"):
|
|
107
|
-
logger.info("Task system disabled in test environment")
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
# Additional environment checks can be added here
|
|
111
|
-
# For example, checking if Redis is available
|
|
112
|
-
|
|
113
|
-
return v
|
|
114
|
-
|
|
115
|
-
def to_django_settings(self) -> Dict[str, Any]:
|
|
116
|
-
"""
|
|
117
|
-
Generate Django settings for task system.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Dictionary with task configuration for Django settings
|
|
121
|
-
|
|
122
|
-
Example:
|
|
123
|
-
>>> config = TaskConfig()
|
|
124
|
-
>>> settings = config.to_django_settings()
|
|
125
|
-
>>> "REARQ_REDIS_URL" in settings
|
|
126
|
-
True
|
|
127
|
-
"""
|
|
128
|
-
if not self.enabled:
|
|
129
|
-
return {}
|
|
130
|
-
|
|
131
|
-
if self.backend == TaskBackend.REARQ:
|
|
132
|
-
return self.rearq.to_django_settings()
|
|
133
|
-
|
|
134
|
-
return {}
|
|
135
|
-
|
|
136
|
-
def get_smart_defaults(self):
|
|
137
|
-
"""
|
|
138
|
-
Get smart default configuration for this module.
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
TaskConfig with smart defaults based on environment
|
|
142
|
-
"""
|
|
143
|
-
from .utils import get_default_task_config
|
|
144
|
-
|
|
145
|
-
config = self.get_config()
|
|
146
|
-
debug = getattr(config, 'debug', False) if config else False
|
|
147
|
-
return get_default_task_config(debug=debug)
|
|
148
|
-
|
|
149
|
-
def get_module_config(self):
|
|
150
|
-
"""
|
|
151
|
-
Get the final configuration for this module.
|
|
152
|
-
|
|
153
|
-
Returns:
|
|
154
|
-
Self (TaskConfig instance)
|
|
155
|
-
"""
|
|
156
|
-
return self
|
|
157
|
-
|
|
158
|
-
@classmethod
|
|
159
|
-
def auto_initialize_if_needed(cls) -> Optional['TaskConfig']:
|
|
160
|
-
"""
|
|
161
|
-
Auto-initialize TaskConfig if needed based on config flags.
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
TaskConfig instance if should be initialized, None otherwise
|
|
165
|
-
|
|
166
|
-
Example:
|
|
167
|
-
>>> task_config = TaskConfig.auto_initialize_if_needed()
|
|
168
|
-
>>> if task_config:
|
|
169
|
-
... print("Tasks enabled")
|
|
170
|
-
"""
|
|
171
|
-
# Get config through BaseCfgModule
|
|
172
|
-
from django_cfg.modules import BaseCfgModule
|
|
173
|
-
base_module = BaseCfgModule()
|
|
174
|
-
config = base_module.get_config()
|
|
175
|
-
|
|
176
|
-
if not config:
|
|
177
|
-
return None
|
|
178
|
-
|
|
179
|
-
# Check if TaskConfig already exists
|
|
180
|
-
if hasattr(config, 'tasks') and config.tasks is not None:
|
|
181
|
-
# Set config reference and return existing
|
|
182
|
-
config.tasks.set_config(config)
|
|
183
|
-
return config.tasks
|
|
184
|
-
|
|
185
|
-
# Check if tasks should be enabled
|
|
186
|
-
if config.should_enable_tasks():
|
|
187
|
-
# Auto-initialize with smart defaults
|
|
188
|
-
task_config = cls().get_smart_defaults()
|
|
189
|
-
task_config.set_config(config)
|
|
190
|
-
config.tasks = task_config
|
|
191
|
-
|
|
192
|
-
logger.info("🚀 Auto-initialized TaskConfig (enabled by knowbase/agents/tasks flags)")
|
|
193
|
-
|
|
194
|
-
return task_config
|
|
195
|
-
|
|
196
|
-
return None
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
# Resolve forward references for Pydantic v2
|
|
200
|
-
from .backends import RearqConfig
|
|
201
|
-
|
|
202
|
-
TaskConfig.model_rebuild()
|
|
203
|
-
|
|
204
|
-
__all__ = [
|
|
205
|
-
"TaskConfig",
|
|
206
|
-
"TaskBackend",
|
|
207
|
-
"QueuePriority",
|
|
208
|
-
"RearqConfig",
|
|
209
|
-
]
|
django_cfg/models/tasks/utils.py
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Task configuration utilities.
|
|
3
|
-
|
|
4
|
-
Contains utility functions for task configuration:
|
|
5
|
-
- Smart queue detection
|
|
6
|
-
- Environment-aware defaults
|
|
7
|
-
- Configuration validation
|
|
8
|
-
|
|
9
|
-
Size: ~120 lines (focused on utilities)
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import logging
|
|
13
|
-
from typing import TYPE_CHECKING, List, Optional
|
|
14
|
-
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from .config import TaskConfig
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_smart_queues(debug: bool = False) -> List[str]:
|
|
22
|
-
"""
|
|
23
|
-
Get smart default queues based on enabled modules.
|
|
24
|
-
|
|
25
|
-
Automatically detects which django-cfg modules are enabled and adds
|
|
26
|
-
their corresponding queues to the default queue list.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
debug: Whether running in debug mode (affects base queues)
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
List of queue names appropriate for enabled modules
|
|
33
|
-
|
|
34
|
-
Example:
|
|
35
|
-
>>> queues = get_smart_queues(debug=True)
|
|
36
|
-
>>> "default" in queues
|
|
37
|
-
True
|
|
38
|
-
"""
|
|
39
|
-
# Base queues
|
|
40
|
-
if debug:
|
|
41
|
-
base_queues = ["default"]
|
|
42
|
-
else:
|
|
43
|
-
base_queues = ["critical", "high", "default", "low", "background"]
|
|
44
|
-
|
|
45
|
-
# Try to detect enabled modules and add their queues
|
|
46
|
-
try:
|
|
47
|
-
from django_cfg.modules.base import BaseCfgModule
|
|
48
|
-
base_module = BaseCfgModule()
|
|
49
|
-
|
|
50
|
-
# Check for knowbase module (requires "knowbase" queue)
|
|
51
|
-
if base_module.is_knowbase_enabled():
|
|
52
|
-
if "knowbase" not in base_queues:
|
|
53
|
-
base_queues.append("knowbase")
|
|
54
|
-
|
|
55
|
-
# Check for payments module (requires "payments" queue)
|
|
56
|
-
if base_module.is_payments_enabled():
|
|
57
|
-
if "payments" not in base_queues:
|
|
58
|
-
base_queues.append("payments")
|
|
59
|
-
|
|
60
|
-
# Check for agents module (may require "agents" queue in future)
|
|
61
|
-
if base_module.is_agents_enabled():
|
|
62
|
-
if "agents" not in base_queues:
|
|
63
|
-
base_queues.append("agents")
|
|
64
|
-
|
|
65
|
-
logger.info(f"🎯 Smart queue detection: {base_queues}")
|
|
66
|
-
|
|
67
|
-
except Exception as e:
|
|
68
|
-
logger.warning(f"Failed to auto-detect queues, using defaults: {e}")
|
|
69
|
-
|
|
70
|
-
return base_queues
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def get_default_task_config(debug: bool = False) -> 'TaskConfig':
|
|
74
|
-
"""
|
|
75
|
-
Get default task configuration based on environment.
|
|
76
|
-
|
|
77
|
-
Creates a TaskConfig with sensible defaults for development or production.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
debug: Whether in debug/development mode
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
TaskConfig with environment-appropriate defaults
|
|
84
|
-
|
|
85
|
-
Example:
|
|
86
|
-
>>> config = get_default_task_config(debug=True)
|
|
87
|
-
>>> config.rearq.max_jobs
|
|
88
|
-
10
|
|
89
|
-
"""
|
|
90
|
-
from .backends import RearqConfig
|
|
91
|
-
from .config import TaskConfig
|
|
92
|
-
|
|
93
|
-
if debug:
|
|
94
|
-
# Development defaults
|
|
95
|
-
return TaskConfig(
|
|
96
|
-
rearq=RearqConfig(
|
|
97
|
-
redis_url="redis://localhost:6379/0",
|
|
98
|
-
db_url="sqlite://./rearq.db",
|
|
99
|
-
max_jobs=5,
|
|
100
|
-
job_timeout=300,
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
else:
|
|
104
|
-
# Production defaults
|
|
105
|
-
return TaskConfig(
|
|
106
|
-
rearq=RearqConfig(
|
|
107
|
-
redis_url="redis://localhost:6379/0",
|
|
108
|
-
db_url="postgresql://localhost/rearq",
|
|
109
|
-
max_jobs=20,
|
|
110
|
-
job_timeout=600,
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def validate_task_config(config: 'TaskConfig', redis_url: Optional[str] = None) -> bool:
|
|
116
|
-
"""
|
|
117
|
-
Validate task configuration and dependencies.
|
|
118
|
-
|
|
119
|
-
Checks if the task configuration is valid and all required dependencies are available.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
config: TaskConfig to validate
|
|
123
|
-
redis_url: Optional Redis URL to validate
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
True if configuration is valid, False otherwise
|
|
127
|
-
|
|
128
|
-
Example:
|
|
129
|
-
>>> config = get_default_task_config()
|
|
130
|
-
>>> validate_task_config(config, "redis://localhost:6379/0")
|
|
131
|
-
True
|
|
132
|
-
"""
|
|
133
|
-
if not config.enabled:
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
|
-
# Check Redis URL if provided
|
|
137
|
-
if redis_url:
|
|
138
|
-
try:
|
|
139
|
-
from urllib.parse import urlparse
|
|
140
|
-
parsed = urlparse(redis_url)
|
|
141
|
-
if not parsed.scheme.startswith("redis"):
|
|
142
|
-
logger.error(f"Invalid Redis URL scheme: {parsed.scheme}")
|
|
143
|
-
return False
|
|
144
|
-
except Exception as e:
|
|
145
|
-
logger.error(f"Invalid Redis URL: {e}")
|
|
146
|
-
return False
|
|
147
|
-
|
|
148
|
-
# Check if ReArq is available
|
|
149
|
-
try:
|
|
150
|
-
import rearq
|
|
151
|
-
except ImportError as e:
|
|
152
|
-
logger.error(f"ReArq dependencies not available: {e}")
|
|
153
|
-
return False
|
|
154
|
-
|
|
155
|
-
return True
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
__all__ = [
|
|
159
|
-
"get_smart_queues",
|
|
160
|
-
"get_default_task_config",
|
|
161
|
-
"validate_task_config",
|
|
162
|
-
]
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# Django-Q2 Module
|
|
2
|
-
|
|
3
|
-
Автоматическая синхронизация расписаний Django-Q2 из конфига в базу данных.
|
|
4
|
-
|
|
5
|
-
## Зачем это нужно?
|
|
6
|
-
|
|
7
|
-
Django-Q2 хранит расписания в базе данных, но **не создаёт их автоматически** из конфига.
|
|
8
|
-
Этот модуль решает эту проблему - синхронизирует расписания после каждой миграции.
|
|
9
|
-
|
|
10
|
-
## Использование
|
|
11
|
-
|
|
12
|
-
### 1. Включи Django-Q2 в конфиге
|
|
13
|
-
|
|
14
|
-
```python
|
|
15
|
-
# config.py
|
|
16
|
-
from django_cfg.models.django import DjangoQ2Config
|
|
17
|
-
|
|
18
|
-
django_q2 = DjangoQ2Config(
|
|
19
|
-
enabled=True, # ← Автоматически добавит django_q и django_cfg.modules.django_q2 в INSTALLED_APPS
|
|
20
|
-
schedules=[...]
|
|
21
|
-
)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Модуль подключается автоматически!** Не нужно вручную добавлять в INSTALLED_APPS.
|
|
25
|
-
|
|
26
|
-
### 2. Определи расписания в конфиге
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from django_cfg.models.django import DjangoQ2Config, DjangoQ2ScheduleConfig
|
|
30
|
-
|
|
31
|
-
django_q2 = DjangoQ2Config(
|
|
32
|
-
enabled=True,
|
|
33
|
-
schedules=[
|
|
34
|
-
DjangoQ2ScheduleConfig(
|
|
35
|
-
name="Sync balances hourly",
|
|
36
|
-
schedule_type="hourly",
|
|
37
|
-
command="sync_account_balances",
|
|
38
|
-
command_args=["--verbose"],
|
|
39
|
-
),
|
|
40
|
-
DjangoQ2ScheduleConfig(
|
|
41
|
-
name="Cleanup daily",
|
|
42
|
-
schedule_type="cron",
|
|
43
|
-
cron="0 2 * * *", # 2 AM каждый день
|
|
44
|
-
command="cleanup_old_data",
|
|
45
|
-
command_kwargs={"days": 30},
|
|
46
|
-
),
|
|
47
|
-
],
|
|
48
|
-
)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### 3. Запусти миграции
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
python manage.py migrate
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Вывод:**
|
|
58
|
-
```
|
|
59
|
-
Running migrations:
|
|
60
|
-
...
|
|
61
|
-
Syncing 2 Django-Q2 schedule(s)...
|
|
62
|
-
✓ Created schedule: Sync balances hourly
|
|
63
|
-
✓ Created schedule: Cleanup daily
|
|
64
|
-
✅ Django-Q2 schedules synced: 2 created, 0 updated
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 4. Запусти qcluster
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
python manage.py qcluster
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Готово! Расписания автоматически синхронизированы и работают.
|
|
74
|
-
|
|
75
|
-
## Как это работает?
|
|
76
|
-
|
|
77
|
-
1. **Модуль подключается** к сигналу `post_migrate`
|
|
78
|
-
2. **После миграций** автоматически:
|
|
79
|
-
- Читает расписания из конфига
|
|
80
|
-
- Создаёт/обновляет их в базе данных (Schedule model)
|
|
81
|
-
3. **Django-Q2 читает** расписания из базы и выполняет задачи
|
|
82
|
-
|
|
83
|
-
## Ручная синхронизация (опционально)
|
|
84
|
-
|
|
85
|
-
Если нужно синхронизировать без миграций:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
python manage.py sync_django_q_schedules
|
|
89
|
-
|
|
90
|
-
# Или с --dry-run для проверки:
|
|
91
|
-
python manage.py sync_django_q_schedules --dry-run
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Безопасность
|
|
95
|
-
|
|
96
|
-
- ✅ **Идемпотентность**: можно запускать много раз, не создаст дубликаты
|
|
97
|
-
- ✅ **Без race conditions**: синхронизация происходит один раз за цикл миграций
|
|
98
|
-
- ✅ **Graceful degradation**: если Django-Q2 не установлен, модуль просто молча пропустит синхронизацию
|
|
99
|
-
- ✅ **Logging**: все операции логируются для отладки
|
|
100
|
-
|
|
101
|
-
## Преимущества перед ручной синхронизацией
|
|
102
|
-
|
|
103
|
-
| Аспект | Ручная синхронизация | Модуль |
|
|
104
|
-
|--------|---------------------|--------|
|
|
105
|
-
| Автоматизация | Нужно помнить запускать | Автоматически |
|
|
106
|
-
| Деплой | Легко забыть | Всегда синхронизировано |
|
|
107
|
-
| CI/CD | Нужно добавлять в скрипты | Работает из коробки |
|
|
108
|
-
| Ошибки | Легко пропустить | Логи миграций |
|
|
109
|
-
|
|
110
|
-
## Troubleshooting
|
|
111
|
-
|
|
112
|
-
### Расписания не создаются
|
|
113
|
-
|
|
114
|
-
Проверь:
|
|
115
|
-
1. Модуль добавлен в INSTALLED_APPS
|
|
116
|
-
2. `django_q2.enabled = True` в конфиге
|
|
117
|
-
3. В конфиге есть расписания
|
|
118
|
-
4. Миграции запущены: `python manage.py migrate`
|
|
119
|
-
|
|
120
|
-
### Расписания не обновляются
|
|
121
|
-
|
|
122
|
-
Запусти миграции повторно или используй ручную синхронизацию:
|
|
123
|
-
```bash
|
|
124
|
-
python manage.py migrate --run-syncdb
|
|
125
|
-
# или
|
|
126
|
-
python manage.py sync_django_q_schedules
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Логи синхронизации
|
|
130
|
-
|
|
131
|
-
Включи DEBUG логи:
|
|
132
|
-
```python
|
|
133
|
-
LOGGING = {
|
|
134
|
-
'loggers': {
|
|
135
|
-
'django_cfg.modules.django_q2': {
|
|
136
|
-
'level': 'DEBUG',
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
```
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AppConfig for Django-Q2 module with automatic schedule synchronization.
|
|
3
|
-
"""
|
|
4
|
-
import logging
|
|
5
|
-
from django.apps import AppConfig
|
|
6
|
-
from django.db.models.signals import post_migrate
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def sync_schedules_after_migrate(sender, **kwargs):
|
|
12
|
-
"""
|
|
13
|
-
Automatically sync Django-Q2 schedules after migrations.
|
|
14
|
-
|
|
15
|
-
This ensures schedules are always up-to-date after deployment.
|
|
16
|
-
Runs only once per migration cycle, safe from race conditions.
|
|
17
|
-
"""
|
|
18
|
-
# Only run for the django_cfg_django_q2 app itself
|
|
19
|
-
if sender.name != 'django_cfg.modules.django_q2':
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
# Import here to avoid circular imports and ensure Django is ready
|
|
23
|
-
try:
|
|
24
|
-
from django_q.models import Schedule
|
|
25
|
-
from django_cfg.core.config import get_current_config
|
|
26
|
-
except ImportError as e:
|
|
27
|
-
logger.warning(f"Could not import Django-Q2 dependencies: {e}")
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
config = get_current_config()
|
|
31
|
-
|
|
32
|
-
if not config or not hasattr(config, 'django_q2') or not config.django_q2 or not config.django_q2.enabled:
|
|
33
|
-
logger.debug("Django-Q2 not enabled, skipping schedule sync")
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
enabled_schedules = config.django_q2.get_enabled_schedules()
|
|
37
|
-
|
|
38
|
-
if not enabled_schedules:
|
|
39
|
-
logger.debug("No Django-Q2 schedules found in config")
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
logger.info(f"Syncing {len(enabled_schedules)} Django-Q2 schedule(s)...")
|
|
43
|
-
|
|
44
|
-
created = 0
|
|
45
|
-
updated = 0
|
|
46
|
-
|
|
47
|
-
for schedule_config in enabled_schedules:
|
|
48
|
-
schedule_dict = schedule_config.to_django_q_format()
|
|
49
|
-
name = schedule_dict['name']
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
schedule, created_flag = Schedule.objects.update_or_create(
|
|
53
|
-
name=name,
|
|
54
|
-
defaults=schedule_dict
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
if created_flag:
|
|
58
|
-
created += 1
|
|
59
|
-
logger.info(f" ✓ Created schedule: {name}")
|
|
60
|
-
else:
|
|
61
|
-
updated += 1
|
|
62
|
-
logger.debug(f" ✓ Updated schedule: {name}")
|
|
63
|
-
|
|
64
|
-
except Exception as e:
|
|
65
|
-
logger.error(f" ✗ Failed to sync schedule '{name}': {e}")
|
|
66
|
-
|
|
67
|
-
logger.info(f"✅ Django-Q2 schedules synced: {created} created, {updated} updated")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class DjangoQ2ModuleConfig(AppConfig):
|
|
71
|
-
"""
|
|
72
|
-
AppConfig for Django-Q2 module.
|
|
73
|
-
|
|
74
|
-
Automatically syncs schedules from config to database after migrations.
|
|
75
|
-
This eliminates the need for manual `sync_django_q_schedules` command.
|
|
76
|
-
|
|
77
|
-
Features:
|
|
78
|
-
- Automatic schedule sync after migrations
|
|
79
|
-
- Safe from race conditions (runs only once)
|
|
80
|
-
- Logs all sync operations
|
|
81
|
-
- Gracefully handles missing dependencies
|
|
82
|
-
|
|
83
|
-
Usage:
|
|
84
|
-
Add to INSTALLED_APPS:
|
|
85
|
-
INSTALLED_APPS = [
|
|
86
|
-
...
|
|
87
|
-
'django_cfg.modules.django_q2', # Auto-syncs schedules
|
|
88
|
-
]
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
default_auto_field = 'django.db.models.BigAutoField'
|
|
92
|
-
name = 'django_cfg.modules.django_q2'
|
|
93
|
-
verbose_name = 'Django-CFG Django-Q2 Module'
|
|
94
|
-
|
|
95
|
-
def ready(self):
|
|
96
|
-
"""
|
|
97
|
-
Connect post_migrate signal to automatically sync schedules.
|
|
98
|
-
|
|
99
|
-
This runs after all migrations are complete, ensuring:
|
|
100
|
-
1. Database tables exist
|
|
101
|
-
2. Config is loaded
|
|
102
|
-
3. Schedules are synced only once per migration cycle
|
|
103
|
-
"""
|
|
104
|
-
# Connect the signal
|
|
105
|
-
post_migrate.connect(sync_schedules_after_migrate, sender=self)
|
|
106
|
-
|
|
107
|
-
logger.debug(f"{self.verbose_name} initialized - auto-sync enabled")
|
|
File without changes
|
|
File without changes
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Management command to sync Django-Q2 schedules from config.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python manage.py sync_django_q_schedules # Create/update schedules
|
|
6
|
-
python manage.py sync_django_q_schedules --dry-run # Show what would be created
|
|
7
|
-
"""
|
|
8
|
-
from django.core.management.base import BaseCommand
|
|
9
|
-
from django_q.models import Schedule
|
|
10
|
-
from django_cfg.core.config import get_current_config
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Command(BaseCommand):
|
|
14
|
-
help = 'Sync Django-Q2 schedules from config to database'
|
|
15
|
-
|
|
16
|
-
def add_arguments(self, parser):
|
|
17
|
-
parser.add_argument(
|
|
18
|
-
'--dry-run',
|
|
19
|
-
action='store_true',
|
|
20
|
-
help='Show what would be created without actually creating',
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
def handle(self, *args, **options):
|
|
24
|
-
config = get_current_config()
|
|
25
|
-
|
|
26
|
-
if not config:
|
|
27
|
-
self.stdout.write(self.style.ERROR('❌ No config found'))
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
# Check if Django-Q2 is enabled
|
|
31
|
-
if not hasattr(config, 'django_q2') or not config.django_q2 or not config.django_q2.enabled:
|
|
32
|
-
self.stdout.write(self.style.WARNING('⚠️ Django-Q2 is not enabled in config'))
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
enabled_schedules = config.django_q2.get_enabled_schedules()
|
|
36
|
-
|
|
37
|
-
if not enabled_schedules:
|
|
38
|
-
self.stdout.write(self.style.WARNING('⚠️ No schedules found in config'))
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
self.stdout.write(f'📋 Found {len(enabled_schedules)} schedule(s) in config\n')
|
|
42
|
-
|
|
43
|
-
created = 0
|
|
44
|
-
updated = 0
|
|
45
|
-
|
|
46
|
-
for schedule_config in enabled_schedules:
|
|
47
|
-
schedule_dict = schedule_config.to_django_q_format()
|
|
48
|
-
name = schedule_dict['name']
|
|
49
|
-
|
|
50
|
-
if options['dry_run']:
|
|
51
|
-
self.stdout.write(f' [DRY RUN] Would create/update: {name}')
|
|
52
|
-
continue
|
|
53
|
-
|
|
54
|
-
# Update or create schedule
|
|
55
|
-
schedule, created_flag = Schedule.objects.update_or_create(
|
|
56
|
-
name=name,
|
|
57
|
-
defaults=schedule_dict
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
if created_flag:
|
|
61
|
-
created += 1
|
|
62
|
-
self.stdout.write(self.style.SUCCESS(f' ✓ Created: {name}'))
|
|
63
|
-
else:
|
|
64
|
-
updated += 1
|
|
65
|
-
self.stdout.write(self.style.SUCCESS(f' ✓ Updated: {name}'))
|
|
66
|
-
|
|
67
|
-
if not options['dry_run']:
|
|
68
|
-
self.stdout.write(self.style.SUCCESS(
|
|
69
|
-
f'\n✅ Summary: {created} created, {updated} updated'
|
|
70
|
-
))
|
|
71
|
-
else:
|
|
72
|
-
self.stdout.write(self.style.WARNING(
|
|
73
|
-
f'\n[DRY RUN] Would create {len(enabled_schedules)} schedule(s)'
|
|
74
|
-
))
|