Extend django-import-export's import form to specify fixed value for each imported row

拜拜、爱过 提交于 2019-12-20 03:13:58

问题


I am using django-import-export 1.0.1 with admin integration in Django 2.1.1. I have two models

from django.db import models

class Sector(models.Model):
    code = models.CharField(max_length=30, primary_key=True)

class Location(models.Model):
    code = models.CharField(max_length=30, primary_key=True)
    sector = ForeignKey(Sector, on_delete=models.CASCADE, related_name='locations')

and they can be imported/exported just fine using model resources

from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget

class SectorResource(resources.ModelResource):
    code = Field(attribute='code', column_name='Sector')
    class Meta:
        model = Sector
        import_id_fields = ('code',)

class LocationResource(resources.ModelResource):
    code = Field(attribute='code', column_name='Location')
    sector = Field(attribute='sector', column_name='Sector',
                   widget=ForeignKeyWidget(Sector, 'code'))
    class Meta:
        model = Location
        import_id_fields = ('code',)

and import/export actions can be integrated into the admin by

from django.contrib import admin
from import_export.admin import ImportExportModelAdmin

class SectorAdmin(ImportExportModelAdmin):
    resource_class = SectorResource

class LocationAdmin(ImportExportModelAdmin):
    resource_class = LocationResource

admin.site.register(Sector, SectorAdmin)
admin.site.register(Location, LocationAdmin)

For Reasons™, I would like to change this set-up so that a spreadsheet of Locations which does not contain a Sector column can be imported; the value of sector (for each imported row) should be taken from an extra field on the ImportForm in the admin.

Such a field can indeed be added by overriding import_action on the ModelAdmin as described in Extending the admin import form for django import_export. The next step, to use this value for all imported rows, is missing there, and I have not been able to figure out how to do it.


回答1:


EDIT(2): Solved through the use of sessions. Having a get_confirm_import_form hook would still really help here, but even better would be having the existing ConfirmImportForm carry across all the submitted fields & values from the initial import form.

EDIT: I'm sorry, I thought I had this nailed, but my own code wasn't working as well as I thought it was. This doesn't solve the problem of passing along the sector form field in the ConfirmImportForm, which is necessary for the import to complete. Currently looking for a solution which doesn't involve pasting the whole of import_action() into an ImportMixin subclass. Having a get_confirm_import_form() hook would help a lot here.

Still working on a solution for myself, and when I have one I'll update this too.


Don't override import_action. It's a big complicated method that you don't want to replicate. More importantly, as I discovered today: there are easier ways of doing this.

First (as you mentioned), make a custom import form for Location that allows the user to choose a Sector:

class LocationImportForm(ImportForm):
    sector = forms.ModelChoiceField(required=True, queryset=Sector.objects.all())

In the Resource API, there's a before_import_row() hook that is called once per row. So, implement that in your LocationResource class, and use it to add the Sector column:

def before_import_row(self, row, **kwargs):
    sector = self.request.POST.get('sector', None)
    if contract:
        self.request.session['import_context_sector'] = sector
    else:
        # if this raises a KeyError, we want to know about it.
        # It means that we got to a point of importing data without
        # contract context, and we don't want to continue.
        try:
            sector = self.request.session['import_context_sector']
        except KeyError as e:
            raise Exception("Sector context failure on row import, " +
                                 f"check resources.py for more info: {e}")
    row['sector'] = sector

(Note: This code uses Django sessions to carry the sector value from the import form to the import confirmation screen. If you're not using sessions, you'll need to find another way to do it.)

This is all you need to get the extra data in, and it works for both the dry-run preview and the actual import.

Note that self.request doesn't exist in the default ModelResource - we have to install it by giving LocationResource a custom constructor:

def __init__(self, request=None):
    super()
    self.request = request

(Don't worry about self.request sticking around. Each LocationResource instance doesn't persist beyond a single request.)

The request isn't usually passed to the ModelResource constructor, so we need to add it to the kwargs dict for that call. Fortunately, Django Import/Export has a dedicated hook for that. Override ImportExportModelAdmin's get_resource_kwargs method in LocationAdmin:

def get_resource_kwargs(self, request, *args, **kwargs):
    rk = super().get_resource_kwargs(request, *args, **kwargs)
    rk['request'] = request
    return rk

And that's all you need.



来源:https://stackoverflow.com/questions/52335510/extend-django-import-exports-import-form-to-specify-fixed-value-for-each-import

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!