Просмотр исходного кода

Validate string model labels and stop catching AttributeError in job result URL functions; clarify docs

Jeremy Stretch 1 день назад
Родитель
Сommit
9d92cda451

+ 1 - 1
docs/development/application-registry.md

@@ -22,7 +22,7 @@ A dictionary mapping each model (identified by its app and label) to its filters
 
 ### `job_result_urls`
 
-A dictionary mapping each model (identified by its app and label) and optional job name to a function which returns the URL of the results page for background jobs associated with objects of that model. These functions are registered using the `register_job_result_url()` decorator in `core.utils`. (See the [plugin development documentation](../plugins/development/background-jobs.md#result-urls) for details.)
+A dictionary mapping `(<app_label>.<model_name>, <job_name>)` tuples to functions which return the URL of the results page for background jobs associated with objects of that model and, unless the job name is `None`, having that name. These functions are registered using the `register_job_result_url()` decorator in `core.utils`. (See the [plugin development documentation](../plugins/development/background-jobs.md#result-urls) for details.)
 
 ### `model_features`
 

+ 1 - 1
docs/plugins/development/background-jobs.md

@@ -95,7 +95,7 @@ class MyModel(JobsMixin, NetBoxModel):
 
 ### Result URLs
 
-By default, links to a job (such as those in the jobs list and in job completion notifications) lead to its generic view. A plugin which provides its own page for presenting the results of jobs associated with one of its models can register a function which returns the URL of that page, using the `register_job_result_url()` decorator from `core.utils`. The model may be passed as a class or as a string in the form `<app_label>.<model_name>`. The function accepts a Job instance and returns either a URL, or `None` to decline. If the function raises `NoReverseMatch`, `ObjectDoesNotExist`, or `AttributeError`, a warning is logged and it is treated as having declined.
+By default, links to a job (such as those in the jobs list and in job completion notifications) lead to its generic view. A plugin which provides its own page for presenting the results of jobs associated with one of its models can register a function which returns the URL of that page, using the `register_job_result_url()` decorator from `core.utils`. The model may be passed as a class or as a string in the form `<app_label>.<model_name>`. The function accepts a Job instance and returns either a URL, or `None` to decline. If the function raises `NoReverseMatch` or `ObjectDoesNotExist`, a warning is logged and it is treated as having declined. Note that the object associated with a job may have been deleted, in which case `job.object` is `None`. The URL is computed once and cached on the Job instance, so it won't reflect later changes to the job (such as its status).
 
 Optionally, a job name may be specified to restrict the function to jobs having that name. A function registered for a specific job name takes precedence over one registered for the model alone, which is consulted if the former returns `None`. The generic view is used if no function returns a URL. Only one function may be registered for each combination of model and job name.
 

+ 7 - 2
netbox/core/tests/test_utils.py

@@ -41,6 +41,11 @@ class JobResultURLTestCase(TestCase):
         with self.assertRaises(ImproperlyConfigured):
             register_job_result_url('core.datasource', job_name='Sync')(lambda job: None)
 
+    def test_register_invalid_label(self):
+        for label in ('datasource', 'core.datasource.foo'):
+            with self.subTest(label=label), self.assertRaises(ImproperlyConfigured):
+                register_job_result_url(label)
+
     def test_no_object(self):
         register_job_result_url(DataSource)(lambda job: '/foo/')
 
@@ -83,9 +88,9 @@ class JobResultURLTestCase(TestCase):
     def test_unexpected_exception(self):
         @register_job_result_url(DataSource)
         def func(job):
-            raise ValueError
+            raise AttributeError
 
-        with self.assertRaises(ValueError):
+        with self.assertRaises(AttributeError):
             self.get_job(DataSource).get_absolute_url()
 
     def test_url_cached(self):

+ 7 - 2
netbox/core/utils.py

@@ -229,7 +229,12 @@ def register_job_result_url(model, job_name=None):
         def my_job_result_url(job):
             return reverse('plugins:myplugin:mymodel_results', args=[job.object_id])
     """
-    label = model.lower() if isinstance(model, str) else model._meta.label_lower
+    if isinstance(model, str):
+        if model.count('.') != 1:
+            raise ImproperlyConfigured(f"Invalid model label {model!r}: must be in the form <app_label>.<model_name>.")
+        label = model.lower()
+    else:
+        label = model._meta.label_lower
     key = (label, job_name)
 
     def _wrapper(func):
@@ -255,7 +260,7 @@ def get_job_result_url(job):
             try:
                 if url := func(job):
                     return url
-            except (NoReverseMatch, ObjectDoesNotExist, AttributeError) as e:
+            except (NoReverseMatch, ObjectDoesNotExist) as e:
                 # A failing function must not break the rendering of jobs lists or notifications.
                 logger.warning(
                     "Failed to resolve the result URL for job %s using %r: %s", job.pk, func, e,