title: How to use Django-import-export ImportForm and ConfirmImportForm outside admin
body: <p>I want to use <strong>django-import-export</strong>'s forms to implement the import feature for regular users, so it needs to be outside the admin section. </p>
  
  <p>So far, all the implementations I have found are about<br>
  a) <a href="https://stackoverflow.com/questions/52335510/extend-django-import-exports-import-form-to-specify-fixed-value-for-each-import">extending the feature inside the admin</a> or<br>
  b) <a href="https://stackoverflow.com/questions/34739370/django-import-export-outside-admin">reimplementing the default views outside the admin</a>. </p>
  
  <p>But since the default forms work perfectly (specially the <strong>ConfirmImportForm</strong> that shows a diff between old records and new records attempting to be imported) I would like to subclass them as part of my project (outside admin) without reimplementing the whole view's logic, if such thing is even possible.</p>
  
  <p>So far I tried (foolishly, I'm afraid) to subclass the <strong>import_export.admin.ImportMixin</strong> as two separated class views to import a model <em>Period</em> with resource <em>PeriodResource</em>. The methods <em>import_action</em> and <em>process_import</em> were reimplemented (basically copy and paste the same code and eliminating any code using <em>self.site_admin</em>) as View.get() and View.post():</p>
  
  <pre class="lang-py prettyprint-override"><code># staging/views.py
  from django.http import HttpResponseRedirect, HttpResponse
  from django.urls import reverse
  from django.views import generic
  from django.template.response import TemplateResponse
  from django.utils.translation import gettext_lazy as _
  from django.utils.encoding import force_text
  
  from import_export.forms import ConfirmImportForm
  from import_export.signals import post_import
  import import_export.admin
  
  from .models import Period
  from .resources import PeriodResource
  
  class PeriodImportView(import_export.admin.ImportMixin, generic.View):
      """
      Subclassing of ImportMixin as a generic View implementing ImportForm
      """
      #: template for import view
      import_template_name = 'period/import.html'
      #: resource class
      resource_class = PeriodResource
      #: model to be imported
      model = Period
  
      def get_confirm_import_form(self):
          '''
          Get the form type used to display the results and confirm the upload.
          '''
          return ConfirmImportForm
  
      def get(self, request, *args, **kwargs):
          """
          Overriding the GET part of ImportMixin.import_action method to be used without site_admin
          """
  
          resource = self.get_import_resource_class()(**self.get_import_resource_kwargs(request, *args, **kwargs))
  
          context = self.get_import_context_data()
  
          import_formats = self.get_import_formats()
          form_type = self.get_import_form()
          form = form_type(import_formats,
                           request.POST or None,
                           request.FILES or None)
  
           # context.update(self.admin_site.each_context(request))
  
          context['title'] = _("Import")
          context['form'] = form
          context['opts'] = self.model._meta
          context['fields'] = [f.column_name for f in resource.get_user_visible_fields()]
  
          # request.current_app = self.admin_site.name
          return TemplateResponse(request, [self.import_template_name],
                                  context)
  
  
      def post(self, request, *args, **kwargs):
          """
          Overriding the POST part of ImportMixin.import_action method to be used without site_admin
          """
  
          resource = self.get_import_resource_class()(**self.get_import_resource_kwargs(request, *args, **kwargs))
  
          context = self.get_import_context_data()
  
          import_formats = self.get_import_formats()
          form_type = self.get_import_form()
          form = form_type(import_formats,
                           request.POST or None,
                           request.FILES or None)
  
          if request.POST and form.is_valid():
              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.write_to_tmp_storage(import_file, input_format)
  
              # then read the file, using the proper format-specific mode
              # warning, big files may exceed memory
              try:
                  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 = input_format.create_dataset(data)
              except UnicodeDecodeError as ex1:
                  return HttpResponse(_(u"&lt;h1&gt;Imported file has a wrong encoding: %s&lt;/h1&gt;" % ex1))
              except Exception as ex2:
                  return HttpResponse(_(u"&lt;h1&gt;%s encountered while trying to read file: %s&lt;/h1&gt;" % (type(ex2).__name__, import_file.name)))
              result = resource.import_data(dataset, dry_run=True,
                                            raise_errors=False,
                                            file_name=import_file.name,
                                            user=request.user)
  
              context['result'] = result
  
              if not result.has_errors() and not result.has_validation_errors():
                  context['confirm_form'] = self.get_confirm_import_form()(initial={
                      'import_file_name': tmp_storage.name,
                      'original_file_name': import_file.name,
                      'input_format': form.cleaned_data['input_format'],
                  })
  
          # context.update(self.admin_site.each_context(request))
  
          context['title'] = _("Import")
          context['form'] = form
          context['opts'] = self.model._meta
          context['fields'] = [f.column_name for f in resource.get_user_visible_fields()]
  
          # request.current_app = self.admin_site.name
          return TemplateResponse(request, [self.import_template_name],
                                  context)
  
  class PeriodConfirmImportView(import_export.admin.ImportMixin, generic.View):
      """
      Subclassing of ImportMixin as a generic View implementing ConfirmImportForm
      """
      #: template for import view
      import_template_name = 'period/import.html'
      #: resource class
      resource_class = PeriodResource
      #: model to be imported
      model = Period
  
      def post(self, request, *args, **kwargs):
          """
          Perform the actual import action (after the user has confirmed the import)
          """
          # if not self.has_import_permission(request):
          #     raise PermissionDenied
  
          confirm_form = ConfirmImportForm(request.POST)
          if confirm_form.is_valid():
              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 = input_format.create_dataset(data)
  
              result = self.process_dataset(dataset, confirm_form, request, *args, **kwargs)
  
              tmp_storage.remove()
  
              self.generate_log_entries(result, request)
              self.add_success_message(result, request)
              post_import.send(sender=None, model=self.model)
  
              url = reverse('staging:index')
              return HttpResponseRedirect(url)
  </code></pre>
  
  <p>and then just show the forms in the template:</p>
  
  <pre class="lang-html prettyprint-override"><code># staging/templates/period/import.html
  {% if confirm_form %}
  &lt;form action="{% url 'staging:confirm_import_period' %}" method="POST"&gt;
    {% csrf_token %}
    {{ confirm_form.as_p }}
    &lt;p&gt;
      {% trans "Below is a preview of data to be imported. If you are satisfied with the results, click 'Confirm import'" %}
    &lt;/p&gt;
    &lt;div class="submit-row"&gt;
      &lt;input type="submit" class="default" name="confirm" value="{% trans "Confirm import" %}"&gt;
    &lt;/div&gt;
  &lt;/form&gt;
  {% else %}
  &lt;form action="" method="post" enctype="multipart/form-data"&gt;
    {% csrf_token %}
  
    &lt;p&gt;
      {% trans "This importer will import the following fields: " %}
      &lt;code&gt;{{ fields|join:", " }}&lt;/code&gt;
    &lt;/p&gt;
  
    &lt;fieldset class="module aligned"&gt;
      {% for field in form %}
        &lt;div class="form-row"&gt;
          {{ field.errors }}
  
          {{ field.label_tag }}
  
          {{ field }}
  
          {% if field.field.help_text %}
          &lt;p class="help"&gt;{{ field.field.help_text|safe }}&lt;/p&gt;
          {% endif %}
        &lt;/div&gt;
      {% endfor %}
    &lt;/fieldset&gt;
  
    &lt;div class="submit-row"&gt;
      &lt;input type="submit" class="default" value="{% trans "Submit" %}"&gt;
    &lt;/div&gt;
  &lt;/form&gt;
  {% endif %}
  </code></pre>
  
  <p>and my urls.py looks like this:</p>
  
  <pre class="lang-py prettyprint-override"><code># staging/urls.py
  from django.urls import path
  from .views import PeriodIndexView, PeriodImportView, PeriodConfirmImportView
  
  app_name = 'staging'
  
  urlpatterns = [
      path('period/', PeriodIndexView.as_view(), name='index'),
      path('period/import/', PeriodImportView.as_view(), name='import_period'),
      path('period/confirm_import/', PeriodConfirmImportView.as_view(), name='confirm_import_period'),
  ]
  </code></pre>
  
  <p>So far it works as intended, but this approach is so tightly coupled to the internal implementation of <em>ImportMixin</em> that I am afraid it will not survive any version upgrade of <em>django-import-export</em>. </p>
  
  <p>Is there any way to achieve that without reimplementing the whole import_action and process_import methods?</p>