Skip to content

Closes #645: Omit hidden fields from custom object create/edit and bulk edit forms - #663

Merged
pheus merged 4 commits into
mainfrom
645-hidden-field-omitted-from-form
Aug 18, 2026
Merged

Closes #645: Omit hidden fields from custom object create/edit and bulk edit forms#663
pheus merged 4 commits into
mainfrom
645-hidden-field-omitted-from-form

Conversation

@bctiemann

@bctiemann bctiemann commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Closes: #645

Summary

  • A CustomObjectTypeField with ui_editable set to "Hidden" was rendered as an (unlabeled-looking, but present) input on the object create/edit form and the bulk edit form, instead of being omitted entirely.
  • Root cause: get_annotated_form_field() only ever sets disabled = True for any ui_editable value other than "Yes" -- it doesn't distinguish "Hidden" (should not be displayed at all) from "No"/read-only (should be displayed, but disabled). The templates render every field listed in custom_object_type_field_groups unconditionally, without checking disabled, so both CustomObjectEditView.get_form() and CustomObjectBulkEditView.get_form() ended up including hidden fields as still-visible inputs.
  • This mirrors core NetBox's own convention (extras.forms.mixins.NetBoxModelMixin._get_custom_fields), which filters out ui_editable=HIDDEN custom fields before building the form, while leaving NO (read-only) fields in place as disabled.

Changes

  • Added _hidden_field_raw_columns() helper in views.py: given a queryset of fields, returns the backing model column name(s) for any field whose ui_editable is HIDDEN (handling coordinates' two-column and polymorphic-object raw-column cases).
  • Both CustomObjectEditView.get_form() and CustomObjectBulkEditView.get_form() now:
    • Add those column(s) to Meta.exclude, so the ModelForm metaclass doesn't auto-generate a plain (non-disabled) field for them from fields="__all__".
    • Skip hidden fields entirely in the per-field loop that builds attrs, so they're never added to custom_object_type_fields/_field_groups/_rendered_names.
  • CSV bulk import already excluded hidden fields entirely (fixed under Bulk Import Ignores Hidden Editable Fields and Returns Misleading Validation Errors #626); this brings the interactive forms in line with the same behavior.

Testing

  • Added test_edit_form_omits_hidden_field and test_bulk_edit_form_omits_hidden_field in test_views.py, asserting a hidden field's name is absent from form.base_fields.
  • Verified manually against netbox-community/netbox main (Django 6.0.7) and a feature-branch commit with Django 6.1 pinned.

…dit forms

A field with ui_editable=hidden was only disabled, not excluded, on the
CustomObjectEditView and CustomObjectBulkEditView forms. The templates
render every field in custom_object_type_field_groups unconditionally
without checking disabled state, so a hidden field still rendered as an
input -- matching core NetBox's convention where ui_editable=HIDDEN means
a field is excluded from the form entirely (ui_editable=NO/read-only is
still shown, just disabled).

Exclude hidden fields' backing column(s) via Meta.exclude and skip them
in both form-building loops, mirroring the existing CSV bulk-import
handling (#626).

Closes: #645
@bctiemann
bctiemann force-pushed the 645-hidden-field-omitted-from-form branch from b1de02f to 7cbefb3 Compare August 17, 2026 20:30
… drop dead branch

- get_form() on both views now fetches custom_object_type.fields once and
  reuses that list for poly-object exclusion, hidden-field exclusion, and
  the per-field render loop, instead of issuing 3-4 separate queries.
- _hidden_field_raw_columns() now calls CoordinatesFieldType.latitude/
  longitude_field_name() instead of duplicating that naming convention.
- Removed the polymorphic-object branch in _hidden_field_raw_columns():
  poly_obj_raw_exclude already excludes those columns unconditionally at
  both call sites, so it never contributed anything.
@bctiemann

Copy link
Copy Markdown
Contributor Author

@claude /review and keep response limited to actionable issues.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

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


Code Review

  • Read diff and relevant files
  • Analyze changes
  • Post review feedback

The approach is sound and mirrors core NetBox's pattern correctly. Two actionable issues:

1. Hidden coordinates field is untested

_hidden_field_raw_columns has a special branch for TYPE_COORDINATES that produces two column names (latitude_field_name / longitude_field_name) instead of one. This path has no test. A hidden coordinates field that's silently included in Meta.exclude with the wrong column name would either error or silently fail to exclude, and the existing tests wouldn't catch it.

Fix this →

2. Tests don't assert the complementary case

Both tests verify that 'hidden' is absent from form.base_fields, but neither asserts that a ui_editable='no' (read-only/disabled) field is still present. Without this, the tests wouldn't catch a regression where get_form() accidentally drops read-only fields too (e.g., if the HIDDEN check were widened to all non-YES values).

Adding self.assertIn('name', view.form.base_fields) (or a dedicated read-only field) to each test would pin the boundary.

Fix this →


Otherwise the implementation is clean: the query deduplication (cot_fields list reuse), the is_polymorphic guard in _hidden_field_raw_columns (since polymorphic raw columns are already in poly_obj_raw_exclude), and the dual exclusion strategy (Meta.exclude + skipping the field-iteration loop) are all correct.

@bctiemann

Copy link
Copy Markdown
Contributor Author

@claude just curious, do you have access to the session context from the review previously requested?

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

Claude finished @bctiemann's task in 19s —— View job


Answer

No — each @claude trigger starts a fresh instance with no memory of previous sessions. I don't retain internal reasoning, intermediate steps, or any state from the earlier review invocation.

What I can see is the output that was posted to this PR's comment thread (the previous review results are included in the context I receive as <comments>), so I can read what was written and reason about it — but that's just reading the comment text, not replaying the prior session.

In short: the artifact (the posted review) is visible to me; the session that produced it is not.

@bctiemann
bctiemann requested review from a team and pheus and removed request for a team August 17, 2026 22:01
- test_edit_form_omits_hidden_field / test_bulk_edit_form_omits_hidden_field
  now also assert a ui_editable=no (read-only) field stays in base_fields,
  pinning the distinction from HIDDEN that this fix depends on.
- Added dedicated tests for a hidden coordinates field, which excludes two
  backing columns (latitude/longitude) instead of one -- the one branch of
  _hidden_field_raw_columns() the existing tests didn't exercise.
@bctiemann

Copy link
Copy Markdown
Contributor Author

Added test coverage.

@pheus pheus 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.

Thanks!

I found one blocking issue with hidden non-polymorphic multi-object fields: skipping them here also bypasses the view-level initialization and persistence path, so their configured defaults are no longer applied when creating an object. This appears to reintroduce the behavior fixed in #42.

Once that path is preserved and covered by a save-level regression test, the rest looks good to me.

Comment thread netbox_custom_objects/views.py Outdated
Comment on lines +800 to +802
# Hidden fields are omitted entirely, not just disabled.
if field.ui_editable == CustomFieldUIEditableChoices.HIDDEN:
continue

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.

Could we preserve the configured default for hidden multiobject fields here?

Skipping the field at this point also removes it from custom_object_type_fields, which is later used to initialize and save non-polymorphic multi-object relations. As these fields do not have a model-level default, creating an object with a hidden field would silently omit its configured default, reintroducing the behavior fixed in #42.

I think we need to keep the field out of the form while still applying its default during creation. It would also be helpful to cover this with a save-level regression test. Existing relations should remain unchanged when editing an object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this; fixed and covered with a new test.

@pheus pheus changed the title Omit hidden fields from custom object create/edit and bulk edit forms Closes #645: Omit hidden fields from custom object create/edit and bulk edit forms Aug 18, 2026
…ations

A hidden non-polymorphic MultiObject field is a real M2M model attribute,
not a rendering-only concept. Skipping it entirely from
custom_object_type_fields (as the earlier fix did) meant custom_init's
default-population and custom_save's M2M-persistence logic never ran for
it -- reintroducing #42's bug (configured defaults not applied) for any
hidden MultiObject field, and additionally risking clearing an existing
relation on edit since cleaned_data has no key for an unrendered field.

- Track hidden non-polymorphic MultiObject fields in
  custom_object_type_fields for bookkeeping only (never rendered; nothing
  reads that dict in a template).
- custom_init stashes the resolved default IDs on self, since a hidden
  field has no form field to carry kwargs['initial'] into cleaned_data.
- custom_save applies that resolved default on create, and leaves the
  relation untouched on edit -- consistent with a hidden field being
  neither displayed nor editable.
- Added regression tests for both the create-with-default and
  edit-preserves-existing-relation cases.

Thanks to @pheus for catching this in review.
@bctiemann
bctiemann requested a review from pheus August 18, 2026 20:32

@pheus pheus 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.

Thanks for addressing this.

@pheus
pheus merged commit 06acdd4 into main Aug 18, 2026
15 checks passed
@pheus
pheus deleted the 645-hidden-field-omitted-from-form branch August 18, 2026 21:05
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.

Field UI editable value of "Hidden" not honored

2 participants