|
@@ -11,6 +11,7 @@ from django.urls import reverse
|
|
|
from django.utils.html import escape
|
|
from django.utils.html import escape
|
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.safestring import mark_safe
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
+from django.utils.translation import ngettext
|
|
|
from django.views.generic import View
|
|
from django.views.generic import View
|
|
|
|
|
|
|
|
from circuits.models import Circuit, CircuitTermination
|
|
from circuits.models import Circuit, CircuitTermination
|
|
@@ -1690,6 +1691,74 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
|
|
table = tables.DeviceTypeTable
|
|
table = tables.DeviceTypeTable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+#
|
|
|
|
|
+# Module bay types
|
|
|
|
|
+#
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'list', path='', detail=False)
|
|
|
|
|
+class ModuleBayTypeListView(generic.ObjectListView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ filterset = filtersets.ModuleBayTypeFilterSet
|
|
|
|
|
+ filterset_form = forms.ModuleBayTypeFilterForm
|
|
|
|
|
+ table = tables.ModuleBayTypeTable
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType)
|
|
|
|
|
+class ModuleBayTypeView(generic.ObjectView):
|
|
|
|
|
+ template_name = 'generic/object.html'
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ layout = layout.SimpleLayout(
|
|
|
|
|
+ left_panels=[
|
|
|
|
|
+ panels.ModuleBayTypePanel(),
|
|
|
|
|
+ TagsPanel(),
|
|
|
|
|
+ CommentsPanel(),
|
|
|
|
|
+ ],
|
|
|
|
|
+ right_panels=[
|
|
|
|
|
+ CustomFieldsPanel(),
|
|
|
|
|
+ ],
|
|
|
|
|
+ bottom_panels=[
|
|
|
|
|
+ ObjectsTablePanel(
|
|
|
|
|
+ model='dcim.ModuleBay',
|
|
|
|
|
+ title=_('Module Bays'),
|
|
|
|
|
+ filters={'module_bay_type_id': lambda ctx: ctx['object'].pk},
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'add', detail=False)
|
|
|
|
|
+@register_model_view(ModuleBayType, 'edit')
|
|
|
|
|
+class ModuleBayTypeEditView(generic.ObjectEditView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ form = forms.ModuleBayTypeForm
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'delete')
|
|
|
|
|
+class ModuleBayTypeDeleteView(generic.ObjectDeleteView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'bulk_import', detail=False)
|
|
|
|
|
+class ModuleBayTypeBulkImportView(generic.BulkImportView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ model_form = forms.ModuleBayTypeImportForm
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'bulk_edit', path='edit', detail=False)
|
|
|
|
|
+class ModuleBayTypeBulkEditView(generic.BulkEditView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ filterset = filtersets.ModuleBayTypeFilterSet
|
|
|
|
|
+ table = tables.ModuleBayTypeTable
|
|
|
|
|
+ form = forms.ModuleBayTypeBulkEditForm
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@register_model_view(ModuleBayType, 'bulk_delete', path='delete', detail=False)
|
|
|
|
|
+class ModuleBayTypeBulkDeleteView(generic.BulkDeleteView):
|
|
|
|
|
+ queryset = ModuleBayType.objects.all()
|
|
|
|
|
+ filterset = filtersets.ModuleBayTypeFilterSet
|
|
|
|
|
+ table = tables.ModuleBayTypeTable
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
#
|
|
#
|
|
|
# Module type profiles
|
|
# Module type profiles
|
|
|
#
|
|
#
|
|
@@ -1833,6 +1902,33 @@ class ModuleTypeEditView(generic.ObjectEditView):
|
|
|
queryset = ModuleType.objects.all()
|
|
queryset = ModuleType.objects.all()
|
|
|
form = forms.ModuleTypeForm
|
|
form = forms.ModuleTypeForm
|
|
|
|
|
|
|
|
|
|
+ def post(self, request, *args, **kwargs):
|
|
|
|
|
+ response = super().post(request, *args, **kwargs)
|
|
|
|
|
+ # A successful save always redirects (302) or returns an HTMX redirect header.
|
|
|
|
|
+ # Quick-add creates new objects (no pk in kwargs), so it is excluded by the pk guard below.
|
|
|
|
|
+ saved = (
|
|
|
|
|
+ getattr(response, 'status_code', None) == 302 or
|
|
|
|
|
+ (hasattr(response, 'headers') and 'HX-Location' in response.headers)
|
|
|
|
|
+ )
|
|
|
|
|
+ if saved and kwargs.get('pk'):
|
|
|
|
|
+ try:
|
|
|
|
|
+ module_type = ModuleType.objects.prefetch_related('module_bay_types').get(pk=kwargs['pk'])
|
|
|
|
|
+ count = module_type.get_incompatible_modules().count()
|
|
|
|
|
+ if count:
|
|
|
|
|
+ messages.warning(
|
|
|
|
|
+ request,
|
|
|
|
|
+ ngettext(
|
|
|
|
|
+ '%(count)d installed module of this type is now incompatible with its module bay '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ '%(count)d installed modules of this type are now incompatible with their module bays '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ count,
|
|
|
|
|
+ ) % {'count': count}
|
|
|
|
|
+ )
|
|
|
|
|
+ except ModuleType.DoesNotExist:
|
|
|
|
|
+ pass
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@register_model_view(ModuleType, 'delete')
|
|
@register_model_view(ModuleType, 'delete')
|
|
|
class ModuleTypeDeleteView(generic.ObjectDeleteView):
|
|
class ModuleTypeDeleteView(generic.ObjectDeleteView):
|
|
@@ -2000,6 +2096,35 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
|
|
|
table = tables.ModuleTypeTable
|
|
table = tables.ModuleTypeTable
|
|
|
form = forms.ModuleTypeBulkEditForm
|
|
form = forms.ModuleTypeBulkEditForm
|
|
|
|
|
|
|
|
|
|
+ def post_save_operations(self, form, obj):
|
|
|
|
|
+ super().post_save_operations(form, obj)
|
|
|
|
|
+ add = form.cleaned_data.get('add_module_bay_types')
|
|
|
|
|
+ remove = form.cleaned_data.get('remove_module_bay_types')
|
|
|
|
|
+ if add:
|
|
|
|
|
+ obj.module_bay_types.add(*add)
|
|
|
|
|
+ if remove:
|
|
|
|
|
+ obj.module_bay_types.remove(*remove)
|
|
|
|
|
+ if add or remove:
|
|
|
|
|
+ # Counts current incompatibilities, not just newly-introduced ones; may over-warn
|
|
|
|
|
+ # if pre-existing incompatibilities exist, but safe to under-warn on.
|
|
|
|
|
+ self._incompatible_count += obj.get_incompatible_modules().count()
|
|
|
|
|
+
|
|
|
|
|
+ def post(self, request, **kwargs):
|
|
|
|
|
+ self._incompatible_count = 0
|
|
|
|
|
+ response = super().post(request, **kwargs)
|
|
|
|
|
+ if self._incompatible_count and getattr(response, 'status_code', None) == 302:
|
|
|
|
|
+ messages.warning(
|
|
|
|
|
+ request,
|
|
|
|
|
+ ngettext(
|
|
|
|
|
+ '%(count)d installed module is now incompatible with its module bay '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ '%(count)d installed modules are now incompatible with their module bays '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ self._incompatible_count,
|
|
|
|
|
+ ) % {'count': self._incompatible_count}
|
|
|
|
|
+ )
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@register_model_view(ModuleType, 'bulk_rename', path='rename', detail=False)
|
|
@register_model_view(ModuleType, 'bulk_rename', path='rename', detail=False)
|
|
|
class ModuleTypeBulkRenameView(generic.BulkRenameView):
|
|
class ModuleTypeBulkRenameView(generic.BulkRenameView):
|
|
@@ -3000,12 +3125,16 @@ class ModuleListView(generic.ObjectListView):
|
|
|
|
|
|
|
|
@register_model_view(Module)
|
|
@register_model_view(Module)
|
|
|
class ModuleView(GetRelatedModelsMixin, generic.ObjectView):
|
|
class ModuleView(GetRelatedModelsMixin, generic.ObjectView):
|
|
|
- queryset = Module.objects.all()
|
|
|
|
|
|
|
+ queryset = Module.objects.prefetch_related(
|
|
|
|
|
+ 'module_bay__module_bay_types',
|
|
|
|
|
+ 'module_type__module_bay_types',
|
|
|
|
|
+ )
|
|
|
layout = layout.SimpleLayout(
|
|
layout = layout.SimpleLayout(
|
|
|
breadcrumbs=[
|
|
breadcrumbs=[
|
|
|
Breadcrumb('module_type', url=filtered_list_url('dcim:module_list', 'module_type_id')),
|
|
Breadcrumb('module_type', url=filtered_list_url('dcim:module_list', 'module_type_id')),
|
|
|
],
|
|
],
|
|
|
left_panels=[
|
|
left_panels=[
|
|
|
|
|
+ panels.BayTypeIncompatibilityPanel(),
|
|
|
panels.ModulePanel(),
|
|
panels.ModulePanel(),
|
|
|
TagsPanel(),
|
|
TagsPanel(),
|
|
|
CommentsPanel(),
|
|
CommentsPanel(),
|
|
@@ -3851,12 +3980,16 @@ class ModuleBayListView(generic.ObjectListView):
|
|
|
@register_model_view(ModuleBay)
|
|
@register_model_view(ModuleBay)
|
|
|
class ModuleBayView(generic.ObjectView):
|
|
class ModuleBayView(generic.ObjectView):
|
|
|
template_name = 'generic/object.html'
|
|
template_name = 'generic/object.html'
|
|
|
- queryset = ModuleBay.objects.all()
|
|
|
|
|
|
|
+ queryset = ModuleBay.objects.prefetch_related(
|
|
|
|
|
+ 'module_bay_types',
|
|
|
|
|
+ 'installed_module__module_type__module_bay_types',
|
|
|
|
|
+ )
|
|
|
layout = layout.SimpleLayout(
|
|
layout = layout.SimpleLayout(
|
|
|
breadcrumbs=[
|
|
breadcrumbs=[
|
|
|
Breadcrumb('device', url=object_view_url('dcim:device_modulebays')),
|
|
Breadcrumb('device', url=object_view_url('dcim:device_modulebays')),
|
|
|
],
|
|
],
|
|
|
left_panels=[
|
|
left_panels=[
|
|
|
|
|
+ panels.BayTypeIncompatibilityPanel(),
|
|
|
panels.ModuleBayPanel(),
|
|
panels.ModuleBayPanel(),
|
|
|
TagsPanel(),
|
|
TagsPanel(),
|
|
|
],
|
|
],
|
|
@@ -3879,6 +4012,29 @@ class ModuleBayEditView(generic.ObjectEditView):
|
|
|
queryset = ModuleBay.objects.all()
|
|
queryset = ModuleBay.objects.all()
|
|
|
form = forms.ModuleBayForm
|
|
form = forms.ModuleBayForm
|
|
|
|
|
|
|
|
|
|
+ def post(self, request, *args, **kwargs):
|
|
|
|
|
+ response = super().post(request, *args, **kwargs)
|
|
|
|
|
+ # A successful save always redirects (302) or returns an HTMX redirect header.
|
|
|
|
|
+ # Quick-add creates new objects (no pk in kwargs), so it is excluded by the pk guard below.
|
|
|
|
|
+ saved = (
|
|
|
|
|
+ getattr(response, 'status_code', None) == 302 or
|
|
|
|
|
+ (hasattr(response, 'headers') and 'HX-Location' in response.headers)
|
|
|
|
|
+ )
|
|
|
|
|
+ if saved and kwargs.get('pk'):
|
|
|
|
|
+ try:
|
|
|
|
|
+ bay = ModuleBay.objects.prefetch_related(
|
|
|
|
|
+ 'module_bay_types', 'installed_module__module_type__module_bay_types'
|
|
|
|
|
+ ).get(pk=kwargs['pk'])
|
|
|
|
|
+ if not bay.is_module_compatible:
|
|
|
|
|
+ messages.warning(
|
|
|
|
|
+ request,
|
|
|
|
|
+ _('The module currently installed in this bay is incompatible with the new bay type '
|
|
|
|
|
+ 'constraints. Consider removing or replacing it.')
|
|
|
|
|
+ )
|
|
|
|
|
+ except ModuleBay.DoesNotExist:
|
|
|
|
|
+ pass
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@register_model_view(ModuleBay, 'delete')
|
|
@register_model_view(ModuleBay, 'delete')
|
|
|
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
|
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
|
@@ -3898,6 +4054,35 @@ class ModuleBayBulkEditView(generic.BulkEditView):
|
|
|
table = tables.ModuleBayTable
|
|
table = tables.ModuleBayTable
|
|
|
form = forms.ModuleBayBulkEditForm
|
|
form = forms.ModuleBayBulkEditForm
|
|
|
|
|
|
|
|
|
|
+ def post_save_operations(self, form, obj):
|
|
|
|
|
+ super().post_save_operations(form, obj)
|
|
|
|
|
+ add = form.cleaned_data.get('add_module_bay_types')
|
|
|
|
|
+ remove = form.cleaned_data.get('remove_module_bay_types')
|
|
|
|
|
+ if add:
|
|
|
|
|
+ obj.module_bay_types.add(*add)
|
|
|
|
|
+ if remove:
|
|
|
|
|
+ obj.module_bay_types.remove(*remove)
|
|
|
|
|
+ if add or remove:
|
|
|
|
|
+ # Counts current incompatibilities, not just newly-introduced ones; may over-warn
|
|
|
|
|
+ # if pre-existing incompatibilities exist, but safe to under-warn on.
|
|
|
|
|
+ self._incompatible_count += int(not obj.is_module_compatible)
|
|
|
|
|
+
|
|
|
|
|
+ def post(self, request, **kwargs):
|
|
|
|
|
+ self._incompatible_count = 0
|
|
|
|
|
+ response = super().post(request, **kwargs)
|
|
|
|
|
+ if self._incompatible_count and getattr(response, 'status_code', None) == 302:
|
|
|
|
|
+ messages.warning(
|
|
|
|
|
+ request,
|
|
|
|
|
+ ngettext(
|
|
|
|
|
+ '%(count)d module bay now has an incompatible module installed '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ '%(count)d module bays now have incompatible modules installed '
|
|
|
|
|
+ 'due to conflicting bay type constraints.',
|
|
|
|
|
+ self._incompatible_count,
|
|
|
|
|
+ ) % {'count': self._incompatible_count}
|
|
|
|
|
+ )
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@register_model_view(ModuleBay, 'bulk_rename', path='rename', detail=False)
|
|
@register_model_view(ModuleBay, 'bulk_rename', path='rename', detail=False)
|
|
|
class ModuleBayBulkRenameView(generic.BulkRenameView):
|
|
class ModuleBayBulkRenameView(generic.BulkRenameView):
|