如何获取外键的ID用作批量文件导入的列?

时间:2019-07-12 15:38:25

标签: python django django-models django-forms django-import-export

我正在与Django import / export库一起使用XLSX / CSV文件批量上传模型。

我有两种模式-公司和竞争对手。竞争对手与公司有多对多关系。我希望管理员用户能够上传一堆竞争对手的名称,并能够选择他们都对应的公司。我要在另一列中标记与之对应的公司的ID。我该怎么办?

我已经按照图书馆Getting Started页上的说明进行操作,但我只是无法保留公司ID。

这些是我的模特:

# app/models.py

from django.db import models

class Company(models.Model):

    name = models.CharField(max_length=200)
    website = models.CharField(max_length=200)


class Competitor(models.Model):

    company = models.ForeignKey(
        Company,
        on_delete=models.CASCADE,
        verbose_name='The related company'
    )

    competitor_name = models.CharField(max_length=200)
    competitor_website = models.CharField(max_length=200)

forms.py中,我定义了自定义表单,允许用户从已定义的公司记录列表中进行选择。

# app/forms.py

from django import forms
from import_export.admin import ImportForm, ConfirmImportForm

from .models import Company, Competitor


class CompetitorImportForm(ImportForm):
    company = forms.ModelChoiceField(
        queryset=Company.objects.all(),
        required=True
    )


class CompetitorConfirmImportForm(ConfirmImportForm):
    company = forms.ModelChoiceField(
        queryset=Company.objects.all(),
        required=True
    )

resources.py上设置我的进出口资源

from import_export import resources

from .models import Company, Competitor


class CompanyResource(resources.ModelResource):

    class Meta:
        model = Company


class CompetitorResource(resources.ModelResource):

    class Meta:
        model = Competitor
        skip_unchanged = True
        report_skipped = True

admin.py中,我将ImportExportMixIn细分为自定义界面并允许公司选择。

# app/admin.py

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

from .models import Company, Competitor
from .forms import CompetitorImportForm, CompetitorConfirmImportForm
from .resources import CompanyResource, CompetitorResource


class CompanyAdmin(ImportExportModelAdmin):
    list_display = ('id', 'name', 'website', 'theme', 'active', 'frequency')
    resource_class = CompanyResource


class CustomCompetitorAdmin(ImportExportMixin, admin.ModelAdmin):
    resource_class = CompetitorResource

    def get_import_form(self):
        return CompetitorImportForm

    def get_confirm_import_form(self):
        return CompetitorConfirmImportForm

    def get_form_kwargs(self, form, *args, **kwargs):
        # pass on `author` to the kwargs for the custom confirm form
        if isinstance(form, CompetitorImportForm):
            if form.is_valid():
                company = form.cleaned_data['company']
                kwargs.update({'company': company.id})
        return kwargs


admin.site.register(Company, CompanyAdmin)
admin.site.register(Competitor, CustomCompetitorAdmin)

我当前收到的错误消息是:

Line number: 1 - (1048, "Column 'company_id' cannot be null")

因为我的竞争对手模型中不允许包含null。但是,如果它会从所选公司提取ID,就不会为空。

1 个答案:

答案 0 :(得分:0)

经过反复试验,我遵循this guide找到了解决方案。

首先在before_import_row的资源类中使用resources.py钩子。

# app/resources.py

class CompetitorResource(resources.ModelResource):

    def __init__(self, request=None):
        """
        This class uses Django sessions to carry the 'company' value from the import form to the import confirmation
          screen. We're not using sessions, so this gives another way to get the extra request data in. It works for
          both the dry-run preview and the actual import.
        :param request: Since 'self.request' doesn't exist in the default ModelResource we have to install it by giving
          competitor resource a custom constructor.
        """
        super(CompetitorResource, self).__init__()
        self.request = request

    class Meta:
        model = Competitor
        export_order = ('company', 'competitor_name')
        skip_unchanged = True
        report_skipped = True

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

然后使用admin.py钩子将其插入get_resource_kwargs

# app/admin.py

class CustomCompetitorAdmin(ImportExportModelAdmin):
    list_display = ('id', 'competitor_name', 'competitor_website', 'company')
    resource_class = CompetitorResource

    def get_import_form(self):
        return CompetitorImportForm

    def get_confirm_import_form(self):
        return CompetitorConfirmImportForm

    def get_resource_kwargs(self, request, *args, **kwargs):
        """
        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.
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        rk = super().get_resource_kwargs(request, *args, **kwargs)
        rk['request'] = request
        return rk

我现在可以从表单的下拉列表中选择一个外键字段,并将其作为表列。