diff --git a/edx_exams/apps/core/api.py b/edx_exams/apps/core/api.py index 6fa04d0f..ef02fe2d 100644 --- a/edx_exams/apps/core/api.py +++ b/edx_exams/apps/core/api.py @@ -9,6 +9,7 @@ from django.utils import dateparse, timezone from opaque_keys.edx.keys import CourseKey, UsageKey +from edx_exams.apps.core.email import send_attempt_status_email from edx_exams.apps.core.exam_types import OnboardingExamType, PracticeExamType, get_exam_type from edx_exams.apps.core.exceptions import ( ExamAttemptAlreadyExists, @@ -140,6 +141,8 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.status = to_status attempt_obj.save() + send_attempt_status_email(attempt_obj) + return attempt_id diff --git a/edx_exams/apps/core/email.py b/edx_exams/apps/core/email.py new file mode 100644 index 00000000..1ceef53e --- /dev/null +++ b/edx_exams/apps/core/email.py @@ -0,0 +1,61 @@ +""" +Handles rendering templates and sending emails. +""" +import logging + +from django.conf import settings +from django.core.mail.message import EmailMessage +from django.template import loader + +from edx_exams.apps.core.exam_types import get_exam_type +from edx_exams.apps.core.statuses import ExamAttemptStatus + +log = logging.getLogger(__name__) + + +def send_attempt_status_email(attempt): + """ + Send email for attempt status if necessary + """ + exam = attempt.exam + exam_type = get_exam_type(exam.exam_type) + + # do not send emails on practice exams or non-proctored exams + if not exam_type.is_proctored or exam_type.is_practice: + return + + if attempt.status == ExamAttemptStatus.submitted: + email_template = 'email/proctoring_attempt_submitted.html' + elif attempt.status == ExamAttemptStatus.verified: + email_template = 'email/proctoring_attempt_verified.html' + elif attempt.status == ExamAttemptStatus.rejected: + email_template = 'email/proctoring_attempt_rejected.html' + else: + return # do not send emails for other statuses + + email_template = loader.get_template(email_template) + course_url = f'{settings.LEARNING_MICROFRONTEND_URL}/course/{exam.course_id}' + contact_url = f'{settings.LMS_ROOT_URL}/support/contact_us' + + email_subject = f'Proctored exam {exam.exam_name} for user {attempt.user.username}' + body = email_template.render({ + 'exam_name': exam.exam_name, + 'course_url': course_url, + 'contact_url': contact_url, + }) + + email = EmailMessage( + subject=email_subject, + body=body, + from_email=settings.DEFAULT_FROM_EMAIL, + to=[attempt.user.email], + ) + email.content_subtype = 'html' + + try: + email.send() + except Exception as err: # pylint: disable=broad-except + log.error( + 'Error while sending proctoring status email for ' + f'user_id {attempt.user.id}, exam_id {exam.id}: {err}' + ) diff --git a/edx_exams/apps/core/templates/email/proctoring_attempt_rejected.html b/edx_exams/apps/core/templates/email/proctoring_attempt_rejected.html new file mode 100644 index 00000000..1765c310 --- /dev/null +++ b/edx_exams/apps/core/templates/email/proctoring_attempt_rejected.html @@ -0,0 +1,33 @@ +{% load i18n %} + +
+ {% block introduction %} + {% blocktrans %} + Hello {{ username }}, + {% endblocktrans %} + {% endblock %} +
++ {% block status_information %} + {% blocktrans %} + Your proctored exam "{{ exam_name }}" in + {{ course_url }} was reviewed and the + course team has identified one or more violations of the proctored exam rules. Examples + of issues that may result in a rules violation include browsing + the internet, using a phone, + or getting help from another person. As a result of the identified issue(s), + you did not successfully meet the proctored exam requirements. + {% endblocktrans %} + {% endblock %} +
++ {% block contact_information %} + {% blocktrans %} + To appeal your proctored exam results, please reach out with any relevant information + about your exam at + + {{ contact_url_text }} + . + {% endblocktrans %} + {% endblock %} +
diff --git a/edx_exams/apps/core/templates/email/proctoring_attempt_submitted.html b/edx_exams/apps/core/templates/email/proctoring_attempt_submitted.html new file mode 100644 index 00000000..c635a2bd --- /dev/null +++ b/edx_exams/apps/core/templates/email/proctoring_attempt_submitted.html @@ -0,0 +1,20 @@ +{% load i18n %} + ++ {% block introduction %} + {% blocktrans %} + Hello {{ username }}, + {% endblocktrans %} + {% endblock %} +
++ {% block status_information %} + {% blocktrans %} + Your proctored exam "{{ exam_name }}" in + {{ course_url }} was submitted + successfully and will now be reviewed to ensure all exam + rules were followed. You should receive an email with your exam + status within 5 business days. + {% endblocktrans %} + {% endblock %} +
diff --git a/edx_exams/apps/core/templates/email/proctoring_attempt_verified.html b/edx_exams/apps/core/templates/email/proctoring_attempt_verified.html new file mode 100644 index 00000000..175ba8d5 --- /dev/null +++ b/edx_exams/apps/core/templates/email/proctoring_attempt_verified.html @@ -0,0 +1,28 @@ +{% load i18n %} + ++ {% block introduction %} + {% blocktrans %} + Hello {{ username }}, + {% endblocktrans %} + {% endblock %} +
++ {% block status_information %} + {% blocktrans %} + Your proctored exam "{{ exam_name }}" in + {{ course_url }} was reviewed and you + met all proctoring requirements. + {% endblocktrans %} + {% endblock %} +
++ {% block contact_information %} + {% blocktrans %} + If you have any questions about your results, you can reach out at + + {{ contact_url }} + . + {% endblocktrans %} + {% endblock %} +
diff --git a/edx_exams/apps/core/tests/test_email.py b/edx_exams/apps/core/tests/test_email.py new file mode 100644 index 00000000..27484c9a --- /dev/null +++ b/edx_exams/apps/core/tests/test_email.py @@ -0,0 +1,105 @@ +""" +Test email notifications for attempt status change +""" + +import ddt +import mock +from django.core import mail +from django.test import TestCase + +from edx_exams.apps.core.api import update_attempt_status +from edx_exams.apps.core.test_utils.factories import ExamAttemptFactory, ExamFactory, UserFactory + + +@ddt.ddt +class TestEmail(TestCase): + """ + Test email notifications for attempt status change + """ + def setUp(self): + super().setUp() + + self.user = UserFactory.create() + self.proctored_exam = ExamFactory.create( + exam_type='proctored', + ) + self.started_attempt = ExamAttemptFactory.create( + exam=self.proctored_exam, + user=self.user, + status='started', + ) + + @staticmethod + def _normalize_whitespace(string): + """ + Replaces newlines and multiple spaces with a single space. + """ + return ' '.join(string.replace('\n', '').split()) + + @ddt.data( + ('submitted', 'was submitted successfully'), + ('verified', 'was reviewed and you met all proctoring requirements'), + ('rejected', 'the course team has identified one or more violations'), + ) + @ddt.unpack + def test_send_email(self, status, expected_message): + """ + Test correct message is sent for statuses that trigger an email + """ + update_attempt_status(self.started_attempt.id, status) + self.assertEqual(len(mail.outbox), 1) + self.assertIn(self.started_attempt.user.email, mail.outbox[0].to) + self.assertIn(expected_message, self._normalize_whitespace(mail.outbox[0].body)) + + @mock.patch('edx_exams.apps.core.email.log.error') + def test_send_email_failure(self, mock_log_error): + """ + Test error is logged when an email fails to send + """ + with mock.patch('edx_exams.apps.core.email.EmailMessage.send', side_effect=Exception): + update_attempt_status(self.started_attempt.id, 'submitted') + mock_log_error.assert_called_once() + self.assertIn('Error while sending proctoring status email', mock_log_error.call_args[0][0]) + + @ddt.data( + 'created', + 'ready_to_start', + 'download_software_clicked', + 'started', + 'ready_to_submit', + 'error', + ) + def test_status_should_not_send_email(self, status): + """ + Test no email is sent for statuses that should not trigger + """ + update_attempt_status(self.started_attempt.id, status) + self.assertEqual(len(mail.outbox), 0) + + def test_non_proctored_exam_should_not_send_email(self): + """ + Test no email is sent for non-proctored exams + """ + timed_attempt = ExamAttemptFactory.create( + exam=ExamFactory.create( + exam_type='timed', + ), + user=self.user, + status='started', + ) + update_attempt_status(timed_attempt.id, 'submitted') + self.assertEqual(len(mail.outbox), 0) + + def test_practice_exam_should_not_send_email(self): + """ + Test no email is sent for practice exams + """ + practice_proctored_attempt = ExamAttemptFactory.create( + exam=ExamFactory.create( + exam_type='onboarding', + ), + user=self.user, + status='started', + ) + update_attempt_status(practice_proctored_attempt.id, 'submitted') + self.assertEqual(len(mail.outbox), 0) diff --git a/edx_exams/settings/base.py b/edx_exams/settings/base.py index 63a933e9..e1fd2b34 100644 --- a/edx_exams/settings/base.py +++ b/edx_exams/settings/base.py @@ -157,6 +157,9 @@ def root(*path_fragments): root('static'), ) +# EMAIL CONFIGURATION +DEFAULT_FROM_EMAIL: 'no-reply@example.com' + # TEMPLATE CONFIGURATION # See: https://docs.djangoproject.com/en/2.2/ref/settings/#templates TEMPLATES = [ diff --git a/edx_exams/settings/production.py b/edx_exams/settings/production.py index ddc70e1d..ffd7e261 100644 --- a/edx_exams/settings/production.py +++ b/edx_exams/settings/production.py @@ -53,3 +53,6 @@ for override, value in DB_OVERRIDES.items(): DATABASES['default'][override] = value + +# EMAIL CONFIGURATION +EMAIL_BACKEND = 'django_ses.SESBackend' diff --git a/requirements/base.txt b/requirements/base.txt index 3b984a87..25c3eae5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -16,9 +16,9 @@ attrs==23.1.0 # openedx-events bleach==6.1.0 # via lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # boto3 # s3transfer @@ -28,7 +28,7 @@ cffi==1.16.0 # via # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via requests click==8.1.7 # via @@ -209,7 +209,7 @@ markupsafe==2.1.3 # xblock mysqlclient==2.2.0 # via -r requirements/base.in -newrelic==9.1.0 +newrelic==9.1.1 # via edx-django-utils oauthlib==3.2.2 # via @@ -337,7 +337,7 @@ uritemplate==4.1.1 # via # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # botocore # requests diff --git a/requirements/ci.txt b/requirements/ci.txt index d611fd0c..fefb1dff 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -26,5 +26,5 @@ tox==3.28.0 # via # -c requirements/common_constraints.txt # -r requirements/ci.in -virtualenv==20.24.5 +virtualenv==20.24.6 # via tox diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 5a86f013..263dccbd 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -1,4 +1,5 @@ + # A central location for most common version constraints # (across edx repos) for pip-installation. # diff --git a/requirements/dev.txt b/requirements/dev.txt index 8112303c..2c87c607 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -12,7 +12,7 @@ asgiref==3.7.2 # via # -r requirements/validation.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # -r requirements/validation.txt # pylint @@ -30,11 +30,11 @@ bleach==6.1.0 # via # -r requirements/validation.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/validation.txt # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/validation.txt # boto3 @@ -54,7 +54,7 @@ cffi==1.16.0 # pynacl chardet==5.2.0 # via diff-cover -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/validation.txt # requests @@ -94,7 +94,6 @@ cryptography==41.0.4 # via # -r requirements/validation.txt # pyjwt - # secretstorage # social-auth-core ddt==1.6.0 # via -r requirements/validation.txt @@ -103,7 +102,7 @@ defusedxml==0.8.0rc2 # -r requirements/validation.txt # python3-openid # social-auth-core -diff-cover==8.0.0 +diff-cover==7.7.0 # via -r requirements/dev.in dill==0.3.7 # via @@ -259,7 +258,7 @@ exceptiongroup==1.1.3 # pytest factory-boy==3.3.0 # via -r requirements/validation.txt -faker==19.10.0 +faker==19.11.0 # via # -r requirements/validation.txt # factory-boy @@ -323,11 +322,6 @@ jaraco-classes==3.3.0 # via # -r requirements/validation.txt # keyring -jeepney==0.8.0 - # via - # -r requirements/validation.txt - # keyring - # secretstorage jinja2==3.1.2 # via # -r requirements/validation.txt @@ -352,10 +346,6 @@ lazy==1.6 # -r requirements/validation.txt # lti-consumer-xblock # xblock -lazy-object-proxy==1.9.0 - # via - # -r requirements/validation.txt - # astroid lti-consumer-xblock==9.6.2 # via -r requirements/validation.txt lxml==4.9.3 @@ -396,7 +386,7 @@ more-itertools==10.1.0 # jaraco-classes mysqlclient==2.2.0 # via -r requirements/validation.txt -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/validation.txt # edx-django-utils @@ -502,7 +492,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # pyjwt # social-auth-core -pylint==2.17.7 +pylint==3.0.2 # via # -r requirements/validation.txt # edx-lint @@ -513,7 +503,7 @@ pylint-celery==0.3 # via # -r requirements/validation.txt # edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via # -r requirements/validation.txt # edx-lint @@ -619,10 +609,6 @@ s3transfer==0.7.0 # via # -r requirements/validation.txt # boto3 -secretstorage==3.3.3 - # via - # -r requirements/validation.txt - # keyring semantic-version==2.10.0 # via # -r requirements/validation.txt @@ -715,14 +701,14 @@ uritemplate==4.1.1 # -r requirements/validation.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/validation.txt # botocore # requests # responses # twine -virtualenv==20.24.5 +virtualenv==20.24.6 # via # -r requirements/validation.txt # tox @@ -747,10 +733,6 @@ wheel==0.41.2 # via # -r requirements/pip-tools.txt # pip-tools -wrapt==1.15.0 - # via - # -r requirements/validation.txt - # astroid xblock[django]==1.8.1 # via # -r requirements/validation.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index ccb79b69..009a5147 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -14,7 +14,7 @@ asgiref==3.7.2 # via # -r requirements/test.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # -r requirements/test.txt # pylint @@ -34,11 +34,11 @@ bleach==6.1.0 # via # -r requirements/test.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/test.txt # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/test.txt # boto3 @@ -54,7 +54,7 @@ cffi==1.16.0 # -r requirements/test.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/test.txt # requests @@ -92,7 +92,6 @@ cryptography==41.0.4 # via # -r requirements/test.txt # pyjwt - # secretstorage # social-auth-core ddt==1.6.0 # via -r requirements/test.txt @@ -256,7 +255,7 @@ exceptiongroup==1.1.3 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==19.10.0 +faker==19.11.0 # via # -r requirements/test.txt # factory-boy @@ -317,10 +316,6 @@ itypes==1.2.0 # coreapi jaraco-classes==3.3.0 # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage jinja2==3.1.2 # via # -r requirements/test.txt @@ -343,10 +338,6 @@ lazy==1.6 # -r requirements/test.txt # lti-consumer-xblock # xblock -lazy-object-proxy==1.9.0 - # via - # -r requirements/test.txt - # astroid lti-consumer-xblock==9.6.2 # via -r requirements/test.txt lxml==4.9.3 @@ -380,7 +371,7 @@ more-itertools==10.1.0 # via jaraco-classes mysqlclient==2.2.0 # via -r requirements/test.txt -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/test.txt # edx-django-utils @@ -471,7 +462,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # pyjwt # social-auth-core -pylint==2.17.7 +pylint==3.0.2 # via # -r requirements/test.txt # edx-lint @@ -482,7 +473,7 @@ pylint-celery==0.3 # via # -r requirements/test.txt # edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via # -r requirements/test.txt # edx-lint @@ -581,8 +572,6 @@ s3transfer==0.7.0 # via # -r requirements/test.txt # boto3 -secretstorage==3.3.3 - # via keyring semantic-version==2.10.0 # via # -r requirements/test.txt @@ -691,14 +680,14 @@ uritemplate==4.1.1 # -r requirements/test.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/test.txt # botocore # requests # responses # twine -virtualenv==20.24.5 +virtualenv==20.24.6 # via # -r requirements/test.txt # tox @@ -719,10 +708,6 @@ webob==1.8.7 # via # -r requirements/test.txt # xblock -wrapt==1.15.0 - # via - # -r requirements/test.txt - # astroid xblock[django]==1.8.1 # via # -r requirements/test.txt diff --git a/requirements/pip.txt b/requirements/pip.txt index 2154d29f..0c788d61 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.41.2 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==23.3 +pip==23.3.1 # via -r requirements/pip.in setuptools==68.2.2 # via -r requirements/pip.in diff --git a/requirements/production.in b/requirements/production.in index 292fe18f..787f3dd5 100644 --- a/requirements/production.in +++ b/requirements/production.in @@ -1,6 +1,7 @@ # Packages required in a production environment -r base.txt +django-ses gevent gunicorn mysqlclient diff --git a/requirements/production.txt b/requirements/production.txt index 5fcb3f72..f9b30918 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -25,11 +25,12 @@ bleach==6.1.0 # via # -r requirements/base.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/base.txt + # django-ses # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/base.txt # boto3 @@ -43,7 +44,7 @@ cffi==1.16.0 # -r requirements/base.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/base.txt # requests @@ -85,6 +86,7 @@ django==3.2.22 # django-extensions # django-filter # django-model-utils + # django-ses # django-statici18n # django-waffle # djangorestframework @@ -130,6 +132,8 @@ django-model-utils==4.3.1 # via -r requirements/base.txt django-rest-swagger==2.2.0 # via -r requirements/base.txt +django-ses==3.5.0 + # via -r requirements/production.in django-simple-history==3.4.0 # via -r requirements/base.txt django-statici18n==2.4.0 @@ -279,7 +283,7 @@ mysqlclient==2.2.0 # via # -r requirements/base.txt # -r requirements/production.in -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/base.txt # edx-django-utils @@ -372,6 +376,7 @@ pytz==2023.3.post1 # via # -r requirements/base.txt # django + # django-ses # djangorestframework # drf-yasg # xblock @@ -464,7 +469,7 @@ uritemplate==4.1.1 # -r requirements/base.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/base.txt # botocore diff --git a/requirements/quality.txt b/requirements/quality.txt index b1611905..79b7b1ed 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -12,7 +12,7 @@ asgiref==3.7.2 # via # -r requirements/test.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # -r requirements/test.txt # pylint @@ -30,11 +30,11 @@ bleach==6.1.0 # via # -r requirements/test.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/test.txt # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/test.txt # boto3 @@ -48,7 +48,7 @@ cffi==1.16.0 # -r requirements/test.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/test.txt # requests @@ -86,7 +86,6 @@ cryptography==41.0.4 # via # -r requirements/test.txt # pyjwt - # secretstorage # social-auth-core ddt==1.6.0 # via -r requirements/test.txt @@ -244,7 +243,7 @@ exceptiongroup==1.1.3 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==19.10.0 +faker==19.11.0 # via # -r requirements/test.txt # factory-boy @@ -302,10 +301,6 @@ itypes==1.2.0 # coreapi jaraco-classes==3.3.0 # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage jinja2==3.1.2 # via # -r requirements/test.txt @@ -327,10 +322,6 @@ lazy==1.6 # -r requirements/test.txt # lti-consumer-xblock # xblock -lazy-object-proxy==1.9.0 - # via - # -r requirements/test.txt - # astroid lti-consumer-xblock==9.6.2 # via -r requirements/test.txt lxml==4.9.3 @@ -364,7 +355,7 @@ more-itertools==10.1.0 # via jaraco-classes mysqlclient==2.2.0 # via -r requirements/test.txt -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/test.txt # edx-django-utils @@ -455,7 +446,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # pyjwt # social-auth-core -pylint==2.17.7 +pylint==3.0.2 # via # -r requirements/test.txt # edx-lint @@ -466,7 +457,7 @@ pylint-celery==0.3 # via # -r requirements/test.txt # edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via # -r requirements/test.txt # edx-lint @@ -559,8 +550,6 @@ s3transfer==0.7.0 # via # -r requirements/test.txt # boto3 -secretstorage==3.3.3 - # via keyring semantic-version==2.10.0 # via # -r requirements/test.txt @@ -648,14 +637,14 @@ uritemplate==4.1.1 # -r requirements/test.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/test.txt # botocore # requests # responses # twine -virtualenv==20.24.5 +virtualenv==20.24.6 # via # -r requirements/test.txt # tox @@ -676,10 +665,6 @@ webob==1.8.7 # via # -r requirements/test.txt # xblock -wrapt==1.15.0 - # via - # -r requirements/test.txt - # astroid xblock[django]==1.8.1 # via # -r requirements/test.txt diff --git a/requirements/test.txt b/requirements/test.txt index 10b0817c..e7bd611a 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -12,7 +12,7 @@ asgiref==3.7.2 # via # -r requirements/base.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # pylint # pylint-celery @@ -29,11 +29,11 @@ bleach==6.1.0 # via # -r requirements/base.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/base.txt # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/base.txt # boto3 @@ -47,7 +47,7 @@ cffi==1.16.0 # -r requirements/base.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/base.txt # requests @@ -229,7 +229,7 @@ exceptiongroup==1.1.3 # via pytest factory-boy==3.3.0 # via -r requirements/test.in -faker==19.10.0 +faker==19.11.0 # via factory-boy fastavro==1.8.4 # via @@ -290,8 +290,6 @@ lazy==1.6 # -r requirements/base.txt # lti-consumer-xblock # xblock -lazy-object-proxy==1.9.0 - # via astroid lti-consumer-xblock==9.6.2 # via -r requirements/base.txt lxml==4.9.3 @@ -317,7 +315,7 @@ mock==5.1.0 # via -r requirements/test.in mysqlclient==2.2.0 # via -r requirements/base.txt -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/base.txt # edx-django-utils @@ -392,7 +390,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # pyjwt # social-auth-core -pylint==2.17.7 +pylint==3.0.2 # via # edx-lint # pylint-celery @@ -400,7 +398,7 @@ pylint==2.17.7 # pylint-plugin-utils pylint-celery==0.3 # via edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via edx-lint pylint-plugin-utils==0.8.2 # via @@ -556,13 +554,13 @@ uritemplate==4.1.1 # -r requirements/base.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/base.txt # botocore # requests # responses -virtualenv==20.24.5 +virtualenv==20.24.6 # via tox walrus==0.9.3 # via @@ -581,8 +579,6 @@ webob==1.8.7 # via # -r requirements/base.txt # xblock -wrapt==1.15.0 - # via astroid xblock[django]==1.8.1 # via # -r requirements/base.txt diff --git a/requirements/validation.txt b/requirements/validation.txt index 51a6a454..eb2b09d6 100644 --- a/requirements/validation.txt +++ b/requirements/validation.txt @@ -14,7 +14,7 @@ asgiref==3.7.2 # -r requirements/quality.txt # -r requirements/test.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -36,12 +36,12 @@ bleach==6.1.0 # -r requirements/quality.txt # -r requirements/test.txt # lti-consumer-xblock -boto3==1.28.64 +boto3==1.28.68 # via # -r requirements/quality.txt # -r requirements/test.txt # fs-s3fs -botocore==1.31.64 +botocore==1.31.68 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -58,7 +58,7 @@ cffi==1.16.0 # -r requirements/test.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -104,7 +104,6 @@ cryptography==41.0.4 # -r requirements/quality.txt # -r requirements/test.txt # pyjwt - # secretstorage # social-auth-core ddt==1.6.0 # via @@ -313,7 +312,7 @@ factory-boy==3.3.0 # via # -r requirements/quality.txt # -r requirements/test.txt -faker==19.10.0 +faker==19.11.0 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -388,11 +387,6 @@ jaraco-classes==3.3.0 # via # -r requirements/quality.txt # keyring -jeepney==0.8.0 - # via - # -r requirements/quality.txt - # keyring - # secretstorage jinja2==3.1.2 # via # -r requirements/quality.txt @@ -420,11 +414,6 @@ lazy==1.6 # -r requirements/test.txt # lti-consumer-xblock # xblock -lazy-object-proxy==1.9.0 - # via - # -r requirements/quality.txt - # -r requirements/test.txt - # astroid lti-consumer-xblock==9.6.2 # via # -r requirements/quality.txt @@ -474,7 +463,7 @@ mysqlclient==2.2.0 # via # -r requirements/quality.txt # -r requirements/test.txt -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -586,7 +575,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # pyjwt # social-auth-core -pylint==2.17.7 +pylint==3.0.2 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -599,7 +588,7 @@ pylint-celery==0.3 # -r requirements/quality.txt # -r requirements/test.txt # edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -722,10 +711,6 @@ s3transfer==0.7.0 # -r requirements/quality.txt # -r requirements/test.txt # boto3 -secretstorage==3.3.3 - # via - # -r requirements/quality.txt - # keyring semantic-version==2.10.0 # via # -r requirements/quality.txt @@ -829,7 +814,7 @@ uritemplate==4.1.1 # -r requirements/test.txt # coreapi # drf-yasg -urllib3==1.26.17 +urllib3==1.26.18 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -837,7 +822,7 @@ urllib3==1.26.17 # requests # responses # twine -virtualenv==20.24.5 +virtualenv==20.24.6 # via # -r requirements/quality.txt # -r requirements/test.txt @@ -863,11 +848,6 @@ webob==1.8.7 # -r requirements/quality.txt # -r requirements/test.txt # xblock -wrapt==1.15.0 - # via - # -r requirements/quality.txt - # -r requirements/test.txt - # astroid xblock[django]==1.8.1 # via # -r requirements/quality.txt