"""Minimal repro: polymorphic-object field + netbox-branching revert bug.
Reverting a merged branch that created a COT with a polymorphic OBJECT field
fails:
ObjectInUse: cannot ALTER TABLE "custom_objects_<id>" because it has pending
trigger events (masked downstream as 'current transaction is aborted')
Cause: remove_polymorphic_object_columns() drops the poly columns with ALTER
TABLE but omits 'SET CONSTRAINTS ALL IMMEDIATE' first (the scalar-field path in
models._schema_remove_field does it). On revert the row delete queues deferred
FK trigger events, so the column drop is rejected.
Run: manage.py polyrevertrepro
"""
import time
import traceback
import uuid
from django.core.management.base import BaseCommand, CommandError
from django.test import RequestFactory
from django.urls import reverse
class Command(BaseCommand):
help = "Minimal repro of the polymorphic-object field branching revert bug."
def handle(self, *a, **o):
from netbox.context_managers import event_tracking
from netbox_branching.choices import BranchStatusChoices
from netbox_branching.models import Branch
from netbox_branching.utilities import activate_branch
from django.contrib.auth import get_user_model
from core.models import ObjectType
from dcim.models import Site
from extras.choices import CustomFieldTypeChoices as F
from netbox_custom_objects.models import CustomObjectType, CustomObjectTypeField
user, _ = get_user_model().objects.get_or_create(
username='polyrepro_user', defaults={'is_superuser': True, 'is_active': True})
site_ot = ObjectType.objects.get(app_label='dcim', model='site')
def req():
r = RequestFactory().get(reverse('home')); r.id = uuid.uuid4(); r.user = user
return r
Branch.objects.filter(name='polyrepro').delete()
CustomObjectType.objects.filter(slug__startswith='polyrepro-').delete()
with event_tracking(req()):
site, _ = Site.objects.get_or_create(slug='polyrepro-site', defaults={'name': 'PolyRepro Site'})
# 1. create + activate a branch
branch = Branch(name='polyrepro'); branch.save(provision=False); branch.provision(user=user)
deadline = time.time() + 180
while time.time() < deadline:
branch.refresh_from_db()
if branch.status == BranchStatusChoices.READY:
break
time.sleep(0.5)
if branch.status != BranchStatusChoices.READY:
raise CommandError(f'branch not ready: {branch.status}')
self.stdout.write(f'branch ready: {branch.schema_name}')
# 2. inside the branch: COT with a polymorphic OBJECT field + a MULTIOBJECT
# field (the multiobject through's deferred FK is what queues the
# pending trigger events when the row is deleted on revert).
with activate_branch(branch), event_tracking(req()):
cot = CustomObjectType.objects.create(name='polyrepro', slug='polyrepro-a')
CustomObjectTypeField.objects.create(custom_object_type=cot, name='name', type=F.TYPE_TEXT, primary=True)
ref = CustomObjectTypeField.objects.create(
custom_object_type=cot, name='ref', type=F.TYPE_OBJECT, is_polymorphic=True)
ref.related_object_types.set([site_ot])
CustomObjectTypeField.objects.create(
custom_object_type=cot, name='sites', type=F.TYPE_MULTIOBJECT, related_object_type=site_ot)
model = cot.get_model()
row = model.objects.create(name='row1', ref=site)
row.sites.set([site])
self.stdout.write('created COT (poly object + multiobject) + 1 row in branch')
# 3. merge (succeeds)
branch.merge(user=user, commit=True)
self.stdout.write('merged OK')
# 4. revert (fails with pending-trigger-events)
try:
branch.revert(user=user, commit=True)
self.stdout.write('revert OK (bug fixed?)')
except Exception as exc:
self.stdout.write(f'revert FAILED: {type(exc).__name__}: {str(exc).splitlines()[0]}')
traceback.print_exc(file=self.stdout)
try:
branch.refresh_from_db(); branch.delete()
except Exception:
pass
CustomObjectType.objects.filter(slug__startswith='polyrepro-').delete()
Site.objects.filter(slug='polyrepro-site').delete()
Revert fails with error.
Plugin Version
v0.6.0
NetBox Version
v4.5
Python Version
v3.13
Steps to Reproduce
ObjectInUse: cannot ALTER TABLE "custom_objects_" because it has pending trigger events
script to repro:
Expected Behavior
Revert should succeed
Observed Behavior
Revert fails with error.