Compare commits

...

4 Commits

Author SHA1 Message Date
Martin Hauser
a696f71656 Merge 60fce84c96 into f0507d00bf 2025-12-10 21:30:01 +01:00
Martin Hauser
60fce84c96 feat(ipam): Normalize numeric ranges in API output
Adds logic to handle numeric range fields in API responses by
converting them into inclusive `[low, high]` pairs for consistent
behavior. Updates test cases with `vid_ranges` fields to reflect the
changes.

Closes #20491
2025-12-10 21:11:23 +01:00
github-actions
f0507d00bf Update source translation strings
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
2025-12-10 05:02:48 +00:00
Arthur Hanson
77b389f105 Fixes #20873: fix webhooks with image fields (#20955) 2025-12-09 22:06:11 -06:00
5 changed files with 364 additions and 346 deletions

View File

@@ -119,7 +119,9 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
if snapshots: if snapshots:
params["snapshots"] = snapshots params["snapshots"] = snapshots
if request: if request:
params["request"] = copy_safe_request(request) # Exclude FILES - webhooks don't need uploaded files,
# which can cause pickle errors with Pillow.
params["request"] = copy_safe_request(request, include_files=False)
# Enqueue the task # Enqueue the task
rq_queue.enqueue( rq_queue.enqueue(

View File

@@ -1071,14 +1071,17 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
{ {
'name': 'VLAN Group 4', 'name': 'VLAN Group 4',
'slug': 'vlan-group-4', 'slug': 'vlan-group-4',
'vid_ranges': [[1, 4094]]
}, },
{ {
'name': 'VLAN Group 5', 'name': 'VLAN Group 5',
'slug': 'vlan-group-5', 'slug': 'vlan-group-5',
'vid_ranges': [[1, 4094]]
}, },
{ {
'name': 'VLAN Group 6', 'name': 'VLAN Group 6',
'slug': 'vlan-group-6', 'slug': 'vlan-group-6',
'vid_ranges': [[1, 4094]]
}, },
] ]
bulk_update_data = { bulk_update_data = {

File diff suppressed because it is too large Load Diff

View File

@@ -35,27 +35,34 @@ class NetBoxFakeRequest:
# Utility functions # Utility functions
# #
def copy_safe_request(request): def copy_safe_request(request, include_files=True):
""" """
Copy selected attributes from a request object into a new fake request object. This is needed in places where Copy selected attributes from a request object into a new fake request object. This is needed in places where
thread safe pickling of the useful request data is needed. thread safe pickling of the useful request data is needed.
Args:
request: The original request object
include_files: Whether to include request.FILES.
""" """
meta = { meta = {
k: request.META[k] k: request.META[k]
for k in HTTP_REQUEST_META_SAFE_COPY for k in HTTP_REQUEST_META_SAFE_COPY
if k in request.META and isinstance(request.META[k], str) if k in request.META and isinstance(request.META[k], str)
} }
return NetBoxFakeRequest({ data = {
'META': meta, 'META': meta,
'COOKIES': request.COOKIES, 'COOKIES': request.COOKIES,
'POST': request.POST, 'POST': request.POST,
'GET': request.GET, 'GET': request.GET,
'FILES': request.FILES,
'user': request.user, 'user': request.user,
'method': request.method, 'method': request.method,
'path': request.path, 'path': request.path,
'id': getattr(request, 'id', None), # UUID assigned by middleware 'id': getattr(request, 'id', None), # UUID assigned by middleware
}) }
if include_files:
data['FILES'] = request.FILES
return NetBoxFakeRequest(data)
def get_client_ip(request, additional_headers=()): def get_client_ip(request, additional_headers=()):

View File

@@ -141,8 +141,8 @@ class ModelTestCase(TestCase):
elif value and type(field) is GenericForeignKey: elif value and type(field) is GenericForeignKey:
model_dict[key] = value.pk model_dict[key] = value.pk
# Handle API output
elif api: elif api:
# Replace ContentType numeric IDs with <app_label>.<model> # Replace ContentType numeric IDs with <app_label>.<model>
if type(getattr(instance, key)) in (ContentType, ObjectType): if type(getattr(instance, key)) in (ContentType, ObjectType):
object_type = ObjectType.objects.get(pk=value) object_type = ObjectType.objects.get(pk=value)
@@ -152,9 +152,13 @@ class ModelTestCase(TestCase):
elif type(value) is IPNetwork: elif type(value) is IPNetwork:
model_dict[key] = str(value) model_dict[key] = str(value)
else: # Normalize arrays of numeric ranges (e.g. VLAN IDs or port ranges).
field = instance._meta.get_field(key) # DB uses canonical half-open [lo, hi) via NumericRange; API uses inclusive [lo, hi].
# Convert to inclusive pairs for stable API comparisons.
elif type(field) is ArrayField and issubclass(type(field.base_field), RangeField):
model_dict[key] = [[r.lower, r.upper - 1] for r in value]
else:
# Convert ArrayFields to CSV strings # Convert ArrayFields to CSV strings
if type(field) is ArrayField: if type(field) is ArrayField:
if getattr(field.base_field, 'choices', None): if getattr(field.base_field, 'choices', None):