Coverage for dcim/views.py: 84%
2819 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.conf import settings
2from django.contrib import messages
3from django.contrib.contenttypes.models import ContentType
4from django.core.exceptions import ValidationError
5from django.core.paginator import EmptyPage, PageNotAnInteger
6from django.db import router, transaction
7from django.db.models import Func, IntegerField, Prefetch
8from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
9from django.shortcuts import get_object_or_404, redirect, render
10from django.urls import reverse
11from django.utils.html import escape
12from django.utils.safestring import mark_safe
13from django.utils.translation import gettext_lazy as _
14from django.utils.translation import ngettext
15from django.views.generic import View
17from circuits.models import Circuit, CircuitTermination
18from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
19from extras.views import ObjectConfigContextView, ObjectRenderConfigView
20from ipam.models import ASN, VLAN, IPAddress, Prefix, VLANGroup
21from ipam.tables import VLANTranslationRuleTable
22from ipam.ui.panels import FHRPGroupAssignmentsPanel
23from netbox.object_actions import *
24from netbox.ui import actions, layout
25from netbox.ui.breadcrumbs import Breadcrumb, filtered_list_url, object_view_url
26from netbox.ui.panels import (
27 CommentsPanel,
28 ContextTablePanel,
29 JSONPanel,
30 NestedGroupObjectPanel,
31 ObjectsTablePanel,
32 OrganizationalObjectPanel,
33 RelatedObjectsPanel,
34 TemplatePanel,
35)
36from netbox.views import generic
37from utilities.forms import ConfirmationForm
38from utilities.paginator import EnhancedPaginator, get_paginate_count
39from utilities.permissions import get_permission_for_model
40from utilities.query import count_related
41from utilities.query_functions import CollateAsChar
42from utilities.request import safe_for_redirect
43from utilities.views import (
44 ConditionalLoginRequiredMixin,
45 GetRelatedModelsMixin,
46 GetReturnURLMixin,
47 ObjectPermissionRequiredMixin,
48 ViewTab,
49 register_model_view,
50)
51from virtualization.filtersets import VirtualMachineFilterSet
52from virtualization.forms import VirtualMachineFilterForm
53from virtualization.models import Cluster, VirtualMachine
54from virtualization.tables import VirtualMachineTable
55from wireless.models import WirelessLAN
57from . import filtersets, forms, tables
58from .choices import DeviceFaceChoices, InterfaceModeChoices
59from .models import *
60from .models.device_components import PortMapping
61from .object_actions import BulkAddComponents, BulkDisconnect
62from .ui import panels
64CABLE_TERMINATION_TYPES = {
65 'dcim.consoleport': ConsolePort,
66 'dcim.consoleserverport': ConsoleServerPort,
67 'dcim.powerport': PowerPort,
68 'dcim.poweroutlet': PowerOutlet,
69 'dcim.interface': Interface,
70 'dcim.frontport': FrontPort,
71 'dcim.rearport': RearPort,
72 'dcim.powerfeed': PowerFeed,
73 'circuits.circuittermination': CircuitTermination,
74}
77class DeviceComponentsView(generic.ObjectChildrenView):
78 queryset = Device.objects.all()
80 def get_children(self, request, parent):
81 return self.child_model.objects.restrict(request.user, 'view').filter(device=parent)
84class DeviceTypeComponentsView(generic.ObjectChildrenView):
85 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
86 queryset = DeviceType.objects.all()
87 viewname = None # Used for return_url resolution
89 def get_children(self, request, parent):
90 return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent)
92 def get_extra_context(self, request, instance):
93 return {
94 'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
95 }
98class ModuleTypeComponentsView(generic.ObjectChildrenView):
99 queryset = ModuleType.objects.all()
100 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
101 viewname = None # Used for return_url resolution
103 def get_children(self, request, parent):
104 return self.child_model.objects.restrict(request.user, 'view').filter(module_type=parent)
106 def get_extra_context(self, request, instance):
107 return {
108 'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
109 }
112class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
113 """
114 An extendable view for disconnection console/power/interface components in bulk.
115 """
116 queryset = None
117 template_name = 'dcim/bulk_disconnect.html'
119 def __init__(self, *args, **kwargs):
120 super().__init__(*args, **kwargs)
122 # Create a new Form class from ConfirmationForm
123 class _Form(ConfirmationForm):
124 pk = ModelMultipleChoiceField(
125 queryset=self.queryset,
126 widget=MultipleHiddenInput()
127 )
129 self.form = _Form
131 def get_required_permission(self):
132 return get_permission_for_model(self.queryset.model, 'change')
134 def post(self, request):
136 selected_objects = []
137 return_url = self.get_return_url(request)
139 if '_confirm' in request.POST:
140 form = self.form(request.POST)
142 if form.is_valid():
144 with transaction.atomic(using=router.db_for_write(Cable)):
145 count = 0
146 cable_ids = set()
147 for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']):
148 if obj.cable:
149 cable_ids.add(obj.cable.pk)
150 count += 1
151 for cable in Cable.objects.filter(pk__in=cable_ids):
152 cable.delete()
154 messages.success(request, _("Disconnected {count} {type}").format(
155 count=count,
156 type=self.queryset.model._meta.verbose_name_plural
157 ))
159 return redirect(return_url)
161 else:
162 form = self.form(initial={'pk': request.POST.getlist('pk')})
163 selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
165 return render(request, self.template_name, {
166 'form': form,
167 'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
168 'selected_objects': selected_objects,
169 'return_url': return_url,
170 })
173class PathTraceView(generic.ObjectView):
174 """
175 Trace a cable path beginning from the given path endpoint (origin).
176 """
177 additional_permissions = ['dcim.view_cable']
178 template_name = 'dcim/cable_trace.html'
180 def dispatch(self, request, *args, **kwargs):
181 model = kwargs.pop('model')
182 self.queryset = model.objects.all()
184 return super().dispatch(request, *args, **kwargs)
186 def get_extra_context(self, request, instance):
187 related_paths = []
189 # If tracing a PathEndpoint, locate the CablePath (if one exists) by its origin
190 if isinstance(instance, PathEndpoint):
191 path = instance._path
193 # Otherwise, find all CablePaths which traverse the specified object
194 else:
195 related_paths = CablePath.objects.filter(_nodes__contains=instance)
196 # Check for specification of a particular path (when tracing pass-through ports)
197 try:
198 path_id = int(request.GET.get('cablepath_id'))
199 except TypeError:
200 path_id = None
201 if path_id in list(related_paths.values_list('pk', flat=True)):
202 path = CablePath.objects.get(pk=path_id)
203 else:
204 path = related_paths.first()
206 # No paths found
207 if path is None:
208 return {
209 'path': None
210 }
212 # Get the total length of the cable and whether the length is definitive (fully defined)
213 total_length, is_definitive = path.get_total_length() if path else (None, False)
215 # Determine the path to the SVG trace image. The `-trace` API action (and the SVG renderer,
216 # which calls origin.trace()) exist only for PathEndpoint origins. Other valid origins such as
217 # CircuitTermination have no such action, so omit the SVG for them.
218 origin_model = path.origin_type.model_class()
219 if issubclass(origin_model, PathEndpoint):
220 api_viewname = f"{path.origin_type.app_label}-api:{path.origin_type.model}-trace"
221 svg_url = f"{reverse(api_viewname, kwargs={'pk': path.origins[0].pk})}?render=svg"
222 else:
223 svg_url = None
225 return {
226 'path': path,
227 'related_paths': related_paths,
228 'total_length': total_length,
229 'is_definitive': is_definitive,
230 'svg_url': svg_url,
231 }
234#
235# Regions
236#
238@register_model_view(Region, 'list', path='', detail=False)
239class RegionListView(generic.ObjectListView):
240 queryset = Region.objects.add_related_count(
241 Region.objects.all(),
242 Site,
243 'region',
244 'site_count',
245 cumulative=True
246 )
247 filterset = filtersets.RegionFilterSet
248 filterset_form = forms.RegionFilterForm
249 table = tables.RegionTable
252@register_model_view(Region)
253class RegionView(GetRelatedModelsMixin, generic.ObjectView):
254 queryset = Region.objects.all()
255 layout = layout.SimpleLayout(
256 breadcrumbs=[
257 Breadcrumb(
258 lambda o: o.get_ancestors(),
259 url=filtered_list_url('dcim:region_list', 'parent_id'),
260 ),
261 ],
262 left_panels=[
263 NestedGroupObjectPanel(),
264 TagsPanel(),
265 CustomFieldsPanel(),
266 CommentsPanel(),
267 ],
268 right_panels=[
269 RelatedObjectsPanel(),
270 ],
271 bottom_panels=[
272 ObjectsTablePanel(
273 model='dcim.Region',
274 title=_('Child Regions'),
275 filters={'parent_id': lambda ctx: ctx['object'].pk},
276 exclude_columns=['parent'],
277 actions=[
278 actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
279 ],
280 ),
281 ]
282 )
284 def get_extra_context(self, request, instance):
285 regions = instance.get_descendants(include_self=True)
287 return {
288 'related_models': self.get_related_models(
289 request,
290 regions,
291 omit=(Cluster, CircuitTermination, Prefix, WirelessLAN),
292 extra=(
293 (Location.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
294 (Rack.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
295 (
296 Circuit.objects.restrict(request.user, 'view').filter(
297 terminations___region=instance
298 ).distinct(),
299 'region_id'
300 ),
301 (
302 VLANGroup.objects.restrict(request.user, 'view').filter(
303 scope_type=ContentType.objects.get_for_model(Region),
304 scope_id__in=regions
305 ).distinct(),
306 'region'
307 ),
309 # Handle these relations manually to avoid erroneous filter name resolution
310 (
311 CircuitTermination.objects.restrict(request.user, 'view').filter(_region__in=regions),
312 'region_id'
313 ),
314 (Cluster.objects.restrict(request.user, 'view').filter(_region__in=regions), 'region_id'),
315 (Prefix.objects.restrict(request.user, 'view').filter(_region__in=regions), 'region_id'),
316 (WirelessLAN.objects.restrict(request.user, 'view').filter(_region__in=regions), 'region_id'),
317 ),
318 ),
319 }
322@register_model_view(Region, 'add', detail=False)
323@register_model_view(Region, 'edit')
324class RegionEditView(generic.ObjectEditView):
325 queryset = Region.objects.all()
326 form = forms.RegionForm
329@register_model_view(Region, 'delete')
330class RegionDeleteView(generic.ObjectDeleteView):
331 queryset = Region.objects.all()
334@register_model_view(Region, 'bulk_import', path='import', detail=False)
335class RegionBulkImportView(generic.BulkImportView):
336 queryset = Region.objects.all()
337 model_form = forms.RegionImportForm
340@register_model_view(Region, 'bulk_edit', path='edit', detail=False)
341class RegionBulkEditView(generic.BulkEditView):
342 queryset = Region.objects.add_related_count(
343 Region.objects.all(),
344 Site,
345 'region',
346 'site_count',
347 cumulative=True
348 )
349 filterset = filtersets.RegionFilterSet
350 table = tables.RegionTable
351 form = forms.RegionBulkEditForm
354@register_model_view(Region, 'bulk_rename', path='rename', detail=False)
355class RegionBulkRenameView(generic.BulkRenameView):
356 queryset = Region.objects.all()
357 filterset = filtersets.RegionFilterSet
360@register_model_view(Region, 'bulk_delete', path='delete', detail=False)
361class RegionBulkDeleteView(generic.BulkDeleteView):
362 queryset = Region.objects.add_related_count(
363 Region.objects.all(),
364 Site,
365 'region',
366 'site_count',
367 cumulative=True
368 )
369 filterset = filtersets.RegionFilterSet
370 table = tables.RegionTable
373#
374# Site groups
375#
377@register_model_view(SiteGroup, 'list', path='', detail=False)
378class SiteGroupListView(generic.ObjectListView):
379 queryset = SiteGroup.objects.add_related_count(
380 SiteGroup.objects.all(),
381 Site,
382 'group',
383 'site_count',
384 cumulative=True
385 )
386 filterset = filtersets.SiteGroupFilterSet
387 filterset_form = forms.SiteGroupFilterForm
388 table = tables.SiteGroupTable
391@register_model_view(SiteGroup)
392class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
393 queryset = SiteGroup.objects.all()
394 layout = layout.SimpleLayout(
395 breadcrumbs=[
396 Breadcrumb(
397 lambda o: o.get_ancestors(),
398 url=filtered_list_url('dcim:sitegroup_list', 'parent_id'),
399 ),
400 ],
401 left_panels=[
402 NestedGroupObjectPanel(),
403 TagsPanel(),
404 CustomFieldsPanel(),
405 CommentsPanel(),
406 ],
407 right_panels=[
408 RelatedObjectsPanel(),
409 ],
410 bottom_panels=[
411 ObjectsTablePanel(
412 model='dcim.SiteGroup',
413 title=_('Child Groups'),
414 filters={'parent_id': lambda ctx: ctx['object'].pk},
415 exclude_columns=['parent'],
416 actions=[
417 actions.AddObject('dcim.SiteGroup', url_params={'parent': lambda ctx: ctx['object'].pk}),
418 ],
419 ),
420 ]
421 )
423 def get_extra_context(self, request, instance):
424 groups = instance.get_descendants(include_self=True)
426 return {
427 'related_models': self.get_related_models(
428 request,
429 groups,
430 omit=(Cluster, CircuitTermination, Prefix, WirelessLAN),
431 extra=(
432 (Location.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
433 (Rack.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
434 (Device.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
435 (VLAN.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
436 (
437 ASN.objects.restrict(request.user, 'view').filter(
438 sites__group__in=groups
439 ).distinct(),
440 'site_group_id'),
441 (
442 VirtualMachine.objects.restrict(request.user, 'view').filter(
443 site__group__in=groups),
444 'site_group_id'
445 ),
446 (
447 VLANGroup.objects.restrict(request.user, 'view').filter(
448 scope_type=ContentType.objects.get_for_model(SiteGroup),
449 scope_id__in=groups
450 ).distinct(),
451 'site_group'
452 ),
453 (
454 Circuit.objects.restrict(request.user, 'view').filter(
455 terminations___site_group=instance
456 ).distinct(),
457 'site_group_id'
458 ),
460 # Handle these relations manually to avoid erroneous filter name resolution
461 (
462 CircuitTermination.objects.restrict(request.user, 'view').filter(_site_group__in=groups),
463 'site_group_id'
464 ),
465 (
466 Cluster.objects.restrict(request.user, 'view').filter(_site_group__in=groups),
467 'site_group_id'
468 ),
469 (
470 Prefix.objects.restrict(request.user, 'view').filter(_site_group__in=groups),
471 'site_group_id'
472 ),
473 (
474 WirelessLAN.objects.restrict(request.user, 'view').filter(_site_group__in=groups),
475 'site_group_id'
476 ),
477 ),
478 ),
479 }
482@register_model_view(SiteGroup, 'add', detail=False)
483@register_model_view(SiteGroup, 'edit')
484class SiteGroupEditView(generic.ObjectEditView):
485 queryset = SiteGroup.objects.all()
486 form = forms.SiteGroupForm
489@register_model_view(SiteGroup, 'delete')
490class SiteGroupDeleteView(generic.ObjectDeleteView):
491 queryset = SiteGroup.objects.all()
494@register_model_view(SiteGroup, 'bulk_import', path='import', detail=False)
495class SiteGroupBulkImportView(generic.BulkImportView):
496 queryset = SiteGroup.objects.all()
497 model_form = forms.SiteGroupImportForm
500@register_model_view(SiteGroup, 'bulk_edit', path='edit', detail=False)
501class SiteGroupBulkEditView(generic.BulkEditView):
502 queryset = SiteGroup.objects.add_related_count(
503 SiteGroup.objects.all(),
504 Site,
505 'group',
506 'site_count',
507 cumulative=True
508 )
509 filterset = filtersets.SiteGroupFilterSet
510 table = tables.SiteGroupTable
511 form = forms.SiteGroupBulkEditForm
514@register_model_view(SiteGroup, 'bulk_rename', path='rename', detail=False)
515class SiteGroupBulkRenameView(generic.BulkRenameView):
516 queryset = SiteGroup.objects.all()
517 filterset = filtersets.SiteGroupFilterSet
520@register_model_view(SiteGroup, 'bulk_delete', path='delete', detail=False)
521class SiteGroupBulkDeleteView(generic.BulkDeleteView):
522 queryset = SiteGroup.objects.add_related_count(
523 SiteGroup.objects.all(),
524 Site,
525 'group',
526 'site_count',
527 cumulative=True
528 )
529 filterset = filtersets.SiteGroupFilterSet
530 table = tables.SiteGroupTable
533#
534# Sites
535#
537@register_model_view(Site, 'list', path='', detail=False)
538class SiteListView(generic.ObjectListView):
539 queryset = Site.objects.annotate(
540 device_count=count_related(Device, 'site'),
541 asn_count=count_related(ASN, 'sites')
542 )
543 filterset = filtersets.SiteFilterSet
544 filterset_form = forms.SiteFilterForm
545 table = tables.SiteTable
548@register_model_view(Site)
549class SiteView(GetRelatedModelsMixin, generic.ObjectView):
550 queryset = Site.objects.prefetch_related('tenant__group')
551 layout = layout.SimpleLayout(
552 left_panels=[
553 panels.SitePanel(),
554 CustomFieldsPanel(),
555 TagsPanel(),
556 CommentsPanel(),
557 ],
558 right_panels=[
559 RelatedObjectsPanel(),
560 ImageAttachmentsPanel(),
561 ],
562 bottom_panels=[
563 ObjectsTablePanel(
564 model='dcim.Location',
565 filters={'site_id': lambda ctx: ctx['object'].pk},
566 exclude_columns=['site'],
567 actions=[
568 actions.AddObject('dcim.Location', url_params={'site': lambda ctx: ctx['object'].pk}),
569 ],
570 ),
571 ObjectsTablePanel(
572 model='dcim.Device',
573 title=_('Non-Racked Devices'),
574 filters={
575 'site_id': lambda ctx: ctx['object'].pk,
576 'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
577 'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
578 },
579 exclude_columns=['site'],
580 actions=[
581 actions.AddObject('dcim.Device', url_params={'site': lambda ctx: ctx['object'].pk}),
582 ],
583 ),
584 ]
585 )
587 def get_extra_context(self, request, instance):
588 return {
589 'related_models': self.get_related_models(
590 request,
591 instance,
592 omit=(CableTermination, CircuitTermination, Cluster, Prefix, VLAN, WirelessLAN),
593 extra=(
594 (VLANGroup.objects.restrict(request.user, 'view').filter(
595 scope_type=ContentType.objects.get_for_model(Site),
596 scope_id=instance.pk
597 ), 'site'),
598 (VLAN.objects.restrict(request.user, 'view').get_related_to_sites([instance]), 'related_to_site'),
599 (ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
600 (
601 Circuit.objects.restrict(request.user, 'view').filter(terminations___site=instance).distinct(),
602 'site_id'
603 ),
605 # Handle these relations manually to avoid erroneous filter name resolution
606 (Cluster.objects.restrict(request.user, 'view').filter(_site=instance), 'site_id'),
607 (Prefix.objects.restrict(request.user, 'view').filter(_site=instance), 'site_id'),
608 (WirelessLAN.objects.restrict(request.user, 'view').filter(_site=instance), 'site_id'),
609 (CircuitTermination.objects.restrict(request.user, 'view').filter(_site=instance), 'site_id'),
610 ),
611 ),
612 }
615@register_model_view(Site, 'add', detail=False)
616@register_model_view(Site, 'edit')
617class SiteEditView(generic.ObjectEditView):
618 queryset = Site.objects.all()
619 form = forms.SiteForm
622@register_model_view(Site, 'delete')
623class SiteDeleteView(generic.ObjectDeleteView):
624 queryset = Site.objects.all()
627@register_model_view(Site, 'bulk_import', path='import', detail=False)
628class SiteBulkImportView(generic.BulkImportView):
629 queryset = Site.objects.all()
630 model_form = forms.SiteImportForm
633@register_model_view(Site, 'bulk_edit', path='edit', detail=False)
634class SiteBulkEditView(generic.BulkEditView):
635 queryset = Site.objects.all()
636 filterset = filtersets.SiteFilterSet
637 table = tables.SiteTable
638 form = forms.SiteBulkEditForm
641@register_model_view(Site, 'bulk_rename', path='rename', detail=False)
642class SiteBulkRenameView(generic.BulkRenameView):
643 queryset = Site.objects.all()
644 filterset = filtersets.SiteFilterSet
647@register_model_view(Site, 'bulk_delete', path='delete', detail=False)
648class SiteBulkDeleteView(generic.BulkDeleteView):
649 queryset = Site.objects.all()
650 filterset = filtersets.SiteFilterSet
651 table = tables.SiteTable
654#
655# Locations
656#
658@register_model_view(Location, 'list', path='', detail=False)
659class LocationListView(generic.ObjectListView):
660 queryset = Location.objects.add_related_count(
661 Location.objects.add_related_count(
662 Location.objects.add_related_count(
663 Location.objects.all(),
664 Device,
665 'location',
666 'device_count',
667 cumulative=True
668 ),
669 Rack,
670 'location',
671 'rack_count',
672 cumulative=True
673 ),
674 VLANGroup,
675 'location',
676 'vlangroup_count',
677 cumulative=True
678 )
679 filterset = filtersets.LocationFilterSet
680 filterset_form = forms.LocationFilterForm
681 table = tables.LocationTable
684@register_model_view(Location)
685class LocationView(GetRelatedModelsMixin, generic.ObjectView):
686 template_name = 'generic/object.html'
687 queryset = Location.objects.all()
688 layout = layout.SimpleLayout(
689 breadcrumbs=[
690 Breadcrumb(lambda o: o.get_ancestors()),
691 ],
692 left_panels=[
693 panels.LocationPanel(),
694 TagsPanel(),
695 CustomFieldsPanel(),
696 CommentsPanel(),
697 ],
698 right_panels=[
699 RelatedObjectsPanel(),
700 ImageAttachmentsPanel(),
701 ],
702 bottom_panels=[
703 ObjectsTablePanel(
704 model='dcim.Location',
705 title=_('Child Locations'),
706 filters={'parent_id': lambda ctx: ctx['object'].pk},
707 exclude_columns=['parent'],
708 actions=[
709 actions.AddObject(
710 'dcim.Location',
711 url_params={
712 'site': lambda ctx: ctx['object'].site_id,
713 'parent': lambda ctx: ctx['object'].pk,
714 }
715 ),
716 ],
717 ),
718 ObjectsTablePanel(
719 model='dcim.Device',
720 title=_('Non-Racked Devices'),
721 filters={
722 'location_id': lambda ctx: ctx['object'].pk,
723 'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
724 'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
725 },
726 exclude_columns=['location'],
727 actions=[
728 actions.AddObject(
729 'dcim.Device',
730 url_params={
731 'site': lambda ctx: ctx['object'].site_id,
732 'parent': lambda ctx: ctx['object'].pk,
733 }
734 ),
735 ],
736 ),
737 ]
738 )
740 def get_extra_context(self, request, instance):
741 locations = instance.get_descendants(include_self=True)
742 location_content_type = ContentType.objects.get_for_model(instance)
743 return {
744 'related_models': self.get_related_models(
745 request,
746 locations,
747 omit=[CableTermination, CircuitTermination, Cluster, Prefix, WirelessLAN],
748 extra=(
749 (
750 Circuit.objects.restrict(request.user, 'view').filter(
751 terminations___location=instance
752 ).distinct(),
753 'location_id'
754 ),
756 # Handle these relations manually to avoid erroneous filter name resolution
757 (
758 CircuitTermination.objects.restrict(request.user, 'view').filter(_location=instance),
759 'location_id'
760 ),
761 (Cluster.objects.restrict(request.user, 'view').filter(_location=instance), 'location_id'),
762 (Prefix.objects.restrict(request.user, 'view').filter(_location=instance), 'location_id'),
763 (WirelessLAN.objects.restrict(request.user, 'view').filter(_location=instance), 'location_id'),
764 (VLANGroup.objects.restrict(request.user, 'view').filter(
765 scope_type_id=location_content_type.id, scope_id=instance.id), 'location'),
766 ),
767 ),
768 }
771@register_model_view(Location, 'add', detail=False)
772@register_model_view(Location, 'edit')
773class LocationEditView(generic.ObjectEditView):
774 queryset = Location.objects.all()
775 form = forms.LocationForm
778@register_model_view(Location, 'delete')
779class LocationDeleteView(generic.ObjectDeleteView):
780 queryset = Location.objects.all()
783@register_model_view(Location, 'bulk_import', path='import', detail=False)
784class LocationBulkImportView(generic.BulkImportView):
785 queryset = Location.objects.all()
786 model_form = forms.LocationImportForm
789@register_model_view(Location, 'bulk_edit', path='edit', detail=False)
790class LocationBulkEditView(generic.BulkEditView):
791 queryset = Location.objects.add_related_count(
792 Location.objects.all(),
793 Rack,
794 'location',
795 'rack_count',
796 cumulative=True
797 ).prefetch_related('site')
798 filterset = filtersets.LocationFilterSet
799 table = tables.LocationTable
800 form = forms.LocationBulkEditForm
803@register_model_view(Location, 'bulk_rename', path='rename', detail=False)
804class LocationBulkRenameView(generic.BulkRenameView):
805 queryset = Location.objects.all()
806 filterset = filtersets.LocationFilterSet
809@register_model_view(Location, 'bulk_delete', path='delete', detail=False)
810class LocationBulkDeleteView(generic.BulkDeleteView):
811 queryset = Location.objects.add_related_count(
812 Location.objects.all(),
813 Rack,
814 'location',
815 'rack_count',
816 cumulative=True
817 ).prefetch_related('site')
818 filterset = filtersets.LocationFilterSet
819 table = tables.LocationTable
822#
823# Rack groups
824#
827@register_model_view(RackGroup, 'list', path='', detail=False)
828class RackGroupListView(generic.ObjectListView):
829 queryset = RackGroup.objects.annotate(
830 rack_count=count_related(Rack, 'group')
831 )
832 filterset = filtersets.RackGroupFilterSet
833 filterset_form = forms.RackGroupFilterForm
834 table = tables.RackGroupTable
837@register_model_view(RackGroup)
838class RackGroupView(GetRelatedModelsMixin, generic.ObjectView):
839 queryset = RackGroup.objects.all()
840 layout = layout.SimpleLayout(
841 left_panels=[
842 OrganizationalObjectPanel(),
843 TagsPanel(),
844 ],
845 right_panels=[
846 RelatedObjectsPanel(),
847 CustomFieldsPanel(),
848 CommentsPanel(),
849 ],
850 )
852 def get_extra_context(self, request, instance):
853 return {
854 'related_models': self.get_related_models(request, instance),
855 }
858@register_model_view(RackGroup, 'add', detail=False)
859@register_model_view(RackGroup, 'edit')
860class RackGroupEditView(generic.ObjectEditView):
861 queryset = RackGroup.objects.all()
862 form = forms.RackGroupForm
865@register_model_view(RackGroup, 'delete')
866class RackGroupDeleteView(generic.ObjectDeleteView):
867 queryset = RackGroup.objects.all()
870@register_model_view(RackGroup, 'bulk_import', path='import', detail=False)
871class RackGroupBulkImportView(generic.BulkImportView):
872 queryset = RackGroup.objects.all()
873 model_form = forms.RackGroupImportForm
876@register_model_view(RackGroup, 'bulk_edit', path='edit', detail=False)
877class RackGroupBulkEditView(generic.BulkEditView):
878 queryset = RackGroup.objects.annotate(
879 rack_count=count_related(Rack, 'group')
880 )
881 filterset = filtersets.RackGroupFilterSet
882 table = tables.RackGroupTable
883 form = forms.RackGroupBulkEditForm
886@register_model_view(RackGroup, 'bulk_rename', path='rename', detail=False)
887class RackGroupBulkRenameView(generic.BulkRenameView):
888 queryset = RackGroup.objects.all()
889 filterset = filtersets.RackGroupFilterSet
892@register_model_view(RackGroup, 'bulk_delete', path='delete', detail=False)
893class RackGroupBulkDeleteView(generic.BulkDeleteView):
894 queryset = RackGroup.objects.annotate(
895 rack_count=count_related(Rack, 'group')
896 )
897 filterset = filtersets.RackGroupFilterSet
898 table = tables.RackGroupTable
901#
902# Rack roles
903#
905@register_model_view(RackRole, 'list', path='', detail=False)
906class RackRoleListView(generic.ObjectListView):
907 queryset = RackRole.objects.annotate(
908 rack_count=count_related(Rack, 'role')
909 )
910 filterset = filtersets.RackRoleFilterSet
911 filterset_form = forms.RackRoleFilterForm
912 table = tables.RackRoleTable
915@register_model_view(RackRole)
916class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
917 queryset = RackRole.objects.all()
918 layout = layout.SimpleLayout(
919 left_panels=[
920 panels.RackRolePanel(),
921 TagsPanel(),
922 ],
923 right_panels=[
924 RelatedObjectsPanel(),
925 CustomFieldsPanel(),
926 CommentsPanel(),
927 ],
928 )
930 def get_extra_context(self, request, instance):
931 return {
932 'related_models': self.get_related_models(request, instance),
933 }
936@register_model_view(RackRole, 'add', detail=False)
937@register_model_view(RackRole, 'edit')
938class RackRoleEditView(generic.ObjectEditView):
939 queryset = RackRole.objects.all()
940 form = forms.RackRoleForm
943@register_model_view(RackRole, 'delete')
944class RackRoleDeleteView(generic.ObjectDeleteView):
945 queryset = RackRole.objects.all()
948@register_model_view(RackRole, 'bulk_import', path='import', detail=False)
949class RackRoleBulkImportView(generic.BulkImportView):
950 queryset = RackRole.objects.all()
951 model_form = forms.RackRoleImportForm
954@register_model_view(RackRole, 'bulk_edit', path='edit', detail=False)
955class RackRoleBulkEditView(generic.BulkEditView):
956 queryset = RackRole.objects.annotate(
957 rack_count=count_related(Rack, 'role')
958 )
959 filterset = filtersets.RackRoleFilterSet
960 table = tables.RackRoleTable
961 form = forms.RackRoleBulkEditForm
964@register_model_view(RackRole, 'bulk_rename', path='rename', detail=False)
965class RackRoleBulkRenameView(generic.BulkRenameView):
966 queryset = RackRole.objects.all()
967 filterset = filtersets.RackRoleFilterSet
970@register_model_view(RackRole, 'bulk_delete', path='delete', detail=False)
971class RackRoleBulkDeleteView(generic.BulkDeleteView):
972 queryset = RackRole.objects.annotate(
973 rack_count=count_related(Rack, 'role')
974 )
975 filterset = filtersets.RackRoleFilterSet
976 table = tables.RackRoleTable
979#
980# RackTypes
981#
983@register_model_view(RackType, 'list', path='', detail=False)
984class RackTypeListView(generic.ObjectListView):
985 queryset = RackType.objects.all()
986 filterset = filtersets.RackTypeFilterSet
987 filterset_form = forms.RackTypeFilterForm
988 table = tables.RackTypeTable
991@register_model_view(RackType)
992class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
993 template_name = 'generic/object.html'
994 queryset = RackType.objects.all()
995 layout = layout.SimpleLayout(
996 left_panels=[
997 panels.RackTypePanel(),
998 panels.RackDimensionsPanel(title=_('Dimensions')),
999 TagsPanel(),
1000 CommentsPanel(),
1001 ],
1002 right_panels=[
1003 panels.RackNumberingPanel(title=_('Numbering')),
1004 panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']),
1005 CustomFieldsPanel(),
1006 RelatedObjectsPanel(),
1007 ImageAttachmentsPanel(),
1008 ],
1009 )
1011 def get_extra_context(self, request, instance):
1012 return {
1013 'related_models': self.get_related_models(request, instance),
1014 }
1017@register_model_view(RackType, 'add', detail=False)
1018@register_model_view(RackType, 'edit')
1019class RackTypeEditView(generic.ObjectEditView):
1020 queryset = RackType.objects.all()
1021 form = forms.RackTypeForm
1024@register_model_view(RackType, 'delete')
1025class RackTypeDeleteView(generic.ObjectDeleteView):
1026 queryset = RackType.objects.all()
1029@register_model_view(RackType, 'bulk_import', path='import', detail=False)
1030class RackTypeBulkImportView(generic.BulkImportView):
1031 queryset = RackType.objects.all()
1032 model_form = forms.RackTypeImportForm
1035@register_model_view(RackType, 'bulk_edit', path='edit', detail=False)
1036class RackTypeBulkEditView(generic.BulkEditView):
1037 queryset = RackType.objects.all()
1038 filterset = filtersets.RackTypeFilterSet
1039 table = tables.RackTypeTable
1040 form = forms.RackTypeBulkEditForm
1043@register_model_view(RackType, 'bulk_rename', path='rename', detail=False)
1044class RackTypeBulkRenameView(generic.BulkRenameView):
1045 queryset = RackType.objects.all()
1046 field_name = 'model'
1047 filterset = filtersets.RackTypeFilterSet
1050@register_model_view(RackType, 'bulk_delete', path='delete', detail=False)
1051class RackTypeBulkDeleteView(generic.BulkDeleteView):
1052 queryset = RackType.objects.all()
1053 filterset = filtersets.RackTypeFilterSet
1054 table = tables.RackTypeTable
1057#
1058# Racks
1059#
1061@register_model_view(Rack, 'list', path='', detail=False)
1062class RackListView(generic.ObjectListView):
1063 queryset = Rack.objects.annotate(
1064 device_count=count_related(Device, 'rack')
1065 )
1066 filterset = filtersets.RackFilterSet
1067 filterset_form = forms.RackFilterForm
1068 table = tables.RackTable
1069 template_name = 'dcim/rack_list.html'
1072class RackElevationListView(generic.ObjectListView):
1073 """
1074 Display a set of rack elevations side-by-side.
1075 """
1076 queryset = Rack.objects.prefetch_related('role')
1078 def get(self, request):
1080 racks = filtersets.RackFilterSet(request.GET, self.queryset).qs
1081 total_count = racks.count()
1083 # Ordering
1084 ORDERING_CHOICES = {
1085 'name': 'Name (A-Z)',
1086 '-name': 'Name (Z-A)',
1087 'facility_id': 'Facility ID (A-Z)',
1088 '-facility_id': 'Facility ID (Z-A)',
1089 }
1090 sort = request.GET.get('sort', 'name')
1091 if sort not in ORDERING_CHOICES:
1092 sort = 'name'
1093 racks = racks.order_by(sort)
1095 # Pagination
1096 per_page = get_paginate_count(request)
1097 page_number = request.GET.get('page', 1)
1098 paginator = EnhancedPaginator(racks, per_page)
1099 try:
1100 page = paginator.page(page_number)
1101 except PageNotAnInteger:
1102 page = paginator.page(1)
1103 except EmptyPage:
1104 page = paginator.page(paginator.num_pages)
1106 # Determine rack face
1107 rack_face = request.GET.get('face', DeviceFaceChoices.FACE_FRONT)
1108 if rack_face not in DeviceFaceChoices.values():
1109 rack_face = DeviceFaceChoices.FACE_FRONT
1111 return render(request, 'dcim/rack_elevation_list.html', {
1112 'paginator': paginator,
1113 'page': page,
1114 'total_count': total_count,
1115 'sort': sort,
1116 'sort_display_name': ORDERING_CHOICES[sort],
1117 'sort_choices': ORDERING_CHOICES,
1118 'rack_face': rack_face,
1119 'filter_form': forms.RackElevationFilterForm(request.GET),
1120 'model': self.queryset.model,
1121 })
1124@register_model_view(Rack)
1125class RackView(GetRelatedModelsMixin, generic.ObjectView):
1126 queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
1127 layout = layout.SimpleLayout(
1128 breadcrumbs=[
1129 Breadcrumb('site', url=filtered_list_url('dcim:rack_list', 'site_id')),
1130 Breadcrumb(
1131 lambda o: o.location.get_ancestors() if o.location else [],
1132 url=filtered_list_url('dcim:rack_list', 'location_id'),
1133 ),
1134 Breadcrumb('location', url=filtered_list_url('dcim:rack_list', 'location_id')),
1135 ],
1136 left_panels=[
1137 panels.RackPanel(),
1138 panels.RackDimensionsPanel(title=_('Dimensions')),
1139 panels.RackNumberingPanel(title=_('Numbering')),
1140 panels.RackWeightPanel(title=_('Weight')),
1141 CustomFieldsPanel(),
1142 TagsPanel(),
1143 CommentsPanel(),
1144 ImageAttachmentsPanel(),
1145 ],
1146 right_panels=[
1147 TemplatePanel('dcim/panels/rack_elevations.html'),
1148 RelatedObjectsPanel(),
1149 ],
1150 )
1152 def get_extra_context(self, request, instance):
1153 peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
1155 if instance.location:
1156 peer_racks = peer_racks.filter(location=instance.location)
1157 else:
1158 peer_racks = peer_racks.filter(location__isnull=True)
1159 next_rack = peer_racks.filter(name__gt=instance.name).first()
1160 prev_rack = peer_racks.filter(name__lt=instance.name).reverse().first()
1162 # Determine any additional parameters to pass when embedding the rack elevations
1163 svg_extra = '&'.join([
1164 f'highlight=id:{pk}' for pk in request.GET.getlist('device')
1165 ])
1167 return {
1168 'related_models': self.get_related_models(
1169 request,
1170 instance,
1171 omit=(CableTermination,),
1172 extra=(
1173 (
1174 VLANGroup.objects.restrict(request.user, 'view').filter(
1175 scope_type=ContentType.objects.get_for_model(Rack),
1176 scope_id=instance.pk
1177 ), 'rack'),
1178 ),
1179 ),
1180 'next_rack': next_rack,
1181 'prev_rack': prev_rack,
1182 'svg_extra': svg_extra,
1183 'peer_racks': peer_racks,
1184 }
1187@register_model_view(Rack, 'reservations')
1188class RackRackReservationsView(generic.ObjectChildrenView):
1189 queryset = Rack.objects.all()
1190 child_model = RackReservation
1191 table = tables.RackReservationTable
1192 filterset = filtersets.RackReservationFilterSet
1193 filterset_form = forms.RackReservationFilterForm
1194 template_name = 'dcim/rack/reservations.html'
1195 tab = ViewTab(
1196 label=_('Reservations'),
1197 badge=lambda obj: obj.reservations.count(),
1198 permission='dcim.view_rackreservation',
1199 weight=510
1200 )
1202 def get_children(self, request, parent):
1203 return parent.reservations.restrict(request.user, 'view')
1206@register_model_view(Rack, 'nonracked_devices', 'nonracked-devices')
1207class RackNonRackedView(generic.ObjectChildrenView):
1208 queryset = Rack.objects.all()
1209 child_model = Device
1210 table = tables.DeviceTable
1211 filterset = filtersets.DeviceFilterSet
1212 filterset_form = forms.DeviceFilterForm
1213 template_name = 'dcim/rack/non_racked_devices.html'
1214 tab = ViewTab(
1215 label=_('Non-Racked Devices'),
1216 badge=lambda obj: obj.devices.filter(rack=obj, position__isnull=True, parent_bay__isnull=True).count(),
1217 weight=500,
1218 permission='dcim.view_device',
1219 )
1221 def get_children(self, request, parent):
1222 return parent.devices.restrict(request.user, 'view').filter(
1223 rack=parent, position__isnull=True, parent_bay__isnull=True
1224 )
1227@register_model_view(Rack, 'add', detail=False)
1228@register_model_view(Rack, 'edit')
1229class RackEditView(generic.ObjectEditView):
1230 queryset = Rack.objects.all()
1231 form = forms.RackForm
1234@register_model_view(Rack, 'delete')
1235class RackDeleteView(generic.ObjectDeleteView):
1236 queryset = Rack.objects.all()
1239@register_model_view(Rack, 'bulk_import', path='import', detail=False)
1240class RackBulkImportView(generic.BulkImportView):
1241 queryset = Rack.objects.all()
1242 model_form = forms.RackImportForm
1245@register_model_view(Rack, 'bulk_edit', path='edit', detail=False)
1246class RackBulkEditView(generic.BulkEditView):
1247 queryset = Rack.objects.all()
1248 filterset = filtersets.RackFilterSet
1249 table = tables.RackTable
1250 form = forms.RackBulkEditForm
1253@register_model_view(Rack, 'bulk_rename', path='rename', detail=False)
1254class RackBulkRenameView(generic.BulkRenameView):
1255 queryset = Rack.objects.all()
1256 filterset = filtersets.RackFilterSet
1259@register_model_view(Rack, 'bulk_delete', path='delete', detail=False)
1260class RackBulkDeleteView(generic.BulkDeleteView):
1261 queryset = Rack.objects.all()
1262 filterset = filtersets.RackFilterSet
1263 table = tables.RackTable
1266#
1267# Rack reservations
1268#
1270@register_model_view(RackReservation, 'list', path='', detail=False)
1271class RackReservationListView(generic.ObjectListView):
1272 queryset = RackReservation.objects.annotate(
1273 unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
1274 )
1275 filterset = filtersets.RackReservationFilterSet
1276 filterset_form = forms.RackReservationFilterForm
1277 table = tables.RackReservationTable
1278 actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
1281@register_model_view(RackReservation)
1282class RackReservationView(generic.ObjectView):
1283 template_name = 'generic/object.html'
1284 queryset = RackReservation.objects.annotate(
1285 unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
1286 )
1287 layout = layout.SimpleLayout(
1288 breadcrumbs=[
1289 Breadcrumb('rack', url=filtered_list_url('dcim:rackreservation_list', 'rack_id')),
1290 Breadcrumb(label=lambda o: f"{_('Units')} {o.unit_list}"),
1291 ],
1292 left_panels=[
1293 panels.RackPanel(accessor='object.rack', only=['region', 'site', 'location', 'group', 'name']),
1294 panels.RackReservationPanel(title=_('Reservation')),
1295 CustomFieldsPanel(),
1296 TagsPanel(),
1297 CommentsPanel(),
1298 ],
1299 right_panels=[
1300 TemplatePanel(template_name='dcim/panels/rack_reservation_elevations.html'),
1301 RelatedObjectsPanel(),
1302 ],
1303 )
1306@register_model_view(RackReservation, 'add', detail=False)
1307@register_model_view(RackReservation, 'edit')
1308class RackReservationEditView(generic.ObjectEditView):
1309 queryset = RackReservation.objects.all()
1310 form = forms.RackReservationForm
1312 def alter_object(self, obj, request, args, kwargs):
1313 if not obj.pk:
1314 if 'rack' in request.GET:
1315 obj.rack = get_object_or_404(Rack, pk=request.GET.get('rack'))
1316 obj.user = request.user
1317 return obj
1320@register_model_view(RackReservation, 'delete')
1321class RackReservationDeleteView(generic.ObjectDeleteView):
1322 queryset = RackReservation.objects.all()
1325@register_model_view(RackReservation, 'bulk_import', path='import', detail=False)
1326class RackReservationImportView(generic.BulkImportView):
1327 queryset = RackReservation.objects.all()
1328 model_form = forms.RackReservationImportForm
1330 def save_object(self, object_form, request):
1331 """
1332 Assign the currently authenticated user to the RackReservation.
1333 """
1334 instance = object_form.save(commit=False)
1335 instance.user = request.user
1336 instance.save()
1338 return instance
1341@register_model_view(RackReservation, 'bulk_edit', path='edit', detail=False)
1342class RackReservationBulkEditView(generic.BulkEditView):
1343 queryset = RackReservation.objects.annotate(
1344 unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
1345 )
1346 filterset = filtersets.RackReservationFilterSet
1347 table = tables.RackReservationTable
1348 form = forms.RackReservationBulkEditForm
1351@register_model_view(RackReservation, 'bulk_delete', path='delete', detail=False)
1352class RackReservationBulkDeleteView(generic.BulkDeleteView):
1353 queryset = RackReservation.objects.annotate(
1354 unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
1355 )
1356 filterset = filtersets.RackReservationFilterSet
1357 table = tables.RackReservationTable
1360#
1361# Manufacturers
1362#
1364@register_model_view(Manufacturer, 'list', path='', detail=False)
1365class ManufacturerListView(generic.ObjectListView):
1366 queryset = Manufacturer.objects.annotate(
1367 racktype_count=count_related(RackType, 'manufacturer'),
1368 devicetype_count=count_related(DeviceType, 'manufacturer'),
1369 moduletype_count=count_related(ModuleType, 'manufacturer'),
1370 inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
1371 platform_count=count_related(Platform, 'manufacturer')
1372 )
1373 filterset = filtersets.ManufacturerFilterSet
1374 filterset_form = forms.ManufacturerFilterForm
1375 table = tables.ManufacturerTable
1378@register_model_view(Manufacturer)
1379class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView):
1380 queryset = Manufacturer.objects.all()
1381 layout = layout.SimpleLayout(
1382 left_panels=[OrganizationalObjectPanel(), TagsPanel()],
1383 right_panels=[RelatedObjectsPanel(), CustomFieldsPanel(), CommentsPanel()],
1384 )
1386 def get_extra_context(self, request, instance):
1387 return {
1388 'related_models': self.get_related_models(request, instance, [InventoryItemTemplate]),
1389 }
1392@register_model_view(Manufacturer, 'add', detail=False)
1393@register_model_view(Manufacturer, 'edit')
1394class ManufacturerEditView(generic.ObjectEditView):
1395 queryset = Manufacturer.objects.all()
1396 form = forms.ManufacturerForm
1399@register_model_view(Manufacturer, 'delete')
1400class ManufacturerDeleteView(generic.ObjectDeleteView):
1401 queryset = Manufacturer.objects.all()
1404@register_model_view(Manufacturer, 'bulk_import', path='import', detail=False)
1405class ManufacturerBulkImportView(generic.BulkImportView):
1406 queryset = Manufacturer.objects.all()
1407 model_form = forms.ManufacturerImportForm
1410@register_model_view(Manufacturer, 'bulk_edit', path='edit', detail=False)
1411class ManufacturerBulkEditView(generic.BulkEditView):
1412 queryset = Manufacturer.objects.annotate(
1413 devicetype_count=count_related(DeviceType, 'manufacturer'),
1414 moduletype_count=count_related(ModuleType, 'manufacturer'),
1415 inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
1416 platform_count=count_related(Platform, 'manufacturer')
1417 )
1418 filterset = filtersets.ManufacturerFilterSet
1419 table = tables.ManufacturerTable
1420 form = forms.ManufacturerBulkEditForm
1423@register_model_view(Manufacturer, 'bulk_rename', path='rename', detail=False)
1424class ManufacturerBulkRenameView(generic.BulkRenameView):
1425 queryset = Manufacturer.objects.all()
1426 filterset = filtersets.ManufacturerFilterSet
1429@register_model_view(Manufacturer, 'bulk_delete', path='delete', detail=False)
1430class ManufacturerBulkDeleteView(generic.BulkDeleteView):
1431 queryset = Manufacturer.objects.annotate(
1432 devicetype_count=count_related(DeviceType, 'manufacturer'),
1433 moduletype_count=count_related(ModuleType, 'manufacturer'),
1434 inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
1435 platform_count=count_related(Platform, 'manufacturer')
1436 )
1437 filterset = filtersets.ManufacturerFilterSet
1438 table = tables.ManufacturerTable
1441#
1442# Device types
1443#
1445@register_model_view(DeviceType, 'list', path='', detail=False)
1446class DeviceTypeListView(generic.ObjectListView):
1447 queryset = DeviceType.objects.all()
1448 filterset = filtersets.DeviceTypeFilterSet
1449 filterset_form = forms.DeviceTypeFilterForm
1450 table = tables.DeviceTypeTable
1452 def export_yaml(self):
1453 # Avoid one module_bay_types query per module bay template across the export.
1454 self.queryset = self.queryset.prefetch_related('modulebaytemplates__module_bay_types')
1455 return super().export_yaml()
1458@register_model_view(DeviceType)
1459class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
1460 queryset = DeviceType.objects.all()
1461 layout = layout.SimpleLayout(
1462 breadcrumbs=[
1463 Breadcrumb('manufacturer', url=filtered_list_url('dcim:devicetype_list', 'manufacturer_id')),
1464 ],
1465 left_panels=[
1466 panels.DeviceTypePanel(),
1467 TagsPanel(),
1468 ],
1469 right_panels=[
1470 RelatedObjectsPanel(),
1471 CustomFieldsPanel(),
1472 CommentsPanel(),
1473 ImageAttachmentsPanel(),
1474 ],
1475 )
1477 def get_extra_context(self, request, instance):
1478 return {
1479 'related_models': self.get_related_models(request, instance, omit=[
1480 ConsolePortTemplate, ConsoleServerPortTemplate, CoolingIntakeTemplate, CoolingOutflowTemplate,
1481 DeviceBayTemplate, FrontPortTemplate, InventoryItemTemplate, InterfaceTemplate, ModuleBayTemplate,
1482 PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
1483 ]),
1484 }
1487@register_model_view(DeviceType, 'add', detail=False)
1488@register_model_view(DeviceType, 'edit')
1489class DeviceTypeEditView(generic.ObjectEditView):
1490 queryset = DeviceType.objects.all()
1491 form = forms.DeviceTypeForm
1494@register_model_view(DeviceType, 'delete')
1495class DeviceTypeDeleteView(generic.ObjectDeleteView):
1496 queryset = DeviceType.objects.all()
1499@register_model_view(DeviceType, 'consoleports', path='console-ports')
1500class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
1501 child_model = ConsolePortTemplate
1502 table = tables.ConsolePortTemplateTable
1503 filterset = filtersets.ConsolePortTemplateFilterSet
1504 viewname = 'dcim:devicetype_consoleports'
1505 tab = ViewTab(
1506 label=_('Console Ports'),
1507 badge=lambda obj: obj.console_port_template_count,
1508 permission='dcim.view_consoleporttemplate',
1509 weight=550,
1510 hide_if_empty=True
1511 )
1514@register_model_view(DeviceType, 'consoleserverports', path='console-server-ports')
1515class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
1516 child_model = ConsoleServerPortTemplate
1517 table = tables.ConsoleServerPortTemplateTable
1518 filterset = filtersets.ConsoleServerPortTemplateFilterSet
1519 viewname = 'dcim:devicetype_consoleserverports'
1520 tab = ViewTab(
1521 label=_('Console Server Ports'),
1522 badge=lambda obj: obj.console_server_port_template_count,
1523 permission='dcim.view_consoleserverporttemplate',
1524 weight=560,
1525 hide_if_empty=True
1526 )
1529@register_model_view(DeviceType, 'powerports', path='power-ports')
1530class DeviceTypePowerPortsView(DeviceTypeComponentsView):
1531 child_model = PowerPortTemplate
1532 table = tables.PowerPortTemplateTable
1533 filterset = filtersets.PowerPortTemplateFilterSet
1534 viewname = 'dcim:devicetype_powerports'
1535 tab = ViewTab(
1536 label=_('Power Ports'),
1537 badge=lambda obj: obj.power_port_template_count,
1538 permission='dcim.view_powerporttemplate',
1539 weight=570,
1540 hide_if_empty=True
1541 )
1544@register_model_view(DeviceType, 'poweroutlets', path='power-outlets')
1545class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
1546 child_model = PowerOutletTemplate
1547 table = tables.PowerOutletTemplateTable
1548 filterset = filtersets.PowerOutletTemplateFilterSet
1549 viewname = 'dcim:devicetype_poweroutlets'
1550 tab = ViewTab(
1551 label=_('Power Outlets'),
1552 badge=lambda obj: obj.power_outlet_template_count,
1553 permission='dcim.view_poweroutlettemplate',
1554 weight=580,
1555 hide_if_empty=True
1556 )
1559@register_model_view(DeviceType, 'coolingintakes', path='cooling-intakes')
1560class DeviceTypeCoolingIntakesView(DeviceTypeComponentsView):
1561 child_model = CoolingIntakeTemplate
1562 table = tables.CoolingIntakeTemplateTable
1563 filterset = filtersets.CoolingIntakeTemplateFilterSet
1564 viewname = 'dcim:devicetype_coolingintakes'
1565 tab = ViewTab(
1566 label=_('Cooling Intakes'),
1567 badge=lambda obj: obj.cooling_intake_template_count,
1568 permission='dcim.view_coolingintaketemplate',
1569 weight=590,
1570 hide_if_empty=True
1571 )
1574@register_model_view(DeviceType, 'coolingoutflows', path='cooling-outflows')
1575class DeviceTypeCoolingOutflowsView(DeviceTypeComponentsView):
1576 child_model = CoolingOutflowTemplate
1577 table = tables.CoolingOutflowTemplateTable
1578 filterset = filtersets.CoolingOutflowTemplateFilterSet
1579 viewname = 'dcim:devicetype_coolingoutflows'
1580 tab = ViewTab(
1581 label=_('Cooling Outflows'),
1582 badge=lambda obj: obj.cooling_outflow_template_count,
1583 permission='dcim.view_coolingoutflowtemplate',
1584 weight=600,
1585 hide_if_empty=True
1586 )
1589@register_model_view(DeviceType, 'interfaces')
1590class DeviceTypeInterfacesView(DeviceTypeComponentsView):
1591 child_model = InterfaceTemplate
1592 table = tables.InterfaceTemplateTable
1593 filterset = filtersets.InterfaceTemplateFilterSet
1594 viewname = 'dcim:devicetype_interfaces'
1595 tab = ViewTab(
1596 label=_('Interfaces'),
1597 badge=lambda obj: obj.interface_template_count,
1598 permission='dcim.view_interfacetemplate',
1599 weight=520,
1600 hide_if_empty=True
1601 )
1604@register_model_view(DeviceType, 'frontports', path='front-ports')
1605class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
1606 child_model = FrontPortTemplate
1607 table = tables.FrontPortTemplateTable
1608 filterset = filtersets.FrontPortTemplateFilterSet
1609 viewname = 'dcim:devicetype_frontports'
1610 tab = ViewTab(
1611 label=_('Front Ports'),
1612 badge=lambda obj: obj.front_port_template_count,
1613 permission='dcim.view_frontporttemplate',
1614 weight=530,
1615 hide_if_empty=True
1616 )
1619@register_model_view(DeviceType, 'rearports', path='rear-ports')
1620class DeviceTypeRearPortsView(DeviceTypeComponentsView):
1621 child_model = RearPortTemplate
1622 table = tables.RearPortTemplateTable
1623 filterset = filtersets.RearPortTemplateFilterSet
1624 viewname = 'dcim:devicetype_rearports'
1625 tab = ViewTab(
1626 label=_('Rear Ports'),
1627 badge=lambda obj: obj.rear_port_template_count,
1628 permission='dcim.view_rearporttemplate',
1629 weight=540,
1630 hide_if_empty=True
1631 )
1634@register_model_view(DeviceType, 'modulebays', path='module-bays')
1635class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
1636 child_model = ModuleBayTemplate
1637 table = tables.ModuleBayTemplateTable
1638 filterset = filtersets.ModuleBayTemplateFilterSet
1639 viewname = 'dcim:devicetype_modulebays'
1640 tab = ViewTab(
1641 label=_('Module Bays'),
1642 badge=lambda obj: obj.module_bay_template_count,
1643 permission='dcim.view_modulebaytemplate',
1644 weight=510,
1645 hide_if_empty=True
1646 )
1649@register_model_view(DeviceType, 'devicebays', path='device-bays')
1650class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
1651 child_model = DeviceBayTemplate
1652 table = tables.DeviceBayTemplateTable
1653 filterset = filtersets.DeviceBayTemplateFilterSet
1654 viewname = 'dcim:devicetype_devicebays'
1655 tab = ViewTab(
1656 label=_('Device Bays'),
1657 badge=lambda obj: obj.device_bay_template_count,
1658 permission='dcim.view_devicebaytemplate',
1659 weight=500,
1660 hide_if_empty=True
1661 )
1664@register_model_view(DeviceType, 'inventoryitems', path='inventory-items')
1665class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
1666 child_model = InventoryItemTemplate
1667 table = tables.InventoryItemTemplateTable
1668 filterset = filtersets.InventoryItemTemplateFilterSet
1669 viewname = 'dcim:devicetype_inventoryitems'
1670 tab = ViewTab(
1671 label=_('Inventory Items'),
1672 badge=lambda obj: obj.inventory_item_template_count,
1673 permission='dcim.view_inventoryitemtemplate',
1674 weight=610,
1675 hide_if_empty=True
1676 )
1679@register_model_view(DeviceType, 'bulk_import', path='import', detail=False)
1680class DeviceTypeImportView(generic.BulkImportView):
1681 additional_permissions = [
1682 'dcim.add_devicetype',
1683 'dcim.add_consoleporttemplate',
1684 'dcim.add_consoleserverporttemplate',
1685 'dcim.add_powerporttemplate',
1686 'dcim.add_poweroutlettemplate',
1687 'dcim.add_coolingintaketemplate',
1688 'dcim.add_coolingoutflowtemplate',
1689 'dcim.add_interfacetemplate',
1690 'dcim.add_frontporttemplate',
1691 'dcim.add_rearporttemplate',
1692 'dcim.add_modulebaytemplate',
1693 'dcim.add_devicebaytemplate',
1694 'dcim.add_inventoryitemtemplate',
1695 ]
1696 queryset = DeviceType.objects.all()
1697 model_form = forms.DeviceTypeImportForm
1698 related_object_forms = {
1699 'console-ports': forms.ConsolePortTemplateImportForm,
1700 'console-server-ports': forms.ConsoleServerPortTemplateImportForm,
1701 'power-ports': forms.PowerPortTemplateImportForm,
1702 'power-outlets': forms.PowerOutletTemplateImportForm,
1703 'cooling-intakes': forms.CoolingIntakeTemplateImportForm,
1704 'cooling-outflows': forms.CoolingOutflowTemplateImportForm,
1705 'interfaces': forms.InterfaceTemplateImportForm,
1706 'rear-ports': forms.RearPortTemplateImportForm,
1707 'front-ports': forms.FrontPortTemplateImportForm,
1708 'port-mappings': forms.PortTemplateMappingImportForm,
1709 'module-bays': forms.ModuleBayTemplateImportForm,
1710 'device-bays': forms.DeviceBayTemplateImportForm,
1711 'inventory-items': forms.InventoryItemTemplateImportForm,
1712 }
1714 def prep_related_object_data(self, parent, data):
1715 data.update({'device_type': parent})
1716 return data
1719@register_model_view(DeviceType, 'bulk_edit', path='edit', detail=False)
1720class DeviceTypeBulkEditView(generic.BulkEditView):
1721 queryset = DeviceType.objects.all()
1722 filterset = filtersets.DeviceTypeFilterSet
1723 table = tables.DeviceTypeTable
1724 form = forms.DeviceTypeBulkEditForm
1727@register_model_view(DeviceType, 'bulk_rename', path='rename', detail=False)
1728class DeviceTypeBulkRenameView(generic.BulkRenameView):
1729 queryset = DeviceType.objects.all()
1730 field_name = 'model'
1731 filterset = filtersets.DeviceTypeFilterSet
1734@register_model_view(DeviceType, 'bulk_delete', path='delete', detail=False)
1735class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
1736 queryset = DeviceType.objects.all()
1737 filterset = filtersets.DeviceTypeFilterSet
1738 table = tables.DeviceTypeTable
1741#
1742# Module bay types
1743#
1745@register_model_view(ModuleBayType, 'list', path='', detail=False)
1746class ModuleBayTypeListView(generic.ObjectListView):
1747 queryset = ModuleBayType.objects.all()
1748 filterset = filtersets.ModuleBayTypeFilterSet
1749 filterset_form = forms.ModuleBayTypeFilterForm
1750 table = tables.ModuleBayTypeTable
1753@register_model_view(ModuleBayType)
1754class ModuleBayTypeView(generic.ObjectView):
1755 template_name = 'generic/object.html'
1756 queryset = ModuleBayType.objects.all()
1757 layout = layout.SimpleLayout(
1758 left_panels=[
1759 panels.ModuleBayTypePanel(),
1760 TagsPanel(),
1761 ],
1762 right_panels=[
1763 CustomFieldsPanel(),
1764 CommentsPanel(),
1765 ],
1766 bottom_panels=[
1767 ObjectsTablePanel(
1768 model='dcim.ModuleType',
1769 title=_('Compatible Module Types'),
1770 filters={'module_bay_type_id': lambda ctx: ctx['object'].pk},
1771 ),
1772 ObjectsTablePanel(
1773 model='dcim.ModuleBay',
1774 title=_('Module Bays'),
1775 filters={'module_bay_type_id': lambda ctx: ctx['object'].pk},
1776 ),
1777 ],
1778 )
1781@register_model_view(ModuleBayType, 'add', detail=False)
1782@register_model_view(ModuleBayType, 'edit')
1783class ModuleBayTypeEditView(generic.ObjectEditView):
1784 queryset = ModuleBayType.objects.all()
1785 form = forms.ModuleBayTypeForm
1788@register_model_view(ModuleBayType, 'delete')
1789class ModuleBayTypeDeleteView(generic.ObjectDeleteView):
1790 queryset = ModuleBayType.objects.all()
1793@register_model_view(ModuleBayType, 'bulk_import', detail=False)
1794class ModuleBayTypeBulkImportView(generic.BulkImportView):
1795 queryset = ModuleBayType.objects.all()
1796 model_form = forms.ModuleBayTypeImportForm
1799@register_model_view(ModuleBayType, 'bulk_edit', path='edit', detail=False)
1800class ModuleBayTypeBulkEditView(generic.BulkEditView):
1801 queryset = ModuleBayType.objects.all()
1802 filterset = filtersets.ModuleBayTypeFilterSet
1803 table = tables.ModuleBayTypeTable
1804 form = forms.ModuleBayTypeBulkEditForm
1807@register_model_view(ModuleBayType, 'bulk_delete', path='delete', detail=False)
1808class ModuleBayTypeBulkDeleteView(generic.BulkDeleteView):
1809 queryset = ModuleBayType.objects.all()
1810 filterset = filtersets.ModuleBayTypeFilterSet
1811 table = tables.ModuleBayTypeTable
1814#
1815# Module type profiles
1816#
1818@register_model_view(ModuleTypeProfile, 'list', path='', detail=False)
1819class ModuleTypeProfileListView(generic.ObjectListView):
1820 queryset = ModuleTypeProfile.objects.annotate(
1821 instance_count=count_related(ModuleType, 'profile')
1822 )
1823 filterset = filtersets.ModuleTypeProfileFilterSet
1824 filterset_form = forms.ModuleTypeProfileFilterForm
1825 table = tables.ModuleTypeProfileTable
1828@register_model_view(ModuleTypeProfile)
1829class ModuleTypeProfileView(generic.ObjectView):
1830 template_name = 'generic/object.html'
1831 queryset = ModuleTypeProfile.objects.all()
1832 layout = layout.SimpleLayout(
1833 left_panels=[
1834 panels.ModuleTypeProfilePanel(),
1835 TagsPanel(),
1836 CommentsPanel(),
1837 ],
1838 right_panels=[
1839 JSONPanel(field_name='schema', title=_('Schema')),
1840 CustomFieldsPanel(),
1841 ],
1842 bottom_panels=[
1843 ObjectsTablePanel(
1844 model='dcim.ModuleType',
1845 title=_('Module Types'),
1846 filters={
1847 'profile_id': lambda ctx: ctx['object'].pk,
1848 },
1849 exclude_columns=['profile'],
1850 actions=[
1851 actions.AddObject(
1852 'dcim.ModuleType',
1853 url_params={
1854 'profile': lambda ctx: ctx['object'].pk,
1855 }
1856 ),
1857 ],
1858 ),
1859 ]
1860 )
1863@register_model_view(ModuleTypeProfile, 'add', detail=False)
1864@register_model_view(ModuleTypeProfile, 'edit')
1865class ModuleTypeProfileEditView(generic.ObjectEditView):
1866 queryset = ModuleTypeProfile.objects.all()
1867 form = forms.ModuleTypeProfileForm
1870@register_model_view(ModuleTypeProfile, 'delete')
1871class ModuleTypeProfileDeleteView(generic.ObjectDeleteView):
1872 queryset = ModuleTypeProfile.objects.all()
1875@register_model_view(ModuleTypeProfile, 'bulk_import', detail=False)
1876class ModuleTypeProfileBulkImportView(generic.BulkImportView):
1877 queryset = ModuleTypeProfile.objects.all()
1878 model_form = forms.ModuleTypeProfileImportForm
1881@register_model_view(ModuleTypeProfile, 'bulk_edit', path='edit', detail=False)
1882class ModuleTypeProfileBulkEditView(generic.BulkEditView):
1883 queryset = ModuleTypeProfile.objects.annotate(
1884 instance_count=count_related(Module, 'module_type')
1885 )
1886 filterset = filtersets.ModuleTypeProfileFilterSet
1887 table = tables.ModuleTypeProfileTable
1888 form = forms.ModuleTypeProfileBulkEditForm
1891@register_model_view(ModuleTypeProfile, 'bulk_rename', path='rename', detail=False)
1892class ModuleTypeProfileBulkRenameView(generic.BulkRenameView):
1893 queryset = ModuleTypeProfile.objects.all()
1894 filterset = filtersets.ModuleTypeProfileFilterSet
1897@register_model_view(ModuleTypeProfile, 'bulk_delete', path='delete', detail=False)
1898class ModuleTypeProfileBulkDeleteView(generic.BulkDeleteView):
1899 queryset = ModuleTypeProfile.objects.annotate(
1900 instance_count=count_related(Module, 'module_type')
1901 )
1902 filterset = filtersets.ModuleTypeProfileFilterSet
1903 table = tables.ModuleTypeProfileTable
1906#
1907# Module types
1908#
1910@register_model_view(ModuleType, 'list', path='', detail=False)
1911class ModuleTypeListView(generic.ObjectListView):
1912 queryset = ModuleType.objects.all()
1913 filterset = filtersets.ModuleTypeFilterSet
1914 filterset_form = forms.ModuleTypeFilterForm
1915 table = tables.ModuleTypeTable
1917 def export_yaml(self):
1918 # Avoid one module_bay_types query per module type across the export. (Unlike
1919 # DeviceType.to_yaml(), ModuleType.to_yaml() doesn't export module bay templates at
1920 # all, so there's nothing to prefetch alongside it.)
1921 self.queryset = self.queryset.prefetch_related('module_bay_types')
1922 return super().export_yaml()
1925@register_model_view(ModuleType)
1926class ModuleTypeView(GetRelatedModelsMixin, generic.ObjectView):
1927 queryset = ModuleType.objects.all()
1928 layout = layout.SimpleLayout(
1929 breadcrumbs=[
1930 Breadcrumb('manufacturer', url=filtered_list_url('dcim:moduletype_list', 'manufacturer_id')),
1931 ],
1932 left_panels=[
1933 panels.ModuleTypePanel(),
1934 TagsPanel(),
1935 CommentsPanel(),
1936 ],
1937 right_panels=[
1938 TemplatePanel(
1939 title=_('Attributes'),
1940 template_name='dcim/panels/module_type_attributes.html',
1941 ),
1942 RelatedObjectsPanel(),
1943 CustomFieldsPanel(),
1944 ImageAttachmentsPanel(),
1945 ],
1946 )
1948 def get_extra_context(self, request, instance):
1949 return {
1950 'related_models': self.get_related_models(request, instance, omit=[
1951 ConsolePortTemplate, ConsoleServerPortTemplate, CoolingIntakeTemplate, CoolingOutflowTemplate,
1952 DeviceBayTemplate, FrontPortTemplate, InventoryItemTemplate, InterfaceTemplate, ModuleBayTemplate,
1953 PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
1954 ]),
1955 }
1958@register_model_view(ModuleType, 'add', detail=False)
1959@register_model_view(ModuleType, 'edit')
1960class ModuleTypeEditView(generic.ObjectEditView):
1961 queryset = ModuleType.objects.all()
1962 form = forms.ModuleTypeForm
1964 def post(self, request, *args, **kwargs):
1965 response = super().post(request, *args, **kwargs)
1966 # A successful save always redirects (302) or returns an HTMX redirect header.
1967 # Quick-add creates new objects (no pk in kwargs), so it is excluded by the pk guard below.
1968 saved = (
1969 getattr(response, 'status_code', None) == 302 or
1970 (hasattr(response, 'headers') and 'HX-Location' in response.headers)
1971 )
1972 if saved and kwargs.get('pk'):
1973 try:
1974 module_type = ModuleType.objects.prefetch_related('module_bay_types').get(pk=kwargs['pk'])
1975 count = module_type.get_incompatible_modules().count()
1976 if count:
1977 messages.warning(
1978 request,
1979 ngettext(
1980 '%(count)d installed module of this type is now incompatible with its module bay '
1981 'due to conflicting bay type constraints.',
1982 '%(count)d installed modules of this type are now incompatible with their module bays '
1983 'due to conflicting bay type constraints.',
1984 count,
1985 ) % {'count': count}
1986 )
1987 except ModuleType.DoesNotExist:
1988 pass
1989 return response
1992@register_model_view(ModuleType, 'delete')
1993class ModuleTypeDeleteView(generic.ObjectDeleteView):
1994 queryset = ModuleType.objects.all()
1997@register_model_view(ModuleType, 'consoleports', path='console-ports')
1998class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
1999 child_model = ConsolePortTemplate
2000 table = tables.ConsolePortTemplateTable
2001 filterset = filtersets.ConsolePortTemplateFilterSet
2002 viewname = 'dcim:moduletype_consoleports'
2003 tab = ViewTab(
2004 label=_('Console Ports'),
2005 badge=lambda obj: obj.console_port_template_count,
2006 permission='dcim.view_consoleporttemplate',
2007 weight=530,
2008 hide_if_empty=True
2009 )
2012@register_model_view(ModuleType, 'consoleserverports', path='console-server-ports')
2013class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
2014 child_model = ConsoleServerPortTemplate
2015 table = tables.ConsoleServerPortTemplateTable
2016 filterset = filtersets.ConsoleServerPortTemplateFilterSet
2017 viewname = 'dcim:moduletype_consoleserverports'
2018 tab = ViewTab(
2019 label=_('Console Server Ports'),
2020 badge=lambda obj: obj.console_server_port_template_count,
2021 permission='dcim.view_consoleserverporttemplate',
2022 weight=540,
2023 hide_if_empty=True
2024 )
2027@register_model_view(ModuleType, 'powerports', path='power-ports')
2028class ModuleTypePowerPortsView(ModuleTypeComponentsView):
2029 child_model = PowerPortTemplate
2030 table = tables.PowerPortTemplateTable
2031 filterset = filtersets.PowerPortTemplateFilterSet
2032 viewname = 'dcim:moduletype_powerports'
2033 tab = ViewTab(
2034 label=_('Power Ports'),
2035 badge=lambda obj: obj.power_port_template_count,
2036 permission='dcim.view_powerporttemplate',
2037 weight=550,
2038 hide_if_empty=True
2039 )
2042@register_model_view(ModuleType, 'poweroutlets', path='power-outlets')
2043class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
2044 child_model = PowerOutletTemplate
2045 table = tables.PowerOutletTemplateTable
2046 filterset = filtersets.PowerOutletTemplateFilterSet
2047 viewname = 'dcim:moduletype_poweroutlets'
2048 tab = ViewTab(
2049 label=_('Power Outlets'),
2050 badge=lambda obj: obj.power_outlet_template_count,
2051 permission='dcim.view_poweroutlettemplate',
2052 weight=560,
2053 hide_if_empty=True
2054 )
2057@register_model_view(ModuleType, 'coolingintakes', path='cooling-intakes')
2058class ModuleTypeCoolingIntakesView(ModuleTypeComponentsView):
2059 child_model = CoolingIntakeTemplate
2060 table = tables.CoolingIntakeTemplateTable
2061 filterset = filtersets.CoolingIntakeTemplateFilterSet
2062 viewname = 'dcim:moduletype_coolingintakes'
2063 tab = ViewTab(
2064 label=_('Cooling Intakes'),
2065 badge=lambda obj: obj.cooling_intake_template_count,
2066 permission='dcim.view_coolingintaketemplate',
2067 weight=570,
2068 hide_if_empty=True
2069 )
2072@register_model_view(ModuleType, 'coolingoutflows', path='cooling-outflows')
2073class ModuleTypeCoolingOutflowsView(ModuleTypeComponentsView):
2074 child_model = CoolingOutflowTemplate
2075 table = tables.CoolingOutflowTemplateTable
2076 filterset = filtersets.CoolingOutflowTemplateFilterSet
2077 viewname = 'dcim:moduletype_coolingoutflows'
2078 tab = ViewTab(
2079 label=_('Cooling Outflows'),
2080 badge=lambda obj: obj.cooling_outflow_template_count,
2081 permission='dcim.view_coolingoutflowtemplate',
2082 weight=580,
2083 hide_if_empty=True
2084 )
2087@register_model_view(ModuleType, 'interfaces')
2088class ModuleTypeInterfacesView(ModuleTypeComponentsView):
2089 child_model = InterfaceTemplate
2090 table = tables.InterfaceTemplateTable
2091 filterset = filtersets.InterfaceTemplateFilterSet
2092 viewname = 'dcim:moduletype_interfaces'
2093 tab = ViewTab(
2094 label=_('Interfaces'),
2095 badge=lambda obj: obj.interface_template_count,
2096 permission='dcim.view_interfacetemplate',
2097 weight=500,
2098 hide_if_empty=True
2099 )
2102@register_model_view(ModuleType, 'frontports', path='front-ports')
2103class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
2104 child_model = FrontPortTemplate
2105 table = tables.FrontPortTemplateTable
2106 filterset = filtersets.FrontPortTemplateFilterSet
2107 viewname = 'dcim:moduletype_frontports'
2108 tab = ViewTab(
2109 label=_('Front Ports'),
2110 badge=lambda obj: obj.front_port_template_count,
2111 permission='dcim.view_frontporttemplate',
2112 weight=510,
2113 hide_if_empty=True
2114 )
2117@register_model_view(ModuleType, 'rearports', path='rear-ports')
2118class ModuleTypeRearPortsView(ModuleTypeComponentsView):
2119 child_model = RearPortTemplate
2120 table = tables.RearPortTemplateTable
2121 filterset = filtersets.RearPortTemplateFilterSet
2122 viewname = 'dcim:moduletype_rearports'
2123 tab = ViewTab(
2124 label=_('Rear Ports'),
2125 badge=lambda obj: obj.rear_port_template_count,
2126 permission='dcim.view_rearporttemplate',
2127 weight=520,
2128 hide_if_empty=True
2129 )
2132@register_model_view(ModuleType, 'modulebays', path='module-bays')
2133class ModuleTypeModuleBaysView(ModuleTypeComponentsView):
2134 child_model = ModuleBayTemplate
2135 table = tables.ModuleBayTemplateTable
2136 filterset = filtersets.ModuleBayTemplateFilterSet
2137 viewname = 'dcim:moduletype_modulebays'
2138 tab = ViewTab(
2139 label=_('Module Bays'),
2140 badge=lambda obj: obj.module_bay_template_count,
2141 permission='dcim.view_modulebaytemplate',
2142 weight=590,
2143 hide_if_empty=True
2144 )
2147@register_model_view(ModuleType, 'bulk_import', path='import', detail=False)
2148class ModuleTypeImportView(generic.BulkImportView):
2149 additional_permissions = [
2150 'dcim.add_moduletype',
2151 'dcim.add_consoleporttemplate',
2152 'dcim.add_consoleserverporttemplate',
2153 'dcim.add_powerporttemplate',
2154 'dcim.add_poweroutlettemplate',
2155 'dcim.add_coolingintaketemplate',
2156 'dcim.add_coolingoutflowtemplate',
2157 'dcim.add_interfacetemplate',
2158 'dcim.add_frontporttemplate',
2159 'dcim.add_rearporttemplate',
2160 'dcim.add_modulebaytemplate',
2161 ]
2162 queryset = ModuleType.objects.all()
2163 model_form = forms.ModuleTypeImportForm
2164 related_object_forms = {
2165 'console-ports': forms.ConsolePortTemplateImportForm,
2166 'console-server-ports': forms.ConsoleServerPortTemplateImportForm,
2167 'power-ports': forms.PowerPortTemplateImportForm,
2168 'power-outlets': forms.PowerOutletTemplateImportForm,
2169 'cooling-intakes': forms.CoolingIntakeTemplateImportForm,
2170 'cooling-outflows': forms.CoolingOutflowTemplateImportForm,
2171 'interfaces': forms.InterfaceTemplateImportForm,
2172 'rear-ports': forms.RearPortTemplateImportForm,
2173 'front-ports': forms.FrontPortTemplateImportForm,
2174 'port-mappings': forms.PortTemplateMappingImportForm,
2175 'module-bays': forms.ModuleBayTemplateImportForm,
2176 }
2178 def prep_related_object_data(self, parent, data):
2179 data.update({'module_type': parent})
2180 return data
2183@register_model_view(ModuleType, 'bulk_edit', path='edit', detail=False)
2184class ModuleTypeBulkEditView(generic.BulkEditView):
2185 queryset = ModuleType.objects.annotate(
2186 instance_count=count_related(Module, 'module_type')
2187 )
2188 filterset = filtersets.ModuleTypeFilterSet
2189 table = tables.ModuleTypeTable
2190 form = forms.ModuleTypeBulkEditForm
2192 def post_save_operations(self, form, obj):
2193 super().post_save_operations(form, obj)
2194 add = form.cleaned_data.get('add_module_bay_types')
2195 remove = form.cleaned_data.get('remove_module_bay_types')
2196 if add:
2197 obj.module_bay_types.add(*add)
2198 if remove:
2199 obj.module_bay_types.remove(*remove)
2200 if add or remove:
2201 # Counts current incompatibilities, not just newly-introduced ones; may over-warn
2202 # if pre-existing incompatibilities exist, but safe to under-warn on.
2203 self._incompatible_count += obj.get_incompatible_modules().count()
2205 def post(self, request, **kwargs):
2206 self._incompatible_count = 0
2207 response = super().post(request, **kwargs)
2208 if self._incompatible_count and getattr(response, 'status_code', None) == 302:
2209 messages.warning(
2210 request,
2211 ngettext(
2212 '%(count)d installed module is now incompatible with its module bay '
2213 'due to conflicting bay type constraints.',
2214 '%(count)d installed modules are now incompatible with their module bays '
2215 'due to conflicting bay type constraints.',
2216 self._incompatible_count,
2217 ) % {'count': self._incompatible_count}
2218 )
2219 return response
2222@register_model_view(ModuleType, 'bulk_rename', path='rename', detail=False)
2223class ModuleTypeBulkRenameView(generic.BulkRenameView):
2224 queryset = ModuleType.objects.all()
2225 filterset = filtersets.ModuleTypeFilterSet
2226 field_name = 'model'
2229@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
2230class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
2231 queryset = ModuleType.objects.annotate(
2232 instance_count=count_related(Module, 'module_type')
2233 )
2234 filterset = filtersets.ModuleTypeFilterSet
2235 table = tables.ModuleTypeTable
2238#
2239# Console port templates
2240#
2242@register_model_view(ConsolePortTemplate, 'add', detail=False)
2243class ConsolePortTemplateCreateView(generic.ComponentCreateView):
2244 queryset = ConsolePortTemplate.objects.all()
2245 form = forms.ConsolePortTemplateCreateForm
2246 model_form = forms.ConsolePortTemplateForm
2249@register_model_view(ConsolePortTemplate, 'edit')
2250class ConsolePortTemplateEditView(generic.ObjectEditView):
2251 queryset = ConsolePortTemplate.objects.all()
2252 form = forms.ConsolePortTemplateForm
2255@register_model_view(ConsolePortTemplate, 'delete')
2256class ConsolePortTemplateDeleteView(generic.ObjectDeleteView):
2257 queryset = ConsolePortTemplate.objects.all()
2260@register_model_view(ConsolePortTemplate, 'bulk_edit', path='edit', detail=False)
2261class ConsolePortTemplateBulkEditView(generic.BulkEditView):
2262 queryset = ConsolePortTemplate.objects.all()
2263 table = tables.ConsolePortTemplateTable
2264 form = forms.ConsolePortTemplateBulkEditForm
2267@register_model_view(ConsolePortTemplate, 'bulk_rename', path='rename', detail=False)
2268class ConsolePortTemplateBulkRenameView(generic.BulkRenameView):
2269 queryset = ConsolePortTemplate.objects.all()
2270 rename_fields = ('name', 'label')
2273@register_model_view(ConsolePortTemplate, 'bulk_delete', path='delete', detail=False)
2274class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
2275 queryset = ConsolePortTemplate.objects.all()
2276 table = tables.ConsolePortTemplateTable
2279#
2280# Console server port templates
2281#
2283@register_model_view(ConsoleServerPortTemplate, 'add', detail=False)
2284class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
2285 queryset = ConsoleServerPortTemplate.objects.all()
2286 form = forms.ConsoleServerPortTemplateCreateForm
2287 model_form = forms.ConsoleServerPortTemplateForm
2290@register_model_view(ConsoleServerPortTemplate, 'edit')
2291class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
2292 queryset = ConsoleServerPortTemplate.objects.all()
2293 form = forms.ConsoleServerPortTemplateForm
2296@register_model_view(ConsoleServerPortTemplate, 'delete')
2297class ConsoleServerPortTemplateDeleteView(generic.ObjectDeleteView):
2298 queryset = ConsoleServerPortTemplate.objects.all()
2301@register_model_view(ConsoleServerPortTemplate, 'bulk_edit', path='edit', detail=False)
2302class ConsoleServerPortTemplateBulkEditView(generic.BulkEditView):
2303 queryset = ConsoleServerPortTemplate.objects.all()
2304 table = tables.ConsoleServerPortTemplateTable
2305 form = forms.ConsoleServerPortTemplateBulkEditForm
2308@register_model_view(ConsoleServerPortTemplate, 'bulk_rename', detail=False)
2309class ConsoleServerPortTemplateBulkRenameView(generic.BulkRenameView):
2310 queryset = ConsoleServerPortTemplate.objects.all()
2311 rename_fields = ('name', 'label')
2314@register_model_view(ConsoleServerPortTemplate, 'bulk_delete', path='delete', detail=False)
2315class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
2316 queryset = ConsoleServerPortTemplate.objects.all()
2317 table = tables.ConsoleServerPortTemplateTable
2320#
2321# Power port templates
2322#
2324@register_model_view(PowerPortTemplate, 'add', detail=False)
2325class PowerPortTemplateCreateView(generic.ComponentCreateView):
2326 queryset = PowerPortTemplate.objects.all()
2327 form = forms.PowerPortTemplateCreateForm
2328 model_form = forms.PowerPortTemplateForm
2331@register_model_view(PowerPortTemplate, 'edit')
2332class PowerPortTemplateEditView(generic.ObjectEditView):
2333 queryset = PowerPortTemplate.objects.all()
2334 form = forms.PowerPortTemplateForm
2337@register_model_view(PowerPortTemplate, 'delete')
2338class PowerPortTemplateDeleteView(generic.ObjectDeleteView):
2339 queryset = PowerPortTemplate.objects.all()
2342@register_model_view(PowerPortTemplate, 'bulk_edit', path='edit', detail=False)
2343class PowerPortTemplateBulkEditView(generic.BulkEditView):
2344 queryset = PowerPortTemplate.objects.all()
2345 table = tables.PowerPortTemplateTable
2346 form = forms.PowerPortTemplateBulkEditForm
2349@register_model_view(PowerPortTemplate, 'bulk_rename', path='rename', detail=False)
2350class PowerPortTemplateBulkRenameView(generic.BulkRenameView):
2351 queryset = PowerPortTemplate.objects.all()
2352 rename_fields = ('name', 'label')
2355@register_model_view(PowerPortTemplate, 'bulk_delete', path='delete', detail=False)
2356class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
2357 queryset = PowerPortTemplate.objects.all()
2358 table = tables.PowerPortTemplateTable
2361#
2362# Power outlet templates
2363#
2365@register_model_view(PowerOutletTemplate, 'add', detail=False)
2366class PowerOutletTemplateCreateView(generic.ComponentCreateView):
2367 queryset = PowerOutletTemplate.objects.all()
2368 form = forms.PowerOutletTemplateCreateForm
2369 model_form = forms.PowerOutletTemplateForm
2372@register_model_view(PowerOutletTemplate, 'edit')
2373class PowerOutletTemplateEditView(generic.ObjectEditView):
2374 queryset = PowerOutletTemplate.objects.all()
2375 form = forms.PowerOutletTemplateForm
2378@register_model_view(PowerOutletTemplate, 'delete')
2379class PowerOutletTemplateDeleteView(generic.ObjectDeleteView):
2380 queryset = PowerOutletTemplate.objects.all()
2383@register_model_view(PowerOutletTemplate, 'bulk_edit', path='edit', detail=False)
2384class PowerOutletTemplateBulkEditView(generic.BulkEditView):
2385 queryset = PowerOutletTemplate.objects.all()
2386 table = tables.PowerOutletTemplateTable
2387 form = forms.PowerOutletTemplateBulkEditForm
2390@register_model_view(PowerOutletTemplate, 'bulk_rename', path='rename', detail=False)
2391class PowerOutletTemplateBulkRenameView(generic.BulkRenameView):
2392 queryset = PowerOutletTemplate.objects.all()
2393 rename_fields = ('name', 'label')
2396@register_model_view(PowerOutletTemplate, 'bulk_delete', path='delete', detail=False)
2397class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
2398 queryset = PowerOutletTemplate.objects.all()
2399 table = tables.PowerOutletTemplateTable
2402#
2403# Cooling port templates
2404#
2406@register_model_view(CoolingIntakeTemplate, 'add', detail=False)
2407class CoolingIntakeTemplateCreateView(generic.ComponentCreateView):
2408 queryset = CoolingIntakeTemplate.objects.all()
2409 form = forms.CoolingIntakeTemplateCreateForm
2410 model_form = forms.CoolingIntakeTemplateForm
2413@register_model_view(CoolingIntakeTemplate, 'edit')
2414class CoolingIntakeTemplateEditView(generic.ObjectEditView):
2415 queryset = CoolingIntakeTemplate.objects.all()
2416 form = forms.CoolingIntakeTemplateForm
2419@register_model_view(CoolingIntakeTemplate, 'delete')
2420class CoolingIntakeTemplateDeleteView(generic.ObjectDeleteView):
2421 queryset = CoolingIntakeTemplate.objects.all()
2424@register_model_view(CoolingIntakeTemplate, 'bulk_edit', path='edit', detail=False)
2425class CoolingIntakeTemplateBulkEditView(generic.BulkEditView):
2426 queryset = CoolingIntakeTemplate.objects.all()
2427 table = tables.CoolingIntakeTemplateTable
2428 form = forms.CoolingIntakeTemplateBulkEditForm
2431@register_model_view(CoolingIntakeTemplate, 'bulk_rename', path='rename', detail=False)
2432class CoolingIntakeTemplateBulkRenameView(generic.BulkRenameView):
2433 queryset = CoolingIntakeTemplate.objects.all()
2434 rename_fields = ('name', 'label')
2437@register_model_view(CoolingIntakeTemplate, 'bulk_delete', path='delete', detail=False)
2438class CoolingIntakeTemplateBulkDeleteView(generic.BulkDeleteView):
2439 queryset = CoolingIntakeTemplate.objects.all()
2440 table = tables.CoolingIntakeTemplateTable
2443#
2444# Cooling outlet templates
2445#
2447@register_model_view(CoolingOutflowTemplate, 'add', detail=False)
2448class CoolingOutflowTemplateCreateView(generic.ComponentCreateView):
2449 queryset = CoolingOutflowTemplate.objects.all()
2450 form = forms.CoolingOutflowTemplateCreateForm
2451 model_form = forms.CoolingOutflowTemplateForm
2454@register_model_view(CoolingOutflowTemplate, 'edit')
2455class CoolingOutflowTemplateEditView(generic.ObjectEditView):
2456 queryset = CoolingOutflowTemplate.objects.all()
2457 form = forms.CoolingOutflowTemplateForm
2460@register_model_view(CoolingOutflowTemplate, 'delete')
2461class CoolingOutflowTemplateDeleteView(generic.ObjectDeleteView):
2462 queryset = CoolingOutflowTemplate.objects.all()
2465@register_model_view(CoolingOutflowTemplate, 'bulk_edit', path='edit', detail=False)
2466class CoolingOutflowTemplateBulkEditView(generic.BulkEditView):
2467 queryset = CoolingOutflowTemplate.objects.all()
2468 table = tables.CoolingOutflowTemplateTable
2469 form = forms.CoolingOutflowTemplateBulkEditForm
2472@register_model_view(CoolingOutflowTemplate, 'bulk_rename', path='rename', detail=False)
2473class CoolingOutflowTemplateBulkRenameView(generic.BulkRenameView):
2474 queryset = CoolingOutflowTemplate.objects.all()
2475 rename_fields = ('name', 'label')
2478@register_model_view(CoolingOutflowTemplate, 'bulk_delete', path='delete', detail=False)
2479class CoolingOutflowTemplateBulkDeleteView(generic.BulkDeleteView):
2480 queryset = CoolingOutflowTemplate.objects.all()
2481 table = tables.CoolingOutflowTemplateTable
2484#
2485# Interface templates
2486#
2488@register_model_view(InterfaceTemplate, 'add', detail=False)
2489class InterfaceTemplateCreateView(generic.ComponentCreateView):
2490 queryset = InterfaceTemplate.objects.all()
2491 form = forms.InterfaceTemplateCreateForm
2492 model_form = forms.InterfaceTemplateForm
2495@register_model_view(InterfaceTemplate, 'edit')
2496class InterfaceTemplateEditView(generic.ObjectEditView):
2497 queryset = InterfaceTemplate.objects.all()
2498 form = forms.InterfaceTemplateForm
2501@register_model_view(InterfaceTemplate, 'delete')
2502class InterfaceTemplateDeleteView(generic.ObjectDeleteView):
2503 queryset = InterfaceTemplate.objects.all()
2506@register_model_view(InterfaceTemplate, 'bulk_edit', path='edit', detail=False)
2507class InterfaceTemplateBulkEditView(generic.BulkEditView):
2508 queryset = InterfaceTemplate.objects.all()
2509 table = tables.InterfaceTemplateTable
2510 form = forms.InterfaceTemplateBulkEditForm
2513@register_model_view(InterfaceTemplate, 'bulk_rename', path='rename', detail=False)
2514class InterfaceTemplateBulkRenameView(generic.BulkRenameView):
2515 queryset = InterfaceTemplate.objects.all()
2516 rename_fields = ('name', 'label')
2519@register_model_view(InterfaceTemplate, 'bulk_delete', path='delete', detail=False)
2520class InterfaceTemplateBulkDeleteView(generic.BulkDeleteView):
2521 queryset = InterfaceTemplate.objects.all()
2522 table = tables.InterfaceTemplateTable
2525#
2526# Front port templates
2527#
2529@register_model_view(FrontPortTemplate, 'add', detail=False)
2530class FrontPortTemplateCreateView(generic.ComponentCreateView):
2531 queryset = FrontPortTemplate.objects.all()
2532 form = forms.FrontPortTemplateCreateForm
2533 model_form = forms.FrontPortTemplateForm
2536@register_model_view(FrontPortTemplate, 'edit')
2537class FrontPortTemplateEditView(generic.ObjectEditView):
2538 queryset = FrontPortTemplate.objects.all()
2539 form = forms.FrontPortTemplateForm
2542@register_model_view(FrontPortTemplate, 'delete')
2543class FrontPortTemplateDeleteView(generic.ObjectDeleteView):
2544 queryset = FrontPortTemplate.objects.all()
2547@register_model_view(FrontPortTemplate, 'bulk_edit', path='edit', detail=False)
2548class FrontPortTemplateBulkEditView(generic.BulkEditView):
2549 queryset = FrontPortTemplate.objects.all()
2550 table = tables.FrontPortTemplateTable
2551 form = forms.FrontPortTemplateBulkEditForm
2554@register_model_view(FrontPortTemplate, 'bulk_rename', path='rename', detail=False)
2555class FrontPortTemplateBulkRenameView(generic.BulkRenameView):
2556 queryset = FrontPortTemplate.objects.all()
2557 rename_fields = ('name', 'label')
2560@register_model_view(FrontPortTemplate, 'bulk_delete', path='delete', detail=False)
2561class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
2562 queryset = FrontPortTemplate.objects.all()
2563 table = tables.FrontPortTemplateTable
2566#
2567# Rear port templates
2568#
2570@register_model_view(RearPortTemplate, 'add', detail=False)
2571class RearPortTemplateCreateView(generic.ComponentCreateView):
2572 queryset = RearPortTemplate.objects.all()
2573 form = forms.RearPortTemplateCreateForm
2574 model_form = forms.RearPortTemplateForm
2577@register_model_view(RearPortTemplate, 'edit')
2578class RearPortTemplateEditView(generic.ObjectEditView):
2579 queryset = RearPortTemplate.objects.all()
2580 form = forms.RearPortTemplateForm
2583@register_model_view(RearPortTemplate, 'delete')
2584class RearPortTemplateDeleteView(generic.ObjectDeleteView):
2585 queryset = RearPortTemplate.objects.all()
2588@register_model_view(RearPortTemplate, 'bulk_edit', path='edit', detail=False)
2589class RearPortTemplateBulkEditView(generic.BulkEditView):
2590 queryset = RearPortTemplate.objects.all()
2591 table = tables.RearPortTemplateTable
2592 form = forms.RearPortTemplateBulkEditForm
2595@register_model_view(RearPortTemplate, 'bulk_rename', path='rename', detail=False)
2596class RearPortTemplateBulkRenameView(generic.BulkRenameView):
2597 queryset = RearPortTemplate.objects.all()
2598 rename_fields = ('name', 'label')
2601@register_model_view(RearPortTemplate, 'bulk_delete', path='delete', detail=False)
2602class RearPortTemplateBulkDeleteView(generic.BulkDeleteView):
2603 queryset = RearPortTemplate.objects.all()
2604 table = tables.RearPortTemplateTable
2607#
2608# Module bay templates
2609#
2611@register_model_view(ModuleBayTemplate, 'add', detail=False)
2612class ModuleBayTemplateCreateView(generic.ComponentCreateView):
2613 queryset = ModuleBayTemplate.objects.all()
2614 form = forms.ModuleBayTemplateCreateForm
2615 model_form = forms.ModuleBayTemplateForm
2618@register_model_view(ModuleBayTemplate, 'edit')
2619class ModuleBayTemplateEditView(generic.ObjectEditView):
2620 queryset = ModuleBayTemplate.objects.all()
2621 form = forms.ModuleBayTemplateForm
2624@register_model_view(ModuleBayTemplate, 'delete')
2625class ModuleBayTemplateDeleteView(generic.ObjectDeleteView):
2626 queryset = ModuleBayTemplate.objects.all()
2629@register_model_view(ModuleBayTemplate, 'bulk_edit', path='edit', detail=False)
2630class ModuleBayTemplateBulkEditView(generic.BulkEditView):
2631 queryset = ModuleBayTemplate.objects.all()
2632 table = tables.ModuleBayTemplateTable
2633 form = forms.ModuleBayTemplateBulkEditForm
2635 def post_save_operations(self, form, obj):
2636 # Unlike the ModuleType and ModuleBay editors, no compatibility warning: a template
2637 # has no installed module to invalidate.
2638 super().post_save_operations(form, obj)
2639 add = form.cleaned_data.get('add_module_bay_types')
2640 remove = form.cleaned_data.get('remove_module_bay_types')
2641 if add:
2642 obj.module_bay_types.add(*add)
2643 if remove:
2644 obj.module_bay_types.remove(*remove)
2647@register_model_view(ModuleBayTemplate, 'bulk_rename', path='rename', detail=False)
2648class ModuleBayTemplateBulkRenameView(generic.BulkRenameView):
2649 queryset = ModuleBayTemplate.objects.all()
2650 rename_fields = ('name', 'label')
2653@register_model_view(ModuleBayTemplate, 'bulk_delete', path='delete', detail=False)
2654class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
2655 queryset = ModuleBayTemplate.objects.all()
2656 table = tables.ModuleBayTemplateTable
2659#
2660# Device bay templates
2661#
2663@register_model_view(DeviceBayTemplate, 'add', detail=False)
2664class DeviceBayTemplateCreateView(generic.ComponentCreateView):
2665 queryset = DeviceBayTemplate.objects.all()
2666 form = forms.DeviceBayTemplateCreateForm
2667 model_form = forms.DeviceBayTemplateForm
2670@register_model_view(DeviceBayTemplate, 'edit')
2671class DeviceBayTemplateEditView(generic.ObjectEditView):
2672 queryset = DeviceBayTemplate.objects.all()
2673 form = forms.DeviceBayTemplateForm
2676@register_model_view(DeviceBayTemplate, 'delete')
2677class DeviceBayTemplateDeleteView(generic.ObjectDeleteView):
2678 queryset = DeviceBayTemplate.objects.all()
2681@register_model_view(DeviceBayTemplate, 'bulk_edit', path='edit', detail=False)
2682class DeviceBayTemplateBulkEditView(generic.BulkEditView):
2683 queryset = DeviceBayTemplate.objects.all()
2684 table = tables.DeviceBayTemplateTable
2685 form = forms.DeviceBayTemplateBulkEditForm
2688@register_model_view(DeviceBayTemplate, 'bulk_rename', path='rename', detail=False)
2689class DeviceBayTemplateBulkRenameView(generic.BulkRenameView):
2690 queryset = DeviceBayTemplate.objects.all()
2691 rename_fields = ('name', 'label')
2694@register_model_view(DeviceBayTemplate, 'bulk_delete', path='delete', detail=False)
2695class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
2696 queryset = DeviceBayTemplate.objects.all()
2697 table = tables.DeviceBayTemplateTable
2700#
2701# Inventory item templates
2702#
2704@register_model_view(InventoryItemTemplate, 'add', detail=False)
2705class InventoryItemTemplateCreateView(generic.ComponentCreateView):
2706 queryset = InventoryItemTemplate.objects.all()
2707 form = forms.InventoryItemTemplateCreateForm
2708 model_form = forms.InventoryItemTemplateForm
2710 def alter_object(self, instance, request):
2711 # Set component (if any)
2712 component_type = request.GET.get('component_type')
2713 component_id = request.GET.get('component_id')
2715 if component_type and component_id:
2716 content_type = get_object_or_404(ContentType, pk=component_type)
2717 instance.component = get_object_or_404(content_type.model_class(), pk=component_id)
2719 return instance
2722@register_model_view(InventoryItemTemplate, 'edit')
2723class InventoryItemTemplateEditView(generic.ObjectEditView):
2724 queryset = InventoryItemTemplate.objects.all()
2725 form = forms.InventoryItemTemplateForm
2728@register_model_view(InventoryItemTemplate, 'delete')
2729class InventoryItemTemplateDeleteView(generic.ObjectDeleteView):
2730 queryset = InventoryItemTemplate.objects.all()
2733@register_model_view(InventoryItemTemplate, 'bulk_edit', path='edit', detail=False)
2734class InventoryItemTemplateBulkEditView(generic.BulkEditView):
2735 queryset = InventoryItemTemplate.objects.all()
2736 table = tables.InventoryItemTemplateTable
2737 form = forms.InventoryItemTemplateBulkEditForm
2740@register_model_view(InventoryItemTemplate, 'bulk_rename', path='rename', detail=False)
2741class InventoryItemTemplateBulkRenameView(generic.BulkRenameView):
2742 queryset = InventoryItemTemplate.objects.all()
2743 rename_fields = ('name', 'label')
2746@register_model_view(InventoryItemTemplate, 'bulk_delete', path='delete', detail=False)
2747class InventoryItemTemplateBulkDeleteView(generic.BulkDeleteView):
2748 queryset = InventoryItemTemplate.objects.all()
2749 table = tables.InventoryItemTemplateTable
2752#
2753# Device roles
2754#
2756@register_model_view(DeviceRole, 'list', path='', detail=False)
2757class DeviceRoleListView(generic.ObjectListView):
2758 queryset = DeviceRole.objects.add_related_count(
2759 DeviceRole.objects.add_related_count(
2760 DeviceRole.objects.all(),
2761 VirtualMachine,
2762 'role',
2763 'vm_count',
2764 cumulative=True
2765 ),
2766 Device,
2767 'role',
2768 'device_count',
2769 cumulative=True
2770 )
2771 filterset = filtersets.DeviceRoleFilterSet
2772 filterset_form = forms.DeviceRoleFilterForm
2773 table = tables.DeviceRoleTable
2776@register_model_view(DeviceRole)
2777class DeviceRoleView(GetRelatedModelsMixin, generic.ObjectView):
2778 queryset = DeviceRole.objects.all()
2779 layout = layout.SimpleLayout(
2780 breadcrumbs=[
2781 Breadcrumb(
2782 lambda o: o.get_ancestors(),
2783 url=filtered_list_url('dcim:devicerole_list', 'parent_id'),
2784 ),
2785 ],
2786 left_panels=[
2787 panels.DeviceRolePanel(),
2788 TagsPanel(),
2789 ],
2790 right_panels=[
2791 RelatedObjectsPanel(),
2792 CustomFieldsPanel(),
2793 CommentsPanel(),
2794 ],
2795 bottom_panels=[
2796 ObjectsTablePanel(
2797 model='dcim.DeviceRole',
2798 title=_('Child Device Roles'),
2799 filters={'parent_id': lambda ctx: ctx['object'].pk},
2800 exclude_columns=['parent'],
2801 actions=[
2802 actions.AddObject('dcim.DeviceRole', url_params={'parent': lambda ctx: ctx['object'].pk}),
2803 ],
2804 ),
2805 ]
2806 )
2808 def get_extra_context(self, request, instance):
2809 return {
2810 'related_models': self.get_related_models(request, instance),
2811 }
2814@register_model_view(DeviceRole, 'add', detail=False)
2815@register_model_view(DeviceRole, 'edit')
2816class DeviceRoleEditView(generic.ObjectEditView):
2817 queryset = DeviceRole.objects.all()
2818 form = forms.DeviceRoleForm
2821@register_model_view(DeviceRole, 'delete')
2822class DeviceRoleDeleteView(generic.ObjectDeleteView):
2823 queryset = DeviceRole.objects.all()
2826@register_model_view(DeviceRole, 'bulk_import', path='import', detail=False)
2827class DeviceRoleBulkImportView(generic.BulkImportView):
2828 queryset = DeviceRole.objects.all()
2829 model_form = forms.DeviceRoleImportForm
2832@register_model_view(DeviceRole, 'bulk_edit', path='edit', detail=False)
2833class DeviceRoleBulkEditView(generic.BulkEditView):
2834 queryset = DeviceRole.objects.annotate(
2835 device_count=count_related(Device, 'role'),
2836 vm_count=count_related(VirtualMachine, 'role')
2837 )
2838 filterset = filtersets.DeviceRoleFilterSet
2839 table = tables.DeviceRoleTable
2840 form = forms.DeviceRoleBulkEditForm
2843@register_model_view(DeviceRole, 'bulk_rename', path='rename', detail=False)
2844class DeviceRoleBulkRenameView(generic.BulkRenameView):
2845 queryset = DeviceRole.objects.all()
2846 filterset = filtersets.DeviceRoleFilterSet
2849@register_model_view(DeviceRole, 'bulk_delete', path='delete', detail=False)
2850class DeviceRoleBulkDeleteView(generic.BulkDeleteView):
2851 queryset = DeviceRole.objects.annotate(
2852 device_count=count_related(Device, 'role'),
2853 vm_count=count_related(VirtualMachine, 'role')
2854 )
2855 filterset = filtersets.DeviceRoleFilterSet
2856 table = tables.DeviceRoleTable
2859#
2860# Platforms
2861#
2863@register_model_view(Platform, 'list', path='', detail=False)
2864class PlatformListView(generic.ObjectListView):
2865 queryset = Platform.objects.add_related_count(
2866 Platform.objects.add_related_count(
2867 Platform.objects.all(),
2868 VirtualMachine,
2869 'platform',
2870 'vm_count',
2871 cumulative=True
2872 ),
2873 Device,
2874 'platform',
2875 'device_count',
2876 cumulative=True
2877 )
2878 table = tables.PlatformTable
2879 filterset = filtersets.PlatformFilterSet
2880 filterset_form = forms.PlatformFilterForm
2883@register_model_view(Platform)
2884class PlatformView(GetRelatedModelsMixin, generic.ObjectView):
2885 queryset = Platform.objects.all()
2886 layout = layout.SimpleLayout(
2887 breadcrumbs=[
2888 Breadcrumb('manufacturer', url=filtered_list_url('dcim:platform_list', 'manufacturer_id')),
2889 Breadcrumb(
2890 lambda o: o.get_ancestors(),
2891 url=filtered_list_url('dcim:platform_list', 'parent_id'),
2892 ),
2893 ],
2894 left_panels=[
2895 panels.PlatformPanel(),
2896 TagsPanel(),
2897 ],
2898 right_panels=[
2899 RelatedObjectsPanel(),
2900 CustomFieldsPanel(),
2901 CommentsPanel(),
2902 ],
2903 bottom_panels=[
2904 ObjectsTablePanel(
2905 model='dcim.Platform',
2906 title=_('Child Platforms'),
2907 filters={'parent_id': lambda ctx: ctx['object'].pk},
2908 exclude_columns=['parent'],
2909 actions=[
2910 actions.AddObject('dcim.Platform', url_params={'parent': lambda ctx: ctx['object'].pk}),
2911 ],
2912 ),
2913 ]
2914 )
2916 def get_extra_context(self, request, instance):
2917 return {
2918 'related_models': self.get_related_models(request, instance),
2919 }
2922@register_model_view(Platform, 'add', detail=False)
2923@register_model_view(Platform, 'edit')
2924class PlatformEditView(generic.ObjectEditView):
2925 queryset = Platform.objects.all()
2926 form = forms.PlatformForm
2929@register_model_view(Platform, 'delete')
2930class PlatformDeleteView(generic.ObjectDeleteView):
2931 queryset = Platform.objects.all()
2934@register_model_view(Platform, 'bulk_import', path='import', detail=False)
2935class PlatformBulkImportView(generic.BulkImportView):
2936 queryset = Platform.objects.all()
2937 model_form = forms.PlatformImportForm
2940@register_model_view(Platform, 'bulk_edit', path='edit', detail=False)
2941class PlatformBulkEditView(generic.BulkEditView):
2942 queryset = Platform.objects.all()
2943 filterset = filtersets.PlatformFilterSet
2944 table = tables.PlatformTable
2945 form = forms.PlatformBulkEditForm
2948@register_model_view(Platform, 'bulk_rename', path='rename', detail=False)
2949class PlatformBulkRenameView(generic.BulkRenameView):
2950 queryset = Platform.objects.all()
2951 filterset = filtersets.PlatformFilterSet
2954@register_model_view(Platform, 'bulk_delete', path='delete', detail=False)
2955class PlatformBulkDeleteView(generic.BulkDeleteView):
2956 queryset = Platform.objects.all()
2957 filterset = filtersets.PlatformFilterSet
2958 table = tables.PlatformTable
2961#
2962# Devices
2963#
2965@register_model_view(Device, 'list', path='', detail=False)
2966class DeviceListView(generic.ObjectListView):
2967 queryset = Device.objects.select_related('virtual_chassis')
2968 filterset = filtersets.DeviceFilterSet
2969 filterset_form = forms.DeviceFilterForm
2970 table = tables.DeviceTable
2971 actions = (AddObject, BulkImport, BulkExport, BulkAddComponents, BulkEdit, BulkRename, BulkDelete)
2974@register_model_view(Device)
2975class DeviceView(generic.ObjectView):
2976 queryset = Device.objects.all()
2977 layout = layout.SimpleLayout(
2978 left_panels=[
2979 panels.DevicePanel(),
2980 panels.VirtualChassisMembersPanel(),
2981 CustomFieldsPanel(),
2982 TagsPanel(),
2983 CommentsPanel(),
2984 ObjectsTablePanel(
2985 model='dcim.VirtualDeviceContext',
2986 filters={'device_id': lambda ctx: ctx['object'].pk},
2987 exclude_columns=['device'],
2988 actions=[
2989 actions.AddObject('dcim.VirtualDeviceContext', url_params={'device': lambda ctx: ctx['object'].pk}),
2990 ],
2991 ),
2992 ],
2993 right_panels=[
2994 panels.DeviceManagementPanel(),
2995 panels.PowerUtilizationPanel(),
2996 ObjectsTablePanel(
2997 model='ipam.Service',
2998 title=_('Application Services'),
2999 filters={'device_id': lambda ctx: ctx['object'].pk},
3000 exclude_columns=['parent'],
3001 actions=[
3002 actions.AddObject(
3003 'ipam.Service',
3004 url_params={
3005 'parent_content_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
3006 'parent_object_id': lambda ctx: ctx['object'].pk
3007 }
3008 ),
3009 ],
3010 ),
3011 ImageAttachmentsPanel(),
3012 panels.DeviceDeviceTypePanel(),
3013 panels.DeviceDimensionsPanel(),
3014 TemplatePanel('dcim/panels/device_rack_elevations.html'),
3015 ],
3016 )
3018 def get_extra_context(self, request, instance):
3019 # VirtualChassis members
3020 if instance.virtual_chassis is not None:
3021 vc_members = Device.objects.restrict(request.user, 'view').filter(
3022 virtual_chassis=instance.virtual_chassis
3023 ).order_by('vc_position')
3024 else:
3025 vc_members = []
3027 return {
3028 'virtual_chassis': instance.virtual_chassis,
3029 'vc_members': vc_members,
3030 'svg_extra': f'highlight=id:{instance.pk}',
3031 }
3034@register_model_view(Device, 'add', detail=False)
3035@register_model_view(Device, 'edit')
3036class DeviceEditView(generic.ObjectEditView):
3037 queryset = Device.objects.all()
3038 form = forms.DeviceForm
3039 template_name = 'dcim/device_edit.html'
3042@register_model_view(Device, 'delete')
3043class DeviceDeleteView(generic.ObjectDeleteView):
3044 queryset = Device.objects.all()
3047@register_model_view(Device, 'consoleports', path='console-ports')
3048class DeviceConsolePortsView(DeviceComponentsView):
3049 child_model = ConsolePort
3050 table = tables.DeviceConsolePortTable
3051 filterset = filtersets.ConsolePortFilterSet
3052 filterset_form = forms.ConsolePortFilterForm
3053 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3054 tab = ViewTab(
3055 label=_('Console Ports'),
3056 badge=lambda obj: obj.console_port_count,
3057 permission='dcim.view_consoleport',
3058 weight=550,
3059 hide_if_empty=True
3060 )
3063@register_model_view(Device, 'consoleserverports', path='console-server-ports')
3064class DeviceConsoleServerPortsView(DeviceComponentsView):
3065 child_model = ConsoleServerPort
3066 table = tables.DeviceConsoleServerPortTable
3067 filterset = filtersets.ConsoleServerPortFilterSet
3068 filterset_form = forms.ConsoleServerPortFilterForm
3069 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3070 tab = ViewTab(
3071 label=_('Console Server Ports'),
3072 badge=lambda obj: obj.console_server_port_count,
3073 permission='dcim.view_consoleserverport',
3074 weight=560,
3075 hide_if_empty=True
3076 )
3079@register_model_view(Device, 'powerports', path='power-ports')
3080class DevicePowerPortsView(DeviceComponentsView):
3081 child_model = PowerPort
3082 table = tables.DevicePowerPortTable
3083 filterset = filtersets.PowerPortFilterSet
3084 filterset_form = forms.PowerPortFilterForm
3085 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3086 tab = ViewTab(
3087 label=_('Power Ports'),
3088 badge=lambda obj: obj.power_port_count,
3089 permission='dcim.view_powerport',
3090 weight=570,
3091 hide_if_empty=True
3092 )
3095@register_model_view(Device, 'poweroutlets', path='power-outlets')
3096class DevicePowerOutletsView(DeviceComponentsView):
3097 child_model = PowerOutlet
3098 table = tables.DevicePowerOutletTable
3099 filterset = filtersets.PowerOutletFilterSet
3100 filterset_form = forms.PowerOutletFilterForm
3101 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3102 tab = ViewTab(
3103 label=_('Power Outlets'),
3104 badge=lambda obj: obj.power_outlet_count,
3105 permission='dcim.view_poweroutlet',
3106 weight=580,
3107 hide_if_empty=True
3108 )
3111@register_model_view(Device, 'coolingintakes', path='cooling-intakes')
3112class DeviceCoolingIntakesView(DeviceComponentsView):
3113 child_model = CoolingIntake
3114 table = tables.DeviceCoolingIntakeTable
3115 filterset = filtersets.CoolingIntakeFilterSet
3116 filterset_form = forms.CoolingIntakeFilterForm
3117 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
3118 tab = ViewTab(
3119 label=_('Cooling Intakes'),
3120 badge=lambda obj: obj.cooling_intake_count,
3121 permission='dcim.view_coolingintake',
3122 weight=590,
3123 hide_if_empty=True
3124 )
3127@register_model_view(Device, 'coolingoutflows', path='cooling-outflows')
3128class DeviceCoolingOutflowsView(DeviceComponentsView):
3129 child_model = CoolingOutflow
3130 table = tables.DeviceCoolingOutflowTable
3131 filterset = filtersets.CoolingOutflowFilterSet
3132 filterset_form = forms.CoolingOutflowFilterForm
3133 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
3134 tab = ViewTab(
3135 label=_('Cooling Outflows'),
3136 badge=lambda obj: obj.cooling_outflow_count,
3137 permission='dcim.view_coolingoutflow',
3138 weight=600,
3139 hide_if_empty=True
3140 )
3143@register_model_view(Device, 'interfaces')
3144class DeviceInterfacesView(DeviceComponentsView):
3145 child_model = Interface
3146 table = tables.DeviceInterfaceTable
3147 filterset = filtersets.InterfaceFilterSet
3148 filterset_form = forms.InterfaceFilterForm
3149 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3150 template_name = 'dcim/device/interfaces.html'
3151 tab = ViewTab(
3152 label=_('Interfaces'),
3153 badge=lambda obj: obj.vc_interfaces().count(),
3154 permission='dcim.view_interface',
3155 weight=520,
3156 hide_if_empty=True
3157 )
3159 def get_children(self, request, parent):
3160 return parent.vc_interfaces().restrict(request.user, 'view').prefetch_related(
3161 Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
3162 Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user))
3163 )
3166@register_model_view(Device, 'frontports', path='front-ports')
3167class DeviceFrontPortsView(DeviceComponentsView):
3168 child_model = FrontPort
3169 table = tables.DeviceFrontPortTable
3170 filterset = filtersets.FrontPortFilterSet
3171 filterset_form = forms.FrontPortFilterForm
3172 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3173 tab = ViewTab(
3174 label=_('Front Ports'),
3175 badge=lambda obj: obj.front_port_count,
3176 permission='dcim.view_frontport',
3177 weight=530,
3178 hide_if_empty=True
3179 )
3182@register_model_view(Device, 'rearports', path='rear-ports')
3183class DeviceRearPortsView(DeviceComponentsView):
3184 child_model = RearPort
3185 table = tables.DeviceRearPortTable
3186 filterset = filtersets.RearPortFilterSet
3187 filterset_form = forms.RearPortFilterForm
3188 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
3189 tab = ViewTab(
3190 label=_('Rear Ports'),
3191 badge=lambda obj: obj.rear_port_count,
3192 permission='dcim.view_rearport',
3193 weight=540,
3194 hide_if_empty=True
3195 )
3198@register_model_view(Device, 'modulebays', path='module-bays')
3199class DeviceModuleBaysView(DeviceComponentsView):
3200 child_model = ModuleBay
3201 table = tables.DeviceModuleBayTable
3202 filterset = filtersets.ModuleBayFilterSet
3203 filterset_form = forms.ModuleBayFilterForm
3204 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
3205 tab = ViewTab(
3206 label=_('Module Bays'),
3207 badge=lambda obj: obj.module_bay_count,
3208 permission='dcim.view_modulebay',
3209 weight=510,
3210 hide_if_empty=True
3211 )
3214@register_model_view(Device, 'devicebays', path='device-bays')
3215class DeviceDeviceBaysView(DeviceComponentsView):
3216 child_model = DeviceBay
3217 table = tables.DeviceDeviceBayTable
3218 filterset = filtersets.DeviceBayFilterSet
3219 filterset_form = forms.DeviceBayFilterForm
3220 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
3221 tab = ViewTab(
3222 label=_('Device Bays'),
3223 badge=lambda obj: obj.device_bay_count,
3224 permission='dcim.view_devicebay',
3225 weight=500,
3226 hide_if_empty=True
3227 )
3230@register_model_view(Device, 'inventory')
3231class DeviceInventoryView(DeviceComponentsView):
3232 child_model = InventoryItem
3233 table = tables.DeviceInventoryItemTable
3234 filterset = filtersets.InventoryItemFilterSet
3235 filterset_form = forms.InventoryItemFilterForm
3236 actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
3237 tab = ViewTab(
3238 label=_('Inventory Items'),
3239 badge=lambda obj: obj.inventory_item_count,
3240 permission='dcim.view_inventoryitem',
3241 weight=610,
3242 hide_if_empty=True
3243 )
3245 def get_children(self, request, parent):
3246 # DeviceInventoryItemTable indents rows by record.level; under MPTT,
3247 # TreeManager forced tree-flatten (tree_id, lft) ordering so descendants
3248 # were contiguous with their parent. InventoryItem.Meta.ordering is a
3249 # flat sort suited to the global list; for the indented device tab,
3250 # order by ltree path so the rendered hierarchy is correct.
3251 return super().get_children(request, parent).order_by('path')
3254@register_model_view(Device, 'configcontext', path='config-context')
3255class DeviceConfigContextView(ObjectConfigContextView):
3256 queryset = Device.objects.all()
3257 base_template = 'dcim/device/base.html'
3258 tab = ViewTab(
3259 label=_('Config Context'),
3260 weight=2000
3261 )
3264@register_model_view(Device, 'render-config')
3265class DeviceRenderConfigView(ObjectRenderConfigView):
3266 queryset = Device.objects.all()
3267 base_template = 'dcim/device/base.html'
3268 additional_permissions = ['dcim.render_config_device']
3269 tab = ViewTab(
3270 label=_('Render Config'),
3271 weight=2100,
3272 )
3275@register_model_view(Device, 'virtual-machines')
3276class DeviceVirtualMachinesView(generic.ObjectChildrenView):
3277 queryset = Device.objects.all()
3278 child_model = VirtualMachine
3279 table = VirtualMachineTable
3280 filterset = VirtualMachineFilterSet
3281 filterset_form = VirtualMachineFilterForm
3282 tab = ViewTab(
3283 label=_('Virtual Machines'),
3284 badge=lambda obj: VirtualMachine.objects.filter(cluster=obj.cluster, device=obj).count(),
3285 weight=2200,
3286 hide_if_empty=True,
3287 permission='virtualization.view_virtualmachine'
3288 )
3290 def get_children(self, request, parent):
3291 return self.child_model.objects.restrict(request.user, 'view').filter(cluster=parent.cluster, device=parent)
3294@register_model_view(Device, 'bulk_import', path='import', detail=False)
3295class DeviceBulkImportView(generic.BulkImportView):
3296 queryset = Device.objects.all()
3297 model_form = forms.DeviceImportForm
3299 def save_object(self, object_form, request):
3300 parent_bay = getattr(object_form.instance, 'parent_bay', None)
3301 obj = object_form.save()
3303 # For child devices, save the reverse relation to the parent device bay
3304 if parent_bay:
3305 device_bay = parent_bay
3306 device_bay.snapshot()
3307 device_bay.installed_device = obj
3308 device_bay.save()
3310 return obj
3313@register_model_view(Device, 'bulk_edit', path='edit', detail=False)
3314class DeviceBulkEditView(generic.BulkEditView):
3315 queryset = Device.objects.prefetch_related('device_type__manufacturer')
3316 filterset = filtersets.DeviceFilterSet
3317 table = tables.DeviceTable
3318 form = forms.DeviceBulkEditForm
3321@register_model_view(Device, 'bulk_rename', path='rename', detail=False)
3322class DeviceBulkRenameView(generic.BulkRenameView):
3323 queryset = Device.objects.all()
3324 filterset = filtersets.DeviceFilterSet
3325 table = tables.DeviceTable
3328@register_model_view(Device, 'bulk_delete', path='delete', detail=False)
3329class DeviceBulkDeleteView(generic.BulkDeleteView):
3330 queryset = Device.objects.prefetch_related('device_type__manufacturer')
3331 filterset = filtersets.DeviceFilterSet
3332 table = tables.DeviceTable
3335#
3336# Modules
3337#
3339@register_model_view(Module, 'list', path='', detail=False)
3340class ModuleListView(generic.ObjectListView):
3341 queryset = Module.objects.prefetch_related('module_type__manufacturer')
3342 filterset = filtersets.ModuleFilterSet
3343 filterset_form = forms.ModuleFilterForm
3344 table = tables.ModuleTable
3345 actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
3348@register_model_view(Module)
3349class ModuleView(GetRelatedModelsMixin, generic.ObjectView):
3350 queryset = Module.objects.prefetch_related(
3351 'module_bay__module_bay_types',
3352 'module_type__module_bay_types',
3353 )
3354 layout = layout.SimpleLayout(
3355 breadcrumbs=[
3356 Breadcrumb('module_type', url=filtered_list_url('dcim:module_list', 'module_type_id')),
3357 ],
3358 left_panels=[
3359 panels.BayTypeIncompatibilityPanel(),
3360 panels.ModulePanel(),
3361 TagsPanel(),
3362 CommentsPanel(),
3363 ],
3364 right_panels=[
3365 TemplatePanel(
3366 title=_('Module Type'),
3367 template_name='dcim/panels/module_type.html',
3368 ),
3369 RelatedObjectsPanel(),
3370 CustomFieldsPanel(),
3371 ],
3372 )
3374 def get_extra_context(self, request, instance):
3375 return {
3376 'related_models': self.get_related_models(request, instance),
3377 }
3380@register_model_view(Module, 'add', detail=False)
3381@register_model_view(Module, 'edit')
3382class ModuleEditView(generic.ObjectEditView):
3383 queryset = Module.objects.all()
3384 form = forms.ModuleForm
3387@register_model_view(Module, 'delete')
3388class ModuleDeleteView(generic.ObjectDeleteView):
3389 queryset = Module.objects.all()
3392@register_model_view(Module, 'bulk_import', path='import', detail=False)
3393class ModuleBulkImportView(generic.BulkImportView):
3394 queryset = Module.objects.all()
3395 model_form = forms.ModuleImportForm
3398@register_model_view(Module, 'bulk_edit', path='edit', detail=False)
3399class ModuleBulkEditView(generic.BulkEditView):
3400 queryset = Module.objects.prefetch_related('module_type__manufacturer')
3401 filterset = filtersets.ModuleFilterSet
3402 table = tables.ModuleTable
3403 form = forms.ModuleBulkEditForm
3406@register_model_view(Module, 'bulk_delete', path='delete', detail=False)
3407class ModuleBulkDeleteView(generic.BulkDeleteView):
3408 queryset = Module.objects.prefetch_related('module_type__manufacturer')
3409 filterset = filtersets.ModuleFilterSet
3410 table = tables.ModuleTable
3413#
3414# Console ports
3415#
3417@register_model_view(ConsolePort, 'list', path='', detail=False)
3418class ConsolePortListView(generic.ObjectListView):
3419 queryset = ConsolePort.objects.all()
3420 filterset = filtersets.ConsolePortFilterSet
3421 filterset_form = forms.ConsolePortFilterForm
3422 table = tables.ConsolePortTable
3425@register_model_view(ConsolePort)
3426class ConsolePortView(generic.ObjectView):
3427 template_name = 'generic/object.html'
3428 queryset = ConsolePort.objects.all()
3429 layout = layout.SimpleLayout(
3430 breadcrumbs=[
3431 Breadcrumb('device', url=object_view_url('dcim:device_consoleports')),
3432 ],
3433 left_panels=[
3434 panels.ConsolePortPanel(),
3435 CustomFieldsPanel(),
3436 TagsPanel(),
3437 ],
3438 right_panels=[
3439 panels.ConnectionPanel(
3440 trace_url_name='dcim:consoleport_trace',
3441 connect_options=[
3442 {
3443 'a_type': 'dcim.consoleport',
3444 'b_type': 'dcim.consoleserverport',
3445 'label': _('Console Server Port'),
3446 },
3447 {'a_type': 'dcim.consoleport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
3448 {'a_type': 'dcim.consoleport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
3449 ],
3450 ),
3451 panels.InventoryItemsPanel(),
3452 ],
3453 )
3456@register_model_view(ConsolePort, 'add', detail=False)
3457class ConsolePortCreateView(generic.ComponentCreateView):
3458 queryset = ConsolePort.objects.all()
3459 form = forms.ConsolePortCreateForm
3460 model_form = forms.ConsolePortForm
3463@register_model_view(ConsolePort, 'edit')
3464class ConsolePortEditView(generic.ObjectEditView):
3465 queryset = ConsolePort.objects.all()
3466 form = forms.ConsolePortForm
3469@register_model_view(ConsolePort, 'delete')
3470class ConsolePortDeleteView(generic.ObjectDeleteView):
3471 queryset = ConsolePort.objects.all()
3474@register_model_view(ConsolePort, 'bulk_import', path='import', detail=False)
3475class ConsolePortBulkImportView(generic.BulkImportView):
3476 queryset = ConsolePort.objects.all()
3477 model_form = forms.ConsolePortImportForm
3480@register_model_view(ConsolePort, 'bulk_edit', path='edit', detail=False)
3481class ConsolePortBulkEditView(generic.BulkEditView):
3482 queryset = ConsolePort.objects.all()
3483 filterset = filtersets.ConsolePortFilterSet
3484 table = tables.ConsolePortTable
3485 form = forms.ConsolePortBulkEditForm
3488@register_model_view(ConsolePort, 'bulk_rename', path='rename', detail=False)
3489class ConsolePortBulkRenameView(generic.BulkRenameView):
3490 queryset = ConsolePort.objects.all()
3491 filterset = filtersets.ConsolePortFilterSet
3492 rename_fields = ('name', 'label')
3495@register_model_view(ConsolePort, 'bulk_disconnect', path='disconnect', detail=False)
3496class ConsolePortBulkDisconnectView(BulkDisconnectView):
3497 queryset = ConsolePort.objects.all()
3500@register_model_view(ConsolePort, 'bulk_delete', path='delete', detail=False)
3501class ConsolePortBulkDeleteView(generic.BulkDeleteView):
3502 queryset = ConsolePort.objects.all()
3503 filterset = filtersets.ConsolePortFilterSet
3504 table = tables.ConsolePortTable
3507# Trace view
3508register_model_view(ConsolePort, 'trace', kwargs={'model': ConsolePort})(PathTraceView)
3511#
3512# Console server ports
3513#
3515@register_model_view(ConsoleServerPort, 'list', path='', detail=False)
3516class ConsoleServerPortListView(generic.ObjectListView):
3517 queryset = ConsoleServerPort.objects.all()
3518 filterset = filtersets.ConsoleServerPortFilterSet
3519 filterset_form = forms.ConsoleServerPortFilterForm
3520 table = tables.ConsoleServerPortTable
3523@register_model_view(ConsoleServerPort)
3524class ConsoleServerPortView(generic.ObjectView):
3525 template_name = 'generic/object.html'
3526 queryset = ConsoleServerPort.objects.all()
3527 layout = layout.SimpleLayout(
3528 breadcrumbs=[
3529 Breadcrumb('device', url=object_view_url('dcim:device_consoleserverports')),
3530 ],
3531 left_panels=[
3532 panels.ConsoleServerPortPanel(),
3533 CustomFieldsPanel(),
3534 TagsPanel(),
3535 ],
3536 right_panels=[
3537 panels.ConnectionPanel(
3538 trace_url_name='dcim:consoleserverport_trace',
3539 connect_options=[
3540 {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.consoleport', 'label': _('Console Port')},
3541 {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
3542 {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
3543 ],
3544 ),
3545 panels.InventoryItemsPanel(),
3546 ],
3547 )
3550@register_model_view(ConsoleServerPort, 'add', detail=False)
3551class ConsoleServerPortCreateView(generic.ComponentCreateView):
3552 queryset = ConsoleServerPort.objects.all()
3553 form = forms.ConsoleServerPortCreateForm
3554 model_form = forms.ConsoleServerPortForm
3557@register_model_view(ConsoleServerPort, 'edit')
3558class ConsoleServerPortEditView(generic.ObjectEditView):
3559 queryset = ConsoleServerPort.objects.all()
3560 form = forms.ConsoleServerPortForm
3563@register_model_view(ConsoleServerPort, 'delete')
3564class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
3565 queryset = ConsoleServerPort.objects.all()
3568@register_model_view(ConsoleServerPort, 'bulk_import', path='import', detail=False)
3569class ConsoleServerPortBulkImportView(generic.BulkImportView):
3570 queryset = ConsoleServerPort.objects.all()
3571 model_form = forms.ConsoleServerPortImportForm
3574@register_model_view(ConsoleServerPort, 'bulk_edit', path='edit', detail=False)
3575class ConsoleServerPortBulkEditView(generic.BulkEditView):
3576 queryset = ConsoleServerPort.objects.all()
3577 filterset = filtersets.ConsoleServerPortFilterSet
3578 table = tables.ConsoleServerPortTable
3579 form = forms.ConsoleServerPortBulkEditForm
3582@register_model_view(ConsoleServerPort, 'bulk_rename', path='rename', detail=False)
3583class ConsoleServerPortBulkRenameView(generic.BulkRenameView):
3584 queryset = ConsoleServerPort.objects.all()
3585 filterset = filtersets.ConsoleServerPortFilterSet
3586 rename_fields = ('name', 'label')
3589@register_model_view(ConsoleServerPort, 'bulk_disconnect', path='disconnect', detail=False)
3590class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
3591 queryset = ConsoleServerPort.objects.all()
3594@register_model_view(ConsoleServerPort, 'bulk_delete', path='delete', detail=False)
3595class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
3596 queryset = ConsoleServerPort.objects.all()
3597 filterset = filtersets.ConsoleServerPortFilterSet
3598 table = tables.ConsoleServerPortTable
3601# Trace view
3602register_model_view(ConsoleServerPort, 'trace', kwargs={'model': ConsoleServerPort})(PathTraceView)
3605#
3606# Power ports
3607#
3609@register_model_view(PowerPort, 'list', path='', detail=False)
3610class PowerPortListView(generic.ObjectListView):
3611 queryset = PowerPort.objects.all()
3612 filterset = filtersets.PowerPortFilterSet
3613 filterset_form = forms.PowerPortFilterForm
3614 table = tables.PowerPortTable
3617@register_model_view(PowerPort)
3618class PowerPortView(generic.ObjectView):
3619 template_name = 'generic/object.html'
3620 queryset = PowerPort.objects.all()
3621 layout = layout.SimpleLayout(
3622 breadcrumbs=[
3623 Breadcrumb('device', url=object_view_url('dcim:device_powerports')),
3624 ],
3625 left_panels=[
3626 panels.PowerPortPanel(),
3627 CustomFieldsPanel(),
3628 TagsPanel(),
3629 ],
3630 right_panels=[
3631 panels.ConnectionPanel(
3632 trace_url_name='dcim:powerport_trace',
3633 connect_options=[
3634 {'a_type': 'dcim.powerport', 'b_type': 'dcim.poweroutlet', 'label': _('Power Outlet')},
3635 {'a_type': 'dcim.powerport', 'b_type': 'dcim.powerfeed', 'label': _('Power Feed')},
3636 ],
3637 ),
3638 panels.InventoryItemsPanel(),
3639 ],
3640 )
3643@register_model_view(PowerPort, 'add', detail=False)
3644class PowerPortCreateView(generic.ComponentCreateView):
3645 queryset = PowerPort.objects.all()
3646 form = forms.PowerPortCreateForm
3647 model_form = forms.PowerPortForm
3650@register_model_view(PowerPort, 'edit')
3651class PowerPortEditView(generic.ObjectEditView):
3652 queryset = PowerPort.objects.all()
3653 form = forms.PowerPortForm
3656@register_model_view(PowerPort, 'delete')
3657class PowerPortDeleteView(generic.ObjectDeleteView):
3658 queryset = PowerPort.objects.all()
3661@register_model_view(PowerPort, 'bulk_import', path='import', detail=False)
3662class PowerPortBulkImportView(generic.BulkImportView):
3663 queryset = PowerPort.objects.all()
3664 model_form = forms.PowerPortImportForm
3667@register_model_view(PowerPort, 'bulk_edit', path='edit', detail=False)
3668class PowerPortBulkEditView(generic.BulkEditView):
3669 queryset = PowerPort.objects.all()
3670 filterset = filtersets.PowerPortFilterSet
3671 table = tables.PowerPortTable
3672 form = forms.PowerPortBulkEditForm
3675@register_model_view(PowerPort, 'bulk_rename', path='rename', detail=False)
3676class PowerPortBulkRenameView(generic.BulkRenameView):
3677 queryset = PowerPort.objects.all()
3678 filterset = filtersets.PowerPortFilterSet
3679 rename_fields = ('name', 'label')
3682@register_model_view(PowerPort, 'bulk_disconnect', path='disconnect', detail=False)
3683class PowerPortBulkDisconnectView(BulkDisconnectView):
3684 queryset = PowerPort.objects.all()
3687@register_model_view(PowerPort, 'bulk_delete', path='delete', detail=False)
3688class PowerPortBulkDeleteView(generic.BulkDeleteView):
3689 queryset = PowerPort.objects.all()
3690 filterset = filtersets.PowerPortFilterSet
3691 table = tables.PowerPortTable
3694# Trace view
3695register_model_view(PowerPort, 'trace', kwargs={'model': PowerPort})(PathTraceView)
3698#
3699# Power outlets
3700#
3702@register_model_view(PowerOutlet, 'list', path='', detail=False)
3703class PowerOutletListView(generic.ObjectListView):
3704 queryset = PowerOutlet.objects.all()
3705 filterset = filtersets.PowerOutletFilterSet
3706 filterset_form = forms.PowerOutletFilterForm
3707 table = tables.PowerOutletTable
3710@register_model_view(PowerOutlet)
3711class PowerOutletView(generic.ObjectView):
3712 template_name = 'generic/object.html'
3713 queryset = PowerOutlet.objects.all()
3714 layout = layout.SimpleLayout(
3715 breadcrumbs=[
3716 Breadcrumb('device', url=object_view_url('dcim:device_poweroutlets')),
3717 ],
3718 left_panels=[
3719 panels.PowerOutletPanel(),
3720 CustomFieldsPanel(),
3721 TagsPanel(),
3722 ],
3723 right_panels=[
3724 panels.ConnectionPanel(
3725 trace_url_name='dcim:poweroutlet_trace',
3726 connect_options=[
3727 {'a_type': 'dcim.poweroutlet', 'b_type': 'dcim.powerport', 'label': _('Power Port')},
3728 ],
3729 ),
3730 panels.InventoryItemsPanel(),
3731 ],
3732 )
3735@register_model_view(PowerOutlet, 'add', detail=False)
3736class PowerOutletCreateView(generic.ComponentCreateView):
3737 queryset = PowerOutlet.objects.all()
3738 form = forms.PowerOutletCreateForm
3739 model_form = forms.PowerOutletForm
3742@register_model_view(PowerOutlet, 'edit')
3743class PowerOutletEditView(generic.ObjectEditView):
3744 queryset = PowerOutlet.objects.all()
3745 form = forms.PowerOutletForm
3748@register_model_view(PowerOutlet, 'delete')
3749class PowerOutletDeleteView(generic.ObjectDeleteView):
3750 queryset = PowerOutlet.objects.all()
3753@register_model_view(PowerOutlet, 'bulk_import', path='import', detail=False)
3754class PowerOutletBulkImportView(generic.BulkImportView):
3755 queryset = PowerOutlet.objects.all()
3756 model_form = forms.PowerOutletImportForm
3759@register_model_view(PowerOutlet, 'bulk_edit', path='edit', detail=False)
3760class PowerOutletBulkEditView(generic.BulkEditView):
3761 queryset = PowerOutlet.objects.all()
3762 filterset = filtersets.PowerOutletFilterSet
3763 table = tables.PowerOutletTable
3764 form = forms.PowerOutletBulkEditForm
3767@register_model_view(PowerOutlet, 'bulk_rename', path='rename', detail=False)
3768class PowerOutletBulkRenameView(generic.BulkRenameView):
3769 queryset = PowerOutlet.objects.all()
3770 filterset = filtersets.PowerOutletFilterSet
3771 rename_fields = ('name', 'label')
3774@register_model_view(PowerOutlet, 'bulk_disconnect', path='disconnect', detail=False)
3775class PowerOutletBulkDisconnectView(BulkDisconnectView):
3776 queryset = PowerOutlet.objects.all()
3779@register_model_view(PowerOutlet, 'bulk_delete', path='delete', detail=False)
3780class PowerOutletBulkDeleteView(generic.BulkDeleteView):
3781 queryset = PowerOutlet.objects.all()
3782 filterset = filtersets.PowerOutletFilterSet
3783 table = tables.PowerOutletTable
3786# Trace view
3787register_model_view(PowerOutlet, 'trace', kwargs={'model': PowerOutlet})(PathTraceView)
3790#
3791# Cooling ports
3792#
3794@register_model_view(CoolingIntake, 'list', path='', detail=False)
3795class CoolingIntakeListView(generic.ObjectListView):
3796 queryset = CoolingIntake.objects.all()
3797 filterset = filtersets.CoolingIntakeFilterSet
3798 filterset_form = forms.CoolingIntakeFilterForm
3799 table = tables.CoolingIntakeTable
3802@register_model_view(CoolingIntake)
3803class CoolingIntakeView(generic.ObjectView):
3804 queryset = CoolingIntake.objects.all()
3805 template_name = 'generic/object.html'
3806 layout = layout.SimpleLayout(
3807 breadcrumbs=[
3808 Breadcrumb('device', url=object_view_url('dcim:device_coolingintakes')),
3809 ],
3810 left_panels=[
3811 panels.CoolingIntakePanel(),
3812 CustomFieldsPanel(),
3813 TagsPanel(),
3814 ],
3815 right_panels=[
3816 panels.InventoryItemsPanel(),
3817 ],
3818 )
3821@register_model_view(CoolingIntake, 'add', detail=False)
3822class CoolingIntakeCreateView(generic.ComponentCreateView):
3823 queryset = CoolingIntake.objects.all()
3824 form = forms.CoolingIntakeCreateForm
3825 model_form = forms.CoolingIntakeForm
3828@register_model_view(CoolingIntake, 'edit')
3829class CoolingIntakeEditView(generic.ObjectEditView):
3830 queryset = CoolingIntake.objects.all()
3831 form = forms.CoolingIntakeForm
3834@register_model_view(CoolingIntake, 'delete')
3835class CoolingIntakeDeleteView(generic.ObjectDeleteView):
3836 queryset = CoolingIntake.objects.all()
3839@register_model_view(CoolingIntake, 'bulk_import', path='import', detail=False)
3840class CoolingIntakeBulkImportView(generic.BulkImportView):
3841 queryset = CoolingIntake.objects.all()
3842 model_form = forms.CoolingIntakeImportForm
3845@register_model_view(CoolingIntake, 'bulk_edit', path='edit', detail=False)
3846class CoolingIntakeBulkEditView(generic.BulkEditView):
3847 queryset = CoolingIntake.objects.all()
3848 filterset = filtersets.CoolingIntakeFilterSet
3849 table = tables.CoolingIntakeTable
3850 form = forms.CoolingIntakeBulkEditForm
3853@register_model_view(CoolingIntake, 'bulk_rename', path='rename', detail=False)
3854class CoolingIntakeBulkRenameView(generic.BulkRenameView):
3855 queryset = CoolingIntake.objects.all()
3856 filterset = filtersets.CoolingIntakeFilterSet
3857 rename_fields = ('name', 'label')
3860@register_model_view(CoolingIntake, 'bulk_delete', path='delete', detail=False)
3861class CoolingIntakeBulkDeleteView(generic.BulkDeleteView):
3862 queryset = CoolingIntake.objects.all()
3863 filterset = filtersets.CoolingIntakeFilterSet
3864 table = tables.CoolingIntakeTable
3867#
3868# Cooling outlets
3869#
3871@register_model_view(CoolingOutflow, 'list', path='', detail=False)
3872class CoolingOutflowListView(generic.ObjectListView):
3873 queryset = CoolingOutflow.objects.all()
3874 filterset = filtersets.CoolingOutflowFilterSet
3875 filterset_form = forms.CoolingOutflowFilterForm
3876 table = tables.CoolingOutflowTable
3879@register_model_view(CoolingOutflow)
3880class CoolingOutflowView(generic.ObjectView):
3881 queryset = CoolingOutflow.objects.all()
3882 template_name = 'generic/object.html'
3883 layout = layout.SimpleLayout(
3884 breadcrumbs=[
3885 Breadcrumb('device', url=object_view_url('dcim:device_coolingoutflows')),
3886 ],
3887 left_panels=[
3888 panels.CoolingOutflowPanel(),
3889 CustomFieldsPanel(),
3890 TagsPanel(),
3891 ],
3892 right_panels=[
3893 panels.InventoryItemsPanel(),
3894 ],
3895 )
3898@register_model_view(CoolingOutflow, 'add', detail=False)
3899class CoolingOutflowCreateView(generic.ComponentCreateView):
3900 queryset = CoolingOutflow.objects.all()
3901 form = forms.CoolingOutflowCreateForm
3902 model_form = forms.CoolingOutflowForm
3905@register_model_view(CoolingOutflow, 'edit')
3906class CoolingOutflowEditView(generic.ObjectEditView):
3907 queryset = CoolingOutflow.objects.all()
3908 form = forms.CoolingOutflowForm
3911@register_model_view(CoolingOutflow, 'delete')
3912class CoolingOutflowDeleteView(generic.ObjectDeleteView):
3913 queryset = CoolingOutflow.objects.all()
3916@register_model_view(CoolingOutflow, 'bulk_import', path='import', detail=False)
3917class CoolingOutflowBulkImportView(generic.BulkImportView):
3918 queryset = CoolingOutflow.objects.all()
3919 model_form = forms.CoolingOutflowImportForm
3922@register_model_view(CoolingOutflow, 'bulk_edit', path='edit', detail=False)
3923class CoolingOutflowBulkEditView(generic.BulkEditView):
3924 queryset = CoolingOutflow.objects.all()
3925 filterset = filtersets.CoolingOutflowFilterSet
3926 table = tables.CoolingOutflowTable
3927 form = forms.CoolingOutflowBulkEditForm
3930@register_model_view(CoolingOutflow, 'bulk_rename', path='rename', detail=False)
3931class CoolingOutflowBulkRenameView(generic.BulkRenameView):
3932 queryset = CoolingOutflow.objects.all()
3933 filterset = filtersets.CoolingOutflowFilterSet
3934 rename_fields = ('name', 'label')
3937@register_model_view(CoolingOutflow, 'bulk_delete', path='delete', detail=False)
3938class CoolingOutflowBulkDeleteView(generic.BulkDeleteView):
3939 queryset = CoolingOutflow.objects.all()
3940 filterset = filtersets.CoolingOutflowFilterSet
3941 table = tables.CoolingOutflowTable
3944#
3945# Interfaces
3946#
3948@register_model_view(Interface, 'list', path='', detail=False)
3949class InterfaceListView(generic.ObjectListView):
3950 queryset = Interface.objects.all()
3951 filterset = filtersets.InterfaceFilterSet
3952 filterset_form = forms.InterfaceFilterForm
3953 table = tables.InterfaceTable
3956@register_model_view(Interface)
3957class InterfaceView(generic.ObjectView):
3958 queryset = Interface.objects.all()
3959 layout = layout.SimpleLayout(
3960 breadcrumbs=[
3961 Breadcrumb('device', url=object_view_url('dcim:device_interfaces')),
3962 ],
3963 left_panels=[
3964 panels.InterfacePanel(),
3965 panels.RelatedInterfacesPanel(),
3966 CustomFieldsPanel(),
3967 TagsPanel(),
3968 ],
3969 right_panels=[
3970 ContextTablePanel('vdc_table', title=_('Virtual Device Contexts')),
3971 panels.InterfaceAddressingPanel(),
3972 panels.VirtualCircuitPanel(),
3973 panels.InterfaceConnectionPanel(),
3974 panels.InterfaceWirelessPanel(),
3975 panels.WirelessLANsPanel(),
3976 FHRPGroupAssignmentsPanel(),
3977 panels.InventoryItemsPanel(),
3978 ],
3979 bottom_panels=[
3980 ObjectsTablePanel(
3981 model='ipam.IPAddress',
3982 filters={'interface_id': lambda ctx: ctx['object'].pk},
3983 title=_('IP Addresses'),
3984 exclude_columns=['assigned', 'assigned_object', 'assigned_object_parent'],
3985 ),
3986 ObjectsTablePanel(
3987 model='dcim.MACAddress',
3988 filters={'interface_id': lambda ctx: ctx['object'].pk},
3989 title=_('MAC Addresses'),
3990 exclude_columns=['assigned_object', 'assigned_object_parent'],
3991 actions=[
3992 actions.AddObject(
3993 'dcim.MACAddress', url_params={'interface': lambda ctx: ctx['object'].pk}
3994 ),
3995 ],
3996 ),
3997 ObjectsTablePanel(
3998 model='ipam.VLAN',
3999 filters={'interface_id': lambda ctx: ctx['object'].pk},
4000 title=_('VLANs'),
4001 ),
4002 ContextTablePanel('lag_interfaces_table', title=_('LAG Members')),
4003 ContextTablePanel('vlan_translation_table', title=_('VLAN Translation')),
4004 ContextTablePanel('bridge_interfaces_table', title=_('Bridged Interfaces')),
4005 ContextTablePanel('child_interfaces_table', title=_('Child Interfaces')),
4006 ],
4007 )
4009 def get_extra_context(self, request, instance):
4010 # Get assigned VDCs
4011 vdc_table = tables.VirtualDeviceContextTable(
4012 data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'),
4013 exclude=(
4014 'tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'comments', 'tags',
4015 'created', 'last_updated', 'actions',
4016 ),
4017 orderable=False
4018 )
4019 vdc_table.configure(request)
4021 # Get bridge interfaces
4022 bridge_interfaces_table = tables.InterfaceTable(
4023 Interface.objects.restrict(request.user, 'view').filter(bridge=instance),
4024 exclude=('device', 'parent'),
4025 orderable=False
4026 )
4027 bridge_interfaces_table.configure(request)
4029 # Get child interfaces
4030 child_interfaces_table = tables.InterfaceTable(
4031 Interface.objects.restrict(request.user, 'view').filter(parent=instance),
4032 exclude=('device', 'parent'),
4033 orderable=False
4034 )
4035 child_interfaces_table.configure(request)
4037 # Get LAG members (only for LAG interfaces)
4038 lag_interfaces_table = None
4039 if instance.is_lag:
4040 lag_interfaces_table = tables.InterfaceLAGMemberTable(
4041 Interface.objects.restrict(request.user, 'view').filter(lag=instance),
4042 orderable=False
4043 )
4044 lag_interfaces_table.configure(request)
4046 # Get VLAN translation rules
4047 vlan_translation_table = None
4048 if instance.vlan_translation_policy:
4049 vlan_translation_table = VLANTranslationRuleTable(
4050 data=instance.vlan_translation_policy.rules.all(),
4051 orderable=False
4052 )
4053 vlan_translation_table.configure(request)
4055 return {
4056 'vdc_table': vdc_table,
4057 'bridge_interfaces_table': bridge_interfaces_table,
4058 'child_interfaces_table': child_interfaces_table,
4059 'lag_interfaces_table': lag_interfaces_table,
4060 'vlan_translation_table': vlan_translation_table,
4061 }
4064@register_model_view(Interface, 'add', detail=False)
4065class InterfaceCreateView(generic.ComponentCreateView):
4066 queryset = Interface.objects.all()
4067 form = forms.InterfaceCreateForm
4068 model_form = forms.InterfaceForm
4071@register_model_view(Interface, 'edit')
4072class InterfaceEditView(generic.ObjectEditView):
4073 queryset = Interface.objects.all()
4074 form = forms.InterfaceForm
4077@register_model_view(Interface, 'delete')
4078class InterfaceDeleteView(generic.ObjectDeleteView):
4079 queryset = Interface.objects.all()
4082@register_model_view(Interface, 'bulk_import', path='import', detail=False)
4083class InterfaceBulkImportView(generic.BulkImportView):
4084 queryset = Interface.objects.all()
4085 model_form = forms.InterfaceImportForm
4088@register_model_view(Interface, 'bulk_edit', path='edit', detail=False)
4089class InterfaceBulkEditView(generic.BulkEditView):
4090 queryset = Interface.objects.all()
4091 filterset = filtersets.InterfaceFilterSet
4092 table = tables.InterfaceTable
4093 form = forms.InterfaceBulkEditForm
4095 def post_save_operations(self, form, obj):
4096 super().post_save_operations(form, obj)
4098 # Add/remove tagged VLANs
4099 if obj.mode == InterfaceModeChoices.MODE_TAGGED:
4100 if form.cleaned_data.get('add_tagged_vlans', None):
4101 obj.tagged_vlans.add(*form.cleaned_data['add_tagged_vlans'])
4102 if form.cleaned_data.get('remove_tagged_vlans', None):
4103 obj.tagged_vlans.remove(*form.cleaned_data['remove_tagged_vlans'])
4106@register_model_view(Interface, 'bulk_rename', path='rename', detail=False)
4107class InterfaceBulkRenameView(generic.BulkRenameView):
4108 queryset = Interface.objects.all()
4109 filterset = filtersets.InterfaceFilterSet
4110 rename_fields = ('name', 'label')
4113@register_model_view(Interface, 'bulk_disconnect', path='disconnect', detail=False)
4114class InterfaceBulkDisconnectView(BulkDisconnectView):
4115 queryset = Interface.objects.all()
4118@register_model_view(Interface, 'bulk_delete', path='delete', detail=False)
4119class InterfaceBulkDeleteView(generic.BulkDeleteView):
4120 # Ensure child interfaces are deleted prior to their parents
4121 queryset = Interface.objects.order_by('device', 'parent', CollateAsChar('_name'))
4122 filterset = filtersets.InterfaceFilterSet
4123 table = tables.InterfaceTable
4126# Trace view
4127register_model_view(Interface, 'trace', kwargs={'model': Interface})(PathTraceView)
4130#
4131# Front ports
4132#
4134@register_model_view(FrontPort, 'list', path='', detail=False)
4135class FrontPortListView(generic.ObjectListView):
4136 queryset = FrontPort.objects.all()
4137 filterset = filtersets.FrontPortFilterSet
4138 filterset_form = forms.FrontPortFilterForm
4139 table = tables.FrontPortTable
4142@register_model_view(FrontPort)
4143class FrontPortView(generic.ObjectView):
4144 template_name = 'generic/object.html'
4145 queryset = FrontPort.objects.all()
4146 layout = layout.SimpleLayout(
4147 breadcrumbs=[
4148 Breadcrumb('device', url=object_view_url('dcim:device_frontports')),
4149 ],
4150 left_panels=[
4151 panels.FrontPortPanel(),
4152 CustomFieldsPanel(),
4153 TagsPanel(),
4154 panels.InventoryItemsPanel(),
4155 ],
4156 right_panels=[
4157 panels.ConnectionPanel(
4158 trace_url_name='dcim:frontport_trace',
4159 show_endpoints=False,
4160 connect_options=[
4161 {'a_type': 'dcim.frontport', 'b_type': 'dcim.interface', 'label': _('Interface')},
4162 {'a_type': 'dcim.frontport', 'b_type': 'dcim.consoleserverport', 'label': _('Console Server Port')},
4163 {'a_type': 'dcim.frontport', 'b_type': 'dcim.consoleport', 'label': _('Console Port')},
4164 {'a_type': 'dcim.frontport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
4165 {'a_type': 'dcim.frontport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
4166 {
4167 'a_type': 'dcim.frontport',
4168 'b_type': 'circuits.circuittermination',
4169 'label': _('Circuit Termination'),
4170 },
4171 ],
4172 ),
4173 TemplatePanel('dcim/panels/front_port_mappings.html'),
4174 ],
4175 )
4177 def get_extra_context(self, request, instance):
4178 return {
4179 'rear_port_mappings': PortMapping.objects.filter(front_port=instance).prefetch_related('rear_port'),
4180 }
4183@register_model_view(FrontPort, 'add', detail=False)
4184class FrontPortCreateView(generic.ComponentCreateView):
4185 queryset = FrontPort.objects.all()
4186 form = forms.FrontPortCreateForm
4187 model_form = forms.FrontPortForm
4190@register_model_view(FrontPort, 'edit')
4191class FrontPortEditView(generic.ObjectEditView):
4192 queryset = FrontPort.objects.all()
4193 form = forms.FrontPortForm
4196@register_model_view(FrontPort, 'delete')
4197class FrontPortDeleteView(generic.ObjectDeleteView):
4198 queryset = FrontPort.objects.all()
4201@register_model_view(FrontPort, 'bulk_import', path='import', detail=False)
4202class FrontPortBulkImportView(generic.BulkImportView):
4203 queryset = FrontPort.objects.all()
4204 model_form = forms.FrontPortImportForm
4207@register_model_view(FrontPort, 'bulk_edit', path='edit', detail=False)
4208class FrontPortBulkEditView(generic.BulkEditView):
4209 queryset = FrontPort.objects.all()
4210 filterset = filtersets.FrontPortFilterSet
4211 table = tables.FrontPortTable
4212 form = forms.FrontPortBulkEditForm
4215@register_model_view(FrontPort, 'bulk_rename', path='rename', detail=False)
4216class FrontPortBulkRenameView(generic.BulkRenameView):
4217 queryset = FrontPort.objects.all()
4218 filterset = filtersets.FrontPortFilterSet
4219 rename_fields = ('name', 'label')
4222@register_model_view(FrontPort, 'bulk_disconnect', path='disconnect', detail=False)
4223class FrontPortBulkDisconnectView(BulkDisconnectView):
4224 queryset = FrontPort.objects.all()
4227@register_model_view(FrontPort, 'bulk_delete', path='delete', detail=False)
4228class FrontPortBulkDeleteView(generic.BulkDeleteView):
4229 queryset = FrontPort.objects.all()
4230 filterset = filtersets.FrontPortFilterSet
4231 table = tables.FrontPortTable
4234# Trace view
4235register_model_view(FrontPort, 'trace', kwargs={'model': FrontPort})(PathTraceView)
4238#
4239# Rear ports
4240#
4242@register_model_view(RearPort, 'list', path='', detail=False)
4243class RearPortListView(generic.ObjectListView):
4244 queryset = RearPort.objects.all()
4245 filterset = filtersets.RearPortFilterSet
4246 filterset_form = forms.RearPortFilterForm
4247 table = tables.RearPortTable
4250@register_model_view(RearPort)
4251class RearPortView(generic.ObjectView):
4252 template_name = 'generic/object.html'
4253 queryset = RearPort.objects.all()
4254 layout = layout.SimpleLayout(
4255 breadcrumbs=[
4256 Breadcrumb('device', url=object_view_url('dcim:device_rearports')),
4257 ],
4258 left_panels=[
4259 panels.RearPortPanel(),
4260 CustomFieldsPanel(),
4261 TagsPanel(),
4262 panels.InventoryItemsPanel(),
4263 ],
4264 right_panels=[
4265 panels.ConnectionPanel(
4266 trace_url_name='dcim:rearport_trace',
4267 show_endpoints=False,
4268 connect_options=[
4269 {'a_type': 'dcim.rearport', 'b_type': 'dcim.interface', 'label': _('Interface')},
4270 {'a_type': 'dcim.rearport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
4271 {'a_type': 'dcim.rearport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
4272 {
4273 'a_type': 'dcim.rearport',
4274 'b_type': 'circuits.circuittermination',
4275 'label': _('Circuit Termination'),
4276 },
4277 ],
4278 ),
4279 TemplatePanel('dcim/panels/rear_port_mappings.html'),
4280 ],
4281 )
4283 def get_extra_context(self, request, instance):
4284 return {
4285 'front_port_mappings': PortMapping.objects.filter(rear_port=instance).prefetch_related('front_port'),
4286 }
4289@register_model_view(RearPort, 'add', detail=False)
4290class RearPortCreateView(generic.ComponentCreateView):
4291 queryset = RearPort.objects.all()
4292 form = forms.RearPortCreateForm
4293 model_form = forms.RearPortForm
4296@register_model_view(RearPort, 'edit')
4297class RearPortEditView(generic.ObjectEditView):
4298 queryset = RearPort.objects.all()
4299 form = forms.RearPortForm
4302@register_model_view(RearPort, 'delete')
4303class RearPortDeleteView(generic.ObjectDeleteView):
4304 queryset = RearPort.objects.all()
4307@register_model_view(RearPort, 'bulk_import', path='import', detail=False)
4308class RearPortBulkImportView(generic.BulkImportView):
4309 queryset = RearPort.objects.all()
4310 model_form = forms.RearPortImportForm
4313@register_model_view(RearPort, 'bulk_edit', path='edit', detail=False)
4314class RearPortBulkEditView(generic.BulkEditView):
4315 queryset = RearPort.objects.all()
4316 filterset = filtersets.RearPortFilterSet
4317 table = tables.RearPortTable
4318 form = forms.RearPortBulkEditForm
4321@register_model_view(RearPort, 'bulk_rename', path='rename', detail=False)
4322class RearPortBulkRenameView(generic.BulkRenameView):
4323 queryset = RearPort.objects.all()
4324 rename_fields = ('name', 'label')
4327@register_model_view(RearPort, 'bulk_disconnect', path='disconnect', detail=False)
4328class RearPortBulkDisconnectView(BulkDisconnectView):
4329 queryset = RearPort.objects.all()
4330 filterset = filtersets.RearPortFilterSet
4333@register_model_view(RearPort, 'bulk_delete', path='delete', detail=False)
4334class RearPortBulkDeleteView(generic.BulkDeleteView):
4335 queryset = RearPort.objects.all()
4336 filterset = filtersets.RearPortFilterSet
4337 table = tables.RearPortTable
4340# Trace view
4341register_model_view(RearPort, 'trace', kwargs={'model': RearPort})(PathTraceView)
4344#
4345# Module bays
4346#
4348@register_model_view(ModuleBay, 'list', path='', detail=False)
4349class ModuleBayListView(generic.ObjectListView):
4350 queryset = ModuleBay.objects.select_related('installed_module__module_type')
4351 filterset = filtersets.ModuleBayFilterSet
4352 filterset_form = forms.ModuleBayFilterForm
4353 table = tables.ModuleBayTable
4356@register_model_view(ModuleBay)
4357class ModuleBayView(generic.ObjectView):
4358 template_name = 'generic/object.html'
4359 queryset = ModuleBay.objects.prefetch_related(
4360 'module_bay_types',
4361 'installed_module__module_type__module_bay_types',
4362 )
4363 layout = layout.SimpleLayout(
4364 breadcrumbs=[
4365 Breadcrumb('device', url=object_view_url('dcim:device_modulebays')),
4366 ],
4367 left_panels=[
4368 panels.BayTypeIncompatibilityPanel(),
4369 panels.ModuleBayPanel(),
4370 TagsPanel(),
4371 ],
4372 right_panels=[
4373 CustomFieldsPanel(),
4374 panels.InstalledModulePanel(),
4375 ],
4376 )
4379@register_model_view(ModuleBay, 'add', detail=False)
4380class ModuleBayCreateView(generic.ComponentCreateView):
4381 queryset = ModuleBay.objects.all()
4382 form = forms.ModuleBayCreateForm
4383 model_form = forms.ModuleBayForm
4386@register_model_view(ModuleBay, 'edit')
4387class ModuleBayEditView(generic.ObjectEditView):
4388 queryset = ModuleBay.objects.all()
4389 form = forms.ModuleBayForm
4391 def post(self, request, *args, **kwargs):
4392 response = super().post(request, *args, **kwargs)
4393 # A successful save always redirects (302) or returns an HTMX redirect header.
4394 # Quick-add creates new objects (no pk in kwargs), so it is excluded by the pk guard below.
4395 saved = (
4396 getattr(response, 'status_code', None) == 302 or
4397 (hasattr(response, 'headers') and 'HX-Location' in response.headers)
4398 )
4399 if saved and kwargs.get('pk'):
4400 try:
4401 bay = ModuleBay.objects.prefetch_related(
4402 'module_bay_types', 'installed_module__module_type__module_bay_types'
4403 ).get(pk=kwargs['pk'])
4404 if not bay.is_module_compatible:
4405 messages.warning(
4406 request,
4407 _('The module currently installed in this bay is incompatible with the new bay type '
4408 'constraints. Consider removing or replacing it.')
4409 )
4410 except ModuleBay.DoesNotExist:
4411 pass
4412 return response
4415@register_model_view(ModuleBay, 'delete')
4416class ModuleBayDeleteView(generic.ObjectDeleteView):
4417 queryset = ModuleBay.objects.all()
4420@register_model_view(ModuleBay, 'bulk_import', path='import', detail=False)
4421class ModuleBayBulkImportView(generic.BulkImportView):
4422 queryset = ModuleBay.objects.all()
4423 model_form = forms.ModuleBayImportForm
4426@register_model_view(ModuleBay, 'bulk_edit', path='edit', detail=False)
4427class ModuleBayBulkEditView(generic.BulkEditView):
4428 queryset = ModuleBay.objects.all()
4429 filterset = filtersets.ModuleBayFilterSet
4430 table = tables.ModuleBayTable
4431 form = forms.ModuleBayBulkEditForm
4433 def post_save_operations(self, form, obj):
4434 super().post_save_operations(form, obj)
4435 add = form.cleaned_data.get('add_module_bay_types')
4436 remove = form.cleaned_data.get('remove_module_bay_types')
4437 if add:
4438 obj.module_bay_types.add(*add)
4439 if remove:
4440 obj.module_bay_types.remove(*remove)
4441 if add or remove:
4442 # Counts current incompatibilities, not just newly-introduced ones; may over-warn
4443 # if pre-existing incompatibilities exist, but safe to under-warn on.
4444 self._incompatible_count += int(not obj.is_module_compatible)
4446 def post(self, request, **kwargs):
4447 self._incompatible_count = 0
4448 response = super().post(request, **kwargs)
4449 if self._incompatible_count and getattr(response, 'status_code', None) == 302:
4450 messages.warning(
4451 request,
4452 ngettext(
4453 '%(count)d module bay now has an incompatible module installed '
4454 'due to conflicting bay type constraints.',
4455 '%(count)d module bays now have incompatible modules installed '
4456 'due to conflicting bay type constraints.',
4457 self._incompatible_count,
4458 ) % {'count': self._incompatible_count}
4459 )
4460 return response
4463@register_model_view(ModuleBay, 'bulk_rename', path='rename', detail=False)
4464class ModuleBayBulkRenameView(generic.BulkRenameView):
4465 queryset = ModuleBay.objects.all()
4466 filterset = filtersets.ModuleBayFilterSet
4467 rename_fields = ('name', 'label')
4470@register_model_view(ModuleBay, 'bulk_delete', path='delete', detail=False)
4471class ModuleBayBulkDeleteView(generic.BulkDeleteView):
4472 queryset = ModuleBay.objects.all()
4473 filterset = filtersets.ModuleBayFilterSet
4474 table = tables.ModuleBayTable
4477#
4478# Device bays
4479#
4481@register_model_view(DeviceBay, 'list', path='', detail=False)
4482class DeviceBayListView(generic.ObjectListView):
4483 queryset = DeviceBay.objects.all()
4484 filterset = filtersets.DeviceBayFilterSet
4485 filterset_form = forms.DeviceBayFilterForm
4486 table = tables.DeviceBayTable
4489@register_model_view(DeviceBay)
4490class DeviceBayView(generic.ObjectView):
4491 template_name = 'generic/object.html'
4492 queryset = DeviceBay.objects.all()
4493 layout = layout.SimpleLayout(
4494 breadcrumbs=[
4495 Breadcrumb('device', url=object_view_url('dcim:device_devicebays')),
4496 ],
4497 left_panels=[
4498 panels.DeviceBayPanel(),
4499 CustomFieldsPanel(),
4500 TagsPanel(),
4501 ],
4502 right_panels=[
4503 panels.InstalledDevicePanel(),
4504 ],
4505 )
4508@register_model_view(DeviceBay, 'add', detail=False)
4509class DeviceBayCreateView(generic.ComponentCreateView):
4510 queryset = DeviceBay.objects.all()
4511 form = forms.DeviceBayCreateForm
4512 model_form = forms.DeviceBayForm
4515@register_model_view(DeviceBay, 'edit')
4516class DeviceBayEditView(generic.ObjectEditView):
4517 queryset = DeviceBay.objects.all()
4518 form = forms.DeviceBayForm
4521@register_model_view(DeviceBay, 'delete')
4522class DeviceBayDeleteView(generic.ObjectDeleteView):
4523 queryset = DeviceBay.objects.all()
4526@register_model_view(DeviceBay, 'populate')
4527class DeviceBayPopulateView(generic.ObjectEditView):
4528 queryset = DeviceBay.objects.all()
4530 def get(self, request, pk):
4531 device_bay = get_object_or_404(self.queryset, pk=pk)
4532 form = forms.PopulateDeviceBayForm(device_bay)
4534 return render(request, 'dcim/devicebay_populate.html', {
4535 'device_bay': device_bay,
4536 'form': form,
4537 'return_url': self.get_return_url(request, device_bay),
4538 })
4540 def post(self, request, pk):
4541 device_bay = get_object_or_404(self.queryset, pk=pk)
4542 form = forms.PopulateDeviceBayForm(device_bay, request.POST)
4544 if form.is_valid():
4545 device_bay.snapshot()
4546 device_bay.installed_device = form.cleaned_data['installed_device']
4547 device_bay.save()
4548 messages.success(
4549 request,
4550 _("Installed device {device} in bay {device_bay}.").format(
4551 device=device_bay.installed_device,
4552 device_bay=device_bay
4553 )
4554 )
4555 return_url = self.get_return_url(request)
4557 return redirect(return_url)
4559 return render(request, 'dcim/devicebay_populate.html', {
4560 'device_bay': device_bay,
4561 'form': form,
4562 'return_url': self.get_return_url(request, device_bay),
4563 })
4566@register_model_view(DeviceBay, 'depopulate')
4567class DeviceBayDepopulateView(generic.ObjectEditView):
4568 queryset = DeviceBay.objects.all()
4570 def get(self, request, pk):
4571 device_bay = get_object_or_404(self.queryset, pk=pk)
4572 form = ConfirmationForm()
4574 return render(request, 'dcim/devicebay_depopulate.html', {
4575 'device_bay': device_bay,
4576 'form': form,
4577 'return_url': self.get_return_url(request, device_bay),
4578 })
4580 def post(self, request, pk):
4581 device_bay = get_object_or_404(self.queryset, pk=pk)
4582 form = ConfirmationForm(request.POST)
4584 if form.is_valid():
4585 device_bay.snapshot()
4586 removed_device = device_bay.installed_device
4587 device_bay.installed_device = None
4588 device_bay.save()
4589 messages.success(
4590 request,
4591 _("Removed device {device} from bay {device_bay}.").format(
4592 device=removed_device,
4593 device_bay=device_bay
4594 )
4595 )
4596 return_url = self.get_return_url(request, device_bay.device)
4598 return redirect(return_url)
4600 return render(request, 'dcim/devicebay_depopulate.html', {
4601 'device_bay': device_bay,
4602 'form': form,
4603 'return_url': self.get_return_url(request, device_bay),
4604 })
4607@register_model_view(DeviceBay, 'bulk_import', path='import', detail=False)
4608class DeviceBayBulkImportView(generic.BulkImportView):
4609 queryset = DeviceBay.objects.all()
4610 model_form = forms.DeviceBayImportForm
4613@register_model_view(DeviceBay, 'bulk_edit', path='edit', detail=False)
4614class DeviceBayBulkEditView(generic.BulkEditView):
4615 queryset = DeviceBay.objects.all()
4616 filterset = filtersets.DeviceBayFilterSet
4617 table = tables.DeviceBayTable
4618 form = forms.DeviceBayBulkEditForm
4621@register_model_view(DeviceBay, 'bulk_rename', path='rename', detail=False)
4622class DeviceBayBulkRenameView(generic.BulkRenameView):
4623 queryset = DeviceBay.objects.all()
4624 filterset = filtersets.DeviceBayFilterSet
4625 rename_fields = ('name', 'label')
4628@register_model_view(DeviceBay, 'bulk_delete', path='delete', detail=False)
4629class DeviceBayBulkDeleteView(generic.BulkDeleteView):
4630 queryset = DeviceBay.objects.all()
4631 filterset = filtersets.DeviceBayFilterSet
4632 table = tables.DeviceBayTable
4635#
4636# Inventory items
4637#
4639@register_model_view(InventoryItem, 'list', path='', detail=False)
4640class InventoryItemListView(generic.ObjectListView):
4641 queryset = InventoryItem.objects.all()
4642 filterset = filtersets.InventoryItemFilterSet
4643 filterset_form = forms.InventoryItemFilterForm
4644 table = tables.InventoryItemTable
4647@register_model_view(InventoryItem)
4648class InventoryItemView(generic.ObjectView):
4649 template_name = 'generic/object.html'
4650 queryset = InventoryItem.objects.all()
4651 layout = layout.SimpleLayout(
4652 breadcrumbs=[
4653 Breadcrumb('device', url=object_view_url('dcim:device_inventory')),
4654 ],
4655 left_panels=[
4656 panels.InventoryItemPanel(),
4657 CustomFieldsPanel(),
4658 TagsPanel(),
4659 ],
4660 )
4663@register_model_view(InventoryItem, 'edit')
4664class InventoryItemEditView(generic.ObjectEditView):
4665 queryset = InventoryItem.objects.all()
4666 form = forms.InventoryItemForm
4669@register_model_view(InventoryItem, 'add', detail=False)
4670class InventoryItemCreateView(generic.ComponentCreateView):
4671 queryset = InventoryItem.objects.all()
4672 form = forms.InventoryItemCreateForm
4673 model_form = forms.InventoryItemForm
4676@register_model_view(InventoryItem, 'delete')
4677class InventoryItemDeleteView(generic.ObjectDeleteView):
4678 queryset = InventoryItem.objects.all()
4681@register_model_view(InventoryItem, 'bulk_import', path='import', detail=False)
4682class InventoryItemBulkImportView(generic.BulkImportView):
4683 queryset = InventoryItem.objects.all()
4684 model_form = forms.InventoryItemImportForm
4687@register_model_view(InventoryItem, 'bulk_edit', path='edit', detail=False)
4688class InventoryItemBulkEditView(generic.BulkEditView):
4689 queryset = InventoryItem.objects.all()
4690 filterset = filtersets.InventoryItemFilterSet
4691 table = tables.InventoryItemTable
4692 form = forms.InventoryItemBulkEditForm
4695@register_model_view(InventoryItem, 'bulk_rename', path='rename', detail=False)
4696class InventoryItemBulkRenameView(generic.BulkRenameView):
4697 queryset = InventoryItem.objects.all()
4698 filterset = filtersets.InventoryItemFilterSet
4699 rename_fields = ('name', 'label')
4702@register_model_view(InventoryItem, 'bulk_delete', path='delete', detail=False)
4703class InventoryItemBulkDeleteView(generic.BulkDeleteView):
4704 queryset = InventoryItem.objects.all()
4705 filterset = filtersets.InventoryItemFilterSet
4706 table = tables.InventoryItemTable
4709@register_model_view(InventoryItem, 'children')
4710class InventoryItemChildrenView(generic.ObjectChildrenView):
4711 queryset = InventoryItem.objects.all()
4712 child_model = InventoryItem
4713 table = tables.InventoryItemTable
4714 filterset = filtersets.InventoryItemFilterSet
4715 filterset_form = forms.InventoryItemFilterForm
4716 tab = ViewTab(
4717 label=_('Children'),
4718 badge=lambda obj: obj.child_items.count(),
4719 permission='dcim.view_inventoryitem',
4720 hide_if_empty=True,
4721 weight=5000
4722 )
4724 def get_children(self, request, parent):
4725 return parent.child_items.restrict(request.user, 'view')
4728#
4729# Inventory item roles
4730#
4732@register_model_view(InventoryItemRole, 'list', path='', detail=False)
4733class InventoryItemRoleListView(generic.ObjectListView):
4734 queryset = InventoryItemRole.objects.annotate(
4735 inventoryitem_count=count_related(InventoryItem, 'role'),
4736 )
4737 filterset = filtersets.InventoryItemRoleFilterSet
4738 filterset_form = forms.InventoryItemRoleFilterForm
4739 table = tables.InventoryItemRoleTable
4742@register_model_view(InventoryItemRole)
4743class InventoryItemRoleView(GetRelatedModelsMixin, generic.ObjectView):
4744 queryset = InventoryItemRole.objects.all()
4745 template_name = 'generic/object.html'
4746 layout = layout.SimpleLayout(
4747 left_panels=[
4748 panels.InventoryItemRolePanel(),
4749 TagsPanel(),
4750 ],
4751 right_panels=[
4752 RelatedObjectsPanel(),
4753 CustomFieldsPanel(),
4754 CommentsPanel(),
4755 ],
4756 )
4758 def get_extra_context(self, request, instance):
4759 return {
4760 'related_models': self.get_related_models(request, instance),
4761 }
4764@register_model_view(InventoryItemRole, 'add', detail=False)
4765@register_model_view(InventoryItemRole, 'edit')
4766class InventoryItemRoleEditView(generic.ObjectEditView):
4767 queryset = InventoryItemRole.objects.all()
4768 form = forms.InventoryItemRoleForm
4771@register_model_view(InventoryItemRole, 'delete')
4772class InventoryItemRoleDeleteView(generic.ObjectDeleteView):
4773 queryset = InventoryItemRole.objects.all()
4776@register_model_view(InventoryItemRole, 'bulk_import', path='import', detail=False)
4777class InventoryItemRoleBulkImportView(generic.BulkImportView):
4778 queryset = InventoryItemRole.objects.all()
4779 model_form = forms.InventoryItemRoleImportForm
4782@register_model_view(InventoryItemRole, 'bulk_edit', path='edit', detail=False)
4783class InventoryItemRoleBulkEditView(generic.BulkEditView):
4784 queryset = InventoryItemRole.objects.annotate(
4785 inventoryitem_count=count_related(InventoryItem, 'role'),
4786 )
4787 filterset = filtersets.InventoryItemRoleFilterSet
4788 table = tables.InventoryItemRoleTable
4789 form = forms.InventoryItemRoleBulkEditForm
4792@register_model_view(InventoryItemRole, 'bulk_rename', path='rename', detail=False)
4793class InventoryItemRoleBulkRenameView(generic.BulkRenameView):
4794 queryset = InventoryItemRole.objects.all()
4795 filterset = filtersets.InventoryItemRoleFilterSet
4798@register_model_view(InventoryItemRole, 'bulk_delete', path='delete', detail=False)
4799class InventoryItemRoleBulkDeleteView(generic.BulkDeleteView):
4800 queryset = InventoryItemRole.objects.annotate(
4801 inventoryitem_count=count_related(InventoryItem, 'role'),
4802 )
4803 filterset = filtersets.InventoryItemRoleFilterSet
4804 table = tables.InventoryItemRoleTable
4807#
4808# Bulk Device component creation
4809#
4811class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView):
4812 parent_model = Device
4813 parent_field = 'device'
4814 form = forms.ConsolePortBulkCreateForm
4815 queryset = ConsolePort.objects.all()
4816 model_form = forms.ConsolePortForm
4817 filterset = filtersets.DeviceFilterSet
4818 table = tables.DeviceTable
4819 default_return_url = 'dcim:device_list'
4822class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView):
4823 parent_model = Device
4824 parent_field = 'device'
4825 form = forms.ConsoleServerPortBulkCreateForm
4826 queryset = ConsoleServerPort.objects.all()
4827 model_form = forms.ConsoleServerPortForm
4828 filterset = filtersets.DeviceFilterSet
4829 table = tables.DeviceTable
4830 default_return_url = 'dcim:device_list'
4833class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView):
4834 parent_model = Device
4835 parent_field = 'device'
4836 form = forms.PowerPortBulkCreateForm
4837 queryset = PowerPort.objects.all()
4838 model_form = forms.PowerPortForm
4839 filterset = filtersets.DeviceFilterSet
4840 table = tables.DeviceTable
4841 default_return_url = 'dcim:device_list'
4844class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView):
4845 parent_model = Device
4846 parent_field = 'device'
4847 form = forms.PowerOutletBulkCreateForm
4848 queryset = PowerOutlet.objects.all()
4849 model_form = forms.PowerOutletForm
4850 filterset = filtersets.DeviceFilterSet
4851 table = tables.DeviceTable
4852 default_return_url = 'dcim:device_list'
4855class DeviceBulkAddCoolingIntakeView(generic.BulkComponentCreateView):
4856 parent_model = Device
4857 parent_field = 'device'
4858 form = forms.CoolingIntakeBulkCreateForm
4859 queryset = CoolingIntake.objects.all()
4860 model_form = forms.CoolingIntakeForm
4861 filterset = filtersets.DeviceFilterSet
4862 table = tables.DeviceTable
4863 default_return_url = 'dcim:device_list'
4866class DeviceBulkAddCoolingOutflowView(generic.BulkComponentCreateView):
4867 parent_model = Device
4868 parent_field = 'device'
4869 form = forms.CoolingOutflowBulkCreateForm
4870 queryset = CoolingOutflow.objects.all()
4871 model_form = forms.CoolingOutflowForm
4872 filterset = filtersets.DeviceFilterSet
4873 table = tables.DeviceTable
4874 default_return_url = 'dcim:device_list'
4877class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
4878 parent_model = Device
4879 parent_field = 'device'
4880 form = forms.InterfaceBulkCreateForm
4881 queryset = Interface.objects.all()
4882 model_form = forms.InterfaceForm
4883 filterset = filtersets.DeviceFilterSet
4884 table = tables.DeviceTable
4885 default_return_url = 'dcim:device_list'
4888class DeviceBulkAddRearPortView(generic.BulkComponentCreateView):
4889 parent_model = Device
4890 parent_field = 'device'
4891 form = forms.RearPortBulkCreateForm
4892 queryset = RearPort.objects.all()
4893 model_form = forms.RearPortForm
4894 filterset = filtersets.DeviceFilterSet
4895 table = tables.DeviceTable
4896 default_return_url = 'dcim:device_list'
4899class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
4900 parent_model = Device
4901 parent_field = 'device'
4902 form = forms.ModuleBayBulkCreateForm
4903 queryset = ModuleBay.objects.all()
4904 model_form = forms.ModuleBayForm
4905 filterset = filtersets.DeviceFilterSet
4906 table = tables.DeviceTable
4907 default_return_url = 'dcim:device_list'
4910class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
4911 parent_model = Device
4912 parent_field = 'device'
4913 form = forms.DeviceBayBulkCreateForm
4914 queryset = DeviceBay.objects.all()
4915 model_form = forms.DeviceBayForm
4916 filterset = filtersets.DeviceFilterSet
4917 table = tables.DeviceTable
4918 default_return_url = 'dcim:device_list'
4921class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
4922 parent_model = Device
4923 parent_field = 'device'
4924 form = forms.InventoryItemBulkCreateForm
4925 queryset = InventoryItem.objects.all()
4926 model_form = forms.InventoryItemForm
4927 filterset = filtersets.DeviceFilterSet
4928 table = tables.DeviceTable
4929 default_return_url = 'dcim:device_list'
4932#
4933# Cable bundles
4934#
4936@register_model_view(CableBundle, 'list', path='', detail=False)
4937class CableBundleListView(generic.ObjectListView):
4938 queryset = CableBundle.objects.annotate(
4939 cable_count=count_related(Cable, 'bundle')
4940 )
4941 filterset = filtersets.CableBundleFilterSet
4942 filterset_form = forms.CableBundleFilterForm
4943 table = tables.CableBundleTable
4946@register_model_view(CableBundle)
4947class CableBundleView(generic.ObjectView):
4948 queryset = CableBundle.objects.all()
4950 def get_extra_context(self, request, instance):
4951 cables_table = tables.CableTable(
4952 instance.cables.all().prefetch_related(
4953 'terminations__termination', 'terminations___device', 'terminations___rack', 'terminations___location',
4954 'terminations___site',
4955 ),
4956 orderable=False,
4957 )
4958 cables_table.configure(request)
4960 return {
4961 'cables_table': cables_table,
4962 }
4965@register_model_view(CableBundle, 'add', detail=False)
4966@register_model_view(CableBundle, 'edit')
4967class CableBundleEditView(generic.ObjectEditView):
4968 queryset = CableBundle.objects.all()
4969 form = forms.CableBundleForm
4972@register_model_view(CableBundle, 'delete')
4973class CableBundleDeleteView(generic.ObjectDeleteView):
4974 queryset = CableBundle.objects.all()
4977@register_model_view(CableBundle, 'bulk_import', path='import', detail=False)
4978class CableBundleBulkImportView(generic.BulkImportView):
4979 queryset = CableBundle.objects.all()
4980 model_form = forms.CableBundleImportForm
4983@register_model_view(CableBundle, 'bulk_edit', path='edit', detail=False)
4984class CableBundleBulkEditView(generic.BulkEditView):
4985 queryset = CableBundle.objects.all()
4986 filterset = filtersets.CableBundleFilterSet
4987 table = tables.CableBundleTable
4988 form = forms.CableBundleBulkEditForm
4991@register_model_view(CableBundle, 'bulk_delete', path='delete', detail=False)
4992class CableBundleBulkDeleteView(generic.BulkDeleteView):
4993 queryset = CableBundle.objects.all()
4994 filterset = filtersets.CableBundleFilterSet
4995 table = tables.CableBundleTable
4998#
4999# Cables
5000#
5002@register_model_view(Cable, 'list', path='', detail=False)
5003class CableListView(generic.ObjectListView):
5004 queryset = Cable.objects.prefetch_related(
5005 'terminations__termination', 'terminations___device', 'terminations___rack', 'terminations___location',
5006 'terminations___site',
5007 )
5008 filterset = filtersets.CableFilterSet
5009 filterset_form = forms.CableFilterForm
5010 table = tables.CableTable
5013@register_model_view(Cable)
5014class CableView(generic.ObjectView):
5015 queryset = Cable.objects.all()
5016 template_name = 'generic/object.html'
5017 layout = layout.SimpleLayout(
5018 left_panels=[
5019 panels.CablePanel(),
5020 CustomFieldsPanel(),
5021 TagsPanel(),
5022 CommentsPanel(),
5023 ],
5024 right_panels=[
5025 TemplatePanel(
5026 title=_('Termination A'),
5027 template_name='dcim/panels/cable_termination_a.html',
5028 ),
5029 TemplatePanel(
5030 title=_('Termination B'),
5031 template_name='dcim/panels/cable_termination_b.html',
5032 ),
5033 ],
5034 )
5037@register_model_view(Cable, 'add', detail=False)
5038@register_model_view(Cable, 'edit')
5039class CableEditView(generic.ObjectEditView):
5040 queryset = Cable.objects.all()
5041 template_name = 'dcim/cable_edit.html'
5042 htmx_template_name = 'dcim/htmx/cable_edit.html'
5044 def alter_object(self, obj, request, url_args, url_kwargs):
5045 """
5046 Hack into alter_object() to set the form class when editing an existing Cable, since ObjectEditView
5047 doesn't currently provide a hook for dynamic class resolution.
5048 """
5049 a_terminations_type = CABLE_TERMINATION_TYPES.get(
5050 request.POST.get('a_terminations_type') or request.GET.get('a_terminations_type')
5051 )
5052 b_terminations_type = CABLE_TERMINATION_TYPES.get(
5053 request.POST.get('b_terminations_type') or request.GET.get('b_terminations_type')
5054 )
5056 if obj.pk:
5057 if not a_terminations_type and (termination_a := obj.terminations.filter(cable_end='A').first()):
5058 a_terminations_type = termination_a.termination._meta.model
5059 if not b_terminations_type and (termination_b := obj.terminations.filter(cable_end='B').first()):
5060 b_terminations_type = termination_b.termination._meta.model
5062 self.form = forms.get_cable_form(a_terminations_type, b_terminations_type)
5064 return super().alter_object(obj, request, url_args, url_kwargs)
5067@register_model_view(Cable, 'delete')
5068class CableDeleteView(generic.ObjectDeleteView):
5069 queryset = Cable.objects.all()
5072@register_model_view(Cable, 'bulk_import', path='import', detail=False)
5073class CableBulkImportView(generic.BulkImportView):
5074 queryset = Cable.objects.all()
5075 model_form = forms.CableImportForm
5078@register_model_view(Cable, 'bulk_edit', path='edit', detail=False)
5079class CableBulkEditView(generic.BulkEditView):
5080 queryset = Cable.objects.prefetch_related(
5081 'terminations__termination', 'terminations___device', 'terminations___rack', 'terminations___location',
5082 'terminations___site',
5083 )
5084 filterset = filtersets.CableFilterSet
5085 table = tables.CableTable
5086 form = forms.CableBulkEditForm
5089@register_model_view(Cable, 'bulk_rename', path='rename', detail=False)
5090class CableBulkRenameView(generic.BulkRenameView):
5091 queryset = Cable.objects.all()
5092 field_name = 'label'
5093 filterset = filtersets.CableFilterSet
5096@register_model_view(Cable, 'bulk_delete', path='delete', detail=False)
5097class CableBulkDeleteView(generic.BulkDeleteView):
5098 queryset = Cable.objects.prefetch_related(
5099 'terminations__termination', 'terminations___device', 'terminations___rack', 'terminations___location',
5100 'terminations___site',
5101 )
5102 filterset = filtersets.CableFilterSet
5103 table = tables.CableTable
5106#
5107# Connections
5108#
5110class ConsoleConnectionsListView(generic.ObjectListView):
5111 queryset = ConsolePort.objects.filter(_path__is_complete=True)
5112 filterset = filtersets.ConsoleConnectionFilterSet
5113 filterset_form = forms.ConsoleConnectionFilterForm
5114 table = tables.ConsoleConnectionTable
5115 template_name = 'dcim/connections_list.html'
5116 actions = (BulkExport,)
5118 def get_extra_context(self, request):
5119 return {
5120 'title': 'Console Connections'
5121 }
5124class PowerConnectionsListView(generic.ObjectListView):
5125 queryset = PowerPort.objects.filter(_path__is_complete=True)
5126 filterset = filtersets.PowerConnectionFilterSet
5127 filterset_form = forms.PowerConnectionFilterForm
5128 table = tables.PowerConnectionTable
5129 template_name = 'dcim/connections_list.html'
5130 actions = (BulkExport,)
5132 def get_extra_context(self, request):
5133 return {
5134 'title': 'Power Connections'
5135 }
5138class InterfaceConnectionsListView(generic.ObjectListView):
5139 queryset = Interface.objects.filter(_path__is_complete=True)
5140 filterset = filtersets.InterfaceConnectionFilterSet
5141 filterset_form = forms.InterfaceConnectionFilterForm
5142 table = tables.InterfaceConnectionTable
5143 template_name = 'dcim/connections_list.html'
5144 actions = (BulkExport,)
5146 def get_extra_context(self, request):
5147 return {
5148 'title': 'Interface Connections'
5149 }
5152#
5153# Virtual chassis
5154#
5156@register_model_view(VirtualChassis, 'list', path='', detail=False)
5157class VirtualChassisListView(generic.ObjectListView):
5158 queryset = VirtualChassis.objects.all()
5159 table = tables.VirtualChassisTable
5160 filterset = filtersets.VirtualChassisFilterSet
5161 filterset_form = forms.VirtualChassisFilterForm
5164@register_model_view(VirtualChassis)
5165class VirtualChassisView(generic.ObjectView):
5166 queryset = VirtualChassis.objects.all()
5167 template_name = 'generic/object.html'
5168 layout = layout.SimpleLayout(
5169 left_panels=[
5170 panels.VirtualChassisPanel(),
5171 TagsPanel(),
5172 CustomFieldsPanel(),
5173 ],
5174 right_panels=[
5175 panels.VirtualChassisMembersPanel(),
5176 CommentsPanel(),
5177 ],
5178 )
5180 def get_extra_context(self, request, instance):
5181 vc_members = Device.objects.restrict(request.user).filter(virtual_chassis=instance).order_by('vc_position')
5182 return {
5183 'virtual_chassis': instance,
5184 'vc_members': vc_members,
5185 }
5188@register_model_view(VirtualChassis, 'add', detail=False)
5189class VirtualChassisCreateView(generic.ObjectEditView):
5190 queryset = VirtualChassis.objects.all()
5191 form = forms.VirtualChassisCreateForm
5194@register_model_view(VirtualChassis, 'edit')
5195class VirtualChassisEditView(ObjectPermissionRequiredMixin, GetReturnURLMixin, View):
5196 queryset = VirtualChassis.objects.all()
5198 def get_required_permission(self):
5199 return 'dcim.change_virtualchassis'
5201 def get(self, request, pk):
5203 virtual_chassis = get_object_or_404(self.queryset, pk=pk)
5204 VCMemberFormSet = modelformset_factory(
5205 model=Device,
5206 form=forms.DeviceVCMembershipForm,
5207 formset=forms.BaseVCMemberFormSet,
5208 extra=0
5209 )
5210 members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
5212 vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
5213 vc_form.fields['master'].queryset = members_queryset
5214 formset = VCMemberFormSet(queryset=members_queryset)
5216 return render(request, 'dcim/virtualchassis_edit.html', {
5217 'object': virtual_chassis,
5218 'vc_form': vc_form,
5219 'formset': formset,
5220 'return_url': self.get_return_url(request, virtual_chassis),
5221 })
5223 def post(self, request, pk):
5225 virtual_chassis = get_object_or_404(self.queryset, pk=pk)
5226 virtual_chassis.snapshot()
5227 VCMemberFormSet = modelformset_factory(
5228 model=Device,
5229 form=forms.DeviceVCMembershipForm,
5230 formset=forms.BaseVCMemberFormSet,
5231 extra=0
5232 )
5233 members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
5235 vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
5236 vc_form.fields['master'].queryset = members_queryset
5237 formset = VCMemberFormSet(request.POST, queryset=members_queryset)
5239 if vc_form.is_valid() and formset.is_valid():
5240 virtual_chassis._changelog_message = vc_form.cleaned_data.pop('changelog_message', '')
5242 with transaction.atomic(using=router.db_for_write(Device)):
5244 # Save the VirtualChassis
5245 vc_form.save()
5247 # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
5248 # duplicate positions. Then save each member instance.
5249 members = formset.save(commit=False)
5250 devices = Device.objects.filter(pk__in=[m.pk for m in members])
5251 for device in devices:
5252 device.snapshot()
5253 device.vc_position = None
5254 device.save()
5255 for member in members:
5256 member.save()
5258 return redirect(virtual_chassis.get_absolute_url())
5260 return render(request, 'dcim/virtualchassis_edit.html', {
5261 'vc_form': vc_form,
5262 'formset': formset,
5263 'return_url': self.get_return_url(request, virtual_chassis),
5264 })
5267@register_model_view(VirtualChassis, 'delete')
5268class VirtualChassisDeleteView(generic.ObjectDeleteView):
5269 queryset = VirtualChassis.objects.all()
5272@register_model_view(VirtualChassis, 'add_member', path='add-member')
5273class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMixin, View):
5274 queryset = VirtualChassis.objects.all()
5276 def get_required_permission(self):
5277 return 'dcim.change_virtualchassis'
5279 def get(self, request, pk):
5280 virtual_chassis = get_object_or_404(self.queryset, pk=pk)
5281 initial_data = {k: request.GET[k] for k in request.GET}
5282 member_select_form = forms.VCMemberSelectForm(initial=initial_data)
5283 membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
5285 return render(request, 'dcim/virtualchassis_add_member.html', {
5286 'virtual_chassis': virtual_chassis,
5287 'member_select_form': member_select_form,
5288 'membership_form': membership_form,
5289 'return_url': self.get_return_url(request, virtual_chassis),
5290 })
5292 def post(self, request, pk):
5293 virtual_chassis = get_object_or_404(self.queryset, pk=pk)
5294 member_select_form = forms.VCMemberSelectForm(request.POST)
5296 if member_select_form.is_valid():
5297 device = member_select_form.cleaned_data['device']
5298 device.snapshot()
5299 device.virtual_chassis = virtual_chassis
5300 data = {
5301 'vc_position': request.POST['vc_position'],
5302 'vc_priority': request.POST['vc_priority'],
5303 }
5304 membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
5306 if membership_form.is_valid():
5307 membership_form.save()
5308 messages.success(request, mark_safe(
5309 _('Added member <a href="{url}">{device}</a>').format(
5310 url=device.get_absolute_url(), device=escape(device)
5311 )
5312 ))
5314 if '_addanother' in request.POST and safe_for_redirect(request.get_full_path()):
5315 return redirect(request.get_full_path())
5316 return redirect(self.get_return_url(request, device))
5318 else:
5319 membership_form = forms.DeviceVCMembershipForm(data=request.POST)
5321 return render(request, 'dcim/virtualchassis_add_member.html', {
5322 'virtual_chassis': virtual_chassis,
5323 'member_select_form': member_select_form,
5324 'membership_form': membership_form,
5325 'return_url': self.get_return_url(request, virtual_chassis),
5326 })
5329class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURLMixin, View):
5330 queryset = Device.objects.all()
5332 def get_required_permission(self):
5333 return 'dcim.change_device'
5335 def get(self, request, pk):
5336 device = get_object_or_404(self.queryset, pk=pk, virtual_chassis__isnull=False)
5337 form = ConfirmationForm(initial=request.GET)
5339 return render(request, 'dcim/virtualchassis_remove_member.html', {
5340 'device': device,
5341 'form': form,
5342 'return_url': self.get_return_url(request, device),
5343 })
5345 def post(self, request, pk):
5346 device = get_object_or_404(self.queryset, pk=pk, virtual_chassis__isnull=False)
5347 form = ConfirmationForm(request.POST)
5349 # Protect master device from being removed
5350 virtual_chassis = VirtualChassis.objects.filter(master=device).first()
5351 if virtual_chassis is not None:
5352 messages.error(
5353 request,
5354 _('Unable to remove master device {device} from the virtual chassis.').format(device=device)
5355 )
5356 return redirect(device.get_absolute_url())
5358 if form.is_valid():
5359 device.snapshot()
5360 device.virtual_chassis = None
5361 device.vc_position = None
5362 device.vc_priority = None
5363 device.save()
5365 msg = _('Removed {device} from virtual chassis {chassis}').format(
5366 device=device,
5367 chassis=device.virtual_chassis
5368 )
5369 messages.success(request, msg)
5371 return redirect(self.get_return_url(request, device))
5373 return render(request, 'dcim/virtualchassis_remove_member.html', {
5374 'device': device,
5375 'form': form,
5376 'return_url': self.get_return_url(request, device),
5377 })
5380@register_model_view(VirtualChassis, 'bulk_import', path='import', detail=False)
5381class VirtualChassisBulkImportView(generic.BulkImportView):
5382 queryset = VirtualChassis.objects.all()
5383 model_form = forms.VirtualChassisImportForm
5386@register_model_view(VirtualChassis, 'bulk_edit', path='edit', detail=False)
5387class VirtualChassisBulkEditView(generic.BulkEditView):
5388 queryset = VirtualChassis.objects.all()
5389 filterset = filtersets.VirtualChassisFilterSet
5390 table = tables.VirtualChassisTable
5391 form = forms.VirtualChassisBulkEditForm
5394@register_model_view(VirtualChassis, 'bulk_rename', path='rename', detail=False)
5395class VirtualChassisBulkRenameView(generic.BulkRenameView):
5396 queryset = VirtualChassis.objects.all()
5397 filterset = filtersets.VirtualChassisFilterSet
5400@register_model_view(VirtualChassis, 'bulk_delete', path='delete', detail=False)
5401class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
5402 queryset = VirtualChassis.objects.all()
5403 filterset = filtersets.VirtualChassisFilterSet
5404 table = tables.VirtualChassisTable
5407#
5408# Power panels
5409#
5411@register_model_view(PowerPanel, 'list', path='', detail=False)
5412class PowerPanelListView(generic.ObjectListView):
5413 queryset = PowerPanel.objects.annotate(
5414 powerfeed_count=count_related(PowerFeed, 'power_panel')
5415 )
5416 filterset = filtersets.PowerPanelFilterSet
5417 filterset_form = forms.PowerPanelFilterForm
5418 table = tables.PowerPanelTable
5421@register_model_view(PowerPanel)
5422class PowerPanelView(GetRelatedModelsMixin, generic.ObjectView):
5423 template_name = 'generic/object.html'
5424 queryset = PowerPanel.objects.all()
5425 layout = layout.SimpleLayout(
5426 breadcrumbs=[
5427 Breadcrumb('site', url=filtered_list_url('dcim:powerpanel_list', 'site_id')),
5428 Breadcrumb(
5429 lambda o: o.location.get_ancestors() if o.location else [],
5430 url=filtered_list_url('dcim:powerpanel_list', 'location_id'),
5431 ),
5432 Breadcrumb('location', url=filtered_list_url('dcim:powerpanel_list', 'location_id')),
5433 ],
5434 left_panels=[
5435 panels.PowerPanelPanel(),
5436 TagsPanel(),
5437 CommentsPanel(),
5438 ],
5439 right_panels=[
5440 RelatedObjectsPanel(),
5441 CustomFieldsPanel(),
5442 ImageAttachmentsPanel(),
5443 ],
5444 bottom_panels=[
5445 ObjectsTablePanel(
5446 model='dcim.PowerFeed',
5447 filters={'power_panel_id': lambda ctx: ctx['object'].pk},
5448 actions=[
5449 actions.AddObject('dcim.PowerFeed', url_params={'power_panel': lambda ctx: ctx['object'].pk}),
5450 ],
5451 ),
5452 ],
5453 )
5455 def get_extra_context(self, request, instance):
5456 return {
5457 'related_models': self.get_related_models(request, instance),
5458 }
5461@register_model_view(PowerPanel, 'add', detail=False)
5462@register_model_view(PowerPanel, 'edit')
5463class PowerPanelEditView(generic.ObjectEditView):
5464 queryset = PowerPanel.objects.all()
5465 form = forms.PowerPanelForm
5468@register_model_view(PowerPanel, 'delete')
5469class PowerPanelDeleteView(generic.ObjectDeleteView):
5470 queryset = PowerPanel.objects.all()
5473@register_model_view(PowerPanel, 'bulk_import', path='import', detail=False)
5474class PowerPanelBulkImportView(generic.BulkImportView):
5475 queryset = PowerPanel.objects.all()
5476 model_form = forms.PowerPanelImportForm
5479@register_model_view(PowerPanel, 'bulk_edit', path='edit', detail=False)
5480class PowerPanelBulkEditView(generic.BulkEditView):
5481 queryset = PowerPanel.objects.all()
5482 filterset = filtersets.PowerPanelFilterSet
5483 table = tables.PowerPanelTable
5484 form = forms.PowerPanelBulkEditForm
5487@register_model_view(PowerPanel, 'bulk_rename', path='rename', detail=False)
5488class PowerPanelBulkRenameView(generic.BulkRenameView):
5489 queryset = PowerPanel.objects.all()
5490 filterset = filtersets.PowerPanelFilterSet
5493@register_model_view(PowerPanel, 'bulk_delete', path='delete', detail=False)
5494class PowerPanelBulkDeleteView(generic.BulkDeleteView):
5495 queryset = PowerPanel.objects.annotate(
5496 powerfeed_count=count_related(PowerFeed, 'power_panel')
5497 )
5498 filterset = filtersets.PowerPanelFilterSet
5499 table = tables.PowerPanelTable
5502#
5503# Power feeds
5504#
5506@register_model_view(PowerFeed, 'list', path='', detail=False)
5507class PowerFeedListView(generic.ObjectListView):
5508 queryset = PowerFeed.objects.all()
5509 filterset = filtersets.PowerFeedFilterSet
5510 filterset_form = forms.PowerFeedFilterForm
5511 table = tables.PowerFeedTable
5514@register_model_view(PowerFeed)
5515class PowerFeedView(generic.ObjectView):
5516 template_name = 'generic/object.html'
5517 queryset = PowerFeed.objects.all()
5518 layout = layout.SimpleLayout(
5519 breadcrumbs=[
5520 Breadcrumb('power_panel.site', url=filtered_list_url('dcim:powerfeed_list', 'site_id')),
5521 Breadcrumb('power_panel', url=filtered_list_url('dcim:powerfeed_list', 'power_panel_id')),
5522 Breadcrumb('rack', url=filtered_list_url('dcim:powerfeed_list', 'rack_id')),
5523 ],
5524 left_panels=[
5525 panels.PowerFeedPanel(),
5526 panels.PowerFeedElectricalPanel(),
5527 CustomFieldsPanel(),
5528 TagsPanel(),
5529 ],
5530 right_panels=[
5531 panels.ConnectionPanel(
5532 trace_url_name='dcim:powerfeed_trace',
5533 connect_options=[
5534 {'a_type': 'dcim.powerfeed', 'b_type': 'dcim.powerport', 'label': _('Power Port')},
5535 ],
5536 ),
5537 CommentsPanel(),
5538 ],
5539 )
5542@register_model_view(PowerFeed, 'add', detail=False)
5543@register_model_view(PowerFeed, 'edit')
5544class PowerFeedEditView(generic.ObjectEditView):
5545 queryset = PowerFeed.objects.all()
5546 form = forms.PowerFeedForm
5549@register_model_view(PowerFeed, 'delete')
5550class PowerFeedDeleteView(generic.ObjectDeleteView):
5551 queryset = PowerFeed.objects.all()
5554@register_model_view(PowerFeed, 'bulk_import', path='import', detail=False)
5555class PowerFeedBulkImportView(generic.BulkImportView):
5556 queryset = PowerFeed.objects.all()
5557 model_form = forms.PowerFeedImportForm
5560@register_model_view(PowerFeed, 'bulk_edit', path='edit', detail=False)
5561class PowerFeedBulkEditView(generic.BulkEditView):
5562 queryset = PowerFeed.objects.all()
5563 filterset = filtersets.PowerFeedFilterSet
5564 table = tables.PowerFeedTable
5565 form = forms.PowerFeedBulkEditForm
5568@register_model_view(PowerFeed, 'bulk_rename', path='rename', detail=False)
5569class PowerFeedBulkRenameView(generic.BulkRenameView):
5570 queryset = PowerFeed.objects.all()
5571 filterset = filtersets.PowerFeedFilterSet
5574@register_model_view(PowerFeed, 'bulk_disconnect', path='disconnect', detail=False)
5575class PowerFeedBulkDisconnectView(BulkDisconnectView):
5576 queryset = PowerFeed.objects.all()
5579@register_model_view(PowerFeed, 'bulk_delete', path='delete', detail=False)
5580class PowerFeedBulkDeleteView(generic.BulkDeleteView):
5581 queryset = PowerFeed.objects.all()
5582 filterset = filtersets.PowerFeedFilterSet
5583 table = tables.PowerFeedTable
5586# Trace view
5587register_model_view(PowerFeed, 'trace', kwargs={'model': PowerFeed})(PathTraceView)
5590#
5591# Cooling sources
5592#
5594@register_model_view(CoolingSource, 'list', path='', detail=False)
5595class CoolingSourceListView(generic.ObjectListView):
5596 queryset = CoolingSource.objects.annotate(
5597 coolingfeed_count=count_related(CoolingFeed, 'cooling_source')
5598 )
5599 filterset = filtersets.CoolingSourceFilterSet
5600 filterset_form = forms.CoolingSourceFilterForm
5601 table = tables.CoolingSourceTable
5604@register_model_view(CoolingSource)
5605class CoolingSourceView(GetRelatedModelsMixin, generic.ObjectView):
5606 queryset = CoolingSource.objects.all()
5607 template_name = 'generic/object.html'
5608 layout = layout.SimpleLayout(
5609 breadcrumbs=[
5610 Breadcrumb('site', url=filtered_list_url('dcim:coolingsource_list', 'site_id')),
5611 Breadcrumb(
5612 lambda o: o.location.get_ancestors() if o.location else [],
5613 url=filtered_list_url('dcim:coolingsource_list', 'location_id'),
5614 ),
5615 Breadcrumb('location', url=filtered_list_url('dcim:coolingsource_list', 'location_id')),
5616 ],
5617 left_panels=[
5618 panels.CoolingSourcePanel(),
5619 TagsPanel(),
5620 CommentsPanel(),
5621 ],
5622 right_panels=[
5623 RelatedObjectsPanel(),
5624 CustomFieldsPanel(),
5625 ImageAttachmentsPanel(),
5626 ],
5627 bottom_panels=[
5628 ObjectsTablePanel(
5629 model='dcim.CoolingFeed',
5630 filters={'cooling_source_id': lambda ctx: ctx['object'].pk},
5631 actions=[
5632 actions.AddObject('dcim.CoolingFeed', url_params={'cooling_source': lambda ctx: ctx['object'].pk}),
5633 ],
5634 ),
5635 ],
5636 )
5638 def get_extra_context(self, request, instance):
5639 return {
5640 'related_models': self.get_related_models(request, instance),
5641 }
5644@register_model_view(CoolingSource, 'add', detail=False)
5645@register_model_view(CoolingSource, 'edit')
5646class CoolingSourceEditView(generic.ObjectEditView):
5647 queryset = CoolingSource.objects.all()
5648 form = forms.CoolingSourceForm
5651@register_model_view(CoolingSource, 'delete')
5652class CoolingSourceDeleteView(generic.ObjectDeleteView):
5653 queryset = CoolingSource.objects.all()
5656@register_model_view(CoolingSource, 'bulk_import', path='import', detail=False)
5657class CoolingSourceBulkImportView(generic.BulkImportView):
5658 queryset = CoolingSource.objects.all()
5659 model_form = forms.CoolingSourceImportForm
5662@register_model_view(CoolingSource, 'bulk_edit', path='edit', detail=False)
5663class CoolingSourceBulkEditView(generic.BulkEditView):
5664 queryset = CoolingSource.objects.all()
5665 filterset = filtersets.CoolingSourceFilterSet
5666 table = tables.CoolingSourceTable
5667 form = forms.CoolingSourceBulkEditForm
5670@register_model_view(CoolingSource, 'bulk_rename', path='rename', detail=False)
5671class CoolingSourceBulkRenameView(generic.BulkRenameView):
5672 queryset = CoolingSource.objects.all()
5673 filterset = filtersets.CoolingSourceFilterSet
5676@register_model_view(CoolingSource, 'bulk_delete', path='delete', detail=False)
5677class CoolingSourceBulkDeleteView(generic.BulkDeleteView):
5678 queryset = CoolingSource.objects.annotate(
5679 coolingfeed_count=count_related(CoolingFeed, 'cooling_source')
5680 )
5681 filterset = filtersets.CoolingSourceFilterSet
5682 table = tables.CoolingSourceTable
5685#
5686# Cooling feeds
5687#
5689@register_model_view(CoolingFeed, 'list', path='', detail=False)
5690class CoolingFeedListView(generic.ObjectListView):
5691 queryset = CoolingFeed.objects.all()
5692 filterset = filtersets.CoolingFeedFilterSet
5693 filterset_form = forms.CoolingFeedFilterForm
5694 table = tables.CoolingFeedTable
5697@register_model_view(CoolingFeed)
5698class CoolingFeedView(generic.ObjectView):
5699 queryset = CoolingFeed.objects.all()
5700 template_name = 'generic/object.html'
5701 layout = layout.SimpleLayout(
5702 breadcrumbs=[
5703 Breadcrumb('cooling_source.site', url=filtered_list_url('dcim:coolingfeed_list', 'site_id')),
5704 Breadcrumb('cooling_source', url=filtered_list_url('dcim:coolingfeed_list', 'cooling_source_id')),
5705 Breadcrumb('rack', url=filtered_list_url('dcim:coolingfeed_list', 'rack_id')),
5706 ],
5707 left_panels=[
5708 panels.CoolingFeedPanel(),
5709 panels.CoolingFeedCharacteristicsPanel(),
5710 CustomFieldsPanel(),
5711 TagsPanel(),
5712 ],
5713 right_panels=[
5714 CommentsPanel(),
5715 ],
5716 )
5719@register_model_view(CoolingFeed, 'add', detail=False)
5720@register_model_view(CoolingFeed, 'edit')
5721class CoolingFeedEditView(generic.ObjectEditView):
5722 queryset = CoolingFeed.objects.all()
5723 form = forms.CoolingFeedForm
5726@register_model_view(CoolingFeed, 'delete')
5727class CoolingFeedDeleteView(generic.ObjectDeleteView):
5728 queryset = CoolingFeed.objects.all()
5731@register_model_view(CoolingFeed, 'bulk_import', path='import', detail=False)
5732class CoolingFeedBulkImportView(generic.BulkImportView):
5733 queryset = CoolingFeed.objects.all()
5734 model_form = forms.CoolingFeedImportForm
5737@register_model_view(CoolingFeed, 'bulk_edit', path='edit', detail=False)
5738class CoolingFeedBulkEditView(generic.BulkEditView):
5739 queryset = CoolingFeed.objects.all()
5740 filterset = filtersets.CoolingFeedFilterSet
5741 table = tables.CoolingFeedTable
5742 form = forms.CoolingFeedBulkEditForm
5745@register_model_view(CoolingFeed, 'bulk_rename', path='rename', detail=False)
5746class CoolingFeedBulkRenameView(generic.BulkRenameView):
5747 queryset = CoolingFeed.objects.all()
5748 filterset = filtersets.CoolingFeedFilterSet
5751@register_model_view(CoolingFeed, 'bulk_delete', path='delete', detail=False)
5752class CoolingFeedBulkDeleteView(generic.BulkDeleteView):
5753 queryset = CoolingFeed.objects.all()
5754 filterset = filtersets.CoolingFeedFilterSet
5755 table = tables.CoolingFeedTable
5758#
5759# Virtual device contexts
5760#
5762@register_model_view(VirtualDeviceContext, 'list', path='', detail=False)
5763class VirtualDeviceContextListView(generic.ObjectListView):
5764 queryset = VirtualDeviceContext.objects.annotate(
5765 interface_count=count_related(Interface, 'vdcs'),
5766 )
5767 filterset = filtersets.VirtualDeviceContextFilterSet
5768 filterset_form = forms.VirtualDeviceContextFilterForm
5769 table = tables.VirtualDeviceContextTable
5770 actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete)
5773@register_model_view(VirtualDeviceContext)
5774class VirtualDeviceContextView(GetRelatedModelsMixin, generic.ObjectView):
5775 queryset = VirtualDeviceContext.objects.all()
5776 template_name = 'generic/object.html'
5777 layout = layout.SimpleLayout(
5778 left_panels=[
5779 panels.VirtualDeviceContextPanel(),
5780 TagsPanel(),
5781 ],
5782 right_panels=[
5783 RelatedObjectsPanel(),
5784 CommentsPanel(),
5785 CustomFieldsPanel(),
5786 ],
5787 bottom_panels=[
5788 ObjectsTablePanel(
5789 model='dcim.Interface',
5790 filters={'vdc_id': lambda ctx: ctx['object'].pk},
5791 ),
5792 ],
5793 )
5795 def get_extra_context(self, request, instance):
5796 return {
5797 'related_models': self.get_related_models(
5798 request,
5799 instance,
5800 extra=(
5801 (Interface.objects.restrict(request.user, 'view').filter(vdcs__in=[instance]), 'vdc_id'),
5802 ),
5803 ),
5804 }
5807@register_model_view(VirtualDeviceContext, 'add', detail=False)
5808@register_model_view(VirtualDeviceContext, 'edit')
5809class VirtualDeviceContextEditView(generic.ObjectEditView):
5810 queryset = VirtualDeviceContext.objects.all()
5811 form = forms.VirtualDeviceContextForm
5814@register_model_view(VirtualDeviceContext, 'delete')
5815class VirtualDeviceContextDeleteView(generic.ObjectDeleteView):
5816 queryset = VirtualDeviceContext.objects.all()
5819@register_model_view(VirtualDeviceContext, 'bulk_import', path='import', detail=False)
5820class VirtualDeviceContextBulkImportView(generic.BulkImportView):
5821 queryset = VirtualDeviceContext.objects.all()
5822 model_form = forms.VirtualDeviceContextImportForm
5825@register_model_view(VirtualDeviceContext, 'bulk_edit', path='edit', detail=False)
5826class VirtualDeviceContextBulkEditView(generic.BulkEditView):
5827 queryset = VirtualDeviceContext.objects.all()
5828 filterset = filtersets.VirtualDeviceContextFilterSet
5829 table = tables.VirtualDeviceContextTable
5830 form = forms.VirtualDeviceContextBulkEditForm
5833@register_model_view(VirtualDeviceContext, 'bulk_rename', path='rename', detail=False)
5834class VirtualDeviceContextBulkRenameView(generic.BulkRenameView):
5835 queryset = VirtualDeviceContext.objects.all()
5836 filterset = filtersets.VirtualDeviceContextFilterSet
5839@register_model_view(VirtualDeviceContext, 'bulk_delete', path='delete', detail=False)
5840class VirtualDeviceContextBulkDeleteView(generic.BulkDeleteView):
5841 queryset = VirtualDeviceContext.objects.all()
5842 filterset = filtersets.VirtualDeviceContextFilterSet
5843 table = tables.VirtualDeviceContextTable
5846#
5847# MAC addresses
5848#
5850@register_model_view(MACAddress, 'list', path='', detail=False)
5851class MACAddressListView(generic.ObjectListView):
5852 queryset = MACAddress.objects.all()
5853 filterset = filtersets.MACAddressFilterSet
5854 filterset_form = forms.MACAddressFilterForm
5855 table = tables.MACAddressTable
5856 actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
5859@register_model_view(MACAddress)
5860class MACAddressView(generic.ObjectView):
5861 queryset = MACAddress.objects.all()
5862 template_name = 'generic/object.html'
5863 layout = layout.SimpleLayout(
5864 left_panels=[
5865 panels.MACAddressPanel(),
5866 TagsPanel(),
5867 CustomFieldsPanel(),
5868 ],
5869 right_panels=[
5870 CommentsPanel(),
5871 ],
5872 )
5875@register_model_view(MACAddress, 'add', detail=False)
5876@register_model_view(MACAddress, 'edit')
5877class MACAddressEditView(generic.ObjectEditView):
5878 queryset = MACAddress.objects.all()
5879 form = forms.MACAddressForm
5882@register_model_view(MACAddress, 'delete')
5883class MACAddressDeleteView(generic.ObjectDeleteView):
5884 queryset = MACAddress.objects.all()
5887@register_model_view(MACAddress, 'set_primary')
5888class MACAddressSetPrimaryView(ConditionalLoginRequiredMixin, GetReturnURLMixin, View):
5889 queryset = MACAddress.objects.all()
5891 def get(self, request, pk):
5892 # Degrade a direct GET (bookmark, prefetch) to the MAC's detail page rather than a 405,
5893 # matching DataSourceSyncView.
5894 mac = get_object_or_404(self.queryset.restrict(request.user, 'view'), pk=pk)
5895 return redirect(mac.get_absolute_url())
5897 def post(self, request, pk):
5898 mac = get_object_or_404(self.queryset.restrict(request.user, 'view'), pk=pk)
5899 assigned_object = mac.assigned_object
5901 if assigned_object is None:
5902 messages.error(request, _('This MAC address is not assigned to an interface.'))
5903 return redirect(mac.get_absolute_url())
5905 # Re-fetch the interface through its change-restricted queryset so object-level permissions
5906 # are enforced, not just the model-level change permission.
5907 model = assigned_object._meta.model
5908 interface = model.objects.restrict(request.user, 'change').filter(pk=assigned_object.pk).first()
5909 if interface is None:
5910 messages.error(
5911 request,
5912 _('You do not have permission to modify {object}.').format(object=assigned_object)
5913 )
5914 return redirect(mac.get_absolute_url())
5916 try:
5917 interface.set_primary_mac_address(mac)
5918 except ValidationError as e:
5919 messages.error(request, ', '.join(e.messages))
5920 return redirect(mac.get_absolute_url())
5922 messages.success(
5923 request,
5924 _('Set {mac} as primary MAC address for {interface}.').format(mac=mac, interface=interface)
5925 )
5926 return redirect(self.get_return_url(request, interface))
5929@register_model_view(MACAddress, 'bulk_import', path='import', detail=False)
5930class MACAddressBulkImportView(generic.BulkImportView):
5931 queryset = MACAddress.objects.all()
5932 model_form = forms.MACAddressImportForm
5935@register_model_view(MACAddress, 'bulk_edit', path='edit', detail=False)
5936class MACAddressBulkEditView(generic.BulkEditView):
5937 queryset = MACAddress.objects.all()
5938 filterset = filtersets.MACAddressFilterSet
5939 table = tables.MACAddressTable
5940 form = forms.MACAddressBulkEditForm
5943@register_model_view(MACAddress, 'bulk_delete', path='delete', detail=False)
5944class MACAddressBulkDeleteView(generic.BulkDeleteView):
5945 queryset = MACAddress.objects.all()
5946 filterset = filtersets.MACAddressFilterSet
5947 table = tables.MACAddressTable