0

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

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Thank you for that thorough and promising answer. I took your advice and stepped into the debugger (didn't think of that at all). Focussing on the organization for now, I noticed that the subclassed clean method is never called. I didn't change the model as per your suggestion so far. I'm not sure how I would access organization.people after doing so and want to stay on topic. I stand corrected if your solution relies on changing the model. Long story short: NOT NULL constraint failed: board.organization_id
Feel free to connect to the django-import-export discord channel and I'll try to help if I can
What's wrong with staying on this current channel?
are you still having issues?
Yes, subclassed foreignkeywidget's clean() is not called at all. render() returns None, if that helps
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.