欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

django-import-export关联外键在后台管理中导入导出的办法

程序员文章站 2022-03-08 22:38:46
...

本文介绍django-import-export在关联外键时,导出可以导出可读字样(而非关联id),导入通过可读字眼进行导入(而非关联id)

模型设计如下:


# models.py
from django.db import models

# Create your models here.


class User(models.Model):
    gender_by_choice = (
        ('male', '男'),
        ('female', '女'),
    )

    username = models.CharField(max_length=10, unique=True, verbose_name='姓名')
    age = models.IntegerField(verbose_name='年龄')
    gender = models.CharField(max_length=6, choices=gender_by_choice, verbose_name='性别')
    nickname = models.CharField(max_length=10, verbose_name='昵称')

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = '用户'
        verbose_name_plural = '用户'


class Book(models.Model):
    owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='归属者')
    name = models.CharField(max_length=64, verbose_name='书名')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '书'
        verbose_name_plural = '书'

可以看到上述的Book模型中的owner关联了User表,先按照django-import-export的官网所述的方法配置,看看会遇到什么问题

  • 配置admin.py

# admin.py
from import_export import resources
from import_export.admin import ImportExportModelAdmin

class BookResource(resources.ModelResource):
    class Meta:
        model = Book


class BookAdmin(ImportExportModelAdmin):
    resource_class = BookResource
    # 这里加几个显示字段
    list_display = ['name', 'owner']


class UserResource(resources.ModelResource):
    class Meta:
        model = User


class UserAdmin(ImportExportModelAdmin):
    resource_class = UserResource
    # 这里加几个显示字段
    list_display = ['username', 'age', 'gender', 'nickname']


admin.site.register(Book, BookAdmin)
admin.site.register(User, UserAdmin)

如下图,右上角已经有了导入导出按钮,django-import-export已经加载成功

录入两条用户数据和三条书籍信息,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFcspVy1-1583570587234)(https://upload-images.jianshu.io/upload_images/15925527-b18310a89989a753.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtdqkOWE-1583570587235)(https://upload-images.jianshu.io/upload_images/15925527-da85dedaec711212.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

如果将书籍导出,会得到如下效果,owner会显示user表的id信息,并不是我们想要的结果:
django-import-export关联外键在后台管理中导入导出的办法

导入会有同样的问题,导入时owner中的值必须是user中的id,而不能是张三、李四,所以我们需要对导入和导出做一些调整,以实现我们的需求:


from django.contrib import admin
from .models import Book, User
from import_export import resources
from django.apps import apps
from import_export.admin import ImportExportModelAdmin
from django.db import models
import tablib
import collections

class BookResource(resources.ModelResource):
    def __init__(self):
        super(BookResource, self).__init__()
        # 获取tables应用下Book模型中的所有字段,请根据自己的应用将tables更改
        field_list = apps.get_model('tables', 'Book')._meta.fields
        self.vname_dict = {}
        self.fkey = []
        for i in field_list:
            self.vname_dict[i.name] = i.verbose_name    # 获取所有字段的verbose_name并存放在字典
            if(isinstance(i, models.ForeignKey)):
                self.fkey.append(i.name)    # 获取所有ForeignKey字段的name存放在列表

    def export(self, queryset=None, *args, **kwargs):
        self.before_export(queryset, *args, **kwargs)

        if queryset is None:
            queryset = self.get_queryset()

        headers = self.get_export_headers()
        data = tablib.Dataset(headers=headers)

        # 获取所有外键名称在headers中的位置
        fk_index = {}
        for fk in self.fkey:
            fk_index[fk] = headers.index(fk)

        iterable = queryset
        for obj in iterable:
            # 获取将要导出的源数据,这里export_resource返回的是列表,便于更改。替换到外键的值
            res = self.export_resource(obj)
            """
            这里是关键,将owner的值到User中获取对应的对象,并截取起可读名称username,
            这里用的是get,所以在User的模型中username必须是unique
            """
            res[fk_index['owner']] = User.objects.get(id=res[fk_index['owner']]).username
            data.append(res)
        self.after_export(queryset, data, *args, **kwargs)
        return data

    def before_import(self, dataset, using_transactions, dry_run, **kwargs):
        dict = []
        for row in dataset.dict:
            tmp = collections.OrderedDict()
            books = Book.objects.all()
            for item in row:
                if item == 'owner':
                    """
                    这里是关键,通过可读名称到User表中找到对应id,并加到导入的数据中去
                    """
                    tmp[item] = User.objects.get(username=row[item]).id
                else:
                    tmp[item] = row[item]
            """
            这里是关键,将数据进行比对,如果数据相同,就把原先在Book表中的id加到需要导入的数据中去,
            这样就不会新增和原先一模一样的数据,类似于create_or_update方法
            """
            for book in books:
                if row['name'] == book.name:
                    tmp['id'] = book.id
            dict.append(tmp)
        dataset.dict = dict
        return dataset

    class Meta:
        model = Book


class BookAdmin(ImportExportModelAdmin):
    resource_class = BookResource


class BookAdmin(ImportExportModelAdmin):
    resource_class = BookResource
    list_display = ['name', 'owner']


class UserResource(resources.ModelResource):
    class Meta:
        model = User


class UserAdmin(ImportExportModelAdmin):
    resource_class = UserResource
    list_display = ['username', 'age', 'gender', 'nickname']


admin.site.register(Book, BookAdmin)
admin.site.register(User, UserAdmin)

请特别注意,关联查询的字段应该在模型中设定unique=True,如果不唯一,在User表中可能会查到多条数据,这样Book表是不知道要关联那条记录的

将书籍导出后的效果如下:
django-import-export关联外键在后台管理中导入导出的办法

再来说导入,导入的表如下表:
django-import-export关联外键在后台管理中导入导出的办法

提交后的校验界面如下:
django-import-export关联外键在后台管理中导入导出的办法

确认导入后,数据如下:
django-import-export关联外键在后台管理中导入导出的办法