mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
commit
d3e2241ff7
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.1.4
|
placeholder: v3.1.5
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.1.4
|
placeholder: v3.1.5
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
# NetBox v3.1
|
# NetBox v3.1
|
||||||
|
|
||||||
|
## v3.1.5 (2022-01-06)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#8231](https://github.com/netbox-community/netbox/issues/8231) - Use in-page dialogs for confirming object deletion
|
||||||
|
* [#8244](https://github.com/netbox-community/netbox/issues/8244) - Add length & length unit fields to cable filter form
|
||||||
|
* [#8252](https://github.com/netbox-community/netbox/issues/8252) - Linkify type and group columns in clusters table
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#8213](https://github.com/netbox-community/netbox/issues/8213) - Fix ValueError exception under prefix IP addresses view
|
||||||
|
* [#8224](https://github.com/netbox-community/netbox/issues/8224) - Fix KeyError exception when creating FHRP group with IP address and protocol "other"
|
||||||
|
* [#8226](https://github.com/netbox-community/netbox/issues/8226) - Honor return URL after populating a device bay
|
||||||
|
* [#8228](https://github.com/netbox-community/netbox/issues/8228) - Optional ChoiceVar fields should not force a selection
|
||||||
|
* [#8255](https://github.com/netbox-community/netbox/issues/8255) - Fix bulk editing of authentication parameters for wireless LANs and links
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v3.1.4 (2022-01-03)
|
## v3.1.4 (2022-01-03)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
@ -578,7 +578,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
|||||||
field_groups = [
|
field_groups = [
|
||||||
['q', 'tag'],
|
['q', 'tag'],
|
||||||
['site_id', 'rack_id', 'device_id'],
|
['site_id', 'rack_id', 'device_id'],
|
||||||
['type', 'status', 'color'],
|
['type', 'status', 'color', 'length', 'length_unit'],
|
||||||
['tenant_group_id', 'tenant_id'],
|
['tenant_group_id', 'tenant_id'],
|
||||||
]
|
]
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@ -603,6 +603,16 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
|||||||
'site_id': '$site_id'
|
'site_id': '$site_id'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
required=False,
|
||||||
|
query_params={
|
||||||
|
'site_id': '$site_id',
|
||||||
|
'tenant_id': '$tenant_id',
|
||||||
|
'rack_id': '$rack_id',
|
||||||
|
},
|
||||||
|
label=_('Device')
|
||||||
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
choices=add_blank_choice(CableTypeChoices),
|
choices=add_blank_choice(CableTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -616,15 +626,12 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
|||||||
color = ColorField(
|
color = ColorField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
device_id = DynamicModelMultipleChoiceField(
|
length = forms.IntegerField(
|
||||||
queryset=Device.objects.all(),
|
required=False
|
||||||
required=False,
|
)
|
||||||
query_params={
|
length_unit = forms.ChoiceField(
|
||||||
'site_id': '$site_id',
|
choices=add_blank_choice(CableLengthUnitChoices),
|
||||||
'tenant_id': '$tenant_id',
|
required=False
|
||||||
'rack_id': '$rack_id',
|
|
||||||
},
|
|
||||||
label=_('Device')
|
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
@ -2035,8 +2035,9 @@ class DeviceBayPopulateView(generic.ObjectEditView):
|
|||||||
device_bay.installed_device = form.cleaned_data['installed_device']
|
device_bay.installed_device = form.cleaned_data['installed_device']
|
||||||
device_bay.save()
|
device_bay.save()
|
||||||
messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
|
messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
|
||||||
|
return_url = self.get_return_url(request)
|
||||||
|
|
||||||
return redirect('dcim:device', pk=device_bay.device.pk)
|
return redirect(return_url)
|
||||||
|
|
||||||
return render(request, 'dcim/devicebay_populate.html', {
|
return render(request, 'dcim/devicebay_populate.html', {
|
||||||
'device_bay': device_bay,
|
'device_bay': device_bay,
|
||||||
|
@ -7,8 +7,8 @@ from extras.models import *
|
|||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField,
|
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField,
|
||||||
ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect,
|
DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
|
|
||||||
@ -41,6 +41,10 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
('Values', ('default', 'choices')),
|
('Values', ('default', 'choices')),
|
||||||
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
||||||
)
|
)
|
||||||
|
widgets = {
|
||||||
|
'type': StaticSelect(),
|
||||||
|
'filter_logic': StaticSelect(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||||
@ -57,6 +61,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
|||||||
('Templates', ('link_text', 'link_url')),
|
('Templates', ('link_text', 'link_url')),
|
||||||
)
|
)
|
||||||
widgets = {
|
widgets = {
|
||||||
|
'button_class': StaticSelect(),
|
||||||
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
|
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
|
'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
}
|
}
|
||||||
@ -96,8 +101,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
|||||||
model = Webhook
|
model = Webhook
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Webhook', ('name', 'enabled')),
|
('Webhook', ('name', 'content_types', 'enabled')),
|
||||||
('Assigned Models', ('content_types',)),
|
|
||||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||||
('HTTP Request', (
|
('HTTP Request', (
|
||||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||||
@ -105,7 +109,13 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
|||||||
('Conditions', ('conditions',)),
|
('Conditions', ('conditions',)),
|
||||||
('SSL', ('ssl_verification', 'ca_file_path')),
|
('SSL', ('ssl_verification', 'ca_file_path')),
|
||||||
)
|
)
|
||||||
|
labels = {
|
||||||
|
'type_create': 'Creations',
|
||||||
|
'type_update': 'Updates',
|
||||||
|
'type_delete': 'Deletions',
|
||||||
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
|
'http_method': StaticSelect(),
|
||||||
'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
|
'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
'body_template': forms.Textarea(attrs={'class': 'font-monospace'}),
|
'body_template': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ from extras.models import JobResult
|
|||||||
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
||||||
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
||||||
from utilities.exceptions import AbortTransaction
|
from utilities.exceptions import AbortTransaction
|
||||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from .context_managers import change_logging
|
from .context_managers import change_logging
|
||||||
from .forms import ScriptForm
|
from .forms import ScriptForm
|
||||||
|
|
||||||
@ -164,16 +164,22 @@ class ChoiceVar(ScriptVariable):
|
|||||||
def __init__(self, choices, *args, **kwargs):
|
def __init__(self, choices, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Set field choices
|
# Set field choices, adding a blank choice to avoid forced selections
|
||||||
self.field_attrs['choices'] = choices
|
self.field_attrs['choices'] = add_blank_choice(choices)
|
||||||
|
|
||||||
|
|
||||||
class MultiChoiceVar(ChoiceVar):
|
class MultiChoiceVar(ScriptVariable):
|
||||||
"""
|
"""
|
||||||
Like ChoiceVar, but allows for the selection of multiple choices.
|
Like ChoiceVar, but allows for the selection of multiple choices.
|
||||||
"""
|
"""
|
||||||
form_field = forms.MultipleChoiceField
|
form_field = forms.MultipleChoiceField
|
||||||
|
|
||||||
|
def __init__(self, choices, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Set field choices
|
||||||
|
self.field_attrs['choices'] = choices
|
||||||
|
|
||||||
|
|
||||||
class ObjectVar(ScriptVariable):
|
class ObjectVar(ScriptVariable):
|
||||||
"""
|
"""
|
||||||
|
@ -65,6 +65,7 @@ FHRP_PROTOCOL_ROLE_MAPPINGS = {
|
|||||||
FHRPGroupProtocolChoices.PROTOCOL_HSRP: IPAddressRoleChoices.ROLE_HSRP,
|
FHRPGroupProtocolChoices.PROTOCOL_HSRP: IPAddressRoleChoices.ROLE_HSRP,
|
||||||
FHRPGroupProtocolChoices.PROTOCOL_GLBP: IPAddressRoleChoices.ROLE_GLBP,
|
FHRPGroupProtocolChoices.PROTOCOL_GLBP: IPAddressRoleChoices.ROLE_GLBP,
|
||||||
FHRPGroupProtocolChoices.PROTOCOL_CARP: IPAddressRoleChoices.ROLE_CARP,
|
FHRPGroupProtocolChoices.PROTOCOL_CARP: IPAddressRoleChoices.ROLE_CARP,
|
||||||
|
FHRPGroupProtocolChoices.PROTOCOL_OTHER: IPAddressRoleChoices.ROLE_VIP,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -580,7 +580,7 @@ class FHRPGroupForm(CustomFieldModelForm):
|
|||||||
vrf=self.cleaned_data['ip_vrf'],
|
vrf=self.cleaned_data['ip_vrf'],
|
||||||
address=self.cleaned_data['ip_address'],
|
address=self.cleaned_data['ip_address'],
|
||||||
status=self.cleaned_data['ip_status'],
|
status=self.cleaned_data['ip_status'],
|
||||||
role=FHRP_PROTOCOL_ROLE_MAPPINGS[self.cleaned_data['protocol']],
|
role=FHRP_PROTOCOL_ROLE_MAPPINGS.get(self.cleaned_data['protocol'], IPAddressRoleChoices.ROLE_VIP),
|
||||||
assigned_object=instance
|
assigned_object=instance
|
||||||
)
|
)
|
||||||
ipaddress.save()
|
ipaddress.save()
|
||||||
@ -628,8 +628,7 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class VLANGroupForm(CustomFieldModelForm):
|
class VLANGroupForm(CustomFieldModelForm):
|
||||||
scope_type = ContentTypeChoiceField(
|
scope_type = ContentTypeChoiceField(
|
||||||
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
||||||
required=False,
|
required=False
|
||||||
widget=StaticSelect
|
|
||||||
)
|
)
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.test import override_settings
|
||||||
|
from django.urls import reverse
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
@ -222,6 +224,21 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_aggregate_prefixes(self):
|
||||||
|
rir = RIR.objects.first()
|
||||||
|
aggregate = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=rir)
|
||||||
|
prefixes = (
|
||||||
|
Prefix(prefix=IPNetwork('192.168.1.0/24')),
|
||||||
|
Prefix(prefix=IPNetwork('192.168.2.0/24')),
|
||||||
|
Prefix(prefix=IPNetwork('192.168.3.0/24')),
|
||||||
|
)
|
||||||
|
Prefix.objects.bulk_create(prefixes)
|
||||||
|
self.assertEqual(aggregate.get_child_prefixes().count(), 3)
|
||||||
|
|
||||||
|
url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
|
||||||
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||||
model = Role
|
model = Role
|
||||||
@ -319,6 +336,48 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_prefix_prefixes(self):
|
||||||
|
prefixes = (
|
||||||
|
Prefix(prefix=IPNetwork('192.168.0.0/16')),
|
||||||
|
Prefix(prefix=IPNetwork('192.168.1.0/24')),
|
||||||
|
Prefix(prefix=IPNetwork('192.168.2.0/24')),
|
||||||
|
Prefix(prefix=IPNetwork('192.168.3.0/24')),
|
||||||
|
)
|
||||||
|
Prefix.objects.bulk_create(prefixes)
|
||||||
|
self.assertEqual(prefixes[0].get_child_prefixes().count(), 3)
|
||||||
|
|
||||||
|
url = reverse('ipam:prefix_prefixes', kwargs={'pk': prefixes[0].pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_prefix_ipranges(self):
|
||||||
|
prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16'))
|
||||||
|
ip_ranges = (
|
||||||
|
IPRange(start_address='192.168.0.1/24', end_address='192.168.0.100/24', size=99),
|
||||||
|
IPRange(start_address='192.168.1.1/24', end_address='192.168.1.100/24', size=99),
|
||||||
|
IPRange(start_address='192.168.2.1/24', end_address='192.168.2.100/24', size=99),
|
||||||
|
)
|
||||||
|
IPRange.objects.bulk_create(ip_ranges)
|
||||||
|
self.assertEqual(prefix.get_child_ranges().count(), 3)
|
||||||
|
|
||||||
|
url = reverse('ipam:prefix_ipranges', kwargs={'pk': prefix.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_prefix_ipaddresses(self):
|
||||||
|
prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16'))
|
||||||
|
ip_addresses = (
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.1/16')),
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.2/16')),
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.3/16')),
|
||||||
|
)
|
||||||
|
IPAddress.objects.bulk_create(ip_addresses)
|
||||||
|
self.assertEqual(prefix.get_child_ips().count(), 3)
|
||||||
|
|
||||||
|
url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
|
||||||
class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
model = IPRange
|
model = IPRange
|
||||||
@ -377,6 +436,24 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_iprange_ipaddresses(self):
|
||||||
|
iprange = IPRange.objects.create(
|
||||||
|
start_address=IPNetwork('192.168.0.1/24'),
|
||||||
|
end_address=IPNetwork('192.168.0.100/24'),
|
||||||
|
size=99
|
||||||
|
)
|
||||||
|
ip_addresses = (
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.1/24')),
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.2/24')),
|
||||||
|
IPAddress(address=IPNetwork('192.168.0.3/24')),
|
||||||
|
)
|
||||||
|
IPAddress.objects.bulk_create(ip_addresses)
|
||||||
|
self.assertEqual(iprange.get_child_ips().count(), 3)
|
||||||
|
|
||||||
|
url = reverse('ipam:iprange_ipaddresses', kwargs={'pk': iprange.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
@ -505,9 +505,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
|||||||
template_name = 'ipam/prefix/ip_addresses.html'
|
template_name = 'ipam/prefix/ip_addresses.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related(
|
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant')
|
||||||
'vrf', 'role', 'tenant',
|
|
||||||
)
|
|
||||||
|
|
||||||
def prep_table_data(self, request, queryset, parent):
|
def prep_table_data(self, request, queryset, parent):
|
||||||
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
||||||
@ -531,7 +529,6 @@ class PrefixEditView(generic.ObjectEditView):
|
|||||||
|
|
||||||
class PrefixDeleteView(generic.ObjectDeleteView):
|
class PrefixDeleteView(generic.ObjectDeleteView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
template_name = 'ipam/prefix_delete.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PrefixBulkImportView(generic.BulkImportView):
|
class PrefixBulkImportView(generic.BulkImportView):
|
||||||
|
@ -19,7 +19,7 @@ from netbox.config import PARAMS
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.1.4'
|
VERSION = '3.1.5'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -10,6 +10,7 @@ from django.db.models import ManyToManyField, ProtectedError
|
|||||||
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
|
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.http import is_safe_url
|
from django.utils.http import is_safe_url
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -430,10 +431,21 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|||||||
obj = self.get_object(kwargs)
|
obj = self.get_object(kwargs)
|
||||||
form = ConfirmationForm(initial=request.GET)
|
form = ConfirmationForm(initial=request.GET)
|
||||||
|
|
||||||
|
# If this is an HTMX request, return only the rendered deletion form as modal content
|
||||||
|
if is_htmx(request):
|
||||||
|
viewname = f'{self.queryset.model._meta.app_label}:{self.queryset.model._meta.model_name}_delete'
|
||||||
|
form_url = reverse(viewname, kwargs={'pk': obj.pk})
|
||||||
|
return render(request, 'htmx/delete_form.html', {
|
||||||
|
'object': obj,
|
||||||
|
'object_type': self.queryset.model._meta.verbose_name,
|
||||||
|
'form': form,
|
||||||
|
'form_url': form_url,
|
||||||
|
})
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'object': obj,
|
||||||
|
'object_type': self.queryset.model._meta.verbose_name,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -466,9 +478,9 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|||||||
logger.debug("Form validation failed")
|
logger.debug("Form validation failed")
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'object': obj,
|
||||||
|
'object_type': self.queryset.model._meta.verbose_name,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-light.css
vendored
BIN
netbox/project-static/dist/netbox-light.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-print.css
vendored
BIN
netbox/project-static/dist/netbox-print.css
vendored
Binary file not shown.
@ -358,7 +358,7 @@ nav.search {
|
|||||||
// Don't overtake dropdowns
|
// Don't overtake dropdowns
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: var(--nbx-body-bg);
|
background-color: $navbar-light-color;
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -452,8 +452,8 @@ main.login-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
background-color: $tab-content-bg;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -517,6 +517,10 @@ h6.accordion-item-title {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
@ -554,6 +558,7 @@ div.content-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.content {
|
div.content {
|
||||||
|
background-color: $tab-content-bg;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -898,6 +903,7 @@ div.card-overlay {
|
|||||||
|
|
||||||
// Tabbed content
|
// Tabbed content
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
|
background-color: $body-bg;
|
||||||
.nav-link {
|
.nav-link {
|
||||||
&:hover {
|
&:hover {
|
||||||
// Don't show a bottom-border on a hovered nav link because it overlaps with the .nav-tab border.
|
// Don't show a bottom-border on a hovered nav link because it overlaps with the .nav-tab border.
|
||||||
@ -919,14 +925,6 @@ div.card-overlay {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: $spacer;
|
padding: $spacer;
|
||||||
background-color: $tab-content-bg;
|
|
||||||
border-bottom: 1px solid $nav-tabs-border-color;
|
|
||||||
|
|
||||||
// Remove background and border when printing.
|
|
||||||
@media print {
|
|
||||||
background-color: var(--nbx-body-bg) !important;
|
|
||||||
border-bottom: none !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override masonry-layout styles when printing.
|
// Override masonry-layout styles when printing.
|
||||||
|
@ -33,95 +33,6 @@ $darkest: #171b1d;
|
|||||||
|
|
||||||
@import '../node_modules/bootstrap/scss/variables';
|
@import '../node_modules/bootstrap/scss/variables';
|
||||||
|
|
||||||
// Make color palette colors available as theme colors.
|
|
||||||
// For example, you could use `.bg-red-100`, if needed.
|
|
||||||
$theme-color-addons: (
|
|
||||||
'darker': $darker,
|
|
||||||
'darkest': $darkest,
|
|
||||||
'gray': $gray-400,
|
|
||||||
'gray-100': $gray-100,
|
|
||||||
'gray-200': $gray-200,
|
|
||||||
'gray-300': $gray-300,
|
|
||||||
'gray-400': $gray-400,
|
|
||||||
'gray-500': $gray-500,
|
|
||||||
'gray-600': $gray-600,
|
|
||||||
'gray-700': $gray-700,
|
|
||||||
'gray-800': $gray-800,
|
|
||||||
'gray-900': $gray-900,
|
|
||||||
'red-100': $red-100,
|
|
||||||
'red-200': $red-200,
|
|
||||||
'red-300': $red-300,
|
|
||||||
'red-400': $red-400,
|
|
||||||
'red-500': $red-500,
|
|
||||||
'red-600': $red-600,
|
|
||||||
'red-700': $red-700,
|
|
||||||
'red-800': $red-800,
|
|
||||||
'red-900': $red-900,
|
|
||||||
'yellow-100': $yellow-100,
|
|
||||||
'yellow-200': $yellow-200,
|
|
||||||
'yellow-300': $yellow-300,
|
|
||||||
'yellow-400': $yellow-400,
|
|
||||||
'yellow-500': $yellow-500,
|
|
||||||
'yellow-600': $yellow-600,
|
|
||||||
'yellow-700': $yellow-700,
|
|
||||||
'yellow-800': $yellow-800,
|
|
||||||
'yellow-900': $yellow-900,
|
|
||||||
'green-100': $green-100,
|
|
||||||
'green-200': $green-200,
|
|
||||||
'green-300': $green-300,
|
|
||||||
'green-400': $green-400,
|
|
||||||
'green-500': $green-500,
|
|
||||||
'green-600': $green-600,
|
|
||||||
'green-700': $green-700,
|
|
||||||
'green-800': $green-800,
|
|
||||||
'green-900': $green-900,
|
|
||||||
'blue-100': $blue-100,
|
|
||||||
'blue-200': $blue-200,
|
|
||||||
'blue-300': $blue-300,
|
|
||||||
'blue-400': $blue-400,
|
|
||||||
'blue-500': $blue-500,
|
|
||||||
'blue-600': $blue-600,
|
|
||||||
'blue-700': $blue-700,
|
|
||||||
'blue-800': $blue-800,
|
|
||||||
'blue-900': $blue-900,
|
|
||||||
'cyan-100': $cyan-100,
|
|
||||||
'cyan-200': $cyan-200,
|
|
||||||
'cyan-300': $cyan-300,
|
|
||||||
'cyan-400': $cyan-400,
|
|
||||||
'cyan-500': $cyan-500,
|
|
||||||
'cyan-600': $cyan-600,
|
|
||||||
'cyan-700': $cyan-700,
|
|
||||||
'cyan-800': $cyan-800,
|
|
||||||
'cyan-900': $cyan-900,
|
|
||||||
'indigo-100': $indigo-100,
|
|
||||||
'indigo-200': $indigo-200,
|
|
||||||
'indigo-300': $indigo-300,
|
|
||||||
'indigo-400': $indigo-400,
|
|
||||||
'indigo-500': $indigo-500,
|
|
||||||
'indigo-600': $indigo-600,
|
|
||||||
'indigo-700': $indigo-700,
|
|
||||||
'indigo-800': $indigo-800,
|
|
||||||
'indigo-900': $indigo-900,
|
|
||||||
'purple-100': $purple-100,
|
|
||||||
'purple-200': $purple-200,
|
|
||||||
'purple-300': $purple-300,
|
|
||||||
'purple-400': $purple-400,
|
|
||||||
'purple-500': $purple-500,
|
|
||||||
'purple-600': $purple-600,
|
|
||||||
'purple-700': $purple-700,
|
|
||||||
'purple-800': $purple-800,
|
|
||||||
'purple-900': $purple-900,
|
|
||||||
'pink-100': $pink-100,
|
|
||||||
'pink-200': $pink-200,
|
|
||||||
'pink-300': $pink-300,
|
|
||||||
'pink-400': $pink-400,
|
|
||||||
'pink-500': $pink-500,
|
|
||||||
'pink-600': $pink-600,
|
|
||||||
'pink-700': $pink-700,
|
|
||||||
'pink-800': $pink-800,
|
|
||||||
'pink-900': $pink-900,
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is the same value as the default from Bootstrap, but it needs to be in scope prior to
|
// This is the same value as the default from Bootstrap, but it needs to be in scope prior to
|
||||||
// importing _variables.scss from Bootstrap.
|
// importing _variables.scss from Bootstrap.
|
||||||
$btn-close-width: 1em;
|
$btn-close-width: 1em;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
@use 'sass:map';
|
@use 'sass:map';
|
||||||
@import './theme-base';
|
@import './theme-base';
|
||||||
|
|
||||||
|
// Theme colors (BS5 classes)
|
||||||
$primary: $blue-300;
|
$primary: $blue-300;
|
||||||
$secondary: $gray-500;
|
$secondary: $gray-500;
|
||||||
$success: $green-300;
|
$success: $green-300;
|
||||||
@ -13,6 +14,7 @@ $light: $gray-300;
|
|||||||
$dark: $gray-500;
|
$dark: $gray-500;
|
||||||
|
|
||||||
$theme-colors: (
|
$theme-colors: (
|
||||||
|
// BS5 theme colors
|
||||||
'primary': $primary,
|
'primary': $primary,
|
||||||
'secondary': $secondary,
|
'secondary': $secondary,
|
||||||
'success': $success,
|
'success': $success,
|
||||||
@ -21,18 +23,23 @@ $theme-colors: (
|
|||||||
'danger': $danger,
|
'danger': $danger,
|
||||||
'light': $light,
|
'light': $light,
|
||||||
'dark': $dark,
|
'dark': $dark,
|
||||||
'red': $red-300,
|
|
||||||
'yellow': $yellow-300,
|
// General-purpose palette
|
||||||
'green': $green-300,
|
|
||||||
'blue': $blue-300,
|
'blue': $blue-300,
|
||||||
'cyan': $cyan-300,
|
|
||||||
'indigo': $indigo-300,
|
'indigo': $indigo-300,
|
||||||
'purple': $purple-300,
|
'purple': $purple-300,
|
||||||
'pink': $pink-300,
|
'pink': $pink-300,
|
||||||
|
'red': $red-300,
|
||||||
|
'orange': $orange-300,
|
||||||
|
'yellow': $yellow-300,
|
||||||
|
'green': $green-300,
|
||||||
|
'teal': $teal-300,
|
||||||
|
'cyan': $cyan-300,
|
||||||
|
'gray': $gray-300,
|
||||||
|
'black': $black,
|
||||||
|
'white': $white,
|
||||||
);
|
);
|
||||||
|
|
||||||
$theme-colors: map-merge($theme-colors, $theme-color-addons);
|
|
||||||
|
|
||||||
// Gradient
|
// Gradient
|
||||||
$gradient: linear-gradient(180deg, rgba($white, 0.15), rgba($white, 0));
|
$gradient: linear-gradient(180deg, rgba($white, 0.15), rgba($white, 0));
|
||||||
|
|
||||||
@ -139,7 +146,7 @@ $nav-tabs-link-active-border-color: $gray-800 $gray-800 $nav-tabs-link-active-bg
|
|||||||
$nav-pills-link-active-color: $component-active-color;
|
$nav-pills-link-active-color: $component-active-color;
|
||||||
$nav-pills-link-active-bg: $component-active-bg;
|
$nav-pills-link-active-bg: $component-active-bg;
|
||||||
|
|
||||||
$navbar-light-color: $gray-500;
|
$navbar-light-color: $darkest;
|
||||||
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");
|
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");
|
||||||
$navbar-light-toggler-border-color: $gray-700;
|
$navbar-light-toggler-border-color: $gray-700;
|
||||||
|
|
||||||
|
@ -2,28 +2,47 @@
|
|||||||
|
|
||||||
@import './theme-base.scss';
|
@import './theme-base.scss';
|
||||||
|
|
||||||
$input-border-color: $gray-200;
|
// Theme colors (BS5 classes)
|
||||||
|
$primary: #337ab7;
|
||||||
|
$secondary: $gray-600;
|
||||||
|
$success: $green-500;
|
||||||
|
$info: #54d6f0;
|
||||||
|
$warning: $yellow-500;
|
||||||
|
$danger: $red-500;
|
||||||
|
$light: $gray-200;
|
||||||
|
$dark: $gray-800;
|
||||||
|
|
||||||
$theme-colors: map-merge(
|
$theme-colors: (
|
||||||
$theme-colors,
|
// BS5 theme colors
|
||||||
(
|
'primary': $primary,
|
||||||
'primary': #337ab7,
|
'secondary': $secondary,
|
||||||
'info': #54d6f0,
|
'success': $success,
|
||||||
'red': $red-500,
|
'info': $info,
|
||||||
'yellow': $yellow-500,
|
'warning': $warning,
|
||||||
'green': $green-500,
|
'danger': $danger,
|
||||||
'blue': $blue-500,
|
'light': $light,
|
||||||
'cyan': $cyan-500,
|
'dark': $dark,
|
||||||
'indigo': $indigo-500,
|
|
||||||
'purple': $purple-500,
|
// General-purpose palette
|
||||||
'pink': $pink-500,
|
'blue': $blue-500,
|
||||||
)
|
'indigo': $indigo-500,
|
||||||
|
'purple': $purple-500,
|
||||||
|
'pink': $pink-500,
|
||||||
|
'red': $red-500,
|
||||||
|
'orange': $orange-500,
|
||||||
|
'yellow': $yellow-500,
|
||||||
|
'green': $green-500,
|
||||||
|
'teal': $teal-500,
|
||||||
|
'cyan': $cyan-500,
|
||||||
|
'gray': $gray-500,
|
||||||
|
'black': $black,
|
||||||
|
'white': $white,
|
||||||
);
|
);
|
||||||
|
|
||||||
$theme-colors: map-merge($theme-colors, $theme-color-addons);
|
|
||||||
|
|
||||||
$light: $gray-200;
|
$light: $gray-200;
|
||||||
|
|
||||||
|
$navbar-light-color: $gray-100;
|
||||||
|
|
||||||
$card-cap-color: $gray-800;
|
$card-cap-color: $gray-800;
|
||||||
|
|
||||||
$accordion-bg: transparent;
|
$accordion-bg: transparent;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Top bar #}
|
{# Top bar #}
|
||||||
<nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid border-bottom noprint">
|
<nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid noprint">
|
||||||
|
|
||||||
{# Mobile Navigation #}
|
{# Mobile Navigation #}
|
||||||
<div class="nav-mobile">
|
<div class="nav-mobile">
|
||||||
@ -103,6 +103,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# BS5 pop-up modals #}
|
||||||
|
{% block modals %}{% endblock %}
|
||||||
|
|
||||||
{# Page footer #}
|
{# Page footer #}
|
||||||
<footer class="footer container-fluid">
|
<footer class="footer container-fluid">
|
||||||
<div class="row align-items-center justify-content-between mx-0">
|
<div class="row align-items-center justify-content-between mx-0">
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -39,5 +39,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -77,5 +77,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -39,5 +39,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -42,5 +42,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% render_errors form %}
|
{% render_errors form %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="." method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-md-6 offset-md-3">
|
<div class="col col-md-6 offset-md-3">
|
||||||
|
@ -73,3 +73,5 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content-wrapper %}
|
{% endblock content-wrapper %}
|
||||||
|
|
||||||
|
{% block modals %}{% endblock %}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}#module.{{ report.module }}">{{ report.module|bettertitle }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}#module.{{ report.module }}">{{ report.module|bettertitle }}</a></li>
|
||||||
{% endblock %}
|
{% endblock breadcrumbs %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
{% if report.description %}
|
{% if report.description %}
|
||||||
@ -18,33 +18,42 @@
|
|||||||
<div class="text-muted">{{ report.description|render_markdown }}</div>
|
<div class="text-muted">{{ report.description|render_markdown }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock subtitle %}
|
||||||
|
|
||||||
{% block controls %}{% endblock %}
|
{% block controls %}{% endblock %}
|
||||||
{% block tabs %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block tabs %}
|
||||||
{% if perms.extras.run_report %}
|
<ul class="nav nav-tabs px-3">
|
||||||
<div class="px-3 float-end noprint">
|
<li class="nav-item" role="presentation">
|
||||||
<form action="{% url 'extras:report' module=report.module name=report.class_name %}" method="post">
|
<a href="#report" role="tab" data-bs-toggle="tab" class="nav-link active">Report</a>
|
||||||
{% csrf_token %}
|
</li>
|
||||||
<button type="submit" name="_run" class="btn btn-primary">
|
</ul>
|
||||||
{% if report.result %}
|
{% endblock tabs %}
|
||||||
<i class="mdi mdi-replay"></i> Run Again
|
|
||||||
{% else %}
|
{% block content %}
|
||||||
<i class="mdi mdi-play"></i> Run Report
|
<div role="tabpanel" class="tab-pane active" id="report">
|
||||||
{% endif %}
|
{% if perms.extras.run_report %}
|
||||||
</button>
|
<div class="float-end noprint">
|
||||||
</form>
|
<form action="{% url 'extras:report' module=report.module name=report.class_name %}" method="post">
|
||||||
</div>
|
{% csrf_token %}
|
||||||
{% endif %}
|
<button type="submit" name="_run" class="btn btn-primary">
|
||||||
<div class="row px-3">
|
{% if report.result %}
|
||||||
<div class="col col-md-12">
|
<i class="mdi mdi-replay"></i> Run Again
|
||||||
{% if report.result %}
|
{% else %}
|
||||||
Last run: <a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">
|
<i class="mdi mdi-play"></i> Run Report
|
||||||
<strong>{{ report.result.created|annotated_date }}</strong>
|
{% endif %}
|
||||||
</a>
|
</button>
|
||||||
{% endif %}
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% if report.result %}
|
||||||
|
Last run: <a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">
|
||||||
|
<strong>{{ report.result.created|annotated_date }}</strong>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends 'extras/report.html' %}
|
{% extends 'extras/report.html' %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="row px-3">
|
<div class="row p-3">
|
||||||
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
|
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
|
||||||
{% include 'extras/htmx/report_result.html' %}
|
{% include 'extras/htmx/report_result.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,69 +7,67 @@
|
|||||||
|
|
||||||
{% block object_identifier %}
|
{% block object_identifier %}
|
||||||
{{ script.full_name }}
|
{{ script.full_name }}
|
||||||
{% endblock %}
|
{% endblock object_identifier %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ module }}">{{ module|bettertitle }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ module }}">{{ module|bettertitle }}</a></li>
|
||||||
{% endblock %}
|
{% endblock breadcrumbs %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
<div class="object-subtitle">
|
<div class="object-subtitle">
|
||||||
<div class="text-muted">{{ script.Meta.description|render_markdown }}</div>
|
<div class="text-muted">{{ script.Meta.description|render_markdown }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock subtitle %}
|
||||||
|
|
||||||
{% block controls %}{% endblock %}
|
{% block controls %}{% endblock %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="#run" role="tab" data-bs-toggle="tab" class="nav-link active">Run</a>
|
<a href="#run" role="tab" data-bs-toggle="tab" class="nav-link active">Run</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
|
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content %}
|
||||||
<div class="tab-content">
|
<div role="tabpanel" class="tab-pane active" id="run">
|
||||||
<div role="tabpanel" class="tab-pane active" id="run">
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col">
|
||||||
<div class="col">
|
{% if not perms.extras.run_script %}
|
||||||
{% if not perms.extras.run_script %}
|
<div class="alert alert-warning">
|
||||||
<div class="alert alert-warning">
|
<i class="mdi mdi-alert"></i>
|
||||||
<i class="mdi mdi-alert"></i>
|
You do not have permission to run scripts.
|
||||||
You do not have permission to run scripts.
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
<form action="" method="post" enctype="multipart/form-data" class="form form-horizontal">
|
||||||
<form action="" method="post" enctype="multipart/form-data" class="form form-horizontal">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<div class="field-group my-4">
|
||||||
<div class="field-group my-4">
|
{% if form.requires_input %}
|
||||||
{% if form.requires_input %}
|
<div class="row mb-2">
|
||||||
<div class="row mb-2">
|
<h5 class="offset-sm-3">Script Data</h5>
|
||||||
<h5 class="offset-sm-3">Script Data</h5>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
{% else %}
|
<div class="alert alert-info">
|
||||||
<div class="alert alert-info">
|
<i class="mdi mdi-information"></i>
|
||||||
<i class="mdi mdi-information"></i>
|
This script does not require any input to run.
|
||||||
This script does not require any input to run.
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% render_form form %}
|
||||||
{% render_form form %}
|
</div>
|
||||||
</div>
|
<div class="float-end">
|
||||||
<div class="float-end">
|
<a href="{% url 'extras:script_list' %}" class="btn btn-outline-danger">Cancel</a>
|
||||||
<a href="{% url 'extras:script_list' %}" class="btn btn-outline-danger">Cancel</a>
|
<button type="submit" name="_run" class="btn btn-primary"{% if not perms.extras.run_script %} disabled="disabled"{% endif %}><i class="mdi mdi-play"></i> Run Script</button>
|
||||||
<button type="submit" name="_run" class="btn btn-primary"{% if not perms.extras.run_script %} disabled="disabled"{% endif %}><i class="mdi mdi-play"></i> Run Script</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div role="tabpanel" class="tab-pane" id="source">
|
|
||||||
<code class="h6 my-3 d-block">{{ script.filename }}</code>
|
|
||||||
<pre class="block">{{ script.source }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content-wrapper %}
|
<div role="tabpanel" class="tab-pane" id="source">
|
||||||
|
<code class="h6 my-3 d-block">{{ script.filename }}</code>
|
||||||
|
<pre class="block">{{ script.source }}</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
@ -100,4 +100,8 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content-wrapper %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% include 'inc/htmx_modal.html' %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
{% extends 'generic/confirmation_form.html' %}
|
{% extends 'base/layout.html' %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}Delete {{ obj_type }}?{% endblock %}
|
{% block title %}Delete {{ object_type }}?{% endblock %}
|
||||||
|
|
||||||
{% block message %}
|
{% block header %}{% endblock %}
|
||||||
<p>Are you sure you want to <strong class="text-danger">delete</strong> {{ obj_type }} <strong>{{ obj }}</strong>?</p>
|
|
||||||
{% block message_extra %}{% endblock %}
|
{% block content %}
|
||||||
{% endblock message %}
|
<div class="modal" tabindex="-1" style="display: block; position: static">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content" >
|
||||||
|
{% include 'htmx/delete_form.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
@ -133,6 +133,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Table config form #}
|
|
||||||
{% table_config_form table table_name="ObjectTable" %}
|
|
||||||
{% endblock content-wrapper %}
|
{% endblock content-wrapper %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table table_name="ObjectTable" %}
|
||||||
|
{% endblock modals %}
|
||||||
|
20
netbox/templates/htmx/delete_form.html
Normal file
20
netbox/templates/htmx/delete_form.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
<form action="{{ form_url }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Confirm Deletion</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to <strong class="text-danger">delete</strong> {{ object_type }} <strong>{{ object }}</strong>?</p>
|
||||||
|
{% render_form form %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{% if return_url %}
|
||||||
|
<a href="{{ return_url }}" class="btn btn-outline-secondary">Cancel</a>
|
||||||
|
{% else %}
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
7
netbox/templates/inc/htmx_modal.html
Normal file
7
netbox/templates/inc/htmx_modal.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<div class="modal fade" id="htmx-modal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content" id="htmx-modal-content">
|
||||||
|
{# Dynamic content goes here #}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -37,5 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -35,5 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -35,5 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -35,5 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -37,5 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{% extends 'generic/object_delete.html' %}
|
|
||||||
|
|
||||||
{% block message_extra %}
|
|
||||||
<p>Note: This will <strong>not</strong> delete any child prefixes or IP addresses.</p>
|
|
||||||
{% endblock %}
|
|
@ -37,9 +37,3 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
|
||||||
<div class="tab-content">
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" id="object_list">
|
<div class="card-body" id="object_list">
|
||||||
{% include 'htmx/table.html' %}
|
{% include 'htmx/table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock modals %}
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" id="object_list">
|
<div class="card-body" id="object_list">
|
||||||
{% include 'htmx/table.html' %}
|
{% include 'htmx/table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock modals %}
|
||||||
|
@ -6,13 +6,11 @@
|
|||||||
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" id="object_list">
|
<div class="card-body" id="object_list">
|
||||||
{% include 'htmx/table.html' %}
|
{% include 'htmx/table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.virtualization.change_cluster %}
|
{% if perms.virtualization.change_cluster %}
|
||||||
@ -23,5 +21,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock modals %}
|
||||||
|
@ -6,13 +6,11 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" id="object_list">
|
<div class="card-body" id="object_list">
|
||||||
{% include 'htmx/table.html' %}
|
{% include 'htmx/table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.virtualization.change_virtualmachine %}
|
{% if perms.virtualization.change_virtualmachine %}
|
||||||
@ -28,5 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock modals %}
|
||||||
|
@ -37,5 +37,8 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock modals %}
|
||||||
|
@ -168,11 +168,11 @@ class ContentTypeChoiceMixin:
|
|||||||
|
|
||||||
|
|
||||||
class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField):
|
class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField):
|
||||||
pass
|
widget = widgets.StaticSelect
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField):
|
class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField):
|
||||||
pass
|
widget = widgets.StaticSelectMultiple
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -14,7 +14,6 @@ __all__ = (
|
|||||||
'BulkEditNullBooleanSelect',
|
'BulkEditNullBooleanSelect',
|
||||||
'ClearableFileInput',
|
'ClearableFileInput',
|
||||||
'ColorSelect',
|
'ColorSelect',
|
||||||
'ContentTypeSelect',
|
|
||||||
'DatePicker',
|
'DatePicker',
|
||||||
'DateTimePicker',
|
'DateTimePicker',
|
||||||
'NumericArrayField',
|
'NumericArrayField',
|
||||||
@ -110,15 +109,6 @@ class SelectWithPK(StaticSelect):
|
|||||||
option_template_name = 'widgets/select_option_with_pk.html'
|
option_template_name = 'widgets/select_option_with_pk.html'
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeSelect(StaticSelect):
|
|
||||||
"""
|
|
||||||
Appends an `api-value` attribute equal to the slugified model name for each ContentType. For example:
|
|
||||||
<option value="37" api-value="console-server-port">console server port</option>
|
|
||||||
This attribute can be used to reference the relevant API endpoint for a particular ContentType.
|
|
||||||
"""
|
|
||||||
option_template_name = 'widgets/select_contenttype.html'
|
|
||||||
|
|
||||||
|
|
||||||
class SelectSpeedWidget(forms.NumberInput):
|
class SelectSpeedWidget(forms.NumberInput):
|
||||||
"""
|
"""
|
||||||
Speed field with dropdown selections for convenience.
|
Speed field with dropdown selections for convenience.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
<a href="{{ url }}" class="btn btn-sm btn-danger" role="button">
|
<a href="#"
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
hx-get="{{ url }}"
|
||||||
|
hx-target="#htmx-modal-content"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#htmx-modal"
|
||||||
|
>
|
||||||
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</a>
|
</a>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %}{% if widget.value %} api-value="{{ widget.label|slugify }}"{% endif %}>{{ widget.label.label|default:widget.label|capfirst }}</option>
|
|
@ -80,6 +80,12 @@ class ClusterTable(BaseTable):
|
|||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
type = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
group = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
tenant = tables.Column(
|
tenant = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ from django import forms
|
|||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from utilities.forms import DynamicModelChoiceField
|
from utilities.forms import add_blank_choice, DynamicModelChoiceField
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
from wireless.constants import SSID_MAX_LENGTH
|
from wireless.constants import SSID_MAX_LENGTH
|
||||||
from wireless.models import *
|
from wireless.models import *
|
||||||
@ -45,24 +45,27 @@ class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
|||||||
vlan = DynamicModelChoiceField(
|
vlan = DynamicModelChoiceField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
label='VLAN'
|
||||||
)
|
)
|
||||||
ssid = forms.CharField(
|
ssid = forms.CharField(
|
||||||
max_length=SSID_MAX_LENGTH,
|
max_length=SSID_MAX_LENGTH,
|
||||||
required=False
|
required=False,
|
||||||
|
label='SSID'
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_type = forms.ChoiceField(
|
auth_type = forms.ChoiceField(
|
||||||
choices=WirelessAuthTypeChoices,
|
choices=add_blank_choice(WirelessAuthTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_cipher = forms.ChoiceField(
|
auth_cipher = forms.ChoiceField(
|
||||||
choices=WirelessAuthCipherChoices,
|
choices=add_blank_choice(WirelessAuthCipherChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_psk = forms.CharField(
|
auth_psk = forms.CharField(
|
||||||
required=False
|
required=False,
|
||||||
|
label='Pre-shared key'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -76,25 +79,27 @@ class WirelessLinkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
ssid = forms.CharField(
|
ssid = forms.CharField(
|
||||||
max_length=SSID_MAX_LENGTH,
|
max_length=SSID_MAX_LENGTH,
|
||||||
required=False
|
required=False,
|
||||||
|
label='SSID'
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=LinkStatusChoices,
|
choices=add_blank_choice(LinkStatusChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_type = forms.ChoiceField(
|
auth_type = forms.ChoiceField(
|
||||||
choices=WirelessAuthTypeChoices,
|
choices=add_blank_choice(WirelessAuthTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_cipher = forms.ChoiceField(
|
auth_cipher = forms.ChoiceField(
|
||||||
choices=WirelessAuthCipherChoices,
|
choices=add_blank_choice(WirelessAuthCipherChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
auth_psk = forms.CharField(
|
auth_psk = forms.CharField(
|
||||||
required=False
|
required=False,
|
||||||
|
label='Pre-shared key'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Django==3.2.10
|
Django==3.2.11
|
||||||
django-cors-headers==3.10.1
|
django-cors-headers==3.10.1
|
||||||
django-debug-toolbar==3.2.4
|
django-debug-toolbar==3.2.4
|
||||||
django-filter==21.1
|
django-filter==21.1
|
||||||
@ -18,7 +18,7 @@ gunicorn==20.1.0
|
|||||||
Jinja2==3.0.3
|
Jinja2==3.0.3
|
||||||
Markdown==3.3.6
|
Markdown==3.3.6
|
||||||
markdown-include==0.6.0
|
markdown-include==0.6.0
|
||||||
mkdocs-material==8.1.3
|
mkdocs-material==8.1.4
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==8.4.0
|
Pillow==8.4.0
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
|
Loading…
Reference in New Issue
Block a user