Skip to content

Fixes: #558 - Avoid "<type> None" in CustomObject.__str__ post-delete#528

Open
Kani999 wants to merge 1 commit into
netboxlabs:mainfrom
Kani999:feature/customobject-str-fallback
Open

Fixes: #558 - Avoid "<type> None" in CustomObject.__str__ post-delete#528
Kani999 wants to merge 1 commit into
netboxlabs:mainfrom
Kani999:feature/customobject-str-fallback

Conversation

@Kani999
Copy link
Copy Markdown
Contributor

@Kani999 Kani999 commented May 26, 2026

Fixes: #558

Summary

CustomObject.__str__ includes self.id in its fallback output when no field on the COT is marked primary=True. But Django's Model.delete() sets self.pk to None after the database row is removed — and NetBox's delete-toast and changelog renderers call str() on the post-delete instance. The result is a literal "None" in the user-facing message:

Deleted SmokeTabTarget SmokeTabTarget None

Fix

Branch on self.pk inside the no-primary-field path:

  • self.pk is None (post-delete) → return just the COT's display name ("SmokeTabTarget").
  • self.pk is set (normal rendering) → behave as before ("SmokeTabTarget 5").

8 lines, no behavioural change for any code path with a live pk.

Verification

Observed while smoke-testing PR #482's related-tabs work.

Context

Surfaced during the related-object-tabs smoke testing (PR #482); independent of that work and easy enough to land standalone.

Django's Model.delete() sets self.pk to None after the DB row is gone.
NetBox's delete-toast and changelog renderers call str() on the
post-delete instance, so when the COT has no primary=True field the
previous output included a literal "None":

  Deleted SmokeTabTarget SmokeTabTarget None

Branch on self.pk: when None, return just the COT's display name
("SmokeTabTarget"); when set, behave as before ("SmokeTabTarget 5").

Observed during the all-in-one smoke run on krupa.vm.cesnet.cz on
2026-05-26 while validating PR netboxlabs#482's related-tabs work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@bctiemann
Copy link
Copy Markdown
Contributor

bctiemann commented May 26, 2026

Hi @Kani999 , as with the other PR just now, please don't open a PR unless there is an accepted bug to link it to (this is for later bookkeeping/forensics). Please open a bug issue with repro steps and any relevant details according to the template, and then once it's triaged and accepted we can reopen this PR. Thanks!

@bctiemann bctiemann closed this May 26, 2026
@bctiemann bctiemann reopened this Jun 5, 2026
@bctiemann bctiemann changed the title Avoid "<type> None" in CustomObject.__str__ post-delete Fixes: #558 - Avoid "<type> None" in CustomObject.__str__ post-delete Jun 5, 2026
@bctiemann
Copy link
Copy Markdown
Contributor

Two small suggestions:

1. Trim the comment (project convention is one short line max):

if self.pk is None:  # post-delete: pk is cleared by Django's Model.delete()
    return str(self.custom_object_type.display_name)

2. Regression test in PrimaryFieldChangeTestCase in tests/test_field_types.py:

def test_str_post_delete_no_primary_field_does_not_contain_none(self):
    """Regression #558: str() on a post-delete instance (pk=None, no primary
    field value) must return the COT display name, not '<type> None'."""
    cot = self.create_custom_object_type(
        name="DeleteStrTest", slug="delete-str-test",
        verbose_name_plural="Delete Str Tests",
    )
    # No primary field — falls through to the pk fallback path in __str__.
    model = cot.get_model()
    instance = model.objects.create()

    instance.delete()  # sets instance.pk = None

    result = str(instance)
    self.assertNotIn("None", result)
    self.assertEqual(result, cot.display_name)

Both verified locally — test passes with the fix and fails on the unpatched code.

Copy link
Copy Markdown
Contributor

@bctiemann bctiemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CustomObject.__str__ renders "<type> None" after deletion when the type has no primary field

2 participants