Skip to content

Closes #36: Add changelog_enabled toggle to CustomObjectType#574

Open
bctiemann wants to merge 5 commits into
featurefrom
36-toggleable-changelog
Open

Closes #36: Add changelog_enabled toggle to CustomObjectType#574
bctiemann wants to merge 5 commits into
featurefrom
36-toggleable-changelog

Conversation

@bctiemann

@bctiemann bctiemann commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Closes: #36

Summary

Adds a changelog_enabled boolean field (default True) to CustomObjectType. When disabled, changes to objects of that type are silently skipped by NetBox's change-logging signal — no ObjectChange rows are written, and the Changelog tab is hidden from the custom object detail page.

How it works

The implementation is confined to two points:

  1. Model fieldCustomObjectType.changelog_enabled (migration 0015, default True).

  2. Generated model overrideget_model() injects a to_objectchange() method that returns None when the flag is off. NetBox's handle_changed_object signal guards on objectchange and objectchange.has_changes before saving, so None is the documented way to suppress a change record — no signal disconnection, no monkey-patching of the signal itself.

Surface area

  • Form: new Options fieldset with the changelog_enabled toggle
  • Serializer: field included in CustomObjectTypeSerializer
  • Table: field available as an optional column
  • COT detail template: status shown with a checkmark
  • CO detail template: Changelog tab is hidden when changelog_enabled=False; direct URL access to the changelog view returns 404

Notes

  • Branching compatibility: if changelog_enabled=False, objects of that type also cannot participate in branching (branching requires change capture). This is consistent with the issue's statement that "this user would be fine with these changes not being branchable."
  • The per-COT toggle was chosen over a global flag because the use case is inherently per-type (some types warrant an audit trail, others don't).

🤖 Generated with Claude Code

bctiemann and others added 4 commits June 11, 2026 21:01
Adds a BooleanField 'changelog_enabled' (default True) to CustomObjectType.
When disabled, the generated model's to_objectchange() returns None,
causing NetBox's change-logging signal handler to skip writing ObjectChange
rows — exactly what users need for high-frequency, low-audit-importance
updates (e.g. nightly fleet scans across thousands of objects).

Implementation:
- Model field with migration 0015
- to_objectchange() override injected at get_model() time when disabled
- Form: new 'Options' fieldset exposes the toggle in the UI
- Serializer: field included in CustomObjectTypeSerializer
- Table: field available as an optional column
- Detail template: shows changelog status with checkmark

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…disabled

- Template: wraps the Changelog tab link in {% if object.custom_object_type.changelog_enabled %}
  so the tab is absent from the navigation rather than visible-but-empty
- View: raises Http404 when the changelog URL is hit directly for a
  changelog-disabled COT, preventing an empty page via direct URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename lambda params self->_self, action->_action to avoid shadowing
  get_model()'s 'self' (the CustomObjectType instance)
- Add ChangelogEnabledTestCase: verifies to_objectchange() returns None
  directly, that ObjectChange rows are suppressed via the signal, and
  that the default (enabled) path still writes rows
- Add ChangelogEnabledViewTestCase: verifies the changelog URL returns
  404 for a disabled COT, and that the Changelog tab link is absent
  from the object detail template

Migration is unchanged: --check confirms Django correctly omits
verbose_name (matches the auto-generated value) and help_text
(metadata-only, not stored in the schema).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bctiemann bctiemann force-pushed the 36-toggleable-changelog branch from e744d0f to 98426bc Compare June 12, 2026 01:01
@bctiemann

Copy link
Copy Markdown
Contributor Author

@claude /review and limit response to areas of potential concern.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Claude finished @bctiemann's task in 2m 56s —— View job


Review: Areas of Potential Concern

  • Read changed files
  • Analyze for concerns

1. Migration is missing help_text and verbose_name

models.py defines:

changelog_enabled = models.BooleanField(
    verbose_name=_('changelog enabled'),
    default=True,
    help_text=_("If disabled, changes to objects…"),
)

But 0015_customobjecttype_changelog_enabled.py has:

field=models.BooleanField(default=True),

Django's Field.deconstruct() includes help_text when non-empty, and verbose_name when it differs from the auto-generated name. Because of lazy-translation proxies, Django may not recognize _('changelog enabled') as equal to the auto-generated 'changelog enabled'. Running makemigrations after merging this will produce a spurious AlterField migration. Recommend regenerating the migration from the final model state before merging. Fix this →


2. Branching side-effect is undocumented in the UI

The PR description notes that changelog_enabled=False also silently disables branching for objects of that type (branching depends on change capture). But this does not appear anywhere in the product:

  • The form field's help_text doesn't mention it
  • The COT detail template only shows a checkmark, no warning
  • There's no form validation warning when this is checked off

An operator disabling changelog for noise-reduction won't know branching is also disabled until they actually try to use it. Consider adding this side-effect to the help_text string, e.g.: "When disabled, objects of this type cannot participate in branching."


3. No test for mid-lifecycle toggle

All three tests create a COT with changelog_enabled fixed at creation time. The realistic user journey — creating a COT with changelog_enabled=True, writing some objects (so the model is cached), then toggling to False and confirming that subsequent saves don't write ObjectChange rows — is not covered.

The cache_timestamp mechanism should handle this correctly (saving the COT bumps cache_timestamp, which forces get_model() to regenerate on the next call), but this is the primary operational path and is currently untested. Fix this →


4. changelog_enabled absent from CustomObjectTypeFilterSet

filtersets.py:259 only exposes id, name, and group_name for COT filtering. Operators can't query the API (or use the list-view filter form) to find all types where changelog is disabled. Given that this is an operational flag, being able to enumerate types with it off seems useful. This is a minor gap, not a blocker.


Minor / Not Concerns

  • The lambda _self, _action: None override is correct. The underscore-prefixed parameters avoid shadowing the outer self (the COT instance); the previous commit note about "fix lambda shadow" confirms this was already caught.
  • Cache invalidation on toggle is handled correctly: CustomObjectType has cache_timestamp = auto_now, so any save() that changes changelog_enabled will cause the next get_model() call to regenerate the model with the new setting.
  • Raising Http404 (rather than 403) on direct URL access to a disabled changelog URL is a reasonable choice — it avoids leaking whether objects exist. The behavior is called out in the PR description.

… toggle test

- Extend changelog_enabled help_text to note that disabling also prevents
  objects from participating in branching (branching requires change capture)
- Add changelog_enabled to CustomObjectTypeFilterSet so operators can query
  ?changelog_enabled=false via the API or list-view filter
- Add test_mid_lifecycle_toggle_disables_changelog: verifies that toggling
  an existing COT from True→False correctly suppresses ObjectChange rows
  for subsequent saves, exercising the cache_timestamp invalidation path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bctiemann

Copy link
Copy Markdown
Contributor Author

Addressed items 2, 3, and 4. The migration is fine as-is (makemigrations produces an identical file).

@bctiemann bctiemann requested a review from arthanson June 12, 2026 01:43

@arthanson arthanson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is really ugly for branching, I think we might need to say this can only be set on creation and never modified (not sure if that breaks the intent) but if you toggle it mid-way and have items already in a branch I can see all sorts of potential bad side-effects.

Also please add a whole set of tests with branching for this - especially with custom objects having fields linking to and being linked from COT that have object-change disabled and run through the sync/merge/revet lifecycle - they can be added as extra test cases to the co-branching tests. Also make sure polymorphic fields are included as well as those are special case. so object, multi-object and those as polymorphic being in a regular CO linking to a non-object-change CO and the same ones in a non-object-change CO linking to a regular CO.

@bctiemann bctiemann linked an issue Jun 13, 2026 that may be closed by this pull request
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.

Toggleable Changelogging

2 participants