2626from utilities .htmx import is_htmx
2727from utilities .permissions import get_permission_for_model
2828from utilities .views import GetReturnURLMixin
29+ from utilities .forms .choices import ImportFormatChoices
2930from .base import BaseMultiObjectView
3031from .mixins import ActionsMixin , TableMixin
3132from .utils import get_prerequisite_model
@@ -288,7 +289,7 @@ def post(self, request):
288289 })
289290
290291
291- class OldBulkImportView (GetReturnURLMixin , BaseMultiObjectView ):
292+ class BulkImportView (GetReturnURLMixin , BaseMultiObjectView ):
292293 """
293294 Import objects in bulk (CSV format).
294295
@@ -297,147 +298,14 @@ class OldBulkImportView(GetReturnURLMixin, BaseMultiObjectView):
297298 """
298299 template_name = 'generic/bulk_import.html'
299300 model_form = None
301+ related_object_forms = dict ()
300302
301- def _import_form (self , * args , ** kwargs ):
302-
303- class ImportForm (BootstrapMixin , Form ):
304- csv = CSVDataField (
305- from_form = self .model_form
306- )
307- csv_file = CSVFileField (
308- label = "CSV file" ,
309- from_form = self .model_form ,
310- required = False
311- )
312-
313- def clean (self ):
314- csv_rows = self .cleaned_data ['csv' ][1 ] if 'csv' in self .cleaned_data else None
315- csv_file = self .files .get ('csv_file' )
316-
317- # Check that the user has not submitted both text data and a file
318- if csv_rows and csv_file :
319- raise ValidationError (
320- "Cannot process CSV text and file attachment simultaneously. Please choose only one import "
321- "method."
322- )
323-
324- return ImportForm (* args , ** kwargs )
325-
326- def _create_objects (self , form , request ):
327- new_objs = []
328- if request .FILES :
329- headers , records = form .cleaned_data ['csv_file' ]
330- else :
331- headers , records = form .cleaned_data ['csv' ]
332-
333- for row , data in enumerate (records , start = 1 ):
334- obj_form = self .model_form (data , headers = headers )
335- restrict_form_fields (obj_form , request .user )
336-
337- if obj_form .is_valid ():
338- obj = self ._save_obj (obj_form , request )
339- new_objs .append (obj )
340- else :
341- for field , err in obj_form .errors .items ():
342- form .add_error ('csv' , f'Row { row } { field } : { err [0 ]} ' )
343- raise ValidationError ("" )
344-
345- return new_objs
346-
347- def _save_obj (self , obj_form , request ):
303+ def prep_related_object_data (self , parent , data ):
348304 """
349- Provide a hook to modify the object immediately before saving it (e.g. to encrypt secret data).
305+ Hook to modify the data for related objects before it's passed to the related object form (for example, to
306+ assign a parent object).
350307 """
351- return obj_form .save ()
352-
353- def get_required_permission (self ):
354- return get_permission_for_model (self .queryset .model , 'add' )
355-
356- #
357- # Request handlers
358- #
359-
360- def get (self , request ):
361-
362- return render (request , self .template_name , {
363- 'model' : self .model_form ._meta .model ,
364- 'form' : self ._import_form (),
365- 'fields' : self .model_form ().fields ,
366- 'return_url' : self .get_return_url (request ),
367- ** self .get_extra_context (request ),
368- })
369-
370- def post (self , request ):
371- logger = logging .getLogger ('netbox.views.BulkImportView' )
372- form = self ._import_form (request .POST , request .FILES )
373-
374- if form .is_valid ():
375- logger .debug ("Form validation was successful" )
376-
377- try :
378- # Iterate through CSV data and bind each row to a new model form instance.
379- with transaction .atomic ():
380- new_objs = self ._create_objects (form , request )
381-
382- # Enforce object-level permissions
383- if self .queryset .filter (pk__in = [obj .pk for obj in new_objs ]).count () != len (new_objs ):
384- raise PermissionsViolation
385-
386- # Compile a table containing the imported objects
387- obj_table = self .table (new_objs )
388-
389- if new_objs :
390- msg = 'Imported {} {}' .format (len (new_objs ), new_objs [0 ]._meta .verbose_name_plural )
391- logger .info (msg )
392- messages .success (request , msg )
393-
394- return render (request , "import_success.html" , {
395- 'table' : obj_table ,
396- 'return_url' : self .get_return_url (request ),
397- })
398-
399- except ValidationError :
400- clear_webhooks .send (sender = self )
401-
402- except (AbortRequest , PermissionsViolation ) as e :
403- logger .debug (e .message )
404- form .add_error (None , e .message )
405- clear_webhooks .send (sender = self )
406-
407- else :
408- logger .debug ("Form validation failed" )
409-
410- return render (request , self .template_name , {
411- 'model' : self .model_form ._meta .model ,
412- 'form' : form ,
413- 'fields' : self .model_form ().fields ,
414- 'return_url' : self .get_return_url (request ),
415- ** self .get_extra_context (request ),
416- })
417-
418-
419- class BulkImportView (GetReturnURLMixin , BaseMultiObjectView ):
420- """
421- Import objects in bulk (CSV format).
422-
423- Attributes:
424- model_form: The form used to create each imported object
425- """
426- template_name = 'generic/bulk_import.html'
427- model_form = None
428-
429- '''
430- supported_formats = [
431- {
432- 'name': 'CSV',
433- 'help_text': 'Enter the list of column headers followed by one line per record to be imported, using ' \
434- 'commas to separate values. Multi-line data and values containing commas may be wrapped ' \
435- 'in double quotes.'
436- },
437- {'name': 'JSON', },
438- {'name': 'YAML', },
439- ]
440- '''
308+ return data
441309
442310 def _create_object (self , request , model_form ):
443311
@@ -478,16 +346,16 @@ def _create_object(self, request, model_form):
478346
479347 return obj
480348
481- def _create_objects (self , form , request ):
349+ def _create_objects (self , form , format , data , request ):
482350 new_objs = []
483351 for row_num , record in enumerate (data ['data' ], start = 1 ):
484- if format == 'csv' :
485- model_form = self .model_form (record , headers = headers )
352+ if format == ImportFormatChoices . CSV :
353+ model_form = self .model_form (record , headers = data [ ' headers' ] )
486354 else :
487355 model_form = self .model_form (record )
488356 restrict_form_fields (model_form , request .user )
489357
490- if format == 'json' or format == 'yaml' :
358+ if format == ImportFormatChoices . JSON or format == ImportFormatChoices . YAML :
491359 # Assign default values for any fields which were not specified.
492360 # We have to do this manually because passing 'initial=' to the form
493361 # on initialization merely sets default values for the widgets.
@@ -505,8 +373,8 @@ def _create_objects(self, form, request):
505373 # Replicate model form errors for display
506374 for field , errors in model_form .errors .items ():
507375 for err in errors :
508- if format == 'csv' :
509- form .add_error ('csv' , f'Row { row } { field } : { err [ 0 ] } ' )
376+ if format == ImportFormatChoices . CSV :
377+ form .add_error (None , f'Row { row_num } { field } : { err } ' )
510378 else :
511379 if field == '__all__' :
512380 form .add_error (None , err )
@@ -530,45 +398,50 @@ def get_required_permission(self):
530398 # Request handlers
531399 #
532400
533- def get_context (self , request , data_form , file_form ):
401+ def get_context (self , request , data_form , file_form , form = None ):
402+ # small hack - need to return 'form' set to either the file or data form
403+ # as the bulk_import base view relies on it for error reporting.
534404 return {
535405 'model' : self .model_form ._meta .model ,
536406 'data_form' : data_form ,
407+ 'form' : form ,
537408 'file_form' : file_form ,
538409 'fields' : self .model_form ().fields ,
539410 'return_url' : self .get_return_url (request ),
540411 ** self .get_extra_context (request ),
541412 }
542413
543414 def get (self , request ):
544- data_form = ImportForm ()
545- file_form = FileUploadImportForm ()
415+ data_form = ImportForm (related = self . related_object_forms )
416+ file_form = FileUploadImportForm (related = self . related_object_forms )
546417
547418 return render (request , self .template_name , self .get_context (request , data_form , file_form ))
548419
549420 def post (self , request ):
550421 logger = logging .getLogger ('netbox.views.BulkImportView' )
551- data_form = ImportForm (request .POST )
552- file_form = FileUploadImportForm (request .POST , request .FILES )
422+ data_form = ImportForm (request .POST , related = self . related_object_forms )
423+ file_form = FileUploadImportForm (request .POST , request .FILES , related = self . related_object_forms )
553424
554425 data = None
555- if 'data_submit' in request .POST :
556- if data_form .is_valid ():
557- logger .debug ("Data Import form validation was successful" )
558- data = data_form .cleaned_data
559- elif 'file_submit' in request .POST :
426+ form = None
427+ if 'file_submit' in request .POST :
428+ form = file_form
560429 if file_form .is_valid ():
561430 logger .debug ("File Import form validation was successful" )
562431 data = file_form .cleaned_data
432+ else : # data_submit
433+ form = data_form
434+ if data_form .is_valid ():
435+ logger .debug ("Data Import form validation was successful" )
436+ data = data_form .cleaned_data
563437
564438 if data :
565439 format = data ['format' ]
566- headers = data ['headers' ] if format == 'csv' else None
567440
568441 try :
569442 # Iterate through data and bind each row to a new model form instance.
570443 with transaction .atomic ():
571- new_objs = self ._create_objects (form , request )
444+ new_objs = self ._create_objects (form , format , data , request )
572445
573446 # Enforce object-level permissions
574447 if self .queryset .filter (pk__in = [obj .pk for obj in new_objs ]).count () != len (new_objs ):
@@ -598,7 +471,7 @@ def post(self, request):
598471 else :
599472 logger .debug ("Form validation failed" )
600473
601- return render (request , self .template_name , self .get_context (request , data_form , file_form ))
474+ return render (request , self .template_name , self .get_context (request , data_form , file_form , form ))
602475
603476
604477class BulkEditView (GetReturnURLMixin , BaseMultiObjectView ):
0 commit comments