From ec0fe62df572d68033b3e2daf27244fb99316c46 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 4 Mar 2026 10:44:37 -0500 Subject: [PATCH] Include the current ETag in the 412 response --- netbox/netbox/api/viewsets/__init__.py | 8 +++++++- netbox/utilities/exceptions.py | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py index 42ae1caf9..f257e3966 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -61,7 +61,13 @@ class ETagMixin: if provided := self._get_if_match(request): current_etag = self._get_etag(instance) if current_etag and current_etag not in provided: - raise PreconditionFailed() + raise PreconditionFailed(etag=current_etag) + + def handle_exception(self, exc): + response = super().handle_exception(exc) + if isinstance(exc, PreconditionFailed) and exc.etag: + response['ETag'] = exc.etag + return response def retrieve(self, request, *args, **kwargs): instance = self.get_object() diff --git a/netbox/utilities/exceptions.py b/netbox/utilities/exceptions.py index 8c95f844b..60ac7d26a 100644 --- a/netbox/utilities/exceptions.py +++ b/netbox/utilities/exceptions.py @@ -44,11 +44,16 @@ class PermissionsViolation(Exception): class PreconditionFailed(APIException): """ Raised when an If-Match precondition is not satisfied (HTTP 412). + Optionally carries the current ETag so it can be included in the response. """ status_code = status.HTTP_412_PRECONDITION_FAILED default_detail = 'Precondition failed.' default_code = 'precondition_failed' + def __init__(self, detail=None, code=None, etag=None): + super().__init__(detail=detail, code=code) + self.etag = etag + class RQWorkerNotRunningException(APIException): """