Django import-export импортирует несуществующие внешние ключи

В спешке мне трудно понять концепцию импорта.

Где отсутствует окончательное создание доски? То есть Board(person=p, organization=p)

Модель

class Person(BaseModel):
    organizations = models.ManyToManyField("Organization",
                                           blank=True,
                                           through="Board")
class Organization(BaseModel):
    people = models.ManyToManyField("Person", blank=True, through="Board")

class Board(BaseModel):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE)

Тест

from django.test import TestCase
import tablib
from import_export import resources

class BoardResource(resources.ModelResource):

    def before_import_row(self, row, **kwargs):
        org_name_1 = row["organization"]
        o=Organization.objects.get_or_create(name_1=org_name_1, defaults={"name_1": org_name_1})
        person_firstname = row["person"]
        p=Person.objects.get_or_create(firstname=person_firstname, defaults={"firstname": person_firstname})

    class Meta:
        model = Board

dataset = tablib.Dataset(['','john','acme'], headers=['id','person','organization'])
class TestCase(TestCase):
    def test_basic_import(self):
        board_resource = BoardResource()
        result = board_resource.import_data(dataset, dry_run=False)
        print(result.totals)
        assert not result.has_errors()

Документация указывает на эту тему, хотя я не могу ничего применить к своему случаю

Мне интересно, можно ли улучшить вашу модель данных. Мои предположения могут быть ошибочными, но я думаю, что они таковы:

  1. Человек может состоять в N организациях.
  2. Экземпляр Board обеспечивает отношения «через» и имеет ссылку на одно лицо и одну организацию.

Поэтому вам не нужно объявлять ссылку на Person из Organization.

class Person(BaseModel):
    organizations = models.ManyToManyField("Organization",
                                         blank=True,
                                         through="Board")

class Organization(BaseModel):
    name = models.CharField(max_length=255)

class Board(BaseModel):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE)

Теперь вы импортируете экземпляр Board, который содержит ссылку на персону (по имени) и организацию по названию. Персоны и/или организации могут не существовать, и их необходимо создать во время импорта.

Экземпляр доски может существовать или не существовать во время импорта (он будет идентифицирован полем 'id'), и будет загружен либо новый, либо существующий экземпляр доски здесь.

Однако теперь вам нужно реализовать логику создания персоны/организации и связать ее с экземпляром.

UPDATE Я переписал свой первоначальный ответ следующим образом:

Лучшим способом создания экземпляра является создание подкласса ForeignKeyWidget, который обрабатывает создание экземпляра, если он не существует:

class PersonForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row=None, **kwargs):
        try:
            val = super().clean(value)
        except Person.DoesNotExist:
            val = Person.objects.create(firstname=row['person'])
        return val

Сделайте то же самое для Organization.

Затем объявите эти виджеты в вашем Resource:

    person = fields.Field(
            widget=PersonForeignKeyWidget(Person, "firstname"))
    
    organization = fields.Field(
            widget=OrganizationForeignKeyWidget(Organization, "name"))

Теперь логика импорта будет продолжена и сохранит экземпляр Board с правильными отношениями.

Есть несколько вопросов, например, что делать, если люди имеют общую фамилию... и это, очевидно, не справляется с созданием других полей (например, таких, как в BaseModel).

Если он по-прежнему выдает ошибку 'invalid', вам нужно проверить источник ошибки. Техники чтения ошибок см. в docs.

Как и в случае с import_export, лучший способ понять это - установить отладчик и пройти через импорт.

Есть два решения:

I.Изменение вашего кода и использование модели django

похоже, что проблема, с которой вы столкнулись, связана с созданием экземпляров Board после импорта данных для пользователя и организации. В методе before_import_row вы правильно извлекаете или создаете экземпляры Person и Organization, но упускаете фактическое создание экземпляра Board, который связывает человека и организацию.

Вот как вы можете настроить код, чтобы обеспечить создание экземпляра Board после успешного импорта или извлечения объектов Person и Organization:

Решение Вам нужно создать экземпляр Board внутри метода before_import_row. Вот обновленная версия вашего класса ресурсов Board:

from import_export import resources
from django.test import TestCase
import tablib
from .models import Person, Organization, Board

class BoardResource(resources.ModelResource):

    def before_import_row(self, row, **kwargs):
        org_name_1 = row["organization"]
        # Retrieve or create the organization
        o, created_org = Organization.objects.get_or_create(name_1=org_name_1, defaults={"name_1": org_name_1})

        person_firstname = row["person"]
        # Retrieve or create the person
        p, created_person = Person.objects.get_or_create(firstname=person_firstname, defaults={"firstname": person_firstname})

        # Create the Board instance that connects the Person and Organization
        Board.objects.get_or_create(person=p, organization=o)

    class Meta:
        model = Board


# Sample dataset for testing
dataset = tablib.Dataset(['','john','acme'], headers=['id','person','organization'])

class TestCase(TestCase):
    def test_basic_import(self):
        board_resource = BoardResource()
        result = board_resource.import_data(dataset, dry_run=False)
        print(result.totals)
        assert not result.has_errors()

I.Модель Django использует sqlite, поэтому вы можете использовать ее напрямую. (Это пример)

import sqlite3
connection = sqlite3.connect(<DIR_TO_DATABASE>,timeout=<secconds>)
connection.execute(f'''CREATE TABLE IF NOT EXISTS ....''')
connection.commit()
connection.close()

Как вы видите, вы могли бы сделать это, запустив чистый sql.

Вернуться на верх