* Closes#22770: Allow plugins to register Event Rule action handlers
Introduces an EventRuleAction registration API (netbox.event_rules /
netbox.extras.event_rules) so plugins can add new EventRule action types
the same way they already register search indexes and event types,
replacing the hardcoded webhook/script/notification elif-chain. Core's
own three action types are refactored onto this mechanism.
An EventRule referencing an unregistered action (e.g. its providing
plugin is uninstalled) remains stored, is skipped during processing
without affecting other rules, is visibly marked unavailable in the
UI/API, and triggers a new extras.W001 system check, resuming
automatically once the plugin is reinstalled, with no need to re-save.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix CI failure: extras.W001 check must tolerate a not-yet-migrated database
check_event_rule_actions() queried EventRule unconditionally, which broke
`manage.py makemigrations --check` (and a fresh `manage.py migrate`) on a
database with no tables yet, since Django runs system checks before
verifying/applying migrations. Wrap the query and swallow DatabaseError,
matching the existing check_postgresql_version precedent for a database
that may not be ready. Verified against a fresh, unmigrated database that
makemigrations --check, migrate, and manage.py check all behave correctly,
and that the warning still fires once a qualifying EventRule exists.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix EventRuleForm action_type widget: HTMXSelect was silently ignored
Meta.widgets only applies to fields the ModelForm auto-generates; action_type
is an explicit class-level field, so its HTMXSelect assignment in Meta.widgets
never took effect, and switching Action type in the browser never refreshed
the action_choice field's label/queryset. Move the widget onto the field
declaration itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Address review feedback from Jeremy Stretch on PR #22793
- Revert action_object_type on_delete to CASCADE (was changed to SET_NULL)
- Make action_type choices dynamic via the model field's own callable
choices=, simplifying EventRule.clean() and making any unavailable
action_type invalid on save, whether new or unchanged
- Rename is_action_available to action_is_available
- Fold new dispatch tests into the existing RQQueueTestMixin test class to
fix a flaky --parallel run (two such classes cross-flush each other's
Redis queues)
- Only catch broad exceptions around plugin-provided actions in
process_event_rules(); let a core action's own bugs propagate
- Add value_action_type() so table exports don't leak the "unavailable"
badge's HTML markup
- Drop the frozen CSVChoiceField on action_type and make action_object
optional at the field level, so bulk import of an object-less action
works
- Map ValidationErrors on unexposed model fields to NON_FIELD_ERRORS in
bulk import instead of letting them surface as a raw ValueError
- Restore EventRuleActionEnum/the enum-based GraphQL filter, built from
the live action registry instead of the static EventRuleActionChoices
- Default EventRuleAction.object_required to False, matching
object_model's default of None; set it explicitly on the three core
actions
- Drop the unused request parameter on get_object_queryset()
- Fix action_object_type's serializer queryset, which incorrectly used
the triggering object_types' feature flag
- Use .format() instead of % in get_action_type_display()
- Remove the extras.W001 system check (a DB query on every management
command) in favor of an action_is_available field on the REST API
- Raise ValidationError instead of a bare Exception on duplicate action
slug registration
- Shorten a couple of overly verbose inline comments
- Split EventRuleAction.validate() into an internal _validate() and a
public no-op validate(), so a subclass's custom validation doesn't
need to remember to call super()
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Trim verbose comments/docstrings added while addressing review feedback
Shortened a number of overly long inline comments and test docstrings
introduced across the previous commit's review-feedback fixes (the
EventRuleActionEnum comment, the _validate()/validate() docstrings, and
several test docstrings that restated context already given elsewhere).
Also drops the auto-generated "Generated by Django" header comment from
migration 0143, matching the rest of this app's hand-touched migrations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Address findings from automated follow-up review of #22793
- Clear stale action_object_type/action_object_id when an action declares
object_model but is left with no object selected (object_required=False);
previously neither branch of the if/elif fired and the old value from
before the edit silently persisted. Fixed in both EventRuleForm and
EventRuleImportForm (the latter matters for CSV updates of an existing
row). Also resolve the content type from the actual selected object
rather than the action's declared object_model, correctly handling
subclass/proxy instances.
- Validate action slugs at registration time (format, and collision via
enum_key() with an already-registered slug) so a bad third-party slug
is rejected immediately instead of crashing GraphQL schema assembly at
startup.
- Strip the dead-code label/description duplication out of
EventRuleActionChoices.CHOICES -- nothing reads it, and it risked
drifting from WebhookAction/ScriptAction/NotificationAction's own.
- Record whether an action is plugin-provided at registration time
instead of introspecting its module on every dispatch; core's three
actions now register with is_plugin_provided=False explicitly.
- Add an action_is_available filter (API + UI) so event rules with a
now-unavailable action can still be found in bulk, now that the
extras.W001 system check is gone.
- Update the plugin dev docs: fix the OpenTicketAction example (it was
missing object_required=True, the exact gap the action_object fix
above addresses), note that an unavailable rule can't be saved at all
(not just skipped), and move an internal-only note out of the
published class docstring.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Address findings from second automated follow-up review of #22793
- Drop the ChoiceSet base from EventRuleActionChoices. With CHOICES=(),
the previous version made ChoiceField(choices=EventRuleActionChoices)
-- the idiomatic pattern used for every other ChoiceSet in this
codebase, and reachable via `from extras.choices import *` -- silently
reject every value instead of failing at first use.
- Reject slugs containing hyphens or a leading underscore at
registration time: a hyphenated slug (plausible, since plugin
distribution names are conventionally hyphenated) or a slug starting
with an underscore both currently pass validation but produce a
GraphQL-invalid or GraphQL-reserved enum member name once sanitized,
crashing schema assembly at startup. Document the constraint in the
plugin docs and the published slug docstring.
- Raise ImproperlyConfigured instead of ValidationError for all three
registration-failure cases in register_event_rule_action() -- these
are packaging/configuration mistakes surfaced from AppConfig.ready(),
not user input, matching the convention ChoiceSetMeta already uses for
the same class of error.
- Remove is_plugin_provided's class-level default; nothing reads it
before an action is registered in any real code path, and the default
masked a class-vs-instance inconsistency. Move its documentation out
of the published Attributes docstring into a plain comment.
- Simplify EventRuleImportForm.clean()'s action_object_type/id
assignment to match EventRuleForm.clean()'s approach (set both fields
once, unconditionally, from the resolved object) rather than assigning
via the GFK setter and then conditionally overwriting the content
type.
- Split a dense doc sentence in eventrule.md onto its own line.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Address findings from third automated follow-up review of #22793
- Restore is_plugin_provided's class-level default of True. Its only
read is inside process_event_rules()'s exception handler; without a
default, an action reaching dispatch without going through
register_event_rule_action() (e.g. inserted into the registry dict
directly) raised AttributeError while already handling the real
exception, masking it entirely instead of degrading gracefully.
- Move the slug/label presence checks out of __init_subclass__ (which
fired at class-definition time, raising TypeError) and into
register_event_rule_action() as ImproperlyConfigured, unifying them
with the other three registration-time checks. This also resolves a
still-open item from the very first automated review: an intermediate
base class shared by several concrete plugin actions couldn't
previously be defined without a placeholder slug/label of its own.
- Restore the GFK assignment (self.instance.action_object = obj) in
EventRuleImportForm.clean() alongside the explicit content-type
assignment, so EventRule.clean()'s later access to action_object hits
the descriptor cache instead of an extra SELECT per imported row.
- Clarify the slug docstring/docs wording (leading underscore
specifically, not underscores in general; tell authors to use an
underscore instead of a hyphen) and document that intermediate base
classes are now supported. Add a test for an uppercase slug.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Document the frozen-at-import tradeoff on EventRuleSerializer.action_type
The EventRuleActionAPITestCase docstring in test_api.py pointed here for
an explanation of why the choices are materialized once at module-import
time rather than dynamically, but the field itself had no such comment.
* Address findings from fourth automated follow-up review of #22793
- Restore the "must start with a letter" slug constraint dropped from
the docs page and class docstring by the previous round's rewording;
reword to "must begin with a lowercase letter", which covers the
leading-digit case SLUG_RE actually rejects and matches the
ImproperlyConfigured message an author will hit.
- Scope the GFK-cache-priming comment in EventRuleImportForm.clean() to
the non-proxy case it actually holds for, rather than claiming it
unconditionally.
- Trim comments and docstrings that had regrown into reviewer-facing
rationale (why a prior finding was reverted, why a check isn't in
__init_subclass__ anymore rather than API documentation, in
event_rules.py and test_event_rules.py.
EOF
)
* Misc cleanup
* Misc cleanup
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Introduce experimental Python package installation workflow as an
alternative to release archive and Git methods. Document package layout,
setup command, upgrade procedure, and migration path for existing
deployments.
Fixes#22604
* Add ModelAction and register_model_actions() API for custom permission actions
* Add ObjectTypeSplitMultiSelectWidget and RegisteredActionsWidget
* Integrate registered actions into ObjectPermissionForm
* Add JavaScript for registered actions show/hide
* Register custom actions for DataSource, Device, and VirtualMachine
* Add tests for ModelAction and register_model_actions
* Refine registered actions widget UI
- Use verbose labels (App | Model) for action group headers
- Simplify template layout with h5 headers instead of cards
- Consolidate Standard/Custom/Additional Actions into single Actions fieldset
* Hide custom actions field when no applicable models selected
The entire field row is now hidden when no selected object types
have registered custom actions, avoiding an empty "Custom actions"
label.
* Add documentation for custom model actions
- Add plugin development guide for registering custom actions
- Update admin permissions docs to mention custom actions UI
- Add docstrings to ModelAction and register_model_actions
* Add RESERVED_ACTIONS constant and fix dedup in registered actions
- Define RESERVED_ACTIONS in users/constants.py for the four built-in
permission actions (view, add, change, delete)
- Replace hardcoded action lists in ObjectPermissionForm with the constant
- Fix duplicate action names in clean() when the same action is registered
across multiple models (e.g. render_config for Device and VirtualMachine)
- Fix template substring matching bug in objectpermission.html detail view
by passing RESERVED_ACTIONS through view context for proper list membership
* Fix shared action pre-selection and additional actions leakage on edit
* Prevent duplicate action registration in register_model_actions()
* Remove stale comment in RegisteredActionsWidget
* Rebuild frontend assets after rebase onto feature
* Refactor SplitMultiSelectWidget to use class attributes for widget classes
* Reject reserved action names in register_model_actions()
* Show all registered actions with enable/disable instead of show/hide
* Validate action name is not empty and clarify RESERVED_ACTIONS origin
* Adapt custom actions panel for declarative layout system
Convert the ObjectPermission detail view to use the new panel-based
layout from #21568. Add ObjectPermissionCustomActionsPanel that
cross-references assigned object types with the model_actions registry
to display which models each custom action applies to.
Also fix dark-mode visibility of disabled action checkboxes in the
permission form by overriding Bootstrap's disabled opacity.
* Flatten registered actions UI and declare via Meta.permissions
Implement two changes requested in review of #21560:
1. Use Meta.permissions for action declaration
- Add Meta.permissions to DataSource, Device, and VirtualMachine
- register_models() auto-registers actions from Meta.permissions
- Remove explicit register_model_actions() calls from apps.py
- Add get_action_model_map() utility to utilities/permissions.py
2. Flatten the ObjectPermission form UI
- Show a single deduplicated list of action checkboxes (one per
unique action name) instead of grouped-by-model checkboxes
- RegisteredActionsWidget uses create_option() to inject model_keys
and help_text; JS enables/disables based on selected object types
- render_field.html bypasses outer wrapper for registeredactionswidget
so widget emits rows with identical DOM structure to CRUD checkboxes
- Unchecking a model now also unchecks unsupported action checkboxes
Fixes#21357
* Address review feedback on registered actions
- Sort model_keys in data-models attribute for deterministic output
- Rename registered_actions field label to 'Registered actions'
- Target object_types selected list via data-object-types-selected
attribute instead of hardcoded DOM ID
- Reduce setTimeout delay to 0ms since moveOption() is synchronous
* Consolidate ObjectPermission detail view actions panel
Merge ObjectPermissionActionsPanel and ObjectPermissionCustomActionsPanel
into a single Actions panel that shows CRUD booleans and all registered
actions in one table, matching the form's consolidated layout.
Also fix data-object-types-selected attribute value (True -> 'true') and
update plugin docs to show Meta.permissions as the primary registration
approach.
* Address additional bot review feedback
- clean() collects all validation errors before raising instead of stopping at the first
- Fix stale admin docs (still referenced "Custom actions" and "grouped by model")
* Update netbox/netbox/registry.py
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
* Fix model_actions registry to use set operations
The registry was changed to defaultdict(set) but the registration
code still used list methods. Update .append() to .add() and fix
tests to use set-compatible access patterns.
* Rename permission migrations for clarity
* Move ModelAction validation into __post_init__
* Drop model name from permission descriptions
* Simplify ObjectPermission form and remove custom widgets
Replace the dynamic UI with standard BooleanField checkboxes for each
registered action. No custom widgets, no JavaScript, no template
changes.
- Remove RegisteredActionsWidget, ObjectTypeSplitMultiSelectWidget,
and registeredActions.ts
- Use dynamic BooleanFields for registered actions (renders identically
to CRUD checkboxes)
- Move action-resolution logic from panel to ObjectPermission model
- Remove object-type cross-validation from form clean()
- Remove unused get_action_model_map utility
* Remove register_model_actions from public API
Meta.permissions is the documented approach for plugins. The
register_model_actions function is now an internal implementation
detail.
* Sort registered actions and improve test coverage
Sort action names alphabetically for stable display order. Add tests
for cloning, empty registry, and models_csv output.
* Add help_text to registered action checkboxes
* Return model_keys as list from get_registered_actions()
Move string joining to the template so callers get native
list data instead of a pre-formatted CSV string.
* Improve detail view: human-friendly descriptions and additional actions
Return dicts from get_registered_actions() with help_text and verbose
model names. Add get_additional_actions() for manually-entered actions
that aren't CRUD or registered. Show both in the Actions panel.
* Renumber permission migrations after feature merge
Resolve migration conflicts with default_ordering_indexes migrations.
Renumber to 0023 (core), 0232 (dcim), 0056 (virtualization) and
update dependencies.
---------
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Introduce `VirtualMachineType` to classify virtual machines and apply
default platform, vCPU, and memory values when creating a VM.
This adds the new model and its relationship to `VirtualMachine`, and
wires it through forms, filtersets, tables, views, the REST API,
GraphQL, navigation, search, documentation, and tests.
Explicit values set on a virtual machine continue to take precedence,
and changes to a type do not retroactively update existing VMs.
* Drop mkdocs from `requirements.txt` and add Zensical
* Replace mkdocs with Zensical in CI and pre-commit tasks
* Remove `.info` from the `docs/` build directory (obsolete)
* Update the legacy ReadTheDocs configuration
* Update upgrade script to use Zensical
* Remove custom docs footer
* Remove obsolete CSS
* Closes#20003: Introduce mechanism to register callbacks for webhook context
* Swap ContentType with ObjectType
* Add plugin dev documentation for webhook callbacks
* Fix tests
* Add note about namespacing webhook data
* Add SavedTableConfig
* Update table configuration logic to support TableConfigs
* Update table config link when updating table
* Correct docstring
* Misc cleanup
* Use multi-select widgets for column selection
* Return null config params for tables with no model
* Fix auto-selection of selected columns
* Update migration
* Clean up template
* Enforce enabled/shared flags
* Search/filter by table name
* Misc cleanup
* Fix population of selected columns
* Ordering field should not be required
* Enable cloning for TableConfig
* Misc cleanup
* Add model documentation for TableConfig
* Drop slug field from TableConfig
* Improve TableConfig validation
* Remove add button from TableConfig list view
* Fix ordering validation to account for leading hyphens
* Add instructions for authenticating using Google oauth2
Signed-off-by: Ian Bishop <151477169+ianb-mp@users.noreply.github.com>
* Add navigation link
* Misc cleanup
---------
Signed-off-by: Ian Bishop <151477169+ianb-mp@users.noreply.github.com>
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Occurrences of the old term have been replaced by the new term. However,
the documentation still needs some work to reflect the new Entra ID
screenshots and terminology.
* Introduce reusable BackgroundJob framework
A new abstract class can be used to implement job function classes. It
handles the necessary logic for starting and stopping jobs, including
exception handling and rescheduling of recurring jobs.
This commit also includes the migration of data source jobs to the new
framework.
* Restore using import_string for jobs
Using the 'import_string()' utility from Django allows the job script
class to be simplified, as module imports no longer need to avoid loops.
This should make it easier to queue and maintain jobs.
* Use SyncDataSourceJob for management command
Instead of maintaining two separate job execution logics, the same job
is now used for both background and interactive execution.
* Implement BackgroundJob for running scripts
The independent implementations of interactive and background script
execution have been merged into a single BackgroundJob implementation.
* Fix documentation of model features
* Ensure consitent code style
* Introduce reusable ScheduledJob
A new abstract class can be used to implement job function classes that
specialize in scheduling. These use the same logic as regular
BackgroundJobs, but ensure that they are only scheduled once at any given
time.
* Introduce reusable SystemJob
A new abstract class can be used to implement job function classes that
specialize in system background tasks (e.g. synchronization or
housekeeping). In addition to the features of the BackgroundJob and
ScheduledJob classes, these implement additional logic to not need to be
bound to an existing NetBox object and to setup job schedules on plugin
load instead of an interactive request.
* Add documentation for jobs framework
* Revert "Use SyncDataSourceJob for management"
This partially reverts commit db591d4. The 'run_now' parameter of
'enqueue()' remains, as its being used by following commits.
* Merge enqueued status into JobStatusChoices
* Fix logger for ScriptJob
* Remove job name for scripts
Because scripts are already linked through the Job Instance field, the
name is displayed twice. Removing this reduces redundancy and opens up
the possibility of simplifying the BackgroundJob framework in future
commits.
* Merge ScheduledJob into BackgroundJob
Instead of using separate classes, the logic of ScheduledJob is now
merged into the generic BackgroundJob class. This allows reusing the
same logic, but dynamically deciding whether to enqueue the same job
once or multiple times.
* Add name attribute for BackgroundJob
Instead of defining individual names on enqueue, BackgroundJob classes
can now set a job name in their meta class. This is equivalent to other
Django classes and NetBox scripts.
* Drop enqueue_sync_job() method from DataSource
* Import ScriptJob directly
* Relax requirement for Jobs to reference a specific object
* Rename 'run_now' arg on Job.enqueue() to 'immediate'
* Fix queue lookup in Job enqueue
* Collapse SystemJob into BackgroundJob
* Remove legacy JobResultStatusChoices
ChoiceSet was moved to core in 40572b5.
* Use queue 'low' for system jobs by default
System jobs usually perform low-priority background tasks and therefore
can use a different queue than 'default', which is used for regular jobs
related to specific objects.
* Add test cases for BackgroundJob handling
* Fix enqueue interval jobs
As the job's name is set by enqueue(), it must not be passed in handle()
to avoid duplicate kwargs with the same name.
* Honor schedule_at for job's enqueue_once
Not only can a job's interval change, but so can the time at which it is
scheduled to run. If a specific scheduled time is set, it will also be
checked against the current job schedule. If there are any changes, the
job is rescheduled with the new time.
* Switch BackgroundJob to regular methods
Instead of using a class method for run(), a regular method is used for
this purpose. This gives the possibility to add more convenience methods
in the future, e.g. for interacting with the job object or for logging,
as implemented for scripts.
* Fix background tasks documentation
* Test enqueue in combination with enqueue_once
* Rename background jobs to tasks (to differentiate from RQ)
* Touch up docs
* Revert "Use queue 'low' for system jobs by default"
This reverts commit b17b2050df.
* Remove system background job
This commit reverts commits 4880d81 and 0b15ecf. Using the database
'connection_created' signal for job registration feels a little wrong at
this point, as it would trigger registration very often. However, the
background job framework is prepared for this use case and can be used
by plugins once the auto-registration of jobs is solved.
* Fix runscript management command
Defining names for background jobs was disabled with fb75389. The
preceeding changes in 257976d did forget the management command.
* Use regular imports for ScriptJob
* Rename BackgroundJob to JobRunner
---------
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
* 12826 add RackType
* 12826 add forms, filters, tables
* 12826 add to menu
* 12826 remove role
* 12826 add api/serializers
* 12826 add tests and fixes
* 12826 fix tests
* 12826 fix tests
* 12826 fix tests
* 12826 fix tests
* 12826 add device_type to device and instantiation
* 12826 test device creation
* 12826 add slug
* 12826 fix tests
* 12826 fix slug field
* 12826 prevent modification of rack fields if rack_type set
* 12826 update rack fields on rack_type edit
* Misc cleanup
* Update model docs
* Add manufacturer field to RackType
* Add test for mounting_depth
* Rename 'type' to 'form_factor'
* Create base classes for Rack & RackType models, serializers
* Hide RackType-defined fields on RackForm when a rack type is set
* Establish a base filter form for Rack & RackType
* Clean up RackType attr inheritance
* Clean up templates
---------
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
* Initial work on #15621
* Signal receiver should ignore models which don't support notifications
* Flesh out NotificationGroup functionality
* Add NotificationGroup filters for users & groups
* Separate read & dimiss actions
* Enable one-click dismissals from notifications list
* Include total notification count in dropdown
* Drop 'kind' field from Notification model
* Register event types in the registry; add colors & icons
* Enable event rules to target notification groups
* Define dynamic choices for Notification.event_name
* Move event registration to core
* Add more job events
* Misc cleanup
* Misc cleanup
* Correct absolute URLs for notifications & subscriptions
* Optimize subscriber notifications
* Use core event types when queuing events
* Standardize queued event attribute to event_type; change content_type to object_type
* Rename Notification.event_name to event_type
* Restore NotificationGroupBulkEditView
* Add API tests
* Add view & filterset tests
* Add model documentation
* Fix tests
* Update notification bell when notifications have been cleared
* Ensure subscribe button appears only on relevant models
* Notifications/subscriptions cannot be ordered by object
* Misc cleanup
* Add event icon & type to notifications table
* Adjust icon sizing
* Mute color of read notifications
* Misc cleanup