Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions netbox/dcim/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,131 @@ def test_import_objects(self):
ii1 = InventoryItemTemplate.objects.first()
self.assertEqual(ii1.name, 'Inventory Item 1')

@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_import_error_numbering(self):
# Add all required permissions to the test user
self.add_permissions(
'dcim.view_devicetype',
'dcim.add_devicetype',
'dcim.add_consoleporttemplate',
'dcim.add_consoleserverporttemplate',
'dcim.add_powerporttemplate',
'dcim.add_poweroutlettemplate',
'dcim.add_interfacetemplate',
'dcim.add_frontporttemplate',
'dcim.add_rearporttemplate',
'dcim.add_modulebaytemplate',
'dcim.add_devicebaytemplate',
'dcim.add_inventoryitemtemplate',
)

import_data = '''
---
manufacturer: Manufacturer 1
model: TEST-2001
slug: test-2001
u_height: 1
module-bays:
- name: Module Bay 1-1
- name: Module Bay 1-2
---
- manufacturer: Manufacturer 1
model: TEST-2002
slug: test-2002
u_height: 1
module-bays:
- name: Module Bay 2-1
- name: Module Bay 2-2
- not_name: Module Bay 2-3
- manufacturer: Manufacturer 1
model: TEST-2003
slug: test-2003
u_height: 1
module-bays:
- name: Module Bay 3-1
'''
form_data = {
'data': import_data,
'format': 'yaml'
}

response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
self.assertHttpStatus(response, 200)
self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")

@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_import_nolist(self):
# Add all required permissions to the test user
self.add_permissions(
'dcim.view_devicetype',
'dcim.add_devicetype',
'dcim.add_consoleporttemplate',
'dcim.add_consoleserverporttemplate',
'dcim.add_powerporttemplate',
'dcim.add_poweroutlettemplate',
'dcim.add_interfacetemplate',
'dcim.add_frontporttemplate',
'dcim.add_rearporttemplate',
'dcim.add_modulebaytemplate',
'dcim.add_devicebaytemplate',
'dcim.add_inventoryitemtemplate',
)

for value in ('', 'null', '3', '"My console port"', '{name: "My other console port"}'):
with self.subTest(value=value):
import_data = f'''
manufacturer: Manufacturer 1
model: TEST-3000
slug: test-3000
u_height: 1
console-ports: {value}
'''
form_data = {
'data': import_data,
'format': 'yaml'
}

response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
self.assertHttpStatus(response, 200)
self.assertContains(response, "Record 1 console-ports: Must be a list.")

@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_import_nodict(self):
# Add all required permissions to the test user
self.add_permissions(
'dcim.view_devicetype',
'dcim.add_devicetype',
'dcim.add_consoleporttemplate',
'dcim.add_consoleserverporttemplate',
'dcim.add_powerporttemplate',
'dcim.add_poweroutlettemplate',
'dcim.add_interfacetemplate',
'dcim.add_frontporttemplate',
'dcim.add_rearporttemplate',
'dcim.add_modulebaytemplate',
'dcim.add_devicebaytemplate',
'dcim.add_inventoryitemtemplate',
)

for value in ('', 'null', '3', '"My console port"', '["My other console port"]'):
with self.subTest(value=value):
import_data = f'''
manufacturer: Manufacturer 1
model: TEST-4000
slug: test-4000
u_height: 1
console-ports:
- {value}
'''
form_data = {
'data': import_data,
'format': 'yaml'
}

response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
self.assertHttpStatus(response, 200)
self.assertContains(response, "Record 1 console-ports[1]: Must be a dictionary.")

def test_export_objects(self):
url = reverse('dcim:devicetype_list')
self.add_permissions('dcim.view_devicetype')
Expand Down
35 changes: 28 additions & 7 deletions netbox/netbox/views/generic/bulk_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def post(self, request):

class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
"""
Import objects in bulk (CSV format).
Import objects in bulk (CSV/JSON/YAML format).

Attributes:
model_form: The form used to create each imported object
Expand Down Expand Up @@ -368,7 +368,7 @@ def _compile_form_errors(self, errors, index, prefix=None):
error_messages.append(f"Record {index} {prefix}{field_name}: {err}")
return error_messages

def _save_object(self, model_form, request):
def _save_object(self, model_form, request, parent_idx):
_action = 'Updated' if model_form.instance.pk else 'Created'

# Save the primary object
Expand All @@ -381,8 +381,25 @@ def _save_object(self, model_form, request):
# Iterate through the related object forms (if any), validating and saving each instance.
for field_name, related_object_form in self.related_object_forms.items():

related_objects = model_form.data.get(field_name, list())
if not isinstance(related_objects, list):
raise ValidationError(
self._compile_form_errors(
{field_name: [_("Must be a list.")]},
index=parent_idx
)
)

related_obj_pks = []
for i, rel_obj_data in enumerate(model_form.data.get(field_name, list())):
for i, rel_obj_data in enumerate(related_objects, start=1):
if not isinstance(rel_obj_data, dict):
raise ValidationError(
self._compile_form_errors(
{f'{field_name}[{i}]': [_("Must be a dictionary.")]},
index=parent_idx,
)
)

rel_obj_data = self.prep_related_object_data(obj, rel_obj_data)
f = related_object_form(rel_obj_data)

Expand All @@ -396,7 +413,7 @@ def _save_object(self, model_form, request):
else:
# Replicate errors on the related object form to the import form for display and abort
raise ValidationError(
self._compile_form_errors(f.errors, index=i, prefix=f'{field_name}[{i}]')
self._compile_form_errors(f.errors, index=parent_idx, prefix=f'{field_name}[{i}]')
)

# Enforce object-level permissions on related objects
Expand Down Expand Up @@ -439,8 +456,12 @@ def create_and_update_objects(self, form, request):
try:
instance = prefetched_objects[object_id]
except KeyError:
form.add_error('data', _("Row {i}: Object with ID {id} does not exist").format(i=i, id=object_id))
raise ValidationError('')
raise ValidationError(
self._compile_form_errors(
{'id': [_("Object with ID {id} does not exist").format(id=object_id)]},
index=i
)
)

# Take a snapshot for change logging
if instance.pk and hasattr(instance, 'snapshot'):
Expand Down Expand Up @@ -481,7 +502,7 @@ def create_and_update_objects(self, form, request):
restrict_form_fields(model_form, request.user)

if model_form.is_valid():
obj = self._save_object(model_form, request)
obj = self._save_object(model_form, request, i)
saved_objects.append(obj)
else:
# Raise model form errors
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/cs/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12822,8 +12822,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Řádek {i}: Objekt s ID {id} neexistuje"
msgid "Object with ID {id} does not exist"
msgstr "Objekt s ID {id} neexistuje"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/da/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12857,8 +12857,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Række {i}: Objekt med ID {id} findes ikke"
msgid "Object with ID {id} does not exist"
msgstr "Objekt med ID {id} findes ikke"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -13055,8 +13055,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Reihe {i}: Objekt mit ID {id} existiert nicht"
msgid "Object with ID {id} does not exist"
msgstr "Objekt mit ID {id} existiert nicht"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
2 changes: 1 addition & 1 deletion netbox/translations/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12513,7 +12513,7 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgid "Object with ID {id} does not exist"
msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:525
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12999,8 +12999,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Fila {i}: Objeto con ID {id} no existe"
msgid "Object with ID {id} does not exist"
msgstr "Objeto con ID {id} no existe"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -13041,8 +13041,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Rangée {i}: Objet avec identifiant {id} n'existe pas"
msgid "Object with ID {id} does not exist"
msgstr "Objet avec identifiant {id} n'existe pas"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/it/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -13033,8 +13033,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Fila {i}: Oggetto con ID {id} non esiste"
msgid "Object with ID {id} does not exist"
msgstr "Oggetto con ID {id} non esiste"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/ja/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12645,8 +12645,8 @@ msgstr "選択したエクスポートテンプレートをレンダリング中

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "行 {i}: ID {id}のオブジェクトは存在しません"
msgid "Object with ID {id} does not exist"
msgstr "ID {id}のオブジェクトは存在しません"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -13000,8 +13000,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Rij {i}: Object met ID {id} bestaat niet"
msgid "Object with ID {id} does not exist"
msgstr "Object met ID {id} bestaat niet"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/pl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12920,8 +12920,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Wiersz {i}: Obiekt z identyfikatorem {id} nie istnieje"
msgid "Object with ID {id} does not exist"
msgstr "Obiekt z identyfikatorem {id} nie istnieje"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/pt/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12944,8 +12944,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Linha {i}: Objeto com ID {id} não existe"
msgid "Object with ID {id} does not exist"
msgstr "Objeto com ID {id} não existe"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/ru/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12939,8 +12939,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Ряд {i}: Объект с идентификатором {id} не существует"
msgid "Object with ID {id} does not exist"
msgstr "Объект с идентификатором {id} не существует"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/tr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12835,8 +12835,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Satır {i}: Kimliği olan nesne {id} mevcut değil"
msgid "Object with ID {id} does not exist"
msgstr "Kimliği olan nesne {id} mevcut değil"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/uk/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12920,8 +12920,8 @@ msgstr ""

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "Ряд {i}: Об'єкт з ідентифікатором {id} не існує"
msgid "Object with ID {id} does not exist"
msgstr "Об'єкт з ідентифікатором {id} не існує"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down
4 changes: 2 additions & 2 deletions netbox/translations/zh/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -12622,8 +12622,8 @@ msgstr "渲染所选导出模板时出错 ({template}): {error}"

#: netbox/netbox/views/generic/bulk_views.py:442
#, python-brace-format
msgid "Row {i}: Object with ID {id} does not exist"
msgstr "第{i}行: ID为{id}的对象不存在"
msgid "Object with ID {id} does not exist"
msgstr "ID为{id}的对象不存在"

#: netbox/netbox/views/generic/bulk_views.py:525
#, python-brace-format
Expand Down