Skip to content

Closes #655: Surface context fields as attributes in global search results - #684

Open
bctiemann wants to merge 2 commits into
mainfrom
655-search-context-attrs
Open

Closes #655: Surface context fields as attributes in global search results#684
bctiemann wants to merge 2 commits into
mainfrom
655-search-context-attrs

Conversation

@bctiemann

@bctiemann bctiemann commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Closes: #655

Summary

Global search results for custom objects only ever showed a bare link to the object — none of its "context" fields appeared in the Attributes column the way core NetBox models do. This wires CustomObjectType's dynamically generated SearchIndex to surface context=True fields there, via NetBox core's existing SearchIndex.display_attrs mechanism.

Credit

This builds directly on #656 by @biwhite, who filed #655 and correctly identified display_attrs as the right hook. His PR is out of sync with main and failing CI as a result — not because of any problem with his approach (see the comment thread on #655) — and I don't have push access to his fork to rebase it directly, so I'm opening this instead.

What changed vs. #656

Reviewing #656 for correctness turned up two issues, both addressed here:

  1. Dead code: insert fields marked as 'context' into the SearchIndex results #656 added a get_display_attrs classmethod to the dynamically generated SearchIndex class, but nothing in NetBox core or this plugin ever calls a method by that name. The actual renderer is CachedValue.display_attrs (a core @property), which only reads the plain display_attrs tuple on the indexer class and does its own getattr() / get_<field>_display() dispatch per entry — the added method was inert.
  2. A real correctness gap: nothing prevents context=True from being set on a multiobject (real M2M) field. CachedValue.display_attrs's getattr()-based renderer has no per-type dispatch — for a real ManyToManyField it returns the RelatedManager instance itself, not its contents, so a multiobject context field would leak a broken object repr into search results instead of the related objects. This PR excludes multiobject context fields from display_attrs to avoid that. Polymorphic and coordinates fields need no equivalent exclusion — they have no real backing column under the field's own name, so the existing present-set guard (already in place to protect stub models generated with skip_object_fields=True) filters them out automatically.

Also consolidated the two separate self.fields queries (one filtered by search_weight, one by context) into a single pass.

Testing

Adds two regression tests to test_models.py:

  • a context=True text field appears in the generated SearchIndex.display_attrs
  • a context=True multiobject field does not

Full plugin suite verified in a clean venv: 1173 tests, 0 failures/errors, 2 skipped.

…sults

Credit to Ben White (@biwhite), who identified this gap and opened #656
with the core insight this builds on: setting SearchIndex.display_attrs
from a COT's context=True fields, so they surface as supplementary
"attributes" on global search results the same way every other NetBox
model's SearchIndex does.

Two issues in the original approach, found while reviewing #656 for
correctness:

- It added a get_display_attrs classmethod to the dynamically generated
  SearchIndex class, but nothing in NetBox core or this plugin ever
  calls a method by that name. The actual renderer is
  CachedValue.display_attrs (netbox core's extras/models/search.py),
  which only reads the plain display_attrs tuple and does its own
  getattr()/get_<field>_display() dispatch per entry -- the added method
  was dead code.
- Nothing prevents context=True from being set on a multiobject (real
  M2M) field. CachedValue.display_attrs's getattr()-based renderer has
  no per-type dispatch: for a real ManyToManyField that returns the
  RelatedManager instance itself, not its contents, so a multiobject
  context field would leak a broken object repr into search results
  instead of the related objects. Excluded multiobject context fields
  from display_attrs to avoid this. Polymorphic and coordinates fields
  need no equivalent exclusion -- they have no real backing column under
  the field's own name, so the existing `present` check (guarding
  against stub models generated with skip_object_fields=True) already
  filters them out.

Also consolidated the two separate self.fields queries (one filtered by
search_weight, one by context) into a single pass.

Ben's own PR (#656) has this exact same root idea but is out of sync
with main and failing CI for that reason, not because of a problem with
his approach; opening this instead since I don't have push access to
his fork to rebase it directly.

Adds two regression tests: a context field appears in display_attrs,
and a multiobject context field does not. Full plugin suite verified in
a clean venv: 1173 tests, 0 failures/errors, 2 skipped.
@bctiemann

Copy link
Copy Markdown
Contributor Author

@claude /review and limit response to actionable issues.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Claude finished @bctiemann's task in 1m 52s —— View job


Code Review

  • Read changed files
  • Analyze diff
  • Post review

Two actionable issues, both in the tests:

1. Positive test has an incomplete assertion (test_models.py:266)

The test verifies "status" in display_attrs but doesn't assert that "name" (the non-context, searchable field) is NOT in display_attrs. A regression that accidentally adds all fields to display_attrs would go undetected.

self.assertIn("status", search_index.display_attrs)
self.assertNotIn("name", search_index.display_attrs)  # add this

Fix this →

2. No test for single-object (TYPE_OBJECT) context field

There's a test that a TYPE_MULTIOBJECT context field is excluded. There's no test confirming that a TYPE_OBJECT (single FK) context field IS included. These are the two sides of the same boundary — one side is tested, the other isn't. Since getattr() on a FK returns the related instance (not a manager), including it is meaningful and correct, but it's currently unverified.

Fix this →


The logic change itself (models.py:1643–1658) is correct — consolidating the two queries into one pass is clean, the TYPE_MULTIOBJECT exclusion is sound, and the present-set guard correctly handles skip_object_fields=True stubs for FK fields.

- Strengthen the positive display_attrs test with assertNotIn("name", ...)
  so a regression that dumps every field into display_attrs (not just
  context=True ones) would be caught.
- Add test_register_search_index_includes_object_context_fields, the
  missing other side of the TYPE_MULTIOBJECT exclusion: a TYPE_OBJECT
  (single FK) context field must still be included, since getattr() on
  it returns the related instance directly, not a manager.
@bctiemann

Copy link
Copy Markdown
Contributor Author

Improved both tests as suggested.

@bctiemann
bctiemann requested review from a team and arthanson and removed request for a team August 27, 2026 20:10
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.

Global search results do not populate 'attributes' fields for Custom Objects

1 participant