nautobot 2.4.4__py3-none-any.whl → 2.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/__init__.py +19 -3
- nautobot/core/celery/__init__.py +5 -3
- nautobot/core/jobs/__init__.py +3 -2
- nautobot/core/testing/__init__.py +2 -0
- nautobot/core/testing/mixins.py +9 -0
- nautobot/core/tests/test_jobs.py +26 -27
- nautobot/dcim/tests/test_jobs.py +4 -6
- nautobot/extras/choices.py +8 -3
- nautobot/extras/jobs.py +181 -103
- nautobot/extras/management/utils.py +13 -2
- nautobot/extras/models/datasources.py +4 -1
- nautobot/extras/models/jobs.py +20 -17
- nautobot/extras/tables.py +25 -29
- nautobot/extras/test_jobs/atomic_transaction.py +6 -6
- nautobot/extras/test_jobs/fail.py +75 -1
- nautobot/extras/tests/test_api.py +1 -1
- nautobot/extras/tests/test_datasources.py +64 -54
- nautobot/extras/tests/test_jobs.py +69 -62
- nautobot/extras/tests/test_models.py +1 -1
- nautobot/extras/tests/test_relationships.py +5 -5
- nautobot/extras/views.py +7 -1
- nautobot/ipam/forms.py +15 -0
- nautobot/ipam/querysets.py +6 -0
- nautobot/ipam/templates/ipam/rir.html +1 -43
- nautobot/ipam/tests/test_models.py +16 -0
- nautobot/ipam/urls.py +1 -21
- nautobot/ipam/views.py +24 -41
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +43 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +35 -0
- nautobot/project-static/docs/development/jobs/index.html +27 -14
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/version-2.4.html +183 -0
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/METADATA +3 -3
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/RECORD +42 -42
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/NOTICE +0 -0
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/WHEEL +0 -0
- {nautobot-2.4.4.dist-info → nautobot-2.4.5.dist-info}/entry_points.txt +0 -0
|
@@ -1784,7 +1784,7 @@ class RelationshipJobTestCase(RequiredRelationshipTestMixin, TransactionTestCase
|
|
|
1784
1784
|
|
|
1785
1785
|
pk_list = [str(vlan.id) for vlan in vlans]
|
|
1786
1786
|
job_result = self.create_job(pk_list)
|
|
1787
|
-
self.
|
|
1787
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1788
1788
|
error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
1789
1789
|
self.assertIn("VLANs require at least one device, but no devices exist yet.", error_log.message)
|
|
1790
1790
|
|
|
@@ -1793,7 +1793,7 @@ class RelationshipJobTestCase(RequiredRelationshipTestMixin, TransactionTestCase
|
|
|
1793
1793
|
|
|
1794
1794
|
# Try editing all 6 VLANs without adding the required device(fails):
|
|
1795
1795
|
job_result = self.create_job(pk_list)
|
|
1796
|
-
self.
|
|
1796
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1797
1797
|
error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
1798
1798
|
self.assertIn(
|
|
1799
1799
|
'6 VLANs require a device for the required relationship \\"VLANs require at least one Device',
|
|
@@ -1802,7 +1802,7 @@ class RelationshipJobTestCase(RequiredRelationshipTestMixin, TransactionTestCase
|
|
|
1802
1802
|
|
|
1803
1803
|
# Try editing 3 VLANs without adding the required device(fails):
|
|
1804
1804
|
job_result = self.create_job(pk_list[:3])
|
|
1805
|
-
self.
|
|
1805
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1806
1806
|
error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
1807
1807
|
self.assertIn(
|
|
1808
1808
|
'These VLANs require a device for the required relationship \\"VLANs require at least one Device',
|
|
@@ -1813,11 +1813,11 @@ class RelationshipJobTestCase(RequiredRelationshipTestMixin, TransactionTestCase
|
|
|
1813
1813
|
|
|
1814
1814
|
# Try editing 6 VLANs and adding the required device (succeeds):
|
|
1815
1815
|
job_result = self.create_job(pk_list, add_cr_vlans_devices_m2m__source=[str(device_for_association.id)])
|
|
1816
|
-
self.
|
|
1816
|
+
self.assertJobResultStatus(job_result)
|
|
1817
1817
|
|
|
1818
1818
|
# Try editing 6 VLANs and removing the required device (fails):
|
|
1819
1819
|
job_result = self.create_job(pk_list, remove_cr_vlans_devices_m2m__source=[str(device_for_association.id)])
|
|
1820
|
-
self.
|
|
1820
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1821
1821
|
error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
1822
1822
|
self.assertIn(
|
|
1823
1823
|
'6 VLANs require a device for the required relationship \\"VLANs require at least one Device',
|
nautobot/extras/views.py
CHANGED
|
@@ -2062,7 +2062,13 @@ def get_annotated_jobresult_queryset():
|
|
|
2062
2062
|
error_log_count=count_related(
|
|
2063
2063
|
JobLogEntry,
|
|
2064
2064
|
"job_result",
|
|
2065
|
-
filter_dict={
|
|
2065
|
+
filter_dict={
|
|
2066
|
+
"log_level__in": [
|
|
2067
|
+
LogLevelChoices.LOG_FAILURE,
|
|
2068
|
+
LogLevelChoices.LOG_ERROR,
|
|
2069
|
+
LogLevelChoices.LOG_CRITICAL,
|
|
2070
|
+
],
|
|
2071
|
+
},
|
|
2066
2072
|
),
|
|
2067
2073
|
)
|
|
2068
2074
|
)
|
nautobot/ipam/forms.py
CHANGED
|
@@ -262,6 +262,21 @@ class RIRFilterForm(NautobotFilterForm):
|
|
|
262
262
|
)
|
|
263
263
|
|
|
264
264
|
|
|
265
|
+
class RIRBulkEditForm(NautobotBulkEditForm):
|
|
266
|
+
pk = forms.ModelMultipleChoiceField(queryset=RIR.objects.all(), widget=forms.MultipleHiddenInput())
|
|
267
|
+
is_private = forms.NullBooleanField(
|
|
268
|
+
required=False,
|
|
269
|
+
label="Private",
|
|
270
|
+
widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
|
|
271
|
+
)
|
|
272
|
+
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
273
|
+
|
|
274
|
+
class Meta:
|
|
275
|
+
nullable_fields = [
|
|
276
|
+
"description",
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
|
|
265
280
|
#
|
|
266
281
|
# Prefixes
|
|
267
282
|
#
|
nautobot/ipam/querysets.py
CHANGED
|
@@ -405,6 +405,12 @@ class IPAddressQuerySet(BaseNetworkQuerySet):
|
|
|
405
405
|
namespace = kwargs.pop("namespace", None)
|
|
406
406
|
host = kwargs.get("host")
|
|
407
407
|
mask_length = kwargs.get("mask_length")
|
|
408
|
+
address = kwargs.get("address")
|
|
409
|
+
if host is None and address is not None:
|
|
410
|
+
address = netaddr.IPNetwork(address)
|
|
411
|
+
host = str(address.ip)
|
|
412
|
+
mask_length = address.prefixlen
|
|
413
|
+
|
|
408
414
|
# If `host` or `mask_length` is None skip; then there is no way of getting the closest parent;
|
|
409
415
|
if parent is None and host is not None and mask_length is not None:
|
|
410
416
|
if namespace is None:
|
|
@@ -1,44 +1,2 @@
|
|
|
1
1
|
{% extends 'generic/object_retrieve.html' %}
|
|
2
|
-
{%
|
|
3
|
-
|
|
4
|
-
{% block content_left_page %}
|
|
5
|
-
<div class="panel panel-default">
|
|
6
|
-
<div class="panel-heading">
|
|
7
|
-
<strong>RIR</strong>
|
|
8
|
-
</div>
|
|
9
|
-
<table class="table table-hover panel-body attr-table">
|
|
10
|
-
<tr>
|
|
11
|
-
<td>Description</td>
|
|
12
|
-
<td>{{ object.description|placeholder }}</td>
|
|
13
|
-
</tr>
|
|
14
|
-
<tr>
|
|
15
|
-
<td>Private</td>
|
|
16
|
-
<td>{{ object.is_private | render_boolean }}</td>
|
|
17
|
-
</tr>
|
|
18
|
-
<tr>
|
|
19
|
-
<td>Assigned Prefixes</td>
|
|
20
|
-
<td>
|
|
21
|
-
<a href="{% url 'ipam:prefix_list' %}?rir={{ object.name }}">{{ assigned_prefix_table.rows|length }}</a>
|
|
22
|
-
</td>
|
|
23
|
-
</tr>
|
|
24
|
-
</table>
|
|
25
|
-
</div>
|
|
26
|
-
{% endblock content_left_page %}
|
|
27
|
-
|
|
28
|
-
{% block content_full_width_page %}
|
|
29
|
-
<div class="panel panel-default">
|
|
30
|
-
<div class="panel-heading">
|
|
31
|
-
<strong>Assigned Prefixes</strong>
|
|
32
|
-
</div>
|
|
33
|
-
{% include 'inc/table.html' with table=assigned_prefix_table %}
|
|
34
|
-
{% if perms.ipam.add_prefix %}
|
|
35
|
-
<div class="panel-footer text-right noprint">
|
|
36
|
-
<a href="{% url 'ipam:prefix_add' %}?rir={{ object.pk }}" class="btn btn-xs btn-primary">
|
|
37
|
-
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add prefix
|
|
38
|
-
</a>
|
|
39
|
-
</div>
|
|
40
|
-
{% endif %}
|
|
41
|
-
</div>
|
|
42
|
-
{% include 'inc/paginator.html' with paginator=assigned_prefix_table.paginator page=assigned_prefix_table.page %}
|
|
43
|
-
<div class="row"></div>
|
|
44
|
-
{% endblock content_full_width_page %}
|
|
2
|
+
{% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
|
|
@@ -1301,6 +1301,22 @@ class TestIPAddress(ModelTestCases.BaseModelTestCase):
|
|
|
1301
1301
|
str(err.exception),
|
|
1302
1302
|
)
|
|
1303
1303
|
|
|
1304
|
+
def test_get_or_create_address_kwarg(self):
|
|
1305
|
+
status = Status.objects.get(name="Active")
|
|
1306
|
+
namespace = Namespace.objects.create(name="Test IPAddress get_or_create with address kwarg")
|
|
1307
|
+
Prefix.objects.create(prefix="10.0.0.0/24", namespace=namespace, status=status)
|
|
1308
|
+
ip_address, created = IPAddress.objects.get_or_create(
|
|
1309
|
+
address="10.0.0.40/32", namespace=namespace, defaults={"status": status}
|
|
1310
|
+
)
|
|
1311
|
+
self.assertEqual(ip_address.host, "10.0.0.40")
|
|
1312
|
+
self.assertEqual(ip_address.mask_length, 32)
|
|
1313
|
+
self.assertTrue(created)
|
|
1314
|
+
_, created = IPAddress.objects.get_or_create(
|
|
1315
|
+
address="10.0.0.40/32", namespace=namespace, defaults={"status": status}
|
|
1316
|
+
)
|
|
1317
|
+
self.assertFalse(created)
|
|
1318
|
+
self.assertTrue(IPAddress.objects.filter(address="10.0.0.40/32", parent__namespace=namespace).exists())
|
|
1319
|
+
|
|
1304
1320
|
def test_create_field_population(self):
|
|
1305
1321
|
"""Test that the various ways of creating an IPAddress result in correctly populated fields."""
|
|
1306
1322
|
if self.namespace != get_default_namespace():
|
nautobot/ipam/urls.py
CHANGED
|
@@ -7,7 +7,6 @@ from . import views
|
|
|
7
7
|
from .models import (
|
|
8
8
|
IPAddress,
|
|
9
9
|
Prefix,
|
|
10
|
-
RIR,
|
|
11
10
|
Service,
|
|
12
11
|
VLAN,
|
|
13
12
|
VLANGroup,
|
|
@@ -20,28 +19,9 @@ router.register("ip-address-to-interface", views.IPAddressToInterfaceUIViewSet)
|
|
|
20
19
|
router.register("namespaces", views.NamespaceUIViewSet)
|
|
21
20
|
router.register("route-targets", views.RouteTargetUIViewSet)
|
|
22
21
|
router.register("vrfs", views.VRFUIViewSet)
|
|
22
|
+
router.register("rirs", views.RIRUIViewSet)
|
|
23
23
|
|
|
24
24
|
urlpatterns = [
|
|
25
|
-
# RIRs
|
|
26
|
-
path("rirs/", views.RIRListView.as_view(), name="rir_list"),
|
|
27
|
-
path("rirs/add/", views.RIREditView.as_view(), name="rir_add"),
|
|
28
|
-
path("rirs/import/", views.RIRBulkImportView.as_view(), name="rir_import"), # 3.0 TODO: remove, unused
|
|
29
|
-
path("rirs/delete/", views.RIRBulkDeleteView.as_view(), name="rir_bulk_delete"),
|
|
30
|
-
path("rirs/<uuid:pk>/", views.RIRView.as_view(), name="rir"),
|
|
31
|
-
path("rirs/<uuid:pk>/edit/", views.RIREditView.as_view(), name="rir_edit"),
|
|
32
|
-
path("rirs/<uuid:pk>/delete/", views.RIRDeleteView.as_view(), name="rir_delete"),
|
|
33
|
-
path(
|
|
34
|
-
"rirs/<uuid:pk>/changelog/",
|
|
35
|
-
ObjectChangeLogView.as_view(),
|
|
36
|
-
name="rir_changelog",
|
|
37
|
-
kwargs={"model": RIR},
|
|
38
|
-
),
|
|
39
|
-
path(
|
|
40
|
-
"rirs/<uuid:pk>/notes/",
|
|
41
|
-
ObjectNotesView.as_view(),
|
|
42
|
-
name="rir_notes",
|
|
43
|
-
kwargs={"model": RIR},
|
|
44
|
-
),
|
|
45
25
|
# Namespaces
|
|
46
26
|
path(
|
|
47
27
|
"namespaces/<uuid:pk>/ip-addresses/",
|
nautobot/ipam/views.py
CHANGED
|
@@ -325,49 +325,32 @@ class RouteTargetUIViewSet(NautobotUIViewSet):
|
|
|
325
325
|
#
|
|
326
326
|
|
|
327
327
|
|
|
328
|
-
class
|
|
328
|
+
class RIRUIViewSet(NautobotUIViewSet):
|
|
329
|
+
bulk_update_form_class = forms.RIRBulkEditForm
|
|
330
|
+
filterset_class = filters.RIRFilterSet
|
|
331
|
+
filterset_form_class = forms.RIRFilterForm
|
|
332
|
+
form_class = forms.RIRForm
|
|
329
333
|
queryset = RIR.objects.all()
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
table = tables.RIRTable
|
|
334
|
+
serializer_class = serializers.RIRSerializer
|
|
335
|
+
table_class = tables.RIRTable
|
|
333
336
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
class RIREditView(generic.ObjectEditView):
|
|
354
|
-
queryset = RIR.objects.all()
|
|
355
|
-
model_form = forms.RIRForm
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
class RIRDeleteView(generic.ObjectDeleteView):
|
|
359
|
-
queryset = RIR.objects.all()
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
class RIRBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
|
|
363
|
-
queryset = RIR.objects.all()
|
|
364
|
-
table = tables.RIRTable
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
class RIRBulkDeleteView(generic.BulkDeleteView):
|
|
368
|
-
queryset = RIR.objects.all()
|
|
369
|
-
filterset = filters.RIRFilterSet
|
|
370
|
-
table = tables.RIRTable
|
|
337
|
+
object_detail_content = object_detail.ObjectDetailContent(
|
|
338
|
+
panels=(
|
|
339
|
+
object_detail.ObjectFieldsPanel(
|
|
340
|
+
section=SectionChoices.LEFT_HALF,
|
|
341
|
+
weight=100,
|
|
342
|
+
fields="__all__",
|
|
343
|
+
),
|
|
344
|
+
object_detail.ObjectsTablePanel(
|
|
345
|
+
section=SectionChoices.FULL_WIDTH,
|
|
346
|
+
weight=100,
|
|
347
|
+
table_title="Assigned Prefixes",
|
|
348
|
+
table_class=tables.PrefixTable,
|
|
349
|
+
table_filter="rir",
|
|
350
|
+
hide_hierarchy_ui=True,
|
|
351
|
+
),
|
|
352
|
+
),
|
|
353
|
+
)
|
|
371
354
|
|
|
372
355
|
|
|
373
356
|
#
|
|
@@ -7419,6 +7419,15 @@
|
|
|
7419
7419
|
</span>
|
|
7420
7420
|
</a>
|
|
7421
7421
|
|
|
7422
|
+
</li>
|
|
7423
|
+
|
|
7424
|
+
<li class="md-nav__item">
|
|
7425
|
+
<a href="#nautobot.apps.jobs.BaseJob.fail" class="md-nav__link">
|
|
7426
|
+
<span class="md-ellipsis">
|
|
7427
|
+
fail
|
|
7428
|
+
</span>
|
|
7429
|
+
</a>
|
|
7430
|
+
|
|
7422
7431
|
</li>
|
|
7423
7432
|
|
|
7424
7433
|
<li class="md-nav__item">
|
|
@@ -9776,6 +9785,15 @@
|
|
|
9776
9785
|
</span>
|
|
9777
9786
|
</a>
|
|
9778
9787
|
|
|
9788
|
+
</li>
|
|
9789
|
+
|
|
9790
|
+
<li class="md-nav__item">
|
|
9791
|
+
<a href="#nautobot.apps.jobs.BaseJob.fail" class="md-nav__link">
|
|
9792
|
+
<span class="md-ellipsis">
|
|
9793
|
+
fail
|
|
9794
|
+
</span>
|
|
9795
|
+
</a>
|
|
9796
|
+
|
|
9779
9797
|
</li>
|
|
9780
9798
|
|
|
9781
9799
|
<li class="md-nav__item">
|
|
@@ -10624,11 +10642,13 @@ during a approval review workflow.</p>
|
|
|
10624
10642
|
<tbody>
|
|
10625
10643
|
<tr class="doc-section-item">
|
|
10626
10644
|
<td>
|
|
10627
|
-
<code>
|
|
10645
|
+
<code>Any</code>
|
|
10628
10646
|
</td>
|
|
10629
10647
|
<td>
|
|
10630
10648
|
<div class="doc-md-description">
|
|
10631
|
-
<p>The return value of this handler is ignored
|
|
10649
|
+
<p>The return value of this handler is ignored normally, <strong>except</strong> if <code>self.fail()</code> is called herein,
|
|
10650
|
+
in which case the return value will be used as the overall JobResult return value
|
|
10651
|
+
since <code>self.run()</code> will <strong>not</strong> be called in such a case.</p>
|
|
10632
10652
|
</div>
|
|
10633
10653
|
</td>
|
|
10634
10654
|
</tr>
|
|
@@ -10835,6 +10855,23 @@ path would consider this a failure of the job execution, as described in <code>n
|
|
|
10835
10855
|
<div class="doc doc-object doc-function">
|
|
10836
10856
|
|
|
10837
10857
|
|
|
10858
|
+
<h3 id="nautobot.apps.jobs.BaseJob.fail" class="doc doc-heading">
|
|
10859
|
+
<code class="highlight language-python"><span class="n">fail</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></code>
|
|
10860
|
+
|
|
10861
|
+
<a href="#nautobot.apps.jobs.BaseJob.fail" class="headerlink" title="Permanent link">¶</a></h3>
|
|
10862
|
+
|
|
10863
|
+
|
|
10864
|
+
<div class="doc doc-contents ">
|
|
10865
|
+
|
|
10866
|
+
<p>Mark this job as failed without immediately raising an exception and aborting.</p>
|
|
10867
|
+
|
|
10868
|
+
</div>
|
|
10869
|
+
|
|
10870
|
+
</div>
|
|
10871
|
+
|
|
10872
|
+
<div class="doc doc-object doc-function">
|
|
10873
|
+
|
|
10874
|
+
|
|
10838
10875
|
<h3 id="nautobot.apps.jobs.BaseJob.file_path" class="doc doc-heading">
|
|
10839
10876
|
<code class="highlight language-python"><span class="n">file_path</span><span class="p">()</span></code>
|
|
10840
10877
|
|
|
@@ -10914,11 +10951,12 @@ path would consider this a failure of the job execution, as described in <code>n
|
|
|
10914
10951
|
<code>exc</code>
|
|
10915
10952
|
</td>
|
|
10916
10953
|
<td>
|
|
10917
|
-
<code>
|
|
10954
|
+
<code>Any</code>
|
|
10918
10955
|
</td>
|
|
10919
10956
|
<td>
|
|
10920
10957
|
<div class="doc-md-description">
|
|
10921
|
-
<p>
|
|
10958
|
+
<p>Exception raised by the task (if any) <strong>or</strong> return value from the task, if it failed cleanly,
|
|
10959
|
+
such as if the Job called <code>self.fail()</code> rather than raising an exception.</p>
|
|
10922
10960
|
</div>
|
|
10923
10961
|
</td>
|
|
10924
10962
|
<td>
|
|
@@ -10982,7 +11020,7 @@ path would consider this a failure of the job execution, as described in <code>n
|
|
|
10982
11020
|
</td>
|
|
10983
11021
|
<td>
|
|
10984
11022
|
<div class="doc-md-description">
|
|
10985
|
-
<p>Exception information.</p>
|
|
11023
|
+
<p>Exception information, or None.</p>
|
|
10986
11024
|
</div>
|
|
10987
11025
|
</td>
|
|
10988
11026
|
<td>
|
|
@@ -8238,6 +8238,15 @@
|
|
|
8238
8238
|
</span>
|
|
8239
8239
|
</a>
|
|
8240
8240
|
|
|
8241
|
+
</li>
|
|
8242
|
+
|
|
8243
|
+
<li class="md-nav__item">
|
|
8244
|
+
<a href="#nautobot.apps.testing.NautobotTestCaseMixin.assertJobResultStatus" class="md-nav__link">
|
|
8245
|
+
<span class="md-ellipsis">
|
|
8246
|
+
assertJobResultStatus
|
|
8247
|
+
</span>
|
|
8248
|
+
</a>
|
|
8249
|
+
|
|
8241
8250
|
</li>
|
|
8242
8251
|
|
|
8243
8252
|
<li class="md-nav__item">
|
|
@@ -11423,6 +11432,15 @@
|
|
|
11423
11432
|
</span>
|
|
11424
11433
|
</a>
|
|
11425
11434
|
|
|
11435
|
+
</li>
|
|
11436
|
+
|
|
11437
|
+
<li class="md-nav__item">
|
|
11438
|
+
<a href="#nautobot.apps.testing.NautobotTestCaseMixin.assertJobResultStatus" class="md-nav__link">
|
|
11439
|
+
<span class="md-ellipsis">
|
|
11440
|
+
assertJobResultStatus
|
|
11441
|
+
</span>
|
|
11442
|
+
</a>
|
|
11443
|
+
|
|
11426
11444
|
</li>
|
|
11427
11445
|
|
|
11428
11446
|
<li class="md-nav__item">
|
|
@@ -14322,6 +14340,23 @@ in the dictionary.</p>
|
|
|
14322
14340
|
<div class="doc doc-object doc-function">
|
|
14323
14341
|
|
|
14324
14342
|
|
|
14343
|
+
<h3 id="nautobot.apps.testing.NautobotTestCaseMixin.assertJobResultStatus" class="doc doc-heading">
|
|
14344
|
+
<code class="highlight language-python"><span class="n">assertJobResultStatus</span><span class="p">(</span><span class="n">job_result</span><span class="p">,</span> <span class="n">expected_status</span><span class="o">=</span><span class="n">JobResultStatusChoices</span><span class="o">.</span><span class="n">STATUS_SUCCESS</span><span class="p">)</span></code>
|
|
14345
|
+
|
|
14346
|
+
<a href="#nautobot.apps.testing.NautobotTestCaseMixin.assertJobResultStatus" class="headerlink" title="Permanent link">¶</a></h3>
|
|
14347
|
+
|
|
14348
|
+
|
|
14349
|
+
<div class="doc doc-contents ">
|
|
14350
|
+
|
|
14351
|
+
<p>Assert that the given job_result has the expected_status, or print the job logs to aid in debugging.</p>
|
|
14352
|
+
|
|
14353
|
+
</div>
|
|
14354
|
+
|
|
14355
|
+
</div>
|
|
14356
|
+
|
|
14357
|
+
<div class="doc doc-object doc-function">
|
|
14358
|
+
|
|
14359
|
+
|
|
14325
14360
|
<h3 id="nautobot.apps.testing.NautobotTestCaseMixin.assertQuerysetEqualAndNotEmpty" class="doc doc-heading">
|
|
14326
14361
|
<code class="highlight language-python"><span class="n">assertQuerysetEqualAndNotEmpty</span><span class="p">(</span><span class="n">qs</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></code>
|
|
14327
14362
|
|
|
@@ -9943,6 +9943,10 @@
|
|
|
9943
9943
|
<td><a href="#dryrun_default">metadata property</a></td>
|
|
9944
9944
|
</tr>
|
|
9945
9945
|
<tr>
|
|
9946
|
+
<td><code>fail</code></td>
|
|
9947
|
+
<td><a href="#marking-a-job-as-failed">helper method</a></td>
|
|
9948
|
+
</tr>
|
|
9949
|
+
<tr>
|
|
9946
9950
|
<td><code>file_path</code></td>
|
|
9947
9951
|
<td>deprecated class property</td>
|
|
9948
9952
|
</tr>
|
|
@@ -10367,7 +10371,7 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10367
10371
|
</details>
|
|
10368
10372
|
<h4 id="the-before_start-method">The <code>before_start()</code> Method<a class="headerlink" href="#the-before_start-method" title="Permanent link">¶</a></h4>
|
|
10369
10373
|
<p>The <code>before_start()</code> method may optionally be implemented to perform any appropriate Job-specific setup before the <code>run()</code> method is called. It has the signature <code>before_start(self, task_id, args, kwargs)</code> for historical reasons; the <code>task_id</code> parameter will always be identical to <code>self.request.id</code>, the <code>args</code> parameter will generally be empty, and any user-specified variables passed into the Job execution will be present in the <code>kwargs</code> parameter.</p>
|
|
10370
|
-
<p>The return value from <code>before_start()</code> is ignored, but if it raises any exception, the Job
|
|
10374
|
+
<p>The return value from <code>before_start()</code> is ignored, but if it raises any exception, the Job result status will be marked as <code>FAILURE</code> and <code>run()</code> will not be called.</p>
|
|
10371
10375
|
<h4 id="the-run-method">The <code>run()</code> Method<a class="headerlink" href="#the-run-method" title="Permanent link">¶</a></h4>
|
|
10372
10376
|
<p>The <code>run()</code> method is the primary worker of any Job, and must be implemented. After the <code>self</code> argument, it should accept keyword arguments for any variables defined on the Job:</p>
|
|
10373
10377
|
<div class="highlight"><pre><span></span><code><a id="__codelineno-18-1" name="__codelineno-18-1" href="#__codelineno-18-1"></a><span class="kn">from</span> <span class="nn">nautobot.apps.jobs</span> <span class="kn">import</span> <span class="n">Job</span><span class="p">,</span> <span class="n">StringVar</span><span class="p">,</span> <span class="n">IntegerVar</span><span class="p">,</span> <span class="n">ObjectVar</span>
|
|
@@ -10389,13 +10393,13 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10389
10393
|
<p class="admonition-title">Be cautious around bulk operations</p>
|
|
10390
10394
|
<p>The Django ORM provides methods to create/edit many objects at once, namely <code>bulk_create()</code> and <code>update()</code>. These are best avoided in most cases as they bypass a model's built-in validation and can easily lead to database corruption if not used carefully.</p>
|
|
10391
10395
|
</div>
|
|
10392
|
-
<p>If <code>run()</code> returns any value (even the implicit <code>None</code>), the Job
|
|
10396
|
+
<p>If <code>run()</code> returns any value (even the implicit <code>None</code>), the returned value will be stored in the associated JobResult database record. (The Job Result will be marked as <code>SUCCESS</code> unless the <code>fail()</code> method was called at some point during <code>run()</code>, in which case it will be marked as <code>FAILURE</code>.) Conversely, if <code>run()</code> raises any exception, the Job execution will be marked as <code>FAILURE</code> and the traceback will be stored in the JobResult.</p>
|
|
10393
10397
|
<h4 id="the-on_success-method">The <code>on_success()</code> Method<a class="headerlink" href="#the-on_success-method" title="Permanent link">¶</a></h4>
|
|
10394
10398
|
<p>If both <code>before_start()</code> and <code>run()</code> are successful, the <code>on_success()</code> method will be called next, if implemented. It has the signature <code>on_success(self, retval, task_id, args, kwargs)</code>; as with <code>before_start()</code> the <code>task_id</code> and <code>args</code> parameters can generally be ignored, while <code>retval</code> is the return value from <code>run()</code>, and <code>kwargs</code> will contain the user-specified variables passed into the Job execution.</p>
|
|
10395
10399
|
<h4 id="the-on_failure-method">The <code>on_failure()</code> Method<a class="headerlink" href="#the-on_failure-method" title="Permanent link">¶</a></h4>
|
|
10396
|
-
<p>If either <code>before_start()</code> or <code>run()</code> raises any unhandled exception, the <code>on_failure()</code> method will be called next, if implemented. It has the signature <code>on_failure(self, exc, task_id, args, kwargs, einfo)</code>; of these parameters, the <code>exc</code> will contain the exception that was raised, and <code>kwargs</code> will contain the user-specified variables passed into the Job.</p>
|
|
10400
|
+
<p>If either <code>before_start()</code> or <code>run()</code> raises any unhandled exception, or reports a failure overall through <code>fail()</code>, the <code>on_failure()</code> method will be called next, if implemented. It has the signature <code>on_failure(self, exc, task_id, args, kwargs, einfo)</code>; of these parameters, the <code>exc</code> will contain the exception that was raised (if any) or the return value from <code>before_start()</code> or <code>run()</code> (otherwise), and <code>kwargs</code> will contain the user-specified variables passed into the Job.</p>
|
|
10397
10401
|
<h4 id="the-after_return-method">The <code>after_return()</code> Method<a class="headerlink" href="#the-after_return-method" title="Permanent link">¶</a></h4>
|
|
10398
|
-
<p>Regardless of the overall Job execution success or failure, the <code>after_return()</code> method will be called after <code>on_success()</code> or <code>on_failure()</code>. It has the signature <code>after_return(self, status, retval, task_id, args, kwargs, einfo)</code>; the <code>status</code> will indicate success or failure (using the <code>JobResultStatusChoices</code> enum), <code>retval</code> is <em>either</em> the return value from <code>run()</code>
|
|
10402
|
+
<p>Regardless of the overall Job execution success or failure, the <code>after_return()</code> method will be called after <code>on_success()</code> or <code>on_failure()</code>. It has the signature <code>after_return(self, status, retval, task_id, args, kwargs, einfo)</code>; the <code>status</code> will indicate success or failure (using the <code>JobResultStatusChoices</code> enum), <code>retval</code> is <em>either</em> the return value from <code>run()</code> or the exception raised, and once again <code>kwargs</code> contains the user variables.</p>
|
|
10399
10403
|
<h3 id="logging">Logging<a class="headerlink" href="#logging" title="Permanent link">¶</a></h3>
|
|
10400
10404
|
<details class="version-changed">
|
|
10401
10405
|
<summary>Changed in version 2.0.0</summary>
|
|
@@ -10416,7 +10420,7 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10416
10420
|
<a id="__codelineno-19-2" name="__codelineno-19-2" href="#__codelineno-19-2"></a>
|
|
10417
10421
|
<a id="__codelineno-19-3" name="__codelineno-19-3" href="#__codelineno-19-3"></a><span class="k">class</span> <span class="nc">MyJob</span><span class="p">(</span><span class="n">Job</span><span class="p">):</span>
|
|
10418
10422
|
<a id="__codelineno-19-4" name="__codelineno-19-4" href="#__codelineno-19-4"></a> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
10419
|
-
<a id="__codelineno-19-5" name="__codelineno-19-5" href="#__codelineno-19-5"></a> <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"This job is running!"</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="p">{</span><span class="s2">"grouping"</span><span class="p">:</span> <span class="s2">"myjobisrunning"</span><span class="p">,</span> <span class="s2">"object"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">job_result</span><span class="p">})</span>
|
|
10423
|
+
<a id="__codelineno-19-5" name="__codelineno-19-5" href="#__codelineno-19-5"></a> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"This job is running!"</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="p">{</span><span class="s2">"grouping"</span><span class="p">:</span> <span class="s2">"myjobisrunning"</span><span class="p">,</span> <span class="s2">"object"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">job_result</span><span class="p">})</span>
|
|
10420
10424
|
</code></pre></div>
|
|
10421
10425
|
</div>
|
|
10422
10426
|
<p>To skip writing a log entry to the database, set the <code>skip_db_logging</code> key in the "extra" kwarg to <code>True</code> when calling the log function. The output will still be written to the console.</p>
|
|
@@ -10427,7 +10431,7 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10427
10431
|
<a id="__codelineno-20-2" name="__codelineno-20-2" href="#__codelineno-20-2"></a>
|
|
10428
10432
|
<a id="__codelineno-20-3" name="__codelineno-20-3" href="#__codelineno-20-3"></a><span class="k">class</span> <span class="nc">MyJob</span><span class="p">(</span><span class="n">Job</span><span class="p">):</span>
|
|
10429
10433
|
<a id="__codelineno-20-4" name="__codelineno-20-4" href="#__codelineno-20-4"></a> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
10430
|
-
<a id="__codelineno-20-5" name="__codelineno-20-5" href="#__codelineno-20-5"></a> <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"This job is running!"</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="p">{</span><span class="s2">"skip_db_logging"</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
|
|
10434
|
+
<a id="__codelineno-20-5" name="__codelineno-20-5" href="#__codelineno-20-5"></a> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"This job is running!"</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="p">{</span><span class="s2">"skip_db_logging"</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
|
|
10431
10435
|
</code></pre></div>
|
|
10432
10436
|
</div>
|
|
10433
10437
|
<p>Markdown rendering is supported for log messages, as well as <a href="../../user-guide/platform-functionality/template-filters.html#render_markdown">a limited subset of HTML</a>.</p>
|
|
@@ -10444,8 +10448,12 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10444
10448
|
<p>The <code>AbortTransaction</code> class was moved from the <code>nautobot.utilities.exceptions</code> module to <code>nautobot.core.exceptions</code>. Jobs should generally import it from <code>nautobot.apps.exceptions</code> if needed.</p>
|
|
10445
10449
|
</details>
|
|
10446
10450
|
<details class="version-added">
|
|
10447
|
-
<summary>Added in version 2.4.0</summary>
|
|
10448
|
-
<p>You can now use <code>self.logger.success()</code> to
|
|
10451
|
+
<summary>Added in version 2.4.0 — <code>logger.success()</code> added</summary>
|
|
10452
|
+
<p>You can now use <code>self.logger.success()</code> to log a message at the level <code>SUCCESS</code>, which is located between the standard <code>INFO</code> and <code>WARNING</code> log levels.</p>
|
|
10453
|
+
</details>
|
|
10454
|
+
<details class="version-added">
|
|
10455
|
+
<summary>Added in version 2.4.5 — <code>logger.failure()</code> added</summary>
|
|
10456
|
+
<p>You can now use <code>self.logger.failure()</code> to log a message at the level <code>FAILURE</code>, which is located between the standard <code>WARNING</code> and <code>ERROR</code> log levels.</p>
|
|
10449
10457
|
</details>
|
|
10450
10458
|
<h3 id="file-output">File Output<a class="headerlink" href="#file-output" title="Permanent link">¶</a></h3>
|
|
10451
10459
|
<details class="version-added">
|
|
@@ -10462,17 +10470,22 @@ Another example of using the nested reference would be to access <a href="../../
|
|
|
10462
10470
|
<p>The above Job when run will create two files, "greeting.txt" and "farewell.txt", that will be made available for download from the JobResult detail view's "Advanced" tab and via the REST API. These files will persist indefinitely, but can automatically be deleted if the JobResult itself is deleted; they can also be deleted manually by an administrator via the "File Proxies" link in the Admin UI.</p>
|
|
10463
10471
|
<p>The maximum size of any single created file (or in other words, the maximum number of bytes that can be passed to <code>self.create_file()</code>) is controlled by the <a href="../../user-guide/administration/configuration/settings.html#job_create_file_max_size"><code>JOB_CREATE_FILE_MAX_SIZE</code></a> system setting. A <code>ValueError</code> exception will be raised if <code>create_file()</code> is called with an overly large <code>content</code> value.</p>
|
|
10464
10472
|
<h3 id="marking-a-job-as-failed">Marking a Job as Failed<a class="headerlink" href="#marking-a-job-as-failed" title="Permanent link">¶</a></h3>
|
|
10465
|
-
<p>
|
|
10466
|
-
<p>
|
|
10473
|
+
<p>Any uncaught exception raised from within the <code>run()</code> method will abort the <code>run()</code> method immediately (as usual in Python), and will result in the Job Result status being marked as <code>FAILURE</code>. The exception message and traceback will be recorded in the Job Result.</p>
|
|
10474
|
+
<p>Alternatively, in Nautobot v2.4.5 and later, you can more "cleanly" fail a Job by calling <code>self.fail(...)</code> and then either returning immediately from the <code>run()</code> method or continuing with the execution of the Job, as desired. In this case, after the <code>run()</code> method completes, the Job Result status will be automatically marked as <code>FAILURE</code> and no exception or traceback will be recorded.</p>
|
|
10475
|
+
<p>As an example, the following Job will abort if the user does not put the word "Taco" in <code>occasion</code>, and fail (but not abort) if the variable does not additionally contain "Tuesday":</p>
|
|
10467
10476
|
<div class="highlight"><pre><span></span><code><a id="__codelineno-22-1" name="__codelineno-22-1" href="#__codelineno-22-1"></a><span class="kn">from</span> <span class="nn">nautobot.apps.jobs</span> <span class="kn">import</span> <span class="n">Job</span><span class="p">,</span> <span class="n">StringVar</span>
|
|
10468
10477
|
<a id="__codelineno-22-2" name="__codelineno-22-2" href="#__codelineno-22-2"></a>
|
|
10469
10478
|
<a id="__codelineno-22-3" name="__codelineno-22-3" href="#__codelineno-22-3"></a><span class="k">class</span> <span class="nc">MyJob</span><span class="p">(</span><span class="n">Job</span><span class="p">):</span>
|
|
10470
|
-
<a id="__codelineno-22-4" name="__codelineno-22-4" href="#__codelineno-22-4"></a> <span class="n">
|
|
10479
|
+
<a id="__codelineno-22-4" name="__codelineno-22-4" href="#__codelineno-22-4"></a> <span class="n">occasion</span> <span class="o">=</span> <span class="n">StringVar</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
|
|
10471
10480
|
<a id="__codelineno-22-5" name="__codelineno-22-5" href="#__codelineno-22-5"></a>
|
|
10472
|
-
<a id="__codelineno-22-6" name="__codelineno-22-6" href="#__codelineno-22-6"></a> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">
|
|
10473
|
-
<a id="__codelineno-22-7" name="__codelineno-22-7" href="#__codelineno-22-7"></a> <span class="k">if</span> <span class="
|
|
10474
|
-
<a id="__codelineno-22-8" name="__codelineno-22-8" href="#__codelineno-22-8"></a> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">
|
|
10481
|
+
<a id="__codelineno-22-6" name="__codelineno-22-6" href="#__codelineno-22-6"></a> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">occasion</span><span class="p">):</span>
|
|
10482
|
+
<a id="__codelineno-22-7" name="__codelineno-22-7" href="#__codelineno-22-7"></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">occasion</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"Taco"</span><span class="p">):</span>
|
|
10483
|
+
<a id="__codelineno-22-8" name="__codelineno-22-8" href="#__codelineno-22-8"></a> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">failure</span><span class="p">(</span><span class="s2">"The occasion must begin with 'Taco'"</span><span class="p">)</span>
|
|
10475
10484
|
<a id="__codelineno-22-9" name="__codelineno-22-9" href="#__codelineno-22-9"></a> <span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="s2">"Argument input validation failed."</span><span class="p">)</span>
|
|
10485
|
+
<a id="__codelineno-22-10" name="__codelineno-22-10" href="#__codelineno-22-10"></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">occasion</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">" Tuesday"</span><span class="p">):</span>
|
|
10486
|
+
<a id="__codelineno-22-11" name="__codelineno-22-11" href="#__codelineno-22-11"></a> <span class="bp">self</span><span class="o">.</span><span class="n">fail</span><span class="p">(</span><span class="s2">"It's supposed to be Tuesday"</span><span class="p">)</span>
|
|
10487
|
+
<a id="__codelineno-22-12" name="__codelineno-22-12" href="#__codelineno-22-12"></a> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"Today is </span><span class="si">%s</span><span class="s2">"</span><span class="p">,</span> <span class="n">occasion</span><span class="p">)</span>
|
|
10488
|
+
<a id="__codelineno-22-13" name="__codelineno-22-13" href="#__codelineno-22-13"></a> <span class="k">return</span> <span class="n">occasion</span>
|
|
10476
10489
|
</code></pre></div>
|
|
10477
10490
|
<h3 id="accessing-user-and-job-result">Accessing User and Job Result<a class="headerlink" href="#accessing-user-and-job-result" title="Permanent link">¶</a></h3>
|
|
10478
10491
|
<details class="version-changed">
|
|
Binary file
|