Skip to content

Commit d6cd714

Browse files
committed
NetBox 2.9 support
1 parent 813ff83 commit d6cd714

File tree

14 files changed

+302
-49
lines changed

14 files changed

+302
-49
lines changed

netbox_onboarding/filters.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ class OnboardingTaskFilter(NameSlugSearchFilterSet):
2626

2727
q = django_filters.CharFilter(method="search", label="Search",)
2828

29-
site_id = django_filters.ModelMultipleChoiceFilter(queryset=Site.objects.all(), label="Site (ID)",)
30-
3129
site = django_filters.ModelMultipleChoiceFilter(
3230
field_name="site__slug", queryset=Site.objects.all(), to_field_name="slug", label="Site (slug)",
3331
)
@@ -46,7 +44,7 @@ class Meta: # noqa: D106 "Missing docstring in public nested class"
4644
model = OnboardingTask
4745
fields = ["id", "site", "site_id", "platform", "role", "status", "failed_reason"]
4846

49-
def search(self, queryset, name, value):
47+
def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
5048
"""Perform the filtered search."""
5149
if not value.strip():
5250
return queryset
@@ -55,7 +53,7 @@ def search(self, queryset, name, value):
5553
| Q(ip_address__icontains=value)
5654
| Q(site__name__icontains=value)
5755
| Q(platform__name__icontains=value)
58-
| Q(device__icontains=value)
56+
| Q(created_device__name__icontains=value)
5957
| Q(status__icontains=value)
6058
| Q(failed_reason__icontains=value)
6159
| Q(message__icontains=value)

netbox_onboarding/forms.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
from django import forms
1616
from django_rq import get_queue
1717

18-
from utilities.forms import BootstrapMixin
18+
from utilities.forms import BootstrapMixin, CSVModelForm
1919
from dcim.models import Site, Platform, DeviceRole, DeviceType
20-
from extras.forms import CustomFieldModelCSVForm
2120

2221
from .models import OnboardingTask
2322
from .choices import OnboardingStatusChoices, OnboardingFailChoices
@@ -33,7 +32,7 @@ class OnboardingTaskForm(BootstrapMixin, forms.ModelForm):
3332
required=True, label="IP address", help_text="IP Address/DNS Name of the device to onboard"
3433
)
3534

36-
site = forms.ModelChoiceField(required=True, queryset=Site.objects.all(), to_field_name="slug")
35+
site = forms.ModelChoiceField(required=True, queryset=Site.objects.all())
3736

3837
username = forms.CharField(required=False, help_text="Device username (will not be stored in database)")
3938
password = forms.CharField(
@@ -106,7 +105,7 @@ class Meta: # noqa: D106 "Missing docstring in public nested class"
106105
fields = ["q", "site", "platform", "status", "failed_reason"]
107106

108107

109-
class OnboardingTaskFeedCSVForm(CustomFieldModelCSVForm):
108+
class OnboardingTaskFeedCSVForm(CSVModelForm):
110109
"""Form for entering CSV to bulk-import OnboardingTask entries."""
111110

112111
site = forms.ModelChoiceField(
@@ -149,7 +148,14 @@ class OnboardingTaskFeedCSVForm(CustomFieldModelCSVForm):
149148

150149
class Meta: # noqa: D106 "Missing docstring in public nested class"
151150
model = OnboardingTask
152-
fields = OnboardingTask.csv_headers
151+
fields = [
152+
"site",
153+
"ip_address",
154+
"port",
155+
"timeout",
156+
"platform",
157+
"role",
158+
]
153159

154160
def save(self, commit=True, **kwargs):
155161
"""Save the model, and add it and the associated credentials to the onboarding worker queue."""

netbox_onboarding/models.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
limitations under the License.
1313
"""
1414
from django.db import models
15+
from django.urls import reverse
1516
from .choices import OnboardingStatusChoices, OnboardingFailChoices
1617

18+
from netbox_onboarding.release import NETBOX_RELEASE_CURRENT, NETBOX_RELEASE_29
19+
1720

1821
class OnboardingTask(models.Model):
1922
"""The status of each onboarding Task is tracked in the OnboardingTask table."""
@@ -47,18 +50,17 @@ class OnboardingTask(models.Model):
4750

4851
created_on = models.DateTimeField(auto_now_add=True)
4952

50-
csv_headers = [
51-
"site",
52-
"ip_address",
53-
"port",
54-
"timeout",
55-
"platform",
56-
"role",
57-
]
58-
5953
class Meta: # noqa: D106 "missing docstring in public nested class"
6054
ordering = ["created_on"]
6155

6256
def __str__(self):
6357
"""String representation of an OnboardingTask."""
6458
return f"{self.site} : {self.ip_address}"
59+
60+
def get_absolute_url(self):
61+
return reverse("plugins:netbox_onboarding:onboardingtask", kwargs={"pk": self.pk})
62+
63+
if NETBOX_RELEASE_CURRENT >= NETBOX_RELEASE_29:
64+
from utilities.querysets import RestrictedQuerySet
65+
66+
objects = RestrictedQuerySet.as_manager()

netbox_onboarding/navigation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@
1717

1818
menu_items = (
1919
PluginMenuItem(
20-
link="plugins:netbox_onboarding:onboarding_task_list",
20+
link="plugins:netbox_onboarding:onboardingtask_list",
2121
link_text="Onboarding Tasks",
2222
permissions=["netbox_onboarding.view_onboardingtask"],
2323
buttons=(
2424
PluginMenuButton(
25-
link="plugins:netbox_onboarding:onboarding_task_add",
25+
link="plugins:netbox_onboarding:onboardingtask_add",
2626
title="Onboard",
2727
icon_class="fa fa-plus",
2828
color=ButtonColorChoices.GREEN,
2929
permissions=["netbox_onboarding.add_onboardingtask"],
3030
),
3131
PluginMenuButton(
32-
link="plugins:netbox_onboarding:onboarding_task_import",
32+
link="plugins:netbox_onboarding:onboardingtask_import",
3333
title="Bulk Onboard",
3434
icon_class="fa fa-download",
3535
color=ButtonColorChoices.BLUE,

netbox_onboarding/netbox_keeper.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,10 @@ def ensure_primary_ip(self):
349349
address=f"{self.netdev_mgmt_ip_address}/{self.netdev_mgmt_pflen}"
350350
)
351351

352-
if created or not self.nb_primary_ip.interface:
352+
if created or not self.nb_primary_ip in self.nb_mgmt_ifname.ip_addresses.all():
353353
logging.info("ASSIGN: IP address %s to %s", self.nb_primary_ip.address, self.nb_mgmt_ifname.name)
354-
self.nb_primary_ip.interface = self.nb_mgmt_ifname
355-
self.nb_primary_ip.save()
354+
self.nb_mgmt_ifname.ip_addresses.add(self.nb_primary_ip)
355+
self.nb_mgmt_ifname.save()
356356

357357
# Ensure the primary IP is assigned to the device
358358
self.device.primary_ip4 = self.nb_primary_ip

netbox_onboarding/release.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Release variables of the NetBox.
2+
3+
(c) 2020 Network To Code
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
"""
14+
15+
from packaging import version
16+
from django.conf import settings
17+
18+
NETBOX_RELEASE_CURRENT = version.parse(settings.VERSION)
19+
NETBOX_RELEASE_28 = version.parse("2.8")
20+
NETBOX_RELEASE_29 = version.parse("2.9")

netbox_onboarding/tables.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
import django_tables2 as tables
1515
from utilities.tables import BaseTable, ToggleColumn
1616
from .models import OnboardingTask
17+
from django_tables2.utils import Accessor
1718

1819

1920
class OnboardingTaskTable(BaseTable):
2021
"""Table for displaying OnboardingTask instances."""
2122

22-
pk = ToggleColumn()
23+
pk = tables.LinkColumn()
2324
site = tables.LinkColumn()
2425
platform = tables.LinkColumn()
2526
created_device = tables.LinkColumn()

netbox_onboarding/templates/netbox_onboarding/onboarding_tasks_list.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
{% block content %}
55
<div class="pull-right noprint">
66
{% if permissions.add %}
7-
{% add_button 'plugins:netbox_onboarding:onboarding_task_add' %}
8-
{% import_button 'plugins:netbox_onboarding:onboarding_task_import' %}
7+
{% add_button 'plugins:netbox_onboarding:onboardingtask_add' %}
8+
{% import_button 'plugins:netbox_onboarding:onboardingtask_import' %}
99
{% endif %}
1010
</div>
1111
<h1>{% block title %}Onboarding Tasks{% endblock %}</h1>
1212
<div class="row">
1313
<div class="col-md-9">
14-
{% include 'utilities/obj_table.html' with bulk_delete_url="plugins:netbox_onboarding:onboarding_task_bulk_delete" %}
14+
{% include 'utilities/obj_table.html' with bulk_delete_url="plugins:netbox_onboarding:onboardingtask_bulk_delete" %}
1515
</div>
1616
<div class="col-md-3 noprint">
1717
{% include 'inc/search_panel.html' %}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{% extends 'base.html' %}
2+
{% load buttons %}
3+
{% load custom_links %}
4+
{% load helpers %}
5+
{% load static %}
6+
{% load tz %}
7+
8+
{% block header %}
9+
<div class="row noprint">
10+
<div class="col-sm-8 col-md-9">
11+
<ol class="breadcrumb">
12+
<li><a href="{% url 'plugins:netbox_onboarding:onboardingtask_list' %}">Onboarding tasks</a></li>
13+
<li>{{ onboardingtask.pk }}</li>
14+
</ol>
15+
</div>
16+
</div>
17+
18+
<h1>{% block title %}Device: {{ onboardingtask.ip_address }}{% endblock %}</h1>
19+
20+
<ul class="nav nav-tabs">
21+
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
22+
<a href="{{ onboardingtask.get_absolute_url }}">Onboarding Task</a>
23+
</li>
24+
</ul>
25+
{% endblock %}
26+
27+
{% block content %}
28+
<div class="row">
29+
<div class="col-md-7">
30+
<div class="panel panel-default">
31+
<div class="panel-heading">
32+
<strong>Onboarding Task</strong>
33+
</div>
34+
<table class="table table-hover panel-body attr-table">
35+
<tr>
36+
<td>Created Device</td>
37+
<td>{{ onboardingtask.created_device|placeholder }}</td>
38+
</tr>
39+
<tr>
40+
<td>IP Address</td>
41+
<td>{{ onboardingtask.ip_address|placeholder }}</td>
42+
</tr>
43+
<tr>
44+
<td>Port</td>
45+
<td>{{ onboardingtask.port|placeholder }}</td>
46+
</tr>
47+
<tr>
48+
<td>Timeout</td>
49+
<td>{{ onboardingtask.timeout|placeholder }}</td>
50+
</tr>
51+
<tr>
52+
<td>Site</td>
53+
<td>{{ onboardingtask.site|placeholder }}</td>
54+
</tr>
55+
<tr>
56+
<td>Role</td>
57+
<td>{{ onboardingtask.role|placeholder }}</td>
58+
</tr>
59+
<tr>
60+
<td>Device Type</td>
61+
<td>{{ onboardingtask.device_type|placeholder }}</td>
62+
</tr>
63+
<tr>
64+
<td>Platform</td>
65+
<td>{{ onboardingtask.platform|placeholder }}</td>
66+
</tr>
67+
<tr>
68+
<td>Status</td>
69+
<td>{{ onboardingtask.status|placeholder }}</td>
70+
</tr>
71+
<tr>
72+
<td>Failed Reason</td>
73+
<td>{{ onboardingtask.failed_reason|placeholder }}</td>
74+
</tr>
75+
<tr>
76+
<td>Message</td>
77+
<td>{{ onboardingtask.message|placeholder }}</td>
78+
</tr>
79+
<tr>
80+
<td>Created On</td>
81+
<td>{{ onboardingtask.created_on|placeholder }}</td>
82+
</tr>
83+
</table>
84+
</div>
85+
</div>
86+
</div>
87+
{% endblock %}
88+
89+
{% block javascript %}
90+
<script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
91+
{% endblock %}

netbox_onboarding/tests/test_netbox_keeper.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,9 @@ def test_ensure_primary_ip_not_exist(self):
308308
nbk.ensure_device()
309309

310310
self.assertIsInstance(nbk.nb_primary_ip, IPAddress)
311-
self.assertEqual(nbk.nb_primary_ip.interface.name, "ge-0/0/0")
311+
self.assertTrue(
312+
nbk.nb_primary_ip in Interface.objects.get(device=nbk.device, name="ge-0/0/0").ip_addresses.all()
313+
)
312314
self.assertEqual(nbk.device.primary_ip, nbk.nb_primary_ip)
313315

314316
def test_ensure_device_platform_missing(self):

netbox_onboarding/tests/test_views.py renamed to netbox_onboarding/tests/test_views_28.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,26 @@
1111
See the License for the specific language governing permissions and
1212
limitations under the License.
1313
"""
14+
from unittest import skipUnless
15+
1416
from django.contrib.auth.models import User, Permission
1517
from django.test import Client, TestCase, override_settings
1618
from django.urls import reverse
1719

1820
from dcim.models import Site
1921

2022
from netbox_onboarding.models import OnboardingTask
23+
from netbox_onboarding.release import NETBOX_RELEASE_CURRENT, NETBOX_RELEASE_29
24+
25+
if NETBOX_RELEASE_CURRENT < NETBOX_RELEASE_29: # pylint: disable=simplifiable-if-statement
26+
RUN_TEST = True
27+
else:
28+
RUN_TEST = False
29+
30+
TEST_SKIP_MESSAGE = "Test requires NetBox release < 2.9"
2131

2232

33+
@skipUnless(RUN_TEST, TEST_SKIP_MESSAGE)
2334
class OnboardingTaskListViewTestCase(TestCase):
2435
"""Test the OnboardingTaskListView view."""
2536

@@ -29,7 +40,7 @@ def setUp(self):
2940
self.client = Client()
3041
self.client.force_login(self.user)
3142

32-
self.url = reverse("plugins:netbox_onboarding:onboarding_task_list")
43+
self.url = reverse("plugins:netbox_onboarding:onboardingtask_list")
3344

3445
self.site1 = Site.objects.create(name="USWEST", slug="uswest")
3546
self.onboarding_task1 = OnboardingTask.objects.create(ip_address="10.10.10.10", site=self.site1)
@@ -60,6 +71,7 @@ def test_list_onboarding_tasks(self):
6071
self.assertTemplateUsed(response, "netbox_onboarding/onboarding_tasks_list.html")
6172

6273

74+
@skipUnless(RUN_TEST, TEST_SKIP_MESSAGE)
6375
class OnboardingTaskCreateViewTestCase(TestCase):
6476
"""Test the OnboardingTaskCreateView view."""
6577

@@ -69,7 +81,7 @@ def setUp(self):
6981
self.client = Client()
7082
self.client.force_login(self.user)
7183

72-
self.url = reverse("plugins:netbox_onboarding:onboarding_task_add")
84+
self.url = reverse("plugins:netbox_onboarding:onboardingtask_add")
7385

7486
self.site1 = Site.objects.create(name="USWEST", slug="uswest")
7587

@@ -124,6 +136,7 @@ def test_post(self):
124136
self.assertEqual(OnboardingTask.objects.count(), 1)
125137

126138

139+
@skipUnless(RUN_TEST, TEST_SKIP_MESSAGE)
127140
class OnboardingTaskBulkDeleteViewTestCase(TestCase):
128141
"""Test the OnboardingTaskBulkDeleteView view."""
129142

@@ -133,7 +146,7 @@ def setUp(self):
133146
self.client = Client()
134147
self.client.force_login(self.user)
135148

136-
self.url = reverse("plugins:netbox_onboarding:onboarding_task_bulk_delete")
149+
self.url = reverse("plugins:netbox_onboarding:onboardingtask_bulk_delete")
137150

138151
self.site1 = Site.objects.create(name="USWEST", slug="uswest")
139152
self.onboarding_task1 = OnboardingTask.objects.create(ip_address="10.10.10.10", site=self.site1)
@@ -168,6 +181,7 @@ def test_post(self):
168181
self.assertEqual(OnboardingTask.objects.count(), 1)
169182

170183

184+
@skipUnless(RUN_TEST, TEST_SKIP_MESSAGE)
171185
class OnboardingTaskFeedBulkImportViewTestCase(TestCase):
172186
"""Test the OnboardingTaskFeedBulkImportView view."""
173187

@@ -177,7 +191,7 @@ def setUp(self):
177191
self.client = Client()
178192
self.client.force_login(self.user)
179193

180-
self.url = reverse("plugins:netbox_onboarding:onboarding_task_import")
194+
self.url = reverse("plugins:netbox_onboarding:onboardingtask_import")
181195

182196
self.site1 = Site.objects.create(name="USWEST", slug="uswest")
183197

0 commit comments

Comments
 (0)