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

Issue:
Make the filename available to import_data
I'd like to be able to use the filename of the uploaded file in the admin to customize the processing - in particular I need to add a column to the dataset to indicate the parent record in a foreign key relationship, based on the name of the file.

In order to do this I propose (and I'm happy to prepare a PR):

We change `admin.ImportMixin` so that the calls to `import_data` look like:

```
    def process_import(self, request, *args, **kwargs):
        '''
        <snip>
        '''
            result = resource.import_data(dataset, dry_run=False,
                                 raise_errors=True, file_name=import_file_name)

    def import_action(self, request, *args, **kwargs):
        '''
        <snip>
        '''
                result = resource.import_data(dataset, dry_run=True,
                                              raise_errors=False, file_name=uploaded_file.name)
```

In order for this to work, `import_data` will have to accept the file name. However, I don't think that we should add this parameter to the formal arguments, because it won't be relevant to most users.

I think we should just allow `kwargs` on `import_data` so that subclasses of `Resource` have the required flexibility. I am doing this for other use cases too - for example I have an `ImplicitParentResource` that can use the provided dataset to import data into a parent model first (using a parent `Resource`) and then into the actual model used by the `Resource` where `import_data` was called. In this case, I need to pass the `user` from the `Request` into each `Resource` so that I can create the `LogEntry` records (because the default implementation can only do it for the main `Resource` called from the admin.):

```
    def import_data(self, dataset, dry_run=False, raise_errors=False,
            use_transactions=None, **kwargs):
```



Discussion:
It would probably also be helpful to pass the `**kwargs` through from `import_data` to `before_import` so that the user can just override `before_import` in the subclass:

``` python
    def before_import(self, dataset, dry_run, **kwargs):
        """
        Override to add additional logic.
        """
        pass

    def import_data(self, dataset, dry_run=False, raise_errors=False,
            use_transactions=None, **kwargs):
        """
        Imports data from ``dataset``.

        ``use_transactions``
            If ``True`` import process will be processed inside transaction.
            If ``dry_run`` is set, or error occurs, transaction will be rolled
            back.
        """
        result = Result()
        result.diff_headers = self.get_diff_headers()

        if use_transactions is None:
            use_transactions = self.get_use_transactions()

        if use_transactions is True:
            # when transactions are used we want to create/update/delete object
            # as transaction will be rolled back if dry_run is set
            real_dry_run = False
            transaction.enter_transaction_management()
            transaction.managed(True)
        else:
            real_dry_run = dry_run

        try:
            self.before_import(dataset, real_dry_run, **kwargs)
```

Hi @rhunwicks, I like the idea

I'll do a PR then.


Resolving patch:
diff --git a/import_export/admin.py b/import_export/admin.py
--- a/import_export/admin.py
+++ b/import_export/admin.py
@@ -137,7 +137,9 @@ def process_import(self, request, *args, **kwargs):
             dataset = input_format.create_dataset(data)
 
             result = resource.import_data(dataset, dry_run=False,
-                                 raise_errors=True)
+                                          raise_errors=True,
+                                          file_name=import_file_name,
+                                          user=request.user)
 
             if not self.get_skip_admin_log():
                 # Add imported objects to LogEntry
@@ -202,7 +204,9 @@ def import_action(self, request, *args, **kwargs):
                     data = force_text(data, self.from_encoding)
                 dataset = input_format.create_dataset(data)
                 result = resource.import_data(dataset, dry_run=True,
-                                              raise_errors=False)
+                                              raise_errors=False,
+                                              file_name=uploaded_file.name,
+                                              user=request.user)
 
             context['result'] = result
 
diff --git a/import_export/resources.py b/import_export/resources.py
--- a/import_export/resources.py
+++ b/import_export/resources.py
@@ -284,7 +284,7 @@ def get_diff_headers(self):
         """
         return self.get_export_headers()
 
-    def before_import(self, dataset, dry_run):
+    def before_import(self, dataset, dry_run, **kwargs):
         """
         Override to add additional logic.
         """
@@ -292,7 +292,7 @@ def before_import(self, dataset, dry_run):
 
     @atomic()
     def import_data(self, dataset, dry_run=False, raise_errors=False,
-            use_transactions=None):
+            use_transactions=None, **kwargs):
         """
         Imports data from ``dataset``.
 
@@ -316,7 +316,7 @@ def import_data(self, dataset, dry_run=False, raise_errors=False,
             real_dry_run = dry_run
 
         try:
-            self.before_import(dataset, real_dry_run)
+            self.before_import(dataset, real_dry_run, **kwargs)
         except Exception as e:
             tb_info = traceback.format_exc(2)
             result.base_errors.append(Error(repr(e), tb_info))


Test patch:
diff --git a/tests/core/tests/resources_tests.py b/tests/core/tests/resources_tests.py
--- a/tests/core/tests/resources_tests.py
+++ b/tests/core/tests/resources_tests.py
@@ -393,6 +393,23 @@ def attempted_save(instance, real_dry_run):
         self.assertFalse(result.has_errors())
         self.assertEqual(len(result.rows), 0)
 
+    def test_before_import_access_to_kwargs(self):
+        class B(BookResource):
+            def before_import(self, dataset, dry_run, **kwargs):
+                if 'extra_arg' in kwargs:
+                    dataset.headers[dataset.headers.index('author_email')] = 'old_email'
+                    dataset.insert_col(0,
+                                       lambda row: kwargs['extra_arg'],
+                                       header='author_email')
+
+        resource = B()
+        result = resource.import_data(self.dataset, raise_errors=True,
+                                      extra_arg='extra@example.com')
+        self.assertFalse(result.has_errors())
+        self.assertEqual(len(result.rows), 1)
+        instance = Book.objects.get(pk=self.book.pk)
+        self.assertEqual(instance.author_email, 'extra@example.com')
+
     def test_link_to_nonexistent_field(self):
         with self.assertRaises(FieldDoesNotExist) as cm:
             class BrokenBook(resources.ModelResource):