Recognize Django's ManyToManyRel in get_prefetches_for_serializer().
Because it is a sibling of ManyToOneRel rather than a subclass, reverse
many-to-many accessors were omitted from the generated prefetch paths and
fetched once per serialized object.
Add regression coverage for both automatically generated fields and
SerializedPKRelatedField(many=True), and regenerate the affected ASN and
ObjectPermission API query-count baselines.
Fixes#23060
Introduce `normalize_update_fields()` utility to convert update_fields
to frozenset, preventing one-shot iterables from being consumed during
membership tests. Update Service, VLANGroup, and CircuitTermination
save methods to use normalized fields. Add comprehensive test coverage.
Fixes generator exhaustion when save() overrides check field membership
before persisting denormalized caches alongside their source fields.
Fixes#23078
Move scope assignment before validation to prevent stale scope values
on instances when validation fails. Refactor VLANGroupForm to inherit
from ScopedForm, removing duplicate scope handling code. Add test
coverage for scope type changes and validation errors.
Fixes#23040
Serializers used only in a nested context have no complete form in the schema, so
prefixing them with "Brief" renamed an existing component to no purpose and dropped
the old name entirely. Exempt serializers declaring an explicit Meta.ref_name from
the prefix, and pin the three affected names.
This narrows the schema diff to the fields the bug actually affected: no components
are removed, and the nine which are added are purely additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Teach _get_nested_serializer() to unwrap ManyRelatedField and resolve the
serializer configured on SerializedPKRelatedField. Preserve the field's
nested value so prefetch discovery follows brief_fields for nested
representations and the full field set otherwise.
Guard the recursion against reference cycles by tracking the serializer
states already being resolved on the current path. Because nested defaults
to False the target expands its full field set, so a self-referential or
mutually referential declaration would otherwise recurse without a
termination condition. The key pairs the serializer class with its
effective field set, since re-entering a serializer at a narrower depth is
finite rather than cyclic, and the states are copied per frame so a sibling
field can still expand what another one stops at.
Populate interface VDC relationships and VRF route target assignments in
the API test fixtures, update the two query count baselines, and add
coverage for direct, many-valued and non-nested serialized related fields
alongside self-referential, mutually referential, brief-depth and
sibling-reuse cases.
Fixes#22988
Move owner field from fieldsets to Meta.fields in PowerOutletForm and
add it to ServiceCreateForm Meta.fields. Ensures owner field appears in
forms and can be properly saved. Adds test coverage with Owner creation.
Fixes#23052
Replace NumberFilter with MultiValueNumberFilter for VLANGroup scope
filters (Region, Site Group, Site, Location, Rack Group, Rack,
Cluster Group, Cluster). Update filter_scope method to use `__in`
lookup and add comprehensive test coverage for multi-value filtering.
Fixes#22671
The REST API's write paths are transactional, but the in-memory events queue
is not: change logging receivers queue events eagerly (for deletions in
pre_delete, before the row is removed), and event_tracking() flushes that queue
only after the response has been rendered. Where a rolled-back write is caught
and converted into a normal response — the ProtectedError/RestrictedError and
AbortRequest handlers in NetBoxModelViewSet.dispatch(), and the
ObjectDoesNotExist to PermissionDenied conversions in the perform_*() methods —
the flush therefore dispatched events for objects that were never created,
updated, or deleted.
Backport discard_events_on_rollback() from the feature branch and enter it
inside the transaction guarding each API write: perform_create(),
perform_update(), and perform_destroy(); the SequentialBulkCreatesMixin,
BulkUpdateModelMixin, and BulkDestroyModelMixin bulk actions; and
AvailableObjectsView.post(). The context manager sends clear_events on the way
out whenever the wrapped transaction is rolled back, whether by an exception
escaping the block or by an explicit set_rollback(), which also covers the
exceptions that reach dispatch(). This mirrors what the UI views now do (#22934).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes clone_fields declarations from ContactAssignment,
ImageAttachment, and FHRPGroupAssignment models that don't inherit
CloningMixin.
Adds test coverage to prevent clone_fields on models without cloning
support.
Fixes#22987
port_mappings_list rendered one token per port, so a service exposing a
large contiguous range (e.g. tcp/8000-8100) filled the list column and
detail panel with hundreds of tokens. Collapse consecutive ports within a
protocol into a range for display, matching the compact form the
pre-multi-protocol Service model rendered via array_to_string().
The port-mappings CSV column expanded only comma-separated individual
protocol/port pairs, while the edit form's port field already accepted
hyphen ranges (e.g. tcp/8000-8010). Route the CSV column through the same
expand_port_mapping() helper so both entry paths accept identical port
syntax. Parsing uses the shared split_port_mapping() helper, and the
blank-protocol error is worded to fit every entry path rather than only the
form widget's dropdown.
- Make the cross-content-type test select a Site pk that is not also a
valid Region pk, rather than asserting the precondition (the two tables'
sequences are independent and not rolled back between test classes, so
the assertion could turn a pk collision into a spurious failure).
- Add a bulk-edit nullification test: clearing a scope via _nullify must
null both concrete columns and must not raise the incomplete-scope
validation error. This exercises the GenericForeignKey branch in
BulkEditView._update_objects(), previously uncovered.
- Strengthen the malformed-input test: use an object ID which overflows
PositiveBigIntegerField (the value that actually reaches the database)
instead of an in-range pk, and assert the rejection lands on the scope
field.
Add view-layer test coverage for the GenericObjectChoiceField scope
handling introduced by #22537, covering behaviors reachable only through
a real request:
- A bulk edit which sets a scope persists the generic foreign key to
every selected object (previously untested).
- A constrained ObjectPermission narrows the scope object selector: a
user cannot assign a scope object they may not view, while a permitted
object still validates. This replaces a test which simulated the
restriction by assigning the field queryset directly.
- An object ID belonging to a content type other than the selected one
is rejected rather than silently accepted.
- Malformed scope input (non-integer or out-of-range identifiers) is
rejected as invalid rather than raising a server error.
Resolves all conflicts between main and feature for #22896. Notable
resolutions:
- dcim/signals.py, dcim/tests/test_signals.py: main's cache_presave_scope_fields
/ sync_cached_scope_fields addition is fully superseded by feature's
PostgreSQL-trigger-based denormalization (confirmed via feature's own
migration docstrings); dropped in favor of feature's existing approach.
Both files now match feature's originals exactly.
- netbox/tables/columns.py: combined main's generic get_ordering_annotation()
protocol with feature's nulls_first-aware order() override. These two
mechanisms cannot both apply to the same column (django-tables2 negates an
entire order_by tuple uniformly on direction toggle, so a fixed nulls_first
placement and multi-column sort composition are mutually exclusive for one
column) -- preserved nulls_first (existing, wired through forms/API/GraphQL)
and removed main's two composition-only tests for CustomFieldColumn. See the
comment on CustomFieldColumn.order() for full reasoning.
- extras/customfields.py, extras/graphql/mixins.py: combined main's
request-cache optimization and has_key-scoped batch updates with feature's
resolve_selection_value() (shared select-field label resolution between
REST and GraphQL).
- extras/events.py, extras/event_rules.py: main's "Honor Script defaults when
triggered by Event Rules" (#22852) fix was written against the old inline
action-type dispatch, which feature had already replaced with a pluggable
action-provider registry (#22770). Re-applied the same two-line fix
(notifications/job_timeout) inside ScriptAction.enqueue() in event_rules.py
instead.
- utilities/jinja2.py: fixed a config-attribute name mismatch the raw merge
would have introduced (main's JINJA2_FILTERS vs feature's renamed
JINJA_FILTERS) by updating the shared _jinja2_filters() helper.
- ipam/migrations/: renumbered main's 0094_ipaddress_host_index to 0096 and
added a merge migration, since main and feature had each independently
added a migration numbered 0094.
- dcim/tests/query_counts.json: regenerated via UPDATE_QUERY_COUNTS=1 against
the merged codebase rather than hand-merging counts.
Verified: manage.py check clean, full migration graph applies cleanly from
scratch, ruff clean, and full test suites pass for dcim, ipam, netbox, extras,
circuits, vpn, wireless, tenancy, virtualization, core, users, and account
(fresh databases, no state carried over between runs).
* Fixes#22161: Rename filterset test mixin base classes to *TestMixin
Completes the test-class naming standardization begun in #22097, which
renamed concrete test classes to the *TestCase suffix but deliberately
left four filterset test mixin base classes untouched because renaming
them is breaking for plugins that inherit from them.
These four are pure mixins, not concrete test cases, so they follow
NetBox's existing mixin naming convention (RQQueueTestMixin,
ComponentTraceMixin) rather than the *TestCase suffix the issue
originally proposed. The literal *TestCase names also collide with two
existing concrete classes (BaseFilterSetTestCase in
utilities/tests/test_filters.py and ChangeLoggedFilterSetTestCase in
extras/tests/test_filtersets.py).
BaseFilterSetTests -> BaseFilterSetTestMixin
ChangeLoggedFilterSetTests -> ChangeLoggedFilterSetTestMixin
DeviceComponentFilterSetTests -> DeviceComponentFilterSetTestMixin
DeviceComponentTemplateFilterSetTests -> DeviceComponentTemplateFilterSetTestMixin
This is a breaking change for plugins whose test suites import the two
exported mixins from utilities.testing; they must update their imports.
* Fixes#22161: Update add-model skill for renamed test mixin
The add-model skill still referenced ChangeLoggedFilterSetTests in its
example filterset test. Update it to ChangeLoggedFilterSetTestMixin.
Include comments field with weight 5000 in search indexes for
DeviceRole, L2VPN, MACAddress, and RouteTarget models to enable
full-text search on comment content.
Fixes#22767
Add missing keyword argument to format() calls in validation error
messages. This ensures the invalid data value is properly included in
the error message returned to the user.
Fixes#22719
CachedScopeMixin._region and ._site_group may cache ancestors of a
Site or Location scope. Change these relationships to SET_NULL so
deleting a Region or SiteGroup clears the cached value instead of
deleting the scoped Prefix, Cluster, or WirelessLAN.
Add reverse GenericRelation fields for Cluster and WirelessLAN on
Region and SiteGroup. This preserves the expected cascade when a
Region or SiteGroup is itself the direct scope, matching the existing
Prefix behavior.
Add migrations recording the ORM-level on_delete changes and regression
coverage for Site, Location, and direct Region/SiteGroup scopes.