如何在admin之外使用Django-import-export ImportForm和ConfirmImportForm

问题描述 投票:0回答:1

我想使用 django-import-export 的表单来为普通用户实现导入功能,因此它需要位于管理部分之外。

到目前为止,我找到的所有实现都是关于
a) 扩展管理内部的功能
b) 在管理员之外重新实现默认视图

但是由于默认表单工作完美(特别是ConfirmImportForm,它显示了旧记录和尝试导入的新记录之间的差异),我想将它们子类化为我的项目的一部分(在管理之外),而不需要重新实现整个视图的逻辑,如果这样的事情是可能的话。

到目前为止,我尝试(恐怕是愚蠢的)将 import_export.admin.ImportMixin 子类化为两个独立的类视图,以导入带有资源 PeriodResource 的模型 Period。方法import_actionprocess_import被重新实现(基本上复制并粘贴相同的代码并使用self.site_admin消除任何代码)作为View.get()和View.post():

# 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"<h1>Imported file has a wrong encoding: %s</h1>" % ex1))
            except Exception as ex2:
                return HttpResponse(_(u"<h1>%s encountered while trying to read file: %s</h1>" % (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)

然后只显示模板中的表单:

# staging/templates/period/import.html
{% if confirm_form %}
<form action="{% url 'staging:confirm_import_period' %}" method="POST">
  {% csrf_token %}
  {{ confirm_form.as_p }}
  <p>
    {% trans "Below is a preview of data to be imported. If you are satisfied with the results, click 'Confirm import'" %}
  </p>
  <div class="submit-row">
    <input type="submit" class="default" name="confirm" value="{% trans "Confirm import" %}">
  </div>
</form>
{% else %}
<form action="" method="post" enctype="multipart/form-data">
  {% csrf_token %}

  <p>
    {% trans "This importer will import the following fields: " %}
    <code>{{ fields|join:", " }}</code>
  </p>

  <fieldset class="module aligned">
    {% for field in form %}
      <div class="form-row">
        {{ field.errors }}

        {{ field.label_tag }}

        {{ field }}

        {% if field.field.help_text %}
        <p class="help">{{ field.field.help_text|safe }}</p>
        {% endif %}
      </div>
    {% endfor %}
  </fieldset>

  <div class="submit-row">
    <input type="submit" class="default" value="{% trans "Submit" %}">
  </div>
</form>
{% endif %}

我的 urls.py 看起来像这样:

# 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'),
]

到目前为止,它按预期工作,但这种方法与 ImportMixin 的内部实现紧密耦合,恐怕它无法在 django-import-export 的任何版本升级中生存。

有没有办法在不重新实现整个 import_action 和 process_import 方法的情况下实现这一点?

python django django-forms django-views django-import-export
1个回答
1
投票

经过多次尝试和错误,我放弃了避免重新实现 import_export.admin.ImportMixin 中的方法 import_actionprocess_import。相反,我创建了自己的 mixins 子类化 import_export.admin.ImportMixindjango.views.generic.View 并将所有对 self.site_admin 的引用从方法 import_actionprocess_import 删除到等效方法 get( )post().

# 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 ImportView(import_export.admin.ImportMixin, generic.View):
        """
        Subclassing of ImportMixin as a generic View implementing ImportForm
        """
        #: template for import view
        import_template_name = 'import.html'
        #: resource class
        resource_class = None
        #: model to be imported
        model = None

        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
            """
            return self.post(request, *args, **kwargs)


        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 = self.get_import_form()(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"<h1>Imported file has a wrong encoding: %s</h1>" % ex1))
                except Exception as ex2:
                    return HttpResponse(_(u"<h1>%s encountered while trying to read file: %s</h1>" % (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 " + self.get_model_info()[1])
            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 ConfirmImportView(import_export.admin.ImportMixin, generic.View):
        """
        Subclassing of ImportMixin as a generic View implementing ConfirmImportForm
        """
        #: template for import view
        import_template_name = 'import.html'
        #: resource class
        resource_class = None
        #: model to be imported
        model = None

        def get_confirm_import_form(self):
            '''
            Get the form type used to display the results and confirm the upload.
            '''
            return ConfirmImportForm

        def post(self, request, *args, **kwargs):
            """
            Perform the actual import action (after the user has confirmed the import)
            """

            confirm_form = self.get_confirm_import_form()(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('{}:{}_index'.format(self.get_model_info()[0], self.get_model_info()[1]))
                return HttpResponseRedirect(url)

所以现在从我的自定义 mixins ImportViewConfirmImportView 中,您可以对特定类进行子类化以导入我的特定模型,只需设置 modelresource_class 属性,这正是我所寻找的。

    class PeriodImportView(ImportView):
        """
        ImportView specific for model Period and resource PeriodResource
        """
        #: resource class
        resource_class = PeriodResource
        #: model to be imported
        model = Period

    class PeriodConfirmImportView(ConfirmImportView):
        """
        ConfirmImportView specific for model Period and resource PeriodResource
        """
        #: resource class
        resource_class = PeriodResource
        #: model to be imported
        model = Period
© www.soinside.com 2019 - 2024. All rights reserved.