Cannot save aggregate by using .save() in custom script #3335

Closed
opened 2025-12-29 18:27:52 +01:00 by adam · 3 comments
Owner

Originally created by @slinderud on GitHub (Feb 15, 2020).

Environment

  • Python version: 3.5.3
  • NetBox version: 2.7.6

Steps to Reproduce

  1. Create a new aggregate prefix.
  2. Upload this script to /opt/netbox/netbox/scripts:
from extras.scripts import *

from ipam.constants import *
from ipam.models import Aggregate, Prefix, IPAddress

import netaddr

class BugTest(Script):

    class Meta:
        name = "Change prefix recursive"
        description = "Change a top level prefix"
        field_order = ['old_prefix', 'new_prefix']

    old_prefix = ObjectVar(
        description="Prefix you want to change",
        queryset = Aggregate.objects.all()
    )

    new_prefix = IPNetworkVar(
        description="The new prefix"
    )

    def run(self, data):
        old_prefix = str(data['old_prefix'])
        new_prefix = str(data['new_prefix'])

        aggregate_dict_obj = Aggregate.objects.get(prefix=old_prefix)
        aggregate_dict_obj.prefix = new_prefix
        aggregate_dict_obj.save()
        self.log_success("Converted old aggregate: {} to new aggregate: {}".format(old_prefix, new_prefix))
  1. Use the custom script to try change the aggregate prefix to something new.

Expected Behavior

The aggregate would change

Observed Behavior

Traceback (most recent call last):
  File "/opt/netbox/netbox/extras/scripts.py", line 395, in run_script
    output = script.run(data)
  File "/opt/netbox/netbox/scripts/bugfail.py", line 31, in run
    aggregate_dict_obj.save()
  File "/opt/netbox/netbox/ipam/models.py", line 240, in save
    self.family = self.prefix.version
AttributeError: 'str' object has no attribute 'version'

It seems like the save function assumes it's a netaddr.IPNetwork type give it's looking for the .version attribute that doesn't exists.
1a8eea5aa9/netbox/ipam/models.py (L231)

We can confirm this by changing this line:
aggregate_dict_obj.prefix = new_prefix
to
aggregate_dict_obj.prefix = netaddr.IPNetwork(new_prefix)

Now it works!

Most likely the save function has to be changed to something like the one used in Prefix:
1a8eea5aa9/netbox/ipam/models.py (L419)

Originally created by @slinderud on GitHub (Feb 15, 2020). ### Environment * Python version: 3.5.3 * NetBox version: 2.7.6 ### Steps to Reproduce 1. Create a new aggregate prefix. 2. Upload this script to ```/opt/netbox/netbox/scripts```: ``` from extras.scripts import * from ipam.constants import * from ipam.models import Aggregate, Prefix, IPAddress import netaddr class BugTest(Script): class Meta: name = "Change prefix recursive" description = "Change a top level prefix" field_order = ['old_prefix', 'new_prefix'] old_prefix = ObjectVar( description="Prefix you want to change", queryset = Aggregate.objects.all() ) new_prefix = IPNetworkVar( description="The new prefix" ) def run(self, data): old_prefix = str(data['old_prefix']) new_prefix = str(data['new_prefix']) aggregate_dict_obj = Aggregate.objects.get(prefix=old_prefix) aggregate_dict_obj.prefix = new_prefix aggregate_dict_obj.save() self.log_success("Converted old aggregate: {} to new aggregate: {}".format(old_prefix, new_prefix)) ``` 3. Use the custom script to try change the aggregate prefix to something new. <!-- What did you expect to happen? --> ### Expected Behavior The aggregate would change <!-- What happened instead? --> ### Observed Behavior ``` Traceback (most recent call last): File "/opt/netbox/netbox/extras/scripts.py", line 395, in run_script output = script.run(data) File "/opt/netbox/netbox/scripts/bugfail.py", line 31, in run aggregate_dict_obj.save() File "/opt/netbox/netbox/ipam/models.py", line 240, in save self.family = self.prefix.version AttributeError: 'str' object has no attribute 'version' ``` <!-- Reason --> It seems like the save function assumes it's a netaddr.IPNetwork type give it's looking for the .version attribute that doesn't exists. https://github.com/netbox-community/netbox/blob/1a8eea5aa943f4f63b76ecadcf9ee7c4ab60e6e2/netbox/ipam/models.py#L231 We can confirm this by changing this line: ``` aggregate_dict_obj.prefix = new_prefix``` to ``` aggregate_dict_obj.prefix = netaddr.IPNetwork(new_prefix)``` Now it works! Most likely the save function has to be changed to something like the one used in Prefix: https://github.com/netbox-community/netbox/blob/1a8eea5aa943f4f63b76ecadcf9ee7c4ab60e6e2/netbox/ipam/models.py#L419
adam closed this issue 2025-12-29 18:27:52 +01:00
Author
Owner

@DanSheps commented on GitHub (Feb 16, 2020):

As you discovered, the model is looking for a IPNetwork, not a string. As such, you need to pass the IPNetwork object yourself derived from the string.

My personal view is this is not a bug. If you need field validation before saving you should call clean() before calling save(). However this would not even save it as it still is not a IPNetwork field.
You should be creating the IPNetwork field from the supplied string.

@DanSheps commented on GitHub (Feb 16, 2020): As you discovered, the model is looking for a IPNetwork, not a string. As such, you need to pass the IPNetwork object yourself derived from the string. My personal view is this is not a bug. If you need field validation before saving you should call clean() before calling save(). However this would not even save it as it still is not a IPNetwork field. You should be creating the IPNetwork field from the supplied string.
Author
Owner

@slinderud commented on GitHub (Feb 16, 2020):

I would agree if saving a prefix/ip would behave the same way.

prefix_dict_obj = Prefix.objects.get(prefix=str(old_p))
prefix_dict_obj.prefix = str(new_p)
prefix_dict_obj.save()
ip_dict_obj = IPAddress.objects.get(address=str(old_ip))
ip_dict_obj.address = str(new_ip)
ip_dict_obj.save()

Both works fine.

Handling these differently doesn't make much sense to me.

@slinderud commented on GitHub (Feb 16, 2020): I would agree if saving a prefix/ip would behave the same way. ``` prefix_dict_obj = Prefix.objects.get(prefix=str(old_p)) prefix_dict_obj.prefix = str(new_p) prefix_dict_obj.save() ``` ``` ip_dict_obj = IPAddress.objects.get(address=str(old_ip)) ip_dict_obj.address = str(new_ip) ip_dict_obj.save() ``` Both works fine. Handling these differently doesn't make much sense to me.
Author
Owner

@jeremystretch commented on GitHub (Feb 17, 2020):

new_prefix = str(data['new_prefix'])
aggregate_dict_obj.prefix = new_prefix

As @DanSheps points out, you can simply assign the field value directly:

aggregate_dict_obj.prefix = data['new_prefix']

The family attribute is being removed in v2.8 anyway (see #4081) so there's no action worth taking here.

@jeremystretch commented on GitHub (Feb 17, 2020): ```python new_prefix = str(data['new_prefix']) aggregate_dict_obj.prefix = new_prefix ``` As @DanSheps points out, you can simply assign the field value directly: ```python aggregate_dict_obj.prefix = data['new_prefix'] ``` The `family` attribute is being removed in v2.8 anyway (see #4081) so there's no action worth taking here.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#3335