mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge branch 'develop' into develop-2.3
This commit is contained in:
commit
6b101d2c49
@ -4,6 +4,7 @@ import django_tables2 as tables
|
||||
from django.utils.safestring import mark_safe
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from tenancy.tables import COL_TENANT
|
||||
from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
|
||||
@ -75,7 +76,7 @@ class CircuitTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
cid = tables.LinkColumn(verbose_name='ID')
|
||||
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
|
||||
termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')
|
||||
|
||||
|
@ -22,6 +22,10 @@ from .models import (
|
||||
|
||||
|
||||
class RegionFilter(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
label='Parent region (ID)',
|
||||
@ -37,6 +41,15 @@ class RegionFilter(django_filters.FilterSet):
|
||||
model = Region
|
||||
fields = ['name', 'slug']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
qs_filter = (
|
||||
Q(name__icontains=value) |
|
||||
Q(slug__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
@ -623,6 +636,10 @@ class DeviceBayFilter(DeviceComponentFilterSet):
|
||||
|
||||
|
||||
class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
label='Parent inventory item (ID)',
|
||||
@ -641,7 +658,19 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ['name', 'part_id', 'serial', 'discovered']
|
||||
fields = ['name', 'part_id', 'serial', 'asset_tag', 'discovered']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
qs_filter = (
|
||||
Q(name__icontains=value) |
|
||||
Q(part_id__icontains=value) |
|
||||
Q(serial__iexact=value) |
|
||||
Q(asset_tag__iexact=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class VirtualChassisFilter(django_filters.FilterSet):
|
||||
|
@ -92,6 +92,11 @@ class RegionCSVForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class RegionFilterForm(BootstrapMixin, forms.Form):
|
||||
model = Site
|
||||
q = forms.CharField(required=False, label='Search')
|
||||
|
||||
|
||||
#
|
||||
# Sites
|
||||
#
|
||||
@ -2212,6 +2217,50 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
|
||||
fields = ['name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description']
|
||||
|
||||
|
||||
class InventoryItemCSVForm(forms.ModelForm):
|
||||
device = FlexibleModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text='Device name or ID',
|
||||
error_messages={
|
||||
'invalid_choice': 'Device not found.',
|
||||
}
|
||||
)
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False,
|
||||
help_text='Manufacturer name',
|
||||
error_messages={
|
||||
'invalid_choice': 'Invalid manufacturer.',
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ['device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description']
|
||||
|
||||
|
||||
class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=InventoryItem.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
|
||||
part_id = forms.CharField(max_length=50, required=False, label='Part ID')
|
||||
description = forms.CharField(max_length=100, required=False)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['manufacturer', 'part_id', 'description']
|
||||
|
||||
|
||||
class InventoryItemFilterForm(BootstrapMixin, forms.Form):
|
||||
model = InventoryItem
|
||||
q = forms.CharField(required=False, label='Search')
|
||||
manufacturer = FilterChoiceField(
|
||||
queryset=Manufacturer.objects.annotate(filter_count=Count('inventory_items')),
|
||||
to_field_name='slug',
|
||||
null_label='-- None --'
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Virtual chassis
|
||||
#
|
||||
|
@ -1558,6 +1558,10 @@ class InventoryItem(models.Model):
|
||||
discovered = models.BooleanField(default=False, verbose_name='Discovered')
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
|
||||
csv_headers = [
|
||||
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['device__id', 'parent__id', 'name']
|
||||
unique_together = ['device', 'parent', 'name']
|
||||
@ -1568,6 +1572,17 @@ class InventoryItem(models.Model):
|
||||
def get_absolute_url(self):
|
||||
return self.device.get_absolute_url()
|
||||
|
||||
def to_csv(self):
|
||||
return csv_format([
|
||||
self.device.name or '{' + self.device.pk + '}',
|
||||
self.name,
|
||||
self.manufacturer.name if self.manufacturer else None,
|
||||
self.part_id,
|
||||
self.serial,
|
||||
self.asset_tag,
|
||||
self.description
|
||||
])
|
||||
|
||||
|
||||
#
|
||||
# Virtual chassis
|
||||
|
@ -3,11 +3,13 @@ from __future__ import unicode_literals
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from tenancy.tables import COL_TENANT
|
||||
from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Platform, PowerOutlet,
|
||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site, VirtualChassis
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, InventoryItem, Manufacturer, Platform,
|
||||
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site,
|
||||
VirtualChassis,
|
||||
)
|
||||
|
||||
REGION_LINK = """
|
||||
@ -147,7 +149,7 @@ class SiteTable(BaseTable):
|
||||
name = tables.LinkColumn()
|
||||
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||
region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Site
|
||||
@ -199,7 +201,7 @@ class RackTable(BaseTable):
|
||||
name = tables.LinkColumn()
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
role = tables.TemplateColumn(RACK_ROLE)
|
||||
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
||||
|
||||
@ -223,7 +225,7 @@ class RackImportTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
facility_id = tables.Column(verbose_name='Facility ID')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
u_height = tables.Column(verbose_name='Height (U)')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
@ -396,7 +398,7 @@ class DeviceTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.TemplateColumn(template_code=DEVICE_LINK)
|
||||
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
|
||||
@ -423,7 +425,7 @@ class DeviceDetailTable(DeviceTable):
|
||||
class DeviceImportTable(BaseTable):
|
||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
|
||||
position = tables.Column(verbose_name='Position')
|
||||
@ -523,6 +525,20 @@ class InterfaceConnectionTable(BaseTable):
|
||||
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
||||
|
||||
|
||||
#
|
||||
# InventoryItems
|
||||
#
|
||||
|
||||
class InventoryItemTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
device = tables.LinkColumn('dcim:device_inventory', args=[Accessor('device.pk')])
|
||||
manufacturer = tables.Column(accessor=Accessor('manufacturer.name'), verbose_name='Manufacturer')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = InventoryItem
|
||||
fields = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description')
|
||||
|
||||
|
||||
#
|
||||
# Virtual chassis
|
||||
#
|
||||
|
@ -199,9 +199,13 @@ urlpatterns = [
|
||||
url(r'^device-bays/rename/$', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'),
|
||||
|
||||
# Inventory items
|
||||
url(r'^devices/(?P<device>\d+)/inventory-items/add/$', views.InventoryItemEditView.as_view(), name='inventoryitem_add'),
|
||||
url(r'^inventory-items/$', views.InventoryItemListView.as_view(), name='inventoryitem_list'),
|
||||
url(r'^inventory-items/import/$', views.InventoryItemBulkImportView.as_view(), name='inventoryitem_import'),
|
||||
url(r'^inventory-items/edit/$', views.InventoryItemBulkEditView.as_view(), name='inventoryitem_bulk_edit'),
|
||||
url(r'^inventory-items/delete/$', views.InventoryItemBulkDeleteView.as_view(), name='inventoryitem_bulk_delete'),
|
||||
url(r'^inventory-items/(?P<pk>\d+)/edit/$', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'),
|
||||
url(r'^inventory-items/(?P<pk>\d+)/delete/$', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'),
|
||||
url(r'^devices/(?P<device>\d+)/inventory-items/add/$', views.InventoryItemEditView.as_view(), name='inventoryitem_add'),
|
||||
|
||||
# Console/power/interface connections
|
||||
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||
|
@ -125,6 +125,8 @@ class BulkDisconnectView(View):
|
||||
|
||||
class RegionListView(ObjectListView):
|
||||
queryset = Region.objects.annotate(site_count=Count('sites'))
|
||||
filter = filters.RegionFilter
|
||||
filter_form = forms.RegionFilterForm
|
||||
table = tables.RegionTable
|
||||
template_name = 'dcim/region_list.html'
|
||||
|
||||
@ -2010,6 +2012,14 @@ class InterfaceConnectionsListView(ObjectListView):
|
||||
# Inventory items
|
||||
#
|
||||
|
||||
class InventoryItemListView(ObjectListView):
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||
filter = filters.InventoryItemFilter
|
||||
filter_form = forms.InventoryItemFilterForm
|
||||
table = tables.InventoryItemTable
|
||||
template_name = 'dcim/inventoryitem_list.html'
|
||||
|
||||
|
||||
class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'dcim.change_inventoryitem'
|
||||
model = InventoryItem
|
||||
@ -2020,6 +2030,9 @@ class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
|
||||
return obj
|
||||
|
||||
def get_return_url(self, request, obj):
|
||||
return reverse('dcim:device_inventory', kwargs={'pk': obj.device.pk})
|
||||
|
||||
|
||||
class InventoryItemDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'dcim.delete_inventoryitem'
|
||||
@ -2169,3 +2182,33 @@ class VCMembershipEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
class VCMembershipDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'dcim.delete_vcmembership'
|
||||
model = VCMembership
|
||||
parent_field = 'device'
|
||||
|
||||
def get_return_url(self, request, obj):
|
||||
return reverse('dcim:device_inventory', kwargs={'pk': obj.device.pk})
|
||||
|
||||
|
||||
class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
permission_required = 'dcim.add_inventoryitem'
|
||||
model_form = forms.InventoryItemCSVForm
|
||||
table = tables.InventoryItemTable
|
||||
default_return_url = 'dcim:inventoryitem_list'
|
||||
|
||||
|
||||
class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_inventoryitem'
|
||||
cls = InventoryItem
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||
filter = filters.InventoryItemFilter
|
||||
table = tables.InventoryItemTable
|
||||
form = forms.InventoryItemBulkEditForm
|
||||
default_return_url = 'dcim:inventoryitem_list'
|
||||
|
||||
|
||||
class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_inventoryitem'
|
||||
cls = InventoryItem
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||
table = tables.InventoryItemTable
|
||||
template_name = 'dcim/inventoryitem_bulk_delete.html'
|
||||
default_return_url = 'dcim:inventoryitem_list'
|
||||
|
@ -283,24 +283,23 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
def get_child_prefixes(self):
|
||||
"""
|
||||
Return all child Prefixes within this Prefix.
|
||||
Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
|
||||
Prefixes belonging to any VRF.
|
||||
"""
|
||||
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||
|
||||
def get_available_prefixes(self):
|
||||
"""
|
||||
Return all available prefixes within this Prefix.
|
||||
"""
|
||||
prefix = netaddr.IPSet(self.prefix)
|
||||
child_prefixes = netaddr.IPSet([p.prefix for p in self.get_child_prefixes()])
|
||||
available_prefixes = prefix - child_prefixes
|
||||
return available_prefixes
|
||||
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
|
||||
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
|
||||
else:
|
||||
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||
|
||||
def get_child_ips(self):
|
||||
"""
|
||||
Return all IPAddresses within this Prefix and VRF.
|
||||
Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
|
||||
child IPAddresses belonging to any VRF.
|
||||
"""
|
||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
|
||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
|
||||
else:
|
||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||
|
||||
def get_available_prefixes(self):
|
||||
"""
|
||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from tenancy.tables import COL_TENANT
|
||||
from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
||||
|
||||
@ -131,9 +132,9 @@ VLANGROUP_ACTIONS = """
|
||||
|
||||
TENANT_LINK = """
|
||||
{% if record.tenant %}
|
||||
<a href="{% url 'tenancy:tenant' slug=record.tenant.slug %}">{{ record.tenant }}</a>
|
||||
<a href="{% url 'tenancy:tenant' slug=record.tenant.slug %}" title="{{ record.tenant.description }}">{{ record.tenant }}</a>
|
||||
{% elif record.vrf.tenant %}
|
||||
<a href="{% url 'tenancy:tenant' slug=record.vrf.tenant.slug %}">{{ record.vrf.tenant }}</a>*
|
||||
<a href="{% url 'tenancy:tenant' slug=record.vrf.tenant.slug %}" title="{{ record.vrf.tenant.description }}">{{ record.vrf.tenant }}</a>*
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
@ -148,7 +149,7 @@ class VRFTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn()
|
||||
rd = tables.Column(verbose_name='RD')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VRF
|
||||
@ -239,7 +240,7 @@ class PrefixTable(BaseTable):
|
||||
prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
|
||||
status = tables.TemplateColumn(STATUS_LABEL)
|
||||
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
||||
tenant = tables.TemplateColumn(TENANT_LINK)
|
||||
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
|
||||
role = tables.TemplateColumn(PREFIX_ROLE_LINK)
|
||||
@ -268,7 +269,7 @@ class IPAddressTable(BaseTable):
|
||||
address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
|
||||
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
||||
status = tables.TemplateColumn(STATUS_LABEL)
|
||||
tenant = tables.TemplateColumn(TENANT_LINK)
|
||||
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
|
||||
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
|
||||
interface = tables.Column(orderable=False)
|
||||
|
||||
@ -330,7 +331,7 @@ class VLANTable(BaseTable):
|
||||
vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
status = tables.TemplateColumn(STATUS_LABEL)
|
||||
role = tables.TemplateColumn(VLAN_ROLE_LINK)
|
||||
|
||||
|
@ -491,11 +491,11 @@ class PrefixPrefixesView(View):
|
||||
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||
|
||||
# Child prefixes table
|
||||
child_prefixes = Prefix.objects.filter(
|
||||
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
||||
).select_related(
|
||||
child_prefixes = prefix.get_child_prefixes().select_related(
|
||||
'site', 'vlan', 'role',
|
||||
).annotate_depth(limit=0)
|
||||
|
||||
# Annotate available prefixes
|
||||
if child_prefixes:
|
||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
||||
|
||||
|
@ -15,7 +15,7 @@ from circuits.tables import CircuitTable, ProviderTable
|
||||
from dcim.filters import DeviceFilter, DeviceTypeFilter, RackFilter, SiteFilter
|
||||
from dcim.models import ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, Site
|
||||
from dcim.tables import DeviceDetailTable, DeviceTypeTable, RackTable, SiteTable
|
||||
from extras.models import TopologyMap, UserAction
|
||||
from extras.models import ReportResult, TopologyMap, UserAction
|
||||
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
||||
from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
|
||||
@ -177,6 +177,7 @@ class HomeView(View):
|
||||
'search_form': SearchForm(),
|
||||
'stats': stats,
|
||||
'topology_maps': TopologyMap.objects.filter(site__isnull=True),
|
||||
'report_results': ReportResult.objects.order_by('-created')[:10],
|
||||
'recent_activity': UserAction.objects.select_related('user')[:50]
|
||||
})
|
||||
|
||||
|
@ -64,13 +64,14 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<div class="panel-footer text-right">
|
||||
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span> Add Inventory Item
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-success">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add Inventory Item
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_inventoryitem %}
|
||||
<a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||
<a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
5
netbox/templates/dcim/inventoryitem_bulk_delete.html
Normal file
5
netbox/templates/dcim/inventoryitem_bulk_delete.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends 'utilities/obj_bulk_delete.html' %}
|
||||
|
||||
{% block message_extra %}
|
||||
<p class="text-center text-danger"><i class="fa fa-warning"></i> This will also delete all child inventory items of those listed.</p>
|
||||
{% endblock %}
|
23
netbox/templates/dcim/inventoryitem_list.html
Normal file
23
netbox/templates/dcim/inventoryitem_list.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
<a href="{% url 'dcim:inventoryitem_import' %}" class="btn btn-info">
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import inventory items
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include 'inc/export_button.html' with obj_type='inventory items' %}
|
||||
</div>
|
||||
<h1>{% block title %}Inventory Items{% endblock %}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:inventoryitem_bulk_edit' bulk_delete_url='dcim:inventoryitem_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -17,8 +17,11 @@
|
||||
</div>
|
||||
<h1>{% block title %}Regions{% endblock %}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:region_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% if report.result.failed %}
|
||||
{% if result.failed %}
|
||||
<label class="label label-danger">Failed</label>
|
||||
{% elif report.result %}
|
||||
{% elif result %}
|
||||
<label class="label label-success">Passed</label>
|
||||
{% else %}
|
||||
<label class="label label-default">N/A</label>
|
||||
|
@ -22,7 +22,7 @@
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{{ report.name }}{% include 'extras/inc/report_label.html' %}</h1>
|
||||
<h1>{{ report.name }}{% include 'extras/inc/report_label.html' with result=report.result %}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% if report.description %}
|
||||
|
@ -24,7 +24,7 @@
|
||||
<a href="{% url 'extras:report' name=report.full_name %}" name="report.{{ report.name }}"><strong>{{ report.name }}</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
{% include 'extras/inc/report_label.html' %}
|
||||
{% include 'extras/inc/report_label.html' with result=report.result %}
|
||||
</td>
|
||||
<td>{{ report.description|default:"" }}</td>
|
||||
{% if report.result %}
|
||||
|
@ -150,6 +150,21 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if report_results %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Reports</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
{% for result in report_results %}
|
||||
<span>
|
||||
<td><a href="{% url 'extras:report' name=result.report %}">{{ result.report }}</a></td>
|
||||
<td class="text-right"><span title="{{ result.created }}">{% include 'extras/inc/report_label.html' %}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Recent Activity</strong>
|
||||
|
@ -104,7 +104,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|contains:'/dcim/device,/dcim/manufacturers/,/dcim/platforms/,-connections/' %} active{% endif %}">
|
||||
<li class="dropdown{% if request.path|contains:'/dcim/device,/dcim/manufacturers/,/dcim/platforms/,-connections/,/dcim/inventory-items/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Devices <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Devices</li>
|
||||
@ -156,6 +156,16 @@
|
||||
<a href="{% url 'dcim:manufacturer_list' %}">Manufacturers</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Inventory</li>
|
||||
<li>
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:inventoryitem_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:inventoryitem_list' %}">Inventory Items</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Connections</li>
|
||||
<li>
|
||||
{% if perms.dcim.change_consoleport %}
|
||||
|
@ -9,7 +9,8 @@
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Confirm Bulk Deletion</strong></div>
|
||||
<div class="panel-body">
|
||||
<strong>Warning:</strong> The following operation will delete {{ table.rows|length }} {{ obj_type_plural }}. Please carefully review the {{ obj_type_plural }} to be deleted and confirm below.
|
||||
<p><strong>Warning:</strong> The following operation will delete {{ table.rows|length }} {{ obj_type_plural }}. Please carefully review the {{ obj_type_plural }} to be deleted and confirm below.</p>
|
||||
{% block message_extra %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,6 +11,14 @@ TENANTGROUP_ACTIONS = """
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
COL_TENANT = """
|
||||
{% if record.tenant %}
|
||||
<a href="{% url 'tenancy:tenant' slug=record.tenant.slug %}" title="{{ record.tenant.description }}">{{ record.tenant }}</a>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
# Tenant groups
|
||||
|
@ -114,7 +114,7 @@ def example_choices(field, arg=3):
|
||||
if len(examples) == arg:
|
||||
examples.append('etc.')
|
||||
break
|
||||
if not id:
|
||||
if not id or not label:
|
||||
continue
|
||||
examples.append(label)
|
||||
return ', '.join(examples) or 'None'
|
||||
|
@ -4,6 +4,7 @@ import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim.models import Interface
|
||||
from tenancy.tables import COL_TENANT
|
||||
from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
|
||||
@ -24,7 +25,7 @@ VIRTUALMACHINE_STATUS = """
|
||||
"""
|
||||
|
||||
VIRTUALMACHINE_ROLE = """
|
||||
<label class="label" style="background-color: #{{ record.role.color }}">{{ value }}</label>
|
||||
{% if record.role %}<label class="label" style="background-color: #{{ record.role.color }}">{{ value }}</label>{% else %}—{% endif %}
|
||||
"""
|
||||
|
||||
VIRTUALMACHINE_PRIMARY_IP = """
|
||||
@ -97,7 +98,7 @@ class VirtualMachineTable(BaseTable):
|
||||
status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
|
||||
cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
|
||||
role = tables.TemplateColumn(VIRTUALMACHINE_ROLE)
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VirtualMachine
|
||||
|
Loading…
Reference in New Issue
Block a user