Skip to content

Commit

Permalink
feat: add management command to delete exam attempts (#1243)
Browse files Browse the repository at this point in the history
* feat: add management command to delete exam attempts

* fix: quality
  • Loading branch information
alangsto authored Nov 6, 2024
1 parent 5384a26 commit 0b89b87
Show file tree
Hide file tree
Showing 5 changed files with 152 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'
74 changes: 74 additions & 0 deletions edx_proctoring/management/commands/reset_attempts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
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 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
"""
from tempfile import NamedTemporaryFile

import ddt

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 num in ids:
writing_file.write(str(num) + '\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 0b89b87

Please sign in to comment.