Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lms/djangoapps/instructor/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4175,6 +4175,16 @@ def test_change_due_date_with_reason(self):
# This operation regenerates the cache, so we can use cached results from edx-when.
assert get_date_for_block(self.course, self.week1, self.user1, use_cached=True) == due_date

def test_reset_due_date_with_reason(self):
url = reverse('reset_due_date', kwargs={'course_id': str(self.course.id)})
response = self.client.post(url, {
'student': self.user1.username,
'url': str(self.week1.location),
'reason': 'Testing reason.' # this is optional field.
})
assert response.status_code == 200
assert 'Successfully reset due date for student' in response.content.decode('utf-8')

def test_change_to_invalid_due_date(self):
url = reverse('change_due_date', kwargs={'course_id': str(self.course.id)})
response = self.client.post(url, {
Expand Down
71 changes: 46 additions & 25 deletions lms/djangoapps/instructor/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@
handle_dashboard_error,
keep_field_private,
parse_datetime,
require_student_from_identifier,
set_due_date_extension,
strip_if_string,
)
Expand Down Expand Up @@ -3035,37 +3034,59 @@ def post(self, request, course_id):
due_date.strftime('%Y-%m-%d %H:%M')))


@handle_dashboard_error
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
@require_post_params('student', 'url')
def reset_due_date(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ResetDueDate(APIView):
"""
Rescinds a due date extension for a student on a particular unit.
"""
course = get_course_by_id(CourseKey.from_string(course_id))
student = require_student_from_identifier(request.POST.get('student'))
unit = find_unit(course, request.POST.get('url'))
reason = strip_tags(request.POST.get('reason', ''))
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.GIVE_STUDENT_EXTENSION
serializer_class = BlockDueDateSerializer

version = getattr(course, 'course_version', None)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
reset a due date extension to a student for a particular unit.
params:
url (str): The URL related to the block that needs the due date update.
student (str): The email or username of the student whose access is being modified.
reason (str): Optional param.
"""
serializer_data = self.serializer_class(data=request.data, context={'disable_due_datetime': True})
if not serializer_data.is_valid():
return HttpResponseBadRequest(reason=serializer_data.errors)

original_due_date = get_date_for_block(course_id, unit.location, published_version=version)
student = serializer_data.validated_data.get('student')
if not student:
response_payload = {
'error': f'Could not find student matching identifier: {request.data.get("student")}'
}
return JsonResponse(response_payload)

set_due_date_extension(course, unit, student, None, request.user, reason=reason)
if not original_due_date:
# It's possible the normal due date was deleted after an extension was granted:
return JsonResponse(
_("Successfully removed invalid due date extension (unit has no due date).")
)
course = get_course_by_id(CourseKey.from_string(course_id))
unit = find_unit(course, serializer_data.validated_data.get('url'))
reason = strip_tags(serializer_data.validated_data.get('reason', ''))

version = getattr(course, 'course_version', None)

original_due_date = get_date_for_block(course_id, unit.location, published_version=version)

original_due_date_str = original_due_date.strftime('%Y-%m-%d %H:%M')
return JsonResponse(_(
'Successfully reset due date for student {0} for {1} '
'to {2}').format(student.profile.name, _display_unit(unit),
original_due_date_str))
try:
set_due_date_extension(course, unit, student, None, request.user, reason=reason)
if not original_due_date:
# It's possible the normal due date was deleted after an extension was granted:
return JsonResponse(
_("Successfully removed invalid due date extension (unit has no due date).")
)

original_due_date_str = original_due_date.strftime('%Y-%m-%d %H:%M')
return JsonResponse(_(
'Successfully reset due date for student {0} for {1} '
'to {2}').format(student.profile.name, _display_unit(unit),
original_due_date_str))

except Exception as error: # pylint: disable=broad-except
return JsonResponse({'error': str(error)}, status=400)


@handle_dashboard_error
Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/instructor/views/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
path('update_forum_role_membership', api.update_forum_role_membership, name='update_forum_role_membership'),
path('change_due_date', api.ChangeDueDate.as_view(), name='change_due_date'),
path('send_email', api.SendEmail.as_view(), name='send_email'),
path('reset_due_date', api.reset_due_date, name='reset_due_date'),
path('reset_due_date', api.ResetDueDate.as_view(), name='reset_due_date'),
path('show_unit_extensions', api.show_unit_extensions, name='show_unit_extensions'),
path('show_student_extensions', api.ShowStudentExtensions.as_view(), name='show_student_extensions'),

Expand Down
7 changes: 7 additions & 0 deletions lms/djangoapps/instructor/views/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,10 @@ def validate_student(self, value):
return None

return user

def __init__(self, *args, **kwargs):
# Get context to check if `due_datetime` should be optional
disable_due_datetime = kwargs.get('context', {}).get('disable_due_datetime', False)
super().__init__(*args, **kwargs)
if disable_due_datetime:
self.fields['due_datetime'].required = False