mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-16 12:38:17 -06:00
Merge branch 'develop' into inline-vlan-editing
This commit is contained in:
commit
b862dac57e
34
CHANGELOG.md
34
CHANGELOG.md
@ -1,13 +1,45 @@
|
|||||||
v2.6.2 (FUTURE)
|
v2.6.3 (FUTURE)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3420](https://github.com/netbox-community/netbox/issues/3420) - Serial number filter for racks, devices, and inventory items is now case-insensitive
|
||||||
|
* [#3428](https://github.com/netbox-community/netbox/issues/3428) - Fixed cache invalidation issues ([#3300](https://github.com/netbox-community/netbox/issues/3300), [#3363](https://github.com/netbox-community/netbox/issues/3363), [#3379](https://github.com/netbox-community/netbox/issues/3379), [#3382](https://github.com/netbox-community/netbox/issues/3382)) by switching to `prefetch_related()` instead of `select_related()` and removing use of `update()`
|
||||||
|
* [#3421](https://github.com/netbox-community/netbox/issues/3421) - Fix exception when ordering power connections list by PDU
|
||||||
|
* [#3424](https://github.com/netbox-community/netbox/issues/3424) - Fix tag coloring for non-linked tags
|
||||||
|
* [#3426](https://github.com/netbox-community/netbox/issues/3426) - Improve API error handling for ChoiceFields
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#3386](https://github.com/netbox-community/netbox/issues/3386) - Add `mac_address` filter for virtual machineså
|
||||||
|
* [#3391](https://github.com/netbox-community/netbox/issues/3391) - Update Bootstrap CSS to v3.4.1
|
||||||
|
* [#3405](https://github.com/netbox-community/netbox/issues/3405) - Fix population of power port/outlet details on device creation
|
||||||
|
* [#3422](https://github.com/netbox-community/netbox/issues/3422) - Prevent navigation menu from overlapping page content
|
||||||
|
* [#3430](https://github.com/netbox-community/netbox/issues/3430) - Linkify platform field on device view
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v2.6.2 (2019-08-02)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* [#984](https://github.com/netbox-community/netbox/issues/984) - Allow ordering circuits by A/Z side
|
* [#984](https://github.com/netbox-community/netbox/issues/984) - Allow ordering circuits by A/Z side
|
||||||
|
* [#3307](https://github.com/netbox-community/netbox/issues/3307) - Add power panels count to home page
|
||||||
|
* [#3314](https://github.com/netbox-community/netbox/issues/3314) - Paginate object changelog entries
|
||||||
|
* [#3367](https://github.com/netbox-community/netbox/issues/3367) - Add BNC port type and coaxial cable type
|
||||||
|
* [#3368](https://github.com/netbox-community/netbox/issues/3368) - Indicate indefinite changelog retention when applicable
|
||||||
|
* [#3370](https://github.com/netbox-community/netbox/issues/3370) - Add filter class to VirtualChassis API
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3018](https://github.com/netbox-community/netbox/issues/3018) - Components connected via a cable must have an equal number of positions
|
||||||
|
* [#3289](https://github.com/netbox-community/netbox/issues/3289) - Prevent position from being nullified when moving a device to a new rack
|
||||||
|
* [#3293](https://github.com/netbox-community/netbox/issues/3293) - Enable filtering device components by multiple device IDs
|
||||||
|
* [#3315](https://github.com/netbox-community/netbox/issues/3315) - Enable filtering devices/interfaces by multiple MAC addresses
|
||||||
* [#3317](https://github.com/netbox-community/netbox/issues/3317) - Fix permissions for ConfigContextBulkDeleteView
|
* [#3317](https://github.com/netbox-community/netbox/issues/3317) - Fix permissions for ConfigContextBulkDeleteView
|
||||||
|
* [#3323](https://github.com/netbox-community/netbox/issues/3323) - Fix permission evaluation for interface connections view
|
||||||
* [#3342](https://github.com/netbox-community/netbox/issues/3342) - Fix cluster delete button
|
* [#3342](https://github.com/netbox-community/netbox/issues/3342) - Fix cluster delete button
|
||||||
|
* [#3384](https://github.com/netbox-community/netbox/issues/3384) - Maximum and allocated draw fields should be included on power port template creation form
|
||||||
|
* [#3385](https://github.com/netbox-community/netbox/issues/3385) - Fix power panels list when bulk editing power feeds
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class DeviceConnectionsReport(Report):
|
|||||||
def test_console_connection(self):
|
def test_console_connection(self):
|
||||||
|
|
||||||
# Check that every console port for every active device has a connection defined.
|
# Check that every console port for every active device has a connection defined.
|
||||||
for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
|
for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
|
||||||
if console_port.connected_endpoint is None:
|
if console_port.connected_endpoint is None:
|
||||||
self.log_failure(
|
self.log_failure(
|
||||||
console_port.device,
|
console_port.device,
|
||||||
|
@ -95,7 +95,7 @@ Pass-through ports can also be used to model "bump in the wire" devices, such as
|
|||||||
|
|
||||||
### Device Bays
|
### Device Bays
|
||||||
|
|
||||||
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear within rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
|
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear within rack elevations or the "Non-Racked Devices" list within the rack view.
|
||||||
|
|
||||||
Child devices are first-class Devices in their own right: that is, fully independent managed entities which don't share any control plane with the parent. Just like normal devices, child devices have their own platform (OS), role, tags, and interfaces. You cannot create a LAG between interfaces in different child devices.
|
Child devices are first-class Devices in their own right: that is, fully independent managed entities which don't share any control plane with the parent. Just like normal devices, child devices have their own platform (OS), role, tags, and interfaces. You cannot create a LAG between interfaces in different child devices.
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ Add the name of the new field to `csv_headers` and included a CSV-friendly repre
|
|||||||
|
|
||||||
### 4. Update relevant querysets
|
### 4. Update relevant querysets
|
||||||
|
|
||||||
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `select_related()` or `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups.
|
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups.
|
||||||
|
|
||||||
### 5. Update API serializer
|
### 5. Update API serializer
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class CircuitTypeViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitViewSet(CustomFieldModelViewSet):
|
class CircuitViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags')
|
queryset = Circuit.objects.prefetch_related('type', 'tenant', 'provider').prefetch_related('tags')
|
||||||
serializer_class = serializers.CircuitSerializer
|
serializer_class = serializers.CircuitSerializer
|
||||||
filterset_class = filters.CircuitFilter
|
filterset_class = filters.CircuitFilter
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTerminationViewSet(ModelViewSet):
|
class CircuitTerminationViewSet(ModelViewSet):
|
||||||
queryset = CircuitTermination.objects.select_related(
|
queryset = CircuitTermination.objects.prefetch_related(
|
||||||
'circuit', 'site', 'connected_endpoint__device', 'cable'
|
'circuit', 'site', 'connected_endpoint__device', 'cable'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.CircuitTerminationSerializer
|
serializer_class = serializers.CircuitTerminationSerializer
|
||||||
|
@ -295,6 +295,6 @@ class CircuitTermination(CableTermination):
|
|||||||
def get_peer_termination(self):
|
def get_peer_termination(self):
|
||||||
peer_side = 'Z' if self.term_side == 'A' else 'A'
|
peer_side = 'Z' if self.term_side == 'A' else 'A'
|
||||||
try:
|
try:
|
||||||
return CircuitTermination.objects.select_related('site').get(circuit=self.circuit, term_side=peer_side)
|
return CircuitTermination.objects.prefetch_related('site').get(circuit=self.circuit, term_side=peer_side)
|
||||||
except CircuitTermination.DoesNotExist:
|
except CircuitTermination.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
@ -10,4 +10,8 @@ def update_circuit(instance, **kwargs):
|
|||||||
"""
|
"""
|
||||||
When a CircuitTermination has been modified, update the last_updated time of its parent Circuit.
|
When a CircuitTermination has been modified, update the last_updated time of its parent Circuit.
|
||||||
"""
|
"""
|
||||||
Circuit.objects.filter(pk=instance.circuit_id).update(last_updated=timezone.now())
|
circuits = Circuit.objects.filter(pk=instance.circuit_id)
|
||||||
|
time = timezone.now()
|
||||||
|
for circuit in circuits:
|
||||||
|
circuit.last_updated = time
|
||||||
|
circuit.save()
|
||||||
|
@ -35,11 +35,7 @@ class ProviderView(PermissionRequiredMixin, View):
|
|||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
|
|
||||||
provider = get_object_or_404(Provider, slug=slug)
|
provider = get_object_or_404(Provider, slug=slug)
|
||||||
circuits = Circuit.objects.filter(provider=provider).select_related(
|
circuits = Circuit.objects.filter(provider=provider).prefetch_related('type', 'tenant', 'terminations__site')
|
||||||
'type', 'tenant'
|
|
||||||
).prefetch_related(
|
|
||||||
'terminations__site'
|
|
||||||
)
|
|
||||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists()
|
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists()
|
||||||
|
|
||||||
return render(request, 'circuits/provider.html', {
|
return render(request, 'circuits/provider.html', {
|
||||||
@ -134,10 +130,8 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
class CircuitListView(PermissionRequiredMixin, ObjectListView):
|
class CircuitListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'circuits.view_circuit'
|
permission_required = 'circuits.view_circuit'
|
||||||
_terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk'))
|
_terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk'))
|
||||||
queryset = Circuit.objects.select_related(
|
queryset = Circuit.objects.prefetch_related(
|
||||||
'provider', 'type', 'tenant'
|
'provider', 'type', 'tenant', 'terminations__site'
|
||||||
).prefetch_related(
|
|
||||||
'terminations__site'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]),
|
a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]),
|
||||||
z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]),
|
z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]),
|
||||||
@ -153,13 +147,13 @@ class CircuitView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
|
circuit = get_object_or_404(Circuit.objects.prefetch_related('provider', 'type', 'tenant__group'), pk=pk)
|
||||||
termination_a = CircuitTermination.objects.select_related(
|
termination_a = CircuitTermination.objects.prefetch_related(
|
||||||
'site__region', 'connected_endpoint__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_A
|
circuit=circuit, term_side=TERM_SIDE_A
|
||||||
).first()
|
).first()
|
||||||
termination_z = CircuitTermination.objects.select_related(
|
termination_z = CircuitTermination.objects.prefetch_related(
|
||||||
'site__region', 'connected_endpoint__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_Z
|
circuit=circuit, term_side=TERM_SIDE_Z
|
||||||
@ -199,7 +193,7 @@ class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'circuits.change_circuit'
|
permission_required = 'circuits.change_circuit'
|
||||||
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
||||||
filter = filters.CircuitFilter
|
filter = filters.CircuitFilter
|
||||||
table = tables.CircuitTable
|
table = tables.CircuitTable
|
||||||
form = forms.CircuitBulkEditForm
|
form = forms.CircuitBulkEditForm
|
||||||
@ -208,7 +202,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_circuit'
|
permission_required = 'circuits.delete_circuit'
|
||||||
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
||||||
filter = filters.CircuitFilter
|
filter = filters.CircuitFilter
|
||||||
table = tables.CircuitTable
|
table = tables.CircuitTable
|
||||||
default_return_url = 'circuits:circuit_list'
|
default_return_url = 'circuits:circuit_list'
|
||||||
|
@ -109,10 +109,8 @@ class RegionViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class SiteViewSet(CustomFieldModelViewSet):
|
class SiteViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Site.objects.select_related(
|
queryset = Site.objects.prefetch_related(
|
||||||
'region', 'tenant'
|
'region', 'tenant', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'site'),
|
device_count=get_subquery(Device, 'site'),
|
||||||
rack_count=get_subquery(Rack, 'site'),
|
rack_count=get_subquery(Rack, 'site'),
|
||||||
@ -140,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupViewSet(ModelViewSet):
|
class RackGroupViewSet(ModelViewSet):
|
||||||
queryset = RackGroup.objects.select_related('site').annotate(
|
queryset = RackGroup.objects.prefetch_related('site').annotate(
|
||||||
rack_count=Count('racks')
|
rack_count=Count('racks')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RackGroupSerializer
|
serializer_class = serializers.RackGroupSerializer
|
||||||
@ -164,10 +162,8 @@ class RackRoleViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackViewSet(CustomFieldModelViewSet):
|
class RackViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Rack.objects.select_related(
|
queryset = Rack.objects.prefetch_related(
|
||||||
'site', 'group__site', 'role', 'tenant'
|
'site', 'group__site', 'role', 'tenant', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'rack'),
|
device_count=get_subquery(Device, 'rack'),
|
||||||
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
||||||
@ -206,7 +202,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackReservationViewSet(ModelViewSet):
|
class RackReservationViewSet(ModelViewSet):
|
||||||
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
|
||||||
serializer_class = serializers.RackReservationSerializer
|
serializer_class = serializers.RackReservationSerializer
|
||||||
filterset_class = filters.RackReservationFilter
|
filterset_class = filters.RackReservationFilter
|
||||||
|
|
||||||
@ -234,7 +230,7 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||||
queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').prefetch_related('tags').annotate(
|
||||||
device_count=Count('instances')
|
device_count=Count('instances')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.DeviceTypeSerializer
|
serializer_class = serializers.DeviceTypeSerializer
|
||||||
@ -246,49 +242,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortTemplateViewSet(ModelViewSet):
|
class ConsolePortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsolePortTemplateSerializer
|
serializer_class = serializers.ConsolePortTemplateSerializer
|
||||||
filterset_class = filters.ConsolePortTemplateFilter
|
filterset_class = filters.ConsolePortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
||||||
filterset_class = filters.ConsoleServerPortTemplateFilter
|
filterset_class = filters.ConsoleServerPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateViewSet(ModelViewSet):
|
class PowerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerPortTemplateSerializer
|
serializer_class = serializers.PowerPortTemplateSerializer
|
||||||
filterset_class = filters.PowerPortTemplateFilter
|
filterset_class = filters.PowerPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateViewSet(ModelViewSet):
|
class PowerOutletTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
|
queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerOutletTemplateSerializer
|
serializer_class = serializers.PowerOutletTemplateSerializer
|
||||||
filterset_class = filters.PowerOutletTemplateFilter
|
filterset_class = filters.PowerOutletTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateViewSet(ModelViewSet):
|
class InterfaceTemplateViewSet(ModelViewSet):
|
||||||
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
|
queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.InterfaceTemplateSerializer
|
serializer_class = serializers.InterfaceTemplateSerializer
|
||||||
filterset_class = filters.InterfaceTemplateFilter
|
filterset_class = filters.InterfaceTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateViewSet(ModelViewSet):
|
class FrontPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = FrontPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.FrontPortTemplateSerializer
|
serializer_class = serializers.FrontPortTemplateSerializer
|
||||||
filterset_class = filters.FrontPortTemplateFilter
|
filterset_class = filters.FrontPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateViewSet(ModelViewSet):
|
class RearPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = RearPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.RearPortTemplateSerializer
|
serializer_class = serializers.RearPortTemplateSerializer
|
||||||
filterset_class = filters.RearPortTemplateFilter
|
filterset_class = filters.RearPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateViewSet(ModelViewSet):
|
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||||
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
|
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||||
filterset_class = filters.DeviceBayTemplateFilter
|
filterset_class = filters.DeviceBayTemplateFilter
|
||||||
|
|
||||||
@ -324,11 +320,9 @@ class PlatformViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class DeviceViewSet(CustomFieldModelViewSet):
|
class DeviceViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Device.objects.select_related(
|
queryset = Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
|
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
|
||||||
'virtual_chassis__master',
|
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
||||||
).prefetch_related(
|
|
||||||
'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
|
||||||
)
|
)
|
||||||
filterset_class = filters.DeviceFilter
|
filterset_class = filters.DeviceFilter
|
||||||
|
|
||||||
@ -429,52 +423,36 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
|
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = ConsolePort.objects.select_related(
|
queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||||
'device', 'connected_endpoint__device', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.ConsolePortSerializer
|
serializer_class = serializers.ConsolePortSerializer
|
||||||
filterset_class = filters.ConsolePortFilter
|
filterset_class = filters.ConsolePortFilter
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
|
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = ConsoleServerPort.objects.select_related(
|
queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||||
'device', 'connected_endpoint__device', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.ConsoleServerPortSerializer
|
serializer_class = serializers.ConsoleServerPortSerializer
|
||||||
filterset_class = filters.ConsoleServerPortFilter
|
filterset_class = filters.ConsoleServerPortFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = PowerPort.objects.select_related(
|
queryset = PowerPort.objects.prefetch_related(
|
||||||
'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable'
|
'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PowerPortSerializer
|
serializer_class = serializers.PowerPortSerializer
|
||||||
filterset_class = filters.PowerPortFilter
|
filterset_class = filters.PowerPortFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = PowerOutlet.objects.select_related(
|
queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||||
'device', 'connected_endpoint__device', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.PowerOutletSerializer
|
serializer_class = serializers.PowerOutletSerializer
|
||||||
filterset_class = filters.PowerOutletFilter
|
filterset_class = filters.PowerOutletFilter
|
||||||
|
|
||||||
|
|
||||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = Interface.objects.filter(
|
queryset = Interface.objects.prefetch_related(
|
||||||
|
'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags'
|
||||||
|
).filter(
|
||||||
device__isnull=False
|
device__isnull=False
|
||||||
).select_related(
|
|
||||||
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'ip_addresses', 'tags'
|
|
||||||
)
|
)
|
||||||
serializer_class = serializers.InterfaceSerializer
|
serializer_class = serializers.InterfaceSerializer
|
||||||
filterset_class = filters.InterfaceFilter
|
filterset_class = filters.InterfaceFilter
|
||||||
@ -491,33 +469,25 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class FrontPortViewSet(ModelViewSet):
|
class FrontPortViewSet(ModelViewSet):
|
||||||
queryset = FrontPort.objects.select_related(
|
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
||||||
'device__device_type__manufacturer', 'rear_port', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.FrontPortSerializer
|
serializer_class = serializers.FrontPortSerializer
|
||||||
filterset_class = filters.FrontPortFilter
|
filterset_class = filters.FrontPortFilter
|
||||||
|
|
||||||
|
|
||||||
class RearPortViewSet(ModelViewSet):
|
class RearPortViewSet(ModelViewSet):
|
||||||
queryset = RearPort.objects.select_related(
|
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
||||||
'device__device_type__manufacturer', 'cable'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.RearPortSerializer
|
serializer_class = serializers.RearPortSerializer
|
||||||
filterset_class = filters.RearPortFilter
|
filterset_class = filters.RearPortFilter
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayViewSet(ModelViewSet):
|
class DeviceBayViewSet(ModelViewSet):
|
||||||
queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags')
|
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
||||||
serializer_class = serializers.DeviceBaySerializer
|
serializer_class = serializers.DeviceBaySerializer
|
||||||
filterset_class = filters.DeviceBayFilter
|
filterset_class = filters.DeviceBayFilter
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemViewSet(ModelViewSet):
|
class InventoryItemViewSet(ModelViewSet):
|
||||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
||||||
serializer_class = serializers.InventoryItemSerializer
|
serializer_class = serializers.InventoryItemSerializer
|
||||||
filterset_class = filters.InventoryItemFilter
|
filterset_class = filters.InventoryItemFilter
|
||||||
|
|
||||||
@ -527,7 +497,7 @@ class InventoryItemViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
queryset = ConsolePort.objects.select_related(
|
queryset = ConsolePort.objects.prefetch_related(
|
||||||
'device', 'connected_endpoint__device'
|
'device', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
connected_endpoint__isnull=False
|
connected_endpoint__isnull=False
|
||||||
@ -537,7 +507,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
queryset = PowerPort.objects.select_related(
|
queryset = PowerPort.objects.prefetch_related(
|
||||||
'device', 'connected_endpoint__device'
|
'device', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
_connected_poweroutlet__isnull=False
|
_connected_poweroutlet__isnull=False
|
||||||
@ -547,7 +517,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.prefetch_related(
|
||||||
'device', '_connected_interface__device'
|
'device', '_connected_interface__device'
|
||||||
).filter(
|
).filter(
|
||||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
@ -579,6 +549,7 @@ class VirtualChassisViewSet(ModelViewSet):
|
|||||||
member_count=Count('members')
|
member_count=Count('members')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
|
filterset_class = filters.VirtualChassisFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -586,7 +557,7 @@ class VirtualChassisViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerPanelViewSet(ModelViewSet):
|
class PowerPanelViewSet(ModelViewSet):
|
||||||
queryset = PowerPanel.objects.select_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=Count('powerfeeds')
|
powerfeed_count=Count('powerfeeds')
|
||||||
@ -600,11 +571,7 @@ class PowerPanelViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerFeedViewSet(CustomFieldModelViewSet):
|
class PowerFeedViewSet(CustomFieldModelViewSet):
|
||||||
queryset = PowerFeed.objects.select_related(
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack', 'tags')
|
||||||
'power_panel', 'rack'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.PowerFeedSerializer
|
serializer_class = serializers.PowerFeedSerializer
|
||||||
filterset_class = filters.PowerFeedFilter
|
filterset_class = filters.PowerFeedFilter
|
||||||
|
|
||||||
|
@ -280,6 +280,7 @@ IFACE_MODE_CHOICES = [
|
|||||||
# Pass-through port types
|
# Pass-through port types
|
||||||
PORT_TYPE_8P8C = 1000
|
PORT_TYPE_8P8C = 1000
|
||||||
PORT_TYPE_110_PUNCH = 1100
|
PORT_TYPE_110_PUNCH = 1100
|
||||||
|
PORT_TYPE_BNC = 1200
|
||||||
PORT_TYPE_ST = 2000
|
PORT_TYPE_ST = 2000
|
||||||
PORT_TYPE_SC = 2100
|
PORT_TYPE_SC = 2100
|
||||||
PORT_TYPE_SC_APC = 2110
|
PORT_TYPE_SC_APC = 2110
|
||||||
@ -296,6 +297,7 @@ PORT_TYPE_CHOICES = [
|
|||||||
[
|
[
|
||||||
[PORT_TYPE_8P8C, '8P8C'],
|
[PORT_TYPE_8P8C, '8P8C'],
|
||||||
[PORT_TYPE_110_PUNCH, '110 Punch'],
|
[PORT_TYPE_110_PUNCH, '110 Punch'],
|
||||||
|
[PORT_TYPE_BNC, 'BNC'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -376,6 +378,7 @@ CABLE_TYPE_CAT6A = 1610
|
|||||||
CABLE_TYPE_CAT7 = 1700
|
CABLE_TYPE_CAT7 = 1700
|
||||||
CABLE_TYPE_DAC_ACTIVE = 1800
|
CABLE_TYPE_DAC_ACTIVE = 1800
|
||||||
CABLE_TYPE_DAC_PASSIVE = 1810
|
CABLE_TYPE_DAC_PASSIVE = 1810
|
||||||
|
CABLE_TYPE_COAXIAL = 1900
|
||||||
CABLE_TYPE_MMF = 3000
|
CABLE_TYPE_MMF = 3000
|
||||||
CABLE_TYPE_MMF_OM1 = 3010
|
CABLE_TYPE_MMF_OM1 = 3010
|
||||||
CABLE_TYPE_MMF_OM2 = 3020
|
CABLE_TYPE_MMF_OM2 = 3020
|
||||||
@ -397,6 +400,7 @@ CABLE_TYPE_CHOICES = (
|
|||||||
(CABLE_TYPE_CAT7, 'CAT7'),
|
(CABLE_TYPE_CAT7, 'CAT7'),
|
||||||
(CABLE_TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
|
(CABLE_TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
|
||||||
(CABLE_TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
|
(CABLE_TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
|
||||||
|
(CABLE_TYPE_COAXIAL, 'Coaxial'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -2,14 +2,15 @@ import django_filters
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from netaddr import EUI
|
|
||||||
from netaddr.core import AddrFormatError
|
|
||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.constants import COLOR_CHOICES
|
from utilities.constants import COLOR_CHOICES
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import (
|
||||||
|
MultiValueMACAddressFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter,
|
||||||
|
TreeNodeMultipleChoiceFilter,
|
||||||
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -159,12 +160,15 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
|
serial = django_filters.CharFilter(
|
||||||
|
lookup_expr='iexact'
|
||||||
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'facility_id', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units',
|
'id', 'name', 'facility_id', 'asset_tag', 'type', 'width', 'u_height', 'desc_units',
|
||||||
'outer_width', 'outer_depth', 'outer_unit',
|
'outer_width', 'outer_depth', 'outer_unit',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -514,10 +518,13 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
field_name='device_type__is_full_depth',
|
field_name='device_type__is_full_depth',
|
||||||
label='Is full depth',
|
label='Is full depth',
|
||||||
)
|
)
|
||||||
mac_address = django_filters.CharFilter(
|
mac_address = MultiValueMACAddressFilter(
|
||||||
method='_mac_address',
|
field_name='interfaces__mac_address',
|
||||||
label='MAC address',
|
label='MAC address',
|
||||||
)
|
)
|
||||||
|
serial = django_filters.CharFilter(
|
||||||
|
lookup_expr='iexact'
|
||||||
|
)
|
||||||
has_primary_ip = django_filters.BooleanFilter(
|
has_primary_ip = django_filters.BooleanFilter(
|
||||||
method='_has_primary_ip',
|
method='_has_primary_ip',
|
||||||
label='Has a primary IP',
|
label='Has a primary IP',
|
||||||
@ -559,7 +566,7 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = ['id', 'name', 'serial', 'asset_tag', 'face', 'position', 'vc_position', 'vc_priority']
|
fields = ['id', 'name', 'asset_tag', 'face', 'position', 'vc_position', 'vc_priority']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -572,16 +579,6 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def _mac_address(self, queryset, name, value):
|
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
try:
|
|
||||||
mac = EUI(value.strip())
|
|
||||||
return queryset.filter(interfaces__mac_address=mac).distinct()
|
|
||||||
except AddrFormatError:
|
|
||||||
return queryset.none()
|
|
||||||
|
|
||||||
def _has_primary_ip(self, queryset, name, value):
|
def _has_primary_ip(self, queryset, name, value):
|
||||||
if value:
|
if value:
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
@ -624,7 +621,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
device_id = django_filters.ModelChoiceFilter(
|
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
label='Device (ID)',
|
label='Device (ID)',
|
||||||
)
|
)
|
||||||
@ -705,8 +702,8 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
field_name='name',
|
field_name='name',
|
||||||
label='Device',
|
label='Device',
|
||||||
)
|
)
|
||||||
device_id = django_filters.NumberFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
method='filter_device',
|
method='filter_device_id',
|
||||||
field_name='pk',
|
field_name='pk',
|
||||||
label='Device (ID)',
|
label='Device (ID)',
|
||||||
)
|
)
|
||||||
@ -724,10 +721,7 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='LAG interface (ID)',
|
label='LAG interface (ID)',
|
||||||
)
|
)
|
||||||
mac_address = django_filters.CharFilter(
|
mac_address = MultiValueMACAddressFilter()
|
||||||
method='_mac_address',
|
|
||||||
label='MAC address',
|
|
||||||
)
|
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
vlan_id = django_filters.CharFilter(
|
vlan_id = django_filters.CharFilter(
|
||||||
method='filter_vlan_id',
|
method='filter_vlan_id',
|
||||||
@ -762,6 +756,17 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
except Device.DoesNotExist:
|
except Device.DoesNotExist:
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
def filter_device_id(self, queryset, name, id_list):
|
||||||
|
# Include interfaces belonging to peer virtual chassis members
|
||||||
|
vc_interface_ids = []
|
||||||
|
try:
|
||||||
|
devices = Device.objects.filter(pk__in=id_list)
|
||||||
|
for device in devices:
|
||||||
|
vc_interface_ids += device.vc_interfaces.values_list('id', flat=True)
|
||||||
|
return queryset.filter(pk__in=vc_interface_ids)
|
||||||
|
except Device.DoesNotExist:
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
def filter_vlan_id(self, queryset, name, value):
|
def filter_vlan_id(self, queryset, name, value):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if not value:
|
if not value:
|
||||||
@ -788,16 +793,6 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES),
|
'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES),
|
||||||
}.get(value, queryset.none())
|
}.get(value, queryset.none())
|
||||||
|
|
||||||
def _mac_address(self, queryset, name, value):
|
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
try:
|
|
||||||
mac = EUI(value.strip())
|
|
||||||
return queryset.filter(mac_address=mac)
|
|
||||||
except AddrFormatError:
|
|
||||||
return queryset.none()
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortFilter(DeviceComponentFilterSet):
|
class FrontPortFilter(DeviceComponentFilterSet):
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
@ -858,10 +853,13 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (slug)',
|
label='Manufacturer (slug)',
|
||||||
)
|
)
|
||||||
|
serial = django_filters.CharFilter(
|
||||||
|
lookup_expr='iexact'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = ['id', 'name', 'part_id', 'serial', 'asset_tag', 'discovered']
|
fields = ['id', 'name', 'part_id', 'asset_tag', 'discovered']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -7,6 +7,8 @@ from django.contrib.postgres.forms.array import SimpleArrayField
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from mptt.forms import TreeNodeChoiceField
|
from mptt.forms import TreeNodeChoiceField
|
||||||
|
from netaddr import EUI
|
||||||
|
from netaddr.core import AddrFormatError
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
from timezone_field import TimeZoneFormField
|
from timezone_field import TimeZoneFormField
|
||||||
|
|
||||||
@ -95,6 +97,28 @@ class BulkRenameForm(forms.Form):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Fields
|
||||||
|
#
|
||||||
|
|
||||||
|
class MACAddressField(forms.Field):
|
||||||
|
widget = forms.CharField
|
||||||
|
default_error_messages = {
|
||||||
|
'invalid': 'MAC address must be in EUI-48 format',
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
value = super().to_python(value)
|
||||||
|
|
||||||
|
# Validate MAC address format
|
||||||
|
try:
|
||||||
|
value = EUI(value.strip())
|
||||||
|
except AddrFormatError:
|
||||||
|
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
@ -627,7 +651,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
group_id = ChainedModelChoiceField(
|
group_id = ChainedModelChoiceField(
|
||||||
label='Rack group',
|
label='Rack group',
|
||||||
queryset=RackGroup.objects.select_related('site'),
|
queryset=RackGroup.objects.prefetch_related('site'),
|
||||||
chains=(
|
chains=(
|
||||||
('site', 'site'),
|
('site', 'site'),
|
||||||
),
|
),
|
||||||
@ -740,7 +764,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
group_id = FilterChoiceField(
|
group_id = FilterChoiceField(
|
||||||
queryset=RackGroup.objects.select_related('site'),
|
queryset=RackGroup.objects.prefetch_related('site'),
|
||||||
label='Rack group',
|
label='Rack group',
|
||||||
null_label='-- None --',
|
null_label='-- None --',
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
@ -973,6 +997,16 @@ class PowerPortTemplateCreateForm(ComponentForm):
|
|||||||
name_pattern = ExpandableNameField(
|
name_pattern = ExpandableNameField(
|
||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
|
maximum_draw = forms.IntegerField(
|
||||||
|
min_value=1,
|
||||||
|
required=False,
|
||||||
|
help_text="Maximum current draw (watts)"
|
||||||
|
)
|
||||||
|
allocated_draw = forms.IntegerField(
|
||||||
|
min_value=1,
|
||||||
|
required=False,
|
||||||
|
help_text="Allocated current draw (watts)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||||
@ -1263,7 +1297,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/',
|
api_url='/api/dcim/racks/',
|
||||||
display_field='display_name',
|
display_field='display_name'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
position = forms.TypedChoiceField(
|
position = forms.TypedChoiceField(
|
||||||
@ -1376,14 +1410,14 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
interface_ids = self.instance.vc_interfaces.values('pk')
|
interface_ids = self.instance.vc_interfaces.values('pk')
|
||||||
|
|
||||||
# Collect interface IPs
|
# Collect interface IPs
|
||||||
interface_ips = IPAddress.objects.select_related('interface').filter(
|
interface_ips = IPAddress.objects.prefetch_related('interface').filter(
|
||||||
family=family, interface_id__in=interface_ids
|
family=family, interface_id__in=interface_ids
|
||||||
)
|
)
|
||||||
if interface_ips:
|
if interface_ips:
|
||||||
ip_list = [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
|
ip_list = [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
|
||||||
ip_choices.append(('Interface IPs', ip_list))
|
ip_choices.append(('Interface IPs', ip_list))
|
||||||
# Collect NAT IPs
|
# Collect NAT IPs
|
||||||
nat_ips = IPAddress.objects.select_related('nat_inside').filter(
|
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
||||||
family=family, nat_inside__interface__in=interface_ids
|
family=family, nat_inside__interface__in=interface_ids
|
||||||
)
|
)
|
||||||
if nat_ips:
|
if nat_ips:
|
||||||
@ -1695,7 +1729,7 @@ class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
rack_group_id = FilterChoiceField(
|
rack_group_id = FilterChoiceField(
|
||||||
queryset=RackGroup.objects.select_related(
|
queryset=RackGroup.objects.prefetch_related(
|
||||||
'site'
|
'site'
|
||||||
),
|
),
|
||||||
label='Rack group',
|
label='Rack group',
|
||||||
@ -1734,7 +1768,7 @@ class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
device_type_id = FilterChoiceField(
|
device_type_id = FilterChoiceField(
|
||||||
queryset=DeviceType.objects.select_related(
|
queryset=DeviceType.objects.prefetch_related(
|
||||||
'manufacturer'
|
'manufacturer'
|
||||||
),
|
),
|
||||||
label='Model',
|
label='Model',
|
||||||
@ -3584,7 +3618,7 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
queryset=PowerPanel.objects.all(),
|
queryset=PowerPanel.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/sites",
|
api_url="/api/dcim/power-panels/",
|
||||||
filter_for={
|
filter_for={
|
||||||
'rackgroup': 'site_id',
|
'rackgroup': 'site_id',
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,12 @@ class ComponentTemplateModel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
"""
|
||||||
|
Instantiate a new component on the specified Device.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def log_change(self, user, request_id, action):
|
def log_change(self, user, request_id, action):
|
||||||
"""
|
"""
|
||||||
Log an ObjectChange including the parent DeviceType.
|
Log an ObjectChange including the parent DeviceType.
|
||||||
@ -601,7 +607,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
# Update racked devices if the assigned Site has been changed.
|
# Update racked devices if the assigned Site has been changed.
|
||||||
if _site_id is not None and self.site_id != _site_id:
|
if _site_id is not None and self.site_id != _site_id:
|
||||||
Device.objects.filter(rack=self).update(site_id=self.site.pk)
|
devices = Device.objects.filter(rack=self)
|
||||||
|
for device in devices:
|
||||||
|
device.site = self.site
|
||||||
|
device.save()
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
@ -658,7 +667,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
# Add devices to rack units list
|
# Add devices to rack units list
|
||||||
if self.pk:
|
if self.pk:
|
||||||
for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
|
for device in Device.objects.prefetch_related('device_type__manufacturer', 'device_role')\
|
||||||
.annotate(devicebay_count=Count('device_bays'))\
|
.annotate(devicebay_count=Count('device_bays'))\
|
||||||
.exclude(pk=exclude)\
|
.exclude(pk=exclude)\
|
||||||
.filter(rack=self, position__gt=0)\
|
.filter(rack=self, position__gt=0)\
|
||||||
@ -691,7 +700,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Gather all devices which consume U space within the rack
|
# Gather all devices which consume U space within the rack
|
||||||
devices = self.devices.select_related('device_type').filter(position__gte=1).exclude(pk__in=exclude)
|
devices = self.devices.prefetch_related('device_type').filter(position__gte=1).exclude(pk__in=exclude)
|
||||||
|
|
||||||
# Initialize the rack unit skeleton
|
# Initialize the rack unit skeleton
|
||||||
units = list(range(1, self.u_height + 1))
|
units = list(range(1, self.u_height + 1))
|
||||||
@ -1010,6 +1019,12 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return ConsolePort(
|
||||||
|
device=device,
|
||||||
|
name=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1033,6 +1048,12 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return ConsoleServerPort(
|
||||||
|
device=device,
|
||||||
|
name=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplate(ComponentTemplateModel):
|
class PowerPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1068,6 +1089,14 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return PowerPort(
|
||||||
|
device=device,
|
||||||
|
name=self.name,
|
||||||
|
maximum_draw=self.maximum_draw,
|
||||||
|
allocated_draw=self.allocated_draw
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplate(ComponentTemplateModel):
|
class PowerOutletTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1112,6 +1141,18 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
"Parent power port ({}) must belong to the same device type".format(self.power_port)
|
"Parent power port ({}) must belong to the same device type".format(self.power_port)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
if self.power_port:
|
||||||
|
power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
|
||||||
|
else:
|
||||||
|
power_port = None
|
||||||
|
return PowerOutlet(
|
||||||
|
device=device,
|
||||||
|
name=self.name,
|
||||||
|
power_port=power_port,
|
||||||
|
feed_leg=self.feed_leg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplate(ComponentTemplateModel):
|
class InterfaceTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1159,6 +1200,14 @@ class InterfaceTemplate(ComponentTemplateModel):
|
|||||||
"""
|
"""
|
||||||
self.type = value
|
self.type = value
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return Interface(
|
||||||
|
device=device,
|
||||||
|
name=self.name,
|
||||||
|
type=self.type,
|
||||||
|
mgmt_only=self.mgmt_only
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplate(ComponentTemplateModel):
|
class FrontPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1213,6 +1262,19 @@ class FrontPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
if self.rear_port:
|
||||||
|
rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
|
||||||
|
else:
|
||||||
|
rear_port = None
|
||||||
|
return FrontPort(
|
||||||
|
device=device,
|
||||||
|
name=self.name,
|
||||||
|
type=self.type,
|
||||||
|
rear_port=rear_port,
|
||||||
|
rear_port_position=self.rear_port_position
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplate(ComponentTemplateModel):
|
class RearPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1243,6 +1305,14 @@ class RearPortTemplate(ComponentTemplateModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return RearPort(
|
||||||
|
device=device,
|
||||||
|
name=self.name,
|
||||||
|
type=self.type,
|
||||||
|
positions=self.positions
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplate(ComponentTemplateModel):
|
class DeviceBayTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
@ -1266,6 +1336,12 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def instantiate(self, device):
|
||||||
|
return DeviceBay(
|
||||||
|
device=device,
|
||||||
|
name=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Devices
|
# Devices
|
||||||
@ -1640,49 +1716,36 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
# If this is a new Device, instantiate all of the related components per the DeviceType definition
|
# If this is a new Device, instantiate all of the related components per the DeviceType definition
|
||||||
if is_new:
|
if is_new:
|
||||||
ConsolePort.objects.bulk_create(
|
ConsolePort.objects.bulk_create(
|
||||||
[ConsolePort(device=self, name=template.name) for template in
|
[x.instantiate(self) for x in self.device_type.consoleport_templates.all()]
|
||||||
self.device_type.consoleport_templates.all()]
|
|
||||||
)
|
)
|
||||||
ConsoleServerPort.objects.bulk_create(
|
ConsoleServerPort.objects.bulk_create(
|
||||||
[ConsoleServerPort(device=self, name=template.name) for template in
|
[x.instantiate(self) for x in self.device_type.consoleserverport_templates.all()]
|
||||||
self.device_type.consoleserverport_templates.all()]
|
|
||||||
)
|
)
|
||||||
PowerPort.objects.bulk_create(
|
PowerPort.objects.bulk_create(
|
||||||
[PowerPort(device=self, name=template.name) for template in
|
[x.instantiate(self) for x in self.device_type.powerport_templates.all()]
|
||||||
self.device_type.powerport_templates.all()]
|
|
||||||
)
|
)
|
||||||
PowerOutlet.objects.bulk_create(
|
PowerOutlet.objects.bulk_create(
|
||||||
[PowerOutlet(device=self, name=template.name) for template in
|
[x.instantiate(self) for x in self.device_type.poweroutlet_templates.all()]
|
||||||
self.device_type.poweroutlet_templates.all()]
|
|
||||||
)
|
)
|
||||||
Interface.objects.bulk_create(
|
Interface.objects.bulk_create(
|
||||||
[Interface(device=self, name=template.name, type=template.type,
|
[x.instantiate(self) for x in self.device_type.interface_templates.all()]
|
||||||
mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()]
|
)
|
||||||
|
RearPort.objects.bulk_create(
|
||||||
|
[x.instantiate(self) for x in self.device_type.rearport_templates.all()]
|
||||||
|
)
|
||||||
|
FrontPort.objects.bulk_create(
|
||||||
|
[x.instantiate(self) for x in self.device_type.frontport_templates.all()]
|
||||||
)
|
)
|
||||||
RearPort.objects.bulk_create([
|
|
||||||
RearPort(
|
|
||||||
device=self,
|
|
||||||
name=template.name,
|
|
||||||
type=template.type,
|
|
||||||
positions=template.positions
|
|
||||||
) for template in self.device_type.rearport_templates.all()
|
|
||||||
])
|
|
||||||
FrontPort.objects.bulk_create([
|
|
||||||
FrontPort(
|
|
||||||
device=self,
|
|
||||||
name=template.name,
|
|
||||||
type=template.type,
|
|
||||||
rear_port=RearPort.objects.get(device=self, name=template.rear_port.name),
|
|
||||||
rear_port_position=template.rear_port_position,
|
|
||||||
) for template in self.device_type.frontport_templates.all()
|
|
||||||
])
|
|
||||||
DeviceBay.objects.bulk_create(
|
DeviceBay.objects.bulk_create(
|
||||||
[DeviceBay(device=self, name=template.name) for template in
|
[x.instantiate(self) for x in self.device_type.device_bay_templates.all()]
|
||||||
self.device_type.device_bay_templates.all()]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update Site and Rack assignment for any child Devices
|
# Update Site and Rack assignment for any child Devices
|
||||||
Device.objects.filter(parent_bay__device=self).update(site=self.site, rack=self.rack)
|
devices = Device.objects.filter(parent_bay__device=self)
|
||||||
|
for device in devices:
|
||||||
|
device.site = self.site
|
||||||
|
device.rack = self.rack
|
||||||
|
device.save()
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
@ -2772,6 +2835,16 @@ class Cable(ChangeLoggedModel):
|
|||||||
self.termination_a_type, self.termination_b_type
|
self.termination_a_type, self.termination_b_type
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# A component with multiple positions must be connected to a component with an equal number of positions
|
||||||
|
term_a_positions = getattr(self.termination_a, 'positions', 1)
|
||||||
|
term_b_positions = getattr(self.termination_b, 'positions', 1)
|
||||||
|
if term_a_positions != term_b_positions:
|
||||||
|
raise ValidationError(
|
||||||
|
"{} has {} positions and {} has {}. Both terminations must have the same number of positions.".format(
|
||||||
|
self.termination_a, term_a_positions, self.termination_b, term_b_positions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# A termination point cannot be connected to itself
|
# A termination point cannot be connected to itself
|
||||||
if self.termination_a == self.termination_b:
|
if self.termination_a == self.termination_b:
|
||||||
raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
|
raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
|
||||||
|
@ -10,7 +10,11 @@ def assign_virtualchassis_master(instance, created, **kwargs):
|
|||||||
When a VirtualChassis is created, automatically assign its master device to the VC.
|
When a VirtualChassis is created, automatically assign its master device to the VC.
|
||||||
"""
|
"""
|
||||||
if created:
|
if created:
|
||||||
Device.objects.filter(pk=instance.master.pk).update(virtual_chassis=instance, vc_position=None)
|
devices = Device.objects.filter(pk=instance.master.pk)
|
||||||
|
for device in devices:
|
||||||
|
device.virtual_chassis = instance
|
||||||
|
device.vc_position = None
|
||||||
|
device.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=VirtualChassis)
|
@receiver(pre_delete, sender=VirtualChassis)
|
||||||
@ -18,7 +22,11 @@ def clear_virtualchassis_members(instance, **kwargs):
|
|||||||
"""
|
"""
|
||||||
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
||||||
"""
|
"""
|
||||||
Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None)
|
devices = Device.objects.filter(virtual_chassis=instance.pk)
|
||||||
|
for device in devices:
|
||||||
|
device.vc_position = None
|
||||||
|
device.vc_priority = None
|
||||||
|
device.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Cable)
|
@receiver(post_save, sender=Cable)
|
||||||
|
@ -424,7 +424,7 @@ class PowerPortTemplateTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name', 'maximum_draw', 'allocated_draw')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
|
|
||||||
|
|
||||||
@ -729,6 +729,7 @@ class PowerConnectionTable(BaseTable):
|
|||||||
viewname='dcim:device',
|
viewname='dcim:device',
|
||||||
accessor=Accessor('connected_endpoint.device'),
|
accessor=Accessor('connected_endpoint.device'),
|
||||||
args=[Accessor('connected_endpoint.device.pk')],
|
args=[Accessor('connected_endpoint.device.pk')],
|
||||||
|
order_by='_connected_poweroutlet__device',
|
||||||
verbose_name='PDU'
|
verbose_name='PDU'
|
||||||
)
|
)
|
||||||
outlet = tables.Column(
|
outlet = tables.Column(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.constants import *
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
|
||||||
|
|
||||||
@ -152,6 +151,137 @@ class RackTestCase(TestCase):
|
|||||||
self.assertTrue(pdu)
|
self.assertTrue(pdu)
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
|
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
|
self.device_type = DeviceType.objects.create(
|
||||||
|
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
||||||
|
)
|
||||||
|
self.device_role = DeviceRole.objects.create(
|
||||||
|
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create DeviceType components
|
||||||
|
ConsolePortTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Console Port 1'
|
||||||
|
).save()
|
||||||
|
|
||||||
|
ConsoleServerPortTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Console Server Port 1'
|
||||||
|
).save()
|
||||||
|
|
||||||
|
ppt = PowerPortTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Power Port 1',
|
||||||
|
maximum_draw=1000,
|
||||||
|
allocated_draw=500
|
||||||
|
)
|
||||||
|
ppt.save()
|
||||||
|
|
||||||
|
PowerOutletTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Power Outlet 1',
|
||||||
|
power_port=ppt,
|
||||||
|
feed_leg=POWERFEED_LEG_A
|
||||||
|
).save()
|
||||||
|
|
||||||
|
InterfaceTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Interface 1',
|
||||||
|
type=IFACE_TYPE_1GE_FIXED,
|
||||||
|
mgmt_only=True
|
||||||
|
).save()
|
||||||
|
|
||||||
|
rpt = RearPortTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Rear Port 1',
|
||||||
|
type=PORT_TYPE_8P8C,
|
||||||
|
positions=8
|
||||||
|
)
|
||||||
|
rpt.save()
|
||||||
|
|
||||||
|
FrontPortTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Front Port 1',
|
||||||
|
type=PORT_TYPE_8P8C,
|
||||||
|
rear_port=rpt,
|
||||||
|
rear_port_position=2
|
||||||
|
).save()
|
||||||
|
|
||||||
|
DeviceBayTemplate(
|
||||||
|
device_type=self.device_type,
|
||||||
|
name='Device Bay 1'
|
||||||
|
).save()
|
||||||
|
|
||||||
|
def test_device_creation(self):
|
||||||
|
"""
|
||||||
|
Ensure that all Device components are copied automatically from the DeviceType.
|
||||||
|
"""
|
||||||
|
d = Device(
|
||||||
|
site=self.site,
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.device_role,
|
||||||
|
name='Test Device 1'
|
||||||
|
)
|
||||||
|
d.save()
|
||||||
|
|
||||||
|
ConsolePort.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Console Port 1'
|
||||||
|
)
|
||||||
|
|
||||||
|
ConsoleServerPort.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Console Server Port 1'
|
||||||
|
)
|
||||||
|
|
||||||
|
pp = PowerPort.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Power Port 1',
|
||||||
|
maximum_draw=1000,
|
||||||
|
allocated_draw=500
|
||||||
|
)
|
||||||
|
|
||||||
|
PowerOutlet.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Power Outlet 1',
|
||||||
|
power_port=pp,
|
||||||
|
feed_leg=POWERFEED_LEG_A
|
||||||
|
)
|
||||||
|
|
||||||
|
Interface.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Interface 1',
|
||||||
|
type=IFACE_TYPE_1GE_FIXED,
|
||||||
|
mgmt_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
rp = RearPort.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Rear Port 1',
|
||||||
|
type=PORT_TYPE_8P8C,
|
||||||
|
positions=8
|
||||||
|
)
|
||||||
|
|
||||||
|
FrontPort.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Front Port 1',
|
||||||
|
type=PORT_TYPE_8P8C,
|
||||||
|
rear_port=rp,
|
||||||
|
rear_port_position=2
|
||||||
|
)
|
||||||
|
|
||||||
|
DeviceBay.objects.get(
|
||||||
|
device=d,
|
||||||
|
name='Device Bay 1'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CableTestCase(TestCase):
|
class CableTestCase(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -185,7 +185,7 @@ class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class SiteListView(PermissionRequiredMixin, ObjectListView):
|
class SiteListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_site'
|
permission_required = 'dcim.view_site'
|
||||||
queryset = Site.objects.select_related('region', 'tenant')
|
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||||
filter = filters.SiteFilter
|
filter = filters.SiteFilter
|
||||||
filter_form = forms.SiteFilterForm
|
filter_form = forms.SiteFilterForm
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
@ -197,7 +197,7 @@ class SiteView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
|
|
||||||
site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug)
|
site = get_object_or_404(Site.objects.prefetch_related('region', 'tenant__group'), slug=slug)
|
||||||
stats = {
|
stats = {
|
||||||
'rack_count': Rack.objects.filter(site=site).count(),
|
'rack_count': Rack.objects.filter(site=site).count(),
|
||||||
'device_count': Device.objects.filter(site=site).count(),
|
'device_count': Device.objects.filter(site=site).count(),
|
||||||
@ -246,7 +246,7 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_site'
|
permission_required = 'dcim.change_site'
|
||||||
queryset = Site.objects.select_related('region', 'tenant')
|
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||||
filter = filters.SiteFilter
|
filter = filters.SiteFilter
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
form = forms.SiteBulkEditForm
|
form = forms.SiteBulkEditForm
|
||||||
@ -255,7 +255,7 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_site'
|
permission_required = 'dcim.delete_site'
|
||||||
queryset = Site.objects.select_related('region', 'tenant')
|
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||||
filter = filters.SiteFilter
|
filter = filters.SiteFilter
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
default_return_url = 'dcim:site_list'
|
default_return_url = 'dcim:site_list'
|
||||||
@ -267,7 +267,7 @@ class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class RackGroupListView(PermissionRequiredMixin, ObjectListView):
|
class RackGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_rackgroup'
|
permission_required = 'dcim.view_rackgroup'
|
||||||
queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
|
queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
|
||||||
filter = filters.RackGroupFilter
|
filter = filters.RackGroupFilter
|
||||||
filter_form = forms.RackGroupFilterForm
|
filter_form = forms.RackGroupFilterForm
|
||||||
table = tables.RackGroupTable
|
table = tables.RackGroupTable
|
||||||
@ -294,7 +294,7 @@ class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rackgroup'
|
permission_required = 'dcim.delete_rackgroup'
|
||||||
queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
|
queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
|
||||||
filter = filters.RackGroupFilter
|
filter = filters.RackGroupFilter
|
||||||
table = tables.RackGroupTable
|
table = tables.RackGroupTable
|
||||||
default_return_url = 'dcim:rackgroup_list'
|
default_return_url = 'dcim:rackgroup_list'
|
||||||
@ -342,10 +342,8 @@ class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class RackListView(PermissionRequiredMixin, ObjectListView):
|
class RackListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_rack'
|
permission_required = 'dcim.view_rack'
|
||||||
queryset = Rack.objects.select_related(
|
queryset = Rack.objects.prefetch_related(
|
||||||
'site', 'group', 'tenant', 'role'
|
'site', 'group', 'tenant', 'role', 'devices__device_type'
|
||||||
).prefetch_related(
|
|
||||||
'devices__device_type'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=Count('devices')
|
device_count=Count('devices')
|
||||||
)
|
)
|
||||||
@ -363,11 +361,7 @@ class RackElevationListView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
racks = Rack.objects.select_related(
|
racks = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role', 'devices__device_type')
|
||||||
'site', 'group', 'tenant', 'role'
|
|
||||||
).prefetch_related(
|
|
||||||
'devices__device_type'
|
|
||||||
)
|
|
||||||
racks = filters.RackFilter(request.GET, racks).qs
|
racks = filters.RackFilter(request.GET, racks).qs
|
||||||
total_count = racks.count()
|
total_count = racks.count()
|
||||||
|
|
||||||
@ -402,15 +396,18 @@ class RackView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
|
rack = get_object_or_404(Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
|
||||||
|
|
||||||
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True) \
|
nonracked_devices = Device.objects.filter(
|
||||||
.select_related('device_type__manufacturer')
|
rack=rack,
|
||||||
|
position__isnull=True,
|
||||||
|
parent_bay__isnull=True
|
||||||
|
).prefetch_related('device_type__manufacturer')
|
||||||
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
||||||
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
|
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
|
||||||
|
|
||||||
reservations = RackReservation.objects.filter(rack=rack)
|
reservations = RackReservation.objects.filter(rack=rack)
|
||||||
power_feeds = PowerFeed.objects.filter(rack=rack).select_related('power_panel')
|
power_feeds = PowerFeed.objects.filter(rack=rack).prefetch_related('power_panel')
|
||||||
|
|
||||||
return render(request, 'dcim/rack.html', {
|
return render(request, 'dcim/rack.html', {
|
||||||
'rack': rack,
|
'rack': rack,
|
||||||
@ -451,7 +448,7 @@ class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_rack'
|
permission_required = 'dcim.change_rack'
|
||||||
queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filter = filters.RackFilter
|
filter = filters.RackFilter
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
form = forms.RackBulkEditForm
|
form = forms.RackBulkEditForm
|
||||||
@ -460,7 +457,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rack'
|
permission_required = 'dcim.delete_rack'
|
||||||
queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filter = filters.RackFilter
|
filter = filters.RackFilter
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
default_return_url = 'dcim:rack_list'
|
default_return_url = 'dcim:rack_list'
|
||||||
@ -472,7 +469,7 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class RackReservationListView(PermissionRequiredMixin, ObjectListView):
|
class RackReservationListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_rackreservation'
|
permission_required = 'dcim.view_rackreservation'
|
||||||
queryset = RackReservation.objects.select_related('rack__site')
|
queryset = RackReservation.objects.prefetch_related('rack__site')
|
||||||
filter = filters.RackReservationFilter
|
filter = filters.RackReservationFilter
|
||||||
filter_form = forms.RackReservationFilterForm
|
filter_form = forms.RackReservationFilterForm
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
@ -508,7 +505,7 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
|
|
||||||
class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_rackreservation'
|
permission_required = 'dcim.change_rackreservation'
|
||||||
queryset = RackReservation.objects.select_related('rack', 'user')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||||
filter = filters.RackReservationFilter
|
filter = filters.RackReservationFilter
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
form = forms.RackReservationBulkEditForm
|
form = forms.RackReservationBulkEditForm
|
||||||
@ -517,7 +514,7 @@ class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rackreservation'
|
permission_required = 'dcim.delete_rackreservation'
|
||||||
queryset = RackReservation.objects.select_related('rack', 'user')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||||
filter = filters.RackReservationFilter
|
filter = filters.RackReservationFilter
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
default_return_url = 'dcim:rackreservation_list'
|
default_return_url = 'dcim:rackreservation_list'
|
||||||
@ -569,7 +566,7 @@ class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
|
class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_devicetype'
|
permission_required = 'dcim.view_devicetype'
|
||||||
queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||||
filter = filters.DeviceTypeFilter
|
filter = filters.DeviceTypeFilter
|
||||||
filter_form = forms.DeviceTypeFilterForm
|
filter_form = forms.DeviceTypeFilterForm
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
@ -666,7 +663,7 @@ class DeviceTypeBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_devicetype'
|
permission_required = 'dcim.change_devicetype'
|
||||||
queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||||
filter = filters.DeviceTypeFilter
|
filter = filters.DeviceTypeFilter
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
form = forms.DeviceTypeBulkEditForm
|
form = forms.DeviceTypeBulkEditForm
|
||||||
@ -675,7 +672,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_devicetype'
|
permission_required = 'dcim.delete_devicetype'
|
||||||
queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||||
filter = filters.DeviceTypeFilter
|
filter = filters.DeviceTypeFilter
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
default_return_url = 'dcim:devicetype_list'
|
default_return_url = 'dcim:devicetype_list'
|
||||||
@ -907,7 +904,7 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceListView(PermissionRequiredMixin, ObjectListView):
|
class DeviceListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_device'
|
permission_required = 'dcim.view_device'
|
||||||
queryset = Device.objects.select_related(
|
queryset = Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
|
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
|
||||||
)
|
)
|
||||||
filter = filters.DeviceFilter
|
filter = filters.DeviceFilter
|
||||||
@ -921,7 +918,7 @@ class DeviceView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
device = get_object_or_404(Device.objects.select_related(
|
device = get_object_or_404(Device.objects.prefetch_related(
|
||||||
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
|
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
|
||||||
), pk=pk)
|
), pk=pk)
|
||||||
|
|
||||||
@ -934,32 +931,31 @@ class DeviceView(PermissionRequiredMixin, View):
|
|||||||
vc_members = []
|
vc_members = []
|
||||||
|
|
||||||
# Console ports
|
# Console ports
|
||||||
console_ports = device.consoleports.select_related('connected_endpoint__device', 'cable')
|
console_ports = device.consoleports.prefetch_related('connected_endpoint__device', 'cable')
|
||||||
|
|
||||||
# Console server ports
|
# Console server ports
|
||||||
consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable')
|
consoleserverports = device.consoleserverports.prefetch_related('connected_endpoint__device', 'cable')
|
||||||
|
|
||||||
# Power ports
|
# Power ports
|
||||||
power_ports = device.powerports.select_related('_connected_poweroutlet__device', 'cable')
|
power_ports = device.powerports.prefetch_related('_connected_poweroutlet__device', 'cable')
|
||||||
|
|
||||||
# Power outlets
|
# Power outlets
|
||||||
poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable', 'power_port')
|
poweroutlets = device.poweroutlets.prefetch_related('connected_endpoint__device', 'cable', 'power_port')
|
||||||
|
|
||||||
# Interfaces
|
# Interfaces
|
||||||
interfaces = device.vc_interfaces.select_related(
|
interfaces = device.vc_interfaces.prefetch_related(
|
||||||
'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable'
|
'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable',
|
||||||
).prefetch_related(
|
|
||||||
'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
|
'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Front ports
|
# Front ports
|
||||||
front_ports = device.frontports.select_related('rear_port', 'cable')
|
front_ports = device.frontports.prefetch_related('rear_port', 'cable')
|
||||||
|
|
||||||
# Rear ports
|
# Rear ports
|
||||||
rear_ports = device.rearports.select_related('cable')
|
rear_ports = device.rearports.prefetch_related('cable')
|
||||||
|
|
||||||
# Device bays
|
# Device bays
|
||||||
device_bays = device.device_bays.select_related('installed_device__device_type__manufacturer')
|
device_bays = device.device_bays.prefetch_related('installed_device__device_type__manufacturer')
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
services = device.services.all()
|
services = device.services.all()
|
||||||
@ -972,7 +968,7 @@ class DeviceView(PermissionRequiredMixin, View):
|
|||||||
site=device.site, device_role=device.device_role
|
site=device.site, device_role=device.device_role
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=device.pk
|
pk=device.pk
|
||||||
).select_related(
|
).prefetch_related(
|
||||||
'rack', 'device_type__manufacturer'
|
'rack', 'device_type__manufacturer'
|
||||||
)[:10]
|
)[:10]
|
||||||
|
|
||||||
@ -1005,10 +1001,8 @@ class DeviceInventoryView(PermissionRequiredMixin, View):
|
|||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
inventory_items = InventoryItem.objects.filter(
|
inventory_items = InventoryItem.objects.filter(
|
||||||
device=device, parent=None
|
device=device, parent=None
|
||||||
).select_related(
|
|
||||||
'manufacturer'
|
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'child_items'
|
'manufacturer', 'child_items'
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/device_inventory.html', {
|
return render(request, 'dcim/device_inventory.html', {
|
||||||
@ -1037,7 +1031,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
|||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
interfaces = device.vc_interfaces.connectable().select_related(
|
interfaces = device.vc_interfaces.connectable().prefetch_related(
|
||||||
'_connected_interface__device'
|
'_connected_interface__device'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1114,7 +1108,7 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_device'
|
permission_required = 'dcim.change_device'
|
||||||
queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||||
filter = filters.DeviceFilter
|
filter = filters.DeviceFilter
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
form = forms.DeviceBulkEditForm
|
form = forms.DeviceBulkEditForm
|
||||||
@ -1123,7 +1117,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_device'
|
permission_required = 'dcim.delete_device'
|
||||||
queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||||
filter = filters.DeviceFilter
|
filter = filters.DeviceFilter
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
@ -1310,7 +1304,7 @@ class InterfaceView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
# Get assigned IP addresses
|
# Get assigned IP addresses
|
||||||
ipaddress_table = InterfaceIPAddressTable(
|
ipaddress_table = InterfaceIPAddressTable(
|
||||||
data=interface.ip_addresses.select_related('vrf', 'tenant'),
|
data=interface.ip_addresses.prefetch_related('vrf', 'tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1319,7 +1313,7 @@ class InterfaceView(PermissionRequiredMixin, View):
|
|||||||
if interface.untagged_vlan is not None:
|
if interface.untagged_vlan is not None:
|
||||||
vlans.append(interface.untagged_vlan)
|
vlans.append(interface.untagged_vlan)
|
||||||
vlans[0].tagged = False
|
vlans[0].tagged = False
|
||||||
for vlan in interface.tagged_vlans.select_related('site', 'group', 'tenant', 'role'):
|
for vlan in interface.tagged_vlans.prefetch_related('site', 'group', 'tenant', 'role'):
|
||||||
vlan.tagged = True
|
vlan.tagged = True
|
||||||
vlans.append(vlan)
|
vlans.append(vlan)
|
||||||
vlan_table = InterfaceVLANTable(
|
vlan_table = InterfaceVLANTable(
|
||||||
@ -1836,7 +1830,7 @@ class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
|
permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
|
||||||
queryset = ConsolePort.objects.select_related(
|
queryset = ConsolePort.objects.prefetch_related(
|
||||||
'device', 'connected_endpoint__device'
|
'device', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
connected_endpoint__isnull=False
|
connected_endpoint__isnull=False
|
||||||
@ -1867,7 +1861,7 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
|
|
||||||
class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
|
permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
|
||||||
queryset = PowerPort.objects.select_related(
|
queryset = PowerPort.objects.prefetch_related(
|
||||||
'device', '_connected_poweroutlet__device'
|
'device', '_connected_poweroutlet__device'
|
||||||
).filter(
|
).filter(
|
||||||
_connected_poweroutlet__isnull=False
|
_connected_poweroutlet__isnull=False
|
||||||
@ -1897,8 +1891,8 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.interface'
|
permission_required = 'dcim.view_interface'
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.prefetch_related(
|
||||||
'device', 'cable', '_connected_interface__device'
|
'device', 'cable', '_connected_interface__device'
|
||||||
).filter(
|
).filter(
|
||||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
@ -1941,7 +1935,7 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
|
|
||||||
class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
|
class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_inventoryitem'
|
permission_required = 'dcim.view_inventoryitem'
|
||||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||||
filter = filters.InventoryItemFilter
|
filter = filters.InventoryItemFilter
|
||||||
filter_form = forms.InventoryItemFilterForm
|
filter_form = forms.InventoryItemFilterForm
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
@ -1976,7 +1970,7 @@ class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_inventoryitem'
|
permission_required = 'dcim.change_inventoryitem'
|
||||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||||
filter = filters.InventoryItemFilter
|
filter = filters.InventoryItemFilter
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
form = forms.InventoryItemBulkEditForm
|
form = forms.InventoryItemBulkEditForm
|
||||||
@ -1985,7 +1979,7 @@ class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_inventoryitem'
|
permission_required = 'dcim.delete_inventoryitem'
|
||||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
template_name = 'dcim/inventoryitem_bulk_delete.html'
|
template_name = 'dcim/inventoryitem_bulk_delete.html'
|
||||||
default_return_url = 'dcim:inventoryitem_list'
|
default_return_url = 'dcim:inventoryitem_list'
|
||||||
@ -1997,7 +1991,7 @@ class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
|
class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_virtualchassis'
|
permission_required = 'dcim.view_virtualchassis'
|
||||||
queryset = VirtualChassis.objects.select_related('master').annotate(member_count=Count('members'))
|
queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members'))
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
filter = filters.VirtualChassisFilter
|
filter = filters.VirtualChassisFilter
|
||||||
filter_form = forms.VirtualChassisFilterForm
|
filter_form = forms.VirtualChassisFilterForm
|
||||||
@ -2017,7 +2011,7 @@ class VirtualChassisCreateView(PermissionRequiredMixin, View):
|
|||||||
return redirect('dcim:device_list')
|
return redirect('dcim:device_list')
|
||||||
device_queryset = Device.objects.filter(
|
device_queryset = Device.objects.filter(
|
||||||
pk__in=pk_form.cleaned_data.get('pk')
|
pk__in=pk_form.cleaned_data.get('pk')
|
||||||
).select_related('rack').order_by('vc_position')
|
).prefetch_related('rack').order_by('vc_position')
|
||||||
|
|
||||||
VCMemberFormSet = modelformset_factory(
|
VCMemberFormSet = modelformset_factory(
|
||||||
model=Device,
|
model=Device,
|
||||||
@ -2071,7 +2065,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|||||||
formset=forms.BaseVCMemberFormSet,
|
formset=forms.BaseVCMemberFormSet,
|
||||||
extra=0
|
extra=0
|
||||||
)
|
)
|
||||||
members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position')
|
members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
|
||||||
|
|
||||||
vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
|
vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
|
||||||
vc_form.fields['master'].queryset = members_queryset
|
vc_form.fields['master'].queryset = members_queryset
|
||||||
@ -2092,7 +2086,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|||||||
formset=forms.BaseVCMemberFormSet,
|
formset=forms.BaseVCMemberFormSet,
|
||||||
extra=0
|
extra=0
|
||||||
)
|
)
|
||||||
members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position')
|
members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
|
||||||
|
|
||||||
vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
|
vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
|
||||||
vc_form.fields['master'].queryset = members_queryset
|
vc_form.fields['master'].queryset = members_queryset
|
||||||
@ -2108,7 +2102,10 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|||||||
# Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
|
# Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
|
||||||
# duplicate positions. Then save each member instance.
|
# duplicate positions. Then save each member instance.
|
||||||
members = formset.save(commit=False)
|
members = formset.save(commit=False)
|
||||||
Device.objects.filter(pk__in=[m.pk for m in members]).update(vc_position=None)
|
devices = Device.objects.filter(pk__in=[m.pk for m in members])
|
||||||
|
for device in devices:
|
||||||
|
device.vc_position = None
|
||||||
|
device.save()
|
||||||
for member in members:
|
for member in members:
|
||||||
member.save()
|
member.save()
|
||||||
|
|
||||||
@ -2209,11 +2206,12 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
|
|||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
Device.objects.filter(pk=device.pk).update(
|
devices = Device.objects.filter(pk=device.pk)
|
||||||
virtual_chassis=None,
|
for device in devices:
|
||||||
vc_position=None,
|
device.virtual_chassis = None
|
||||||
vc_priority=None
|
device.vc_position = None
|
||||||
)
|
device.vc_priority = None
|
||||||
|
device.save()
|
||||||
|
|
||||||
msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
|
msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
@ -2233,7 +2231,7 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
|
|||||||
|
|
||||||
class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
|
class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_powerpanel'
|
permission_required = 'dcim.view_powerpanel'
|
||||||
queryset = PowerPanel.objects.select_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=Count('powerfeeds')
|
powerfeed_count=Count('powerfeeds')
|
||||||
@ -2249,9 +2247,9 @@ class PowerPanelView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
powerpanel = get_object_or_404(PowerPanel.objects.select_related('site', 'rack_group'), pk=pk)
|
powerpanel = get_object_or_404(PowerPanel.objects.prefetch_related('site', 'rack_group'), pk=pk)
|
||||||
powerfeed_table = tables.PowerFeedTable(
|
powerfeed_table = tables.PowerFeedTable(
|
||||||
data=PowerFeed.objects.filter(power_panel=powerpanel).select_related('rack'),
|
data=PowerFeed.objects.filter(power_panel=powerpanel).prefetch_related('rack'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
powerfeed_table.exclude = ['power_panel']
|
powerfeed_table.exclude = ['power_panel']
|
||||||
@ -2288,7 +2286,7 @@ class PowerPanelBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_powerpanel'
|
permission_required = 'dcim.delete_powerpanel'
|
||||||
queryset = PowerPanel.objects.select_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
rack_count=Count('powerfeeds')
|
rack_count=Count('powerfeeds')
|
||||||
@ -2304,7 +2302,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
|
class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_powerfeed'
|
permission_required = 'dcim.view_powerfeed'
|
||||||
queryset = PowerFeed.objects.select_related(
|
queryset = PowerFeed.objects.prefetch_related(
|
||||||
'power_panel', 'rack'
|
'power_panel', 'rack'
|
||||||
)
|
)
|
||||||
filter = filters.PowerFeedFilter
|
filter = filters.PowerFeedFilter
|
||||||
@ -2318,7 +2316,7 @@ class PowerFeedView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
powerfeed = get_object_or_404(PowerFeed.objects.select_related('power_panel', 'rack'), pk=pk)
|
powerfeed = get_object_or_404(PowerFeed.objects.prefetch_related('power_panel', 'rack'), pk=pk)
|
||||||
|
|
||||||
return render(request, 'dcim/powerfeed.html', {
|
return render(request, 'dcim/powerfeed.html', {
|
||||||
'powerfeed': powerfeed,
|
'powerfeed': powerfeed,
|
||||||
@ -2352,7 +2350,7 @@ class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'dcim.change_powerfeed'
|
permission_required = 'dcim.change_powerfeed'
|
||||||
queryset = PowerFeed.objects.select_related('power_panel', 'rack')
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||||
filter = filters.PowerFeedFilter
|
filter = filters.PowerFeedFilter
|
||||||
table = tables.PowerFeedTable
|
table = tables.PowerFeedTable
|
||||||
form = forms.PowerFeedBulkEditForm
|
form = forms.PowerFeedBulkEditForm
|
||||||
@ -2361,7 +2359,7 @@ class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_powerfeed'
|
permission_required = 'dcim.delete_powerfeed'
|
||||||
queryset = PowerFeed.objects.select_related('power_panel', 'rack')
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||||
filter = filters.PowerFeedFilter
|
filter = filters.PowerFeedFilter
|
||||||
table = tables.PowerFeedTable
|
table = tables.PowerFeedTable
|
||||||
default_return_url = 'dcim:powerfeed_list'
|
default_return_url = 'dcim:powerfeed_list'
|
||||||
|
@ -120,7 +120,7 @@ class ExportTemplateViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class TopologyMapViewSet(ModelViewSet):
|
class TopologyMapViewSet(ModelViewSet):
|
||||||
queryset = TopologyMap.objects.select_related('site')
|
queryset = TopologyMap.objects.prefetch_related('site')
|
||||||
serializer_class = serializers.TopologyMapSerializer
|
serializer_class = serializers.TopologyMapSerializer
|
||||||
filterset_class = filters.TopologyMapFilter
|
filterset_class = filters.TopologyMapFilter
|
||||||
|
|
||||||
@ -260,6 +260,6 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Retrieve a list of recent changes.
|
Retrieve a list of recent changes.
|
||||||
"""
|
"""
|
||||||
queryset = ObjectChange.objects.select_related('user')
|
queryset = ObjectChange.objects.prefetch_related('user')
|
||||||
serializer_class = serializers.ObjectChangeSerializer
|
serializer_class = serializers.ObjectChangeSerializer
|
||||||
filterset_class = filters.ObjectChangeFilter
|
filterset_class = filters.ObjectChangeFilter
|
||||||
|
@ -111,8 +111,10 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
|
|
||||||
# If editing an existing object, initialize values for all custom fields
|
# If editing an existing object, initialize values for all custom fields
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
|
existing_values = CustomFieldValue.objects.filter(
|
||||||
.select_related('field')
|
obj_type=self.obj_type,
|
||||||
|
obj_id=self.instance.pk
|
||||||
|
).prefetch_related('field')
|
||||||
for cfv in existing_values:
|
for cfv in existing_values:
|
||||||
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
|
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
|
||||||
|
|
||||||
@ -120,9 +122,11 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
|
|
||||||
for field_name in self.custom_fields:
|
for field_name in self.custom_fields:
|
||||||
try:
|
try:
|
||||||
cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model,
|
cfv = CustomFieldValue.objects.prefetch_related('field').get(
|
||||||
obj_type=self.obj_type,
|
field=self.fields[field_name].model,
|
||||||
obj_id=self.instance.pk)
|
obj_type=self.obj_type,
|
||||||
|
obj_id=self.instance.pk
|
||||||
|
)
|
||||||
except CustomFieldValue.DoesNotExist:
|
except CustomFieldValue.DoesNotExist:
|
||||||
# Skip this field if none exists already and its value is empty
|
# Skip this field if none exists already and its value is empty
|
||||||
if self.cleaned_data[field_name] in [None, '']:
|
if self.cleaned_data[field_name] in [None, '']:
|
||||||
|
@ -5,6 +5,7 @@ import sys
|
|||||||
from django import get_version
|
from django import get_version
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization']
|
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization']
|
||||||
@ -50,6 +51,9 @@ class Command(BaseCommand):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Additional objects to include
|
||||||
|
namespace['User'] = User
|
||||||
|
|
||||||
# Load convenience commands
|
# Load convenience commands
|
||||||
namespace.update({
|
namespace.update({
|
||||||
'lsmodels': self._lsmodels,
|
'lsmodels': self._lsmodels,
|
||||||
|
@ -569,7 +569,7 @@ class TopologyMap(models.Model):
|
|||||||
# Add each device to the graph
|
# Add each device to the graph
|
||||||
devices = []
|
devices = []
|
||||||
for query in device_set.strip(';').split(';'): # Split regexes on semicolons
|
for query in device_set.strip(';').split(';'): # Split regexes on semicolons
|
||||||
devices += Device.objects.filter(name__regex=query).select_related('device_role')
|
devices += Device.objects.filter(name__regex=query).prefetch_related('device_role')
|
||||||
# Remove duplicate devices
|
# Remove duplicate devices
|
||||||
devices = [d for d in devices if d.id not in seen]
|
devices = [d for d in devices if d.id not in seen]
|
||||||
seen.update([d.id for d in devices])
|
seen.update([d.id for d in devices])
|
||||||
@ -607,7 +607,7 @@ class TopologyMap(models.Model):
|
|||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
|
|
||||||
# Add all interface connections to the graph
|
# Add all interface connections to the graph
|
||||||
connected_interfaces = Interface.objects.select_related(
|
connected_interfaces = Interface.objects.prefetch_related(
|
||||||
'_connected_interface__device'
|
'_connected_interface__device'
|
||||||
).filter(
|
).filter(
|
||||||
Q(device__in=devices) | Q(_connected_interface__device__in=devices),
|
Q(device__in=devices) | Q(_connected_interface__device__in=devices),
|
||||||
|
@ -47,10 +47,8 @@ class TagView(View):
|
|||||||
tag = get_object_or_404(Tag, slug=slug)
|
tag = get_object_or_404(Tag, slug=slug)
|
||||||
tagged_items = TaggedItem.objects.filter(
|
tagged_items = TaggedItem.objects.filter(
|
||||||
tag=tag
|
tag=tag
|
||||||
).select_related(
|
|
||||||
'content_type'
|
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'content_object'
|
'content_type', 'content_object'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate a table of all items tagged with this Tag
|
# Generate a table of all items tagged with this Tag
|
||||||
@ -178,7 +176,7 @@ class ObjectConfigContextView(View):
|
|||||||
|
|
||||||
class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
|
class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'extras.view_objectchange'
|
permission_required = 'extras.view_objectchange'
|
||||||
queryset = ObjectChange.objects.select_related('user', 'changed_object_type')
|
queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
|
||||||
filter = filters.ObjectChangeFilter
|
filter = filters.ObjectChangeFilter
|
||||||
filter_form = ObjectChangeFilterForm
|
filter_form = ObjectChangeFilterForm
|
||||||
table = ObjectChangeTable
|
table = ObjectChangeTable
|
||||||
@ -217,7 +215,7 @@ class ObjectChangeLogView(View):
|
|||||||
|
|
||||||
# Gather all changes for this object (and its related objects)
|
# Gather all changes for this object (and its related objects)
|
||||||
content_type = ContentType.objects.get_for_model(model)
|
content_type = ContentType.objects.get_for_model(model)
|
||||||
objectchanges = ObjectChange.objects.select_related(
|
objectchanges = ObjectChange.objects.prefetch_related(
|
||||||
'user', 'changed_object_type'
|
'user', 'changed_object_type'
|
||||||
).filter(
|
).filter(
|
||||||
Q(changed_object_type=content_type, changed_object_id=obj.pk) |
|
Q(changed_object_type=content_type, changed_object_id=obj.pk) |
|
||||||
@ -228,6 +226,13 @@ class ObjectChangeLogView(View):
|
|||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Apply the request context
|
||||||
|
paginate = {
|
||||||
|
'paginator_class': EnhancedPaginator,
|
||||||
|
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||||
|
}
|
||||||
|
RequestConfig(request, paginate).configure(objectchanges_table)
|
||||||
|
|
||||||
# Check whether a header template exists for this model
|
# Check whether a header template exists for this model
|
||||||
base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
|
base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
|
||||||
try:
|
try:
|
||||||
@ -239,7 +244,7 @@ class ObjectChangeLogView(View):
|
|||||||
|
|
||||||
return render(request, 'extras/object_changelog.html', {
|
return render(request, 'extras/object_changelog.html', {
|
||||||
object_var: obj,
|
object_var: obj,
|
||||||
'objectchanges_table': objectchanges_table,
|
'table': objectchanges_table,
|
||||||
'base_template': base_template,
|
'base_template': base_template,
|
||||||
'active_tab': 'changelog',
|
'active_tab': 'changelog',
|
||||||
})
|
})
|
||||||
|
@ -33,7 +33,7 @@ class IPAMFieldChoicesViewSet(FieldChoicesViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VRFViewSet(CustomFieldModelViewSet):
|
class VRFViewSet(CustomFieldModelViewSet):
|
||||||
queryset = VRF.objects.select_related('tenant').prefetch_related('tags').annotate(
|
queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate(
|
||||||
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
||||||
prefix_count=get_subquery(Prefix, 'vrf')
|
prefix_count=get_subquery(Prefix, 'vrf')
|
||||||
)
|
)
|
||||||
@ -58,7 +58,7 @@ class RIRViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class AggregateViewSet(CustomFieldModelViewSet):
|
class AggregateViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Aggregate.objects.select_related('rir').prefetch_related('tags')
|
queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
|
||||||
serializer_class = serializers.AggregateSerializer
|
serializer_class = serializers.AggregateSerializer
|
||||||
filterset_class = filters.AggregateFilter
|
filterset_class = filters.AggregateFilter
|
||||||
|
|
||||||
@ -81,11 +81,7 @@ class RoleViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PrefixViewSet(CustomFieldModelViewSet):
|
class PrefixViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Prefix.objects.select_related(
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags')
|
||||||
'site', 'vrf__tenant', 'tenant', 'vlan', 'role'
|
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.PrefixSerializer
|
serializer_class = serializers.PrefixSerializer
|
||||||
filterset_class = filters.PrefixFilter
|
filterset_class = filters.PrefixFilter
|
||||||
|
|
||||||
@ -263,9 +259,8 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class IPAddressViewSet(CustomFieldModelViewSet):
|
class IPAddressViewSet(CustomFieldModelViewSet):
|
||||||
queryset = IPAddress.objects.select_related(
|
queryset = IPAddress.objects.prefetch_related(
|
||||||
'vrf__tenant', 'tenant', 'nat_inside', 'interface__device__device_type', 'interface__virtual_machine'
|
'vrf__tenant', 'tenant', 'nat_inside', 'interface__device__device_type', 'interface__virtual_machine',
|
||||||
).prefetch_related(
|
|
||||||
'nat_outside', 'tags',
|
'nat_outside', 'tags',
|
||||||
)
|
)
|
||||||
serializer_class = serializers.IPAddressSerializer
|
serializer_class = serializers.IPAddressSerializer
|
||||||
@ -277,7 +272,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VLANGroupViewSet(ModelViewSet):
|
class VLANGroupViewSet(ModelViewSet):
|
||||||
queryset = VLANGroup.objects.select_related('site').annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=Count('vlans')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VLANGroupSerializer
|
serializer_class = serializers.VLANGroupSerializer
|
||||||
@ -289,10 +284,8 @@ class VLANGroupViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VLANViewSet(CustomFieldModelViewSet):
|
class VLANViewSet(CustomFieldModelViewSet):
|
||||||
queryset = VLAN.objects.select_related(
|
queryset = VLAN.objects.prefetch_related(
|
||||||
'site', 'group', 'tenant', 'role'
|
'site', 'group', 'tenant', 'role', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
prefix_count=get_subquery(Prefix, 'role')
|
prefix_count=get_subquery(Prefix, 'role')
|
||||||
)
|
)
|
||||||
@ -305,6 +298,6 @@ class VLANViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ServiceViewSet(ModelViewSet):
|
class ServiceViewSet(ModelViewSet):
|
||||||
queryset = Service.objects.select_related('device').prefetch_related('tags')
|
queryset = Service.objects.prefetch_related('device').prefetch_related('tags')
|
||||||
serializer_class = serializers.ServiceSerializer
|
serializer_class = serializers.ServiceSerializer
|
||||||
filterset_class = filters.ServiceFilter
|
filterset_class = filters.ServiceFilter
|
||||||
|
@ -360,7 +360,7 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
try:
|
try:
|
||||||
device = Device.objects.select_related('device_type').get(**{name: value})
|
device = Device.objects.prefetch_related('device_type').get(**{name: value})
|
||||||
vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
|
vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
|
||||||
return queryset.filter(interface_id__in=vc_interface_ids)
|
return queryset.filter(interface_id__in=vc_interface_ids)
|
||||||
except Device.DoesNotExist:
|
except Device.DoesNotExist:
|
||||||
|
@ -115,7 +115,7 @@ def add_available_vlans(vlan_group, vlans):
|
|||||||
|
|
||||||
class VRFListView(PermissionRequiredMixin, ObjectListView):
|
class VRFListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_vrf'
|
permission_required = 'ipam.view_vrf'
|
||||||
queryset = VRF.objects.select_related('tenant')
|
queryset = VRF.objects.prefetch_related('tenant')
|
||||||
filter = filters.VRFFilter
|
filter = filters.VRFFilter
|
||||||
filter_form = forms.VRFFilterForm
|
filter_form = forms.VRFFilterForm
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
@ -163,7 +163,7 @@ class VRFBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_vrf'
|
permission_required = 'ipam.change_vrf'
|
||||||
queryset = VRF.objects.select_related('tenant')
|
queryset = VRF.objects.prefetch_related('tenant')
|
||||||
filter = filters.VRFFilter
|
filter = filters.VRFFilter
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
form = forms.VRFBulkEditForm
|
form = forms.VRFBulkEditForm
|
||||||
@ -172,7 +172,7 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vrf'
|
permission_required = 'ipam.delete_vrf'
|
||||||
queryset = VRF.objects.select_related('tenant')
|
queryset = VRF.objects.prefetch_related('tenant')
|
||||||
filter = filters.VRFFilter
|
filter = filters.VRFFilter
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
default_return_url = 'ipam:vrf_list'
|
default_return_url = 'ipam:vrf_list'
|
||||||
@ -291,7 +291,7 @@ class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class AggregateListView(PermissionRequiredMixin, ObjectListView):
|
class AggregateListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_aggregate'
|
permission_required = 'ipam.view_aggregate'
|
||||||
queryset = Aggregate.objects.select_related('rir').extra(select={
|
queryset = Aggregate.objects.prefetch_related('rir').extra(select={
|
||||||
'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
|
'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
|
||||||
})
|
})
|
||||||
filter = filters.AggregateFilter
|
filter = filters.AggregateFilter
|
||||||
@ -326,7 +326,7 @@ class AggregateView(PermissionRequiredMixin, View):
|
|||||||
# Find all child prefixes contained by this aggregate
|
# Find all child prefixes contained by this aggregate
|
||||||
child_prefixes = Prefix.objects.filter(
|
child_prefixes = Prefix.objects.filter(
|
||||||
prefix__net_contained_or_equal=str(aggregate.prefix)
|
prefix__net_contained_or_equal=str(aggregate.prefix)
|
||||||
).select_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
).annotate_depth(
|
).annotate_depth(
|
||||||
limit=0
|
limit=0
|
||||||
@ -384,7 +384,7 @@ class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_aggregate'
|
permission_required = 'ipam.change_aggregate'
|
||||||
queryset = Aggregate.objects.select_related('rir')
|
queryset = Aggregate.objects.prefetch_related('rir')
|
||||||
filter = filters.AggregateFilter
|
filter = filters.AggregateFilter
|
||||||
table = tables.AggregateTable
|
table = tables.AggregateTable
|
||||||
form = forms.AggregateBulkEditForm
|
form = forms.AggregateBulkEditForm
|
||||||
@ -393,7 +393,7 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_aggregate'
|
permission_required = 'ipam.delete_aggregate'
|
||||||
queryset = Aggregate.objects.select_related('rir')
|
queryset = Aggregate.objects.prefetch_related('rir')
|
||||||
filter = filters.AggregateFilter
|
filter = filters.AggregateFilter
|
||||||
table = tables.AggregateTable
|
table = tables.AggregateTable
|
||||||
default_return_url = 'ipam:aggregate_list'
|
default_return_url = 'ipam:aggregate_list'
|
||||||
@ -441,7 +441,7 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class PrefixListView(PermissionRequiredMixin, ObjectListView):
|
class PrefixListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_prefix'
|
permission_required = 'ipam.view_prefix'
|
||||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filter = filters.PrefixFilter
|
filter = filters.PrefixFilter
|
||||||
filter_form = forms.PrefixFilterForm
|
filter_form = forms.PrefixFilterForm
|
||||||
table = tables.PrefixDetailTable
|
table = tables.PrefixDetailTable
|
||||||
@ -458,7 +458,7 @@ class PrefixView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
prefix = get_object_or_404(Prefix.objects.select_related(
|
prefix = get_object_or_404(Prefix.objects.prefetch_related(
|
||||||
'vrf', 'site__region', 'tenant__group', 'vlan__group', 'role'
|
'vrf', 'site__region', 'tenant__group', 'vlan__group', 'role'
|
||||||
), pk=pk)
|
), pk=pk)
|
||||||
|
|
||||||
@ -472,7 +472,7 @@ class PrefixView(PermissionRequiredMixin, View):
|
|||||||
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
||||||
).filter(
|
).filter(
|
||||||
prefix__net_contains=str(prefix.prefix)
|
prefix__net_contains=str(prefix.prefix)
|
||||||
).select_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
).annotate_depth()
|
).annotate_depth()
|
||||||
parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
|
parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
|
||||||
@ -483,7 +483,7 @@ class PrefixView(PermissionRequiredMixin, View):
|
|||||||
vrf=prefix.vrf, prefix=str(prefix.prefix)
|
vrf=prefix.vrf, prefix=str(prefix.prefix)
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=prefix.pk
|
pk=prefix.pk
|
||||||
).select_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
)
|
)
|
||||||
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
||||||
@ -505,7 +505,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View):
|
|||||||
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||||
|
|
||||||
# Child prefixes table
|
# Child prefixes table
|
||||||
child_prefixes = prefix.get_child_prefixes().select_related(
|
child_prefixes = prefix.get_child_prefixes().prefetch_related(
|
||||||
'site', 'vlan', 'role',
|
'site', 'vlan', 'role',
|
||||||
).annotate_depth(limit=0)
|
).annotate_depth(limit=0)
|
||||||
|
|
||||||
@ -548,7 +548,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
|
|||||||
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||||
|
|
||||||
# Find all IPAddresses belonging to this Prefix
|
# Find all IPAddresses belonging to this Prefix
|
||||||
ipaddresses = prefix.get_child_ips().select_related(
|
ipaddresses = prefix.get_child_ips().prefetch_related(
|
||||||
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
||||||
)
|
)
|
||||||
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
||||||
@ -608,7 +608,7 @@ class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_prefix'
|
permission_required = 'ipam.change_prefix'
|
||||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filter = filters.PrefixFilter
|
filter = filters.PrefixFilter
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
form = forms.PrefixBulkEditForm
|
form = forms.PrefixBulkEditForm
|
||||||
@ -617,7 +617,7 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_prefix'
|
permission_required = 'ipam.delete_prefix'
|
||||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filter = filters.PrefixFilter
|
filter = filters.PrefixFilter
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
default_return_url = 'ipam:prefix_list'
|
default_return_url = 'ipam:prefix_list'
|
||||||
@ -629,10 +629,8 @@ class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class IPAddressListView(PermissionRequiredMixin, ObjectListView):
|
class IPAddressListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_ipaddress'
|
permission_required = 'ipam.view_ipaddress'
|
||||||
queryset = IPAddress.objects.select_related(
|
queryset = IPAddress.objects.prefetch_related(
|
||||||
'vrf__tenant', 'tenant', 'nat_inside'
|
'vrf__tenant', 'tenant', 'nat_inside', 'interface__device', 'interface__virtual_machine'
|
||||||
).prefetch_related(
|
|
||||||
'interface__device', 'interface__virtual_machine'
|
|
||||||
)
|
)
|
||||||
filter = filters.IPAddressFilter
|
filter = filters.IPAddressFilter
|
||||||
filter_form = forms.IPAddressFilterForm
|
filter_form = forms.IPAddressFilterForm
|
||||||
@ -645,12 +643,12 @@ class IPAddressView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
ipaddress = get_object_or_404(IPAddress.objects.select_related('vrf__tenant', 'tenant'), pk=pk)
|
ipaddress = get_object_or_404(IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'), pk=pk)
|
||||||
|
|
||||||
# Parent prefixes table
|
# Parent prefixes table
|
||||||
parent_prefixes = Prefix.objects.filter(
|
parent_prefixes = Prefix.objects.filter(
|
||||||
vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
|
vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
|
||||||
).select_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
)
|
)
|
||||||
parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
|
parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
|
||||||
@ -661,10 +659,8 @@ class IPAddressView(PermissionRequiredMixin, View):
|
|||||||
vrf=ipaddress.vrf, address=str(ipaddress.address)
|
vrf=ipaddress.vrf, address=str(ipaddress.address)
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=ipaddress.pk
|
pk=ipaddress.pk
|
||||||
).select_related(
|
|
||||||
'nat_inside'
|
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'interface__device'
|
'nat_inside', 'interface__device'
|
||||||
)
|
)
|
||||||
# Exclude anycast IPs if this IP is anycast
|
# Exclude anycast IPs if this IP is anycast
|
||||||
if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
|
if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
|
||||||
@ -742,7 +738,7 @@ class IPAddressAssignView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
queryset = IPAddress.objects.select_related(
|
queryset = IPAddress.objects.prefetch_related(
|
||||||
'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
|
'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
|
||||||
).filter(
|
).filter(
|
||||||
vrf=form.cleaned_data['vrf'],
|
vrf=form.cleaned_data['vrf'],
|
||||||
@ -781,7 +777,7 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_ipaddress'
|
permission_required = 'ipam.change_ipaddress'
|
||||||
queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
|
||||||
filter = filters.IPAddressFilter
|
filter = filters.IPAddressFilter
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
form = forms.IPAddressBulkEditForm
|
form = forms.IPAddressBulkEditForm
|
||||||
@ -790,7 +786,7 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_ipaddress'
|
permission_required = 'ipam.delete_ipaddress'
|
||||||
queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
|
||||||
filter = filters.IPAddressFilter
|
filter = filters.IPAddressFilter
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
default_return_url = 'ipam:ipaddress_list'
|
default_return_url = 'ipam:ipaddress_list'
|
||||||
@ -802,7 +798,7 @@ class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class VLANGroupListView(PermissionRequiredMixin, ObjectListView):
|
class VLANGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_vlangroup'
|
permission_required = 'ipam.view_vlangroup'
|
||||||
queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans'))
|
||||||
filter = filters.VLANGroupFilter
|
filter = filters.VLANGroupFilter
|
||||||
filter_form = forms.VLANGroupFilterForm
|
filter_form = forms.VLANGroupFilterForm
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
@ -829,7 +825,7 @@ class VLANGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vlangroup'
|
permission_required = 'ipam.delete_vlangroup'
|
||||||
queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans'))
|
||||||
filter = filters.VLANGroupFilter
|
filter = filters.VLANGroupFilter
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
default_return_url = 'ipam:vlangroup_list'
|
default_return_url = 'ipam:vlangroup_list'
|
||||||
@ -878,7 +874,7 @@ class VLANGroupVLANsView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
class VLANListView(PermissionRequiredMixin, ObjectListView):
|
class VLANListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_vlan'
|
permission_required = 'ipam.view_vlan'
|
||||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes')
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes')
|
||||||
filter = filters.VLANFilter
|
filter = filters.VLANFilter
|
||||||
filter_form = forms.VLANFilterForm
|
filter_form = forms.VLANFilterForm
|
||||||
table = tables.VLANDetailTable
|
table = tables.VLANDetailTable
|
||||||
@ -890,10 +886,10 @@ class VLANView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
vlan = get_object_or_404(VLAN.objects.select_related(
|
vlan = get_object_or_404(VLAN.objects.prefetch_related(
|
||||||
'site__region', 'tenant__group', 'role'
|
'site__region', 'tenant__group', 'role'
|
||||||
), pk=pk)
|
), pk=pk)
|
||||||
prefixes = Prefix.objects.filter(vlan=vlan).select_related('vrf', 'site', 'role')
|
prefixes = Prefix.objects.filter(vlan=vlan).prefetch_related('vrf', 'site', 'role')
|
||||||
prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
|
prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
|
||||||
prefix_table.exclude = ('vlan',)
|
prefix_table.exclude = ('vlan',)
|
||||||
|
|
||||||
@ -909,7 +905,7 @@ class VLANMembersView(PermissionRequiredMixin, View):
|
|||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
vlan = get_object_or_404(VLAN.objects.all(), pk=pk)
|
vlan = get_object_or_404(VLAN.objects.all(), pk=pk)
|
||||||
members = vlan.get_members().select_related('device', 'virtual_machine')
|
members = vlan.get_members().prefetch_related('device', 'virtual_machine')
|
||||||
|
|
||||||
members_table = tables.VLANMemberTable(members)
|
members_table = tables.VLANMemberTable(members)
|
||||||
|
|
||||||
@ -953,7 +949,7 @@ class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_vlan'
|
permission_required = 'ipam.change_vlan'
|
||||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filter = filters.VLANFilter
|
filter = filters.VLANFilter
|
||||||
table = tables.VLANTable
|
table = tables.VLANTable
|
||||||
form = forms.VLANBulkEditForm
|
form = forms.VLANBulkEditForm
|
||||||
@ -962,7 +958,7 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vlan'
|
permission_required = 'ipam.delete_vlan'
|
||||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filter = filters.VLANFilter
|
filter = filters.VLANFilter
|
||||||
table = tables.VLANTable
|
table = tables.VLANTable
|
||||||
default_return_url = 'ipam:vlan_list'
|
default_return_url = 'ipam:vlan_list'
|
||||||
@ -974,7 +970,7 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class ServiceListView(PermissionRequiredMixin, ObjectListView):
|
class ServiceListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_service'
|
permission_required = 'ipam.view_service'
|
||||||
queryset = Service.objects.select_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filter = filters.ServiceFilter
|
filter = filters.ServiceFilter
|
||||||
filter_form = forms.ServiceFilterForm
|
filter_form = forms.ServiceFilterForm
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
@ -1021,7 +1017,7 @@ class ServiceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
|
|
||||||
class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'ipam.change_service'
|
permission_required = 'ipam.change_service'
|
||||||
queryset = Service.objects.select_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filter = filters.ServiceFilter
|
filter = filters.ServiceFilter
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
form = forms.ServiceBulkEditForm
|
form = forms.ServiceBulkEditForm
|
||||||
@ -1030,7 +1026,7 @@ class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class ServiceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class ServiceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_service'
|
permission_required = 'ipam.delete_service'
|
||||||
queryset = Service.objects.select_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filter = filters.ServiceFilter
|
filter = filters.ServiceFilter
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
default_return_url = 'ipam:service_list'
|
default_return_url = 'ipam:service_list'
|
||||||
|
@ -37,7 +37,7 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
|||||||
def authenticate_credentials(self, key):
|
def authenticate_credentials(self, key):
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
try:
|
try:
|
||||||
token = model.objects.select_related('user').get(key=key)
|
token = model.objects.prefetch_related('user').get(key=key)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise exceptions.AuthenticationFailed("Invalid token")
|
raise exceptions.AuthenticationFailed("Invalid token")
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '2.6.2-dev'
|
VERSION = '2.6.3-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -15,7 +15,6 @@ schema_view = get_schema_view(
|
|||||||
default_version='v2',
|
default_version='v2',
|
||||||
description="API to access NetBox",
|
description="API to access NetBox",
|
||||||
terms_of_service="https://github.com/netbox-community/netbox",
|
terms_of_service="https://github.com/netbox-community/netbox",
|
||||||
contact=openapi.Contact(email="netbox@digitalocean.com"),
|
|
||||||
license=openapi.License(name="Apache v2 License"),
|
license=openapi.License(name="Apache v2 License"),
|
||||||
),
|
),
|
||||||
validators=['flex', 'ssv'],
|
validators=['flex', 'ssv'],
|
||||||
|
@ -15,7 +15,7 @@ from dcim.filters import (
|
|||||||
VirtualChassisFilter,
|
VirtualChassisFilter,
|
||||||
)
|
)
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
Cable, ConsolePort, Device, DeviceType, Interface, PowerFeed, PowerPort, Rack, RackGroup, Site, VirtualChassis
|
Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, RackGroup, Site, VirtualChassis
|
||||||
)
|
)
|
||||||
from dcim.tables import (
|
from dcim.tables import (
|
||||||
CableTable, DeviceDetailTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
|
CableTable, DeviceDetailTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
|
||||||
@ -46,38 +46,38 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'circuits:provider_list',
|
'url': 'circuits:provider_list',
|
||||||
}),
|
}),
|
||||||
('circuit', {
|
('circuit', {
|
||||||
'queryset': Circuit.objects.select_related('type', 'provider', 'tenant').prefetch_related('terminations__site'),
|
'queryset': Circuit.objects.prefetch_related('type', 'provider', 'tenant').prefetch_related('terminations__site'),
|
||||||
'filter': CircuitFilter,
|
'filter': CircuitFilter,
|
||||||
'table': CircuitTable,
|
'table': CircuitTable,
|
||||||
'url': 'circuits:circuit_list',
|
'url': 'circuits:circuit_list',
|
||||||
}),
|
}),
|
||||||
# DCIM
|
# DCIM
|
||||||
('site', {
|
('site', {
|
||||||
'queryset': Site.objects.select_related('region', 'tenant'),
|
'queryset': Site.objects.prefetch_related('region', 'tenant'),
|
||||||
'filter': SiteFilter,
|
'filter': SiteFilter,
|
||||||
'table': SiteTable,
|
'table': SiteTable,
|
||||||
'url': 'dcim:site_list',
|
'url': 'dcim:site_list',
|
||||||
}),
|
}),
|
||||||
('rack', {
|
('rack', {
|
||||||
'queryset': Rack.objects.select_related('site', 'group', 'tenant', 'role'),
|
'queryset': Rack.objects.prefetch_related('site', 'group', 'tenant', 'role'),
|
||||||
'filter': RackFilter,
|
'filter': RackFilter,
|
||||||
'table': RackTable,
|
'table': RackTable,
|
||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
('rackgroup', {
|
('rackgroup', {
|
||||||
'queryset': RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')),
|
'queryset': RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')),
|
||||||
'filter': RackGroupFilter,
|
'filter': RackGroupFilter,
|
||||||
'table': RackGroupTable,
|
'table': RackGroupTable,
|
||||||
'url': 'dcim:rackgroup_list',
|
'url': 'dcim:rackgroup_list',
|
||||||
}),
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
|
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')),
|
||||||
'filter': DeviceTypeFilter,
|
'filter': DeviceTypeFilter,
|
||||||
'table': DeviceTypeTable,
|
'table': DeviceTypeTable,
|
||||||
'url': 'dcim:devicetype_list',
|
'url': 'dcim:devicetype_list',
|
||||||
}),
|
}),
|
||||||
('device', {
|
('device', {
|
||||||
'queryset': Device.objects.select_related(
|
'queryset': Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6',
|
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6',
|
||||||
),
|
),
|
||||||
'filter': DeviceFilter,
|
'filter': DeviceFilter,
|
||||||
@ -85,7 +85,7 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'dcim:device_list',
|
'url': 'dcim:device_list',
|
||||||
}),
|
}),
|
||||||
('virtualchassis', {
|
('virtualchassis', {
|
||||||
'queryset': VirtualChassis.objects.select_related('master').annotate(member_count=Count('members')),
|
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')),
|
||||||
'filter': VirtualChassisFilter,
|
'filter': VirtualChassisFilter,
|
||||||
'table': VirtualChassisTable,
|
'table': VirtualChassisTable,
|
||||||
'url': 'dcim:virtualchassis_list',
|
'url': 'dcim:virtualchassis_list',
|
||||||
@ -104,58 +104,58 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
}),
|
}),
|
||||||
# IPAM
|
# IPAM
|
||||||
('vrf', {
|
('vrf', {
|
||||||
'queryset': VRF.objects.select_related('tenant'),
|
'queryset': VRF.objects.prefetch_related('tenant'),
|
||||||
'filter': VRFFilter,
|
'filter': VRFFilter,
|
||||||
'table': VRFTable,
|
'table': VRFTable,
|
||||||
'url': 'ipam:vrf_list',
|
'url': 'ipam:vrf_list',
|
||||||
}),
|
}),
|
||||||
('aggregate', {
|
('aggregate', {
|
||||||
'queryset': Aggregate.objects.select_related('rir'),
|
'queryset': Aggregate.objects.prefetch_related('rir'),
|
||||||
'filter': AggregateFilter,
|
'filter': AggregateFilter,
|
||||||
'table': AggregateTable,
|
'table': AggregateTable,
|
||||||
'url': 'ipam:aggregate_list',
|
'url': 'ipam:aggregate_list',
|
||||||
}),
|
}),
|
||||||
('prefix', {
|
('prefix', {
|
||||||
'queryset': Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
|
'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
|
||||||
'filter': PrefixFilter,
|
'filter': PrefixFilter,
|
||||||
'table': PrefixTable,
|
'table': PrefixTable,
|
||||||
'url': 'ipam:prefix_list',
|
'url': 'ipam:prefix_list',
|
||||||
}),
|
}),
|
||||||
('ipaddress', {
|
('ipaddress', {
|
||||||
'queryset': IPAddress.objects.select_related('vrf__tenant', 'tenant'),
|
'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'),
|
||||||
'filter': IPAddressFilter,
|
'filter': IPAddressFilter,
|
||||||
'table': IPAddressTable,
|
'table': IPAddressTable,
|
||||||
'url': 'ipam:ipaddress_list',
|
'url': 'ipam:ipaddress_list',
|
||||||
}),
|
}),
|
||||||
('vlan', {
|
('vlan', {
|
||||||
'queryset': VLAN.objects.select_related('site', 'group', 'tenant', 'role'),
|
'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'),
|
||||||
'filter': VLANFilter,
|
'filter': VLANFilter,
|
||||||
'table': VLANTable,
|
'table': VLANTable,
|
||||||
'url': 'ipam:vlan_list',
|
'url': 'ipam:vlan_list',
|
||||||
}),
|
}),
|
||||||
# Secrets
|
# Secrets
|
||||||
('secret', {
|
('secret', {
|
||||||
'queryset': Secret.objects.select_related('role', 'device'),
|
'queryset': Secret.objects.prefetch_related('role', 'device'),
|
||||||
'filter': SecretFilter,
|
'filter': SecretFilter,
|
||||||
'table': SecretTable,
|
'table': SecretTable,
|
||||||
'url': 'secrets:secret_list',
|
'url': 'secrets:secret_list',
|
||||||
}),
|
}),
|
||||||
# Tenancy
|
# Tenancy
|
||||||
('tenant', {
|
('tenant', {
|
||||||
'queryset': Tenant.objects.select_related('group'),
|
'queryset': Tenant.objects.prefetch_related('group'),
|
||||||
'filter': TenantFilter,
|
'filter': TenantFilter,
|
||||||
'table': TenantTable,
|
'table': TenantTable,
|
||||||
'url': 'tenancy:tenant_list',
|
'url': 'tenancy:tenant_list',
|
||||||
}),
|
}),
|
||||||
# Virtualization
|
# Virtualization
|
||||||
('cluster', {
|
('cluster', {
|
||||||
'queryset': Cluster.objects.select_related('type', 'group'),
|
'queryset': Cluster.objects.prefetch_related('type', 'group'),
|
||||||
'filter': ClusterFilter,
|
'filter': ClusterFilter,
|
||||||
'table': ClusterTable,
|
'table': ClusterTable,
|
||||||
'url': 'virtualization:cluster_list',
|
'url': 'virtualization:cluster_list',
|
||||||
}),
|
}),
|
||||||
('virtualmachine', {
|
('virtualmachine', {
|
||||||
'queryset': VirtualMachine.objects.select_related(
|
'queryset': VirtualMachine.objects.prefetch_related(
|
||||||
'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
||||||
),
|
),
|
||||||
'filter': VirtualMachineFilter,
|
'filter': VirtualMachineFilter,
|
||||||
@ -196,6 +196,7 @@ class HomeView(View):
|
|||||||
'cable_count': cables.count(),
|
'cable_count': cables.count(),
|
||||||
'console_connections_count': connected_consoleports.count(),
|
'console_connections_count': connected_consoleports.count(),
|
||||||
'power_connections_count': connected_powerports.count(),
|
'power_connections_count': connected_powerports.count(),
|
||||||
|
'powerpanel_count': PowerPanel.objects.count(),
|
||||||
'powerfeed_count': PowerFeed.objects.count(),
|
'powerfeed_count': PowerFeed.objects.count(),
|
||||||
|
|
||||||
# IPAM
|
# IPAM
|
||||||
@ -223,7 +224,7 @@ class HomeView(View):
|
|||||||
'stats': stats,
|
'stats': stats,
|
||||||
'topology_maps': TopologyMap.objects.filter(site__isnull=True),
|
'topology_maps': TopologyMap.objects.filter(site__isnull=True),
|
||||||
'report_results': ReportResult.objects.order_by('-created')[:10],
|
'report_results': ReportResult.objects.order_by('-created')[:10],
|
||||||
'changelog': ObjectChange.objects.select_related('user', 'changed_object_type')[:50]
|
'changelog': ObjectChange.objects.prefetch_related('user', 'changed_object_type')[:50]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
/*!
|
/*!
|
||||||
* Bootstrap v3.3.7 (http://getbootstrap.com)
|
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
*/
|
*/
|
||||||
.btn-default,
|
.btn-default,
|
||||||
@ -9,9 +9,9 @@
|
|||||||
.btn-info,
|
.btn-info,
|
||||||
.btn-warning,
|
.btn-warning,
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
.btn-default:active,
|
.btn-default:active,
|
||||||
.btn-primary:active,
|
.btn-primary:active,
|
||||||
@ -25,8 +25,8 @@
|
|||||||
.btn-info.active,
|
.btn-info.active,
|
||||||
.btn-warning.active,
|
.btn-warning.active,
|
||||||
.btn-danger.active {
|
.btn-danger.active {
|
||||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
}
|
}
|
||||||
.btn-default.disabled,
|
.btn-default.disabled,
|
||||||
.btn-primary.disabled,
|
.btn-primary.disabled,
|
||||||
@ -47,7 +47,7 @@ fieldset[disabled] .btn-info,
|
|||||||
fieldset[disabled] .btn-warning,
|
fieldset[disabled] .btn-warning,
|
||||||
fieldset[disabled] .btn-danger {
|
fieldset[disabled] .btn-danger {
|
||||||
-webkit-box-shadow: none;
|
-webkit-box-shadow: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.btn-default .badge,
|
.btn-default .badge,
|
||||||
.btn-primary .badge,
|
.btn-primary .badge,
|
||||||
@ -62,15 +62,15 @@ fieldset[disabled] .btn-danger {
|
|||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
.btn-default {
|
.btn-default {
|
||||||
text-shadow: 0 1px 0 #fff;
|
|
||||||
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||||
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
|
||||||
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #dbdbdb;
|
border-color: #dbdbdb;
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
}
|
}
|
||||||
.btn-default:hover,
|
.btn-default:hover,
|
||||||
@ -106,9 +106,9 @@ fieldset[disabled] .btn-default.active {
|
|||||||
}
|
}
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@ -147,9 +147,9 @@ fieldset[disabled] .btn-primary.active {
|
|||||||
}
|
}
|
||||||
.btn-success {
|
.btn-success {
|
||||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
||||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@ -188,9 +188,9 @@ fieldset[disabled] .btn-success.active {
|
|||||||
}
|
}
|
||||||
.btn-info {
|
.btn-info {
|
||||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
||||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@ -229,9 +229,9 @@ fieldset[disabled] .btn-info.active {
|
|||||||
}
|
}
|
||||||
.btn-warning {
|
.btn-warning {
|
||||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
||||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@ -270,9 +270,9 @@ fieldset[disabled] .btn-warning.active {
|
|||||||
}
|
}
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
||||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@ -311,81 +311,81 @@ fieldset[disabled] .btn-danger.active {
|
|||||||
}
|
}
|
||||||
.thumbnail,
|
.thumbnail,
|
||||||
.img-thumbnail {
|
.img-thumbnail {
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
.dropdown-menu > li > a:hover,
|
.dropdown-menu > li > a:hover,
|
||||||
.dropdown-menu > li > a:focus {
|
.dropdown-menu > li > a:focus {
|
||||||
background-color: #e8e8e8;
|
|
||||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
|
background-color: #e8e8e8;
|
||||||
}
|
}
|
||||||
.dropdown-menu > .active > a,
|
.dropdown-menu > .active > a,
|
||||||
.dropdown-menu > .active > a:hover,
|
.dropdown-menu > .active > a:hover,
|
||||||
.dropdown-menu > .active > a:focus {
|
.dropdown-menu > .active > a:focus {
|
||||||
background-color: #2e6da4;
|
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
|
background-color: #2e6da4;
|
||||||
}
|
}
|
||||||
.navbar-default {
|
.navbar-default {
|
||||||
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
|
||||||
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));
|
||||||
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
|
background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
.navbar-default .navbar-nav > .open > a,
|
.navbar-default .navbar-nav > .open > a,
|
||||||
.navbar-default .navbar-nav > .active > a {
|
.navbar-default .navbar-nav > .active > a {
|
||||||
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||||
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
||||||
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
.navbar-brand,
|
.navbar-brand,
|
||||||
.navbar-nav > li > a {
|
.navbar-nav > li > a {
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
.navbar-inverse {
|
.navbar-inverse {
|
||||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||||
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
|
||||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.navbar-inverse .navbar-nav > .open > a,
|
.navbar-inverse .navbar-nav > .open > a,
|
||||||
.navbar-inverse .navbar-nav > .active > a {
|
.navbar-inverse .navbar-nav > .active > a {
|
||||||
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||||
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
||||||
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
|
||||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
.navbar-inverse .navbar-brand,
|
.navbar-inverse .navbar-brand,
|
||||||
.navbar-inverse .navbar-nav > li > a {
|
.navbar-inverse .navbar-nav > li > a {
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
.navbar-static-top,
|
.navbar-static-top,
|
||||||
.navbar-fixed-top,
|
.navbar-fixed-top,
|
||||||
@ -398,120 +398,120 @@ fieldset[disabled] .btn-danger.active {
|
|||||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert {
|
.alert {
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
.alert-success {
|
.alert-success {
|
||||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
||||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #b2dba1;
|
border-color: #b2dba1;
|
||||||
}
|
}
|
||||||
.alert-info {
|
.alert-info {
|
||||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
||||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #9acfea;
|
border-color: #9acfea;
|
||||||
}
|
}
|
||||||
.alert-warning {
|
.alert-warning {
|
||||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
||||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #f5e79e;
|
border-color: #f5e79e;
|
||||||
}
|
}
|
||||||
.alert-danger {
|
.alert-danger {
|
||||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||||
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
||||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #dca7a7;
|
border-color: #dca7a7;
|
||||||
}
|
}
|
||||||
.progress {
|
.progress {
|
||||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||||
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
||||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar-success {
|
.progress-bar-success {
|
||||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
||||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar-info {
|
.progress-bar-info {
|
||||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
||||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar-warning {
|
.progress-bar-warning {
|
||||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
||||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar-danger {
|
.progress-bar-danger {
|
||||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
||||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.progress-bar-striped {
|
.progress-bar-striped {
|
||||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||||
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||||
}
|
}
|
||||||
.list-group {
|
.list-group {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
.list-group-item.active,
|
.list-group-item.active,
|
||||||
.list-group-item.active:hover,
|
.list-group-item.active:hover,
|
||||||
.list-group-item.active:focus {
|
.list-group-item.active:focus {
|
||||||
text-shadow: 0 -1px 0 #286090;
|
text-shadow: 0 -1px 0 #286090;
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #2b669a;
|
border-color: #2b669a;
|
||||||
@ -522,66 +522,66 @@ fieldset[disabled] .btn-danger.active {
|
|||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
.panel {
|
.panel {
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
.panel-default > .panel-heading {
|
.panel-default > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.panel-primary > .panel-heading {
|
.panel-primary > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.panel-success > .panel-heading {
|
.panel-success > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
||||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.panel-info > .panel-heading {
|
.panel-info > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
||||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.panel-warning > .panel-heading {
|
.panel-warning > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
||||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.panel-danger > .panel-heading {
|
.panel-danger > .panel-heading {
|
||||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||||
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
||||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
.well {
|
.well {
|
||||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||||
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
||||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border-color: #dcdcdc;
|
border-color: #dcdcdc;
|
||||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
/*# sourceMappingURL=bootstrap-theme.css.map */
|
/*# sourceMappingURL=bootstrap-theme.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
netbox/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css
vendored
Normal file
6
netbox/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@ -1,6 +1,6 @@
|
|||||||
/*!
|
/*!
|
||||||
* Bootstrap v3.3.7 (http://getbootstrap.com)
|
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under the MIT license
|
* Licensed under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: transition.js v3.3.7
|
* Bootstrap: transition.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#transitions
|
* https://getbootstrap.com/docs/3.4/javascript/#transitions
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
+function ($) {
|
+function ($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
// CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
function transitionEnd() {
|
function transitionEnd() {
|
||||||
@ -50,7 +50,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
return false // explicit for ie8 ( ._.)
|
return false // explicit for ie8 ( ._.)
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://blog.alexmaccaw.com/css-transitions
|
// https://blog.alexmaccaw.com/css-transitions
|
||||||
$.fn.emulateTransitionEnd = function (duration) {
|
$.fn.emulateTransitionEnd = function (duration) {
|
||||||
var called = false
|
var called = false
|
||||||
var $el = this
|
var $el = this
|
||||||
@ -77,10 +77,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: alert.js v3.3.7
|
* Bootstrap: alert.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#alerts
|
* https://getbootstrap.com/docs/3.4/javascript/#alerts
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
$(el).on('click', dismiss, this.close)
|
$(el).on('click', dismiss, this.close)
|
||||||
}
|
}
|
||||||
|
|
||||||
Alert.VERSION = '3.3.7'
|
Alert.VERSION = '3.4.1'
|
||||||
|
|
||||||
Alert.TRANSITION_DURATION = 150
|
Alert.TRANSITION_DURATION = 150
|
||||||
|
|
||||||
@ -109,7 +109,8 @@ if (typeof jQuery === 'undefined') {
|
|||||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||||
}
|
}
|
||||||
|
|
||||||
var $parent = $(selector === '#' ? [] : selector)
|
selector = selector === '#' ? [] : selector
|
||||||
|
var $parent = $(document).find(selector)
|
||||||
|
|
||||||
if (e) e.preventDefault()
|
if (e) e.preventDefault()
|
||||||
|
|
||||||
@ -172,10 +173,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: button.js v3.3.7
|
* Bootstrap: button.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#buttons
|
* https://getbootstrap.com/docs/3.4/javascript/#buttons
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -192,7 +193,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Button.VERSION = '3.3.7'
|
Button.VERSION = '3.4.1'
|
||||||
|
|
||||||
Button.DEFAULTS = {
|
Button.DEFAULTS = {
|
||||||
loadingText: 'loading...'
|
loadingText: 'loading...'
|
||||||
@ -298,10 +299,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: carousel.js v3.3.7
|
* Bootstrap: carousel.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#carousel
|
* https://getbootstrap.com/docs/3.4/javascript/#carousel
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -329,7 +330,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
Carousel.VERSION = '3.3.7'
|
Carousel.VERSION = '3.4.1'
|
||||||
|
|
||||||
Carousel.TRANSITION_DURATION = 600
|
Carousel.TRANSITION_DURATION = 600
|
||||||
|
|
||||||
@ -443,7 +444,9 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
||||||
if ($.support.transition && this.$element.hasClass('slide')) {
|
if ($.support.transition && this.$element.hasClass('slide')) {
|
||||||
$next.addClass(type)
|
$next.addClass(type)
|
||||||
$next[0].offsetWidth // force reflow
|
if (typeof $next === 'object' && $next.length) {
|
||||||
|
$next[0].offsetWidth // force reflow
|
||||||
|
}
|
||||||
$active.addClass(direction)
|
$active.addClass(direction)
|
||||||
$next.addClass(direction)
|
$next.addClass(direction)
|
||||||
$active
|
$active
|
||||||
@ -505,10 +508,17 @@ if (typeof jQuery === 'undefined') {
|
|||||||
// =================
|
// =================
|
||||||
|
|
||||||
var clickHandler = function (e) {
|
var clickHandler = function (e) {
|
||||||
var href
|
|
||||||
var $this = $(this)
|
var $this = $(this)
|
||||||
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
var href = $this.attr('href')
|
||||||
|
if (href) {
|
||||||
|
href = href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = $this.attr('data-target') || href
|
||||||
|
var $target = $(document).find(target)
|
||||||
|
|
||||||
if (!$target.hasClass('carousel')) return
|
if (!$target.hasClass('carousel')) return
|
||||||
|
|
||||||
var options = $.extend({}, $target.data(), $this.data())
|
var options = $.extend({}, $target.data(), $this.data())
|
||||||
var slideIndex = $this.attr('data-slide-to')
|
var slideIndex = $this.attr('data-slide-to')
|
||||||
if (slideIndex) options.interval = false
|
if (slideIndex) options.interval = false
|
||||||
@ -536,10 +546,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: collapse.js v3.3.7
|
* Bootstrap: collapse.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#collapse
|
* https://getbootstrap.com/docs/3.4/javascript/#collapse
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -567,7 +577,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
if (this.options.toggle) this.toggle()
|
if (this.options.toggle) this.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
Collapse.VERSION = '3.3.7'
|
Collapse.VERSION = '3.4.1'
|
||||||
|
|
||||||
Collapse.TRANSITION_DURATION = 350
|
Collapse.TRANSITION_DURATION = 350
|
||||||
|
|
||||||
@ -674,7 +684,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Collapse.prototype.getParent = function () {
|
Collapse.prototype.getParent = function () {
|
||||||
return $(this.options.parent)
|
return $(document).find(this.options.parent)
|
||||||
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
||||||
.each($.proxy(function (i, element) {
|
.each($.proxy(function (i, element) {
|
||||||
var $element = $(element)
|
var $element = $(element)
|
||||||
@ -697,7 +707,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var target = $trigger.attr('data-target')
|
var target = $trigger.attr('data-target')
|
||||||
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
||||||
|
|
||||||
return $(target)
|
return $(document).find(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -749,10 +759,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: dropdown.js v3.3.7
|
* Bootstrap: dropdown.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#dropdowns
|
* https://getbootstrap.com/docs/3.4/javascript/#dropdowns
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -769,7 +779,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
$(element).on('click.bs.dropdown', this.toggle)
|
$(element).on('click.bs.dropdown', this.toggle)
|
||||||
}
|
}
|
||||||
|
|
||||||
Dropdown.VERSION = '3.3.7'
|
Dropdown.VERSION = '3.4.1'
|
||||||
|
|
||||||
function getParent($this) {
|
function getParent($this) {
|
||||||
var selector = $this.attr('data-target')
|
var selector = $this.attr('data-target')
|
||||||
@ -779,7 +789,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||||
}
|
}
|
||||||
|
|
||||||
var $parent = selector && $(selector)
|
var $parent = selector !== '#' ? $(document).find(selector) : null
|
||||||
|
|
||||||
return $parent && $parent.length ? $parent : $this.parent()
|
return $parent && $parent.length ? $parent : $this.parent()
|
||||||
}
|
}
|
||||||
@ -915,10 +925,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: modal.js v3.3.7
|
* Bootstrap: modal.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#modals
|
* https://getbootstrap.com/docs/3.4/javascript/#modals
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -930,15 +940,16 @@ if (typeof jQuery === 'undefined') {
|
|||||||
// ======================
|
// ======================
|
||||||
|
|
||||||
var Modal = function (element, options) {
|
var Modal = function (element, options) {
|
||||||
this.options = options
|
this.options = options
|
||||||
this.$body = $(document.body)
|
this.$body = $(document.body)
|
||||||
this.$element = $(element)
|
this.$element = $(element)
|
||||||
this.$dialog = this.$element.find('.modal-dialog')
|
this.$dialog = this.$element.find('.modal-dialog')
|
||||||
this.$backdrop = null
|
this.$backdrop = null
|
||||||
this.isShown = null
|
this.isShown = null
|
||||||
this.originalBodyPad = null
|
this.originalBodyPad = null
|
||||||
this.scrollbarWidth = 0
|
this.scrollbarWidth = 0
|
||||||
this.ignoreBackdropClick = false
|
this.ignoreBackdropClick = false
|
||||||
|
this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom'
|
||||||
|
|
||||||
if (this.options.remote) {
|
if (this.options.remote) {
|
||||||
this.$element
|
this.$element
|
||||||
@ -949,7 +960,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.VERSION = '3.3.7'
|
Modal.VERSION = '3.4.1'
|
||||||
|
|
||||||
Modal.TRANSITION_DURATION = 300
|
Modal.TRANSITION_DURATION = 300
|
||||||
Modal.BACKDROP_TRANSITION_DURATION = 150
|
Modal.BACKDROP_TRANSITION_DURATION = 150
|
||||||
@ -966,7 +977,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
Modal.prototype.show = function (_relatedTarget) {
|
Modal.prototype.show = function (_relatedTarget) {
|
||||||
var that = this
|
var that = this
|
||||||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||||
|
|
||||||
this.$element.trigger(e)
|
this.$element.trigger(e)
|
||||||
|
|
||||||
@ -1057,8 +1068,8 @@ if (typeof jQuery === 'undefined') {
|
|||||||
.off('focusin.bs.modal') // guard against infinite focus loop
|
.off('focusin.bs.modal') // guard against infinite focus loop
|
||||||
.on('focusin.bs.modal', $.proxy(function (e) {
|
.on('focusin.bs.modal', $.proxy(function (e) {
|
||||||
if (document !== e.target &&
|
if (document !== e.target &&
|
||||||
this.$element[0] !== e.target &&
|
this.$element[0] !== e.target &&
|
||||||
!this.$element.has(e.target).length) {
|
!this.$element.has(e.target).length) {
|
||||||
this.$element.trigger('focus')
|
this.$element.trigger('focus')
|
||||||
}
|
}
|
||||||
}, this))
|
}, this))
|
||||||
@ -1160,7 +1171,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||||
|
|
||||||
this.$element.css({
|
this.$element.css({
|
||||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1185,11 +1196,26 @@ if (typeof jQuery === 'undefined') {
|
|||||||
Modal.prototype.setScrollbar = function () {
|
Modal.prototype.setScrollbar = function () {
|
||||||
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
||||||
this.originalBodyPad = document.body.style.paddingRight || ''
|
this.originalBodyPad = document.body.style.paddingRight || ''
|
||||||
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
var scrollbarWidth = this.scrollbarWidth
|
||||||
|
if (this.bodyIsOverflowing) {
|
||||||
|
this.$body.css('padding-right', bodyPad + scrollbarWidth)
|
||||||
|
$(this.fixedContent).each(function (index, element) {
|
||||||
|
var actualPadding = element.style.paddingRight
|
||||||
|
var calculatedPadding = $(element).css('padding-right')
|
||||||
|
$(element)
|
||||||
|
.data('padding-right', actualPadding)
|
||||||
|
.css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.prototype.resetScrollbar = function () {
|
Modal.prototype.resetScrollbar = function () {
|
||||||
this.$body.css('padding-right', this.originalBodyPad)
|
this.$body.css('padding-right', this.originalBodyPad)
|
||||||
|
$(this.fixedContent).each(function (index, element) {
|
||||||
|
var padding = $(element).data('padding-right')
|
||||||
|
$(element).removeData('padding-right')
|
||||||
|
element.style.paddingRight = padding ? padding : ''
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.prototype.measureScrollbar = function () { // thx walsh
|
Modal.prototype.measureScrollbar = function () { // thx walsh
|
||||||
@ -1207,8 +1233,8 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
function Plugin(option, _relatedTarget) {
|
function Plugin(option, _relatedTarget) {
|
||||||
return this.each(function () {
|
return this.each(function () {
|
||||||
var $this = $(this)
|
var $this = $(this)
|
||||||
var data = $this.data('bs.modal')
|
var data = $this.data('bs.modal')
|
||||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||||
|
|
||||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
||||||
@ -1219,7 +1245,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
var old = $.fn.modal
|
var old = $.fn.modal
|
||||||
|
|
||||||
$.fn.modal = Plugin
|
$.fn.modal = Plugin
|
||||||
$.fn.modal.Constructor = Modal
|
$.fn.modal.Constructor = Modal
|
||||||
|
|
||||||
|
|
||||||
@ -1236,10 +1262,13 @@ if (typeof jQuery === 'undefined') {
|
|||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
||||||
var $this = $(this)
|
var $this = $(this)
|
||||||
var href = $this.attr('href')
|
var href = $this.attr('href')
|
||||||
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
var target = $this.attr('data-target') ||
|
||||||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
(href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
||||||
|
|
||||||
|
var $target = $(document).find(target)
|
||||||
|
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
||||||
|
|
||||||
if ($this.is('a')) e.preventDefault()
|
if ($this.is('a')) e.preventDefault()
|
||||||
|
|
||||||
@ -1255,18 +1284,148 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: tooltip.js v3.3.7
|
* Bootstrap: tooltip.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#tooltip
|
* https://getbootstrap.com/docs/3.4/javascript/#tooltip
|
||||||
* Inspired by the original jQuery.tipsy by Jason Frame
|
* Inspired by the original jQuery.tipsy by Jason Frame
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
|
|
||||||
+function ($) {
|
+function ($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']
|
||||||
|
|
||||||
|
var uriAttrs = [
|
||||||
|
'background',
|
||||||
|
'cite',
|
||||||
|
'href',
|
||||||
|
'itemtype',
|
||||||
|
'longdesc',
|
||||||
|
'poster',
|
||||||
|
'src',
|
||||||
|
'xlink:href'
|
||||||
|
]
|
||||||
|
|
||||||
|
var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
|
||||||
|
|
||||||
|
var DefaultWhitelist = {
|
||||||
|
// Global attributes allowed on any supplied element below.
|
||||||
|
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
||||||
|
a: ['target', 'href', 'title', 'rel'],
|
||||||
|
area: [],
|
||||||
|
b: [],
|
||||||
|
br: [],
|
||||||
|
col: [],
|
||||||
|
code: [],
|
||||||
|
div: [],
|
||||||
|
em: [],
|
||||||
|
hr: [],
|
||||||
|
h1: [],
|
||||||
|
h2: [],
|
||||||
|
h3: [],
|
||||||
|
h4: [],
|
||||||
|
h5: [],
|
||||||
|
h6: [],
|
||||||
|
i: [],
|
||||||
|
img: ['src', 'alt', 'title', 'width', 'height'],
|
||||||
|
li: [],
|
||||||
|
ol: [],
|
||||||
|
p: [],
|
||||||
|
pre: [],
|
||||||
|
s: [],
|
||||||
|
small: [],
|
||||||
|
span: [],
|
||||||
|
sub: [],
|
||||||
|
sup: [],
|
||||||
|
strong: [],
|
||||||
|
u: [],
|
||||||
|
ul: []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pattern that recognizes a commonly useful subset of URLs that are safe.
|
||||||
|
*
|
||||||
|
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
||||||
|
*/
|
||||||
|
var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pattern that matches safe data URLs. Only matches image, video and audio types.
|
||||||
|
*
|
||||||
|
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
||||||
|
*/
|
||||||
|
var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i
|
||||||
|
|
||||||
|
function allowedAttribute(attr, allowedAttributeList) {
|
||||||
|
var attrName = attr.nodeName.toLowerCase()
|
||||||
|
|
||||||
|
if ($.inArray(attrName, allowedAttributeList) !== -1) {
|
||||||
|
if ($.inArray(attrName, uriAttrs) !== -1) {
|
||||||
|
return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var regExp = $(allowedAttributeList).filter(function (index, value) {
|
||||||
|
return value instanceof RegExp
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if a regular expression validates the attribute.
|
||||||
|
for (var i = 0, l = regExp.length; i < l; i++) {
|
||||||
|
if (attrName.match(regExp[i])) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
|
||||||
|
if (unsafeHtml.length === 0) {
|
||||||
|
return unsafeHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sanitizeFn && typeof sanitizeFn === 'function') {
|
||||||
|
return sanitizeFn(unsafeHtml)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE 8 and below don't support createHTMLDocument
|
||||||
|
if (!document.implementation || !document.implementation.createHTMLDocument) {
|
||||||
|
return unsafeHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdDocument = document.implementation.createHTMLDocument('sanitization')
|
||||||
|
createdDocument.body.innerHTML = unsafeHtml
|
||||||
|
|
||||||
|
var whitelistKeys = $.map(whiteList, function (el, i) { return i })
|
||||||
|
var elements = $(createdDocument.body).find('*')
|
||||||
|
|
||||||
|
for (var i = 0, len = elements.length; i < len; i++) {
|
||||||
|
var el = elements[i]
|
||||||
|
var elName = el.nodeName.toLowerCase()
|
||||||
|
|
||||||
|
if ($.inArray(elName, whitelistKeys) === -1) {
|
||||||
|
el.parentNode.removeChild(el)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeList = $.map(el.attributes, function (el) { return el })
|
||||||
|
var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])
|
||||||
|
|
||||||
|
for (var j = 0, len2 = attributeList.length; j < len2; j++) {
|
||||||
|
if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {
|
||||||
|
el.removeAttribute(attributeList[j].nodeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdDocument.body.innerHTML
|
||||||
|
}
|
||||||
|
|
||||||
// TOOLTIP PUBLIC CLASS DEFINITION
|
// TOOLTIP PUBLIC CLASS DEFINITION
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
||||||
@ -1282,7 +1441,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
this.init('tooltip', element, options)
|
this.init('tooltip', element, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.VERSION = '3.3.7'
|
Tooltip.VERSION = '3.4.1'
|
||||||
|
|
||||||
Tooltip.TRANSITION_DURATION = 150
|
Tooltip.TRANSITION_DURATION = 150
|
||||||
|
|
||||||
@ -1299,7 +1458,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
viewport: {
|
viewport: {
|
||||||
selector: 'body',
|
selector: 'body',
|
||||||
padding: 0
|
padding: 0
|
||||||
}
|
},
|
||||||
|
sanitize : true,
|
||||||
|
sanitizeFn : null,
|
||||||
|
whiteList : DefaultWhitelist
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.init = function (type, element, options) {
|
Tooltip.prototype.init = function (type, element, options) {
|
||||||
@ -1307,7 +1469,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
this.type = type
|
this.type = type
|
||||||
this.$element = $(element)
|
this.$element = $(element)
|
||||||
this.options = this.getOptions(options)
|
this.options = this.getOptions(options)
|
||||||
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
||||||
this.inState = { click: false, hover: false, focus: false }
|
this.inState = { click: false, hover: false, focus: false }
|
||||||
|
|
||||||
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
||||||
@ -1340,7 +1502,15 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.getOptions = function (options) {
|
Tooltip.prototype.getOptions = function (options) {
|
||||||
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
|
var dataAttributes = this.$element.data()
|
||||||
|
|
||||||
|
for (var dataAttr in dataAttributes) {
|
||||||
|
if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
|
||||||
|
delete dataAttributes[dataAttr]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options = $.extend({}, this.getDefaults(), dataAttributes, options)
|
||||||
|
|
||||||
if (options.delay && typeof options.delay == 'number') {
|
if (options.delay && typeof options.delay == 'number') {
|
||||||
options.delay = {
|
options.delay = {
|
||||||
@ -1349,6 +1519,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.sanitize) {
|
||||||
|
options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1460,7 +1634,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
.addClass(placement)
|
.addClass(placement)
|
||||||
.data('bs.' + this.type, this)
|
.data('bs.' + this.type, this)
|
||||||
|
|
||||||
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
|
this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
|
||||||
this.$element.trigger('inserted.bs.' + this.type)
|
this.$element.trigger('inserted.bs.' + this.type)
|
||||||
|
|
||||||
var pos = this.getPosition()
|
var pos = this.getPosition()
|
||||||
@ -1562,7 +1736,16 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var $tip = this.tip()
|
var $tip = this.tip()
|
||||||
var title = this.getTitle()
|
var title = this.getTitle()
|
||||||
|
|
||||||
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
if (this.options.html) {
|
||||||
|
if (this.options.sanitize) {
|
||||||
|
title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
$tip.find('.tooltip-inner').html(title)
|
||||||
|
} else {
|
||||||
|
$tip.find('.tooltip-inner').text(title)
|
||||||
|
}
|
||||||
|
|
||||||
$tip.removeClass('fade in top bottom left right')
|
$tip.removeClass('fade in top bottom left right')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1743,6 +1926,9 @@ if (typeof jQuery === 'undefined') {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {
|
||||||
|
return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)
|
||||||
|
}
|
||||||
|
|
||||||
// TOOLTIP PLUGIN DEFINITION
|
// TOOLTIP PLUGIN DEFINITION
|
||||||
// =========================
|
// =========================
|
||||||
@ -1776,10 +1962,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: popover.js v3.3.7
|
* Bootstrap: popover.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#popovers
|
* https://getbootstrap.com/docs/3.4/javascript/#popovers
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -1796,7 +1982,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
||||||
|
|
||||||
Popover.VERSION = '3.3.7'
|
Popover.VERSION = '3.4.1'
|
||||||
|
|
||||||
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
@ -1822,10 +2008,25 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var title = this.getTitle()
|
var title = this.getTitle()
|
||||||
var content = this.getContent()
|
var content = this.getContent()
|
||||||
|
|
||||||
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
if (this.options.html) {
|
||||||
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
var typeContent = typeof content
|
||||||
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
|
||||||
](content)
|
if (this.options.sanitize) {
|
||||||
|
title = this.sanitizeHtml(title)
|
||||||
|
|
||||||
|
if (typeContent === 'string') {
|
||||||
|
content = this.sanitizeHtml(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tip.find('.popover-title').html(title)
|
||||||
|
$tip.find('.popover-content').children().detach().end()[
|
||||||
|
typeContent === 'string' ? 'html' : 'append'
|
||||||
|
](content)
|
||||||
|
} else {
|
||||||
|
$tip.find('.popover-title').text(title)
|
||||||
|
$tip.find('.popover-content').children().detach().end().text(content)
|
||||||
|
}
|
||||||
|
|
||||||
$tip.removeClass('fade top bottom left right in')
|
$tip.removeClass('fade top bottom left right in')
|
||||||
|
|
||||||
@ -1844,8 +2045,8 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
return $e.attr('data-content')
|
return $e.attr('data-content')
|
||||||
|| (typeof o.content == 'function' ?
|
|| (typeof o.content == 'function' ?
|
||||||
o.content.call($e[0]) :
|
o.content.call($e[0]) :
|
||||||
o.content)
|
o.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
Popover.prototype.arrow = function () {
|
Popover.prototype.arrow = function () {
|
||||||
@ -1885,10 +2086,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: scrollspy.js v3.3.7
|
* Bootstrap: scrollspy.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#scrollspy
|
* https://getbootstrap.com/docs/3.4/javascript/#scrollspy
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -1914,7 +2115,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
this.process()
|
this.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollSpy.VERSION = '3.3.7'
|
ScrollSpy.VERSION = '3.4.1'
|
||||||
|
|
||||||
ScrollSpy.DEFAULTS = {
|
ScrollSpy.DEFAULTS = {
|
||||||
offset: 10
|
offset: 10
|
||||||
@ -2058,10 +2259,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: tab.js v3.3.7
|
* Bootstrap: tab.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#tabs
|
* https://getbootstrap.com/docs/3.4/javascript/#tabs
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -2078,7 +2279,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
// jscs:enable requireDollarBeforejQueryAssignment
|
// jscs:enable requireDollarBeforejQueryAssignment
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab.VERSION = '3.3.7'
|
Tab.VERSION = '3.4.1'
|
||||||
|
|
||||||
Tab.TRANSITION_DURATION = 150
|
Tab.TRANSITION_DURATION = 150
|
||||||
|
|
||||||
@ -2107,7 +2308,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
|
|
||||||
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
||||||
|
|
||||||
var $target = $(selector)
|
var $target = $(document).find(selector)
|
||||||
|
|
||||||
this.activate($this.closest('li'), $ul)
|
this.activate($this.closest('li'), $ul)
|
||||||
this.activate($target, $target.parent(), function () {
|
this.activate($target, $target.parent(), function () {
|
||||||
@ -2132,15 +2333,15 @@ if (typeof jQuery === 'undefined') {
|
|||||||
$active
|
$active
|
||||||
.removeClass('active')
|
.removeClass('active')
|
||||||
.find('> .dropdown-menu > .active')
|
.find('> .dropdown-menu > .active')
|
||||||
.removeClass('active')
|
.removeClass('active')
|
||||||
.end()
|
.end()
|
||||||
.find('[data-toggle="tab"]')
|
.find('[data-toggle="tab"]')
|
||||||
.attr('aria-expanded', false)
|
.attr('aria-expanded', false)
|
||||||
|
|
||||||
element
|
element
|
||||||
.addClass('active')
|
.addClass('active')
|
||||||
.find('[data-toggle="tab"]')
|
.find('[data-toggle="tab"]')
|
||||||
.attr('aria-expanded', true)
|
.attr('aria-expanded', true)
|
||||||
|
|
||||||
if (transition) {
|
if (transition) {
|
||||||
element[0].offsetWidth // reflow for transition
|
element[0].offsetWidth // reflow for transition
|
||||||
@ -2152,10 +2353,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
if (element.parent('.dropdown-menu').length) {
|
if (element.parent('.dropdown-menu').length) {
|
||||||
element
|
element
|
||||||
.closest('li.dropdown')
|
.closest('li.dropdown')
|
||||||
.addClass('active')
|
.addClass('active')
|
||||||
.end()
|
.end()
|
||||||
.find('[data-toggle="tab"]')
|
.find('[data-toggle="tab"]')
|
||||||
.attr('aria-expanded', true)
|
.attr('aria-expanded', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
callback && callback()
|
callback && callback()
|
||||||
@ -2214,10 +2415,10 @@ if (typeof jQuery === 'undefined') {
|
|||||||
}(jQuery);
|
}(jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: affix.js v3.3.7
|
* Bootstrap: affix.js v3.4.1
|
||||||
* http://getbootstrap.com/javascript/#affix
|
* https://getbootstrap.com/docs/3.4/javascript/#affix
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2011-2016 Twitter, Inc.
|
* Copyright 2011-2019 Twitter, Inc.
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
@ -2231,7 +2432,9 @@ if (typeof jQuery === 'undefined') {
|
|||||||
var Affix = function (element, options) {
|
var Affix = function (element, options) {
|
||||||
this.options = $.extend({}, Affix.DEFAULTS, options)
|
this.options = $.extend({}, Affix.DEFAULTS, options)
|
||||||
|
|
||||||
this.$target = $(this.options.target)
|
var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)
|
||||||
|
|
||||||
|
this.$target = target
|
||||||
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
||||||
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
||||||
|
|
||||||
@ -2243,7 +2446,7 @@ if (typeof jQuery === 'undefined') {
|
|||||||
this.checkPosition()
|
this.checkPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
Affix.VERSION = '3.3.7'
|
Affix.VERSION = '3.4.1'
|
||||||
|
|
||||||
Affix.RESET = 'affix affix-top affix-bottom'
|
Affix.RESET = 'affix affix-top affix-bottom'
|
||||||
|
|
6
netbox/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js
vendored
Normal file
6
netbox/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -42,8 +42,8 @@ footer p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the search bar in the navigation menu on displays less than 1200px wide */
|
/* Hide the search bar in the navigation menu on displays less than 1250px wide */
|
||||||
@media (max-width: 1199px) {
|
@media (max-width: 1249px) {
|
||||||
#navbar_search {
|
#navbar_search {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -62,8 +62,8 @@ footer p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapse the nav menu on displays less than 960px wide */
|
/* Collapse the nav menu on displays less than 980px wide */
|
||||||
@media (max-width: 959px) {
|
@media (max-width: 979px) {
|
||||||
.navbar-header {
|
.navbar-header {
|
||||||
float: none;
|
float: none;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ $(document).ready(function() {
|
|||||||
// Additional query params
|
// Additional query params
|
||||||
$.each(element.attributes, function(index, attr){
|
$.each(element.attributes, function(index, attr){
|
||||||
if (attr.name.includes("data-additional-query-param-")){
|
if (attr.name.includes("data-additional-query-param-")){
|
||||||
var param_name = attr.name.split("data-additional-query-param-")[1]
|
var param_name = attr.name.split("data-additional-query-param-")[1];
|
||||||
parameters[param_name] = attr.value;
|
parameters[param_name] = attr.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -196,6 +196,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
processResults: function (data) {
|
processResults: function (data) {
|
||||||
var element = this.$element[0];
|
var element = this.$element[0];
|
||||||
|
$(element).children('option').attr('disabled', false);
|
||||||
var results = data.results;
|
var results = data.results;
|
||||||
|
|
||||||
results = results.reduce((results,record) => {
|
results = results.reduce((results,record) => {
|
||||||
@ -233,7 +234,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// Handle the null option, but only add it once
|
// Handle the null option, but only add it once
|
||||||
if (element.getAttribute('data-null-option') && data.previous === null) {
|
if (element.getAttribute('data-null-option') && data.previous === null) {
|
||||||
var null_option = $(element).children()[0]
|
var null_option = $(element).children()[0];
|
||||||
results.unshift({
|
results.unshift({
|
||||||
id: null_option.value,
|
id: null_option.value,
|
||||||
text: null_option.text
|
text: null_option.text
|
||||||
|
66
netbox/scripts/examples.py
Normal file
66
netbox/scripts/examples.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
|
from dcim.constants import *
|
||||||
|
from dcim.models import Device, DeviceRole, DeviceType, Site
|
||||||
|
from extras.scripts import *
|
||||||
|
|
||||||
|
|
||||||
|
class NewBranchScript(Script):
|
||||||
|
script_name = "New Branch"
|
||||||
|
script_description = "Provision a new branch site"
|
||||||
|
script_fields = ['site_name', 'switch_count', 'switch_model']
|
||||||
|
|
||||||
|
site_name = StringVar(
|
||||||
|
description="Name of the new site"
|
||||||
|
)
|
||||||
|
switch_count = IntegerVar(
|
||||||
|
description="Number of access switches to create"
|
||||||
|
)
|
||||||
|
switch_model = ObjectVar(
|
||||||
|
description="Access switch model",
|
||||||
|
queryset=DeviceType.objects.filter(
|
||||||
|
manufacturer__name='Cisco',
|
||||||
|
model__in=['Catalyst 3560X-48T', 'Catalyst 3750X-48T']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
x = BooleanVar(
|
||||||
|
description="Check me out"
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, data):
|
||||||
|
|
||||||
|
# Create the new site
|
||||||
|
site = Site(
|
||||||
|
name=data['site_name'],
|
||||||
|
slug=slugify(data['site_name']),
|
||||||
|
status=SITE_STATUS_PLANNED
|
||||||
|
)
|
||||||
|
site.save()
|
||||||
|
self.log_success("Created new site: {}".format(site))
|
||||||
|
|
||||||
|
# Create access switches
|
||||||
|
switch_role = DeviceRole.objects.get(name='Access Switch')
|
||||||
|
for i in range(1, data['switch_count'] + 1):
|
||||||
|
switch = Device(
|
||||||
|
device_type=data['switch_model'],
|
||||||
|
name='{}-switch{}'.format(site.slug, i),
|
||||||
|
site=site,
|
||||||
|
status=DEVICE_STATUS_PLANNED,
|
||||||
|
device_role=switch_role
|
||||||
|
)
|
||||||
|
switch.save()
|
||||||
|
self.log_success("Created new switch: {}".format(switch))
|
||||||
|
|
||||||
|
# Generate a CSV table of new devices
|
||||||
|
output = [
|
||||||
|
'name,make,model'
|
||||||
|
]
|
||||||
|
for switch in Device.objects.filter(site=site):
|
||||||
|
attrs = [
|
||||||
|
switch.name,
|
||||||
|
switch.device_type.manufacturer.name,
|
||||||
|
switch.device_type.model
|
||||||
|
]
|
||||||
|
output.append(','.join(attrs))
|
||||||
|
|
||||||
|
return '\n'.join(output)
|
54
netbox/scripts/myscripts.py
Normal file
54
netbox/scripts/myscripts.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from dcim.models import Site
|
||||||
|
from extras.scripts import Script, BooleanVar, IntegerVar, ObjectVar, StringVar
|
||||||
|
|
||||||
|
|
||||||
|
class NoInputScript(Script):
|
||||||
|
description = "This script does not require any input"
|
||||||
|
|
||||||
|
def run(self, data):
|
||||||
|
|
||||||
|
self.log_debug("This a debug message.")
|
||||||
|
self.log_info("This an info message.")
|
||||||
|
self.log_success("This a success message.")
|
||||||
|
self.log_warning("This a warning message.")
|
||||||
|
self.log_failure("This a failure message.")
|
||||||
|
|
||||||
|
|
||||||
|
class DemoScript(Script):
|
||||||
|
name = "Script Demo"
|
||||||
|
description = "A quick demonstration of the available field types"
|
||||||
|
|
||||||
|
my_string1 = StringVar(
|
||||||
|
description="Input a string between 3 and 10 characters",
|
||||||
|
min_length=3,
|
||||||
|
max_length=10
|
||||||
|
)
|
||||||
|
my_string2 = StringVar(
|
||||||
|
description="This field enforces a regex: three letters followed by three numbers",
|
||||||
|
regex=r'[a-z]{3}\d{3}'
|
||||||
|
)
|
||||||
|
my_number = IntegerVar(
|
||||||
|
description="Pick a number between 1 and 255 (inclusive)",
|
||||||
|
min_value=1,
|
||||||
|
max_value=255
|
||||||
|
)
|
||||||
|
my_boolean = BooleanVar(
|
||||||
|
description="Use the checkbox to toggle true/false"
|
||||||
|
)
|
||||||
|
my_object = ObjectVar(
|
||||||
|
description="Select a NetBox site",
|
||||||
|
queryset=Site.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, data):
|
||||||
|
|
||||||
|
self.log_info("Your string was {}".format(data['my_string1']))
|
||||||
|
self.log_info("Your second string was {}".format(data['my_string2']))
|
||||||
|
self.log_info("Your number was {}".format(data['my_number']))
|
||||||
|
if data['my_boolean']:
|
||||||
|
self.log_info("You ticked the checkbox")
|
||||||
|
else:
|
||||||
|
self.log_info("You did not tick the checkbox")
|
||||||
|
self.log_info("You chose the sites {}".format(data['my_object']))
|
||||||
|
|
||||||
|
return "Here's some output"
|
@ -46,10 +46,8 @@ class SecretRoleViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class SecretViewSet(ModelViewSet):
|
class SecretViewSet(ModelViewSet):
|
||||||
queryset = Secret.objects.select_related(
|
queryset = Secret.objects.prefetch_related(
|
||||||
'device__primary_ip4', 'device__primary_ip6', 'role',
|
'device__primary_ip4', 'device__primary_ip6', 'role', 'role__users', 'role__groups', 'tags',
|
||||||
).prefetch_related(
|
|
||||||
'role__users', 'role__groups', 'tags',
|
|
||||||
)
|
)
|
||||||
serializer_class = serializers.SecretSerializer
|
serializer_class = serializers.SecretSerializer
|
||||||
filterset_class = filters.SecretFilter
|
filterset_class = filters.SecretFilter
|
||||||
|
@ -69,7 +69,7 @@ class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class SecretListView(PermissionRequiredMixin, ObjectListView):
|
class SecretListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'secrets.view_secret'
|
permission_required = 'secrets.view_secret'
|
||||||
queryset = Secret.objects.select_related('role', 'device')
|
queryset = Secret.objects.prefetch_related('role', 'device')
|
||||||
filter = filters.SecretFilter
|
filter = filters.SecretFilter
|
||||||
filter_form = forms.SecretFilterForm
|
filter_form = forms.SecretFilterForm
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
@ -247,7 +247,7 @@ class SecretBulkImportView(BulkImportView):
|
|||||||
|
|
||||||
class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'secrets.change_secret'
|
permission_required = 'secrets.change_secret'
|
||||||
queryset = Secret.objects.select_related('role', 'device')
|
queryset = Secret.objects.prefetch_related('role', 'device')
|
||||||
filter = filters.SecretFilter
|
filter = filters.SecretFilter
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
form = forms.SecretBulkEditForm
|
form = forms.SecretBulkEditForm
|
||||||
@ -256,7 +256,7 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'secrets.delete_secret'
|
permission_required = 'secrets.delete_secret'
|
||||||
queryset = Secret.objects.select_related('role', 'device')
|
queryset = Secret.objects.prefetch_related('role', 'device')
|
||||||
filter = filters.SecretFilter
|
filter = filters.SecretFilter
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
default_return_url = 'secrets:secret_list'
|
default_return_url = 'secrets:secret_list'
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Server Error</title>
|
<title>Server Error</title>
|
||||||
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
|
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
|
<link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
</head>
|
</head>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}Home{% endblock %} - NetBox</title>
|
<title>{% block title %}Home{% endblock %} - NetBox</title>
|
||||||
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
|
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
|
<link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.css' %}">
|
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'select2-4.0.5/css/select2.min.css' %}">
|
<link rel="stylesheet" href="{% static 'select2-4.0.5/css/select2.min.css' %}">
|
||||||
@ -67,7 +67,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
|
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
|
||||||
<script src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
|
<script src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
|
||||||
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
|
||||||
<script src="{% static 'select2-4.0.5/js/select2.min.js' %}"></script>
|
<script src="{% static 'select2-4.0.5/js/select2.min.js' %}"></script>
|
||||||
<script src="{% static 'clipboard-2.0.4.min.js' %}"></script>
|
<script src="{% static 'clipboard-2.0.4.min.js' %}"></script>
|
||||||
<script src="{% static 'js/forms.js' %}?v{{ settings.VERSION }}"></script>
|
<script src="{% static 'js/forms.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
|
@ -239,7 +239,7 @@
|
|||||||
<td>Platform</td>
|
<td>Platform</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.platform %}
|
{% if device.platform %}
|
||||||
<span>{{ device.platform }}</span>
|
<a href="{{ device.platform.get_absolute_url }}">{{ device.platform }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if obj %}<h1>{{ obj }}</h1>{% endif %}
|
{% if obj %}<h1>{{ obj }}</h1>{% endif %}
|
||||||
{% include 'panel_table.html' with table=objectchanges_table %}
|
{% include 'panel_table.html' %}
|
||||||
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
{% if settings.CHANGELOG_RETENTION %}
|
{% if settings.CHANGELOG_RETENTION %}
|
||||||
<div class="pull-right text-muted">
|
<div class="text-muted">
|
||||||
Changelog retention: {{ settings.CHANGELOG_RETENTION }} days
|
Changelog retention: {% if settings.CHANGELOG_RETENTION == 0 %}Indefinite{% else %}{{ settings.CHANGELOG_RETENTION }} days{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{% include 'utilities/obj_table.html' %}
|
{% include 'utilities/obj_table.html' %}
|
||||||
{% if settings.CHANGELOG_RETENTION %}
|
{% if settings.CHANGELOG_RETENTION %}
|
||||||
<div class="pull-right text-muted">
|
<div class="pull-right text-muted">
|
||||||
Changelog retention: {{ settings.CHANGELOG_RETENTION }} days
|
Changelog retention: {% if settings.CHANGELOG_RETENTION == 0 %}Indefinite{% else %}{{ settings.CHANGELOG_RETENTION }} days{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,6 +115,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="list-group-item-text text-muted">Electrical circuits delivering power from panels</p>
|
<p class="list-group-item-text text-muted">Electrical circuits delivering power from panels</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="list-group-item">
|
||||||
|
{% if perms.dcim.view_powerpanel %}
|
||||||
|
<span class="badge pull-right">{{ stats.powerpanel_count }}</span>
|
||||||
|
<h4 class="list-group-item-heading"><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></h4>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge pull-right"><i class="fa fa-lock"></i></span>
|
||||||
|
<h4 class="list-group-item-heading">Power Panels</h4>
|
||||||
|
{% endif %}
|
||||||
|
<p class="list-group-item-text text-muted">Electrical panels receiving utility power</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% if url_name %}
|
{% if url_name %}<a href="{% url url_name %}?tag={{ tag.slug }}">{% endif %}
|
||||||
<a href="{% url url_name %}?tag={{ tag.slug }}"><span class="label label-default" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span></a>
|
<span class="label label-default" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>
|
||||||
{% else %}
|
{% if url_name %}</a>{% endif %}
|
||||||
<span class="label label-default">{{ tag }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
@ -35,10 +35,8 @@ class TenantGroupViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class TenantViewSet(CustomFieldModelViewSet):
|
class TenantViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Tenant.objects.select_related(
|
queryset = Tenant.objects.prefetch_related(
|
||||||
'group'
|
'group', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
circuit_count=get_subquery(Circuit, 'tenant'),
|
circuit_count=get_subquery(Circuit, 'tenant'),
|
||||||
device_count=get_subquery(Device, 'tenant'),
|
device_count=get_subquery(Device, 'tenant'),
|
||||||
|
@ -56,7 +56,7 @@ class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class TenantListView(PermissionRequiredMixin, ObjectListView):
|
class TenantListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'tenancy.view_tenant'
|
permission_required = 'tenancy.view_tenant'
|
||||||
queryset = Tenant.objects.select_related('group')
|
queryset = Tenant.objects.prefetch_related('group')
|
||||||
filter = filters.TenantFilter
|
filter = filters.TenantFilter
|
||||||
filter_form = forms.TenantFilterForm
|
filter_form = forms.TenantFilterForm
|
||||||
table = tables.TenantTable
|
table = tables.TenantTable
|
||||||
@ -115,7 +115,7 @@ class TenantBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'tenancy.change_tenant'
|
permission_required = 'tenancy.change_tenant'
|
||||||
queryset = Tenant.objects.select_related('group')
|
queryset = Tenant.objects.prefetch_related('group')
|
||||||
filter = filters.TenantFilter
|
filter = filters.TenantFilter
|
||||||
table = tables.TenantTable
|
table = tables.TenantTable
|
||||||
form = forms.TenantBulkEditForm
|
form = forms.TenantBulkEditForm
|
||||||
@ -124,7 +124,7 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'tenancy.delete_tenant'
|
permission_required = 'tenancy.delete_tenant'
|
||||||
queryset = Tenant.objects.select_related('group')
|
queryset = Tenant.objects.prefetch_related('group')
|
||||||
filter = filters.TenantFilter
|
filter = filters.TenantFilter
|
||||||
table = tables.TenantTable
|
table = tables.TenantTable
|
||||||
default_return_url = 'tenancy:tenant_list'
|
default_return_url = 'tenancy:tenant_list'
|
||||||
|
@ -85,9 +85,9 @@ class ChoiceField(Field):
|
|||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
|
|
||||||
# Provide an explicit error message if the request is trying to write a dict
|
# Provide an explicit error message if the request is trying to write a dict or list
|
||||||
if type(data) is dict:
|
if isinstance(data, (dict, list)):
|
||||||
raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary.')
|
raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.')
|
||||||
|
|
||||||
# Check for string representations of boolean/integer values
|
# Check for string representations of boolean/integer values
|
||||||
if hasattr(data, 'lower'):
|
if hasattr(data, 'lower'):
|
||||||
@ -101,10 +101,13 @@ class ChoiceField(Field):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if data not in self._choices:
|
try:
|
||||||
raise ValidationError("{} is not a valid choice.".format(data))
|
if data in self._choices:
|
||||||
|
return data
|
||||||
|
except TypeError: # Input is an unhashable type
|
||||||
|
pass
|
||||||
|
|
||||||
return data
|
raise ValidationError("{} is not a valid choice.".format(data))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def choices(self):
|
def choices(self):
|
||||||
|
@ -3,6 +3,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from dcim.forms import MACAddressField
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +50,14 @@ class MultiValueTimeFilter(django_filters.MultipleChoiceFilter):
|
|||||||
field_class = multivalue_field_factory(forms.TimeField)
|
field_class = multivalue_field_factory(forms.TimeField)
|
||||||
|
|
||||||
|
|
||||||
|
class MACAddressFilter(django_filters.CharFilter):
|
||||||
|
field_class = MACAddressField
|
||||||
|
|
||||||
|
|
||||||
|
class MultiValueMACAddressFilter(django_filters.MultipleChoiceFilter):
|
||||||
|
field_class = multivalue_field_factory(MACAddressField)
|
||||||
|
|
||||||
|
|
||||||
class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
|
class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
|
||||||
"""
|
"""
|
||||||
Filters for a set of Models, including all descendant models within a Tree. Example: [<Region: R1>,<Region: R2>]
|
Filters for a set of Models, including all descendant models within a Tree. Example: [<Region: R1>,<Region: R2>]
|
||||||
|
@ -40,10 +40,8 @@ class ClusterGroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterViewSet(CustomFieldModelViewSet):
|
class ClusterViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Cluster.objects.select_related(
|
queryset = Cluster.objects.prefetch_related(
|
||||||
'type', 'group', 'site',
|
'type', 'group', 'site', 'tags'
|
||||||
).prefetch_related(
|
|
||||||
'tags'
|
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'cluster'),
|
device_count=get_subquery(Device, 'cluster'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
||||||
@ -57,9 +55,9 @@ class ClusterViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualMachineViewSet(CustomFieldModelViewSet):
|
class VirtualMachineViewSet(CustomFieldModelViewSet):
|
||||||
queryset = VirtualMachine.objects.select_related(
|
queryset = VirtualMachine.objects.prefetch_related(
|
||||||
'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6'
|
'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags'
|
||||||
).prefetch_related('tags')
|
)
|
||||||
filterset_class = filters.VirtualMachineFilter
|
filterset_class = filters.VirtualMachineFilter
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
@ -86,7 +84,9 @@ class VirtualMachineViewSet(CustomFieldModelViewSet):
|
|||||||
class InterfaceViewSet(ModelViewSet):
|
class InterfaceViewSet(ModelViewSet):
|
||||||
queryset = Interface.objects.filter(
|
queryset = Interface.objects.filter(
|
||||||
virtual_machine__isnull=False
|
virtual_machine__isnull=False
|
||||||
).select_related('virtual_machine').prefetch_related('tags')
|
).prefetch_related(
|
||||||
|
'virtual_machine', 'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.InterfaceSerializer
|
serializer_class = serializers.InterfaceSerializer
|
||||||
filterset_class = filters.InterfaceFilter
|
filterset_class = filters.InterfaceFilter
|
||||||
|
|
||||||
|
@ -6,7 +6,9 @@ from netaddr.core import AddrFormatError
|
|||||||
from dcim.models import DeviceRole, Interface, Platform, Region, Site
|
from dcim.models import DeviceRole, Interface, Platform, Region, Site
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import (
|
||||||
|
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
||||||
|
)
|
||||||
from .constants import VM_STATUS_CHOICES
|
from .constants import VM_STATUS_CHOICES
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
@ -160,6 +162,10 @@ class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Platform (slug)',
|
label='Platform (slug)',
|
||||||
)
|
)
|
||||||
|
mac_address = MultiValueMACAddressFilter(
|
||||||
|
field_name='interfaces__mac_address',
|
||||||
|
label='MAC address',
|
||||||
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -376,7 +376,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
for family in [4, 6]:
|
for family in [4, 6]:
|
||||||
ip_choices = [(None, '---------')]
|
ip_choices = [(None, '---------')]
|
||||||
# Collect interface IPs
|
# Collect interface IPs
|
||||||
interface_ips = IPAddress.objects.select_related('interface').filter(
|
interface_ips = IPAddress.objects.prefetch_related('interface').filter(
|
||||||
family=family, interface__virtual_machine=self.instance
|
family=family, interface__virtual_machine=self.instance
|
||||||
)
|
)
|
||||||
if interface_ips:
|
if interface_ips:
|
||||||
@ -386,7 +386,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
# Collect NAT IPs
|
# Collect NAT IPs
|
||||||
nat_ips = IPAddress.objects.select_related('nat_inside').filter(
|
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
||||||
family=family, nat_inside__interface__virtual_machine=self.instance
|
family=family, nat_inside__interface__virtual_machine=self.instance
|
||||||
)
|
)
|
||||||
if nat_ips:
|
if nat_ips:
|
||||||
@ -525,7 +525,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
|||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
field_order = [
|
field_order = [
|
||||||
'q', 'cluster_group', 'cluster_type', 'cluster_id', 'status', 'role', 'region', 'site', 'tenant_group',
|
'q', 'cluster_group', 'cluster_type', 'cluster_id', 'status', 'role', 'region', 'site', 'tenant_group',
|
||||||
'tenant', 'platform',
|
'tenant', 'platform', 'mac_address',
|
||||||
]
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -606,6 +606,10 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
|||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
mac_address = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='MAC address'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -96,7 +96,7 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class ClusterListView(PermissionRequiredMixin, ObjectListView):
|
class ClusterListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'virtualization.view_cluster'
|
permission_required = 'virtualization.view_cluster'
|
||||||
queryset = Cluster.objects.select_related('type', 'group', 'site')
|
queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
|
||||||
table = tables.ClusterTable
|
table = tables.ClusterTable
|
||||||
filter = filters.ClusterFilter
|
filter = filters.ClusterFilter
|
||||||
filter_form = forms.ClusterFilterForm
|
filter_form = forms.ClusterFilterForm
|
||||||
@ -109,7 +109,7 @@ class ClusterView(PermissionRequiredMixin, View):
|
|||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
cluster = get_object_or_404(Cluster, pk=pk)
|
cluster = get_object_or_404(Cluster, pk=pk)
|
||||||
devices = Device.objects.filter(cluster=cluster).select_related(
|
devices = Device.objects.filter(cluster=cluster).prefetch_related(
|
||||||
'site', 'rack', 'tenant', 'device_type__manufacturer'
|
'site', 'rack', 'tenant', 'device_type__manufacturer'
|
||||||
)
|
)
|
||||||
device_table = DeviceTable(list(devices), orderable=False)
|
device_table = DeviceTable(list(devices), orderable=False)
|
||||||
@ -148,7 +148,7 @@ class ClusterBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'virtualization.change_cluster'
|
permission_required = 'virtualization.change_cluster'
|
||||||
queryset = Cluster.objects.select_related('type', 'group', 'site')
|
queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
|
||||||
filter = filters.ClusterFilter
|
filter = filters.ClusterFilter
|
||||||
table = tables.ClusterTable
|
table = tables.ClusterTable
|
||||||
form = forms.ClusterBulkEditForm
|
form = forms.ClusterBulkEditForm
|
||||||
@ -157,7 +157,7 @@ class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class ClusterBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class ClusterBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'virtualization.delete_cluster'
|
permission_required = 'virtualization.delete_cluster'
|
||||||
queryset = Cluster.objects.select_related('type', 'group', 'site')
|
queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
|
||||||
filter = filters.ClusterFilter
|
filter = filters.ClusterFilter
|
||||||
table = tables.ClusterTable
|
table = tables.ClusterTable
|
||||||
default_return_url = 'virtualization:cluster_list'
|
default_return_url = 'virtualization:cluster_list'
|
||||||
@ -253,7 +253,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
class VirtualMachineListView(PermissionRequiredMixin, ObjectListView):
|
class VirtualMachineListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'virtualization.view_virtualmachine'
|
permission_required = 'virtualization.view_virtualmachine'
|
||||||
queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6')
|
queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6')
|
||||||
filter = filters.VirtualMachineFilter
|
filter = filters.VirtualMachineFilter
|
||||||
filter_form = forms.VirtualMachineFilterForm
|
filter_form = forms.VirtualMachineFilterForm
|
||||||
table = tables.VirtualMachineDetailTable
|
table = tables.VirtualMachineDetailTable
|
||||||
@ -265,7 +265,7 @@ class VirtualMachineView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
virtualmachine = get_object_or_404(VirtualMachine.objects.select_related('tenant__group'), pk=pk)
|
virtualmachine = get_object_or_404(VirtualMachine.objects.prefetch_related('tenant__group'), pk=pk)
|
||||||
interfaces = Interface.objects.filter(virtual_machine=virtualmachine)
|
interfaces = Interface.objects.filter(virtual_machine=virtualmachine)
|
||||||
services = Service.objects.filter(virtual_machine=virtualmachine)
|
services = Service.objects.filter(virtual_machine=virtualmachine)
|
||||||
|
|
||||||
@ -309,7 +309,7 @@ class VirtualMachineBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|||||||
|
|
||||||
class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
permission_required = 'virtualization.change_virtualmachine'
|
permission_required = 'virtualization.change_virtualmachine'
|
||||||
queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role')
|
queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
|
||||||
filter = filters.VirtualMachineFilter
|
filter = filters.VirtualMachineFilter
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
form = forms.VirtualMachineBulkEditForm
|
form = forms.VirtualMachineBulkEditForm
|
||||||
@ -318,7 +318,7 @@ class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class VirtualMachineBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VirtualMachineBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'virtualization.delete_virtualmachine'
|
permission_required = 'virtualization.delete_virtualmachine'
|
||||||
queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role')
|
queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
|
||||||
filter = filters.VirtualMachineFilter
|
filter = filters.VirtualMachineFilter
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
default_return_url = 'virtualization:virtualmachine_list'
|
default_return_url = 'virtualization:virtualmachine_list'
|
||||||
|
Loading…
Reference in New Issue
Block a user