django-import-export - 从 JSON 文件导入 ManyToMany

django-import-export - Importing ManyToMany from JSON file

我正在尝试导入包含多对多关系的 JSON 文件,但遇到了一些问题。这是我的代码:

book.json

{
  "title": "The Lord of the Rings",
  "categories": [
      "Fantasy",
      "Action",
      "Adventure"
  ]
}

models.py

from django.db import models

class Category(models.Model):
    guid = models.CharField(max_length=36)
    display_name = models.CharField(max_length=50)
    description = models.TextField()

    def __str__(self):
        return self.display_name

class Book(models.Model):
    title = models.CharField(max_length=200)
    categories = models.ManyToManyField(Category)

    def __str__(self):
        return self.title

resources.py

from import_export import resources
from import_export.fields import Field
from import_export.widgets import ManyToManyWidget
from .models import Book, Category

class BookResource(resources.ModelResource):
    categories = Field(widget=ManyToManyWidget(Category))

    class Meta:
        model = Book
        import_id_fields = ('title',)

importer.py

from resources import BookResource
from models import Book

with open("book.json", 'r') as f:
        book = '[' + f.read() + ']'
        result = BookResource().import_data(tablib.Dataset().load(book), raise_errors=True, dry_run=True)

这似乎 运行 没有错误,并将新的 book 添加到数据库中。问题是它不会在 book_categories table (PostgreSQL) 中创建新条目。我认为我肯定在某处遗漏了一步,因为我没有定义如何在给定 display_name 字符串列表的情况下找到正确的 category 数据库条目。我走在正确的轨道上吗?

简而言之,django-import-export 不处理这种情况:如果您查看 ManyToManyWidget.clean(),您会发现它默认需要一串逗号分隔的整数。您可以在那里做一些事情,但它只是希望您先导入类别,然后再导入书籍。

我最后做的是删除 BookResource 上的 Field 和 Widget 并将其替换为 widgets 字典:

class BookResource(resources.ModelResource):
    class Meta:
        model = Book
        import_id_fields = ("title",)
        fields = ("id", "title", "categories")
        widgets = {"categories": {"field": "display_name"}}

如果您随后先导入类别,那么您的图书将被正确链接。好吧,对 JSON 进行了一次修改,我将导入程序脚本包装起来,因此在控制台上它可以是 运行:

import django
import os

os.environ["DJANGO_SETTINGS_MODULE"] = "testbed.settings"

BOOK_DATA = """[{
  "title": "The Lord of the Rings",
  "categories": "Fantasy,Action,Adventure"
}]"""


def run():
    from bookstore.resources import BookResource
    import tablib

    importer = BookResource()
    dataset = tablib.Dataset()
    data = dataset.load(BOOK_DATA, format="json")
    result = importer.import_data(data, raise_errors=True, dry_run=False)
    print(result.has_errors())


if __name__ == "__main__":
    import sys

    sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
    django.setup()
    run()

可以编写 JSON 特定的 M2M 小部件,但随后在导入类别时,您将需要弄清楚每一行哪些已经存在,哪些不存在。这是可行的,所以也许我已经让你走上了正确的轨道来修复它;)

感谢@Melvyn,我能够通过为 JSON 数组创建自定义小部件来解决这个问题。我会 post 它以防其他人遇到这个问题。

class ManyToManyJSONWidget(ManyToManyWidget):
    def __init__(self, model, field=None, *args, **kwargs):
        if field is None:
            field = 'pk'
        self.model = model
        self.separator = None
        self.field = field

    def clean(self, value, row=None, *args, **kwargs):
        if not value:
            return self.model.objects.none()
        else:
            objs = []
            for i in value:
                obj = self.model.objects.filter(**{
                    '%s__iexact' % self.field: i
                }).first()
                objs.append(obj)
            return objs

    def render(self, value, obj=None):
        return ""