mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Merge branch 'develop' into develop-2.3
This commit is contained in:
commit
0714a40509
@ -24,7 +24,7 @@ sudo pip install django-auth-ldap
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
|
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](http://django-auth-ldap.readthedocs.io/).
|
||||||
|
|
||||||
## General Server Configuration
|
## General Server Configuration
|
||||||
|
|
||||||
@ -52,6 +52,8 @@ AUTH_LDAP_BIND_PASSWORD = "demo"
|
|||||||
LDAP_IGNORE_CERT_ERRORS = True
|
LDAP_IGNORE_CERT_ERRORS = True
|
||||||
```
|
```
|
||||||
|
|
||||||
|
STARTTLS can be configured by setting `AUTH_LDAP_START_TLS = True` and using the `ldap://` URI scheme.
|
||||||
|
|
||||||
## User Authentication
|
## User Authentication
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
@ -78,8 +80,8 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
# User Groups for Permissions
|
# User Groups for Permissions
|
||||||
!!! Info
|
!!! info
|
||||||
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
|
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
|
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
|
||||||
|
@ -174,7 +174,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
|
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
|
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
|
||||||
|
@ -776,6 +776,8 @@ class InventoryItemSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class WritableInventoryItemSerializer(ValidatedModelSerializer):
|
class WritableInventoryItemSerializer(ValidatedModelSerializer):
|
||||||
|
# Provide a default value to satisfy UniqueTogetherValidator
|
||||||
|
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
|
@ -163,7 +163,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ['serial', 'type', 'width', 'u_height', 'desc_units']
|
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -340,7 +340,7 @@ class DeviceRoleFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ['name', 'slug', 'color']
|
fields = ['name', 'slug', 'color', 'vm_role']
|
||||||
|
|
||||||
|
|
||||||
class PlatformFilter(django_filters.FilterSet):
|
class PlatformFilter(django_filters.FilterSet):
|
||||||
@ -476,7 +476,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = ['serial']
|
fields = ['serial', 'position']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -175,7 +175,7 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('sites')),
|
queryset=Tenant.objects.annotate(filter_count=Count('sites')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -371,17 +371,17 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
group_id = FilterChoiceField(
|
group_id = FilterChoiceField(
|
||||||
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks')),
|
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks')),
|
||||||
label='Rack group',
|
label='Rack group',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('racks')),
|
queryset=Tenant.objects.annotate(filter_count=Count('racks')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
|
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -423,12 +423,12 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
|
|||||||
group_id = FilterChoiceField(
|
group_id = FilterChoiceField(
|
||||||
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks__reservations')),
|
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks__reservations')),
|
||||||
label='Rack group',
|
label='Rack group',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('rackreservations')),
|
queryset=Tenant.objects.annotate(filter_count=Count('rackreservations')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1053,7 +1053,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
rack_id = FilterChoiceField(
|
rack_id = FilterChoiceField(
|
||||||
queryset=Rack.objects.annotate(filter_count=Count('devices')),
|
queryset=Rack.objects.annotate(filter_count=Count('devices')),
|
||||||
label='Rack',
|
label='Rack',
|
||||||
null_option=(0, 'None'),
|
null_label='-- None --',
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=DeviceRole.objects.annotate(filter_count=Count('devices')),
|
queryset=DeviceRole.objects.annotate(filter_count=Count('devices')),
|
||||||
@ -1062,7 +1062,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('devices')),
|
queryset=Tenant.objects.annotate(filter_count=Count('devices')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None'),
|
null_label='-- None --',
|
||||||
)
|
)
|
||||||
manufacturer_id = FilterChoiceField(queryset=Manufacturer.objects.all(), label='Manufacturer')
|
manufacturer_id = FilterChoiceField(queryset=Manufacturer.objects.all(), label='Manufacturer')
|
||||||
device_type_id = FilterChoiceField(
|
device_type_id = FilterChoiceField(
|
||||||
@ -1074,7 +1074,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
platform = FilterChoiceField(
|
platform = FilterChoiceField(
|
||||||
queryset=Platform.objects.annotate(filter_count=Count('devices')),
|
queryset=Platform.objects.annotate(filter_count=Count('devices')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None'),
|
null_label='-- None --',
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=device_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=device_status_choices, required=False)
|
||||||
mac_address = forms.CharField(required=False, label='MAC address')
|
mac_address = forms.CharField(required=False, label='MAC address')
|
||||||
|
@ -389,6 +389,7 @@ class PlatformTable(BaseTable):
|
|||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
device_count = tables.Column(verbose_name='Devices')
|
device_count = tables.Column(verbose_name='Devices')
|
||||||
|
vm_count = tables.Column(verbose_name='VMs')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=PLATFORM_ACTIONS,
|
template_code=PLATFORM_ACTIONS,
|
||||||
@ -398,7 +399,7 @@ class PlatformTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ('pk', 'name', 'manufacturer', 'device_count', 'slug', 'napalm_driver', 'actions')
|
fields = ('pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -802,7 +802,10 @@ class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PlatformListView(ObjectListView):
|
class PlatformListView(ObjectListView):
|
||||||
queryset = Platform.objects.annotate(device_count=Count('devices'))
|
queryset = Platform.objects.annotate(
|
||||||
|
device_count=Count('devices', distinct=True),
|
||||||
|
vm_count=Count('virtual_machines', distinct=True)
|
||||||
|
)
|
||||||
table = tables.PlatformTable
|
table = tables.PlatformTable
|
||||||
template_name = 'dcim/platform_list.html'
|
template_name = 'dcim/platform_list.html'
|
||||||
|
|
||||||
|
@ -78,8 +78,11 @@ class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = VRF
|
model = VRF
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('vrfs')), to_field_name='slug',
|
tenant = FilterChoiceField(
|
||||||
null_option=(0, None))
|
queryset=Tenant.objects.annotate(filter_count=Count('vrfs')),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_label='-- None --'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -368,23 +371,23 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
queryset=VRF.objects.annotate(filter_count=Count('prefixes')),
|
queryset=VRF.objects.annotate(filter_count=Count('prefixes')),
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_option=(0, 'Global')
|
null_label='-- Global --'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('prefixes')),
|
queryset=Tenant.objects.annotate(filter_count=Count('prefixes')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=prefix_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=prefix_status_choices, required=False)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('prefixes')),
|
queryset=Site.objects.annotate(filter_count=Count('prefixes')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=Role.objects.annotate(filter_count=Count('prefixes')),
|
queryset=Role.objects.annotate(filter_count=Count('prefixes')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
|
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
|
||||||
|
|
||||||
@ -719,12 +722,12 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
queryset=VRF.objects.annotate(filter_count=Count('ip_addresses')),
|
queryset=VRF.objects.annotate(filter_count=Count('ip_addresses')),
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_option=(0, 'Global')
|
null_label='-- Global --'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')),
|
queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False)
|
||||||
role = forms.MultipleChoiceField(choices=ipaddress_role_choices, required=False)
|
role = forms.MultipleChoiceField(choices=ipaddress_role_choices, required=False)
|
||||||
@ -766,7 +769,7 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
|||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('vlan_groups')),
|
queryset=Site.objects.annotate(filter_count=Count('vlan_groups')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'Global')
|
null_label='-- Global --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -896,23 +899,23 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('vlans')),
|
queryset=Site.objects.annotate(filter_count=Count('vlans')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'Global')
|
null_label='-- Global --'
|
||||||
)
|
)
|
||||||
group_id = FilterChoiceField(
|
group_id = FilterChoiceField(
|
||||||
queryset=VLANGroup.objects.annotate(filter_count=Count('vlans')),
|
queryset=VLANGroup.objects.annotate(filter_count=Count('vlans')),
|
||||||
label='VLAN group',
|
label='VLAN group',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('vlans')),
|
queryset=Tenant.objects.annotate(filter_count=Count('vlans')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=vlan_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=vlan_status_choices, required=False)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=Role.objects.annotate(filter_count=Count('vlans')),
|
queryset=Role.objects.annotate(filter_count=Count('vlans')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,10 +298,20 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
|
|
||||||
def get_child_ips(self):
|
def get_child_ips(self):
|
||||||
"""
|
"""
|
||||||
Return all IPAddresses within this Prefix.
|
Return all IPAddresses within this Prefix and VRF.
|
||||||
"""
|
"""
|
||||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||||
|
|
||||||
|
def get_available_prefixes(self):
|
||||||
|
"""
|
||||||
|
Return all available Prefixes within this prefix as an IPSet.
|
||||||
|
"""
|
||||||
|
prefix = netaddr.IPSet(self.prefix)
|
||||||
|
child_prefixes = netaddr.IPSet([child.prefix for child in self.get_child_prefixes()])
|
||||||
|
available_prefixes = prefix - child_prefixes
|
||||||
|
|
||||||
|
return available_prefixes
|
||||||
|
|
||||||
def get_available_ips(self):
|
def get_available_ips(self):
|
||||||
"""
|
"""
|
||||||
Return all available IPs within this prefix as an IPSet.
|
Return all available IPs within this prefix as an IPSet.
|
||||||
@ -319,15 +329,23 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
|
|
||||||
return available_ips
|
return available_ips
|
||||||
|
|
||||||
|
def get_first_available_prefix(self):
|
||||||
|
"""
|
||||||
|
Return the first available child prefix within the prefix (or None).
|
||||||
|
"""
|
||||||
|
available_prefixes = self.get_available_prefixes()
|
||||||
|
if not available_prefixes:
|
||||||
|
return None
|
||||||
|
return available_prefixes.iter_cidrs()[0]
|
||||||
|
|
||||||
def get_first_available_ip(self):
|
def get_first_available_ip(self):
|
||||||
"""
|
"""
|
||||||
Return the first available IP within the prefix (or None).
|
Return the first available IP within the prefix (or None).
|
||||||
"""
|
"""
|
||||||
available_ips = self.get_available_ips()
|
available_ips = self.get_available_ips()
|
||||||
if available_ips:
|
if not available_ips:
|
||||||
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
|
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
|
||||||
|
|
||||||
def get_utilization(self):
|
def get_utilization(self):
|
||||||
"""
|
"""
|
||||||
@ -345,17 +363,6 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
prefix_size -= 2
|
prefix_size -= 2
|
||||||
return int(float(child_count) / prefix_size * 100)
|
return int(float(child_count) / prefix_size * 100)
|
||||||
|
|
||||||
@property
|
|
||||||
def new_subnet(self):
|
|
||||||
if self.family == 4:
|
|
||||||
if self.prefix.prefixlen <= 30:
|
|
||||||
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
|
|
||||||
return None
|
|
||||||
if self.family == 6:
|
|
||||||
if self.prefix.prefixlen <= 126:
|
|
||||||
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class IPAddressManager(models.Manager):
|
class IPAddressManager(models.Manager):
|
||||||
|
|
||||||
|
@ -48,13 +48,7 @@ PREFIX_LINK = """
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-nowrap" style="padding-left: {{ record.depth }}9px">
|
<span class="text-nowrap" style="padding-left: {{ record.depth }}9px">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if parent.vrf %}&vrf={{ parent.vrf.pk }}{% endif %}{% if parent.site %}&site={{ parent.site.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
|
<a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if parent.vrf %}&vrf={{ parent.vrf.pk }}{% endif %}{% if parent.site %}&site={{ parent.site.pk }}{% endif %}{% if parent.tenant %}&tenant_group={{ parent.tenant.group.pk }}&tenant={{ parent.tenant.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
|
||||||
</span>
|
|
||||||
"""
|
|
||||||
|
|
||||||
PREFIX_LINK_BRIEF = """
|
|
||||||
<span style="padding-left: {{ record.depth }}0px">
|
|
||||||
<a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if parent.vrf %}&vrf={{ parent.vrf.pk }}{% endif %}{% if parent.site %}&site={{ parent.site.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
|
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ urlpatterns = [
|
|||||||
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
|
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
|
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
||||||
|
url(r'^prefixes/(?P<pk>\d+)/prefixes/$', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
|
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
|
||||||
|
|
||||||
# IP addresses
|
# IP addresses
|
||||||
|
@ -476,6 +476,20 @@ class PrefixView(View):
|
|||||||
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
||||||
duplicate_prefix_table.exclude = ('vrf',)
|
duplicate_prefix_table.exclude = ('vrf',)
|
||||||
|
|
||||||
|
return render(request, 'ipam/prefix.html', {
|
||||||
|
'prefix': prefix,
|
||||||
|
'aggregate': aggregate,
|
||||||
|
'parent_prefix_table': parent_prefix_table,
|
||||||
|
'duplicate_prefix_table': duplicate_prefix_table,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixPrefixesView(View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||||
|
|
||||||
# Child prefixes table
|
# Child prefixes table
|
||||||
child_prefixes = Prefix.objects.filter(
|
child_prefixes = Prefix.objects.filter(
|
||||||
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
||||||
@ -484,15 +498,16 @@ class PrefixView(View):
|
|||||||
).annotate_depth(limit=0)
|
).annotate_depth(limit=0)
|
||||||
if child_prefixes:
|
if child_prefixes:
|
||||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
||||||
child_prefix_table = tables.PrefixDetailTable(child_prefixes)
|
|
||||||
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
child_prefix_table.columns.show('pk')
|
prefix_table.columns.show('pk')
|
||||||
|
|
||||||
paginate = {
|
paginate = {
|
||||||
'klass': EnhancedPaginator,
|
'klass': EnhancedPaginator,
|
||||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(child_prefix_table)
|
RequestConfig(request, paginate).configure(prefix_table)
|
||||||
|
|
||||||
# Compile permissions list for rendering the object table
|
# Compile permissions list for rendering the object table
|
||||||
permissions = {
|
permissions = {
|
||||||
@ -501,15 +516,12 @@ class PrefixView(View):
|
|||||||
'delete': request.user.has_perm('ipam.delete_prefix'),
|
'delete': request.user.has_perm('ipam.delete_prefix'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/prefix.html', {
|
return render(request, 'ipam/prefix_prefixes.html', {
|
||||||
'prefix': prefix,
|
'prefix': prefix,
|
||||||
'aggregate': aggregate,
|
'first_available_prefix': prefix.get_first_available_prefix(),
|
||||||
'parent_prefix_table': parent_prefix_table,
|
'prefix_table': prefix_table,
|
||||||
'child_prefix_table': child_prefix_table,
|
|
||||||
'duplicate_prefix_table': duplicate_prefix_table,
|
|
||||||
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf or '0', prefix.prefix),
|
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'return_url': prefix.get_absolute_url(),
|
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -544,6 +556,7 @@ class PrefixIPAddressesView(View):
|
|||||||
|
|
||||||
return render(request, 'ipam/prefix_ipaddresses.html', {
|
return render(request, 'ipam/prefix_ipaddresses.html', {
|
||||||
'prefix': prefix,
|
'prefix': prefix,
|
||||||
|
'first_available_ip': prefix.get_first_available_ip(),
|
||||||
'ip_table': ip_table,
|
'ip_table': ip_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
|
@ -20,6 +20,9 @@ class FormlessBrowsableAPIRenderer(BrowsableAPIRenderer):
|
|||||||
def show_form_for_method(self, *args, **kwargs):
|
def show_form_for_method(self, *args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_filter_form(self, data, view, request):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Authentication
|
# Authentication
|
||||||
|
@ -58,9 +58,10 @@ $(document).ready(function() {
|
|||||||
// Glean configured hostnames/interfaces from the DOM
|
// Glean configured hostnames/interfaces from the DOM
|
||||||
var configured_device = row.children('td.configured_device').attr('data');
|
var configured_device = row.children('td.configured_device').attr('data');
|
||||||
var configured_interface = row.children('td.configured_interface').attr('data');
|
var configured_interface = row.children('td.configured_interface').attr('data');
|
||||||
|
var configured_interface_short = null;
|
||||||
if (configured_interface) {
|
if (configured_interface) {
|
||||||
// Match long-form IOS names against short ones (e.g. Gi0/1 == GigabitEthernet0/1).
|
// Match long-form IOS names against short ones (e.g. Gi0/1 == GigabitEthernet0/1).
|
||||||
configured_interface = configured_interface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, "$1$2");
|
configured_interface_short = configured_interface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, "$1$2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up hostnames/interfaces learned via LLDP
|
// Clean up hostnames/interfaces learned via LLDP
|
||||||
@ -76,6 +77,8 @@ $(document).ready(function() {
|
|||||||
row.addClass('info');
|
row.addClass('info');
|
||||||
} else if (configured_device == lldp_device && configured_interface == lldp_interface) {
|
} else if (configured_device == lldp_device && configured_interface == lldp_interface) {
|
||||||
row.addClass('success');
|
row.addClass('success');
|
||||||
|
} else if (configured_device == lldp_device && configured_interface_short == lldp_interface) {
|
||||||
|
row.addClass('success');
|
||||||
} else {
|
} else {
|
||||||
row.addClass('danger');
|
row.addClass('danger');
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
Import device types
|
Import device types
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'inc/export_button.html' with obj_type='devicetypes' %}
|
{% include 'inc/export_button.html' with obj_type='device types' %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Device Types{% endblock %}</h1>
|
<h1>{% block title %}Device Types{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
Import rack groups
|
Import rack groups
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'inc/export_button.html' with obj_type='rackgroups' %}
|
{% include 'inc/export_button.html' with obj_type='rack groups' %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Rack Groups{% endblock %}</h1>
|
<h1>{% block title %}Rack Groups{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -22,8 +22,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.get_first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ prefix.vrf.pk }}&site={{ prefix.site.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||||
|
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
Add an IP Address
|
Add an IP Address
|
||||||
</a>
|
</a>
|
||||||
@ -45,5 +50,6 @@
|
|||||||
{% include 'inc/created_updated.html' with obj=prefix %}
|
{% include 'inc/created_updated.html' with obj=prefix %}
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
||||||
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
|
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
|
||||||
|
<li role="presentation"{% if active_tab == 'prefixes' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_prefixes' pk=prefix.pk %}">Child Prefixes <span class="badge">{{ prefix.get_child_prefixes.count }}</span></a></li>
|
||||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a></li>
|
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -139,15 +139,4 @@
|
|||||||
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %}
|
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
{% if child_prefix_table.rows %}
|
|
||||||
{% include 'utilities/obj_table.html' with table=child_prefix_table table_template='panel_table.html' heading='Child Prefixes' parent=prefix bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
|
||||||
{% elif prefix.new_subnet %}
|
|
||||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ prefix.new_subnet }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.site %}&site={{ prefix.site.pk }}{% endif %}" class="btn btn-success">
|
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
{% block title %}{{ prefix }} - IP Addresses{% endblock %}
|
{% block title %}{{ prefix }} - IP Addresses{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
|
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
12
netbox/templates/ipam/prefix_prefixes.html
Normal file
12
netbox/templates/ipam/prefix_prefixes.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ prefix }} - Prefixes{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'ipam/inc/prefix_header.html' with active_tab='prefixes' %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% include 'utilities/obj_table.html' with table=prefix_table table_template='panel_table.html' heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -13,12 +13,14 @@
|
|||||||
{% for obj_type in results %}
|
{% for obj_type in results %}
|
||||||
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
|
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
|
||||||
{% include 'panel_table.html' with table=obj_type.table hide_paginator=True %}
|
{% include 'panel_table.html' with table=obj_type.table hide_paginator=True %}
|
||||||
{% if obj_type.table.page.has_next %}
|
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
|
||||||
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
|
<span class="fa fa-arrow-right" aria-hidden="true"></span>
|
||||||
<span class="fa fa-arrow-right" aria-hidden="true"></span>
|
{% if obj_type.table.page.has_next %}
|
||||||
See all {{ obj_type.table.page.paginator.count }} results
|
See all {{ obj_type.table.page.paginator.count }} results
|
||||||
</a>
|
{% else %}
|
||||||
{% endif %}
|
Refine search
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +81,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
group = FilterChoiceField(
|
group = FilterChoiceField(
|
||||||
queryset=TenantGroup.objects.annotate(filter_count=Count('tenants')),
|
queryset=TenantGroup.objects.annotate(filter_count=Count('tenants')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|||||||
"""
|
"""
|
||||||
iterator = forms.models.ModelChoiceIterator
|
iterator = forms.models.ModelChoiceIterator
|
||||||
|
|
||||||
def __init__(self, null_value=0, null_label='None', *args, **kwargs):
|
def __init__(self, null_value=0, null_label='-- None --', *args, **kwargs):
|
||||||
self.null_value = null_value
|
self.null_value = null_value
|
||||||
self.null_label = null_label
|
self.null_label = null_label
|
||||||
super(NullableModelMultipleChoiceField, self).__init__(*args, **kwargs)
|
super(NullableModelMultipleChoiceField, self).__init__(*args, **kwargs)
|
||||||
|
@ -407,11 +407,25 @@ class SlugField(forms.SlugField):
|
|||||||
self.widget.attrs['slug-source'] = slug_source
|
self.widget.attrs['slug-source'] = slug_source
|
||||||
|
|
||||||
|
|
||||||
class FilterChoiceFieldMixin(object):
|
class FilterChoiceIterator(forms.models.ModelChoiceIterator):
|
||||||
iterator = forms.models.ModelChoiceIterator
|
|
||||||
|
|
||||||
def __init__(self, null_option=None, *args, **kwargs):
|
def __iter__(self):
|
||||||
self.null_option = null_option
|
# Filter on "empty" choice using FILTERS_NULL_CHOICE_VALUE (instead of an empty string)
|
||||||
|
if self.field.null_label is not None:
|
||||||
|
yield (settings.FILTERS_NULL_CHOICE_VALUE, self.field.null_label)
|
||||||
|
queryset = self.queryset.all()
|
||||||
|
# Can't use iterator() when queryset uses prefetch_related()
|
||||||
|
if not queryset._prefetch_related_lookups:
|
||||||
|
queryset = queryset.iterator()
|
||||||
|
for obj in queryset:
|
||||||
|
yield self.choice(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterChoiceFieldMixin(object):
|
||||||
|
iterator = FilterChoiceIterator
|
||||||
|
|
||||||
|
def __init__(self, null_label=None, *args, **kwargs):
|
||||||
|
self.null_label = null_label
|
||||||
if 'required' not in kwargs:
|
if 'required' not in kwargs:
|
||||||
kwargs['required'] = False
|
kwargs['required'] = False
|
||||||
if 'widget' not in kwargs:
|
if 'widget' not in kwargs:
|
||||||
@ -424,15 +438,6 @@ class FilterChoiceFieldMixin(object):
|
|||||||
return '{} ({})'.format(label, obj.filter_count)
|
return '{} ({})'.format(label, obj.filter_count)
|
||||||
return label
|
return label
|
||||||
|
|
||||||
def _get_choices(self):
|
|
||||||
if hasattr(self, '_choices'):
|
|
||||||
return self._choices
|
|
||||||
if self.null_option is not None:
|
|
||||||
return itertools.chain([self.null_option], self.iterator(self))
|
|
||||||
return self.iterator(self)
|
|
||||||
|
|
||||||
choices = property(_get_choices, forms.ChoiceField._set_choices)
|
|
||||||
|
|
||||||
|
|
||||||
class FilterChoiceField(FilterChoiceFieldMixin, forms.ModelMultipleChoiceField):
|
class FilterChoiceField(FilterChoiceFieldMixin, forms.ModelMultipleChoiceField):
|
||||||
pass
|
pass
|
||||||
|
@ -4,7 +4,7 @@ import sys
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import ProgrammingError
|
from django.db import ProgrammingError
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -61,6 +61,10 @@ class ExceptionHandlingMiddleware(object):
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Ignore Http404s (defer to Django's built-in 404 handling)
|
||||||
|
if isinstance(exception, Http404):
|
||||||
|
return
|
||||||
|
|
||||||
# Determine the type of exception
|
# Determine the type of exception
|
||||||
if isinstance(exception, ProgrammingError):
|
if isinstance(exception, ProgrammingError):
|
||||||
template_name = 'exceptions/programming_error.html'
|
template_name = 'exceptions/programming_error.html'
|
||||||
|
@ -309,8 +309,14 @@ class BulkCreateView(View):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
|
# Set initial values for visible form fields from query args
|
||||||
|
initial = {}
|
||||||
|
for field in getattr(self.model_form._meta, 'fields', []):
|
||||||
|
if request.GET.get(field):
|
||||||
|
initial[field] = request.GET[field]
|
||||||
|
|
||||||
form = self.form()
|
form = self.form()
|
||||||
model_form = self.model_form()
|
model_form = self.model_form(initial=initial)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
||||||
|
@ -84,6 +84,17 @@ class VirtualMachineFilter(CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Cluster group (slug)',
|
label='Cluster group (slug)',
|
||||||
)
|
)
|
||||||
|
cluster_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__type',
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
label='Cluster type (ID)',
|
||||||
|
)
|
||||||
|
cluster_type = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__type__slug',
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Cluster type (slug)',
|
||||||
|
)
|
||||||
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
label='Cluster (ID)',
|
label='Cluster (ID)',
|
||||||
|
@ -137,13 +137,13 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
group = FilterChoiceField(
|
group = FilterChoiceField(
|
||||||
queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),
|
queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None'),
|
null_label='-- None --',
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('clusters')),
|
queryset=Site.objects.annotate(filter_count=Count('clusters')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None'),
|
null_label='-- None --',
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -338,7 +338,12 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
cluster_group = FilterChoiceField(
|
cluster_group = FilterChoiceField(
|
||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
|
)
|
||||||
|
cluster_type = FilterChoiceField(
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
cluster_id = FilterChoiceField(
|
cluster_id = FilterChoiceField(
|
||||||
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
||||||
@ -347,23 +352,23 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
|
queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
|
queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')),
|
queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
platform = FilterChoiceField(
|
platform = FilterChoiceField(
|
||||||
queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')),
|
queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.name,
|
self.name,
|
||||||
self.type.name,
|
self.type.name,
|
||||||
self.group.name if self.group else None,
|
self.group.name if self.group else None,
|
||||||
|
self.site.name if self.site else None,
|
||||||
self.comments,
|
self.comments,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user