Replace the obsolete Django admin `vLargeTextField` class with Tabler's
`font-monospace` utility for the four banner configuration parameters.
Define the widget styling in the parameter definitions, where the metaclass
constructs the form fields, and remove the ineffective `Meta.widgets`
overrides. Add regression coverage for all six code-oriented configuration
fields, including the two JSON fields that already use a monospace font.
Update the add-config-param skill to recommend `font-monospace` so future
textarea-backed parameters do not reintroduce the obsolete admin class.
Pin `twine` and `packaging` versions in build job to match bundled
versions in `gh-action-pypi-publish` v1.14.2.
Enforce Core Metadata 2.4 in wheel and sdist targets with verification
in validation scripts.
Fixes#22903
During partial bulk updates, fields omitted from the CSV are removed
from the import form before validation. Model validation can still
return an error for one of these fields, causing Django to raise a
ValueError instead of displaying the validation error.
Remap errors for absent fields to prefixed non-field errors on
NetBoxModelImportForm while preserving their codes, parameters, lazy
pluralization, and literal percent values. Genuine non-field errors
remain unchanged.
Add form-level and view-level regression coverage for mixed and
parameterized errors and for the reported interface bulk-update
workflow, including verification that invalid updates leave the object
unchanged.
- 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.
The batched job delete can't fast-delete (a global pre_delete receiver forces
per-instance signals), so each batch still instantiates its Job rows. Load only the PK
via only('pk') so those instances don't pull the large data/log_entries payloads,
cutting the resident set per batch. Also drop a dead `no-toggle` CSS class from the
delete-confirmation template (it is defined nowhere and, under Tabler, has no effect)
and use JobStatusChoices.STATUS_COMPLETED in the tests instead of a string literal.
Django's Collector calls into the jobs GenericRelation branch unconditionally, so
ConfirmCollector recorded a zero count for objects with no jobs. _get_dependent_objects
then added a CountOnly(0), and the delete-confirmation page rendered "The following
objects will be deleted as a result of this action." plus a "0 jobs" row for every
jobless JobsMixin object. Only record a count when there are actually jobs.
Deleting a Script via the UI is only possible by deleting its parent ScriptModule
(no Script delete view exists). That cascades to the child Script rows, and the
collector materialized every one of those Scripts' jobs — the memory blowup, which
scales with jobs-per-script. JobsMixin.delete() only batched the deleted object's own
jobs, and a ScriptModule has none; the jobs live on its child Scripts.
Extract the chunked job-deletion loop from JobsMixin.delete() into a shared
batch_delete_jobs() helper, and add a ScriptModule.delete() override that batch-deletes
its child Scripts' jobs (in a single queryset keyed on the script PKs, no per-script
loop) before delegating to the cascade. This bounds peak memory to one batch regardless
of how many jobs the module's scripts hold.
Scripts triggered by Event Rules now respect notifications_default and
job_timeout from script Meta class. Updates documentation to clarify
this behavior and adds regression test coverage.
Fixes#22852
Deleting a Script (or any JobsMixin object) with thousands of associated jobs could
consume several GB of memory and exhaust the host, because Django's deletion collector
loads every related Job into memory. Jobs can never be fast-deleted (a global pre_delete
receiver forces per-instance signal dispatch), and each Job carries potentially large
data and log_entries payloads.
Two paths loaded the full job set independently, so both are addressed:
- The delete cascade: JobsMixin.delete() now deletes the object's jobs in batches before
delegating to super().delete(), wrapped in a transaction so a failure in the parent
delete rolls the job deletions back. After the loop the cascade collector finds no jobs
to materialize.
- The delete-confirmation page: _get_dependent_objects() uses a ConfirmCollector that
counts the jobs relation rather than descending into it, so the page never instantiates
the jobs. Counted relations render as a non-expandable row (via a CountOnly stand-in)
alongside the itemized dependents.
CablePath.save() and delete() wrote the _path back-reference onto the path's
origin object, and PathTraceView built the trace SVG URL from the origin's REST
API action. Both assume the origin is a PathEndpoint, but a CircuitTermination
is a valid cable-path origin (per from_origin) without the _path field or a
-trace API action, so those paths raised FieldDoesNotExist and NoReverseMatch
respectively. Guard the _path writes and the SVG URL on PathEndpoint membership,
and skip the SVG block in the template when no URL is available.