Django自定义through多对多表django-import-export导入及使用咨询
django-import-export 导入带自定义中间表的多对多TSV数据方案
一、模型定义优化
当前自定义中间表的思路是可行的,但外键字段命名存在冗余:Django ORM会自动为外键字段添加_id后缀作为数据库列名,手动给外键加_id后缀会导致实际数据库字段变成book_id_id这类错误命名。优化后直接在主表声明多对多关系并指定自定义中间表,后续ORM调用更顺畅:
class Book(models.Model): title = models.CharField(max_length=255) # 显式声明多对多关联,绑定自定义中间表 genres = models.ManyToManyField('Genre', through='BookGenre') class Genre(models.Model): genre = models.CharField(max_length=64) class BookGenre(models.Model): # 外键字段直接用关联模型名小写命名即可 book = models.ForeignKey(Book, on_delete=models.CASCADE) genre = models.ForeignKey(Genre, on_delete=models.CASCADE) class Meta: # 添加唯一约束避免重复创建相同关联 unique_together = ('book', 'genre')
二、对应TSV导入实现
不需要分三次导入三张表,根据你的TSV文件结构选对应方案即可,导入效率更高。
场景1:单条记录对应一本图书,体裁为分隔符拼接的单列
如果你的TSV每行是一本图书,体裁列用固定分隔符(比如逗号、竖线)拼接多个体裁值,直接为Book模型编写Resource类,自动完成体裁去重创建、中间表关联生成:
from import_export import resources, fields from import_export.widgets import ForeignKeyWidget from .models import Book, Genre, BookGenre class BookResource(resources.ModelResource): genres = fields.Field(column_name='genre_list') # genre_list替换为你TSV里的体裁列名 class Meta: model = Book import_id_fields = ('title',) # 替换为你业务里判断图书重复的唯一字段 fields = ('id', 'title', 'genres') def save_m2m(self, obj, data, *args, **kwargs): # 按分隔符拆分体裁值,这里用逗号举例,TSV里如果是其他分隔符自行替换 genre_name_list = [g.strip() for g in data['genre_list'].split(',') if g.strip()] for genre_name in genre_name_list: genre, _ = Genre.objects.get_or_create(genre=genre_name) BookGenre.objects.get_or_create(book=obj, genre=genre)
场景2:单条记录对应一条图书-体裁关联
如果你的TSV每行就是一组关联关系,包含图书名称、体裁名称两个核心列,直接为中间表编写Resource类,一次导入即可完成所有关联创建,不存在的图书、体裁记录会自动生成:
class BookGenreResource(resources.ModelResource): book = fields.Field( column_name='book_title', # 替换为TSV里的图书名列名 attribute='book', widget=ForeignKeyWidget(Book, field='title') ) genre = fields.Field( column_name='genre_name', # 替换为TSV里的体裁名列名 attribute='genre', widget=ForeignKeyWidget(Genre, field='genre') ) class Meta: model = BookGenre import_id_fields = ('book', 'genre') # 已存在的关联自动跳过,避免重复 fields = ('id', 'book', 'genre') def before_import_row(self, row, **kwargs): # 导入前自动创建不存在的图书、体裁记录 Book.objects.get_or_create(title=row['book_title']) Genre.objects.get_or_create(genre=row['genre_name'])
注:TSV是django-import-export原生支持的格式,本质是用制表符分隔的文本,不需要额外编写解析逻辑,导入时指定format为
tsv即可。
三、视图与模板最简实现
如果不需要admin后台的导入功能,普通视图+模板几十行代码就能完成上传导入流程:
- 视图代码:
from django.shortcuts import render, redirect from tablib import Dataset from .resources import BookResource # 替换为你实际使用的Resource类 from django.contrib import messages def tsv_import_view(request): if request.method == 'POST': resource = BookResource() dataset = Dataset() uploaded_file = request.FILES.get('tsv_file') if not uploaded_file: messages.error(request, "请上传TSV文件") return redirect('tsv_import') # 读取文件内容按tsv格式解析 imported_data = dataset.load(uploaded_file.read().decode('utf-8'), format='tsv') result = resource.import_data(imported_data, dry_run=False, raise_errors=False) if result.has_errors(): messages.error(request, f"导入完成,存在{len(result.errors)}条错误数据") else: messages.success(request, f"导入成功,共处理{result.total_rows}条记录") return redirect('tsv_import') return render(request, 'tsv_import.html')
- 模板代码(
templates/tsv_import.html):
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>图书数据导入</title> </head> <body> <h2>上传TSV文件导入图书数据</h2> {% if messages %} <div class="messages"> {% for msg in messages %} <p style="color: {% if msg.level == DEFAULT_MESSAGE_LEVELS.ERROR %}#c8232c{% else %}#198754{% endif %}"> {{ msg }} </p> {% endfor %} </div> {% endif %} <form method="post" enctype="multipart/form-data"> {% csrf_token %} <div> <input type="file" name="tsv_file" accept=".tsv,.txt" required> </div> <div style="margin-top: 12px;"> <button type="submit">开始导入</button> </div> </form> </body> </html>
如果数据量超过1万行,建议在import_data时开启use_bulk=True参数批量写入,能大幅降低导入耗时。
内容的提问来源于stack exchange,提问作者David Amlaw
相关产品推荐
相关产品推荐

