Search code examples
djangodjango-adminwagtaildjango-import-export

How can I integrate Django Import Export and Wagtail?


I'm trying to add a model resource from django-import-export into the admin for Wagtail. The only documentation I can find says that you would do it through hooks. The problem is, I keep getting the error:

missing 2 required positional arguments: 'model' and 'admin_site'

The whole resource and ModelAdmin are:

class AccountResource(resources.ModelResource):

    class Meta:
        model = Account
        fields = ('first_name', 'last_name', 'email', 'created', 'archived')

class AccountsAdmin(ImportExportModelAdmin, ModelAdmin):
    resource_class = AccountResource
    model = Account
    menu_label = 'Accounts'  # ditch this to use verbose_name_plural from model
    menu_icon = 'group'  # change as required
    menu_order = 200  # will put in 3rd place (000 being 1st, 100 2nd)
    add_to_settings_menu = False  # or True to add your model to the Settings sub-menu
    exclude_from_explorer = False # or True to exclude pages of this type from Wagtail's explorer view
    list_display = ('first_name', 'last_name', 'email', 'created', 'archived')
    search_fields = ('first_name', 'last_name', 'email', 'created')

# Now you just need to register your customised ModelAdmin class with Wagtail
modeladmin_register(AccountsAdmin)

Any suggestions?


Solution

  • The Wagtail ModelAdmin does not share the API of the Django ModelAdmin.

    The mixins from django-import-export expect to be used with a Django ModelAdmin and won't work with Wagtail ModelAdmin as you have experienced yourself.

    For the export functionality, I have solved the problem by hooking the export_action of a Django ModelAdmin with the ExportMixin into the urls of a Wagtail ModelAdmin.

    This might not be too pretty, but allows reusing the view and the logic that is part of the ExportMixin.

    I have published an example Project on GitHub that makes use of this design.

    The actual glue code that can be found here is not all that much:

    from django.conf.urls import url
    from django.contrib.admin import ModelAdmin as DjangoModelAdmin
    from django.core.exceptions import ImproperlyConfigured
    from django.utils.http import urlencode
    from django.utils.translation import gettext_lazy as _
    from import_export.admin import ExportMixin
    from wagtail.contrib.modeladmin.helpers import ButtonHelper
    
    
    class ExporterDummySite:
        name = None
    
        def each_context(self, request):
            return {}
    
    
    class WMAExporter(ExportMixin, DjangoModelAdmin):
        export_template_name = 'wma_export/export.html'
    
        def __init__(self, wagtail_model_admin):
            self.wagtail_model_admin = wagtail_model_admin
            super().__init__(wagtail_model_admin.model, ExporterDummySite())
    
        def get_export_queryset(self, request):
            index_view = self.wagtail_model_admin.index_view_class(
                model_admin=self.wagtail_model_admin
            )
            index_view.dispatch(request)
            return index_view.get_queryset(request)
    
    
    class ExportButtonHelper(ButtonHelper):
        export_button_classnames = ['bicolor', 'icon', 'icon-download']
    
        def export_button(self, classnames_add=None, classnames_exclude=None):
            if classnames_add is None:
                classnames_add = []
            if classnames_exclude is None:
                classnames_exclude = []
            classnames = self.export_button_classnames + classnames_add
            cn = self.finalise_classname(classnames, classnames_exclude)
            return {
                'url': self.url_helper.get_action_url("export") + '?' + urlencode(self.view.params),
                'label': _('Export'),
                'classname': cn,
                'title': _('Export these %s') % self.verbose_name_plural,
            }
    
    
    class WMAExportMixin:
        button_helper_class = ExportButtonHelper
        exporter_class = None
    
        def get_admin_urls_for_registration(self):
            return super().get_admin_urls_for_registration() + (
                url(self.url_helper._get_action_url_pattern("export"),
                    self.export_view,
                    name=self.url_helper.get_action_url_name("export")),
            )
    
        def export_view(self, request):
            if self.exporter_class is None:
                raise ImproperlyConfigured(f"{self.__class__.__name__}.exporter_class not set!")
            exporter = self.exporter_class(self)
            return exporter.export_action(request)
    

    I would assume that something similar can be done to implement the import part.