EDUCATOR-4916 - additional error handling
This commit is contained in:
@@ -125,6 +125,8 @@ class TeamMembershipImportManager(object):
|
|||||||
self.teamset_ids = reader.fieldnames[2:]
|
self.teamset_ids = reader.fieldnames[2:]
|
||||||
row_dictionaries = []
|
row_dictionaries = []
|
||||||
csv_usernames = set()
|
csv_usernames = set()
|
||||||
|
if not self.validate_header(reader.fieldnames):
|
||||||
|
return False
|
||||||
if not self.validate_teamsets():
|
if not self.validate_teamsets():
|
||||||
return False
|
return False
|
||||||
self.load_user_ids_by_teamset_id()
|
self.load_user_ids_by_teamset_id()
|
||||||
@@ -132,6 +134,8 @@ class TeamMembershipImportManager(object):
|
|||||||
self.load_course_teams()
|
self.load_course_teams()
|
||||||
# process student rows:
|
# process student rows:
|
||||||
for row in reader:
|
for row in reader:
|
||||||
|
if not self.validate_teams_have_matching_teamsets(row):
|
||||||
|
return False
|
||||||
username = row['user']
|
username = row['user']
|
||||||
if not username:
|
if not username:
|
||||||
continue
|
continue
|
||||||
@@ -141,7 +145,7 @@ class TeamMembershipImportManager(object):
|
|||||||
user = self.get_user(username)
|
user = self.get_user(username)
|
||||||
if user is None:
|
if user is None:
|
||||||
continue
|
continue
|
||||||
if not self.validate_user_enrolled_in_course(user):
|
if not self.validate_user_enrollment_is_valid(user, row['mode']):
|
||||||
row['user'] = None
|
row['user'] = None
|
||||||
continue
|
continue
|
||||||
row['user'] = user
|
row['user'] = user
|
||||||
@@ -173,6 +177,19 @@ class TeamMembershipImportManager(object):
|
|||||||
for team in CourseTeam.objects.filter(course_id=self.course.id):
|
for team in CourseTeam.objects.filter(course_id=self.course.id):
|
||||||
self.existing_course_teams[(team.name, team.topic_id)] = team
|
self.existing_course_teams[(team.name, team.topic_id)] = team
|
||||||
|
|
||||||
|
def validate_header(self, header):
|
||||||
|
"""
|
||||||
|
Validates header row to ensure that it contains at a minimum columns called 'user', 'mode'.
|
||||||
|
Teamset validation is handled separately
|
||||||
|
"""
|
||||||
|
if 'user' not in header:
|
||||||
|
self.validation_errors.append("Header must contain column 'user'.")
|
||||||
|
return False
|
||||||
|
if 'mode' not in header:
|
||||||
|
self.validation_errors.append("Header must contain column 'mode'.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def validate_teamsets(self):
|
def validate_teamsets(self):
|
||||||
"""
|
"""
|
||||||
Validates team set ids. Returns true if there are no errors.
|
Validates team set ids. Returns true if there are no errors.
|
||||||
@@ -205,14 +222,19 @@ class TeamMembershipImportManager(object):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_user_enrolled_in_course(self, user):
|
def validate_user_enrollment_is_valid(self, user, supplied_enrollment):
|
||||||
"""
|
"""
|
||||||
Invalid states:
|
Invalid states:
|
||||||
user not enrolled in course
|
user not enrolled in course
|
||||||
|
enrollment mode from csv doesn't match actual user enrollment
|
||||||
"""
|
"""
|
||||||
if not CourseEnrollment.is_enrolled(user, self.course.id):
|
actual_enrollment_mode, user_enrolled = CourseEnrollment.enrollment_mode_for_user(user, self.course.id)
|
||||||
|
if not user_enrolled:
|
||||||
self.validation_errors.append('User ' + user.username + ' is not enrolled in this course.')
|
self.validation_errors.append('User ' + user.username + ' is not enrolled in this course.')
|
||||||
return False
|
return False
|
||||||
|
if actual_enrollment_mode != supplied_enrollment.strip():
|
||||||
|
self.validation_errors.append('User ' + user.username + ' enrollment mismatch.')
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -226,6 +248,23 @@ class TeamMembershipImportManager(object):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def validate_teams_have_matching_teamsets(self, row):
|
||||||
|
"""
|
||||||
|
It's possible for a user to create a row that has more team names in it
|
||||||
|
than there are teamset ids provided in the header.
|
||||||
|
In that case, `row` will have one or more null keys mapping to team names, for example:
|
||||||
|
{'teamset-1': 'team-a', 'teamset-2': 'team-beta', None: 'team-37'}
|
||||||
|
|
||||||
|
This method will add a validation error and return False if this is the case.
|
||||||
|
"""
|
||||||
|
if None in row:
|
||||||
|
error_message = "Team(s) {0} don't have matching teamsets.".format(
|
||||||
|
row[None]
|
||||||
|
)
|
||||||
|
if self.add_error_and_check_if_max_exceeded(error_message):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def validate_user_assignment_to_team_and_teamset(self, row):
|
def validate_user_assignment_to_team_and_teamset(self, row):
|
||||||
"""
|
"""
|
||||||
Validates a user entry relative to an existing team.
|
Validates a user entry relative to an existing team.
|
||||||
|
|||||||
@@ -1712,7 +1712,7 @@ class TestBulkMembershipManagement(TeamAPITestCase):
|
|||||||
def test_upload_valid_csv_simple(self):
|
def test_upload_valid_csv_simple(self):
|
||||||
self.create_and_enroll_student(username='a_user')
|
self.create_and_enroll_student(username='a_user')
|
||||||
csv_content = 'user,mode,topic_0' + '\n'
|
csv_content = 'user,mode,topic_0' + '\n'
|
||||||
csv_content += 'a_user, masters, team wind power'
|
csv_content += 'a_user,audit,team wind power'
|
||||||
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
||||||
@@ -1723,7 +1723,7 @@ class TestBulkMembershipManagement(TeamAPITestCase):
|
|||||||
def test_upload_invalid_teamset(self):
|
def test_upload_invalid_teamset(self):
|
||||||
self.create_and_enroll_student(username='a_user')
|
self.create_and_enroll_student(username='a_user')
|
||||||
csv_content = 'user,mode,topic_0_bad' + '\n'
|
csv_content = 'user,mode,topic_0_bad' + '\n'
|
||||||
csv_content += 'a_user, masters, team wind power'
|
csv_content += 'a_user,audit,team wind power'
|
||||||
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
||||||
@@ -1746,9 +1746,9 @@ class TestBulkMembershipManagement(TeamAPITestCase):
|
|||||||
self.create_and_enroll_student(username='b_user')
|
self.create_and_enroll_student(username='b_user')
|
||||||
self.create_and_enroll_student(username='c_user')
|
self.create_and_enroll_student(username='c_user')
|
||||||
csv_content = 'user,mode,topic_0,topic_1,topic_2' + '\n'
|
csv_content = 'user,mode,topic_0,topic_1,topic_2' + '\n'
|
||||||
csv_content += 'a_user, masters,team wind power,team 2' + '\n'
|
csv_content += 'a_user,audit,team wind power,team 2' + '\n'
|
||||||
csv_content += 'b_user, masters,,team 2' + '\n'
|
csv_content += 'b_user,audit,,team 2' + '\n'
|
||||||
csv_content += 'c_user, masters,,,team 3'
|
csv_content += 'c_user,audit,,,team 3'
|
||||||
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
self.make_call(reverse('team_membership_bulk_management', args=[self.good_course_id]),
|
||||||
@@ -1771,17 +1771,25 @@ class TestBulkMembershipManagement(TeamAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_upload_only_existing_courses(self):
|
def test_upload_only_existing_courses(self):
|
||||||
self.create_and_enroll_student(username='a_user')
|
self.create_and_enroll_student(username='a_user', mode=CourseMode.MASTERS)
|
||||||
self.create_and_enroll_student(username='b_user')
|
self.create_and_enroll_student(username='b_user', mode=CourseMode.MASTERS)
|
||||||
existing_team_1 = CourseTeamFactory.create(course_id=self.test_course_1.id, topic_id='topic_1')
|
existing_team_1 = CourseTeamFactory.create(
|
||||||
existing_team_2 = CourseTeamFactory.create(course_id=self.test_course_1.id, topic_id='topic_2')
|
course_id=self.test_course_1.id,
|
||||||
|
topic_id='topic_1',
|
||||||
|
organization_protected=True
|
||||||
|
)
|
||||||
|
existing_team_2 = CourseTeamFactory.create(
|
||||||
|
course_id=self.test_course_1.id,
|
||||||
|
topic_id='topic_2',
|
||||||
|
organization_protected=True
|
||||||
|
)
|
||||||
|
|
||||||
csv_content = 'user,mode,topic_1,topic_2' + '\n'
|
csv_content = 'user,mode,topic_1,topic_2' + '\n'
|
||||||
csv_content += 'a_user, masters,{},{}'.format(
|
csv_content += 'a_user,masters,{},{}'.format(
|
||||||
existing_team_1.name,
|
existing_team_1.name,
|
||||||
existing_team_2.name
|
existing_team_2.name
|
||||||
) + '\n'
|
) + '\n'
|
||||||
csv_content += 'b_user, masters,{},{}'.format(
|
csv_content += 'b_user,masters,{},{}'.format(
|
||||||
existing_team_1.name,
|
existing_team_1.name,
|
||||||
existing_team_2.name
|
existing_team_2.name
|
||||||
) + '\n'
|
) + '\n'
|
||||||
@@ -1794,3 +1802,41 @@ class TestBulkMembershipManagement(TeamAPITestCase):
|
|||||||
data={'csv': csv_file},
|
data={'csv': csv_file},
|
||||||
user='staff'
|
user='staff'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_upload_invalid_header(self):
|
||||||
|
self.create_and_enroll_student(username='a_user')
|
||||||
|
csv_content = 'mode,topic_1' + '\n'
|
||||||
|
csv_content += 'a_user,audit, team wind power'
|
||||||
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
|
self.make_call(reverse(
|
||||||
|
'team_membership_bulk_management', args=[self.good_course_id]),
|
||||||
|
400, method='post',
|
||||||
|
data={'csv': csv_file}, user='staff'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_upload_invalid_more_teams_than_teamsets(self):
|
||||||
|
self.create_and_enroll_student(username='a_user')
|
||||||
|
csv_content = 'user,mode,topic_1' + '\n'
|
||||||
|
csv_content += 'a_user, masters, team wind power, extra1, extra2'
|
||||||
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
|
self.make_call(reverse(
|
||||||
|
'team_membership_bulk_management',
|
||||||
|
args=[self.good_course_id]),
|
||||||
|
400, method='post',
|
||||||
|
data={'csv': csv_file}, user='staff'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_upload_invalid_student_enrollment_mismatch(self):
|
||||||
|
self.create_and_enroll_student(username='a_user', mode=CourseMode.AUDIT)
|
||||||
|
csv_content = 'user,mode,topic_1' + '\n'
|
||||||
|
csv_content += 'a_user,masters,team wind power'
|
||||||
|
csv_file = SimpleUploadedFile('test_file.csv', csv_content.encode('utf8'), content_type='text/csv')
|
||||||
|
self.client.login(username=self.users['course_staff'].username, password=self.users['course_staff'].password)
|
||||||
|
self.make_call(reverse(
|
||||||
|
'team_membership_bulk_management',
|
||||||
|
args=[self.good_course_id]),
|
||||||
|
400, method='post',
|
||||||
|
data={'csv': csv_file}, user='staff'
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user