netbox-maintenance-device 1.2.1__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 (35) hide show
  1. netbox_maintenance_device/__init__.py +40 -0
  2. netbox_maintenance_device/admin.py +16 -0
  3. netbox_maintenance_device/api/__init__.py +1 -0
  4. netbox_maintenance_device/api/__pycache__/views.cpython-312.pyc +0 -0
  5. netbox_maintenance_device/api/permissions.py +65 -0
  6. netbox_maintenance_device/api/serializers.py +217 -0
  7. netbox_maintenance_device/api/urls.py +60 -0
  8. netbox_maintenance_device/api/views.py +461 -0
  9. netbox_maintenance_device/database_healer.py +126 -0
  10. netbox_maintenance_device/forms.py +68 -0
  11. netbox_maintenance_device/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  12. netbox_maintenance_device/locale/pt_BR/LC_MESSAGES/django.po +251 -0
  13. netbox_maintenance_device/migrations/0001_initial.py +56 -0
  14. netbox_maintenance_device/migrations/0002_cleanup_notifications.py +137 -0
  15. netbox_maintenance_device/migrations/__init__.py +1 -0
  16. netbox_maintenance_device/models.py +126 -0
  17. netbox_maintenance_device/navigation.py +43 -0
  18. netbox_maintenance_device/static/netbox_maintenance_device/css/maintenance.css +318 -0
  19. netbox_maintenance_device/tables.py +132 -0
  20. netbox_maintenance_device/template_content.py +50 -0
  21. netbox_maintenance_device/templates/netbox_maintenance_device/device_maintenance_buttons.html +24 -0
  22. netbox_maintenance_device/templates/netbox_maintenance_device/device_maintenance_section.html +266 -0
  23. netbox_maintenance_device/templates/netbox_maintenance_device/device_maintenance_tab.html +290 -0
  24. netbox_maintenance_device/templates/netbox_maintenance_device/maintenanceexecution.html +57 -0
  25. netbox_maintenance_device/templates/netbox_maintenance_device/maintenanceplan.html +94 -0
  26. netbox_maintenance_device/templates/netbox_maintenance_device/upcoming_maintenance.html +218 -0
  27. netbox_maintenance_device/templatetags/__init__.py +1 -0
  28. netbox_maintenance_device/templatetags/maintenance_extras.py +27 -0
  29. netbox_maintenance_device/urls.py +32 -0
  30. netbox_maintenance_device/views.py +219 -0
  31. netbox_maintenance_device-1.2.1.dist-info/METADATA +309 -0
  32. netbox_maintenance_device-1.2.1.dist-info/RECORD +35 -0
  33. netbox_maintenance_device-1.2.1.dist-info/WHEEL +5 -0
  34. netbox_maintenance_device-1.2.1.dist-info/licenses/LICENSE +198 -0
  35. netbox_maintenance_device-1.2.1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,40 @@
1
+ from netbox.plugins import PluginConfig
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ class MaintenanceDeviceConfig(PluginConfig):
5
+ name = 'netbox_maintenance_device'
6
+ verbose_name = _('NetBox Device Maintenance')
7
+ description = 'Manage device preventive and corrective maintenance with multilingual support'
8
+ version = '1.2.1'
9
+ author = 'Diego Godoy'
10
+ author_email = 'diegoalex-gdy@outlook.com'
11
+ base_url = 'maintenance-device'
12
+ icon = 'mdi-wrench-cog'
13
+
14
+ # Required NetBox version - Conservative range for this release
15
+ min_version = '4.4.0'
16
+ max_version = '4.4.99'
17
+
18
+ # Default configurations
19
+ default_settings = {
20
+ 'default_frequency_days': 30,
21
+ 'auto_heal_database': True, # Enable automatic database healing
22
+ }
23
+
24
+ # Translation configuration
25
+ default_language = 'en'
26
+ locale_paths = ['locale']
27
+
28
+ def ready(self):
29
+ """
30
+ Called when the plugin is ready. Perform any necessary initialization.
31
+ """
32
+ super().ready()
33
+
34
+ # Note: Database auto-healing is handled by migrations and model operations
35
+ # to avoid issues during initial Django setup and collectstatic operations
36
+ import logging
37
+ logger = logging.getLogger(__name__)
38
+ logger.info("NetBox Maintenance Device v1.2.1 initialized successfully")
39
+
40
+ config = MaintenanceDeviceConfig
@@ -0,0 +1,16 @@
1
+ from django.contrib import admin
2
+ from . import models
3
+
4
+ @admin.register(models.MaintenancePlan)
5
+ class MaintenancePlanAdmin(admin.ModelAdmin):
6
+ list_display = ['device', 'name', 'maintenance_type', 'frequency_days', 'is_active', 'created']
7
+ list_filter = ['maintenance_type', 'is_active', 'created']
8
+ search_fields = ['device__name', 'name', 'description']
9
+ ordering = ['device', 'name']
10
+
11
+ @admin.register(models.MaintenanceExecution)
12
+ class MaintenanceExecutionAdmin(admin.ModelAdmin):
13
+ list_display = ['maintenance_plan', 'scheduled_date', 'completed_date', 'status', 'technician']
14
+ list_filter = ['status', 'completed', 'scheduled_date']
15
+ search_fields = ['maintenance_plan__device__name', 'maintenance_plan__name', 'technician']
16
+ ordering = ['-scheduled_date']
@@ -0,0 +1 @@
1
+ # API module for netbox_maintenance_device plugin
@@ -0,0 +1,65 @@
1
+ """
2
+ Custom permissions for NetBox Maintenance Device API.
3
+
4
+ This module defines custom permission classes for the API endpoints,
5
+ following NetBox patterns and ensuring proper access control.
6
+ """
7
+
8
+ from rest_framework import permissions
9
+
10
+
11
+ class MaintenanceDevicePermissions(permissions.BasePermission):
12
+ """
13
+ Custom permissions for maintenance device operations.
14
+
15
+ Provides granular control for maintenance device API operations
16
+ with proper object-level permissions.
17
+ """
18
+
19
+ def has_permission(self, request, view):
20
+ """Check if user has permission for the requested action"""
21
+ if not request.user or not request.user.is_authenticated:
22
+ return False
23
+
24
+ # Map HTTP methods to permission requirements
25
+ permission_map = {
26
+ 'GET': 'view',
27
+ 'POST': 'add',
28
+ 'PUT': 'change',
29
+ 'PATCH': 'change',
30
+ 'DELETE': 'delete',
31
+ }
32
+
33
+ required_action = permission_map.get(request.method, 'view')
34
+
35
+ # Get the model name from the viewset
36
+ if hasattr(view, 'queryset') and view.queryset is not None:
37
+ model_name = view.queryset.model._meta.model_name
38
+ perm_name = f'netbox_maintenance_device.{required_action}_{model_name}'
39
+ return request.user.has_perm(perm_name)
40
+
41
+ return True
42
+
43
+ def has_object_permission(self, request, view, obj):
44
+ """Check object-level permissions"""
45
+ return self.has_permission(request, view)
46
+
47
+
48
+ class CanScheduleMaintenance(permissions.BasePermission):
49
+ """Permission to schedule maintenance executions"""
50
+
51
+ def has_permission(self, request, view):
52
+ """Check if user has permission to schedule maintenance"""
53
+ return (request.user and
54
+ request.user.is_authenticated and
55
+ request.user.has_perm('netbox_maintenance_device.add_maintenanceexecution'))
56
+
57
+
58
+ class CanCompleteMaintenance(permissions.BasePermission):
59
+ """Permission to complete maintenance executions"""
60
+
61
+ def has_permission(self, request, view):
62
+ """Check if user has permission to complete maintenance"""
63
+ return (request.user and
64
+ request.user.is_authenticated and
65
+ request.user.has_perm('netbox_maintenance_device.change_maintenanceexecution'))
@@ -0,0 +1,217 @@
1
+ from rest_framework import serializers
2
+ from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
3
+ from netbox_maintenance_device import models
4
+ from django.utils import timezone
5
+
6
+
7
+ class DeviceNestedSerializer(WritableNestedSerializer):
8
+ """Nested serializer for Device references - NetBox 4.4.x compatible"""
9
+
10
+ class Meta:
11
+ # Import Device model at class level
12
+ from dcim import models as dcim_models
13
+ model = dcim_models.Device
14
+ fields = ['id', 'url', 'display', 'name']
15
+
16
+
17
+ class NestedMaintenancePlanSerializer(WritableNestedSerializer):
18
+ """Nested serializer for MaintenancePlan references"""
19
+ url = serializers.HyperlinkedIdentityField(
20
+ view_name='plugins-api:netbox_maintenance_device-api:maintenanceplan-detail'
21
+ )
22
+
23
+ class Meta:
24
+ model = models.MaintenancePlan
25
+ fields = ['id', 'url', 'display', 'name', 'maintenance_type']
26
+
27
+
28
+ class NestedMaintenanceExecutionSerializer(WritableNestedSerializer):
29
+ """Nested serializer for MaintenanceExecution references"""
30
+ url = serializers.HyperlinkedIdentityField(
31
+ view_name='plugins-api:netbox_maintenance_device-api:maintenanceexecution-detail'
32
+ )
33
+
34
+ class Meta:
35
+ model = models.MaintenanceExecution
36
+ fields = ['id', 'url', 'display', 'scheduled_date', 'status']
37
+
38
+
39
+ class MaintenancePlanSerializer(NetBoxModelSerializer):
40
+ """Complete serializer for MaintenancePlan with all relationships and computed fields"""
41
+
42
+ url = serializers.HyperlinkedIdentityField(
43
+ view_name='plugins-api:netbox_maintenance_device-api:maintenanceplan-detail'
44
+ )
45
+
46
+ # Nested relationships
47
+ device = DeviceNestedSerializer()
48
+ executions = NestedMaintenanceExecutionSerializer(many=True, read_only=True)
49
+
50
+ # Computed fields
51
+ execution_count = serializers.IntegerField(read_only=True)
52
+ next_maintenance_date = serializers.DateTimeField(read_only=True)
53
+ is_overdue = serializers.BooleanField(read_only=True)
54
+ days_until_due = serializers.IntegerField(read_only=True)
55
+ last_execution_date = serializers.DateTimeField(read_only=True)
56
+
57
+ class Meta:
58
+ model = models.MaintenancePlan
59
+ fields = [
60
+ 'id', 'url', 'display', 'device', 'name', 'description',
61
+ 'maintenance_type', 'frequency_days', 'is_active',
62
+ 'created', 'last_updated', 'custom_field_data', 'tags',
63
+ # Relationships
64
+ 'executions', 'execution_count',
65
+ # Computed fields
66
+ 'next_maintenance_date', 'is_overdue', 'days_until_due',
67
+ 'last_execution_date'
68
+ ]
69
+
70
+ def validate_frequency_days(self, value):
71
+ """Validate frequency days is positive and reasonable"""
72
+ if value <= 0:
73
+ raise serializers.ValidationError("Frequency must be greater than 0 days")
74
+ if value > 3650: # 10 years
75
+ raise serializers.ValidationError("Frequency cannot exceed 3650 days (10 years)")
76
+ return value
77
+
78
+ def validate(self, data):
79
+ """Additional model-level validations"""
80
+ device = data.get('device')
81
+ name = data.get('name')
82
+
83
+ # Check for duplicate plan names per device (excluding current instance)
84
+ if device and name:
85
+ existing_plans = models.MaintenancePlan.objects.filter(
86
+ device=device, name=name
87
+ )
88
+ if self.instance:
89
+ existing_plans = existing_plans.exclude(pk=self.instance.pk)
90
+
91
+ if existing_plans.exists():
92
+ raise serializers.ValidationError({
93
+ 'name': f"A maintenance plan with name '{name}' already exists for this device."
94
+ })
95
+
96
+ return data
97
+
98
+ def to_representation(self, instance):
99
+ """Add computed fields to the representation"""
100
+ data = super().to_representation(instance)
101
+
102
+ # Add computed fields
103
+ data['execution_count'] = instance.executions.count()
104
+ data['next_maintenance_date'] = instance.get_next_maintenance_date()
105
+ data['is_overdue'] = instance.is_overdue()
106
+ data['days_until_due'] = instance.days_until_due()
107
+
108
+ # Add last execution date
109
+ last_execution = instance.executions.filter(completed=True).order_by('-completed_date').first()
110
+ data['last_execution_date'] = last_execution.completed_date if last_execution else None
111
+
112
+ return data
113
+
114
+
115
+ class MaintenanceExecutionSerializer(NetBoxModelSerializer):
116
+ """Complete serializer for MaintenanceExecution with all relationships and validations"""
117
+
118
+ url = serializers.HyperlinkedIdentityField(
119
+ view_name='plugins-api:netbox_maintenance_device-api:maintenanceexecution-detail'
120
+ )
121
+
122
+ # Nested relationships
123
+ maintenance_plan = NestedMaintenancePlanSerializer()
124
+
125
+ # Computed fields
126
+ device = serializers.CharField(source='maintenance_plan.device.name', read_only=True)
127
+ device_id = serializers.IntegerField(source='maintenance_plan.device.id', read_only=True)
128
+ plan_name = serializers.CharField(source='maintenance_plan.name', read_only=True)
129
+ duration_days = serializers.SerializerMethodField()
130
+
131
+ class Meta:
132
+ model = models.MaintenanceExecution
133
+ fields = [
134
+ 'id', 'url', 'display', 'maintenance_plan', 'scheduled_date',
135
+ 'completed_date', 'status', 'notes', 'technician', 'completed',
136
+ 'created', 'last_updated', 'custom_field_data', 'tags',
137
+ # Computed fields
138
+ 'device', 'device_id', 'plan_name', 'duration_days'
139
+ ]
140
+
141
+ def get_duration_days(self, obj):
142
+ """Calculate duration between scheduled and completed dates"""
143
+ if obj.scheduled_date and obj.completed_date:
144
+ delta = obj.completed_date - obj.scheduled_date
145
+ return delta.days
146
+ return None
147
+
148
+ def validate_scheduled_date(self, value):
149
+ """Validate scheduled date is not too far in the past"""
150
+ if value and value.date() < (timezone.now().date() - timezone.timedelta(days=365)):
151
+ raise serializers.ValidationError("Scheduled date cannot be more than 1 year in the past")
152
+ return value
153
+
154
+ def validate_completed_date(self, value):
155
+ """Validate completed date is not in the future"""
156
+ if value and value.date() > timezone.now().date():
157
+ raise serializers.ValidationError("Completed date cannot be in the future")
158
+ return value
159
+
160
+ def validate(self, data):
161
+ """Additional model-level validations"""
162
+ scheduled_date = data.get('scheduled_date')
163
+ completed_date = data.get('completed_date')
164
+ status = data.get('status')
165
+
166
+ # If status is completed, completed_date should be set
167
+ if status == 'completed' and not completed_date:
168
+ raise serializers.ValidationError({
169
+ 'completed_date': "Completed date is required when status is 'completed'."
170
+ })
171
+
172
+ # If completed_date is set, status should be completed
173
+ if completed_date and status != 'completed':
174
+ raise serializers.ValidationError({
175
+ 'status': "Status must be 'completed' when completed date is set."
176
+ })
177
+
178
+ # Completed date should not be before scheduled date
179
+ if scheduled_date and completed_date and completed_date < scheduled_date:
180
+ raise serializers.ValidationError({
181
+ 'completed_date': "Completed date cannot be before scheduled date."
182
+ })
183
+
184
+ return data
185
+
186
+
187
+ # Bulk operation serializers for efficient operations
188
+ class BulkMaintenancePlanSerializer(serializers.ListSerializer):
189
+ """Bulk operations for MaintenancePlan"""
190
+
191
+ def create(self, validated_data):
192
+ """Bulk create maintenance plans"""
193
+ return [models.MaintenancePlan.objects.create(**attrs) for attrs in validated_data]
194
+
195
+ def update(self, instance_list, validated_data):
196
+ """Bulk update maintenance plans"""
197
+ plan_mapping = {plan.id: plan for plan in instance_list}
198
+
199
+ updated_plans = []
200
+ for attrs in validated_data:
201
+ plan_id = attrs.pop('id', None)
202
+ if plan_id and plan_id in plan_mapping:
203
+ plan = plan_mapping[plan_id]
204
+ for key, value in attrs.items():
205
+ setattr(plan, key, value)
206
+ plan.save()
207
+ updated_plans.append(plan)
208
+
209
+ return updated_plans
210
+
211
+
212
+ class BulkMaintenanceExecutionSerializer(serializers.ListSerializer):
213
+ """Bulk operations for MaintenanceExecution"""
214
+
215
+ def create(self, validated_data):
216
+ """Bulk create maintenance executions"""
217
+ return [models.MaintenanceExecution.objects.create(**attrs) for attrs in validated_data]
@@ -0,0 +1,60 @@
1
+ from django.urls import path, include
2
+ from netbox.api.routers import NetBoxRouter
3
+ from . import views
4
+
5
+ app_name = 'netbox_maintenance_device-api'
6
+
7
+ # Main API router for CRUD operations
8
+ router = NetBoxRouter()
9
+
10
+ # Register main viewsets
11
+ router.register('maintenance-plans', views.MaintenancePlanViewSet, basename='maintenanceplan')
12
+ router.register('maintenance-executions', views.MaintenanceExecutionViewSet, basename='maintenanceexecution')
13
+
14
+ # Include all router URLs
15
+ urlpatterns = [
16
+ # Main API routes (includes all CRUD operations and custom actions)
17
+ path('', include(router.urls)),
18
+
19
+ # Additional custom endpoints can be added here if needed
20
+ # Example: path('reports/', views.MaintenanceReportView.as_view(), name='maintenance-reports'),
21
+ ]
22
+
23
+ # URL patterns summary for documentation:
24
+ #
25
+ # MaintenancePlan endpoints:
26
+ # GET /api/plugins/netbox-maintenance-device/maintenance-plans/ - List all plans
27
+ # POST /api/plugins/netbox-maintenance-device/maintenance-plans/ - Create new plan
28
+ # GET /api/plugins/netbox-maintenance-device/maintenance-plans/{id}/ - Get specific plan
29
+ # PUT /api/plugins/netbox-maintenance-device/maintenance-plans/{id}/ - Update plan (full)
30
+ # PATCH /api/plugins/netbox-maintenance-device/maintenance-plans/{id}/ - Update plan (partial)
31
+ # DELETE /api/plugins/netbox-maintenance-device/maintenance-plans/{id}/ - Delete plan
32
+ #
33
+ # Custom MaintenancePlan actions:
34
+ # POST /api/plugins/netbox-maintenance-device/maintenance-plans/{id}/schedule-maintenance/ - Schedule execution
35
+ # GET /api/plugins/netbox-maintenance-device/maintenance-plans/overdue/ - Get overdue plans
36
+ # GET /api/plugins/netbox-maintenance-device/maintenance-plans/upcoming/ - Get upcoming plans
37
+ # GET /api/plugins/netbox-maintenance-device/maintenance-plans/statistics/ - Get plan statistics
38
+ #
39
+ # MaintenanceExecution endpoints:
40
+ # GET /api/plugins/netbox-maintenance-device/maintenance-executions/ - List all executions
41
+ # POST /api/plugins/netbox-maintenance-device/maintenance-executions/ - Create new execution
42
+ # GET /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/ - Get specific execution
43
+ # PUT /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/ - Update execution (full)
44
+ # PATCH /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/ - Update execution (partial)
45
+ # DELETE /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/ - Delete execution
46
+ #
47
+ # Custom MaintenanceExecution actions:
48
+ # POST /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/complete/ - Mark as completed
49
+ # POST /api/plugins/netbox-maintenance-device/maintenance-executions/{id}/cancel/ - Cancel execution
50
+ # GET /api/plugins/netbox-maintenance-device/maintenance-executions/pending/ - Get pending executions
51
+ # GET /api/plugins/netbox-maintenance-device/maintenance-executions/overdue-executions/ - Get overdue executions
52
+ # GET /api/plugins/netbox-maintenance-device/maintenance-executions/statistics/ - Get execution statistics
53
+ #
54
+ # All endpoints support:
55
+ # - Filtering via query parameters (see FilterSets in views.py)
56
+ # - Ordering via ?ordering=field_name
57
+ # - Search via ?q=search_term
58
+ # - Pagination via ?limit=N&offset=N
59
+ # - Field selection via ?fields=field1,field2
60
+ # - Format selection via ?format=json|api (HTML browsable API)