Skip to content

Commit

Permalink
feat: add management command to delete exam attempts
Browse files Browse the repository at this point in the history
  • Loading branch information
alangsto committed Nov 4, 2024
1 parent 5384a26 commit 47d1216
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Change Log
Unreleased
~~~~~~~~~~

[4.18.3] - 2024-11-04
~~~~~~~~~~~~~~~~~~~~~
* add management command to delete attempts

[4.18.2] - 2024-10-03
~~~~~~~~~~~~~~~~~~~~~
* fix various bugs related to exams configured with removed proctoring backend
Expand Down
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"""

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '4.18.2'
__version__ = '4.18.3'
76 changes: 76 additions & 0 deletions edx_proctoring/management/commands/reset_attempts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Django management command to delete attempts. This command should only be used
to remove attempts that have not been started or completed, as it will not
reset problem state or grade overrides.
"""
import csv
import logging
import time

from django.core.management.base import BaseCommand

from edx_proctoring.models import ProctoredExamStudentAttempt

log = logging.getLogger(__name__)


class Command(BaseCommand):
"""
Django Management command to delete attempts.
"""

def add_arguments(self, parser):
parser.add_argument(
'-p',
'--file_path',
metavar='file_path',
dest='file_path',
required=True,
help='Path to file.'
)
parser.add_argument(
'--batch_size',
action='store',
dest='batch_size',
type=int,
default=300,
help='Maximum number of attempt_ids to process. '
'This helps avoid overloading the database while updating large amount of data.'
)
parser.add_argument(
'--sleep_time',
action='store',
dest='sleep_time',
type=int,
default=10,
help='Sleep time in seconds between update of batches'
)

def handle(self, *args, **options):
"""
Management command entry point, simply call into the signal firing
"""
batch_size = options['batch_size']
sleep_time = options['sleep_time']
file_path = options['file_path']

with open(file_path, 'r') as file:
ids_to_delete = file.readlines()

total_deleted = 0

for i in range(0, len(ids_to_delete), batch_size):
batch_to_delete = ids_to_delete[i:i + batch_size]

delete_queryset = ProctoredExamStudentAttempt.objects.filter(
id__in=batch_to_delete
)
deleted_count, _ = delete_queryset.delete()

total_deleted += deleted_count

log.info(f'{deleted_count} attempts deleted.')
time.sleep(sleep_time)


log.info(f'Job completed. {total_deleted} attempts deleted.')
72 changes: 72 additions & 0 deletions edx_proctoring/management/commands/tests/test_reset_attempts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Tests for the reset_attempts management command
"""

import ddt
from tempfile import NamedTemporaryFile

from django.core.management import call_command

from edx_proctoring.api import create_exam
from edx_proctoring.models import ProctoredExamStudentAttempt
from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus
from edx_proctoring.tests.utils import LoggedInTestCase


@ddt.ddt
class ResetAttemptsTests(LoggedInTestCase):
"""
Coverage of the reset_attempts.py file
"""

def setUp(self):
"""
Build up test data
"""
super().setUp()
self.exam_id = create_exam(
course_id='a/b/c',
content_id='bar',
exam_name='Test Exam',
time_limit_mins=90
)

self.num_attempts = 10

user_list = self.create_batch_users(self.num_attempts)
for user in user_list:
ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=self.exam_id,
user_id=user.id,
external_id='foo',
status=ProctoredExamStudentAttemptStatus.created,
allowed_time_limit_mins=10,
taking_as_proctored=True,
is_sample_attempt=False
)

@ddt.data(
5,
7,
10,
)
def test_run_command(self, num_to_delete):
"""
Run the management command
"""
ids = list(ProctoredExamStudentAttempt.objects.all().values_list('id', flat=True))[:num_to_delete]

with NamedTemporaryFile() as file:
with open(file.name, 'w') as writing_file:
for id in ids:
writing_file.write(str(id) + '\n')

call_command(
'reset_attempts',
batch_size=2,
sleep_time=0,
file_path=file.name,
)

attempts = ProctoredExamStudentAttempt.objects.all()
self.assertEqual(len(attempts), self.num_attempts - num_to_delete)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@edx/edx-proctoring",
"//": "Note that the version format is slightly different than that of the Python version when using prereleases.",
"version": "4.18.2",
"version": "4.18.3",
"main": "edx_proctoring/static/index.js",
"scripts": {
"test": "gulp test"
Expand Down

0 comments on commit 47d1216

Please sign in to comment.