Skip to content

Commit e9c9256

Browse files
Merge pull request #20 from networktocode/gfm-task-view
Single task view, fully exclude credentials from database
2 parents c797506 + c25c392 commit e9c9256

File tree

8 files changed

+110
-86
lines changed

8 files changed

+110
-86
lines changed

netbox_onboarding/forms.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,77 @@
1313
"""
1414

1515
from django import forms
16+
from django_rq import get_queue
1617

1718
from utilities.forms import BootstrapMixin
18-
from dcim.models import Site, Platform, DeviceRole
19+
from dcim.models import Site, Platform, DeviceRole, DeviceType
1920
from extras.forms import CustomFieldModelCSVForm
2021

2122
from .models import OnboardingTask
2223
from .choices import OnboardingStatusChoices, OnboardingFailChoices
24+
from .utils.credentials import Credentials
2325

2426
BLANK_CHOICE = (("", "---------"),)
2527

2628

29+
class OnboardingTaskForm(BootstrapMixin, forms.ModelForm):
30+
"""Form for creating a new OnboardingTask instance."""
31+
32+
ip_address = forms.CharField(required=True, label="IP address", help_text="IP address of the device to onboard")
33+
34+
site = forms.ModelChoiceField(required=True, queryset=Site.objects.all(), to_field_name="slug")
35+
36+
username = forms.CharField(required=False, help_text="Device username (will not be stored in database)")
37+
password = forms.CharField(
38+
required=False, widget=forms.PasswordInput, help_text="Device password (will not be stored in database)"
39+
)
40+
secret = forms.CharField(
41+
required=False, widget=forms.PasswordInput, help_text="Device secret (will not be stored in database)"
42+
)
43+
44+
platform = forms.ModelChoiceField(
45+
queryset=Platform.objects.all(),
46+
required=False,
47+
to_field_name="slug",
48+
help_text="Device platform. Define ONLY to override auto-recognition of platform.",
49+
)
50+
role = forms.ModelChoiceField(
51+
queryset=DeviceRole.objects.all(),
52+
required=False,
53+
to_field_name="slug",
54+
help_text="Device role. Define ONLY to override auto-recognition of role.",
55+
)
56+
device_type = forms.ModelChoiceField(
57+
queryset=DeviceType.objects.all(),
58+
required=False,
59+
to_field_name="slug",
60+
help_text="Device type. Define ONLY to override auto-recognition of type.",
61+
)
62+
63+
class Meta: # noqa: D106 "Missing docstring in public nested class"
64+
model = OnboardingTask
65+
fields = [
66+
"site",
67+
"ip_address",
68+
"port",
69+
"timeout",
70+
"username",
71+
"password",
72+
"secret",
73+
"platform",
74+
"role",
75+
"device_type",
76+
]
77+
78+
def save(self, commit=True, **kwargs):
79+
"""Save the model, and add it and the associated credentials to the onboarding worker queue."""
80+
model = super().save(commit=commit, **kwargs)
81+
if commit:
82+
credentials = Credentials(self.data["username"], self.data["password"], self.data["secret"])
83+
get_queue("default").enqueue("netbox_onboarding.worker.onboard_device", model.pk, credentials)
84+
return model
85+
86+
2787
class OnboardingTaskFilterForm(BootstrapMixin, forms.ModelForm):
2888
"""Form for filtering OnboardingTask instances."""
2989

@@ -45,7 +105,7 @@ class Meta: # noqa: D106 "Missing docstring in public nested class"
45105

46106

47107
class OnboardingTaskFeedCSVForm(CustomFieldModelCSVForm):
48-
"""TODO document me."""
108+
"""Form for entering CSV to bulk-import OnboardingTask entries."""
49109

50110
site = forms.ModelChoiceField(
51111
queryset=Site.objects.all(),
@@ -80,3 +140,11 @@ class OnboardingTaskFeedCSVForm(CustomFieldModelCSVForm):
80140
class Meta: # noqa: D106 "Missing docstring in public nested class"
81141
model = OnboardingTask
82142
fields = OnboardingTask.csv_headers
143+
144+
def save(self, commit=True, **kwargs):
145+
"""Save the model, and add it and the associated credentials to the onboarding worker queue."""
146+
model = super().save(commit=commit, **kwargs)
147+
if commit:
148+
credentials = Credentials(self.data.get("username"), self.data.get("password"), self.data.get("secret"))
149+
get_queue("default").enqueue("netbox_onboarding.worker.onboard_device", model.pk, credentials)
150+
return model

netbox_onboarding/migrations/0001_initial.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 3.0.6 on 2020-05-12 10:21
1+
# Generated by Django 3.0.6 on 2020-05-18 13:04
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -21,9 +21,6 @@ class Migration(migrations.Migration):
2121
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False)),
2222
("group_id", models.CharField(blank=True, max_length=255)),
2323
("ip_address", models.CharField(max_length=255, null=True)),
24-
("username", models.CharField(blank=True, max_length=255)),
25-
("password", models.CharField(blank=True, max_length=255)),
26-
("secret", models.CharField(blank=True, max_length=255)),
2724
("device_type", models.CharField(max_length=255, null=True)),
2825
("status", models.CharField(max_length=255)),
2926
("failed_reason", models.CharField(max_length=255, null=True)),

netbox_onboarding/models.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ class OnboardingTask(models.Model):
3131

3232
site = models.ForeignKey(to="dcim.Site", on_delete=models.SET_NULL, blank=True, null=True)
3333

34-
username = models.CharField(max_length=255, help_text="Device Username (optional)", blank=True)
35-
36-
password = models.CharField(max_length=255, help_text="Device Password (optional)", blank=True)
37-
38-
secret = models.CharField(max_length=255, help_text="Device Secret Password (optional)", blank=True)
39-
4034
role = models.ForeignKey(to="dcim.DeviceRole", on_delete=models.SET_NULL, blank=True, null=True)
4135

4236
device_type = models.CharField(
@@ -63,9 +57,6 @@ class OnboardingTask(models.Model):
6357
csv_headers = [
6458
"site",
6559
"ip_address",
66-
"username",
67-
"password",
68-
"secret",
6960
"port",
7061
"timeout",
7162
"platform",

netbox_onboarding/navigation.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,25 @@
1313
"""
1414

1515
from extras.plugins import PluginMenuButton, PluginMenuItem
16+
from utilities.choices import ButtonColorChoices
1617

1718
menu_items = (
1819
PluginMenuItem(
1920
link="plugins:netbox_onboarding:onboarding_task_list",
2021
link_text="Onboarding Tasks",
2122
permissions=[],
2223
buttons=(
24+
PluginMenuButton(
25+
link="plugins:netbox_onboarding:onboarding_task_add",
26+
title="Onboard",
27+
icon_class="fa fa-plus",
28+
color=ButtonColorChoices.GREEN,
29+
),
2330
PluginMenuButton(
2431
link="plugins:netbox_onboarding:onboarding_task_import",
2532
title="Bulk Onboard",
2633
icon_class="fa fa-download",
34+
color=ButtonColorChoices.BLUE,
2735
),
2836
),
2937
),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{% extends 'utilities/obj_edit.html' %}
2+
{% load form_helpers %}

netbox_onboarding/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
"""
1414
from django.urls import path
1515

16-
from .views import OnboardingTaskListView, OnboardingTaskFeedBulkImportView
16+
from .views import OnboardingTaskListView, OnboardingTaskCreateView, OnboardingTaskFeedBulkImportView
1717

1818
urlpatterns = [
1919
path("", OnboardingTaskListView.as_view(), name="onboarding_task_list"),
20+
path("add/", OnboardingTaskCreateView.as_view(), name="onboarding_task_add"),
2021
path("import/", OnboardingTaskFeedBulkImportView.as_view(), name="onboarding_task_import"),
2122
]

netbox_onboarding/views.py

Lines changed: 15 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,11 @@
1212
limitations under the License.
1313
"""
1414
import logging
15-
from django.contrib import messages
1615
from django.contrib.auth.mixins import PermissionRequiredMixin
17-
from django.core.exceptions import ValidationError
18-
from django.db import transaction
19-
from django.shortcuts import render
20-
from django_rq import get_queue
21-
from utilities.views import BulkImportView, ObjectListView
16+
from utilities.views import BulkImportView, ObjectEditView, ObjectListView
2217

23-
from netbox_onboarding.utils.credentials import Credentials
2418
from .filters import OnboardingTaskFilter
25-
from .forms import OnboardingTaskFilterForm, OnboardingTaskFeedCSVForm
19+
from .forms import OnboardingTaskForm, OnboardingTaskFilterForm, OnboardingTaskFeedCSVForm
2620
from .models import OnboardingTask
2721
from .tables import OnboardingTaskTable, OnboardingTaskFeedBulkTable
2822

@@ -35,71 +29,27 @@ class OnboardingTaskListView(PermissionRequiredMixin, ObjectListView):
3529

3630
permission_required = "dcim.view_device"
3731
queryset = OnboardingTask.objects.all().order_by("-id")
38-
filter = OnboardingTaskFilter
39-
filter_form = OnboardingTaskFilterForm
32+
filterset = OnboardingTaskFilter
33+
filterset_form = OnboardingTaskFilterForm
4034
table = OnboardingTaskTable
4135
template_name = "netbox_onboarding/onboarding_tasks_list.html"
4236

4337

38+
class OnboardingTaskCreateView(PermissionRequiredMixin, ObjectEditView):
39+
"""View for creating a new OnboardingTask."""
40+
41+
permission_required = "dcim.add_device"
42+
model = OnboardingTask
43+
queryset = OnboardingTask.objects.all()
44+
model_form = OnboardingTaskForm
45+
template_name = "netbox_onboarding/onboarding_task_edit.html"
46+
default_return_url = "plugins:netbox_onboarding:onboarding_task_list"
47+
48+
4449
class OnboardingTaskFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
4550
"""View for bulk-importing a CSV file to create OnboardingTasks."""
4651

4752
permission_required = "dcim.add_device"
4853
model_form = OnboardingTaskFeedCSVForm
4954
table = OnboardingTaskFeedBulkTable
5055
default_return_url = "plugins:netbox_onboarding:onboarding_task_list"
51-
52-
def post(self, request):
53-
"""Process an HTTP POST request."""
54-
new_objs = []
55-
form = self._import_form(request.POST)
56-
57-
if form.is_valid():
58-
try:
59-
# Iterate through CSV data and bind each row to a new model form instance.
60-
with transaction.atomic():
61-
headers, records = form.cleaned_data["csv"]
62-
for row, data in enumerate(records, start=1):
63-
obj_form = self.model_form(data, headers=headers)
64-
if obj_form.is_valid():
65-
obj = self._save_obj(obj_form, request)
66-
new_objs.append(obj)
67-
else:
68-
for field, err in obj_form.errors.items():
69-
form.add_error("csv", "Row {} {}: {}".format(row, field, err[0]))
70-
raise ValidationError("")
71-
72-
for ot in new_objs:
73-
credentials = Credentials(username=ot.username, password=ot.password, secret=ot.secret,)
74-
75-
ot.username = ""
76-
ot.password = ""
77-
ot.secret = ""
78-
ot.owner = self.request.user
79-
ot.save()
80-
81-
get_queue("default").enqueue("netbox_onboarding.worker.onboard_device", ot.pk, credentials)
82-
83-
if new_objs:
84-
msg = "Imported {} {}".format(len(new_objs), new_objs[0]._meta.verbose_name_plural)
85-
messages.success(request, msg)
86-
87-
return render(
88-
request,
89-
"import_success.html",
90-
{"table": self.table(new_objs), "return_url": self.get_return_url(request),},
91-
)
92-
93-
except ValidationError:
94-
pass
95-
96-
return render(
97-
request,
98-
self.template_name,
99-
{
100-
"form": form,
101-
"fields": self.model_form().fields,
102-
"obj_type": self.model_form._meta.model._meta.verbose_name, # pylint:disable=no-member
103-
"return_url": self.get_return_url(request),
104-
},
105-
)

tasks.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,12 @@ def create_user(context, user="admin", netbox_ver=NETBOX_VER, python_ver=PYTHON_
167167

168168

169169
@task
170-
def makemigrations(context, netbox_ver=NETBOX_VER, python_ver=PYTHON_VER):
170+
def makemigrations(context, name="", netbox_ver=NETBOX_VER, python_ver=PYTHON_VER):
171171
"""Run Make Migration in Django.
172172
173173
Args:
174174
context (obj): Used to run specific commands
175+
name (str): Name of the migration to be created
175176
netbox_ver (str): NetBox version to use to build the container
176177
python_ver (str): Will use the Python version docker image to build from
177178
"""
@@ -180,10 +181,16 @@ def makemigrations(context, netbox_ver=NETBOX_VER, python_ver=PYTHON_VER):
180181
env={"NETBOX_VER": netbox_ver, "PYTHON_VER": python_ver},
181182
)
182183

183-
context.run(
184-
f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run netbox python manage.py makemigrations",
185-
env={"NETBOX_VER": netbox_ver, "PYTHON_VER": python_ver},
186-
)
184+
if name:
185+
context.run(
186+
f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run netbox python manage.py makemigrations --name {name}",
187+
env={"NETBOX_VER": netbox_ver, "PYTHON_VER": python_ver},
188+
)
189+
else:
190+
context.run(
191+
f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run netbox python manage.py makemigrations",
192+
env={"NETBOX_VER": netbox_ver, "PYTHON_VER": python_ver},
193+
)
187194

188195
context.run(
189196
f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} down",

0 commit comments

Comments
 (0)