from django.views.generic import FormView from django.utils.translation import ugettext_lazy as _ from django.contrib import messages from import_export.formats import base_formats from import_export.forms import ImportForm, ConfirmImportForm from import_export.resources import modelresource_factory from django.http import HttpResponseRedirect from import_export.tmp_storages import TempFolderStorage try: from django.utils.encoding import force_text except ImportError: from django.utils.encoding import force_unicode as force_text from django.conf import settings TMP_STORAGE_CLASS = getattr(settings, 'IMPORT_EXPORT_TMP_STORAGE_CLASS', TempFolderStorage) DEFAULT_FORMATS = ( base_formats.CSV, base_formats.XLS, base_formats.TSV, base_formats.ODS, base_formats.JSON, base_formats.YAML, base_formats.HTML, ) class ImportView(FormView): """ Combines django.views.generic.FormView with import_export.admin.ImportExportMixin to reproduce, in a CBV, the Django admin import integration from Django-import-export. """ import_resource_class = None resource_class = None form_class = ImportForm confirm_form_class = ConfirmImportForm model = None formats = DEFAULT_FORMATS from_encoding = "utf-8" tmp_storage_class = None success_url = None # NB serve .as_view(confirm=True) at a different URL to perform the import confirm = False def get(self, request, *args, **kwargs): form = self.get_form() resource = self.get_import_resource_class()() return self.render_to_response(self.get_context_data( form=form, fields=[f.column_name for f in resource.get_fields()] )) def get_form_kwargs(self, import_formats=True): kwargs = super().get_form_kwargs() if import_formats is True: kwargs['import_formats'] = self.get_import_formats() return kwargs def get_form(self, form_class=None): if form_class is None: form_class = self.get_form_class() if form_class is self.form_class: import_formats = True else: import_formats = False return form_class(**self.get_form_kwargs(import_formats=import_formats)) def post(self, request, *args, **kwargs): if self.confirm is True: confirm_form = self.get_form(form_class=self.confirm_form_class) if confirm_form.is_valid(): return self.process_import(confirm_form) return super().post(request, *args, **kwargs) def get_import_formats(self): """ Returns available import formats. """ return [f for f in self.formats if f().can_import()] def create_dataset(self, input_format, data): """ Separate, so that this is hookable for extra logic. """ dataset = input_format.create_dataset(data) return dataset def get_resource_class(self): if not self.resource_class: return modelresource_factory(self.model) else: return self.resource_class def get_import_resource_class(self): """ Returns ResourceClass to use for import. """ return self.get_resource_class() def get_tmp_storage_class(self): if self.tmp_storage_class is None: return TMP_STORAGE_CLASS else: return self.tmp_storage_class def form_valid(self, form): """ This bit reproduces ImportMixin.import_action Perform a dry_run of the import to make sure the import will not result in errors. If there where no error, save the user uploaded file to a local temp file that will be used by 'process_import' for the actual import. """ resource = self.get_import_resource_class()() context = {} import_formats = self.get_import_formats() input_format = import_formats[ int(form.cleaned_data['input_format']) ]() import_file = form.cleaned_data['import_file'] # first always write the uploaded file to disk as it may be a # memory file or else based on settings upload handlers tmp_storage = self.get_tmp_storage_class()() data = bytes() for chunk in import_file.chunks(): data += chunk tmp_storage.save(data, input_format.get_read_mode()) # then read the file, using the proper format-specific mode # warning, big files may exceed memory data = tmp_storage.read(input_format.get_read_mode()) if not input_format.is_binary() and self.from_encoding: data = force_text(data, self.from_encoding) dataset = self.create_dataset(input_format, data) result = resource.import_data(dataset, dry_run=True, raise_errors=False, file_name=import_file.name, user=self.request.user) context['result'] = result if not result.has_errors(): context['confirm_form'] = self.confirm_form_class(initial={ 'import_file_name': tmp_storage.name, 'original_file_name': import_file.name, 'input_format': form.cleaned_data['input_format'], }) return self.render_to_response(self.get_context_data( form=form, fields=[f.column_name for f in resource.get_fields()], **context)) def process_import(self, confirm_form): """ This bit reproduces ImportMixin.process_import """ resource = self.get_import_resource_class()() import_formats = self.get_import_formats() input_format = import_formats[ int(confirm_form.cleaned_data['input_format']) ]() tmp_storage = self.get_tmp_storage_class()( name=confirm_form.cleaned_data['import_file_name']) data = tmp_storage.read(input_format.get_read_mode()) if not input_format.is_binary() and self.from_encoding: data = force_text(data, self.from_encoding) dataset = self.create_dataset(input_format, data) resource.import_data( dataset, dry_run=False, raise_errors=True, file_name=confirm_form.cleaned_data['original_file_name'], user=self.request.user) success_message = _('Import finished') messages.success(self.request, success_message) tmp_storage.remove() return HttpResponseRedirect(self.get_success_url())