We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
The command 'nautobot-server celery result task_id' says "Cannot resolve keyword 'task_id' into field"
### Environment
* Nautobot version : v2.1.4
* Python version: 3.10.12
* Database platform, version: psql (PostgreSQL) 14.11 (Ubuntu 14.11-0ubuntu0.22.04.1)
* Middleware(s): NA

### Steps to Reproduce
1. open terminal as nautobot user 
2. enter command :- 'nautobot-server celery result 505........89f' where the last argument would be task_id

### Expected Behavior
I expect to see the result of the task whose task_id is mentioned.

### Observed Behavior
Instead the following exception is returned
```
Traceback (most recent call last):
  File "/opt/nautobot/bin/nautobot-server", line 8, in <module>
    sys.exit(main())
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/cli/__init__.py", line 52, in main
    run_app(
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/runner/runner.py", line 297, in run_app
    management.execute_from_command_line([runner_name, command, *command_args])
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/management/commands/celery.py", line 23, in run_from_argv
    celery_main.main()
  File "/opt/nautobot/lib/python3.10/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
  File "/opt/nautobot/lib/python3.10/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/opt/nautobot/lib/python3.10/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/opt/nautobot/lib/python3.10/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/celery/bin/base.py", line 134, in caller
    return f(ctx, *args, **kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/celery/bin/result.py", line 27, in result
    value = task_result.traceback if traceback else task_result.get()
  File "/opt/nautobot/lib/python3.10/site-packages/celery/result.py", line 251, in get
    return self.backend.wait_for_pending(
  File "/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py", line 755, in wait_for_pending
    meta = self.wait_for(
  File "/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py", line 782, in wait_for
    meta = self.get_task_meta(task_id)
  File "/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py", line 608, in get_task_meta
    meta = self._get_task_meta_for(task_id)
  File "/opt/nautobot/lib/python3.10/site-packages/django_celery_results/backends/database.py", line 137, in _get_task_meta_for
    obj = self.TaskModel._default_manager.get_task(task_id)
  File "/opt/nautobot/lib/python3.10/site-packages/django_celery_results/managers.py", line 111, in get_task
    return self.get(task_id=task_id)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py", line 424, in get
    clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/models/querysets.py", line 92, in filter
    return super().filter(*args, **self.split_composite_key_into_kwargs(composite_key, **kwargs))
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py", line 941, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py", line 961, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py", line 968, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1416, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1435, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1309, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1135, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
  File "/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1562, in names_to_path
    raise FieldError("Cannot resolve keyword '%s' into field. "
django.core.exceptions.FieldError: Cannot resolve keyword 'task_id' into field. Choices are: _custom_field_data, celery_kwargs, date_created, date_done, files, id, job_log_entries, job_model, job_model_id, meta, name, result, scheduled_job, scheduled_job_id, status, task_args, task_kwargs, task_name, traceback, user, user_id, worker
```

![image (1)](https://github.com/nautobot/nautobot/assets/124126542/17b835f0-7f9c-4dfc-b541-45da4509db78)
Here is the image which shows usage of the command

--- 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/managers.py`
Content:
```
1 from celery import states
2 from django.utils import timezone
3 from django_celery_beat.managers import ExtendedManager
4 from django_celery_results.managers import TaskResultManager, transaction_retry
5 
6 from nautobot.core.models import BaseManager
7 from nautobot.core.models.querysets import RestrictedQuerySet
8 
9 
10 class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResultManager):
11     @transaction_retry(max_retries=2)
12     def store_result(
13         self,
14         task_id,
15         result,
16         status,
17         traceback=None,
18         meta=None,
19         periodic_task_name=None,
20         task_name=None,
21         task_args=None,
22         task_kwargs=None,
23         celery_kwargs=None,
24         job_model_id=None,
25         scheduled_job_id=None,
26         worker=None,
27         user_id=None,
28         using=None,
29         content_type=None,
30         content_encoding=None,
31     ):
32         """
33         Store the result and status of a Celery task.
34 
35         This overloads default model options provided by `django-celery-results` to manage custom
36         behaviors for integration with Nautobot. Specifically these changes are:
37 
38         - Ignore incoming `content_type` and `content_encoding` fields as Nautobot explicitly only uses
39           JSON utf-8 encoding for Celery messages.
40         - Ensure that `name` is set to `task_name` if not otherwise set.
41         - Only set `date_done` if the task has reached a ready state (execution completed),
42           otherwise keep it null.
43 
44         Args:
45             task_id (uuid): UUID of task.
46             periodic_task_name (str): Celery periodic task name. (not used by Nautobot)
47             task_name (str): Celery task name.
48             task_args (list): Task arguments.
49             task_kwargs (dict): Task kwargs.
50             celery_kwargs (dict): Celery kwargs (kwargs passed to apply_async).
51             job_model_id (uuid): UUID of the Job model instance of the task being run.
52             scheduled_job_id (uuid): UUID of the ScheduledJob model instance that initiated
53                 this task, or None if not scheduled.
54             result (obj): Return value of the task, or an exception instance raised
55                 by the task.
56             status (str): Task status. See `JobResultStatusChoices` for a list of possible status
57                 values.
58             worker (str): Worker that executes the task.
59             user_id (uuid): UUID of the user that initiated the task.
60             using (str): Django database connection to use.
61             traceback (str): Traceback string taken at the point of exception (only passed if the
62                 task failed).
63             meta (json): JSON-serialized result meta data (this contains e.g. children).
64             content_type: Ignored. Kept for interface compatibility.
65             content_encoding: Ignored. Kept for interface compatibility.
66 
67         Returns:
68             JobResult
69         """
70 
71         # Prepare the fields for creating/updating a `JobResult`.
72         fields = {
73             "status": status,
74             "result": result,
75             "traceback": traceback,
76             "meta": meta,
77             "date_done": None,
78             "task_name": task_name,
79             "task_args": task_args,
80             "task_kwargs": task_kwargs,
81             "celery_kwargs": celery_kwargs,
82             "job_model_id": job_model_id,
83             "scheduled_job_id": scheduled_job_id,
84             "user_id": user_id,
85             "worker": worker,
86         }
87         from nautobot.extras.models.jobs import Job
88 
89         # Need to have a try/except block here
90         # because sometimes job_model_id will be None.
91         try:
92             job = Job.objects.get(id=job_model_id)
93             if job.has_sensitive_variables:
94                 del fields["task_args"]
95                 del fields["task_kwargs"]
96         except Job.DoesNotExist:
97             pass
98 
99         obj, created = self.using(using).get_or_create(id=task_id, defaults=fields)
100 
101         if not created:
102             # Make sure `date_done` is allowed to stay null until the task reacheas a ready state.
103             #
104             # Default behavior in `django-celery-results` has this field as a
105             # `DateField(auto_now=True)` which just automatically updates the `date_done` field on every
106             # state transition. This is different than Celery's default behavior (and the current
107             # behavior of Nautobot) to keep it null until there is a state transition to a ready state
108             # (e.g. `SUCCESS`, `REVOKED`, `FAILURE`).
109             if fields["status"] in states.READY_STATES:
110                 fields["date_done"] = timezone.now()
111 
112             # Always make sure the Job `name` is set.
113             if not obj.name and fields["task_name"]:
114                 fields["name"] = fields["task_name"]
115 
116             # Set the field values on the model instance.
117             for k, v in fields.items():
118                 setattr(obj, k, v)
119 
120             obj.save(using=using)
121 
122         return obj
123 
124 
125 class ScheduledJobsManager(BaseManager.from_queryset(RestrictedQuerySet), ExtendedManager):
126     pass
127
```
--- 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/managers.py b/nautobot/extras/managers.py
--- a/nautobot/extras/managers.py
+++ b/nautobot/extras/managers.py
@@ -8,6 +8,20 @@
 
 
 class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResultManager):
+    def get_task(self, task_id):
+        """Get result for task by ``task_id``.
+
+        This overloads `TaskResultManager.get_task` provided by `django-celery-results` to manage custom
+        behaviors for integration with Nautobot.
+        """
+        try:
+            return self.get(id=task_id)
+        except self.model.DoesNotExist:
+            if self._last_id == task_id:
+                self.warn_if_repeatable_read()
+            self._last_id = task_id
+            return self.model(id=task_id)
+
     @transaction_retry(max_retries=2)
     def store_result(
         self,

{"golden_diff": "diff --git a/nautobot/extras/managers.py b/nautobot/extras/managers.py\n--- a/nautobot/extras/managers.py\n+++ b/nautobot/extras/managers.py\n@@ -8,6 +8,20 @@\n \n \n class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResultManager):\n+    def get_task(self, task_id):\n+        \"\"\"Get result for task by ``task_id``.\n+\n+        This overloads `TaskResultManager.get_task` provided by `django-celery-results` to manage custom\n+        behaviors for integration with Nautobot.\n+        \"\"\"\n+        try:\n+            return self.get(id=task_id)\n+        except self.model.DoesNotExist:\n+            if self._last_id == task_id:\n+                self.warn_if_repeatable_read()\n+            self._last_id = task_id\n+            return self.model(id=task_id)\n+\n     @transaction_retry(max_retries=2)\n     def store_result(\n         self,\n", "issue": "The command 'nautobot-server celery result task_id' says \"Cannot resolve keyword 'task_id' into field\"\n### Environment\r\n* Nautobot version : v2.1.4\r\n* Python version: 3.10.12\r\n* Database platform, version: psql (PostgreSQL) 14.11 (Ubuntu 14.11-0ubuntu0.22.04.1)\r\n* Middleware(s): NA\r\n\r\n### Steps to Reproduce\r\n1. open terminal as nautobot user \r\n2. enter command :- 'nautobot-server celery result 505........89f' where the last argument would be task_id\r\n\r\n### Expected Behavior\r\nI expect to see the result of the task whose task_id is mentioned.\r\n\r\n### Observed Behavior\r\nInstead the following exception is returned\r\n```\r\nTraceback (most recent call last):\r\n  File \"/opt/nautobot/bin/nautobot-server\", line 8, in <module>\r\n    sys.exit(main())\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/cli/__init__.py\", line 52, in main\r\n    run_app(\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/runner/runner.py\", line 297, in run_app\r\n    management.execute_from_command_line([runner_name, command, *command_args])\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py\", line 419, in execute_from_command_line\r\n    utility.execute()\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py\", line 413, in execute\r\n    self.fetch_command(subcommand).run_from_argv(self.argv)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/management/commands/celery.py\", line 23, in run_from_argv\r\n    celery_main.main()\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/click/core.py\", line 1078, in main\r\n    rv = self.invoke(ctx)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/click/core.py\", line 1688, in invoke\r\n    return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/click/core.py\", line 1434, in invoke\r\n    return ctx.invoke(self.callback, **ctx.params)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/click/core.py\", line 783, in invoke\r\n    return __callback(*args, **kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/click/decorators.py\", line 33, in new_func\r\n    return f(get_current_context(), *args, **kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/bin/base.py\", line 134, in caller\r\n    return f(ctx, *args, **kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/bin/result.py\", line 27, in result\r\n    value = task_result.traceback if traceback else task_result.get()\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/result.py\", line 251, in get\r\n    return self.backend.wait_for_pending(\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py\", line 755, in wait_for_pending\r\n    meta = self.wait_for(\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py\", line 782, in wait_for\r\n    meta = self.get_task_meta(task_id)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/celery/backends/base.py\", line 608, in get_task_meta\r\n    meta = self._get_task_meta_for(task_id)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django_celery_results/backends/database.py\", line 137, in _get_task_meta_for\r\n    obj = self.TaskModel._default_manager.get_task(task_id)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django_celery_results/managers.py\", line 111, in get_task\r\n    return self.get(task_id=task_id)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/manager.py\", line 85, in manager_method\r\n    return getattr(self.get_queryset(), name)(*args, **kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py\", line 424, in get\r\n    clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/models/querysets.py\", line 92, in filter\r\n    return super().filter(*args, **self.split_composite_key_into_kwargs(composite_key, **kwargs))\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py\", line 941, in filter\r\n    return self._filter_or_exclude(False, args, kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py\", line 961, in _filter_or_exclude\r\n    clone._filter_or_exclude_inplace(negate, args, kwargs)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/query.py\", line 968, in _filter_or_exclude_inplace\r\n    self._query.add_q(Q(*args, **kwargs))\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py\", line 1416, in add_q\r\n    clause, _ = self._add_q(q_object, self.used_aliases)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py\", line 1435, in _add_q\r\n    child_clause, needed_inner = self.build_filter(\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py\", line 1309, in build_filter\r\n    lookups, parts, reffed_expression = self.solve_lookup_type(arg)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py\", line 1135, in solve_lookup_type\r\n    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/db/models/sql/query.py\", line 1562, in names_to_path\r\n    raise FieldError(\"Cannot resolve keyword '%s' into field. \"\r\ndjango.core.exceptions.FieldError: Cannot resolve keyword 'task_id' into field. Choices are: _custom_field_data, celery_kwargs, date_created, date_done, files, id, job_log_entries, job_model, job_model_id, meta, name, result, scheduled_job, scheduled_job_id, status, task_args, task_kwargs, task_name, traceback, user, user_id, worker\r\n```\r\n\r\n![image (1)](https://github.com/nautobot/nautobot/assets/124126542/17b835f0-7f9c-4dfc-b541-45da4509db78)\r\nHere is the image which shows usage of the command\n", "before_files": [{"content": "from celery import states\nfrom django.utils import timezone\nfrom django_celery_beat.managers import ExtendedManager\nfrom django_celery_results.managers import TaskResultManager, transaction_retry\n\nfrom nautobot.core.models import BaseManager\nfrom nautobot.core.models.querysets import RestrictedQuerySet\n\n\nclass JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResultManager):\n    @transaction_retry(max_retries=2)\n    def store_result(\n        self,\n        task_id,\n        result,\n        status,\n        traceback=None,\n        meta=None,\n        periodic_task_name=None,\n        task_name=None,\n        task_args=None,\n        task_kwargs=None,\n        celery_kwargs=None,\n        job_model_id=None,\n        scheduled_job_id=None,\n        worker=None,\n        user_id=None,\n        using=None,\n        content_type=None,\n        content_encoding=None,\n    ):\n        \"\"\"\n        Store the result and status of a Celery task.\n\n        This overloads default model options provided by `django-celery-results` to manage custom\n        behaviors for integration with Nautobot. Specifically these changes are:\n\n        - Ignore incoming `content_type` and `content_encoding` fields as Nautobot explicitly only uses\n          JSON utf-8 encoding for Celery messages.\n        - Ensure that `name` is set to `task_name` if not otherwise set.\n        - Only set `date_done` if the task has reached a ready state (execution completed),\n          otherwise keep it null.\n\n        Args:\n            task_id (uuid): UUID of task.\n            periodic_task_name (str): Celery periodic task name. (not used by Nautobot)\n            task_name (str): Celery task name.\n            task_args (list): Task arguments.\n            task_kwargs (dict): Task kwargs.\n            celery_kwargs (dict): Celery kwargs (kwargs passed to apply_async).\n            job_model_id (uuid): UUID of the Job model instance of the task being run.\n            scheduled_job_id (uuid): UUID of the ScheduledJob model instance that initiated\n                this task, or None if not scheduled.\n            result (obj): Return value of the task, or an exception instance raised\n                by the task.\n            status (str): Task status. See `JobResultStatusChoices` for a list of possible status\n                values.\n            worker (str): Worker that executes the task.\n            user_id (uuid): UUID of the user that initiated the task.\n            using (str): Django database connection to use.\n            traceback (str): Traceback string taken at the point of exception (only passed if the\n                task failed).\n            meta (json): JSON-serialized result meta data (this contains e.g. children).\n            content_type: Ignored. Kept for interface compatibility.\n            content_encoding: Ignored. Kept for interface compatibility.\n\n        Returns:\n            JobResult\n        \"\"\"\n\n        # Prepare the fields for creating/updating a `JobResult`.\n        fields = {\n            \"status\": status,\n            \"result\": result,\n            \"traceback\": traceback,\n            \"meta\": meta,\n            \"date_done\": None,\n            \"task_name\": task_name,\n            \"task_args\": task_args,\n            \"task_kwargs\": task_kwargs,\n            \"celery_kwargs\": celery_kwargs,\n            \"job_model_id\": job_model_id,\n            \"scheduled_job_id\": scheduled_job_id,\n            \"user_id\": user_id,\n            \"worker\": worker,\n        }\n        from nautobot.extras.models.jobs import Job\n\n        # Need to have a try/except block here\n        # because sometimes job_model_id will be None.\n        try:\n            job = Job.objects.get(id=job_model_id)\n            if job.has_sensitive_variables:\n                del fields[\"task_args\"]\n                del fields[\"task_kwargs\"]\n        except Job.DoesNotExist:\n            pass\n\n        obj, created = self.using(using).get_or_create(id=task_id, defaults=fields)\n\n        if not created:\n            # Make sure `date_done` is allowed to stay null until the task reacheas a ready state.\n            #\n            # Default behavior in `django-celery-results` has this field as a\n            # `DateField(auto_now=True)` which just automatically updates the `date_done` field on every\n            # state transition. This is different than Celery's default behavior (and the current\n            # behavior of Nautobot) to keep it null until there is a state transition to a ready state\n            # (e.g. `SUCCESS`, `REVOKED`, `FAILURE`).\n            if fields[\"status\"] in states.READY_STATES:\n                fields[\"date_done\"] = timezone.now()\n\n            # Always make sure the Job `name` is set.\n            if not obj.name and fields[\"task_name\"]:\n                fields[\"name\"] = fields[\"task_name\"]\n\n            # Set the field values on the model instance.\n            for k, v in fields.items():\n                setattr(obj, k, v)\n\n            obj.save(using=using)\n\n        return obj\n\n\nclass ScheduledJobsManager(BaseManager.from_queryset(RestrictedQuerySet), ExtendedManager):\n    pass\n", "path": "nautobot/extras/managers.py"}], "after_files": [{"content": "from celery import states\nfrom django.utils import timezone\nfrom django_celery_beat.managers import ExtendedManager\nfrom django_celery_results.managers import TaskResultManager, transaction_retry\n\nfrom nautobot.core.models import BaseManager\nfrom nautobot.core.models.querysets import RestrictedQuerySet\n\n\nclass JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResultManager):\n    def get_task(self, task_id):\n        \"\"\"Get result for task by ``task_id``.\n\n        This overloads `TaskResultManager.get_task` provided by `django-celery-results` to manage custom\n        behaviors for integration with Nautobot.\n        \"\"\"\n        try:\n            return self.get(id=task_id)\n        except self.model.DoesNotExist:\n            if self._last_id == task_id:\n                self.warn_if_repeatable_read()\n            self._last_id = task_id\n            return self.model(id=task_id)\n\n    @transaction_retry(max_retries=2)\n    def store_result(\n        self,\n        task_id,\n        result,\n        status,\n        traceback=None,\n        meta=None,\n        periodic_task_name=None,\n        task_name=None,\n        task_args=None,\n        task_kwargs=None,\n        celery_kwargs=None,\n        job_model_id=None,\n        scheduled_job_id=None,\n        worker=None,\n        user_id=None,\n        using=None,\n        content_type=None,\n        content_encoding=None,\n    ):\n        \"\"\"\n        Store the result and status of a Celery task.\n\n        This overloads default model options provided by `django-celery-results` to manage custom\n        behaviors for integration with Nautobot. Specifically these changes are:\n\n        - Ignore incoming `content_type` and `content_encoding` fields as Nautobot explicitly only uses\n          JSON utf-8 encoding for Celery messages.\n        - Ensure that `name` is set to `task_name` if not otherwise set.\n        - Only set `date_done` if the task has reached a ready state (execution completed),\n          otherwise keep it null.\n\n        Args:\n            task_id (uuid): UUID of task.\n            periodic_task_name (str): Celery periodic task name. (not used by Nautobot)\n            task_name (str): Celery task name.\n            task_args (list): Task arguments.\n            task_kwargs (dict): Task kwargs.\n            celery_kwargs (dict): Celery kwargs (kwargs passed to apply_async).\n            job_model_id (uuid): UUID of the Job model instance of the task being run.\n            scheduled_job_id (uuid): UUID of the ScheduledJob model instance that initiated\n                this task, or None if not scheduled.\n            result (obj): Return value of the task, or an exception instance raised\n                by the task.\n            status (str): Task status. See `JobResultStatusChoices` for a list of possible status\n                values.\n            worker (str): Worker that executes the task.\n            user_id (uuid): UUID of the user that initiated the task.\n            using (str): Django database connection to use.\n            traceback (str): Traceback string taken at the point of exception (only passed if the\n                task failed).\n            meta (json): JSON-serialized result meta data (this contains e.g. children).\n            content_type: Ignored. Kept for interface compatibility.\n            content_encoding: Ignored. Kept for interface compatibility.\n\n        Returns:\n            JobResult\n        \"\"\"\n\n        # Prepare the fields for creating/updating a `JobResult`.\n        fields = {\n            \"status\": status,\n            \"result\": result,\n            \"traceback\": traceback,\n            \"meta\": meta,\n            \"date_done\": None,\n            \"task_name\": task_name,\n            \"task_args\": task_args,\n            \"task_kwargs\": task_kwargs,\n            \"celery_kwargs\": celery_kwargs,\n            \"job_model_id\": job_model_id,\n            \"scheduled_job_id\": scheduled_job_id,\n            \"user_id\": user_id,\n            \"worker\": worker,\n        }\n        from nautobot.extras.models.jobs import Job\n\n        # Need to have a try/except block here\n        # because sometimes job_model_id will be None.\n        try:\n            job = Job.objects.get(id=job_model_id)\n            if job.has_sensitive_variables:\n                del fields[\"task_args\"]\n                del fields[\"task_kwargs\"]\n        except Job.DoesNotExist:\n            pass\n\n        obj, created = self.using(using).get_or_create(id=task_id, defaults=fields)\n\n        if not created:\n            # Make sure `date_done` is allowed to stay null until the task reacheas a ready state.\n            #\n            # Default behavior in `django-celery-results` has this field as a\n            # `DateField(auto_now=True)` which just automatically updates the `date_done` field on every\n            # state transition. This is different than Celery's default behavior (and the current\n            # behavior of Nautobot) to keep it null until there is a state transition to a ready state\n            # (e.g. `SUCCESS`, `REVOKED`, `FAILURE`).\n            if fields[\"status\"] in states.READY_STATES:\n                fields[\"date_done\"] = timezone.now()\n\n            # Always make sure the Job `name` is set.\n            if not obj.name and fields[\"task_name\"]:\n                fields[\"name\"] = fields[\"task_name\"]\n\n            # Set the field values on the model instance.\n            for k, v in fields.items():\n                setattr(obj, k, v)\n\n            obj.save(using=using)\n\n        return obj\n\n\nclass ScheduledJobsManager(BaseManager.from_queryset(RestrictedQuerySet), ExtendedManager):\n    pass\n", "path": "nautobot/extras/managers.py"}]}