diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index ccb3c65ff..b773ee63b 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -1054,33 +1054,48 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): return get_permission_for_model(self.queryset.model, 'change') def post(self, request): + logger = logging.getLogger('netbox.views.BulkRenameView') if '_preview' in request.POST or '_apply' in request.POST: form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')}) selected_objects = self.queryset.filter(pk__in=form.initial['pk']) if form.is_valid(): - for obj in selected_objects: - 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) + try: + with transaction.atomic(): + renamed_pks = [] + for obj in selected_objects: + 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) + renamed_pks.append(obj.pk) - if '_apply' in request.POST: - for obj in selected_objects: - obj.name = obj.new_name - obj.save() - messages.success(request, "Renamed {} {}".format( - len(selected_objects), - self.queryset.model._meta.verbose_name_plural - )) - return redirect(self.get_return_url(request)) + if '_apply' in request.POST: + for obj in selected_objects: + obj.name = obj.new_name + obj.save() + + # Enforce constrained permissions + if self.queryset.filter(pk__in=renamed_pks).count() != len(selected_objects): + raise ObjectDoesNotExist + + messages.success(request, "Renamed {} {}".format( + len(selected_objects), + self.queryset.model._meta.verbose_name_plural + )) + return redirect(self.get_return_url(request)) + + except ObjectDoesNotExist: + msg = "Object update failed due to object-level permissions violation" + logger.debug(msg) + form.add_error(None, msg) else: form = self.form(initial={'pk': request.POST.getlist('pk')})