Commit c6ecf255 by Matt Drayer

Fix for SOL-475 (double exam grader bug)

- Remove grader(s) upon deletion of existing entrance exam
- Remove grader(s) upon addition of new entrance exam
parent 6eda3ef7
...@@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required ...@@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from contentstore.views.helpers import create_xblock from contentstore.views.helpers import create_xblock, remove_entrance_exam_graders
from contentstore.views.item import delete_item from contentstore.views.item import delete_item
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.keys import CourseKey, UsageKey
...@@ -270,6 +270,9 @@ def _delete_entrance_exam(request, course_key): ...@@ -270,6 +270,9 @@ def _delete_entrance_exam(request, course_key):
} }
CourseMetadata.update_from_dict(metadata, course, request.user) CourseMetadata.update_from_dict(metadata, course, request.user)
# Clean up any pre-existing entrance exam graders
remove_entrance_exam_graders(course_key, request.user)
return HttpResponse(status=204) return HttpResponse(status=204)
......
...@@ -25,6 +25,17 @@ from models.settings.course_grading import CourseGradingModel ...@@ -25,6 +25,17 @@ from models.settings.course_grading import CourseGradingModel
__all__ = ['edge', 'event', 'landing'] __all__ = ['edge', 'event', 'landing']
# Note: Grader types are used throughout the platform but most usages are simply in-line
# strings. In addition, new grader types can be defined on the fly anytime one is needed
# (because they're just strings). This dict is an attempt to constrain the sprawl in Studio.
GRADER_TYPES = {
"HOMEWORK": "Homework",
"LAB": "Lab",
"ENTRANCE_EXAM": "Entrance Exam",
"MIDTERM_EXAM": "Midterm Exam",
"FINAL_EXAM": "Final Exam"
}
# points to the temporary course landing page with log in and sign up # points to the temporary course landing page with log in and sign up
def landing(request, org, course, coursename): def landing(request, org, course, coursename):
...@@ -173,6 +184,18 @@ def usage_key_with_run(usage_key_string): ...@@ -173,6 +184,18 @@ def usage_key_with_run(usage_key_string):
return usage_key return usage_key
def remove_entrance_exam_graders(course_key, user):
"""
Removes existing entrance exam graders attached to the specified course
Typically used when adding/removing an entrance exam.
"""
grading_model = CourseGradingModel.fetch(course_key)
graders = grading_model.graders
for i, grader in enumerate(graders):
if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']:
CourseGradingModel.delete_grader(course_key, i, user)
def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False): def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False):
""" """
Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc. Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc.
...@@ -228,11 +251,14 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None ...@@ -228,11 +251,14 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None
# Entrance Exams: Grader assignment # Entrance Exams: Grader assignment
if settings.FEATURES.get('ENTRANCE_EXAMS', False): if settings.FEATURES.get('ENTRANCE_EXAMS', False):
course = store.get_course(usage_key.course_key) course_key = usage_key.course_key
course = store.get_course(course_key)
if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled: if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled:
if category == 'sequential' and parent_locator == course.entrance_exam_id: if category == 'sequential' and parent_locator == course.entrance_exam_id:
# Clean up any pre-existing entrance exam graders
remove_entrance_exam_graders(course_key, user)
grader = { grader = {
"type": "Entrance Exam", "type": GRADER_TYPES['ENTRANCE_EXAM'],
"min_count": 0, "min_count": 0,
"drop_count": 0, "drop_count": 0,
"short_label": "Entrance", "short_label": "Entrance",
......
...@@ -11,6 +11,7 @@ from django.test.client import RequestFactory ...@@ -11,6 +11,7 @@ from django.test.client import RequestFactory
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase
from contentstore.utils import reverse_url from contentstore.utils import reverse_url
from contentstore.views.entrance_exam import create_entrance_exam, update_entrance_exam, delete_entrance_exam from contentstore.views.entrance_exam import create_entrance_exam, update_entrance_exam, delete_entrance_exam
from contentstore.views.helpers import GRADER_TYPES
from models.settings.course_grading import CourseGradingModel from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
...@@ -84,7 +85,7 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -84,7 +85,7 @@ class EntranceExamHandlerTests(CourseTestCase):
seq_locator_string = json.loads(resp.content).get('locator') seq_locator_string = json.loads(resp.content).get('locator')
seq_locator = UsageKey.from_string(seq_locator_string) seq_locator = UsageKey.from_string(seq_locator_string)
section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator) section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator)
self.assertEqual('Entrance Exam', section_grader_type['graderType']) self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType'])
def test_contentstore_views_entrance_exam_get(self): def test_contentstore_views_entrance_exam_get(self):
""" """
...@@ -140,6 +141,14 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -140,6 +141,14 @@ class EntranceExamHandlerTests(CourseTestCase):
resp = self.client.get(self.exam_url) resp = self.client.get(self.exam_url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
# Confirm that we have only one Entrance Exam grader after re-adding the exam (validates SOL-475)
graders = CourseGradingModel.fetch(self.course_key).graders
count = 0
for grader in graders:
if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']:
count += 1
self.assertEqual(count, 1)
def test_contentstore_views_entrance_exam_delete_bogus_course(self): def test_contentstore_views_entrance_exam_delete_bogus_course(self):
""" """
Unit Test: test_contentstore_views_entrance_exam_delete_bogus_course Unit Test: test_contentstore_views_entrance_exam_delete_bogus_course
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment