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()
Документация указывает на эту тему, хотя я не могу ничего применить к своему случаю
Мне интересно, можно ли улучшить вашу модель данных. Мои предположения могут быть ошибочными, но я думаю, что они таковы:
- Человек может состоять в N организациях.
- Экземпляр 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.