I'm experiencing a persistent issue with importing CSV data into my Django project using the django-import-export library in the admin panel. When trying to import records for a Copy model which has ForeignKey relations, I keep encountering a NOT NULL constraint failed error for the book_id field. AGAIN THIS ONLY OCCURS IN THE ADMIN PANEL, I have tested in shell and it works. I am sure there is no issue with the CSV and likely not even the resources/models files. It seems something is going wrong with the admin logic before the actual import.
Error looks like this but for every single row. It occurs when hitting "submit". I never even get to "Confirm Import" button
Line number: 1 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 2 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 3 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 4 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 5 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 6 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 7 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 8 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 9 - NOT NULL constraint failed: catalog_copy.book_id
Line number: 10 - NOT NULL constraint failed: catalog_copy.book_id
Here are the relevant parts of my code:
models.py
from django.db import models
from django.core.exceptions import ValidationError
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
isbn = models.CharField(max_length=13)
class Format(models.Model):
name = models.CharField(max_length=100)
class Copy(models.Model):
book = models.ForeignKey(Book, related_name='copies', on_delete=models.CASCADE)
format = models.ForeignKey(Format, related_name='copies', on_delete=models.CASCADE)
location = models.CharField(max_length=100)
is_available = models.BooleanField(default=True)
resources.py
from import_export import resources
from .models import Copy
class CopyResource(resources.ModelResource):
class Meta:
model = Copy
fields = ('id', 'book', 'format', 'location', 'is_available')
import_id_fields = ['id']
def before_import_row(self, row, **kwargs):
book_title = row.get('Book Title')
isbn = row.get('ISBN')
author = row.get('Author')
format_name = row.get('Format')
book, created = Book.objects.get_or_create(
title=book_title,
defaults={'isbn': isbn, 'author': author}
)
format, created = Format.objects.get_or_create(
name=format_name
)
row['book'] = book.pk
row['format'] = format.pk
admin.py
from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from .models import Copy
from .resources import CopyResource
class CopyAdmin(ImportExportModelAdmin):
resource_class = CopyResource
admin.site.register(Copy, CopyAdmin)
I've manually verified the CSV and even tested uploading in the django shell which worked properly. But admin panel still does not work!!!
import tablib
...: from catalog.resources import CopyResource
...:
...: with open('library_copies.csv', 'r', encoding='utf-8') as f:
...: data = tablib.Dataset().load(f.read(), format='csv')
...:
...: resource = CopyResource()
...: result = resource.import_data(data, dry_run=False)
...:
...: if result.has_errors():
...: print("Errors occurred during the import:")
...: for error in result.base_errors:
...: print(f"Base error: {error.error}")
...: else:
...: print("The import would proceed without errors.")
The above succeeds but import through admin panel always has NOT NULL error
Edit: Adding logs for manual shell run vs admin panel. It leads me to believe the CopyResource logic is not being used at all in the admin panel, and I cannot figure out why. It says Resource: CopyResource in the admin panel, but it does not follow the proper logic like in shell.
shell:
0.000) BEGIN; args=None; alias=default
(0.000) SAVEPOINT "s8585724224_x6"; args=None; alias=default
(0.000) RELEASE SAVEPOINT "s8585724224_x6"; args=None; alias=default
(0.000) SAVEPOINT "s8585724224_x7"; args=None; alias=default
Before importing row... OrderedDict({'id': '1', 'Book Title': 'Thank You for My Service ', 'ISBN': '9781524796501', 'Author': 'Mat Best', 'Format': 'Large Print', 'Location': 'Juliefurt', 'Is Available': 'True'})
Row data: OrderedDict({'id': '1', 'Book Title': 'Thank You for My Service ', 'ISBN': '9781524796501', 'Author': 'Mat Best', 'Format': 'Large Print', 'Location': 'Juliefurt', 'Is Available': 'True'})
(0.000) SELECT "catalog_book"."id", "catalog_book"."title", "catalog_book"."author", "catalog_book"."isbn" FROM "catalog_book" WHERE "catalog_book"."title" = 'Thank You for My Service ' LIMIT 21; args=('Thank You for My Service ',); alias=default
(0.000) SAVEPOINT "s8585724224_x8"; args=None; alias=default
(0.000) INSERT INTO "catalog_book" ("title", "author", "isbn") VALUES ('Thank You for My Service ', 'Mat Best', '9781524796501') RETURNING "catalog_book"."id"; args=('Thank You for My Service ', 'Mat Best', '9781524796501'); alias=default
(0.000) ROLLBACK TO SAVEPOINT "s8585724224_x8"; args=None; alias=default
(0.000) RELEASE SAVEPOINT "s8585724224_x8"; args=None; alias=default
(0.000) RELEASE SAVEPOINT "s8585724224_x7"; args=None; alias=default
(0.000) SAVEPOINT "s8585724224_x9"; args=None; alias=default
Before importing row... OrderedDict({'id': '2', 'Book Title': 'Thank You for My Service ', 'ISBN': '9781524796501', 'Author': 'Mat Best', 'Format': 'Large Print', 'Location': 'Hernandezport', 'Is Available': 'True'})
admin panel:
(0.000) BEGIN; args=None; alias=default
(0.000) SAVEPOINT "s6196490240_x1"; args=None; alias=default
(0.000) RELEASE SAVEPOINT "s6196490240_x1"; args=None; alias=default
(0.000) SAVEPOINT "s6196490240_x2"; args=None; alias=default
(0.000) SELECT "catalog_copy"."id", "catalog_copy"."book_id", "catalog_copy"."format_id", "catalog_copy"."location", "catalog_copy"."is_available" FROM "catalog_copy" WHERE "catalog_copy"."id" IS NULL LIMIT 21; args=(); alias=default
(0.000) INSERT INTO "catalog_copy" ("book_id", "format_id", "location", "is_available") VALUES (NULL, NULL, '', 1) RETURNING "catalog_copy"."id"; args=(None, None, '', True); alias=default
(0.000) ROLLBACK TO SAVEPOINT "s6196490240_x2"; args=None; alias=default
(0.000) RELEASE SAVEPOINT "s6196490240_x2"; args=None; alias=default
(0.000) SAVEPOINT "s6196490240_x3"; args=None; alias=default
(0.000) SELECT "catalog_copy"."id", "catalog_copy"."book_id", "catalog_copy"."format_id", "catalog_copy"."location", "catalog_copy"."is_available" FROM "catalog_copy" WHERE "catalog_copy"."id" IS NULL LIMIT 21; args=(); alias=default