-
Hi all. I'm fairly new when it comes to working with Django, but I've gotten a decent idea of how the Models are structured in Nautobot. In one of my jobs, I'm looking for a way to duplicate a device multiple times (to create stack members of a virtual chassis). I've seen a lot of recommendations on how to duplicate Model objects, including the Django recommended way. For example: import nautobot.dcim.models as dcim_m
class CreateStack(Job):
...
def new_member(self, head_device: dcim_m.Device, ... ) -> dcim_m.Device:
....
head_device_fork = dcim_m.Device.objects.get(pk=head_device.pk)
head_device_fork.pk = None
head_device_fork.id = None # type: ignore
head_device_fork._state.adding = True
if head_device.virtual_chassis is not None:
head_device_fork.vc_position = member_number
head_device_fork.vc_priority = vc_priority
head_device_fork.name = base_name + f"-{member_number}"
head_device_fork.position = rack_position
head_device_fork.validated_save()
return head_device_fork I've noticed that, in my dry-runs of this job, new "copied" instances of things like the Interfaces and Power Ports of the original device are created on their own, which is great(?). I didn't expect it to do that all on its own. The only place it seems to run into trouble is whenever any one-to-one relationships exist, such as a Device having a Primary IPv4 Address assigned. As long as the cloned object has those one-to-one fields cleared out before being saved, it'll go through successfully. However, I haven't handled any Models yet that have a many-to-many relationship. While it seems to work well to me, I definitely don't think I have the full picture. Is it safe to copy Model instances this way? Could this cause any database issues down the road? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
I would recommend not doing this and instead being more declarative in exactly which fields you would like to carry over in a new instance, for example: def new_member(self, head_device: dcim_m.Device, ... ) -> dcim_m.Device:
....
# Explicitly declare the other fields you want from head_device here
head_device_fork = dcim_m.Device(
name=head_device.name,
asset_tag=head_device.asset_tag,
# etc...
)
# The rest of this still applies since this is also declarative
if head_device.virtual_chassis is not None:
head_device_fork.vc_position = member_number
head_device_fork.vc_priority = vc_priority
head_device_fork.name = base_name + f"-{member_number}"
head_device_fork.position = rack_position
head_device_fork.validated_save()
return head_device_fork You might also try a pattern like this that's a little more dynamic but still explicit: def new_member(self, head_device: dcim_m.Device, ... ) -> dcim_m.Device
head_device_fork = dcim_m.Device()
desired_fields = ["name", "asset_tag", ...]
for field_name in desired_fields:
forked_value = getattr(head_device, field_name)
setattr(head_device_fork, field_name, forked_value)
# And then the rest of your code. |
Beta Was this translation helpful? Give feedback.
I would recommend not doing this and instead being more declarative in exactly which fields you would like to carry over in a new instance, for example: