Django import-export import foreign keys that do not exist - Stack Overflow

admin2025-04-17  2

I'm having a hard time understanding the import concept.

How can I create the board after the person and organization were imported?

Where is the final board creation missing? i.e. Board(person=p, organization=p)

Current result: OrderedDict([('new', 0), ('update', 0), ('delete', 0), ('skip', 0), ('error', 0), ('invalid', 1)])

Model

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)

Test

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()

The documentation points to this thread though I'm unable to apply anything to my case


Env:

  • Python 3.8
  • Django 4.2
  • Import-Export 4.11
  • Pytest-Django

I'm having a hard time understanding the import concept.

How can I create the board after the person and organization were imported?

Where is the final board creation missing? i.e. Board(person=p, organization=p)

Current result: OrderedDict([('new', 0), ('update', 0), ('delete', 0), ('skip', 0), ('error', 0), ('invalid', 1)])

Model

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)

Test

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()

The documentation points to this thread though I'm unable to apply anything to my case


Env:

  • Python 3.8
  • Django 4.2
  • Import-Export 4.11
  • Pytest-Django
Share edited Feb 10 at 8:20 jjk asked Jan 31 at 12:06 jjkjjk 5932 gold badges11 silver badges31 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2 +50

I'm wondering if your data model can be improved. My assumptions may be wrong, but I think they are as follows:

  1. A Person can be in N Organizations.
  2. A Board instance provides the 'through' relationship and has a reference to a single Person, and single Organization.

Therefore you don't need to declare a reference to Person from 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)

Now you import a Board instance which contains a reference to a Person (by first name) and an Organization by name. The Person and / or Organization may not exist and may need to be created during import.

The Board instance may or may not exist during import (it will be identified by the 'id' field), and either a new or existing Board instance will be loaded here.

However, now you need to implement the logic to create the Person / Organization and link it with the instance.

UPDATE I rewrote my original answer as follows:

The best way to create an instance is to create a subclass of ForeignKeyWidget which handles the creation of the instance if it does not exist:

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

Do the same for Organization as well.

Then declare these widgets in your Resource:

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

Now the import logic will continue and persist the Board instance with the correct relations.

There's a few questions like what if people share a firstname... and this obviously doesn't handle creation of other fields (e.g. like what's in BaseModel).

If it is still failing with 'invalid', you will need to inspect the source of the error. See docs for techniques on reading errors.

As always with import_export the best way to understand it is to set a debugger and step through an import.

转载请注明原文地址:http://anycun.com/QandA/1744863178a88680.html