django-cfg 1.2.17__py3-none-any.whl → 1.2.19__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.
Files changed (81) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/models/__init__.py +68 -0
  3. django_cfg/apps/accounts/models/activity.py +34 -0
  4. django_cfg/apps/accounts/models/auth.py +50 -0
  5. django_cfg/apps/accounts/models/base.py +8 -0
  6. django_cfg/apps/accounts/models/choices.py +32 -0
  7. django_cfg/apps/accounts/models/integrations.py +75 -0
  8. django_cfg/apps/accounts/models/registration.py +52 -0
  9. django_cfg/apps/accounts/models/user.py +80 -0
  10. django_cfg/apps/maintenance/__init__.py +53 -24
  11. django_cfg/apps/maintenance/admin/__init__.py +7 -18
  12. django_cfg/apps/maintenance/admin/api_key_admin.py +185 -0
  13. django_cfg/apps/maintenance/admin/log_admin.py +156 -0
  14. django_cfg/apps/maintenance/admin/scheduled_admin.py +390 -0
  15. django_cfg/apps/maintenance/admin/site_admin.py +448 -0
  16. django_cfg/apps/maintenance/apps.py +9 -96
  17. django_cfg/apps/maintenance/management/commands/maintenance.py +193 -307
  18. django_cfg/apps/maintenance/management/commands/process_scheduled_maintenance.py +241 -0
  19. django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +152 -111
  20. django_cfg/apps/maintenance/managers/__init__.py +7 -12
  21. django_cfg/apps/maintenance/managers/cloudflare_site_manager.py +192 -0
  22. django_cfg/apps/maintenance/managers/maintenance_log_manager.py +151 -0
  23. django_cfg/apps/maintenance/migrations/0001_initial.py +145 -705
  24. django_cfg/apps/maintenance/migrations/0002_cloudflaresite_maintenance_url.py +21 -0
  25. django_cfg/apps/maintenance/models/__init__.py +23 -21
  26. django_cfg/apps/maintenance/models/cloudflare_api_key.py +109 -0
  27. django_cfg/apps/maintenance/models/cloudflare_site.py +125 -0
  28. django_cfg/apps/maintenance/models/maintenance_log.py +131 -0
  29. django_cfg/apps/maintenance/models/scheduled_maintenance.py +307 -0
  30. django_cfg/apps/maintenance/services/__init__.py +37 -16
  31. django_cfg/apps/maintenance/services/bulk_operations_service.py +400 -0
  32. django_cfg/apps/maintenance/services/maintenance_service.py +230 -0
  33. django_cfg/apps/maintenance/services/scheduled_maintenance_service.py +381 -0
  34. django_cfg/apps/maintenance/services/site_sync_service.py +390 -0
  35. django_cfg/apps/maintenance/utils/__init__.py +12 -0
  36. django_cfg/apps/maintenance/utils/retry_utils.py +109 -0
  37. django_cfg/config.py +4 -0
  38. django_cfg/core/config.py +4 -6
  39. django_cfg/modules/django_unfold/dashboard.py +4 -5
  40. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/METADATA +52 -1
  41. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/RECORD +45 -55
  42. django_cfg/apps/maintenance/README.md +0 -305
  43. django_cfg/apps/maintenance/admin/deployments_admin.py +0 -251
  44. django_cfg/apps/maintenance/admin/events_admin.py +0 -374
  45. django_cfg/apps/maintenance/admin/monitoring_admin.py +0 -215
  46. django_cfg/apps/maintenance/admin/sites_admin.py +0 -464
  47. django_cfg/apps/maintenance/managers/deployments.py +0 -287
  48. django_cfg/apps/maintenance/managers/events.py +0 -374
  49. django_cfg/apps/maintenance/managers/monitoring.py +0 -301
  50. django_cfg/apps/maintenance/managers/sites.py +0 -335
  51. django_cfg/apps/maintenance/models/cloudflare.py +0 -316
  52. django_cfg/apps/maintenance/models/maintenance.py +0 -334
  53. django_cfg/apps/maintenance/models/monitoring.py +0 -393
  54. django_cfg/apps/maintenance/models/sites.py +0 -419
  55. django_cfg/apps/maintenance/serializers/__init__.py +0 -60
  56. django_cfg/apps/maintenance/serializers/actions.py +0 -310
  57. django_cfg/apps/maintenance/serializers/base.py +0 -44
  58. django_cfg/apps/maintenance/serializers/deployments.py +0 -209
  59. django_cfg/apps/maintenance/serializers/events.py +0 -210
  60. django_cfg/apps/maintenance/serializers/monitoring.py +0 -278
  61. django_cfg/apps/maintenance/serializers/sites.py +0 -213
  62. django_cfg/apps/maintenance/services/README.md +0 -168
  63. django_cfg/apps/maintenance/services/cloudflare_client.py +0 -441
  64. django_cfg/apps/maintenance/services/dns_manager.py +0 -497
  65. django_cfg/apps/maintenance/services/maintenance_manager.py +0 -504
  66. django_cfg/apps/maintenance/services/site_sync.py +0 -448
  67. django_cfg/apps/maintenance/services/sync_command_service.py +0 -330
  68. django_cfg/apps/maintenance/services/worker_manager.py +0 -264
  69. django_cfg/apps/maintenance/signals.py +0 -38
  70. django_cfg/apps/maintenance/urls.py +0 -36
  71. django_cfg/apps/maintenance/views/__init__.py +0 -18
  72. django_cfg/apps/maintenance/views/base.py +0 -61
  73. django_cfg/apps/maintenance/views/deployments.py +0 -175
  74. django_cfg/apps/maintenance/views/events.py +0 -204
  75. django_cfg/apps/maintenance/views/monitoring.py +0 -213
  76. django_cfg/apps/maintenance/views/sites.py +0 -338
  77. django_cfg/models/cloudflare.py +0 -316
  78. /django_cfg/apps/accounts/{models.py → __models.py} +0 -0
  79. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/WHEEL +0 -0
  80. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/entry_points.txt +0 -0
  81. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/licenses/LICENSE +0 -0
@@ -1,287 +0,0 @@
1
- """
2
- Custom managers for deployment models.
3
-
4
- Provides enhanced querying capabilities for CloudflareDeployment model.
5
- """
6
-
7
- from django.db import models
8
- from django.utils import timezone
9
- from typing import Optional, Dict, Any
10
- from datetime import timedelta
11
-
12
-
13
- class CloudflareDeploymentQuerySet(models.QuerySet):
14
- """Custom queryset for CloudflareDeployment."""
15
-
16
- def for_user(self, user):
17
- """Filter deployments for sites owned by specific user."""
18
- return self.filter(site__owner=user)
19
-
20
- def by_status(self, status: str):
21
- """Filter by deployment status."""
22
- return self.filter(status=status)
23
-
24
- def by_type(self, deployment_type: str):
25
- """Filter by deployment type."""
26
- return self.filter(deployment_type=deployment_type)
27
-
28
- def active(self):
29
- """Get active deployments."""
30
- return self.filter(status='active')
31
-
32
- def failed(self):
33
- """Get failed deployments."""
34
- return self.filter(status='failed')
35
-
36
- def rolled_back(self):
37
- """Get rolled back deployments."""
38
- return self.filter(status='rolled_back')
39
-
40
- def pending(self):
41
- """Get pending deployments."""
42
- return self.filter(status='pending')
43
-
44
- def worker_deployments(self):
45
- """Get Worker deployments."""
46
- return self.filter(deployment_type='worker')
47
-
48
- def page_rule_deployments(self):
49
- """Get Page Rule deployments."""
50
- return self.filter(deployment_type='page_rule')
51
-
52
- def custom_page_deployments(self):
53
- """Get Custom Page deployments."""
54
- return self.filter(deployment_type='custom_page')
55
-
56
- def dns_deployments(self):
57
- """Get DNS deployments."""
58
- return self.filter(deployment_type='dns')
59
-
60
- def recent(self, days: int = 7):
61
- """Get recent deployments."""
62
- cutoff = timezone.now() - timedelta(days=days)
63
- return self.filter(deployed_at__gte=cutoff)
64
-
65
- def successful(self):
66
- """Get successful deployments (active status)."""
67
- return self.filter(status='active')
68
-
69
- def by_environment(self, environment: str):
70
- """Filter by site environment."""
71
- return self.filter(site__environment=environment)
72
-
73
- def production(self):
74
- """Get deployments for production sites."""
75
- return self.filter(site__environment='production')
76
-
77
- def staging(self):
78
- """Get deployments for staging sites."""
79
- return self.filter(site__environment='staging')
80
-
81
- def for_maintenance_event(self, event):
82
- """Get deployments for specific maintenance event."""
83
- return self.filter(maintenance_event=event)
84
-
85
- def can_rollback(self):
86
- """Get deployments that can be rolled back."""
87
- return self.filter(
88
- status='active',
89
- deployment_type__in=['worker', 'page_rule']
90
- )
91
-
92
- def with_site(self):
93
- """Include related site in query."""
94
- return self.select_related('site')
95
-
96
- def with_maintenance_event(self):
97
- """Include related maintenance event."""
98
- return self.select_related('maintenance_event')
99
-
100
- def with_full_relations(self):
101
- """Include all related objects."""
102
- return self.select_related('site', 'maintenance_event')
103
-
104
-
105
- class CloudflareDeploymentManager(models.Manager):
106
- """Custom manager for CloudflareDeployment."""
107
-
108
- def get_queryset(self):
109
- return CloudflareDeploymentQuerySet(self.model, using=self._db)
110
-
111
- def for_user(self, user):
112
- """Get deployments for specific user."""
113
- return self.get_queryset().for_user(user)
114
-
115
- def by_status(self, status: str):
116
- """Get deployments by status."""
117
- return self.get_queryset().by_status(status)
118
-
119
- def by_type(self, deployment_type: str):
120
- """Get deployments by type."""
121
- return self.get_queryset().by_type(deployment_type)
122
-
123
- def active(self):
124
- """Get active deployments."""
125
- return self.get_queryset().active()
126
-
127
- def failed(self):
128
- """Get failed deployments."""
129
- return self.get_queryset().failed()
130
-
131
- def rolled_back(self):
132
- """Get rolled back deployments."""
133
- return self.get_queryset().rolled_back()
134
-
135
- def pending(self):
136
- """Get pending deployments."""
137
- return self.get_queryset().pending()
138
-
139
- def worker_deployments(self):
140
- """Get Worker deployments."""
141
- return self.get_queryset().worker_deployments()
142
-
143
- def page_rule_deployments(self):
144
- """Get Page Rule deployments."""
145
- return self.get_queryset().page_rule_deployments()
146
-
147
- def custom_page_deployments(self):
148
- """Get Custom Page deployments."""
149
- return self.get_queryset().custom_page_deployments()
150
-
151
- def dns_deployments(self):
152
- """Get DNS deployments."""
153
- return self.get_queryset().dns_deployments()
154
-
155
- def recent(self, days: int = 7):
156
- """Get recent deployments."""
157
- return self.get_queryset().recent(days)
158
-
159
- def successful(self):
160
- """Get successful deployments."""
161
- return self.get_queryset().successful()
162
-
163
- def by_environment(self, environment: str):
164
- """Get deployments by environment."""
165
- return self.get_queryset().by_environment(environment)
166
-
167
- def production(self):
168
- """Get production deployments."""
169
- return self.get_queryset().production()
170
-
171
- def staging(self):
172
- """Get staging deployments."""
173
- return self.get_queryset().staging()
174
-
175
- def for_maintenance_event(self, event):
176
- """Get deployments for maintenance event."""
177
- return self.get_queryset().for_maintenance_event(event)
178
-
179
- def can_rollback(self):
180
- """Get deployments that can be rolled back."""
181
- return self.get_queryset().can_rollback()
182
-
183
- def with_site(self):
184
- """Get deployments with site."""
185
- return self.get_queryset().with_site()
186
-
187
- def with_maintenance_event(self):
188
- """Get deployments with maintenance event."""
189
- return self.get_queryset().with_maintenance_event()
190
-
191
- def with_full_relations(self):
192
- """Get deployments with all relations."""
193
- return self.get_queryset().with_full_relations()
194
-
195
- def create_deployment(
196
- self,
197
- site,
198
- deployment_type: str,
199
- cloudflare_id: str,
200
- config: Optional[Dict] = None,
201
- maintenance_event=None,
202
- **kwargs
203
- ):
204
- """Create a new Cloudflare deployment."""
205
- return self.create(
206
- site=site,
207
- deployment_type=deployment_type,
208
- cloudflare_id=cloudflare_id,
209
- config=config or {},
210
- maintenance_event=maintenance_event,
211
- deployed_at=timezone.now(),
212
- **kwargs
213
- )
214
-
215
- def get_stats_for_user(self, user) -> Dict[str, Any]:
216
- """Get deployment statistics for user."""
217
- deployments = self.for_user(user)
218
-
219
- return {
220
- 'total': deployments.count(),
221
- 'active': deployments.active().count(),
222
- 'failed': deployments.failed().count(),
223
- 'rolled_back': deployments.rolled_back().count(),
224
- 'pending': deployments.pending().count(),
225
- 'worker_deployments': deployments.worker_deployments().count(),
226
- 'page_rule_deployments': deployments.page_rule_deployments().count(),
227
- 'custom_page_deployments': deployments.custom_page_deployments().count(),
228
- 'dns_deployments': deployments.dns_deployments().count(),
229
- 'production': deployments.production().count(),
230
- 'staging': deployments.staging().count(),
231
- 'recent': deployments.recent().count(),
232
- 'can_rollback': deployments.can_rollback().count(),
233
- 'success_rate': self._get_success_rate(deployments),
234
- }
235
-
236
- def get_deployment_summary(self, user) -> Dict[str, Any]:
237
- """Get deployment summary for user."""
238
- deployments = self.for_user(user)
239
- total = deployments.count()
240
-
241
- if total == 0:
242
- return {
243
- 'total_deployments': 0,
244
- 'success_rate': 0,
245
- 'active_count': 0,
246
- 'failed_count': 0,
247
- 'recent_activity': [],
248
- }
249
-
250
- active = deployments.active().count()
251
- failed = deployments.failed().count()
252
- success_rate = self._get_success_rate(deployments)
253
-
254
- # Get recent activity
255
- recent_activity = []
256
- recent_deployments = deployments.recent(7).with_site().order_by('-deployed_at')[:10]
257
-
258
- for deployment in recent_deployments:
259
- recent_activity.append({
260
- 'id': deployment.id,
261
- 'site': deployment.site.domain,
262
- 'type': deployment.deployment_type,
263
- 'status': deployment.status,
264
- 'deployed_at': deployment.deployed_at,
265
- 'can_rollback': deployment.can_rollback,
266
- })
267
-
268
- return {
269
- 'total_deployments': total,
270
- 'success_rate': success_rate,
271
- 'active_count': active,
272
- 'failed_count': failed,
273
- 'recent_activity': recent_activity,
274
- }
275
-
276
- def get_rollback_candidates(self, user) -> 'CloudflareDeploymentQuerySet':
277
- """Get deployments that can be rolled back for user."""
278
- return self.for_user(user).can_rollback().with_site().order_by('-deployed_at')
279
-
280
- def _get_success_rate(self, queryset) -> float:
281
- """Calculate success rate percentage."""
282
- total = queryset.count()
283
- if total == 0:
284
- return 0.0
285
-
286
- successful = queryset.successful().count()
287
- return (successful / total) * 100
@@ -1,374 +0,0 @@
1
- """
2
- Custom managers for maintenance event models.
3
-
4
- Provides enhanced querying capabilities for MaintenanceEvent and MaintenanceLog models.
5
- """
6
-
7
- from django.db import models
8
- from django.utils import timezone
9
- from typing import Optional, Dict, Any, List
10
- from datetime import timedelta
11
-
12
-
13
- class MaintenanceEventQuerySet(models.QuerySet):
14
- """Custom queryset for MaintenanceEvent."""
15
-
16
- def for_user(self, user):
17
- """Filter events initiated by specific user."""
18
- return self.filter(initiated_by=user)
19
-
20
- def by_status(self, status: str):
21
- """Filter by event status."""
22
- return self.filter(status=status)
23
-
24
- def by_reason(self, reason: str):
25
- """Filter by maintenance reason."""
26
- return self.filter(reason__icontains=reason)
27
-
28
- def active(self):
29
- """Get currently active maintenance events."""
30
- return self.filter(status='active')
31
-
32
- def completed(self):
33
- """Get completed maintenance events."""
34
- return self.filter(status='completed')
35
-
36
- def failed(self):
37
- """Get failed maintenance events."""
38
- return self.filter(status='failed')
39
-
40
- def cancelled(self):
41
- """Get cancelled maintenance events."""
42
- return self.filter(status='cancelled')
43
-
44
- def scheduled(self):
45
- """Get scheduled maintenance events."""
46
- return self.filter(status='scheduled')
47
-
48
- def recent(self, days: int = 7):
49
- """Get recent maintenance events."""
50
- cutoff = timezone.now() - timedelta(days=days)
51
- return self.filter(created_at__gte=cutoff)
52
-
53
- def long_running(self, hours: int = 2):
54
- """Get long-running maintenance events."""
55
- cutoff = timezone.now() - timedelta(hours=hours)
56
- return self.filter(
57
- status='active',
58
- started_at__lte=cutoff
59
- )
60
-
61
- def with_sites(self):
62
- """Include related sites in query."""
63
- return self.prefetch_related('sites')
64
-
65
- def with_logs(self):
66
- """Include related logs in query."""
67
- return self.prefetch_related('logs')
68
-
69
- def with_deployments(self):
70
- """Include related Cloudflare deployments."""
71
- return self.prefetch_related('cloudflare_deployments')
72
-
73
- def with_full_relations(self):
74
- """Include all related objects."""
75
- return self.select_related('initiated_by').prefetch_related(
76
- 'sites', 'logs', 'cloudflare_deployments'
77
- )
78
-
79
- def affecting_production(self):
80
- """Get events affecting production sites."""
81
- return self.filter(sites__environment='production').distinct()
82
-
83
- def affecting_staging(self):
84
- """Get events affecting staging sites."""
85
- return self.filter(sites__environment='staging').distinct()
86
-
87
- def by_duration_range(self, min_minutes: Optional[int] = None, max_minutes: Optional[int] = None):
88
- """Filter by event duration."""
89
- queryset = self.filter(ended_at__isnull=False)
90
-
91
- if min_minutes is not None:
92
- min_duration = timedelta(minutes=min_minutes)
93
- queryset = queryset.extra(
94
- where=["ended_at - started_at >= %s"],
95
- params=[min_duration]
96
- )
97
-
98
- if max_minutes is not None:
99
- max_duration = timedelta(minutes=max_minutes)
100
- queryset = queryset.extra(
101
- where=["ended_at - started_at <= %s"],
102
- params=[max_duration]
103
- )
104
-
105
- return queryset
106
-
107
-
108
- class MaintenanceEventManager(models.Manager):
109
- """Custom manager for MaintenanceEvent."""
110
-
111
- def get_queryset(self):
112
- return MaintenanceEventQuerySet(self.model, using=self._db)
113
-
114
- def for_user(self, user):
115
- """Get events for specific user."""
116
- return self.get_queryset().for_user(user)
117
-
118
- def by_status(self, status: str):
119
- """Get events by status."""
120
- return self.get_queryset().by_status(status)
121
-
122
- def by_reason(self, reason: str):
123
- """Get events by reason."""
124
- return self.get_queryset().by_reason(reason)
125
-
126
- def active(self):
127
- """Get active events."""
128
- return self.get_queryset().active()
129
-
130
- def completed(self):
131
- """Get completed events."""
132
- return self.get_queryset().completed()
133
-
134
- def failed(self):
135
- """Get failed events."""
136
- return self.get_queryset().failed()
137
-
138
- def cancelled(self):
139
- """Get cancelled events."""
140
- return self.get_queryset().cancelled()
141
-
142
- def scheduled(self):
143
- """Get scheduled events."""
144
- return self.get_queryset().scheduled()
145
-
146
- def recent(self, days: int = 7):
147
- """Get recent events."""
148
- return self.get_queryset().recent(days)
149
-
150
- def long_running(self, hours: int = 2):
151
- """Get long-running events."""
152
- return self.get_queryset().long_running(hours)
153
-
154
- def with_sites(self):
155
- """Get events with sites."""
156
- return self.get_queryset().with_sites()
157
-
158
- def with_logs(self):
159
- """Get events with logs."""
160
- return self.get_queryset().with_logs()
161
-
162
- def with_deployments(self):
163
- """Get events with deployments."""
164
- return self.get_queryset().with_deployments()
165
-
166
- def with_full_relations(self):
167
- """Get events with all relations."""
168
- return self.get_queryset().with_full_relations()
169
-
170
- def affecting_production(self):
171
- """Get events affecting production."""
172
- return self.get_queryset().affecting_production()
173
-
174
- def affecting_staging(self):
175
- """Get events affecting staging."""
176
- return self.get_queryset().affecting_staging()
177
-
178
- def by_duration_range(self, min_minutes: Optional[int] = None, max_minutes: Optional[int] = None):
179
- """Get events by duration range."""
180
- return self.get_queryset().by_duration_range(min_minutes, max_minutes)
181
-
182
- def create_event(
183
- self,
184
- title: str,
185
- initiated_by,
186
- reason: str,
187
- site_ids: Optional[List[int]] = None,
188
- estimated_duration: Optional[timedelta] = None,
189
- maintenance_message: Optional[str] = None,
190
- **kwargs
191
- ):
192
- """Create a new maintenance event."""
193
- event = self.create(
194
- title=title,
195
- initiated_by=initiated_by,
196
- reason=reason,
197
- estimated_duration=estimated_duration,
198
- maintenance_message=maintenance_message or f"Maintenance in progress: {reason}",
199
- **kwargs
200
- )
201
-
202
- if site_ids:
203
- # Add sites to the event
204
- from ..models import CloudflareSite
205
- sites = CloudflareSite.objects.filter(id__in=site_ids)
206
- event.sites.add(*sites)
207
-
208
- return event
209
-
210
- def get_stats_for_user(self, user) -> Dict[str, Any]:
211
- """Get maintenance event statistics for user."""
212
- events = self.for_user(user)
213
-
214
- return {
215
- 'total': events.count(),
216
- 'active': events.active().count(),
217
- 'completed': events.completed().count(),
218
- 'failed': events.failed().count(),
219
- 'cancelled': events.cancelled().count(),
220
- 'scheduled': events.scheduled().count(),
221
- 'recent': events.recent().count(),
222
- 'long_running': events.long_running().count(),
223
- 'affecting_production': events.affecting_production().count(),
224
- 'avg_duration_minutes': self._get_avg_duration_minutes(events),
225
- 'success_rate': self._get_success_rate(events),
226
- }
227
-
228
- def get_maintenance_calendar(self, user, days: int = 30) -> List[Dict[str, Any]]:
229
- """Get maintenance calendar for user."""
230
- cutoff = timezone.now() - timedelta(days=days)
231
- events = self.for_user(user).filter(
232
- created_at__gte=cutoff
233
- ).with_sites().order_by('created_at')
234
-
235
- calendar = []
236
- for event in events:
237
- calendar.append({
238
- 'id': event.id,
239
- 'title': event.title,
240
- 'status': event.status,
241
- 'reason': event.reason,
242
- 'created_at': event.created_at,
243
- 'started_at': event.started_at,
244
- 'ended_at': event.ended_at,
245
- 'duration': event.duration,
246
- 'sites_count': event.sites.count(),
247
- 'sites': [site.domain for site in event.sites.all()[:5]], # Limit to 5
248
- })
249
-
250
- return calendar
251
-
252
- def _get_avg_duration_minutes(self, queryset) -> Optional[float]:
253
- """Calculate average duration in minutes."""
254
- completed = queryset.completed().filter(
255
- started_at__isnull=False,
256
- ended_at__isnull=False
257
- )
258
-
259
- if not completed.exists():
260
- return None
261
-
262
- total_seconds = 0
263
- count = 0
264
-
265
- for event in completed:
266
- if event.duration:
267
- total_seconds += event.duration.total_seconds()
268
- count += 1
269
-
270
- return (total_seconds / count / 60) if count > 0 else None
271
-
272
- def _get_success_rate(self, queryset) -> float:
273
- """Calculate success rate percentage."""
274
- total = queryset.filter(status__in=['completed', 'failed']).count()
275
- if total == 0:
276
- return 0.0
277
-
278
- successful = queryset.completed().count()
279
- return (successful / total) * 100
280
-
281
-
282
- class MaintenanceLogQuerySet(models.QuerySet):
283
- """Custom queryset for MaintenanceLog."""
284
-
285
- def for_event(self, event):
286
- """Filter logs for specific maintenance event."""
287
- return self.filter(maintenance_event=event)
288
-
289
- def by_level(self, level: str):
290
- """Filter by log level."""
291
- return self.filter(level=level)
292
-
293
- def info(self):
294
- """Get info level logs."""
295
- return self.filter(level='info')
296
-
297
- def warning(self):
298
- """Get warning level logs."""
299
- return self.filter(level='warning')
300
-
301
- def error(self):
302
- """Get error level logs."""
303
- return self.filter(level='error')
304
-
305
- def recent(self, minutes: int = 60):
306
- """Get recent logs."""
307
- cutoff = timezone.now() - timedelta(minutes=minutes)
308
- return self.filter(timestamp__gte=cutoff)
309
-
310
- def with_event(self):
311
- """Include related maintenance event."""
312
- return self.select_related('maintenance_event')
313
-
314
-
315
- class MaintenanceLogManager(models.Manager):
316
- """Custom manager for MaintenanceLog."""
317
-
318
- def get_queryset(self):
319
- return MaintenanceLogQuerySet(self.model, using=self._db)
320
-
321
- def for_event(self, event):
322
- """Get logs for specific event."""
323
- return self.get_queryset().for_event(event)
324
-
325
- def by_level(self, level: str):
326
- """Get logs by level."""
327
- return self.get_queryset().by_level(level)
328
-
329
- def info(self):
330
- """Get info logs."""
331
- return self.get_queryset().info()
332
-
333
- def warning(self):
334
- """Get warning logs."""
335
- return self.get_queryset().warning()
336
-
337
- def error(self):
338
- """Get error logs."""
339
- return self.get_queryset().error()
340
-
341
- def recent(self, minutes: int = 60):
342
- """Get recent logs."""
343
- return self.get_queryset().recent(minutes)
344
-
345
- def with_event(self):
346
- """Get logs with event."""
347
- return self.get_queryset().with_event()
348
-
349
- def log_info(self, event, message: str, **kwargs):
350
- """Create info log entry."""
351
- return self.create(
352
- maintenance_event=event,
353
- level='info',
354
- message=message,
355
- **kwargs
356
- )
357
-
358
- def log_warning(self, event, message: str, **kwargs):
359
- """Create warning log entry."""
360
- return self.create(
361
- maintenance_event=event,
362
- level='warning',
363
- message=message,
364
- **kwargs
365
- )
366
-
367
- def log_error(self, event, message: str, **kwargs):
368
- """Create error log entry."""
369
- return self.create(
370
- maintenance_event=event,
371
- level='error',
372
- message=message,
373
- **kwargs
374
- )