We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Job Buttons do not commit changes in their job.
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 1.6.11
* Python version: 3.11
* Database platform, version: MySQL
* Middleware(s): NA

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. Create  JobButtonReciever, set it to commit by default.
2. Create a Job Button and assign to a ContentType.
3. Click the Job Button on the assigned Content Type.

<!-- What did you expect to happen? -->
### Expected Behavior

The job runs and commits the changes.

<!-- What happened instead? -->
### Observed Behavior

The Job runs and the database changes are reverted.
![image](https://github.com/nautobot/nautobot/assets/17092348/c98724cf-ec91-4e58-aad2-b940346b80b8)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/extras/templatetags/job_buttons.py`
Content:
```
1 from collections import OrderedDict
2 
3 from django import template
4 from django.contrib.contenttypes.models import ContentType
5 from django.urls import reverse
6 from django.utils.html import format_html
7 from django.utils.safestring import mark_safe
8 
9 from nautobot.extras.models import Job, JobButton
10 from nautobot.utilities.utils import render_jinja2
11 
12 
13 register = template.Library()
14 
15 GROUP_DROPDOWN = """
16 <div class="btn-group">
17   <button type="button" class="btn btn-sm btn-{group_button_class} dropdown-toggle" data-toggle="dropdown">
18     {group_name} <span class="caret"></span>
19   </button>
20   <ul class="dropdown-menu pull-right">
21     {grouped_buttons}
22   </ul>
23 </div>
24 """
25 
26 HIDDEN_INPUTS = """
27 <input type="hidden" name="csrfmiddlewaretoken" value="{csrf_token}">
28 <input type="hidden" name="object_pk" value="{object_pk}">
29 <input type="hidden" name="object_model_name" value="{object_model_name}">
30 <input type="hidden" name="_schedule_type" value="immediately">
31 <input type="hidden" name="_return_url" value="{redirect_path}">
32 """
33 
34 NO_CONFIRM_BUTTON = """
35 <button type="submit" form="form_id_{button_id}" class="btn btn-sm btn-{button_class}" {disabled}>{button_text}</button>
36 """
37 
38 NO_CONFIRM_FORM = """
39 <form id="form_id_{button_id}" action="{button_url}" method="post" class="form">
40   {hidden_inputs}
41 </form>
42 """
43 
44 CONFIRM_BUTTON = """
45 <button type="button" class="btn btn-sm btn-{button_class}" data-toggle="modal" data-target="#confirm_modal_id_{button_id}" {disabled}>
46   {button_text}
47 </button>
48 """
49 
50 CONFIRM_MODAL = """
51 <div class="modal fade" id="confirm_modal_id_{button_id}" tabindex="-1" role="dialog" aria-labelledby="confirm_modal_label_{button_id}">
52   <div class="modal-dialog" role="document">
53     <div class="modal-content">
54       <div class="modal-header">
55         <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
56         <h4 class="modal-title" id="confirm_modal_label_{button_id}">Confirmation</h4>
57       </div>
58       <form id="form_id_{button_id}" action="{button_url}" method="post" class="form">
59         <div class="modal-body">
60           {hidden_inputs}
61           Run Job <strong>'{job}'</strong> with object <strong>'{object}'</strong>?
62         </div>
63         <div class="modal-footer">
64           <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
65           <button type="submit" class="btn btn-primary">Confirm</button>
66         </div>
67       </form>
68     </div>
69   </div>
70 </div>
71 """
72 
73 SAFE_EMPTY_STR = mark_safe("")  # noqa: S308
74 
75 
76 def _render_job_button_for_obj(job_button, obj, context, content_type):
77     """
78     Helper method for job_buttons templatetag to reduce repetition of code.
79 
80     Returns:
81        (str, str): (button_html, form_html)
82     """
83     # Pass select context data when rendering the JobButton text as Jinja2
84     button_context = {
85         "obj": obj,
86         "debug": context.get("debug", False),  # django.template.context_processors.debug
87         "request": context["request"],  # django.template.context_processors.request
88         "user": context["user"],  # django.contrib.auth.context_processors.auth
89         "perms": context["perms"],  # django.contrib.auth.context_processors.auth
90     }
91     try:
92         text_rendered = render_jinja2(job_button.text, button_context)
93     except Exception as exc:
94         return (
95             format_html(
96                 '<a class="btn btn-sm btn-{}" disabled="disabled" title="{}"><i class="mdi mdi-alert"></i> {}</a>\n',
97                 "default" if not job_button.group_name else "link",
98                 exc,
99                 job_button.name,
100             ),
101             SAFE_EMPTY_STR,
102         )
103 
104     if not text_rendered:
105         return (SAFE_EMPTY_STR, SAFE_EMPTY_STR)
106 
107     # Disable buttons if the user doesn't have permission to run the underlying Job.
108     has_run_perm = Job.objects.check_perms(context["user"], instance=job_button.job, action="run")
109     hidden_inputs = format_html(
110         HIDDEN_INPUTS,
111         csrf_token=context["csrf_token"],
112         object_pk=obj.pk,
113         object_model_name=f"{content_type.app_label}.{content_type.model}",
114         redirect_path=context["request"].path,
115     )
116     template_args = {
117         "button_id": job_button.pk,
118         "button_text": text_rendered,
119         "button_class": job_button.button_class if not job_button.group_name else "link",
120         "button_url": reverse("extras:job_run", kwargs={"slug": job_button.job.slug}),
121         "object": obj,
122         "job": job_button.job,
123         "hidden_inputs": hidden_inputs,
124         "disabled": "" if has_run_perm else "disabled",
125     }
126 
127     if job_button.confirmation:
128         return (
129             format_html(CONFIRM_BUTTON, **template_args),
130             format_html(CONFIRM_MODAL, **template_args),
131         )
132     else:
133         return (
134             format_html(NO_CONFIRM_BUTTON, **template_args),
135             format_html(NO_CONFIRM_FORM, **template_args),
136         )
137 
138 
139 @register.simple_tag(takes_context=True)
140 def job_buttons(context, obj):
141     """
142     Render all applicable job buttons for the given object.
143     """
144     content_type = ContentType.objects.get_for_model(obj)
145     # We will enforce "run" permission later in deciding which buttons to show as disabled.
146     buttons = JobButton.objects.filter(content_types=content_type)
147     if not buttons:
148         return SAFE_EMPTY_STR
149 
150     buttons_html = forms_html = SAFE_EMPTY_STR
151     group_names = OrderedDict()
152 
153     for jb in buttons:
154         # Organize job buttons by group for later processing
155         if jb.group_name:
156             group_names.setdefault(jb.group_name, []).append(jb)
157 
158         # Render and add non-grouped buttons
159         else:
160             button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)
161             buttons_html += button_html
162             forms_html += form_html
163 
164     # Add grouped buttons to template
165     for group_name, buttons in group_names.items():
166         group_button_class = buttons[0].button_class
167 
168         buttons_rendered = SAFE_EMPTY_STR
169 
170         for jb in buttons:
171             # Render grouped buttons as list items
172             button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)
173             buttons_rendered += format_html("<li>{}</li>", button_html)
174             forms_html += form_html
175 
176         if buttons_rendered:
177             buttons_html += format_html(
178                 GROUP_DROPDOWN,
179                 group_button_class=group_button_class,
180                 group_name=group_name,
181                 grouped_buttons=buttons_rendered,
182             )
183 
184     # We want all of the buttons first and then any modals and forms so the buttons render properly
185     return buttons_html + forms_html
186
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nautobot/extras/templatetags/job_buttons.py b/nautobot/extras/templatetags/job_buttons.py
--- a/nautobot/extras/templatetags/job_buttons.py
+++ b/nautobot/extras/templatetags/job_buttons.py
@@ -29,6 +29,7 @@
 <input type="hidden" name="object_model_name" value="{object_model_name}">
 <input type="hidden" name="_schedule_type" value="immediately">
 <input type="hidden" name="_return_url" value="{redirect_path}">
+<input type="hidden" name="_commit" value="on">
 """
 
 NO_CONFIRM_BUTTON = """
@@ -170,8 +171,10 @@
         for jb in buttons:
             # Render grouped buttons as list items
             button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)
-            buttons_rendered += format_html("<li>{}</li>", button_html)
-            forms_html += form_html
+            if button_html:
+                buttons_rendered += format_html("<li>{}</li>", button_html)
+            if form_html:
+                forms_html += form_html
 
         if buttons_rendered:
             buttons_html += format_html(

{"golden_diff": "diff --git a/nautobot/extras/templatetags/job_buttons.py b/nautobot/extras/templatetags/job_buttons.py\n--- a/nautobot/extras/templatetags/job_buttons.py\n+++ b/nautobot/extras/templatetags/job_buttons.py\n@@ -29,6 +29,7 @@\n <input type=\"hidden\" name=\"object_model_name\" value=\"{object_model_name}\">\n <input type=\"hidden\" name=\"_schedule_type\" value=\"immediately\">\n <input type=\"hidden\" name=\"_return_url\" value=\"{redirect_path}\">\n+<input type=\"hidden\" name=\"_commit\" value=\"on\">\n \"\"\"\n \n NO_CONFIRM_BUTTON = \"\"\"\n@@ -170,8 +171,10 @@\n         for jb in buttons:\n             # Render grouped buttons as list items\n             button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)\n-            buttons_rendered += format_html(\"<li>{}</li>\", button_html)\n-            forms_html += form_html\n+            if button_html:\n+                buttons_rendered += format_html(\"<li>{}</li>\", button_html)\n+            if form_html:\n+                forms_html += form_html\n \n         if buttons_rendered:\n             buttons_html += format_html(\n", "issue": "Job Buttons do not commit changes in their job.\n<!--\r\n    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.\r\n\r\n    This form is only for reporting reproducible bugs. If you need assistance\r\n    with Nautobot installation, or if you have a general question, please start a\r\n    discussion instead: https://github.com/nautobot/nautobot/discussions\r\n\r\n    Please describe the environment in which you are running Nautobot. Be sure\r\n    that you are running an unmodified instance of the latest stable release\r\n    before submitting a bug report, and that any plugins have been disabled.\r\n-->\r\n### Environment\r\n* Nautobot version (Docker tag too if applicable): 1.6.11\r\n* Python version: 3.11\r\n* Database platform, version: MySQL\r\n* Middleware(s): NA\r\n\r\n<!--\r\n    Describe in detail the exact steps that someone else can take to reproduce\r\n    this bug using the current stable release of Nautobot. Begin with the\r\n    creation of any necessary database objects and call out every operation\r\n    being performed explicitly. If reporting a bug in the REST API, be sure to\r\n    reconstruct the raw HTTP request(s) being made: Don't rely on a client\r\n    library such as pynautobot.\r\n-->\r\n### Steps to Reproduce\r\n1. Create  JobButtonReciever, set it to commit by default.\r\n2. Create a Job Button and assign to a ContentType.\r\n3. Click the Job Button on the assigned Content Type.\r\n\r\n<!-- What did you expect to happen? -->\r\n### Expected Behavior\r\n\r\nThe job runs and commits the changes.\r\n\r\n<!-- What happened instead? -->\r\n### Observed Behavior\r\n\r\nThe Job runs and the database changes are reverted.\r\n![image](https://github.com/nautobot/nautobot/assets/17092348/c98724cf-ec91-4e58-aad2-b940346b80b8)\r\n\n", "before_files": [{"content": "from collections import OrderedDict\n\nfrom django import template\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfrom django.utils.safestring import mark_safe\n\nfrom nautobot.extras.models import Job, JobButton\nfrom nautobot.utilities.utils import render_jinja2\n\n\nregister = template.Library()\n\nGROUP_DROPDOWN = \"\"\"\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-sm btn-{group_button_class} dropdown-toggle\" data-toggle=\"dropdown\">\n    {group_name} <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu pull-right\">\n    {grouped_buttons}\n  </ul>\n</div>\n\"\"\"\n\nHIDDEN_INPUTS = \"\"\"\n<input type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"{csrf_token}\">\n<input type=\"hidden\" name=\"object_pk\" value=\"{object_pk}\">\n<input type=\"hidden\" name=\"object_model_name\" value=\"{object_model_name}\">\n<input type=\"hidden\" name=\"_schedule_type\" value=\"immediately\">\n<input type=\"hidden\" name=\"_return_url\" value=\"{redirect_path}\">\n\"\"\"\n\nNO_CONFIRM_BUTTON = \"\"\"\n<button type=\"submit\" form=\"form_id_{button_id}\" class=\"btn btn-sm btn-{button_class}\" {disabled}>{button_text}</button>\n\"\"\"\n\nNO_CONFIRM_FORM = \"\"\"\n<form id=\"form_id_{button_id}\" action=\"{button_url}\" method=\"post\" class=\"form\">\n  {hidden_inputs}\n</form>\n\"\"\"\n\nCONFIRM_BUTTON = \"\"\"\n<button type=\"button\" class=\"btn btn-sm btn-{button_class}\" data-toggle=\"modal\" data-target=\"#confirm_modal_id_{button_id}\" {disabled}>\n  {button_text}\n</button>\n\"\"\"\n\nCONFIRM_MODAL = \"\"\"\n<div class=\"modal fade\" id=\"confirm_modal_id_{button_id}\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"confirm_modal_label_{button_id}\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n        <h4 class=\"modal-title\" id=\"confirm_modal_label_{button_id}\">Confirmation</h4>\n      </div>\n      <form id=\"form_id_{button_id}\" action=\"{button_url}\" method=\"post\" class=\"form\">\n        <div class=\"modal-body\">\n          {hidden_inputs}\n          Run Job <strong>'{job}'</strong> with object <strong>'{object}'</strong>?\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"submit\" class=\"btn btn-primary\">Confirm</button>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n\"\"\"\n\nSAFE_EMPTY_STR = mark_safe(\"\")  # noqa: S308\n\n\ndef _render_job_button_for_obj(job_button, obj, context, content_type):\n    \"\"\"\n    Helper method for job_buttons templatetag to reduce repetition of code.\n\n    Returns:\n       (str, str): (button_html, form_html)\n    \"\"\"\n    # Pass select context data when rendering the JobButton text as Jinja2\n    button_context = {\n        \"obj\": obj,\n        \"debug\": context.get(\"debug\", False),  # django.template.context_processors.debug\n        \"request\": context[\"request\"],  # django.template.context_processors.request\n        \"user\": context[\"user\"],  # django.contrib.auth.context_processors.auth\n        \"perms\": context[\"perms\"],  # django.contrib.auth.context_processors.auth\n    }\n    try:\n        text_rendered = render_jinja2(job_button.text, button_context)\n    except Exception as exc:\n        return (\n            format_html(\n                '<a class=\"btn btn-sm btn-{}\" disabled=\"disabled\" title=\"{}\"><i class=\"mdi mdi-alert\"></i> {}</a>\\n',\n                \"default\" if not job_button.group_name else \"link\",\n                exc,\n                job_button.name,\n            ),\n            SAFE_EMPTY_STR,\n        )\n\n    if not text_rendered:\n        return (SAFE_EMPTY_STR, SAFE_EMPTY_STR)\n\n    # Disable buttons if the user doesn't have permission to run the underlying Job.\n    has_run_perm = Job.objects.check_perms(context[\"user\"], instance=job_button.job, action=\"run\")\n    hidden_inputs = format_html(\n        HIDDEN_INPUTS,\n        csrf_token=context[\"csrf_token\"],\n        object_pk=obj.pk,\n        object_model_name=f\"{content_type.app_label}.{content_type.model}\",\n        redirect_path=context[\"request\"].path,\n    )\n    template_args = {\n        \"button_id\": job_button.pk,\n        \"button_text\": text_rendered,\n        \"button_class\": job_button.button_class if not job_button.group_name else \"link\",\n        \"button_url\": reverse(\"extras:job_run\", kwargs={\"slug\": job_button.job.slug}),\n        \"object\": obj,\n        \"job\": job_button.job,\n        \"hidden_inputs\": hidden_inputs,\n        \"disabled\": \"\" if has_run_perm else \"disabled\",\n    }\n\n    if job_button.confirmation:\n        return (\n            format_html(CONFIRM_BUTTON, **template_args),\n            format_html(CONFIRM_MODAL, **template_args),\n        )\n    else:\n        return (\n            format_html(NO_CONFIRM_BUTTON, **template_args),\n            format_html(NO_CONFIRM_FORM, **template_args),\n        )\n\n\n@register.simple_tag(takes_context=True)\ndef job_buttons(context, obj):\n    \"\"\"\n    Render all applicable job buttons for the given object.\n    \"\"\"\n    content_type = ContentType.objects.get_for_model(obj)\n    # We will enforce \"run\" permission later in deciding which buttons to show as disabled.\n    buttons = JobButton.objects.filter(content_types=content_type)\n    if not buttons:\n        return SAFE_EMPTY_STR\n\n    buttons_html = forms_html = SAFE_EMPTY_STR\n    group_names = OrderedDict()\n\n    for jb in buttons:\n        # Organize job buttons by group for later processing\n        if jb.group_name:\n            group_names.setdefault(jb.group_name, []).append(jb)\n\n        # Render and add non-grouped buttons\n        else:\n            button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)\n            buttons_html += button_html\n            forms_html += form_html\n\n    # Add grouped buttons to template\n    for group_name, buttons in group_names.items():\n        group_button_class = buttons[0].button_class\n\n        buttons_rendered = SAFE_EMPTY_STR\n\n        for jb in buttons:\n            # Render grouped buttons as list items\n            button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)\n            buttons_rendered += format_html(\"<li>{}</li>\", button_html)\n            forms_html += form_html\n\n        if buttons_rendered:\n            buttons_html += format_html(\n                GROUP_DROPDOWN,\n                group_button_class=group_button_class,\n                group_name=group_name,\n                grouped_buttons=buttons_rendered,\n            )\n\n    # We want all of the buttons first and then any modals and forms so the buttons render properly\n    return buttons_html + forms_html\n", "path": "nautobot/extras/templatetags/job_buttons.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nfrom django import template\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfrom django.utils.safestring import mark_safe\n\nfrom nautobot.extras.models import Job, JobButton\nfrom nautobot.utilities.utils import render_jinja2\n\n\nregister = template.Library()\n\nGROUP_DROPDOWN = \"\"\"\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-sm btn-{group_button_class} dropdown-toggle\" data-toggle=\"dropdown\">\n    {group_name} <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu pull-right\">\n    {grouped_buttons}\n  </ul>\n</div>\n\"\"\"\n\nHIDDEN_INPUTS = \"\"\"\n<input type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"{csrf_token}\">\n<input type=\"hidden\" name=\"object_pk\" value=\"{object_pk}\">\n<input type=\"hidden\" name=\"object_model_name\" value=\"{object_model_name}\">\n<input type=\"hidden\" name=\"_schedule_type\" value=\"immediately\">\n<input type=\"hidden\" name=\"_return_url\" value=\"{redirect_path}\">\n<input type=\"hidden\" name=\"_commit\" value=\"on\">\n\"\"\"\n\nNO_CONFIRM_BUTTON = \"\"\"\n<button type=\"submit\" form=\"form_id_{button_id}\" class=\"btn btn-sm btn-{button_class}\" {disabled}>{button_text}</button>\n\"\"\"\n\nNO_CONFIRM_FORM = \"\"\"\n<form id=\"form_id_{button_id}\" action=\"{button_url}\" method=\"post\" class=\"form\">\n  {hidden_inputs}\n</form>\n\"\"\"\n\nCONFIRM_BUTTON = \"\"\"\n<button type=\"button\" class=\"btn btn-sm btn-{button_class}\" data-toggle=\"modal\" data-target=\"#confirm_modal_id_{button_id}\" {disabled}>\n  {button_text}\n</button>\n\"\"\"\n\nCONFIRM_MODAL = \"\"\"\n<div class=\"modal fade\" id=\"confirm_modal_id_{button_id}\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"confirm_modal_label_{button_id}\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n        <h4 class=\"modal-title\" id=\"confirm_modal_label_{button_id}\">Confirmation</h4>\n      </div>\n      <form id=\"form_id_{button_id}\" action=\"{button_url}\" method=\"post\" class=\"form\">\n        <div class=\"modal-body\">\n          {hidden_inputs}\n          Run Job <strong>'{job}'</strong> with object <strong>'{object}'</strong>?\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"submit\" class=\"btn btn-primary\">Confirm</button>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n\"\"\"\n\nSAFE_EMPTY_STR = mark_safe(\"\")  # noqa: S308\n\n\ndef _render_job_button_for_obj(job_button, obj, context, content_type):\n    \"\"\"\n    Helper method for job_buttons templatetag to reduce repetition of code.\n\n    Returns:\n       (str, str): (button_html, form_html)\n    \"\"\"\n    # Pass select context data when rendering the JobButton text as Jinja2\n    button_context = {\n        \"obj\": obj,\n        \"debug\": context.get(\"debug\", False),  # django.template.context_processors.debug\n        \"request\": context[\"request\"],  # django.template.context_processors.request\n        \"user\": context[\"user\"],  # django.contrib.auth.context_processors.auth\n        \"perms\": context[\"perms\"],  # django.contrib.auth.context_processors.auth\n    }\n    try:\n        text_rendered = render_jinja2(job_button.text, button_context)\n    except Exception as exc:\n        return (\n            format_html(\n                '<a class=\"btn btn-sm btn-{}\" disabled=\"disabled\" title=\"{}\"><i class=\"mdi mdi-alert\"></i> {}</a>\\n',\n                \"default\" if not job_button.group_name else \"link\",\n                exc,\n                job_button.name,\n            ),\n            SAFE_EMPTY_STR,\n        )\n\n    if not text_rendered:\n        return (SAFE_EMPTY_STR, SAFE_EMPTY_STR)\n\n    # Disable buttons if the user doesn't have permission to run the underlying Job.\n    has_run_perm = Job.objects.check_perms(context[\"user\"], instance=job_button.job, action=\"run\")\n    hidden_inputs = format_html(\n        HIDDEN_INPUTS,\n        csrf_token=context[\"csrf_token\"],\n        object_pk=obj.pk,\n        object_model_name=f\"{content_type.app_label}.{content_type.model}\",\n        redirect_path=context[\"request\"].path,\n    )\n    template_args = {\n        \"button_id\": job_button.pk,\n        \"button_text\": text_rendered,\n        \"button_class\": job_button.button_class if not job_button.group_name else \"link\",\n        \"button_url\": reverse(\"extras:job_run\", kwargs={\"slug\": job_button.job.slug}),\n        \"object\": obj,\n        \"job\": job_button.job,\n        \"hidden_inputs\": hidden_inputs,\n        \"disabled\": \"\" if has_run_perm else \"disabled\",\n    }\n\n    if job_button.confirmation:\n        return (\n            format_html(CONFIRM_BUTTON, **template_args),\n            format_html(CONFIRM_MODAL, **template_args),\n        )\n    else:\n        return (\n            format_html(NO_CONFIRM_BUTTON, **template_args),\n            format_html(NO_CONFIRM_FORM, **template_args),\n        )\n\n\n@register.simple_tag(takes_context=True)\ndef job_buttons(context, obj):\n    \"\"\"\n    Render all applicable job buttons for the given object.\n    \"\"\"\n    content_type = ContentType.objects.get_for_model(obj)\n    # We will enforce \"run\" permission later in deciding which buttons to show as disabled.\n    buttons = JobButton.objects.filter(content_types=content_type)\n    if not buttons:\n        return SAFE_EMPTY_STR\n\n    buttons_html = forms_html = SAFE_EMPTY_STR\n    group_names = OrderedDict()\n\n    for jb in buttons:\n        # Organize job buttons by group for later processing\n        if jb.group_name:\n            group_names.setdefault(jb.group_name, []).append(jb)\n\n        # Render and add non-grouped buttons\n        else:\n            button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)\n            buttons_html += button_html\n            forms_html += form_html\n\n    # Add grouped buttons to template\n    for group_name, buttons in group_names.items():\n        group_button_class = buttons[0].button_class\n\n        buttons_rendered = SAFE_EMPTY_STR\n\n        for jb in buttons:\n            # Render grouped buttons as list items\n            button_html, form_html = _render_job_button_for_obj(jb, obj, context, content_type)\n            if button_html:\n                buttons_rendered += format_html(\"<li>{}</li>\", button_html)\n            if form_html:\n                forms_html += form_html\n\n        if buttons_rendered:\n            buttons_html += format_html(\n                GROUP_DROPDOWN,\n                group_button_class=group_button_class,\n                group_name=group_name,\n                grouped_buttons=buttons_rendered,\n            )\n\n    # We want all of the buttons first and then any modals and forms so the buttons render properly\n    return buttons_html + forms_html\n", "path": "nautobot/extras/templatetags/job_buttons.py"}]}