Skip to content

Add related_name field to CustomObjectTypeField to handle reverse accessors on object/multiobject fields#446

Open
bctiemann wants to merge 4 commits intomainfrom
385-related_name-accessor-for-object-cot-fields
Open

Add related_name field to CustomObjectTypeField to handle reverse accessors on object/multiobject fields#446
bctiemann wants to merge 4 commits intomainfrom
385-related_name-accessor-for-object-cot-fields

Conversation

@bctiemann
Copy link
Copy Markdown
Contributor

@bctiemann bctiemann commented Apr 5, 2026

Fixes: #385

Adds the ability for objects targeted in object/multiobject fields to fetch the related Custom Objects via a reverse accessor.

The solution for this is reasonably simple: adding a related_name field to the CustomObjectTypeField model and then not disabling reverse query traversal if that field is set.

Note: This PR has a 0005 migration which will conflict with #442. Since that PR is more complex and needs more work, we can merge this one first and then renumber the migrations in #442.

@bctiemann
Copy link
Copy Markdown
Contributor Author

@claude /review

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

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


PR Review: Add related_name to CustomObjectTypeField

  • Gather context and read changed files
  • Analyze the diff
  • Provide review feedback

Overview

This PR adds a user-configurable related_name field to CustomObjectTypeField, enabling reverse accessors from target objects (e.g. slb.certificates.all()) on both Object (FK) and MultiObject (M2M) fields. The implementation is clean and minimal. The core logic is a simple conditional: use the user-supplied name if set, otherwise fall back to the existing behavior (auto-generated accessor for FK fields, disabled reverse for M2M).


Issues Found

1. Migration validator doesn't match model — flags=re.RegexFlag['IGNORECASE'] and stale message

migrations/0005_...py, lines 23–27

The auto-generated migration records the related_name validator with flags=re.RegexFlag['IGNORECASE'] and the message "Only alphanumeric characters and underscores are allowed.", while the model has neither the flag nor that message (it says "Only lowercase alphanumeric characters and underscores are allowed.").

This suggests the migration was generated before the model was finalized. While Django doesn't enforce validators at the DB level, this is inconsistent and could confuse future developers reading the migration history.

Recommendation: Regenerate the migration after finalizing the model field definition. Fix this →

2. related_name not in form fieldsets — field may not appear in UI

forms.py, lines 149–172

CustomObjectTypeFieldForm has explicitly defined fieldsets but related_name is not in either of them. With NetBox's form rendering, fields not listed in any FieldSet may not be visible in the UI edit form.

Note that related_object_type and related_object_filter are also not in the fieldsets and presumably work already (possibly rendered via the parent CustomFieldForm). It's worth verifying that related_name actually appears in the web UI, and if not, adding it to the appropriate fieldset (e.g. alongside related_object_type). Fix this →

3. No __ restriction on related_name

models.py, line 877

The name field forbids double underscores (__) via an explicit validator because they conflict with Django's ORM lookup separator. The related_name field should have the same restriction — a value like has__double passes the current regex ^[a-z0-9_]+$ and while it won't break Python attribute access, it will produce confusing ORM query syntax (e.g. filtering across has__double would be ambiguous). This is low-severity but inconsistent with the treatment of name. Fix this →


Minor Observations

  • clone_fields = ("custom_object_type",) on CustomObjectTypeField correctly excludes related_name from cloning, avoiding uniqueness constraint violations on clone. Good.
  • Asymmetric default behavior (FK fields auto-generate an accessor; M2M fields default to disabled) is noted in the help text and is a reasonable design choice.
  • related_query_name=m2m_related_name in field_types.py:777 — when a related_name is set, related_query_name is set to the same string. This means reverse filtering (Certificate.objects.filter(slbs=slb)) will also use that name. This is correct and expected behavior.
  • The duplicate uniqueness check (both UniqueConstraint in Meta.constraints and manual clean()) is standard Django practice — the clean() gives user-friendly errors, the constraint is the DB safety net.

Test Coverage

The new RelatedNameTestCase (240 lines, 10 test methods) is comprehensive and well-structured:

  • Validation rejection tests for non-object fields, bad characters, and duplicate names
  • Positive cases: different targets, blank names
  • Functional round-trip tests for both FK and M2M reverse accessors

One gap: no test for cross-COT FK fields (where both source and target are custom object types, not built-in NetBox models). The code path for cross-COT fields falls through to the same related_name logic, so it should work, but explicit test coverage would be useful.


Summary

The implementation is correct and well-tested. There are two things worth addressing before merge:

  1. Regenerate the migration to fix the validator mismatch (cosmetic but worth fixing for correctness)
  2. Verify the field appears in the web UI — check if related_name needs to be added to the fieldsets in forms.py

The double-underscore restriction is a lower-priority consistency improvement.

…ject-cot-fields' into 385-related_name-accessor-for-object-cot-fields

# Conflicts:
#	netbox_custom_objects/migrations/0005_customobjecttypefield_related_name_and_more.py
#	netbox_custom_objects/models.py
@bctiemann
Copy link
Copy Markdown
Contributor Author

All review issues have been addressed.

@bctiemann bctiemann requested a review from arthanson April 5, 2026 02:45
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.

Add reverse relation access for Object and MultiObject fields

1 participant