Repository: django-import-export/django-import-export

Issue:
raise Error when model  has attribute __unicode__ which print some foreignKey value.
If A model has attribute `__unicode__`, foreignKey and print the foreignKey in `__unicode__`, when I import some new data, it will raise an Exception says `RelatedObjectDoesNotExist: XX has no yy.`
For example

``` python
## models.py
title = models.CharField(verbose_name=u'需求内容')
proposer = models.ForeignKey(User, verbose_name=u'需求提出人', related_name='proposer')
    def __unicode__(self):
        return u'%s %s' % (self.proposer.username, self.title )
```

After reading the code in django-import-export, I found something wrong as follow:

``` python
## resource.py
instance, new = self.get_or_init_instance(instance_loader, row)  # if row is new, will return an None instance
row_result.object_repr = force_text(instance) # try to get the foreignKey value, and raise Exception
```



Discussion:
> `instance, new = self.get_or_init_instance(instance_loader, row)  # if row is new, will return an None instance`

The instance loader should always return an object for `instance`. If the row is new, then the only difference is `new` will be `True`. That's what the name of the function means: if the instance exists, get it, otherwise init a new instance.

Does it mean I need to override the `init_instance` , otherwise it will raise Exception when using `__unicode__`  for  a new row

I'm not sure if you need to override `init_instance` or if it's ever necessary.

What exactly is raising an Exception? Do you have a callstack?

You can except the error in your model's  `__unicode__` method as in #439. However it's probably better to only except the specific error instead of using the broad except with

``` python
from django.core.exceptions import DoesNotExist
```

Yet I think this is an issue with `django-import-export` and should be fixed at some point.

Thank  @JarnoRFB  and  @thauk-copperleaf  for replying.

@JarnoRFB I'm not sure how this could be fixed. In #439 was your proposal to not call `Resource.save_instance()` (which calls `Model.save()`) in `Resource.import_row()`?

I think my proposal in #439 is pretty much bullshit. I thought if the models are first instantiated in the dry_run and saved only in the real import they would have the `related_model`, but isn't that what is already happening?
 Now I think just wrapping

``` python
row_result.object_repr = force_text(instance)
```

in a `try  - exept`  block would maybe do it. Or maybe the issue is with 

``` python
instance, new = self.get_or_init_instance(instance_loader, row)
```

returning something strange for new rows as @GuihaiHU pointed out.

I think you're on to something there. I see that line twice inside `Resource.import_row()`, actually. That's a little wonky. I guess it's recording what the object is immediately since it might get deleted or skipped, and then again since saving it could change its representation (like in this case, with a field that is a foreign key). I suppose wrapping the first call in a try/except would be sufficient...


Resolving patch:
diff --git a/import_export/resources.py b/import_export/resources.py
--- a/import_export/resources.py
+++ b/import_export/resources.py
@@ -437,8 +437,6 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
             else:
                 row_result.import_type = RowResult.IMPORT_TYPE_UPDATE
             row_result.new_record = new
-            row_result.object_repr = force_text(instance)
-            row_result.object_id = instance.pk
             original = deepcopy(instance)
             diff = Diff(self, original, new)
             if self.for_delete(row, instance):
@@ -457,11 +455,12 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
                     with transaction.atomic():
                         self.save_instance(instance, dry_run)
                     self.save_m2m(instance, row, using_transactions, dry_run)
-                    # Add object info to RowResult for LogEntry
-                    row_result.object_repr = force_text(instance)
-                    row_result.object_id = instance.pk
                 diff.compare_with(self, instance, dry_run)
             row_result.diff = diff.as_html()
+            # Add object info to RowResult for LogEntry
+            if row_result.import_type != RowResult.IMPORT_TYPE_SKIP:
+                row_result.object_id = instance.pk
+                row_result.object_repr = force_text(instance)
             self.after_import_row(row, row_result, **kwargs)
         except Exception as e:
             # There is no point logging a transaction error for each row


Test patch:
diff --git a/tests/core/admin.py b/tests/core/admin.py
--- a/tests/core/admin.py
+++ b/tests/core/admin.py
@@ -5,7 +5,11 @@
 from import_export.resources import ModelResource
 from import_export.admin import ImportExportMixin, ImportMixin, ExportActionModelAdmin
 
-from .models import Book, Category, Author
+from .models import Book, Category, Author, Child
+
+
+class ChildAdmin(ImportMixin, admin.ModelAdmin):
+    pass
 
 
 class BookResource(ModelResource):
@@ -32,3 +36,4 @@ class AuthorAdmin(ImportMixin, admin.ModelAdmin):
 admin.site.register(Book, BookAdmin)
 admin.site.register(Category, CategoryAdmin)
 admin.site.register(Author, AuthorAdmin)
+admin.site.register(Child, ChildAdmin)
diff --git a/tests/core/exports/child.csv b/tests/core/exports/child.csv
new file mode 100644
--- /dev/null
+++ b/tests/core/exports/child.csv
@@ -0,0 +1,2 @@
+id,parent,name
+1,1234,Some
diff --git a/tests/core/migrations/0005_addparentchild.py b/tests/core/migrations/0005_addparentchild.py
new file mode 100644
--- /dev/null
+++ b/tests/core/migrations/0005_addparentchild.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-19 17:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0004_bookwithchapters'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Child',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Parent',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='child',
+            name='parent',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Parent'),
+        ),
+    ]
diff --git a/tests/core/models.py b/tests/core/models.py
--- a/tests/core/models.py
+++ b/tests/core/models.py
@@ -39,6 +39,23 @@ def __str__(self):
         return self.name
 
 
+@python_2_unicode_compatible
+class Parent(models.Model):
+    name = models.CharField(max_length=100)
+
+    def __str__(self):
+        return self.name
+
+
+@python_2_unicode_compatible
+class Child(models.Model):
+    parent = models.ForeignKey(Parent)
+    name = models.CharField(max_length=100)
+
+    def __str__(self):
+        return '%s - child of %s' % (self.name, self.parent.name)
+
+
 class Profile(models.Model):
     user = models.OneToOneField('auth.User')
     is_private = models.BooleanField(default=True)
diff --git a/tests/core/tests/admin_integration_tests.py b/tests/core/tests/admin_integration_tests.py
--- a/tests/core/tests/admin_integration_tests.py
+++ b/tests/core/tests/admin_integration_tests.py
@@ -9,7 +9,7 @@
 from django.contrib.admin.models import LogEntry
 
 from core.admin import BookAdmin, AuthorAdmin
-from core.models import Category, Author
+from core.models import Category, Parent
 
 
 class ImportExportAdminIntegrationTest(TestCase):
@@ -174,6 +174,30 @@ def test_import_log_entry(self):
         self.assertEqual(book.object_repr, "Some book")
         self.assertEqual(book.object_id, str(1))
 
+    def test_import_log_entry_with_fk(self):
+        Parent.objects.create(id=1234, name='Some Parent')
+        input_format = '0'
+        filename = os.path.join(
+            os.path.dirname(__file__),
+            os.path.pardir,
+            'exports',
+            'child.csv')
+        with open(filename, "rb") as f:
+            data = {
+                'input_format': input_format,
+                'import_file': f,
+            }
+            response = self.client.post('/admin/core/child/import/', data)
+        self.assertEqual(response.status_code, 200)
+        confirm_form = response.context['confirm_form']
+        data = confirm_form.initial
+        response = self.client.post('/admin/core/child/process_import/', data,
+                                    follow=True)
+        self.assertEqual(response.status_code, 200)
+        child = LogEntry.objects.latest('id')
+        self.assertEqual(child.object_repr, 'Some - child of Some Parent')
+        self.assertEqual(child.object_id, str(1))
+
 
 class ExportActionAdminIntegrationTest(TestCase):