【问题标题】:How to use Django-import-export ImportForm and ConfirmImportForm outside admin如何在管理员之外使用 Django-import-export ImportForm 和 ConfirmImportForm
【发布时间】:2019-09-07 19:49:46
【问题描述】:

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

到目前为止,我发现的所有实现都是关于
a) extending the feature inside the admin
b)reimplementing the default views outside the admin

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

到目前为止,我尝试(很愚蠢,恐怕)将 import_export.admin.ImportMixin 子类化为两个独立的类视图,以导入具有资源 Period 的模型em>PeriodResource。 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】:

    经过多次尝试和错误,我放弃了避免从 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 中,您可以子类化特定的类来导入我的特定模型,只需设置 model 和 resource_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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-29
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      相关资源
      最近更新 更多