Skip to content

Job Extensions

In addition to standard Jobs, Nautobot supports two specialized types of Jobs that respond to specific events:

  • Job Button Receivers: Triggered by a button click on a specific object (e.g., a Device or Location)
  • Job Hook Receivers: Automatically triggered in response to object changes (create, update, delete)

These extensions let you tightly couple automation to user actions or system changes without requiring manual Job execution from the Jobs view.

Job Button Receivers

These are Jobs that subclass the nautobot.apps.jobs.JobButtonReceiver class. Job Button Receivers are similar to normal Jobs except they are hard coded to accept only object_pk and object_model_name variables. The JobButtonReceiver class only implements one method called receive_job_button.

Disabled by default just like other Jobs

Job Button Receivers still need to be enabled through the web UI before they can be used just like other Jobs.

The receive_job_button() Method

All JobButtonReceiver subclasses must implement receive_job_button(self, obj), where obj is the instance of the model where the button was pressed.

Example Job Button Receiver

from nautobot.apps.jobs import JobButtonReceiver, register_jobs

class ExampleSimpleJobButtonReceiver(JobButtonReceiver):
    class Meta:
        name = "Example Simple Job Button Receiver"

    def receive_job_button(self, obj):
        self.logger.info("Running Job Button Receiver.", extra={"object": obj})
        # Add job logic here


register_jobs(ExampleSimpleJobButtonReceiver)

Job Buttons for Multiple Types

Since Job Buttons can be associated to multiple object types, it would be easy to create a Job that can change what it runs based on the object type.

You can support multiple object types in a single Job by checking the object type using isinstance() and performing permission checks with self.user.has_perm().

Checking Permissions

You can use self.user.has_perm() to restrict logic based on the object or user's role.

Example

from nautobot.apps.jobs import JobButtonReceiver, register_jobs
from nautobot.dcim.models import Device, Location

class ExampleComplexJobButtonReceiver(JobButtonReceiver):
    class Meta:
        name = "Example Complex Job Button Receiver"

    def _run_location_job(self, obj):
        self.logger.info("Running Location Job Button Receiver.", extra={"object": obj})
        # Run Location Job function

    def _run_device_job(self, obj):
        self.logger.info("Running Device Job Button Receiver.", extra={"object": obj})
        # Run Device Job function

    def receive_job_button(self, obj):
        user = self.user
        if isinstance(obj, Location):
            if not user.has_perm("dcim.add_location"):
                self.logger.error("User '%s' does not have permission to add a Location.", user, extra={"object": obj})
                raise Exception("User does not have permission to add a Location.")
            else:
                self._run_location_job(obj)
        elif isinstance(obj, Device):
            if not user.has_perm("dcim.add_device"):
                self.logger.error("User '%s' does not have permission to add a Device.", user, extra={"object": obj})
                raise Exception("User does not have permission to add a Device.")
            else:
                self._run_device_job(obj)
        else:
            self.logger.error("Unable to run Job Button for type %s.", type(obj).__name__, extra={"object": obj})
            raise Exception("Job button called on unsupported object type.")


register_jobs(ExampleComplexJobButtonReceiver)

Job Hook Receivers

A Job Hook Receiver listens for database changes and runs automatically when an object is created, updated, or deleted. Job Hooks are only able to initiate a specific type of Job called a Job Hook Receiver. These are Jobs that subclass the nautobot.apps.jobs.JobHookReceiver class. Job Hook Receivers are similar to normal Jobs except they are hard coded to accept only an object_change variable. The JobHookReceiver class only implements one method called receive_job_hook.

Use Job Hooks to enforce policy, run audits, or trigger external actions in response to changes.

No support for approval_required at this time

Requiring approval for execution of Job Hooks by setting the Meta.approval_required attribute to True on your JobHookReceiver subclass is not supported. The value of this attribute will be ignored. Support for requiring approval of Job Hooks may be added in a future release.

No recursive JobHookReceivers

To prevent negatively impacting system performance through an infinite loop, a change that was made by a JobHookReceiver Job will not trigger another JobHookReceiver Job to run.

Example Job Hook Receiver

from nautobot.apps.jobs import JobHookReceiver, register_jobs
from nautobot.extras.choices import ObjectChangeActionChoices

class ExampleJobHookReceiver(JobHookReceiver):
    def receive_job_hook(self, change, action, changed_object):
        # return on delete action
        if action == ObjectChangeActionChoices.ACTION_DELETE:
            return

        # log diff output
        snapshots = change.get_snapshots()
        self.logger.info("DIFF: %s", snapshots['differences'])

        # validate changes to serial field
        if "serial" in snapshots["differences"]["added"]:
            old_serial = snapshots["differences"]["removed"]["serial"]
            new_serial = snapshots["differences"]["added"]["serial"]
            self.logger.info("%s serial has been changed from %s to %s", changed_object, old_serial, new_serial)

            # Check the new serial is valid and revert if necessary
            if not self.validate_serial(new_serial):
                changed_object.serial = old_serial
                changed_object.save()
                self.logger.info("%s serial %s was not valid. Reverted to %s", changed_object, new_serial, old_serial)

            self.logger.info("Serial validation completed for %s", changed_object)

    def validate_serial(self, serial):
        # add business logic to validate serial
        return False

register_jobs(ExampleJobHookReceiver)

The receive_job_hook() Method

All JobHookReceiver subclasses must implement receive_job_hook(self, change, action, changed_object), which receives:

  1. change: the ObjectChange instance that triggered the hook
  2. action: A string with the action performed on the changed object - one of "create", "update", or "delete"
  3. changed_object: An instance of the object that was changed, or None if the object has been deleted

In the example above, the Job inspects changes to the serial field and reverts them if they don't meet custom criteria. It also logs differences using change.get_snapshots() and avoids acting on delete events. This kind of hook is useful for enforcing policy or catching problematic edits before they propagate further.

If the action is "delete", the method exits early since changed_object is None.

Note

You can use the change object to inspect more detailed diffs if needed. See the ObjectChange docs for more.