django-import-exportは
インポート・エクスポートできるだけじゃない
〜カスタマイズと使いこなし〜
Takanori Suzuki
BPStyle 180 / 2026 Jan 8
はなすこと 
なんのために?
django-import-exportの基本
django-import-exportの使いこなし
なんのために? 
明治図書のマナビリア案件で必要 
教材データを原稿作成環境から本番環境に
コピーするため
教材データとは 
以下のようなデータがDBに入っている
学習教材の目次構成
問題文
解答形式
正解
教材データのコピーが必要 
作成中の教材は間違いがある
レビューして修正
レビューOKな教材のみが本番にある
別環境で作成した教材データを
本番環境にコピーする運用
既存の教材データコピー 
原稿作成環境(Edit)から本番環境(Production)にExcelファイルで教材データをコピー
architecture-beta
service stg(logos:aws-ecs)[Edit]
service excel(vscode-icons:file-type-excel)[Excel]
service prd(logos:aws-ecs)[Production]
stg:R --> L:excel
excel:R --> L:prd
運用できていたわけですが… 
マナビリアCBTが爆誕!! [1] 
新しい教材データコピーが必要 
教材データの構成は似ているが微妙に違う
Excelで実装すると工数が…テストも…
django-import-exportで省力化に挑戦 
django-import-exportの基本 
django-import-exportとは 
Django Admin画面にインポート・エクスポート機能を追加する拡張

インストールと有効化 [2]
pip install django-import-export
INSTALLED_APPS = (
...
'import_export',
)
Installation and configuration: https://django-import-export.readthedocs.io/en/latest/installation.html
リソースクラスを作成 [3]
リソースクラスで対象となるモデルを指定
from import_export import resources
from core.models import Book
class BookResource(resources.ModelResource):
class Meta:
model = Book
Creating a resource: https://django-import-export.readthedocs.io/en/latest/getting_started.html#creating-a-resource
Django Adminに適用 [4]
親クラスを変更
リソースクラスを指定
from django.contrib import admin
from .models import Book
from import_export.admin import ImportExportModelAdmin
@admin.register(Book)
class BookAdmin(ImportExportModelAdmin):
resource_classes = [BookResource]
Admin integration: https://django-import-export.readthedocs.io/en/latest/admin_integration.htmla
Django Admin画面で
インポート・エクスポートできる! 

対象フォーマットは以下
csv, xlsx, tsv, json, yaml, html
しかし、このままでは教材データの
コピーには使えない… 
django-import-exportの
使いこなし 
対象フィールドの指定
作成日時、更新日時等は不要
erDiagram
Work["Work(教材)"] {
int id PK
string code "コード"
int textbook_id FK "教科書ID"
int subject_id FK "教科ID"
string name "教材名"
datetime created_at "作成日時"
datetime updated_at "更新日時"
int updated_by FK "更新者"
str updated_by_name "更新者名"
}
対象フィールドの指定
リソースで対象フィールドを指定 [5]
class BookResource(resources.ModelResource):
class Meta:
model = Book
# 対象フィールドを指定
fields = ("id", "name", "price")
class BookResource(resources.ModelResource):
class Meta:
model = Book
# 対象外フィールドを指定
exclude = ("created_at", "updated_at")
Declare fields: https://django-import-export.readthedocs.io/en/latest/advanced_usage.html#declare-fields
対象フィールドの指定
今回は
excludeを選択フィールドが増えても自動で対応
class WorkResource(resources.ModelResource):
class Meta:
model = Work
exclude = ("id", "created_at", "updated_at",
"updated_by")
主キーの変更
デフォルトでは
idを使って追加、更新する環境が異なるため
idがずれる
erDiagram
Work["Work(教材)"] {
int id PK
string code "コード"
int textbook_id FK "教科書ID"
int subject_id FK "教科ID"
string name "教材名"
datetime created_at "作成日時"
datetime updated_at "更新日時"
int updated_by FK "更新者"
str updated_by_name "更新者名"
}
主キーの変更
データを一意に識別するフィールドを指定 [6]
codeというユニークな文字列を使用
class WorkResource(resources.ModelResource):
class Meta:
model = Work
import_id_fields = ("code",) # codeで一意に識別
Create or update model instances: https://django-import-export.readthedocs.io/en/latest/advanced_usage.html#create-or-update-model-instances
大量データ対応 [7]
教材の問題は大量→全件インポート
同じ値の場合は処理を飛ばす
飛ばしたデータは確認画面で表示しない
class WorkResource(resources.ModelResource):
class Meta:
model = Work
skip_unchanged = True # 処理を飛ばす
report_skipped = False # 表示しない
Handling duplicate data: https://django-import-export.readthedocs.io/en/latest/advanced_usage.html#handling-duplicate-data
外部キーの維持
教材の目次構造は外部キーで実現
erDiagram
direction LR
Work ||--|{ Unit : has
Unit ||--|{ Question : has
Work["Work(教材)"] {
int id PK
string code
string name
}
Unit["Unit(ユニット)"] {
int id PK
int work_id FK "教材ID"
string code
string title
}
Question["Qustion(問題)"] {
int id PK
int unit_id FK "ユニットID"
string code
string text
}
外部キーの維持
idは環境ごとに異なる任意のフィールド(
code)を外部キー代わりに使用 [8]
class UnitResource(resources.ModelResource):
class Meta:
model = Unit
work = fields.Field(
column_name="work",
attribute="work",
widget=ForeignKeyWidget(Work, field="code"))
Foreign Key relations: https://django-import-export.readthedocs.io/en/latest/advanced_usage.html#foreign-key-relations
外部キーの維持(複数カラム)
教材は任意の教科書に紐付く
erDiagram
direction LR
Subject ||--|{ Textbook : has
Textbook ||--|{ Work : has
Subject["Subject(教科)"] {
int id PK
string name "国語等"
}
Textbook["Textbook(教科書)"] {
int id PK
int subject_id FK "教科ID"
int edition "発行年"
int year "学年"
int publisher "出版社"
}
Work["Work(教材)"] {
int id PK
int textbook_id FK "教科書ID"
string name
}
外部キーの維持(複数カラム)
教科書は複数カラムで一意になる
erDiagram
direction LR
Textbook["Textbook(教科書)"] {
int id PK
int subject_id FK "教科ID"
int edition "発行年"
int year "学年"
int publisher "出版社"
}
外部キーの維持(複数カラム)
外部キーの維持にnatural keyを使用[9]
複数の情報がJSONにシリアライズされる
Django Natural Keys: https://django-import-export.readthedocs.io/en/stable/advanced_usage.html#django-natural-keys
class TextbookManager(models.Manager):
def get_by_natural_key(self, edition, subject_name, year, publisher):
"""natural keyを使用してデータを取得するメソッド"""
return self.get(
edition=edition,
subject__name=subject_name,
year=year,
publisher=publisher,
)
外部キーの維持(複数カラム)
モデルに
natural_keyメソッド追加objectsにTextbookManager()を指定
class Textbook(BaseModel):
"""教科書モデル"""
objects = TextbookManager()
def natural_key(self):
return (
self.edition, self.subject.name,
self.year, self.publisher,
)
外部キーの維持(複数カラム)
ForeignKeyWidgetでnatural keyを使用
class WorkResource(resources.ModelResource):
textbook = fields.Field(
column_name="textbook",
attribute="textbook",
widget=ForeignKeyWidget(Textbook,
use_natural_foreign_keys=True)
)
任意のデータをエクスポート対象に
全件エクスポートだとレビュー中の教材データも本番に入る
レビュー完了したデータのみをエクスポート対象にしたい
任意のデータをエクスポート対象に
can_exrpotがTrueのデータをエクスポート上位がFalseならエクスポートしない
erDiagram
direction LR
Work ||--|{ Unit : has
Unit ||--|{ Question : has
Work["Work(教材)"] {
int id PK
string code
bool can_export "フラグ"
}
Unit["Unit(ユニット)"] {
int id PK
int work_id FK "教材ID"
string code
bool can_export "フラグ"
}
Question["Question(問題)"] {
int id PK
int unit_id FK "ユニットID"
string code
string text "問題文"
}
任意のデータをエクスポート対象に
get_export_queryset()で絞り込み [10]
class WorkAdmin(ImportExportModelAdmin):
def get_export_queryset(self, request):
return Work.objects.filter(can_export=True)
Exporting via Admin action: https://django-import-export.readthedocs.io/en/latest/admin_integration.html#exporting-via-admin-action
任意のデータをエクスポート対象に
class UnitAdmin(ImportExportModelAdmin):
def get_export_queryset(self, request):
# UnitとWorkの両方のcan_exportがTrue
return Unit.objects.select_related("work").filter(
can_export=True,
work__can_export=True,
)
class QuestionAdmin(ImportExportModelAdmin):
def get_export_queryset(self, request):
return Question.objects.select_related("unit__work").filter(
unit__can_export=True,
unit__work__can_export=True
)
インポート時のデータ削除
原稿作成環境で削除されたデータを本番環境でも自動で削除したい
→モデルに削除フラグを追加
class Question(BaseModel):
"""問題のモデルクラス"""
...
is_delete = models.BooleanField("削除フラグ", default=False)
...
インポート時のデータ削除
リソースに
for_delete()メソッドを追加 [11]
class QuestionResource(resources.ModelResource):
"""Question(問題)のインポート・エクスポート用の設定"""
def for_delete(self, row, instance):
"""is_deleteがTrueのデータを削除する"""
return row["is_delete"] == "1"
...
Deleting data: https://django-import-export.readthedocs.io/en/latest/getting_started.html#deleting-data
まとめ 
単純な処理以外にいろいろできる
対象フィールドの指定
主キーの変更
大量データ対応
外部キーの維持(複数カラムも)
任意のデータをエクスポート対象に
インポート時のデータ削除