- 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).
- batch_delete_jobs now writes through the same DB alias it reads from. In JobsMixin.delete
the read queryset is bound to the instance's DB while Job.objects would use the router
default; if those diverged on a multi-DB setup the deleted rows never left the read side
and the batch loop never terminated.
- JobsMixin.delete and ScriptModule.delete honor a caller-supplied `using`, matching
DeleteMixin.delete, instead of always recomputing it.
- Raise JOB_DELETE_BATCH_SIZE from 100 to 1000 and correct its rationale. With only('pk')
the per-batch payload is gone, so the size now bounds per-cycle work rather than memory;
1000 matches EXPORT_CHUNK_SIZE and was the fastest of 100/1000/5000 when benchmarked
against a 200k-job deletion.
- Document that JobsMixin must precede DeleteMixin in the MRO or the batching is bypassed,
and scope the ScriptModule.delete comment so it doesn't imply the on-disk file removal is
transactional.
- Add a module-path rollback test alongside the existing script-path one.
Fixes form check input border contrast in dark mode by using solid grey
instead of translucent border. Updates checked checkbox glyph color to
rich black for better visibility against teal primary background.
Fixes#22879
Prevent out-of-order dynamic select responses from restoring options for an
earlier dependency state.
Track the latest load request, preserve valid selections across superseding
loads, and properly finalize stale requests and their loading state. Clear any
pending restored value when a request cannot be made or fails.
humanize_duration() is a general-purpose helper, newly exposed as a template
filter, so clamping negatives inside it made every present and future caller
suppress the exact symptom of clock skew. It now renders a negative duration
with a leading minus sign, which also fixes the nonsensical output the divmod
decomposition previously produced for one (e.g. "-1d 23h 59m 55s").
The floor moves to Job.elapsed_time, which is the value NetBox displays and
covers the list, the detail panel, the script result view and runscript in one
place. The stored execution_time is untouched, so the API and exports still
surface the anomaly.
Also renames the sub-second branch's variable, which held a value in seconds
rather than milliseconds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The jobs list sorts by the displayed value, so a running job orders by how
long it has been going, while execution_time__gte/__lte match only the
recorded column — a long-running job can therefore top a descending sort yet
be excluded by a filter on the same attribute.
Keeping the filters on the stored column is deliberate: the filterset is
shared with the REST API, where matching against a live, clock-dependent
value would make results non-reproducible. Document the distinction, along
with the export's use of the recorded value, rather than reconciling them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Job.duration has been public since 3.4 and is reachable from user-authored
export templates as well as plugins, so removing it outright was a silent
breaking change. Restore the original implementation verbatim — including the
fallback to `created` when a job never started, and the preformatted string —
so existing templates keep working, and warn on access. Planned for removal
in v5.0, matching the rack legacy fields.
Note that elapsed_time deliberately does not reproduce the `created`
fallback: measuring from creation conflates queue wait time with execution
time, which is what the new field is meant to record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
JobTable defines both render_execution_time() and value_execution_time(), so
django-tables2 never invoked DurationColumn for that column and the new
timedelta branch was unreachable and untested. Restore the column to its
minutes-only form and use a plain Column, which is what the table was
effectively getting anyway.
The export path also passed through the render path's clamping, so an
anomalous negative execution_time was normalized to zero in the one output
intended for analysis, and a running job's provisional elapsed time was
indistinguishable from a completed job's final value. Export the recorded
value verbatim and leave the still-running distinction to the UI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renaming the attribute to elapsed_time changed its auto-derived label to
"Elapsed time", disagreeing with the list column, the filter form, the API
field and the model docs. The derived label is also built at runtime before
being passed to gettext, so it would never have been extracted into the
message catalog. An explicit label addresses both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The expression coalesced to Now() - started with no regard for whether the
job had finished, so a row with both started and completed set but a null
execution_time resolved to an ever-growing interval, while the elapsed_time
property returned None for the same row. Sorting the jobs table descending
by execution time therefore ranked those rows above every real value.
Gate the live branch on completed__isnull=True so the expression agrees with
the property.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Batching the backfill bounded statement size but not lock duration: sharing
a transaction with the AddField meant the ACCESS EXCLUSIVE lock from ALTER
TABLE was held for the whole run, which is exactly the case the batching was
meant to help. 0025 goes back to adding the column only, and the backfill
moves to 0026 with atomic = False so the lock is released first.
The backfill now also skips rows which already have a value, making it
idempotent and letting an interrupted run simply be resumed. As a separate
migration it additionally reaches installations which had already applied
0025, rather than silently leaving their historical jobs unpopulated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
started__* and completed__* are two halves of the same time range, so
splitting them across the Scheduling and Execution field sets made a run
window awkward to filter. Execution now holds only execution_time, and the
grouping matches JobSchedulingPanel on the detail view.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Normalize RQ timeout values before validating global and per-webhook
timeouts, including duration strings and RQ's default and unlimited values.
Improve timeout logging and visibility in the UI and documentation, raise
the default webhook timeout to 60 seconds, and add coverage for the new
validation and filtering behavior.