Repository: carpentries/amy

Issue:
Bulk-upload: Not checking whether it is a valid csv file
When a file with first line in valid .csv format and if the following lines are not in a valid format, 

for example if the file uploaded is :
"""
personal,middle,family,email
This is a test file
"""

There is no error being displayed for the wrong file uploaded, instead a ticket is being displayed. I think there should be a check to validate csv file before trying to upload the data.
Am I right?
![screenshot from 2015-03-17 13 28 34](https://cloud.githubusercontent.com/assets/6889955/6683386/8f7ff0ec-ccab-11e4-97af-4f72e51b9d82.png)



Discussion:

Resolving patch:
diff --git a/workshops/util.py b/workshops/util.py
--- a/workshops/util.py
+++ b/workshops/util.py
@@ -62,9 +62,12 @@ def upload_person_task_csv(stream):
     for row in reader:
         entry = {}
         for col in Person.PERSON_UPLOAD_FIELDS:
-            if col in row:
+            try:
                 entry[col] = row[col].strip()
-            else:
+            except (KeyError, IndexError, AttributeError):
+                # either `col` is not in `entry`, or not in `row`, or
+                # `.strip()` doesn't work (e.g. `row[col]` gives `None` instead
+                # of string)
                 entry[col] = None
                 empty_fields.add(col)
 
@@ -139,20 +142,9 @@ def verify_upload_person_task(data):
 
         if person:
             if not any([event, role]):
-                errors.append("User exists but no event and role to assign"
+                errors.append("User exists but no event and role to assign to"
                               " the user to was provided")
 
-            else:
-                # check for duplicate Task
-                try:
-                    Task.objects.get(event__slug=event, role__name=role,
-                                     person=person)
-                except Task.DoesNotExist:
-                    pass
-                else:
-                    errors.append("Existing person {2} already has role {0}"
-                                  " in event {1}".format(role, event, person))
-
         if (event and not role) or (role and not event):
             errors.append("Must have both or either of event ({0}) and role"
                           " ({1})".format(event, role))
@@ -199,9 +191,10 @@ def create_uploaded_persons_tasks(data):
                 if row['event'] and row['role']:
                     e = Event.objects.get(slug=row['event'])
                     r = Role.objects.get(name=row['role'])
-                    t = Task(person=p, event=e, role=r)
-                    t.save()
-                    tasks_created.append(t)
+                    t, created = Task.objects.get_or_create(person=p, event=e,
+                                                            role=r)
+                    if created:
+                        tasks_created.append(t)
 
             except IntegrityError as e:
                 raise IntegrityError('{0} (for {1})'.format(str(e), row))


Test patch:
diff --git a/workshops/test/test_util.py b/workshops/test/test_util.py
--- a/workshops/test/test_util.py
+++ b/workshops/test/test_util.py
@@ -69,6 +69,16 @@ def test_serializability_of_parsed(self):
         except TypeError:
             self.fail('Dumping person_tasks to JSON unexpectedly failed!')
 
+    def test_malformed_CSV_with_proper_header_row(self):
+        csv = """personal,middle,family,email
+This is a malformed CSV
+        """
+        person_tasks, empty_fields = self.compute_from_string(csv)
+        self.assertEqual(person_tasks[0]["personal"],
+                         "This is a malformed CSV")
+        self.assertEqual(set(empty_fields),
+                         set(["middle", "family", "email"]))
+
 
 class CSVBulkUploadTestBase(TestBase):
     """
@@ -270,7 +280,7 @@ def test_upload_existing_user(self):
     def test_upload_existing_user_existing_task(self):
         """
         Check if uploading existing user and assigning existing task to that
-        user fails.
+        user is silent (ie. no Task nor Person is being created).
         """
         foobar = Event.objects.get(slug="foobar")
         instructor = Role.objects.get(name="Instructor")
@@ -299,15 +309,14 @@ def test_upload_existing_user_existing_task(self):
 
         tasks_pre = set(Task.objects.filter(person=self.harry,
                                             event__slug="foobar"))
+        users_pre = set(Person.objects.all())
+
         rv = self.client.post(reverse('person_bulk_add_confirmation'), payload,
                               follow=True)
+
         tasks_post = set(Task.objects.filter(person=self.harry,
                                              event__slug="foobar"))
+        users_post = set(Person.objects.all())
         self.assertEqual(tasks_pre, tasks_post)
-        self.assertEqual(rv.status_code, 400)
-
-        # we need to decode rv.content, because it's bytes, not str
-        _, params = cgi.parse_header(rv['content-type'])
-        charset = params['charset']
-        content = rv.content.decode(charset)
-        self.assertIn('already has role', content)
+        self.assertEqual(users_pre, users_post)
+        self.assertEqual(rv.status_code, 200)
diff --git a/workshops/test/upload-malformed-file-with-header.csv b/workshops/test/upload-malformed-file-with-header.csv
new file mode 100644
--- /dev/null
+++ b/workshops/test/upload-malformed-file-with-header.csv
@@ -0,0 +1,2 @@
+personal,middle,family,email,event,role
+This is a test file