From f499f2dd66d6e2526917c154319a1efa5f5effd3 Mon Sep 17 00:00:00 2001 From: dansheps Date: Fri, 14 Dec 2018 11:51:20 -0600 Subject: [PATCH 01/18] Closes #2693 --- netbox/project-static/css/base.css | 5 +++++ netbox/utilities/constants.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 155dd21b7..eff4876df 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -119,6 +119,11 @@ label.required { input[name="pk"] { margin-top: 0; } +/* Get around the background color problem if someone chooses white for the background */ +label[style="background-color: #ffffff"] { + color: #000000; +} + /* Tables */ th.pk, td.pk { diff --git a/netbox/utilities/constants.py b/netbox/utilities/constants.py index 64c2fab85..ad6e8fd90 100644 --- a/netbox/utilities/constants.py +++ b/netbox/utilities/constants.py @@ -2,6 +2,7 @@ COLOR_CHOICES = ( ('aa1409', 'Dark red'), ('f44336', 'Red'), ('e91e63', 'Pink'), + ('ffe4e1', 'Rose'), ('ff66ff', 'Fuschia'), ('9c27b0', 'Purple'), ('673ab7', 'Dark purple'), @@ -10,6 +11,7 @@ COLOR_CHOICES = ( ('03a9f4', 'Light blue'), ('00bcd4', 'Cyan'), ('009688', 'Teal'), + ('00ffff', 'Aqua'), ('2f6a31', 'Dark green'), ('4caf50', 'Green'), ('8bc34a', 'Light green'), @@ -23,4 +25,5 @@ COLOR_CHOICES = ( ('9e9e9e', 'Grey'), ('607d8b', 'Dark grey'), ('111111', 'Black'), + ('ffffff', 'White'), ) From d144d3a5840beca06113ef0de07c6511fd608f47 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 21 Dec 2018 11:48:12 -0500 Subject: [PATCH 02/18] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index bd4574487..8651c3d26 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -22,7 +22,7 @@ except ImportError: ) -VERSION = '2.5.2' +VERSION = '2.5.3-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From c6d9206dd135ddfc204ef12231864e2ff21ff908 Mon Sep 17 00:00:00 2001 From: TakeMeNL Date: Sat, 29 Dec 2018 22:16:12 +0100 Subject: [PATCH 03/18] Added ability to search for cables in global search --- netbox/netbox/forms.py | 1 + netbox/netbox/views.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/forms.py b/netbox/netbox/forms.py index d5ab09410..a2ad1376b 100644 --- a/netbox/netbox/forms.py +++ b/netbox/netbox/forms.py @@ -15,6 +15,7 @@ OBJ_TYPE_CHOICES = ( ('devicetype', 'Device types'), ('device', 'Devices'), ('virtualchassis', 'Virtual Chassis'), + ('cable', 'Cables'), )), ('IPAM', ( ('vrf', 'VRFs'), diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 263acb8ee..9340ce2b6 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -11,13 +11,13 @@ from circuits.filters import CircuitFilter, ProviderFilter from circuits.models import Circuit, Provider from circuits.tables import CircuitTable, ProviderTable from dcim.filters import ( - DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter + DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter, CableFilter ) from dcim.models import ( - Cable, ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis + Cable, ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis, Cable ) from dcim.tables import ( - DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable + DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable, CableTable ) from extras.models import ObjectChange, ReportResult, TopologyMap from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter @@ -88,6 +88,12 @@ SEARCH_TYPES = OrderedDict(( 'table': VirtualChassisTable, 'url': 'dcim:virtualchassis_list', }), + ('cable', { + 'queryset': Cable.objects.all(), + 'filter': CableFilter, + 'table': CableTable, + 'url': 'dcim:cable_list', + }), # IPAM ('vrf', { 'queryset': VRF.objects.select_related('tenant'), From 0c86693dc4a8783022b4917ecd49f832bc11f2b8 Mon Sep 17 00:00:00 2001 From: dansheps Date: Thu, 3 Jan 2019 13:20:29 -0600 Subject: [PATCH 04/18] Closes #2693 * Revert CSS Background Hack --- netbox/project-static/css/base.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index eff4876df..155dd21b7 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -119,11 +119,6 @@ label.required { input[name="pk"] { margin-top: 0; } -/* Get around the background color problem if someone chooses white for the background */ -label[style="background-color: #ffffff"] { - color: #000000; -} - /* Tables */ th.pk, td.pk { From 3101a86381b42f73077516efc20cc17e86a01c08 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 3 Jan 2019 15:30:12 -0500 Subject: [PATCH 05/18] Changelog updates; import cleanup --- CHANGELOG.md | 9 +++++++++ netbox/netbox/views.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f5ee223..2999d3e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +v2.5.3 (FUTURE) + +## Enhancements + +* [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors +* [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search + +--- + v2.5.2 (2018-12-21) ## Enhancements diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 9340ce2b6..ff11e3892 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -11,13 +11,13 @@ from circuits.filters import CircuitFilter, ProviderFilter from circuits.models import Circuit, Provider from circuits.tables import CircuitTable, ProviderTable from dcim.filters import ( - DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter, CableFilter + CableFilter, DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter ) from dcim.models import ( - Cable, ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis, Cable + Cable, ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis ) from dcim.tables import ( - DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable, CableTable + CableTable, DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable ) from extras.models import ObjectChange, ReportResult, TopologyMap from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter From 209a9f0ffcc64ce8095b7e4119524c643a805a77 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 3 Jan 2019 16:21:21 -0500 Subject: [PATCH 06/18] Closes #1630: Enable bulk editing of prefix/IP mask length --- CHANGELOG.md | 1 + netbox/ipam/forms.py | 10 ++++++++++ netbox/ipam/models.py | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2999d3e82..e3724ddf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ v2.5.3 (FUTURE) ## Enhancements +* [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 570716532..b6209f5df 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -422,6 +422,11 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF required=False, label='VRF' ) + prefix_length = forms.IntegerField( + min_value=1, + max_value=127, + required=False + ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), required=False @@ -819,6 +824,11 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd required=False, label='VRF' ) + mask_length = forms.IntegerField( + min_value=1, + max_value=128, + required=False + ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), required=False diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 789e65b82..6f7a21236 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -385,6 +385,15 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): self.description, ) + def _set_prefix_length(self, value): + """ + Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly, + e.g. for bulk editing. + """ + if self.prefix is not None: + self.prefix.prefixlen = value + prefix_length = property(fset=_set_prefix_length) + def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] @@ -630,6 +639,15 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): self.description, ) + def _set_mask_length(self, value): + """ + Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly, + e.g. for bulk editing. + """ + if self.address is not None: + self.address.prefixlen = value + mask_length = property(fset=_set_mask_length) + @property def device(self): if self.interface: From 0a820d9c98406b067f3d5e29aff7b026def51189 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 3 Jan 2019 16:59:49 -0500 Subject: [PATCH 07/18] Closes #1871: Enable filtering sites by parent region --- CHANGELOG.md | 1 + netbox/dcim/filters.py | 22 ++++++++++++++++------ netbox/dcim/forms.py | 3 ++- netbox/dcim/models.py | 7 +++++++ netbox/dcim/views.py | 2 +- netbox/utilities/forms.py | 8 +++++--- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3724ddf0..683090ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ v2.5.3 (FUTURE) ## Enhancements * [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length +* [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 0d9deadc2..0f186f162 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -62,14 +62,14 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): choices=SITE_STATUS_CHOICES, null_value=None ) - region_id = django_filters.ModelMultipleChoiceFilter( - queryset=Region.objects.all(), + region_id = django_filters.NumberFilter( + method='filter_region', + field_name='pk', label='Region (ID)', ) - region = django_filters.ModelMultipleChoiceFilter( - field_name='region__slug', - queryset=Region.objects.all(), - to_field_name='slug', + region = django_filters.CharFilter( + method='filter_region', + field_name='slug', label='Region (slug)', ) tenant_id = django_filters.ModelMultipleChoiceFilter( @@ -108,6 +108,16 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): pass return queryset.filter(qs_filter) + def filter_region(self, queryset, name, value): + try: + region = Region.objects.get(**{name: value}) + except ObjectDoesNotExist: + return queryset.none() + return queryset.filter( + Q(region=region) | + Q(region__in=region.get_descendants()) + ) + class RackGroupFilter(django_filters.FilterSet): q = django_filters.CharFilter( diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index d02235277..61feba08c 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -236,9 +236,10 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm): required=False ) region = FilterTreeNodeMultipleChoiceField( - queryset=Region.objects.annotate(filter_count=Count('sites')), + queryset=Region.objects.all(), to_field_name='slug', required=False, + count_attr='site_count' ) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('sites')), diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 347f0c8b8..567f9046e 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -201,6 +201,13 @@ class Region(MPTTModel, ChangeLoggedModel): self.parent.name if self.parent else None, ) + @property + def site_count(self): + return Site.objects.filter( + Q(region=self) | + Q(region__in=self.get_descendants()) + ).count() + # # Sites diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 1d74c1c85..93a5178f3 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -124,7 +124,7 @@ class BulkDisconnectView(GetReturnURLMixin, View): # class RegionListView(ObjectListView): - queryset = Region.objects.annotate(site_count=Count('sites')) + queryset = Region.objects.all() filter = filters.RegionFilter filter_form = forms.RegionFilterForm table = tables.RegionTable diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 46dbcc789..b531fa637 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -505,8 +505,9 @@ class FilterChoiceIterator(forms.models.ModelChoiceIterator): class FilterChoiceFieldMixin(object): iterator = FilterChoiceIterator - def __init__(self, null_label=None, *args, **kwargs): + def __init__(self, null_label=None, count_attr='filter_count', *args, **kwargs): self.null_label = null_label + self.count_attr = count_attr if 'required' not in kwargs: kwargs['required'] = False if 'widget' not in kwargs: @@ -515,8 +516,9 @@ class FilterChoiceFieldMixin(object): def label_from_instance(self, obj): label = super().label_from_instance(obj) - if hasattr(obj, 'filter_count'): - return '{} ({})'.format(label, obj.filter_count) + obj_count = getattr(obj, self.count_attr, None) + if obj_count is not None: + return '{} ({})'.format(label, obj_count) return label From 848aa0b0980cec86cf5dc5873cb5afc9f0a04e73 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Jan 2019 11:07:55 -0500 Subject: [PATCH 08/18] Closes #1870: Add per-page toggle to object lists --- CHANGELOG.md | 1 + netbox/netbox/settings.py | 8 ++++++++ netbox/project-static/css/base.css | 3 +++ netbox/project-static/js/forms.js | 5 +++++ netbox/templates/inc/paginator.html | 9 +++++++- netbox/templates/responsive_table.html | 3 --- netbox/templates/utilities/obj_table.html | 25 +++++++++++++---------- 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 683090ceb..3406e5497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ v2.5.3 (FUTURE) ## Enhancements * [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length +* [#1870](https://github.com/digitalocean/netbox/issues/1870) - Add per-page toggle to object lists * [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8651c3d26..e50e9bd72 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -246,6 +246,14 @@ LOGIN_URL = '/{}login/'.format(BASE_PATH) # Secrets SECRETS_MIN_PUBKEY_SIZE = 2048 +# Pagination +PER_PAGE_DEFAULTS = [ + 25, 50, 100, 250, 500, 1000 +] +if PAGINATE_COUNT not in PER_PAGE_DEFAULTS: + PER_PAGE_DEFAULTS.append(PAGINATE_COUNT) + PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS) + # Django filters FILTERS_NULL_CHOICE_LABEL = 'None' FILTERS_NULL_CHOICE_VALUE = 'null' diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 155dd21b7..ad1b02e3f 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -140,6 +140,9 @@ table.attr-table td:nth-child(1) { div.paginator { margin-bottom: 20px; } +div.paginator form { + margin-bottom: 6px; +} nav ul.pagination { margin-top: 0; margin-bottom: 8px !important; diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index fa23f1592..a1b2fca60 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -1,5 +1,10 @@ $(document).ready(function() { + // Pagination + $('select#per_page').change(function() { + this.form.submit(); + }); + // "Toggle" checkbox for object lists (PK column) $('input:checkbox.toggle').click(function() { $(this).closest('table').find('input:checkbox[name=pk]').prop('checked', $(this).prop('checked')); diff --git a/netbox/templates/inc/paginator.html b/netbox/templates/inc/paginator.html index 27d04c15c..e7be9eddb 100644 --- a/netbox/templates/inc/paginator.html +++ b/netbox/templates/inc/paginator.html @@ -1,6 +1,6 @@ {% load helpers %} -
+
{% if paginator.num_pages > 1 %} +
+ per page +
{% endif %} {% if page %}
diff --git a/netbox/templates/responsive_table.html b/netbox/templates/responsive_table.html index 81d9126fd..a6aaf5a6f 100644 --- a/netbox/templates/responsive_table.html +++ b/netbox/templates/responsive_table.html @@ -3,6 +3,3 @@
{% render_table table 'inc/table.html' %}
-{% with paginator=table.paginator page=table.page %} - {% include 'inc/paginator.html' %} -{% endwith %} diff --git a/netbox/templates/utilities/obj_table.html b/netbox/templates/utilities/obj_table.html index 058c7ef07..8c5e3b8be 100644 --- a/netbox/templates/utilities/obj_table.html +++ b/netbox/templates/utilities/obj_table.html @@ -28,19 +28,22 @@
{% endif %} {% include table_template|default:'responsive_table.html' %} - {% block extra_actions %}{% endblock %} - {% if bulk_edit_url and permissions.change %} - - {% endif %} - {% if bulk_delete_url and permissions.delete %} - - {% endif %} +
+ {% block extra_actions %}{% endblock %} + {% if bulk_edit_url and permissions.change %} + + {% endif %} + {% if bulk_delete_url and permissions.delete %} + + {% endif %} +
{% else %} {% include table_template|default:'responsive_table.html' %} {% endif %} + {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
From 99dc46a89e12e68b91f9598b6b0f7ed7f0d5135c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Jan 2019 11:46:53 -0500 Subject: [PATCH 09/18] Fixes #2742: Preserve cluster assignment when editing a device --- CHANGELOG.md | 5 +++++ netbox/dcim/forms.py | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3406e5497..8a56c967b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ v2.5.3 (FUTURE) * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search +## Bug Fixes + +* [#2742](https://github.com/digitalocean/netbox/issues/2742) - Preserve cluster assignment when editing a device + + --- v2.5.2 (2018-12-21) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 61feba08c..c15952330 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1213,11 +1213,13 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): # Initialize helper selectors instance = kwargs.get('instance') + if 'initial' not in kwargs: + kwargs['initial'] = {} # Using hasattr() instead of "is not None" to avoid RelatedObjectDoesNotExist on required field if instance and hasattr(instance, 'device_type'): - initial = kwargs.get('initial', {}).copy() - initial['manufacturer'] = instance.device_type.manufacturer - kwargs['initial'] = initial + kwargs['initial']['manufacturer'] = instance.device_type.manufacturer + if instance and instance.cluster is not None: + kwargs['initial']['cluster_group'] = instance.cluster.group super().__init__(*args, **kwargs) From 170e01b549d9bc4909a952f13c062de118bf17fd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Jan 2019 12:30:38 -0500 Subject: [PATCH 10/18] Closes #1983: Enable regular expressions when bulk renaming device components --- CHANGELOG.md | 1 + netbox/dcim/forms.py | 16 ++++++++++++++++ netbox/dcim/views.py | 13 ++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a56c967b..fd64207ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ v2.5.3 (FUTURE) * [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length * [#1870](https://github.com/digitalocean/netbox/issues/1870) - Add per-page toggle to object lists * [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region +* [#1983](https://github.com/digitalocean/netbox/issues/1983) - Enable regular expressions when bulk renaming device components * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c15952330..887e7f74f 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -58,6 +58,22 @@ class BulkRenameForm(forms.Form): """ find = forms.CharField() replace = forms.CharField() + use_regex = forms.BooleanField( + required=False, + initial=True, + label='Use regular expressions' + ) + + def clean(self): + + # Validate regular expression in "find" field + if self.cleaned_data['use_regex']: + try: + re.compile(self.cleaned_data['find']) + except re.error: + raise forms.ValidationError({ + 'find': "Invalid regular expression" + }) # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 93a5178f3..4c2dee5df 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,3 +1,5 @@ +import re + from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.paginator import EmptyPage, PageNotAnInteger @@ -50,7 +52,16 @@ class BulkRenameView(GetReturnURLMixin, View): if form.is_valid(): for obj in selected_objects: - obj.new_name = obj.name.replace(form.cleaned_data['find'], form.cleaned_data['replace']) + find = form.cleaned_data['find'] + replace = form.cleaned_data['replace'] + if form.cleaned_data['use_regex']: + try: + obj.new_name = re.sub(find, replace, obj.name) + # Catch regex group reference errors + except re.error: + obj.new_name = obj.name + else: + obj.new_name = obj.name.replace(find, replace) if '_apply' in request.POST: for obj in selected_objects: From f7f6704fc1956427608c86870f4bd609df0fb8ef Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Jan 2019 13:17:24 -0500 Subject: [PATCH 11/18] Preserve filtering/ordering parameters when modifying per_page count --- netbox/templates/inc/paginator.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netbox/templates/inc/paginator.html b/netbox/templates/inc/paginator.html index e7be9eddb..ac4648988 100644 --- a/netbox/templates/inc/paginator.html +++ b/netbox/templates/inc/paginator.html @@ -20,6 +20,11 @@
+ {% for k, v in request.GET.items %} + {% if k != 'per_page' %} + + {% endif %} + {% endfor %}