0

I am struggling to populate models in Django by using ForeignKey. Let's say we have as in import_export documentation the following example:

class Author(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Category(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Book(models.Model):
    name = models.CharField('Book name', max_length=100)
    author = models.ForeignKey(Author, blank=True, null=True, )
    ...
    price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    categories = models.ManyToManyField(Category, blank=True)

    def __str__(self):
        return self.name

How can I implement import_export module that can check if there is an existing author by name (not by id), that is not case sensitive, and that can generate a new author if it does not exist?

As an example, let's say the CSV file looks like:

name,author,...,price,categories
J.R.R. Tolkien,Lord of the Rings,...,40,["cat1","cat2"]

Also, if there is a DateTime field, how to generate that in ForeignKey table?

NOTE: I know about use of natural key:

from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget

class AuthorManager(models.Manager):

    def get_by_natural_key(self, name):
        return self.get(name=name)

class Author(models.Model):

    objects = AuthorManager()

    name = models.CharField(max_length=100)
    birthday = models.DateTimeField(auto_now_add=True)

    def natural_key(self):
        return (self.name,)

# Only the author field uses natural foreign keys.
class BookResource(resources.ModelResource):

    author = Field(
        column_name = "author",
        attribute = "author",
        widget = ForeignKeyWidget(Author, use_natural_foreign_keys=True)
    )

    class Meta:
        model = Book

But I am not sure how to check for UPPER or lower case in the CSV. And how to generate a new Author if it does not exist.

1 Answer 1

1

There are a couple of ways of creating an FK relation during import if it does not already exist.

Option 1 - override the before_import_row() method

class BookResource(resources.ModelResource):

    # note use of 'iexact' for case-insensitive lookup
    def before_import_row(self, row, **kwargs):
        author_name = row["author"]
        Author.objects.get_or_create(name__iexact=author_name, 
            defaults={"name": author_name})

    # other code omitted

Option 2 - subclass ForeignKeyWidget

Simply subclass ForeignKeyWidget and implement the check in clean():

class AuthorForeignKeyWidget(widgets.ForeignKeyWidget):

    def clean(self, value, row=None, **kwargs):
        author, created = Author.objects.get_or_create(name__iexact=value,
            defaults={"name": value})
        return author


class BookResource(resources.ModelResource):
    author = fields.Field(
        column_name='author',
        attribute='author',
        widget=AuthorForeignKeyWidget(Author))

    # other code omitted

Either way will work fine. I would personally use option 2.

Also, if there is a DateTime field, how to generate that in ForeignKey table?

Since you are calling Author.objects.get_or_create() you can add a date if you wish, for example:

author, created = Author.objects.get_or_create(name__iexact=value, 
    defaults={"name": value, "created": timezone.now()})

If using natural keys you can adjust the code as desired.

Related answer about creating in bulk mode

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

2 Comments

This is great! Amazing! But what happens if I have two columns (author_name and author_birthday)? How do you populate both for the same model?
In both examples the row data is passed in, so you can read row values and pass to create()

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.