CustomField MultiObject GET response not allowed in PATCH request due to NestedSerializer #7428

Closed
opened 2025-12-29 20:23:23 +01:00 by adam · 2 comments
Owner

Originally created by @moonrail on GitHub (Dec 28, 2022).

NetBox version

v3.4.1

Python version

3.11

Steps to Reproduce

  1. Create a Site & Location
  2. Create a multiobject CustomField locations on ContentType virtualization.virtualmachine referencing ObjectType dcim.location
  3. Create a VM and add a Location for the CustomField locations
  4. Use API from now on
  5. GET /virtualizations/virtual-machines/{id_of_created_vm}
  6. Use custom_fields data from GET as is for the following PATCH request
  7. PATCH /virtualizations/virtual-machines/{id_of_created_vm}

Expected Behavior

  • As a user:
    • NetBox is able to serialize its own serialized data from the previous request.
      • This commit brought support for compatibility in this regard: d5538c1ca3. So since 3.3.9 this should work.
  • As an admin running NetBox:
    • NetBox is able to differentiate serializer errors to the user and admin instead of responding with "Unknown related object(s)" for any errors happening in the serializer. Otherwise any admin has to modify some lines of source code of NetBox to print these errors and spin up dev instances with current test data to determine if the logic of the user is the issue or a "real" error is present in NetBox.

Observed Behavior

NetBox is unable to serialize its own serialized data from the previous request.

By running a local instance of NetBox and printing the errors of the serializer we see the following:

[[ErrorDetail(string="Cannot resolve keyword '_depth' into field. Choices are: cabletermination, children, contacts, created, custom_field_data, description, devices, id, images, last_updated, level, lft, name, parent, parent_id, powerpanel, racks, rght, site, site_id, slug, status, tagged_items, tags, tenant, tenant_id, tree_id, vlan_groups", code='invalid')], {}]

This seems to be raised here in WritableNestedSerializer when converting serialized data back: https://github.com/netbox-community/netbox/blob/v3.4.1/netbox/netbox/api/serializers/nested.py#L28-L29

So DRF seems to somehow omit some fields, such as url, as data but all others are passed into to_internal_value and dict_to_filter_params 27c71b8ec0/netbox/utilities/utils.py (L196)
And _depth is not a known field in database, therefore Django raises the error mentioned above that is seemingly catched by DRF serializer exception handling and added to serializers errors.

As WritableNestedSerializer is a generalized class for a lot of NestedSerializers, this issue should therefore also affect others, such as NestedRegionSerializer and NestedSiteSerializer. Both also have a _depth field on the serializer only.

Here are some "proof" screenshots of the issue on demo.netbox.dev using nothing more than the SwaggerUI:

  • GET
    • Screenshot_20221228_122043
  • PATCH
    • Screenshot_20221228_122324
    • Screenshot_20221228_122228

For the possibility of it being useful, here are some pynetbox based lines of python for reproducing the issue:

import pynetbox
import time


api = pynetbox.api(
    url='https://demo.netbox.dev',
    token='...'
)

test_objects_name = 'test_cf_multiobjects'
cf_dummy_name = f'{test_objects_name}_dummy'

print('Ensure a basic CF to enforce pynetbox of sending custom field data later')
cf_dummy = api.extras.custom_fields.get(name=cf_dummy_name)
if cf_dummy is None:
    api.extras.custom_fields.create(
        content_types=[
            "virtualization.virtualmachine"
        ],
        type='text',
        name=cf_dummy_name
    )

print('Ensure multiobject CF referencing locations on VMs')
cf_locations = api.extras.custom_fields.get(name=test_objects_name)
if cf_locations is None:
    api.extras.custom_fields.create(
        content_types=[
            "virtualization.virtualmachine"
        ],
        type='multiobject',
        object_type='dcim.location',
        name=test_objects_name
    )

print('Ensure Test Site, Location & VM')
site = api.dcim.sites.get(name=test_objects_name)
if site is None:
    site = api.dcim.sites.create(
        name=test_objects_name,
        slug=test_objects_name
    )
location = api.dcim.locations.get(name=test_objects_name)
if location is None:
    location = api.dcim.locations.create(
        name=test_objects_name,
        slug=test_objects_name,
        site=site.id
    )
vm = api.virtualization.virtual_machines.get(name=test_objects_name)
if vm is None:
    vm = api.virtualization.virtual_machines.create(
        name=test_objects_name,
        status='planned',
        site=site.id
    )

print('Setting item as pk value')
vm.custom_fields[test_objects_name] = [
    location.id
]
vm.save()

print('Setting item as descriptive attribute')
vm.custom_fields[test_objects_name] = [{
    'name': location.name
}]
vm.save()

print('Updating other custom field value to get pynetbox to send complete custom field data containing unmodified CF multiobject data')
vm = api.virtualization.virtual_machines.get(name=test_objects_name)
vm.custom_fields[cf_dummy_name] = str(time.time())
vm.save()
Originally created by @moonrail on GitHub (Dec 28, 2022). ### NetBox version v3.4.1 ### Python version 3.11 ### Steps to Reproduce 1. Create a Site & Location 2. Create a `multiobject` CustomField `locations` on ContentType `virtualization.virtualmachine` referencing ObjectType `dcim.location` 3. Create a VM and add a Location for the CustomField `locations` 4. Use API from now on 5. GET `/virtualizations/virtual-machines/{id_of_created_vm}` 6. Use `custom_fields` data from GET as is for the following PATCH request 7. PATCH `/virtualizations/virtual-machines/{id_of_created_vm}` ### Expected Behavior - As a user: - NetBox is able to serialize its own serialized data from the previous request. - This commit brought support for compatibility in this regard: https://github.com/netbox-community/netbox/commit/d5538c1ca3d7ef7ca2d457ce22bbc645b8b65505. So since 3.3.9 this should work. - As an admin running NetBox: - NetBox is able to differentiate serializer errors to the user and admin instead of responding with "Unknown related object(s)" for any errors happening in the serializer. Otherwise any admin has to modify some lines of source code of NetBox to print these errors and spin up dev instances with current test data to determine if the logic of the user is the issue or a "real" error is present in NetBox. ### Observed Behavior NetBox is unable to serialize its own serialized data from the previous request. By running a local instance of NetBox and printing the errors of the serializer we see the following: > [[ErrorDetail(string="Cannot resolve keyword '_depth' into field. Choices are: cabletermination, children, contacts, created, custom_field_data, description, devices, id, images, last_updated, level, lft, name, parent, parent_id, powerpanel, racks, rght, site, site_id, slug, status, tagged_items, tags, tenant, tenant_id, tree_id, vlan_groups", code='invalid')], {}] This seems to be raised here in `WritableNestedSerializer` when converting serialized data back: https://github.com/netbox-community/netbox/blob/v3.4.1/netbox/netbox/api/serializers/nested.py#L28-L29 So DRF seems to somehow omit some fields, such as `url`, as data but all others are passed into `to_internal_value` and `dict_to_filter_params` https://github.com/netbox-community/netbox/blob/27c71b8ec0d14f67c3f893713d8986cc7ad6ce3a/netbox/utilities/utils.py#L196 And `_depth` is not a known field in database, therefore Django raises the error mentioned above that is seemingly catched by DRF serializer exception handling and added to serializers errors. As `WritableNestedSerializer` is a generalized class for a lot of NestedSerializers, this issue should therefore also affect others, such as `NestedRegionSerializer` and `NestedSiteSerializer`. Both also have a `_depth` field on the serializer only. Here are some "proof" screenshots of the issue on demo.netbox.dev using nothing more than the SwaggerUI: - GET - ![Screenshot_20221228_122043](https://user-images.githubusercontent.com/40096303/209808578-eac56ff6-e066-4d96-b203-fffaedbb694e.png) - PATCH - ![Screenshot_20221228_122324](https://user-images.githubusercontent.com/40096303/209808575-2e87a36e-7c30-4aad-a9bf-31267b878402.png) - ![Screenshot_20221228_122228](https://user-images.githubusercontent.com/40096303/209808577-64419ae8-0488-43d4-9dee-68dcb608899f.png) For the possibility of it being useful, here are some pynetbox based lines of python for reproducing the issue: ```python import pynetbox import time api = pynetbox.api( url='https://demo.netbox.dev', token='...' ) test_objects_name = 'test_cf_multiobjects' cf_dummy_name = f'{test_objects_name}_dummy' print('Ensure a basic CF to enforce pynetbox of sending custom field data later') cf_dummy = api.extras.custom_fields.get(name=cf_dummy_name) if cf_dummy is None: api.extras.custom_fields.create( content_types=[ "virtualization.virtualmachine" ], type='text', name=cf_dummy_name ) print('Ensure multiobject CF referencing locations on VMs') cf_locations = api.extras.custom_fields.get(name=test_objects_name) if cf_locations is None: api.extras.custom_fields.create( content_types=[ "virtualization.virtualmachine" ], type='multiobject', object_type='dcim.location', name=test_objects_name ) print('Ensure Test Site, Location & VM') site = api.dcim.sites.get(name=test_objects_name) if site is None: site = api.dcim.sites.create( name=test_objects_name, slug=test_objects_name ) location = api.dcim.locations.get(name=test_objects_name) if location is None: location = api.dcim.locations.create( name=test_objects_name, slug=test_objects_name, site=site.id ) vm = api.virtualization.virtual_machines.get(name=test_objects_name) if vm is None: vm = api.virtualization.virtual_machines.create( name=test_objects_name, status='planned', site=site.id ) print('Setting item as pk value') vm.custom_fields[test_objects_name] = [ location.id ] vm.save() print('Setting item as descriptive attribute') vm.custom_fields[test_objects_name] = [{ 'name': location.name }] vm.save() print('Updating other custom field value to get pynetbox to send complete custom field data containing unmodified CF multiobject data') vm = api.virtualization.virtual_machines.get(name=test_objects_name) vm.custom_fields[cf_dummy_name] = str(time.time()) vm.save() ```
adam closed this issue 2025-12-29 20:23:23 +01:00
Author
Owner

@jeremystretch commented on GitHub (Dec 28, 2022):

Use custom_fields data from GET as is for the following PATCH request

This is not going to work; all API requests must abide by the specified schema for their operation. Related objects need to be specified either by their numeric IDs, or by a dictionary of attributes (e.g. {"name": "foo"}). This is true for both custom and native fields.

The error message is expected because the request you've sent is invalid. Modifying your request body to a supported format will resolve the issue.

@jeremystretch commented on GitHub (Dec 28, 2022): > Use custom_fields data from GET as is for the following PATCH request This is not going to work; all API requests must abide by the specified schema for their operation. Related objects need to be specified either by their numeric IDs, or by a dictionary of attributes (e.g. `{"name": "foo"}`). This is true for both custom and native fields. The error message is expected because the request you've sent is invalid. Modifying your request body to a supported format will resolve the issue.
Author
Owner

@moonrail commented on GitHub (Dec 28, 2022):

@jeremystretch
I understand the implementation from your point of view.

I would argue that an Application should be able to consume its own unmodified data it yields to users via its API.

Noone, and I repeat: Not a single client, would be able to GET an object with these CF-DataTypes, modify anything on it and PATCH it as is.
Every client would have to modify or (if not modifying custom field data) remove parts of the instance(s) data before being able to send it to the API.
Why not consider solving this in the application?

@moonrail commented on GitHub (Dec 28, 2022): @jeremystretch I understand the implementation from your point of view. I would argue that an Application should be able to consume its own unmodified data it yields to users via its API. Noone, and I repeat: Not a single client, would be able to GET an object with these CF-DataTypes, modify anything on it and PATCH it as is. Every client would have to modify or (if not modifying custom field data) remove parts of the instance(s) data before being able to send it to the API. Why not consider solving this in the application?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#7428