Skip to content

Commit 95cee68

Browse files
authored
Npl 379 import (#160)
1 parent c65d985 commit 95cee68

File tree

5 files changed

+143
-36
lines changed

5 files changed

+143
-36
lines changed

netbox_custom_objects/field_types.py

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from django.utils.translation import gettext_lazy as _
1515
from extras.choices import CustomFieldTypeChoices, CustomFieldUIEditableChoices
1616
from utilities.api import get_serializer_for_model
17-
from utilities.forms.fields import (CSVChoiceField, CSVMultipleChoiceField,
17+
from utilities.forms.fields import (CSVChoiceField, CSVModelChoiceField,
18+
CSVModelMultipleChoiceField, CSVMultipleChoiceField,
1819
DynamicChoiceField,
1920
DynamicMultipleChoiceField, JSONField,
2021
LaxURLField)
@@ -357,7 +358,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
357358
"""
358359
content_type = ContentType.objects.get(pk=field.related_object_type_id)
359360

360-
from utilities.forms.fields import DynamicModelChoiceField
361361
if content_type.app_label == APP_LABEL:
362362
# This is a custom object type
363363
from netbox_custom_objects.models import CustomObjectType
@@ -367,24 +367,34 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
367367
)
368368
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
369369
model = custom_object_type.get_model()
370-
field_class = DynamicModelChoiceField
371370
else:
372371
# This is a regular NetBox model
373372
model = content_type.model_class()
374373

374+
if for_csv_import:
375+
field_class = CSVModelChoiceField
376+
# For CSV import, determine to_field_name from the field configuration
377+
to_field_name = getattr(field, 'to_field_name', None) or 'name'
378+
return field_class(
379+
queryset=model.objects.all(),
380+
required=field.required,
381+
initial=field.default,
382+
to_field_name=to_field_name,
383+
)
384+
else:
385+
from utilities.forms.fields import DynamicModelChoiceField
375386
field_class = DynamicModelChoiceField
376-
377-
return field_class(
378-
queryset=model.objects.all(),
379-
required=field.required,
380-
initial=field.default,
381-
query_params=(
382-
field.related_object_filter
383-
if hasattr(field, "related_object_filter")
384-
else None
385-
),
386-
selector=True,
387-
)
387+
return field_class(
388+
queryset=model.objects.all(),
389+
required=field.required,
390+
initial=field.default,
391+
query_params=(
392+
field.related_object_filter
393+
if hasattr(field, "related_object_filter")
394+
else None
395+
),
396+
selector=True,
397+
)
388398

389399
def get_filterform_field(self, field, **kwargs):
390400
return None
@@ -658,19 +668,30 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
658668
# This is a regular NetBox model
659669
model = content_type.model_class()
660670

661-
from utilities.forms.fields import DynamicModelMultipleChoiceField
662-
663-
return DynamicModelMultipleChoiceField(
664-
queryset=model.objects.all(),
665-
required=field.required,
666-
initial=field.default,
667-
query_params=(
668-
field.related_object_filter
669-
if hasattr(field, "related_object_filter")
670-
else None
671-
),
672-
selector=True,
673-
)
671+
if for_csv_import:
672+
field_class = CSVModelMultipleChoiceField
673+
# For CSV import, determine to_field_name from the field configuration
674+
to_field_name = getattr(field, 'to_field_name', None) or 'name'
675+
return field_class(
676+
queryset=model.objects.all(),
677+
required=field.required,
678+
initial=field.default,
679+
to_field_name=to_field_name,
680+
)
681+
else:
682+
from utilities.forms.fields import DynamicModelMultipleChoiceField
683+
field_class = DynamicModelMultipleChoiceField
684+
return field_class(
685+
queryset=model.objects.all(),
686+
required=field.required,
687+
initial=field.default,
688+
query_params=(
689+
field.related_object_filter
690+
if hasattr(field, "related_object_filter")
691+
else None
692+
),
693+
selector=True,
694+
)
674695

675696
def get_filterform_field(self, field, **kwargs):
676697
return None

netbox_custom_objects/templates/netbox_custom_objects/custom_object_list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{% plugin_list_buttons model %}
88
{% block extra_controls %}{% endblock %}
99
{% custom_object_add_button model custom_object_type %}
10-
{# {% custom_object_import_button model %} #}
10+
{% custom_object_import_button model custom_object_type %}
1111
{% custom_object_export_button model %}
1212
</div>
1313
{% endblock controls %}

netbox_custom_objects/templatetags/custom_object_buttons.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,12 @@ def custom_object_add_button(model, custom_object_type, action="add"):
203203

204204

205205
@register.inclusion_tag("buttons/import.html")
206-
def custom_object_import_button(model, action="bulk_import"):
206+
def custom_object_import_button(model, custom_object_type, action="bulk_import"):
207207
try:
208-
url = reverse(get_viewname(model, action))
208+
viewname = get_viewname(model, action)
209+
url = reverse(
210+
viewname, kwargs={"custom_object_type": custom_object_type.name.lower()}
211+
)
209212
except NoReverseMatch:
210213
url = None
211214

netbox_custom_objects/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
views.CustomObjectBulkDeleteView.as_view(),
5050
name="customobject_bulk_delete",
5151
),
52+
path(
53+
"<str:custom_object_type>/bulk-import/",
54+
views.CustomObjectBulkImportView.as_view(),
55+
name="customobject_bulk_import",
56+
),
5257
path(
5358
"<str:custom_object_type>/<int:pk>/",
5459
views.CustomObjectView.as_view(),

netbox_custom_objects/views.py

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
from extras.models import JournalEntry
1515
from extras.tables import JournalEntryTable
1616
from netbox.filtersets import BaseFilterSet
17-
from netbox.forms import NetBoxModelBulkEditForm, NetBoxModelFilterSetForm
17+
from netbox.forms import (
18+
NetBoxModelBulkEditForm,
19+
NetBoxModelFilterSetForm,
20+
NetBoxModelImportForm,
21+
)
1822
from netbox.views import generic
1923
from netbox.views.generic.mixins import TableMixin
2024
from utilities.forms import ConfirmationForm
@@ -31,7 +35,7 @@
3135
from . import field_types, filtersets, forms, tables
3236
from .models import CustomObject, CustomObjectType, CustomObjectTypeField
3337

34-
logger = logging.getLogger('netbox_custom_objects.views')
38+
logger = logging.getLogger("netbox_custom_objects.views")
3539

3640

3741
class CustomJournalEntryForm(JournalEntryForm):
@@ -123,7 +127,9 @@ def get_table(self, data, request, bulk_actions=True):
123127
attrs[field.name] = field_type.get_table_column_field(field)
124128
except NotImplementedError:
125129
logger.debug(
126-
"table mixin: {} field is not implemented; using a default column".format(field.name)
130+
"table mixin: {} field is not implemented; using a default column".format(
131+
field.name
132+
)
127133
)
128134
# Define a method "render_table_column" method on any FieldType to customize output
129135
# See https://django-tables2.readthedocs.io/en/latest/pages/custom-data.html#table-render-foo-methods
@@ -568,8 +574,17 @@ def get_queryset(self, request):
568574
return model.objects.all()
569575

570576
def get_form(self, queryset):
577+
meta = type(
578+
"Meta",
579+
(),
580+
{
581+
"model": queryset.model,
582+
"fields": "__all__",
583+
},
584+
)
585+
571586
attrs = {
572-
"model": queryset.model,
587+
"Meta": meta,
573588
"__module__": "database.forms",
574589
}
575590

@@ -578,7 +593,9 @@ def get_form(self, queryset):
578593
try:
579594
attrs[field.name] = field_type.get_annotated_form_field(field)
580595
except NotImplementedError:
581-
logger.debug("bulk edit form: {} field is not supported".format(field.name))
596+
logger.debug(
597+
"bulk edit form: {} field is not supported".format(field.name)
598+
)
582599

583600
form = type(
584601
f"{queryset.model._meta.object_name}BulkEditForm",
@@ -612,6 +629,67 @@ def get_queryset(self, request):
612629
return model.objects.all()
613630

614631

632+
@register_model_view(CustomObject, "bulk_import", path="import", detail=False)
633+
class CustomObjectBulkImportView(generic.BulkImportView):
634+
queryset = None
635+
model_form = None
636+
637+
def get(self, request, custom_object_type):
638+
# Necessary because get() in BulkImportView only takes request and no **kwargs
639+
return super().get(request)
640+
641+
def post(self, request, custom_object_type):
642+
# Necessary because post() in BulkImportView only takes request and no **kwargs
643+
return super().post(request)
644+
645+
def setup(self, request, *args, **kwargs):
646+
super().setup(request, *args, **kwargs)
647+
self.queryset = self.get_queryset(request)
648+
self.model_form = self.get_model_form(self.queryset)
649+
650+
def get_queryset(self, request):
651+
if self.queryset:
652+
return self.queryset
653+
custom_object_type = self.kwargs.get("custom_object_type", None)
654+
self.custom_object_type = CustomObjectType.objects.get(
655+
name__iexact=custom_object_type
656+
)
657+
model = self.custom_object_type.get_model()
658+
return model.objects.all()
659+
660+
def get_model_form(self, queryset):
661+
meta = type(
662+
"Meta",
663+
(),
664+
{
665+
"model": queryset.model,
666+
"fields": "__all__",
667+
},
668+
)
669+
670+
attrs = {
671+
"Meta": meta,
672+
"__module__": "database.forms",
673+
}
674+
675+
for field in self.custom_object_type.fields.all():
676+
field_type = field_types.FIELD_TYPE_CLASS[field.type]()
677+
try:
678+
attrs[field.name] = field_type.get_annotated_form_field(
679+
field, for_csv_import=True
680+
)
681+
except NotImplementedError:
682+
print(f"bulk import form: {field.name} field is not supported")
683+
684+
form = type(
685+
f"{queryset.model._meta.object_name}BulkImportForm",
686+
(NetBoxModelImportForm,),
687+
attrs,
688+
)
689+
690+
return form
691+
692+
615693
class CustomObjectJournalView(ConditionalLoginRequiredMixin, View):
616694
"""
617695
Custom journal view for CustomObject instances.

0 commit comments

Comments
 (0)