def upsert(self, fileobj, as_string_obj=False):
    'Expects a file *object*, not a file path. This is important because this has to work for both\n        the management command and the web uploader; the web uploader will pass in in-memory file\n        with no path!\n\n        Header row is:\n        Title, Group, Task List, Created Date, Due Date, Completed, Created By, Assigned To, Note, Priority\n        '
    if as_string_obj:
        csv_reader = csv.DictReader(fileobj)
    else:
        csv_reader = csv.DictReader(codecs.iterdecode(fileobj, 'utf-8'))
    header = csv_reader.fieldnames
    expected = ['Title', 'Group', 'Task List', 'Created By', 'Created Date', 'Due Date', 'Completed', 'Assigned To', 'Note', 'Priority']
    if (header != expected):
        self.errors.append(f'''Inbound data does not have expected columns.
Should be: {expected}''')
        return
    for row in csv_reader:
        self.line_count += 1
        newrow = self.validate_row(row)
        if newrow:
            assignee = (newrow.get('Assigned To') if newrow.get('Assigned To') else None)
            created_at = (newrow.get('Created Date') if newrow.get('Created Date') else datetime.datetime.today())
            due_date = (newrow.get('Due Date') if newrow.get('Due Date') else None)
            priority = (newrow.get('Priority') if newrow.get('Priority') else None)
            (obj, created) = Task.objects.update_or_create(created_by=newrow.get('Created By'), task_list=newrow.get('Task List'), title=newrow.get('Title'), defaults={'assigned_to': assignee, 'completed': newrow.get('Completed'), 'created_at': created_at, 'due_date': due_date, 'note': newrow.get('Note'), 'priority': priority})
            self.upsert_count += 1
            msg = f'Upserted task {obj.id}: "{obj.title}" in list "{obj.task_list}" (group "{obj.task_list.group}")'
            self.upserts.append(msg)
    self.summaries.append(f'Processed {self.line_count} CSV rows')
    self.summaries.append(f'Upserted {self.upsert_count} rows')
    self.summaries.append(f'Skipped {(self.line_count - self.upsert_count)} rows')
    return {'summaries': self.summaries, 'upserts': self.upserts, 'errors': self.errors}
Expects a file *object*, not a file path. This is important because this has to work for both
the management command and the web uploader; the web uploader will pass in in-memory file
with no path!

Header row is:
Title, Group, Task List, Created Date, Due Date, Completed, Created By, Assigned To, Note, Priority
def upsert(self, fileobj, as_string_obj=False):
    'Expects a file *object*, not a file path. This is important because this has to work for both\n        the management command and the web uploader; the web uploader will pass in in-memory file\n        with no path!\n\n        Header row is:\n        Title, Group, Task List, Created Date, Due Date, Completed, Created By, Assigned To, Note, Priority\n        '
    if as_string_obj:
        csv_reader = csv.DictReader(fileobj)
    else:
        csv_reader = csv.DictReader(codecs.iterdecode(fileobj, 'utf-8'))
    header = csv_reader.fieldnames
    expected = ['Title', 'Group', 'Task List', 'Created By', 'Created Date', 'Due Date', 'Completed', 'Assigned To', 'Note', 'Priority']
    if (header != expected):
        self.errors.append(f'Inbound data does not have expected columns.
Should be: {expected}')
        return
    for row in csv_reader:
        self.line_count += 1
        newrow = self.validate_row(row)
        if newrow:
            assignee = (newrow.get('Assigned To') if newrow.get('Assigned To') else None)
            created_at = (newrow.get('Created Date') if newrow.get('Created Date') else datetime.datetime.today())
            due_date = (newrow.get('Due Date') if newrow.get('Due Date') else None)
            priority = (newrow.get('Priority') if newrow.get('Priority') else None)
            (obj, created) = Task.objects.update_or_create(created_by=newrow.get('Created By'), task_list=newrow.get('Task List'), title=newrow.get('Title'), defaults={'assigned_to': assignee, 'completed': newrow.get('Completed'), 'created_at': created_at, 'due_date': due_date, 'note': newrow.get('Note'), 'priority': priority})
            self.upsert_count += 1
            msg = f'Upserted task {obj.id}: "{obj.title}" in list "{obj.task_list}" (group "{obj.task_list.group}")'
            self.upserts.append(msg)
    self.summaries.append(f'Processed {self.line_count} CSV rows')
    self.summaries.append(f'Upserted {self.upsert_count} rows')
    self.summaries.append(f'Skipped {(self.line_count - self.upsert_count)} rows')
    return {'summaries': self.summaries, 'upserts': self.upserts, 'errors': self.errors}