mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into develop-2.5 (v2.4.4 release)
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@ -19,16 +19,25 @@ v2.5.0 (FUTURE)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
v2.4.4 (FUTURE)
|
v2.4.4 (2018-08-22)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2168](https://github.com/digitalocean/netbox/issues/2168) - Added Extreme SummitStack interface form factors
|
||||||
* [#2356](https://github.com/digitalocean/netbox/issues/2356) - Include cluster site as read-only field in VirtualMachine serializer
|
* [#2356](https://github.com/digitalocean/netbox/issues/2356) - Include cluster site as read-only field in VirtualMachine serializer
|
||||||
* [#2362](https://github.com/digitalocean/netbox/issues/2362) - Implemented custom admin site to properly handle BASE_PATH
|
* [#2362](https://github.com/digitalocean/netbox/issues/2362) - Implemented custom admin site to properly handle BASE_PATH
|
||||||
|
* [#2254](https://github.com/digitalocean/netbox/issues/2254) - Implemented searchability for Rack Groups
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2353](https://github.com/digitalocean/netbox/issues/2353) - Handle `DoesNotExist` exception when deleting a device with connected interfaces
|
||||||
|
* [#2354](https://github.com/digitalocean/netbox/issues/2354) - Increased maximum MTU for interfaces to 65536 bytes
|
||||||
* [#2355](https://github.com/digitalocean/netbox/issues/2355) - Added item count to inventory tab on device view
|
* [#2355](https://github.com/digitalocean/netbox/issues/2355) - Added item count to inventory tab on device view
|
||||||
|
* [#2368](https://github.com/digitalocean/netbox/issues/2368) - Record change in device changelog when altering cluster assignment
|
||||||
|
* [#2369](https://github.com/digitalocean/netbox/issues/2369) - Corrected time zone validation on site API serializer
|
||||||
|
* [#2370](https://github.com/digitalocean/netbox/issues/2370) - Redirect to parent device after deleting device bays
|
||||||
|
* [#2374](https://github.com/digitalocean/netbox/issues/2374) - Fix toggling display of IP addresses in virtual machine interfaces list
|
||||||
|
* [#2378](https://github.com/digitalocean/netbox/issues/2378) - Corrected "edit" link for virtual machine interfaces
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -12,5 +12,5 @@ While NetBox has many configuration settings, only a few of them must be defined
|
|||||||
Configuration settings may be changed at any time. However, the NetBox service must be restarted before the changes will take effect:
|
Configuration settings may be changed at any time. However, the NetBox service must be restarted before the changes will take effect:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
# sudo supervsiorctl restart netbox
|
# sudo supervisorctl restart netbox
|
||||||
```
|
```
|
||||||
|
@ -48,9 +48,9 @@ Close the release milestone on GitHub. Ensure that there are no remaining open i
|
|||||||
|
|
||||||
Ensure that continuous integration testing on the `develop` branch is completing successfully.
|
Ensure that continuous integration testing on the `develop` branch is completing successfully.
|
||||||
|
|
||||||
## Update VERSION
|
## Update Version and Changelog
|
||||||
|
|
||||||
Update the `VERSION` constant in `settings.py` to the new release.
|
Update the `VERSION` constant in `settings.py` to the new release version and add the current date to the release notes in `CHANGELOG.md`.
|
||||||
|
|
||||||
## Submit a Pull Request
|
## Submit a Pull Request
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https:
|
|||||||
## Option B: Apache
|
## Option B: Apache
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
# apt-get install -y apache2
|
# apt-get install -y apache2 libapache2-mod-wsgi-py3
|
||||||
```
|
```
|
||||||
|
|
||||||
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
||||||
|
@ -91,6 +91,11 @@ IFACE_FF_STACKWISE_PLUS = 5050
|
|||||||
IFACE_FF_FLEXSTACK = 5100
|
IFACE_FF_FLEXSTACK = 5100
|
||||||
IFACE_FF_FLEXSTACK_PLUS = 5150
|
IFACE_FF_FLEXSTACK_PLUS = 5150
|
||||||
IFACE_FF_JUNIPER_VCP = 5200
|
IFACE_FF_JUNIPER_VCP = 5200
|
||||||
|
IFACE_FF_SUMMITSTACK = 5300
|
||||||
|
IFACE_FF_SUMMITSTACK128 = 5310
|
||||||
|
IFACE_FF_SUMMITSTACK256 = 5320
|
||||||
|
IFACE_FF_SUMMITSTACK512 = 5330
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
IFACE_FF_OTHER = 32767
|
IFACE_FF_OTHER = 32767
|
||||||
|
|
||||||
@ -166,6 +171,10 @@ IFACE_FF_CHOICES = [
|
|||||||
[IFACE_FF_FLEXSTACK, 'Cisco FlexStack'],
|
[IFACE_FF_FLEXSTACK, 'Cisco FlexStack'],
|
||||||
[IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
|
[IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
|
||||||
[IFACE_FF_JUNIPER_VCP, 'Juniper VCP'],
|
[IFACE_FF_JUNIPER_VCP, 'Juniper VCP'],
|
||||||
|
[IFACE_FF_SUMMITSTACK, 'Extreme SummitStack'],
|
||||||
|
[IFACE_FF_SUMMITSTACK128, 'Extreme SummitStack-128'],
|
||||||
|
[IFACE_FF_SUMMITSTACK256, 'Extreme SummitStack-256'],
|
||||||
|
[IFACE_FF_SUMMITSTACK512, 'Extreme SummitStack-512'],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -110,6 +110,10 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class RackGroupFilter(django_filters.FilterSet):
|
class RackGroupFilter(django_filters.FilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -125,6 +129,15 @@ class RackGroupFilter(django_filters.FilterSet):
|
|||||||
model = RackGroup
|
model = RackGroup
|
||||||
fields = ['site_id', 'name', 'slug']
|
fields = ['site_id', 'name', 'slug']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = (
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(slug__icontains=value)
|
||||||
|
)
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class RackRoleFilter(django_filters.FilterSet):
|
class RackRoleFilter(django_filters.FilterSet):
|
||||||
|
|
||||||
|
29
netbox/dcim/migrations/0062_interface_mtu.py
Normal file
29
netbox/dcim/migrations/0062_interface_mtu.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-08-22 14:23
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0061_platform_napalm_args'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='interface',
|
||||||
|
name='mtu',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65536)], verbose_name='MTU'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='interface',
|
||||||
|
name='form_factor',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='form_factor',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 2.0.8 on 2018-08-16 16:17
|
# Generated by Django 2.0.8 on 2018-08-22 16:09
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dcim', '0061_platform_napalm_args'),
|
('dcim', '0062_interface_mtu'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
@ -1754,9 +1754,10 @@ class Interface(ComponentModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='MAC Address'
|
verbose_name='MAC Address'
|
||||||
)
|
)
|
||||||
mtu = models.PositiveSmallIntegerField(
|
mtu = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
validators=[MinValueValidator(1), MaxValueValidator(65536)],
|
||||||
verbose_name='MTU'
|
verbose_name='MTU'
|
||||||
)
|
)
|
||||||
mgmt_only = models.BooleanField(
|
mgmt_only = models.BooleanField(
|
||||||
@ -2012,6 +2013,7 @@ class InterfaceConnection(models.Model):
|
|||||||
(self.interface_a, self.interface_b),
|
(self.interface_a, self.interface_b),
|
||||||
(self.interface_b, self.interface_a),
|
(self.interface_b, self.interface_a),
|
||||||
)
|
)
|
||||||
|
|
||||||
for interface, peer_interface in interfaces:
|
for interface, peer_interface in interfaces:
|
||||||
if action == OBJECTCHANGE_ACTION_DELETE:
|
if action == OBJECTCHANGE_ACTION_DELETE:
|
||||||
connection_data = {
|
connection_data = {
|
||||||
@ -2022,11 +2024,17 @@ class InterfaceConnection(models.Model):
|
|||||||
'connected_interface': peer_interface.pk,
|
'connected_interface': peer_interface.pk,
|
||||||
'connection_status': self.connection_status
|
'connection_status': self.connection_status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
parent_obj = interface.parent
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
parent_obj = None
|
||||||
|
|
||||||
ObjectChange(
|
ObjectChange(
|
||||||
user=user,
|
user=user,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
changed_object=interface,
|
changed_object=interface,
|
||||||
related_object=interface.parent,
|
related_object=parent_obj,
|
||||||
action=OBJECTCHANGE_ACTION_UPDATE,
|
action=OBJECTCHANGE_ACTION_UPDATE,
|
||||||
object_data=serialize_object(interface, extra=connection_data)
|
object_data=serialize_object(interface, extra=connection_data)
|
||||||
).save()
|
).save()
|
||||||
|
@ -11,6 +11,7 @@ OBJ_TYPE_CHOICES = (
|
|||||||
('DCIM', (
|
('DCIM', (
|
||||||
('site', 'Sites'),
|
('site', 'Sites'),
|
||||||
('rack', 'Racks'),
|
('rack', 'Racks'),
|
||||||
|
('rackgroup', 'Rack Groups'),
|
||||||
('devicetype', 'Device types'),
|
('devicetype', 'Device types'),
|
||||||
('device', 'Devices'),
|
('device', 'Devices'),
|
||||||
('virtualchassis', 'Virtual Chassis'),
|
('virtualchassis', 'Virtual Chassis'),
|
||||||
|
@ -21,7 +21,7 @@ except ImportError:
|
|||||||
"Configuration file is not present. Please define netbox/netbox/configuration.py per the documentation."
|
"Configuration file is not present. Please define netbox/netbox/configuration.py per the documentation."
|
||||||
)
|
)
|
||||||
|
|
||||||
VERSION = '2.4.4-dev'
|
VERSION = '2.5.0-dev'
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
@ -10,9 +10,16 @@ from rest_framework.views import APIView
|
|||||||
from circuits.filters import CircuitFilter, ProviderFilter
|
from circuits.filters import CircuitFilter, ProviderFilter
|
||||||
from circuits.models import Circuit, Provider
|
from circuits.models import Circuit, Provider
|
||||||
from circuits.tables import CircuitTable, ProviderTable
|
from circuits.tables import CircuitTable, ProviderTable
|
||||||
from dcim.filters import DeviceFilter, DeviceTypeFilter, RackFilter, SiteFilter, VirtualChassisFilter
|
from dcim.filters import (
|
||||||
from dcim.models import ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, Site, VirtualChassis
|
DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter
|
||||||
from dcim.tables import DeviceDetailTable, DeviceTypeTable, RackTable, SiteTable, VirtualChassisTable
|
)
|
||||||
|
from dcim.models import (
|
||||||
|
ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, RackGroup, Site,
|
||||||
|
VirtualChassis
|
||||||
|
)
|
||||||
|
from dcim.tables import (
|
||||||
|
DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable
|
||||||
|
)
|
||||||
from extras.models import ObjectChange, ReportResult, TopologyMap
|
from extras.models import ObjectChange, ReportResult, TopologyMap
|
||||||
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
|
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
||||||
@ -56,6 +63,12 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'table': RackTable,
|
'table': RackTable,
|
||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
|
('rackgroup', {
|
||||||
|
'queryset': RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')),
|
||||||
|
'filter': RackGroupFilter,
|
||||||
|
'table': RackGroupTable,
|
||||||
|
'url': 'dcim:rackgroup_list',
|
||||||
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
|
'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
|
||||||
'filter': DeviceTypeFilter,
|
'filter': DeviceTypeFilter,
|
||||||
|
@ -447,7 +447,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% if device_bays or device.device_type.is_parent_device %}
|
{% if device_bays or device.device_type.is_parent_device %}
|
||||||
{% if perms.dcim.delete_devicebay %}
|
{% if perms.dcim.delete_devicebay %}
|
||||||
<form method="post" action="{% url 'dcim:devicebay_bulk_delete' pk=device.pk %}">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@ -483,7 +483,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if device_bays and perms.dcim.delete_devicebay %}
|
{% if device_bays and perms.dcim.delete_devicebay %}
|
||||||
<button type="submit" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' pk=device.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.dcim.change_interface %}
|
{% if perms.dcim.change_interface %}
|
||||||
<a href="{% url 'dcim:interface_edit' pk=interface.pk %}" class="btn btn-warning">
|
<a href="{% if interface.device %}{% url 'dcim:interface_edit' pk=interface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=interface.pk %}{% endif %}" class="btn btn-warning">
|
||||||
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
|
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_interface %}
|
{% if perms.dcim.delete_interface %}
|
||||||
<a href="{% url 'dcim:interface_delete' pk=interface.pk %}" class="btn btn-danger">
|
<a href="{% if interface.device %}{% url 'dcim:interface_delete' pk=interface.pk %}{% else %}{% url 'virtualization:interface_delete' pk=interface.pk %}{% endif %}" class="btn btn-danger">
|
||||||
<span class="fa fa-trash" aria-hidden="true"></span> Delete this interface
|
<span class="fa fa-trash" aria-hidden="true"></span> Delete this interface
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -315,9 +315,9 @@
|
|||||||
$('button.toggle-ips').click(function() {
|
$('button.toggle-ips').click(function() {
|
||||||
var selected = $(this).attr('selected');
|
var selected = $(this).attr('selected');
|
||||||
if (selected) {
|
if (selected) {
|
||||||
$('#interfaces_table tr.ipaddress').hide();
|
$('#interfaces_table tr.ipaddresses').hide();
|
||||||
} else {
|
} else {
|
||||||
$('#interfaces_table tr.ipaddress').show();
|
$('#interfaces_table tr.ipaddresses').show();
|
||||||
}
|
}
|
||||||
$(this).attr('selected', !selected);
|
$(this).attr('selected', !selected);
|
||||||
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
||||||
|
@ -106,10 +106,9 @@ class TimeZoneField(Field):
|
|||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
if not data:
|
if not data:
|
||||||
return ""
|
return ""
|
||||||
try:
|
if data not in pytz.common_timezones:
|
||||||
return pytz.timezone(str(data))
|
raise ValidationError('Unknown time zone "{}" (see pytz.common_timezones for all options)'.format(data))
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
return pytz.timezone(data)
|
||||||
raise ValidationError('Invalid time zone "{}"'.format(data))
|
|
||||||
|
|
||||||
|
|
||||||
class SerializedPKRelatedField(PrimaryKeyRelatedField):
|
class SerializedPKRelatedField(PrimaryKeyRelatedField):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
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.urls import reverse
|
||||||
@ -181,17 +182,21 @@ class ClusterAddDevicesView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
# Assign the selected Devices to the Cluster
|
device_pks = form.cleaned_data['devices']
|
||||||
devices = form.cleaned_data['devices']
|
with transaction.atomic():
|
||||||
Device.objects.filter(pk__in=devices).update(cluster=cluster)
|
|
||||||
|
# Assign the selected Devices to the Cluster
|
||||||
|
for device in Device.objects.filter(pk__in=device_pks):
|
||||||
|
device.cluster = cluster
|
||||||
|
device.save()
|
||||||
|
|
||||||
messages.success(request, "Added {} devices to cluster {}".format(
|
messages.success(request, "Added {} devices to cluster {}".format(
|
||||||
len(devices), cluster
|
len(device_pks), cluster
|
||||||
))
|
))
|
||||||
return redirect(cluster.get_absolute_url())
|
return redirect(cluster.get_absolute_url())
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'cluser': cluster,
|
'cluster': cluster,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': cluster.get_absolute_url(),
|
'return_url': cluster.get_absolute_url(),
|
||||||
})
|
})
|
||||||
@ -210,12 +215,16 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View):
|
|||||||
form = self.form(request.POST)
|
form = self.form(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
# Remove the selected Devices from the Cluster
|
device_pks = form.cleaned_data['pk']
|
||||||
devices = form.cleaned_data['pk']
|
with transaction.atomic():
|
||||||
Device.objects.filter(pk__in=devices).update(cluster=None)
|
|
||||||
|
# Remove the selected Devices from the Cluster
|
||||||
|
for device in Device.objects.filter(pk__in=device_pks):
|
||||||
|
device.cluster = None
|
||||||
|
device.save()
|
||||||
|
|
||||||
messages.success(request, "Removed {} devices from cluster {}".format(
|
messages.success(request, "Removed {} devices from cluster {}".format(
|
||||||
len(devices), cluster
|
len(device_pks), cluster
|
||||||
))
|
))
|
||||||
return redirect(cluster.get_absolute_url())
|
return redirect(cluster.get_absolute_url())
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user