Repository: e-valuation/EvaP

Issue:
Handle multiple sheets in import files correctly
When importing an excel file with multiple sheets (e.g. when using the person importer on the person management page for a certain course) and at least one of the sheets is not correctly formatted, processing the import fails with a `TypeError`:
```
cannot unpack non-iterable NoneType object

File "/opt/evap/evap/staff/importers.py", line 660, in process_file_content
    user_list, importer.success_messages, importer.warnings, importer.errors = UserImporter.process(file_content, test_run)
```

The importer should show an error message instead. The same should be done for other importers as well.


Discussion:
The `None` is coming from a missing return statement, is this intended?
```python
    @classmethod
    def process(cls, excel_content, test_run):
        """
            Entry point for the view.
        """
        try:
            importer = cls()
            # ...
            return importer.save_users_to_db(), importer.success_messages, importer.warnings, importer.errors
        except Exception as e:  # pylint: disable=broad-except
            importer.errors[ImporterError.GENERAL].append(_("Import finally aborted after exception: '%s'" % e))
            if settings.DEBUG:
                # re-raise error for further introspection if in debug mode
                raise
            # No return, so function returns None?
```
I mean, there still is a warning, but when errors occur in other places, there is something like
```python
return [], importer.success_messages, importer.warnings, importer.errors
```
this is actually a mistake and the messages should be returned here as well

Resolving patch:
diff --git a/evap/staff/importers.py b/evap/staff/importers.py
--- a/evap/staff/importers.py
+++ b/evap/staff/importers.py
@@ -540,7 +540,7 @@ def save_users_to_db(self):
                 except Exception as error:
                     self.errors[ImporterError.GENERAL].append(
                         _("A problem occured while writing the entries to the database."
-                          " The error message has been: '{}'").format(error=error))
+                          " The error message has been: '{}'").format(error))
                     raise
 
         msg = format_html(_("Successfully created {} users:"), len(created_users))
@@ -603,6 +603,7 @@ def process(cls, excel_content, test_run):
             if settings.DEBUG:
                 # re-raise error for further introspection if in debug mode
                 raise
+            return [], importer.success_messages, importer.warnings, importer.errors
 
 
 class PersonImporter:


Test patch:
diff --git a/evap/staff/tests/test_importers.py b/evap/staff/tests/test_importers.py
--- a/evap/staff/tests/test_importers.py
+++ b/evap/staff/tests/test_importers.py
@@ -1,5 +1,7 @@
 import os
 from datetime import date, datetime
+from unittest.mock import patch
+
 from django.test import TestCase, override_settings
 from django.conf import settings
 from model_bakery import baker
@@ -120,6 +122,17 @@ def test_import_makes_inactive_user_active(self):
 
         self.assertEqual(UserProfile.objects.count(), 2)
 
+    @override_settings(DEBUG=False)
+    @patch("evap.evaluation.models.UserProfile.objects.update_or_create")
+    def test_unhandled_exception(self, mocked_db_access):
+        mocked_db_access.side_effect = Exception("Contact your database admin right now!")
+        result, __, __, errors = UserImporter.process(self.valid_excel_content, test_run=False)
+        self.assertEqual(result, [])
+        self.assertIn(
+            "Import finally aborted after exception: 'Contact your database admin right now!'",
+            errors[ImporterError.GENERAL],
+        )
+
 
 class TestEnrollmentImporter(TestCase):
     filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/test_enrollment_data.xls")