chore: resolve conflicts
This commit is contained in:
@@ -2,7 +2,7 @@ version: '3'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
container_name: edx.devstack.mysql57
|
||||
container_name: edx.devstack.mysql80
|
||||
ports:
|
||||
- '3306:3306'
|
||||
environment:
|
||||
|
||||
@@ -256,7 +256,7 @@ def update_special_exams_and_publish(course_key_str):
|
||||
"""
|
||||
from cms.djangoapps.contentstore.exams import register_exams
|
||||
from cms.djangoapps.contentstore.proctoring import register_special_exams as register_exams_legacy
|
||||
from openedx.core.djangoapps.credit.signals import on_course_publish
|
||||
from openedx.core.djangoapps.credit.signals.handlers import on_course_publish
|
||||
|
||||
course_key = CourseKey.from_string(course_key_str)
|
||||
LOGGER.info('Attempting to register exams for course %s', course_key_str)
|
||||
|
||||
@@ -208,6 +208,10 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
|
||||
@mock.patch(
|
||||
'cms.djangoapps.course_creators.admin.render_to_string',
|
||||
mock.Mock(side_effect=mock_render_to_string, autospec=True)
|
||||
)
|
||||
def test_course_creation_when_user_in_org_with_creator_role(self):
|
||||
"""
|
||||
Tests course creation with user having the organization content creation role.
|
||||
@@ -218,6 +222,9 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
'description': 'Testing Organization Description',
|
||||
})
|
||||
update_org_role(self.global_admin, OrgContentCreatorRole, self.user, [self.source_course_key.org])
|
||||
self.course_creator_entry.all_organizations = True
|
||||
self.course_creator_entry.state = CourseCreator.GRANTED
|
||||
self.creator_admin.save_model(self.request, self.course_creator_entry, None, True)
|
||||
self.assertIn(self.source_course_key.org, get_allowed_organizations(self.user))
|
||||
response = self.client.ajax_post(self.course_create_rerun_url, {
|
||||
'org': self.source_course_key.org,
|
||||
|
||||
@@ -195,7 +195,7 @@ class RegisterExamsTaskTestCase(CourseTestCase): # pylint: disable=missing-clas
|
||||
@mock.patch('cms.djangoapps.contentstore.proctoring.register_special_exams')
|
||||
def test_register_exams_failure(self, _mock_register_exams_proctoring, _mock_register_exams_service):
|
||||
""" credit requirements update signal fires even if exam registration fails """
|
||||
with mock.patch('openedx.core.djangoapps.credit.signals.on_course_publish') as course_publish:
|
||||
with mock.patch('openedx.core.djangoapps.credit.signals.handlers.on_course_publish') as course_publish:
|
||||
_mock_register_exams_proctoring.side_effect = Exception('boom!')
|
||||
update_special_exams_and_publish(str(self.course.id))
|
||||
course_publish.assert_called()
|
||||
|
||||
@@ -1786,7 +1786,7 @@ def get_organizations(user):
|
||||
Returns the list of organizations for which the user is allowed to create courses.
|
||||
"""
|
||||
course_creator = CourseCreator.objects.filter(user=user).first()
|
||||
if not course_creator:
|
||||
if not course_creator or course_creator.state != CourseCreator.GRANTED:
|
||||
return []
|
||||
elif course_creator.all_organizations:
|
||||
organizations = Organization.objects.all().values_list('short_name', flat=True)
|
||||
|
||||
@@ -83,8 +83,9 @@ def user_can_create_library(user, org=None):
|
||||
is_course_creator = get_course_creator_status(user) == 'granted'
|
||||
has_org_staff_role = OrgStaffRole().get_orgs_for_user(user).exists()
|
||||
has_course_staff_role = UserBasedRole(user=user, role=CourseStaffRole.ROLE).courses_with_role().exists()
|
||||
has_course_admin_role = UserBasedRole(user=user, role=CourseInstructorRole.ROLE).courses_with_role().exists()
|
||||
|
||||
return is_course_creator or has_org_staff_role or has_course_staff_role
|
||||
return is_course_creator or has_org_staff_role or has_course_staff_role or has_course_admin_role
|
||||
else:
|
||||
# EDUCATOR-1924: DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION, if present.
|
||||
disable_library_creation = settings.FEATURES.get('DISABLE_LIBRARY_CREATION', None)
|
||||
|
||||
@@ -9,7 +9,7 @@ from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url
|
||||
from openedx.core.djangoapps.credit.api import get_credit_requirements
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse
|
||||
from openedx.core.djangoapps.credit.signals import on_course_publish
|
||||
from openedx.core.djangoapps.credit.signals.handlers import on_course_publish
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from organizations.exceptions import InvalidOrganizationException
|
||||
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_library_url
|
||||
from cms.djangoapps.course_creators.views import add_user_with_status_granted as grant_course_creator_status
|
||||
from common.djangoapps.student.roles import LibraryUserRole, CourseStaffRole
|
||||
from common.djangoapps.student.roles import LibraryUserRole, CourseStaffRole, CourseInstructorRole
|
||||
from xmodule.modulestore.tests.factories import LibraryFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from cms.djangoapps.course_creators.models import CourseCreator
|
||||
|
||||
@@ -101,6 +101,14 @@ class UnitTestLibraries(CourseTestCase):
|
||||
auth.add_users(self.user, CourseStaffRole(self.course.id), nostaff_user)
|
||||
self.assertEqual(user_can_create_library(nostaff_user), True)
|
||||
|
||||
# When creator groups are enabled, course instructor members can create libraries
|
||||
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
|
||||
def test_library_creator_status_with_course_instructor_role_for_enabled_creator_group_setting(self):
|
||||
_, nostaff_user = self.create_non_staff_authed_user_client()
|
||||
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
|
||||
auth.add_users(self.user, CourseInstructorRole(self.course.id), nostaff_user)
|
||||
self.assertEqual(user_can_create_library(nostaff_user), True)
|
||||
|
||||
@ddt.data(
|
||||
(False, False, True),
|
||||
(False, True, False),
|
||||
@@ -480,9 +488,14 @@ class UnitTestLibraries(CourseTestCase):
|
||||
# Assert that the method returned the expected value
|
||||
self.assertEqual(organizations, [])
|
||||
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
|
||||
organizations = get_allowed_organizations_for_libraries(self.user)
|
||||
# Assert that the method returned the expected value
|
||||
self.assertEqual(organizations, ['org1', 'org2'])
|
||||
# Assert that correct org values are returned based on course creator state
|
||||
for course_creator_state in CourseCreator.STATES:
|
||||
course_creator.state = course_creator_state
|
||||
organizations = get_allowed_organizations_for_libraries(self.user)
|
||||
if course_creator_state != CourseCreator.GRANTED:
|
||||
self.assertEqual(organizations, [])
|
||||
else:
|
||||
self.assertEqual(organizations, ['org1', 'org2'])
|
||||
with mock.patch.dict(
|
||||
'django.conf.settings.FEATURES',
|
||||
{"ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES": True}
|
||||
|
||||
@@ -2787,3 +2787,7 @@ SPECTACULAR_SETTINGS = {
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
'PREPROCESSING_HOOKS': ['cms.lib.spectacular.cms_api_filter'], # restrict spectacular to CMS API endpoints
|
||||
}
|
||||
|
||||
#### Event bus publishing ####
|
||||
## Will be more filled out as part of https://github.com/edx/edx-arch-experiments/issues/381
|
||||
EVENT_BUS_PRODUCER_CONFIG = {}
|
||||
|
||||
@@ -208,7 +208,7 @@ DATABASES:
|
||||
ATOMIC_REQUESTS: true
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
@@ -218,7 +218,7 @@ DATABASES:
|
||||
read_replica:
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
@@ -228,7 +228,7 @@ DATABASES:
|
||||
student_module_history:
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp_csmh
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""Tests for util.db module."""
|
||||
|
||||
from importlib.metadata import version
|
||||
from io import StringIO
|
||||
|
||||
import ddt
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from django.db.transaction import TransactionManagementError, atomic
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
@@ -12,8 +10,6 @@ from django.test.utils import override_settings
|
||||
|
||||
from common.djangoapps.util.db import enable_named_outer_atomic, generate_int_id, outer_atomic
|
||||
|
||||
GET_DJANGO_VERSION = int(version('django').split('.')[0])
|
||||
|
||||
|
||||
def do_nothing():
|
||||
"""Just return."""
|
||||
@@ -119,7 +115,6 @@ class GenerateIntIdTestCase(TestCase):
|
||||
assert int_id in list(set(range(minimum, (maximum + 1))) - used_ids)
|
||||
|
||||
|
||||
@pytest.mark.skipif(GET_DJANGO_VERSION > 3, reason="django4.2 brings new migrations, so only run for dj32 for now.")
|
||||
class MigrationTests(TestCase):
|
||||
"""
|
||||
Tests for migrations.
|
||||
|
||||
184
lms/envs/bok_choy_docker.yml
Normal file
184
lms/envs/bok_choy_docker.yml
Normal file
@@ -0,0 +1,184 @@
|
||||
# ingested edx-platform/lms/envs/bok_choy_docker.auth.json
|
||||
# ingested edx-platform/lms/envs/bok_choy_docker.env.json
|
||||
ACTIVATION_EMAIL_SUPPORT_LINK: https://support.example.com/activation-email-help.html
|
||||
ANALYTICS_DASHBOARD_URL: ''
|
||||
AWS_ACCESS_KEY_ID: ''
|
||||
AWS_SECRET_ACCESS_KEY: ''
|
||||
BUGS_EMAIL: bugs@example.com
|
||||
BULK_EMAIL_DEFAULT_FROM_EMAIL: no-reply@example.com
|
||||
CACHES:
|
||||
celery:
|
||||
BACKEND: django.core.cache.backends.memcached.PyMemcacheCache
|
||||
KEY_FUNCTION: common.djangoapps.util.memcache.safe_key
|
||||
KEY_PREFIX: integration_celery
|
||||
LOCATION: ['edx.devstack.memcached:11211']
|
||||
OPTIONS:
|
||||
no_delay: true
|
||||
ignore_exc: true
|
||||
use_pooling: true
|
||||
default:
|
||||
BACKEND: django.core.cache.backends.memcached.PyMemcacheCache
|
||||
KEY_FUNCTION: common.djangoapps.util.memcache.safe_key
|
||||
KEY_PREFIX: sandbox_default
|
||||
LOCATION: ['edx.devstack.memcached:11211']
|
||||
OPTIONS:
|
||||
no_delay: true
|
||||
ignore_exc: true
|
||||
use_pooling: true
|
||||
general:
|
||||
BACKEND: django.core.cache.backends.memcached.PyMemcacheCache
|
||||
KEY_FUNCTION: common.djangoapps.util.memcache.safe_key
|
||||
KEY_PREFIX: sandbox_general
|
||||
LOCATION: ['edx.devstack.memcached:11211']
|
||||
OPTIONS:
|
||||
no_delay: true
|
||||
ignore_exc: true
|
||||
use_pooling: true
|
||||
mongo_metadata_inheritance:
|
||||
BACKEND: django.core.cache.backends.memcached.PyMemcacheCache
|
||||
KEY_FUNCTION: common.djangoapps.util.memcache.safe_key
|
||||
KEY_PREFIX: integration_mongo_metadata_inheritance
|
||||
LOCATION: ['edx.devstack.memcached:11211']
|
||||
OPTIONS:
|
||||
no_delay: true
|
||||
ignore_exc: true
|
||||
use_pooling: true
|
||||
staticfiles:
|
||||
BACKEND: django.core.cache.backends.memcached.PyMemcacheCache
|
||||
KEY_FUNCTION: common.djangoapps.util.memcache.safe_key
|
||||
KEY_PREFIX: integration_static_files
|
||||
LOCATION: ['edx.devstack.memcached:11211']
|
||||
OPTIONS:
|
||||
no_delay: true
|
||||
ignore_exc: true
|
||||
use_pooling: true
|
||||
CELERY_BROKER_HOSTNAME: localhost
|
||||
CELERY_BROKER_PASSWORD: celery
|
||||
CELERY_BROKER_TRANSPORT: amqp
|
||||
CELERY_BROKER_USER: celery
|
||||
CERT_QUEUE: certificates
|
||||
CMS_BASE: '** OVERRIDDEN **'
|
||||
CODE_JAIL:
|
||||
limits: {REALTIME: 3, VMEM: 0}
|
||||
COMMENTS_SERVICE_KEY: password
|
||||
COMMENTS_SERVICE_URL: http://edx.devstack.lms:4567
|
||||
CONTACT_EMAIL: info@example.com
|
||||
CONTENTSTORE:
|
||||
DOC_STORE_CONFIG:
|
||||
collection: modulestore
|
||||
db: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
ENGINE: xmodule.contentstore.mongo.MongoContentStore
|
||||
OPTIONS:
|
||||
db: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
DATABASES:
|
||||
default: {ENGINE: django.db.backends.mysql, HOST: edx.devstack.mysql80, NAME: edxtest,
|
||||
PASSWORD: '', PORT: '3306', USER: root}
|
||||
student_module_history: {ENGINE: django.db.backends.mysql, HOST: edx.devstack.mysql80,
|
||||
NAME: student_module_history_test, PASSWORD: '', PORT: '3306', USER: root}
|
||||
DEFAULT_FEEDBACK_EMAIL: feedback@example.com
|
||||
DEFAULT_FROM_EMAIL: registration@example.com
|
||||
DJFS: {aws_access_key_id: test, aws_secret_access_key: test, bucket: test, prefix: test,
|
||||
type: s3fs}
|
||||
DOC_STORE_CONFIG:
|
||||
collection: modulestore
|
||||
db: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
EMAIL_BACKEND: django.core.mail.backends.dummy.EmailBackend
|
||||
EVENT_TRACKING_BACKENDS:
|
||||
mongo:
|
||||
ENGINE: eventtracking.backends.mongodb.MongoBackend
|
||||
OPTIONS:
|
||||
collection: events
|
||||
database: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
FEATURES: {ALLOW_AUTOMATED_SIGNUPS: true, AUTOMATIC_AUTH_FOR_TESTING: true,
|
||||
AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING: true, CERTIFICATES_HTML_VIEW: true,
|
||||
CERTIFICATES_INSTRUCTOR_GENERATION: true, CUSTOM_COURSES_EDX: true,
|
||||
ENABLE_COURSE_DISCOVERY: true, ENABLE_DISCUSSION_SERVICE: true, ENABLE_GRADE_DOWNLOADS: true,
|
||||
ENABLE_SPECIAL_EXAMS: true, ENABLE_THIRD_PARTY_AUTH: true,
|
||||
ENABLE_VERIFIED_CERTIFICATES: true, EXPOSE_CACHE_PROGRAMS_ENDPOINT: true, MODE_CREATION_FOR_TESTING: true,
|
||||
PREVIEW_LMS_BASE: 'preview.localhost:8003', RESTRICT_AUTOMATIC_AUTH: false, SHOW_HEADER_LANGUAGE_SELECTOR: true}
|
||||
GITHUB_REPO_ROOT: '** OVERRIDDEN **'
|
||||
JWT_AUTH: {JWT_PRIVATE_SIGNING_JWK: '{"e": "AQAB", "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ",
|
||||
"n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ",
|
||||
"q": "3T3DEtBUka7hLGdIsDlC96Uadx_q_E4Vb1cxx_4Ss_wGp1Loz3N3ZngGyInsKlmbBgLo1Ykd6T9TRvRNEWEtFSOcm2INIBoVoXk7W5RuPa8Cgq2tjQj9ziGQ08JMejrPlj3Q1wmALJr5VTfvSYBu0WkljhKNCy1KB6fCby0C9WE",
|
||||
"p": "vUqzWPZnDG4IXyo-k5F0bHV0BNL_pVhQoLW7eyFHnw74IOEfSbdsMspNcPSFIrtgPsn7981qv3lN_staZ6JflKfHayjB_lvltHyZxfl0dvruShZOx1N6ykEo7YrAskC_qxUyrIvqmJ64zPW3jkuOYrFs7Ykj3zFx3Zq1H5568G0",
|
||||
"kid": "BTZ9HA6K", "kty": "RSA"}', JWT_PUBLIC_SIGNING_JWK_SET: '{"keys": [{"kid":
|
||||
"BTZ9HA6K", "e": "AQAB", "kty": "RSA", "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ"}]}',
|
||||
JWT_SECRET_KEY: super-secret-key}
|
||||
LMS_BASE: http://edx.devstack.lms:18003
|
||||
LMS_ROOT_URL: http://edx.devstack.lms:18003
|
||||
LOCAL_LOGLEVEL: INFO
|
||||
LOGGING_ENV: sandbox
|
||||
LOG_DIR: '** OVERRIDDEN **'
|
||||
MEDIA_URL: /media/
|
||||
MKTG_URL_LINK_MAP: {ABOUT: about, BLOG: blog, CAREERS: careers, CONTACT: contact,
|
||||
COURSES: courses, DONATE: donate, HELP_CENTER: help-center, HONOR: honor, NEWS: news,
|
||||
PRESS: press, PRIVACY: privacy, ROOT: root, SITEMAP.XML: sitemap_xml, TOS: tos,
|
||||
WHAT_IS_VERIFIED_CERT: verified-certificate}
|
||||
MODULESTORE:
|
||||
default:
|
||||
ENGINE: xmodule.modulestore.mixed.MixedModuleStore
|
||||
OPTIONS:
|
||||
mappings: {}
|
||||
stores:
|
||||
- DOC_STORE_CONFIG:
|
||||
collection: modulestore
|
||||
db: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
ENGINE: xmodule.modulestore.mongo.DraftMongoModuleStore
|
||||
NAME: draft
|
||||
OPTIONS:
|
||||
collection: modulestore
|
||||
db: test
|
||||
default_class: xmodule.hidden_block.HiddenBlock
|
||||
fs_root: '** OVERRIDDEN **'
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
render_template: common.djangoapps.edxmako.shortcuts.render_to_string
|
||||
- ENGINE: xmodule.modulestore.xml.XMLModuleStore
|
||||
NAME: xml
|
||||
OPTIONS: {data_dir: '** OVERRIDDEN **', default_class: xmodule.hidden_block.HiddenBlock}
|
||||
PASSWORD_RESET_SUPPORT_LINK: https://support.example.com/password-reset-help.html
|
||||
REGISTRATION_EXTENSION_FORM: openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm
|
||||
REGISTRATION_EXTRA_FIELDS: {city: hidden, country: required, gender: optional, goals: optional,
|
||||
honor_code: required, level_of_education: optional, mailing_address: optional, terms_of_service: hidden,
|
||||
year_of_birth: optional}
|
||||
SECRET_KEY: 'bokchoy_docker_secret_key'
|
||||
SERVER_EMAIL: devops@example.com
|
||||
SESSION_COOKIE_DOMAIN: null
|
||||
SITE_NAME: localhost:8003
|
||||
SOCIAL_SHARING_SETTINGS: {CERTIFICATE_FACEBOOK: true, CERTIFICATE_FACEBOOK_TEXT: 'Testing
|
||||
facebook feature:', CUSTOM_COURSE_URLS: true, DASHBOARD_FACEBOOK: true, DASHBOARD_TWITTER: true,
|
||||
DASHBOARD_TWITTER_TEXT: 'Testing feature:'}
|
||||
STATIC_URL_BASE: /static/
|
||||
SUPPORT_SITE_LINK: https://support.example.com
|
||||
SYSLOG_SERVER: ''
|
||||
TECH_SUPPORT_EMAIL: technical@example.com
|
||||
THIRD_PARTY_AUTH_BACKENDS: [social_core.backends.google.GoogleOAuth2, social_core.backends.linkedin.LinkedinOAuth2,
|
||||
social_core.backends.facebook.FacebookOAuth2, common.djangoapps.third_party_auth.dummy.DummyBackend,
|
||||
common.djangoapps.third_party_auth.saml.SAMLAuthBackend]
|
||||
TIME_ZONE: America/New_York
|
||||
TRACKING_BACKENDS:
|
||||
mongo:
|
||||
ENGINE: common.djangoapps.track.backends.mongodb.MongoBackend
|
||||
OPTIONS:
|
||||
collection: events
|
||||
database: test
|
||||
host: [edx.devstack.mongo]
|
||||
port: 27017
|
||||
WIKI_ENABLED: true
|
||||
XQUEUE_INTERFACE:
|
||||
basic_auth: [edx, edx]
|
||||
django_auth: {password: password, username: lms}
|
||||
url: '** OVERRIDDEN **'
|
||||
ZENDESK_API_KEY: ''
|
||||
ZENDESK_USER: ''
|
||||
|
||||
@@ -5384,3 +5384,7 @@ EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000
|
||||
#### django-simple-history##
|
||||
# disable indexing on date field its coming from django-simple-history.
|
||||
SIMPLE_HISTORY_DATE_INDEX = False
|
||||
|
||||
#### Event bus publishing ####
|
||||
## Will be more filled out as part of https://github.com/edx/edx-arch-experiments/issues/381
|
||||
EVENT_BUS_PRODUCER_CONFIG = {}
|
||||
|
||||
@@ -228,7 +228,7 @@ DATABASES:
|
||||
ATOMIC_REQUESTS: true
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
@@ -238,7 +238,7 @@ DATABASES:
|
||||
read_replica:
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
@@ -248,7 +248,7 @@ DATABASES:
|
||||
student_module_history:
|
||||
CONN_MAX_AGE: 0
|
||||
ENGINE: django.db.backends.mysql
|
||||
HOST: edx.devstack.mysql57
|
||||
HOST: edx.devstack.mysql80
|
||||
NAME: edxapp_csmh
|
||||
OPTIONS:
|
||||
isolation_level: read committed
|
||||
|
||||
@@ -544,6 +544,28 @@ describe('Program Details View', () => {
|
||||
.toContainText(body);
|
||||
};
|
||||
|
||||
const testSubscriptionSunsetting = (state, heading, body) => {
|
||||
const subscriptionData = {
|
||||
...options.subscriptionData[0],
|
||||
subscription_state: state,
|
||||
};
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
view = initView({
|
||||
// eslint-disable-next-line no-undef
|
||||
programData: $.extend({}, options.programData, {
|
||||
subscription_eligible: false,
|
||||
}),
|
||||
isUserB2CSubscriptionsEnabled: true,
|
||||
subscriptionData: [subscriptionData],
|
||||
});
|
||||
view.render();
|
||||
expect(view.$('.upgrade-subscription')[0]).not.toBeInDOM();
|
||||
expect(view.$('.upgrade-subscription .upgrade-button')).not
|
||||
.toContainText(heading);
|
||||
expect(view.$('.upgrade-subscription .subscription-info-brief')).not
|
||||
.toContainText(body);
|
||||
};
|
||||
|
||||
const initView = (updates) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const viewOptions = $.extend({}, options, updates);
|
||||
@@ -709,8 +731,8 @@ describe('Program Details View', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the get subscription link if program is subscription eligible', () => {
|
||||
testSubscriptionState(
|
||||
it('should not render the get subscription link if program is not active', () => {
|
||||
testSubscriptionSunsetting(
|
||||
'pre',
|
||||
'Start 7-day free trial',
|
||||
'$100/month USD subscription after trial ends. Cancel anytime.',
|
||||
@@ -734,8 +756,8 @@ describe('Program Details View', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render appropriate subscription text when subscription is inactive', () => {
|
||||
testSubscriptionState(
|
||||
it('should not render appropriate subscription text when subscription is inactive', () => {
|
||||
testSubscriptionSunsetting(
|
||||
'inactive',
|
||||
'Restart my subscription',
|
||||
'$100/month USD subscription. Cancel anytime.',
|
||||
|
||||
@@ -32,35 +32,10 @@ describe('Sidebar View', () => {
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not render the subscription upsell section if B2CSubscriptions are disabled', () => {
|
||||
view.remove();
|
||||
view = new SidebarView({
|
||||
el: '.sidebar',
|
||||
context: {
|
||||
...context,
|
||||
isUserB2CSubscriptionsEnabled: false,
|
||||
},
|
||||
});
|
||||
view.render();
|
||||
it('should not render the subscription upsell section', () => {
|
||||
expect(view.$('.js-subscription-upsell')[0]).not.toBeInDOM();
|
||||
});
|
||||
|
||||
it('should render the subscription upsell section', () => {
|
||||
expect(view.$('.js-subscription-upsell')[0]).toBeInDOM();
|
||||
expect(view.$('.js-subscription-upsell .badge').html().trim())
|
||||
.toEqual('New');
|
||||
expect(view.$('.js-subscription-upsell h4').html().trim())
|
||||
.toMatch(/^Monthly program subscriptions . more flexible, more affordable$/);
|
||||
expect(view.$('.js-subscription-upsell .advertise-message').html().trim())
|
||||
.toEqual(
|
||||
'Now available for many popular programs, affordable monthly subscription pricing can help you manage your budget more effectively. Subscriptions start at $39/month USD per program, after a 7-day full access free trial. Cancel at any time.',
|
||||
);
|
||||
expect(view.$('.js-subscription-upsell a span:last').html().trim())
|
||||
.toEqual('Explore subscription options');
|
||||
expect(view.$('.js-subscription-upsell a').attr('href'))
|
||||
.toEqual('https://www.example.org/program-subscriptions');
|
||||
});
|
||||
|
||||
it('should load the exploration panel given a marketing URL', () => {
|
||||
expect(view.$('.program-advertise .advertise-message').html().trim())
|
||||
.toEqual(
|
||||
|
||||
@@ -215,9 +215,13 @@ class ProgramDetailsView extends Backbone.View {
|
||||
&& this.remainingCourseCollection.length === 0
|
||||
);
|
||||
|
||||
const isSubscriptionActiveSunsetting = (
|
||||
this.subscriptionModel.get('subscriptionState') === 'active'
|
||||
)
|
||||
|
||||
return (
|
||||
this.options.isUserB2CSubscriptionsEnabled
|
||||
&& this.options.programData.subscription_eligible
|
||||
&& isSubscriptionActiveSunsetting
|
||||
&& !programPurchasedWithoutSubscription
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import Backbone from 'backbone';
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
import NewProgramsView from './explore_new_programs_view';
|
||||
import SubscriptionUpsellView from './subscription_upsell_view';
|
||||
|
||||
import sidebarTpl from '../../../templates/learner_dashboard/sidebar.underscore';
|
||||
|
||||
@@ -30,12 +29,6 @@ class SidebarView extends Backbone.View {
|
||||
}
|
||||
|
||||
postRender() {
|
||||
if (this.context.isUserB2CSubscriptionsEnabled) {
|
||||
this.subscriptionUpsellView = new SubscriptionUpsellView({
|
||||
subscriptionUpsellData: this.context.subscriptionUpsellData,
|
||||
});
|
||||
}
|
||||
|
||||
this.newProgramsView = new NewProgramsView({
|
||||
context: this.context,
|
||||
});
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
<% if (isUserB2CSubscriptionsEnabled) { %>
|
||||
<aside class="aside js-subscription-upsell"></aside>
|
||||
<% } %>
|
||||
<div class="aside program-advertise"></div>
|
||||
|
||||
@@ -15,7 +15,7 @@ class CreditConfig(AppConfig):
|
||||
name = 'openedx.core.djangoapps.credit'
|
||||
|
||||
def ready(self):
|
||||
from . import signals # lint-amnesty, pylint: disable=unused-import
|
||||
from .signals import handlers # lint-amnesty, pylint: disable=unused-import
|
||||
if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
|
||||
from .services import CreditService
|
||||
set_runtime_service('credit', CreditService())
|
||||
|
||||
0
openedx/core/djangoapps/credit/signals/__init__.py
Normal file
0
openedx/core/djangoapps/credit/signals/__init__.py
Normal file
@@ -5,15 +5,78 @@ This file contains receivers of course publication signals.
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx_events.learning.signals import (
|
||||
EXAM_ATTEMPT_ERRORED,
|
||||
EXAM_ATTEMPT_REJECTED,
|
||||
EXAM_ATTEMPT_RESET,
|
||||
EXAM_ATTEMPT_SUBMITTED,
|
||||
EXAM_ATTEMPT_VERIFIED
|
||||
)
|
||||
|
||||
from openedx.core.djangoapps.credit.api.eligibility import (
|
||||
is_credit_course,
|
||||
remove_credit_requirement_status,
|
||||
set_credit_requirement_status
|
||||
)
|
||||
from openedx.core.djangoapps.signals.signals import COURSE_GRADE_CHANGED
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_exam_event(signal, event_data, credit_status=None):
|
||||
"""
|
||||
update credit requirements based on exam event
|
||||
"""
|
||||
user_data = event_data.student_user
|
||||
course_key = event_data.course_key
|
||||
usage_key = event_data.usage_key
|
||||
request_namespace = 'exam'
|
||||
|
||||
# quick exit, if course is not credit enabled
|
||||
if not is_credit_course(course_key):
|
||||
return
|
||||
|
||||
# log any activity to the credit requirements table
|
||||
log.info(
|
||||
f'Recieved {signal} signal, changing credit requirement for '
|
||||
f'user_id={user_data.id}, course_key_or_id={course_key} '
|
||||
f'content_id={usage_key}'
|
||||
)
|
||||
|
||||
try:
|
||||
user = User.objects.get(id=user_data.id)
|
||||
except ObjectDoesNotExist:
|
||||
log.error(
|
||||
'Error occurred while handling exam event for '
|
||||
f'{user_data.id} and content_id {usage_key}. '
|
||||
'User does not exist!'
|
||||
)
|
||||
return
|
||||
|
||||
if signal == EXAM_ATTEMPT_RESET:
|
||||
remove_credit_requirement_status(
|
||||
user.username,
|
||||
course_key,
|
||||
request_namespace,
|
||||
str(usage_key),
|
||||
)
|
||||
else:
|
||||
set_credit_requirement_status(
|
||||
user.username,
|
||||
course_key,
|
||||
request_namespace,
|
||||
str(usage_key),
|
||||
credit_status
|
||||
)
|
||||
|
||||
|
||||
def on_course_publish(course_key):
|
||||
"""
|
||||
Will receive a delegated 'course_published' signal from cms/djangoapps/contentstore/signals.py
|
||||
@@ -94,3 +157,48 @@ def listen_for_grade_calculation(sender, user, course_grade, course_key, deadlin
|
||||
api.set_credit_requirement_status(
|
||||
user, course_id, 'grade', 'grade', status=status, reason=reason
|
||||
)
|
||||
|
||||
|
||||
@receiver(EXAM_ATTEMPT_RESET)
|
||||
def listen_for_exam_reset(sender, signal, **kwargs):
|
||||
"""
|
||||
exam reset event from the event bus
|
||||
"""
|
||||
event_data = kwargs.get('exam_attempt')
|
||||
handle_exam_event(signal, event_data)
|
||||
|
||||
|
||||
@receiver(EXAM_ATTEMPT_SUBMITTED)
|
||||
def listen_for_exam_submitted(sender, signal, **kwargs):
|
||||
"""
|
||||
exam submission event from the event bus
|
||||
"""
|
||||
event_data = kwargs.get('exam_attempt')
|
||||
handle_exam_event(signal, event_data, credit_status='submitted')
|
||||
|
||||
|
||||
@receiver(EXAM_ATTEMPT_VERIFIED)
|
||||
def listen_for_exam_verified(sender, signal, **kwargs):
|
||||
"""
|
||||
exam verification event from the event bus
|
||||
"""
|
||||
event_data = kwargs.get('exam_attempt')
|
||||
handle_exam_event(signal, event_data, credit_status='satisfied')
|
||||
|
||||
|
||||
@receiver(EXAM_ATTEMPT_REJECTED)
|
||||
def listen_for_exam_rejected(sender, signal, **kwargs):
|
||||
"""
|
||||
exam rejection event from the event bus
|
||||
"""
|
||||
event_data = kwargs.get('exam_attempt')
|
||||
handle_exam_event(signal, event_data, credit_status='failed')
|
||||
|
||||
|
||||
@receiver(EXAM_ATTEMPT_ERRORED)
|
||||
def listen_for_exam_errored(sender, signal, **kwargs):
|
||||
"""
|
||||
exam error event from the event bus
|
||||
"""
|
||||
event_data = kwargs.get('exam_attempt')
|
||||
handle_exam_event(signal, event_data, credit_status='failed')
|
||||
@@ -1,23 +1,41 @@
|
||||
"""
|
||||
Tests for minimum grade requirement status
|
||||
Tests for minimum grade and credit requirement status
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
import ddt
|
||||
import pytz
|
||||
from django.test.client import RequestFactory
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from openedx_events.data import EventsMetadata
|
||||
from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData
|
||||
from openedx_events.learning.signals import (
|
||||
EXAM_ATTEMPT_ERRORED,
|
||||
EXAM_ATTEMPT_REJECTED,
|
||||
EXAM_ATTEMPT_RESET,
|
||||
EXAM_ATTEMPT_SUBMITTED,
|
||||
EXAM_ATTEMPT_VERIFIED
|
||||
)
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from openedx.core.djangoapps.credit.api import get_credit_requirement_status, set_credit_requirements
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
|
||||
from openedx.core.djangoapps.credit.signals import listen_for_grade_calculation
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from openedx.core.djangoapps.credit.api import get_credit_requirement_status, set_credit_requirements
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
|
||||
from openedx.core.djangoapps.credit.signals.handlers import (
|
||||
listen_for_exam_errored,
|
||||
listen_for_exam_rejected,
|
||||
listen_for_exam_reset,
|
||||
listen_for_exam_submitted,
|
||||
listen_for_exam_verified,
|
||||
listen_for_grade_calculation
|
||||
)
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.modulestore.tests.django_utils import \
|
||||
ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
@@ -76,7 +94,7 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
|
||||
|
||||
def assert_requirement_status(self, grade, due_date, expected_status):
|
||||
""" Verify the user's credit requirement status is as expected after simulating a grading calculation. """
|
||||
course_grade = MagicMock()
|
||||
course_grade = mock.MagicMock()
|
||||
course_grade.percent = grade
|
||||
listen_for_grade_calculation(None, self.user, course_grade, self.course.id, due_date)
|
||||
req_status = get_credit_requirement_status(self.course.id, self.request.user.username, 'grade', 'grade')
|
||||
@@ -129,3 +147,146 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
|
||||
"""Test with valid grades submitted before deadline with non-verified enrollment."""
|
||||
self.enrollment.update_enrollment(mode, True)
|
||||
self.assert_requirement_status(0.8, self.VALID_DUE_DATE, None)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.ddt
|
||||
class TestExamEvents(ModuleStoreTestCase):
|
||||
"""
|
||||
Test exam events
|
||||
"""
|
||||
HANDLERS = {
|
||||
EXAM_ATTEMPT_ERRORED: listen_for_exam_errored,
|
||||
EXAM_ATTEMPT_REJECTED: listen_for_exam_rejected,
|
||||
EXAM_ATTEMPT_RESET: listen_for_exam_reset,
|
||||
EXAM_ATTEMPT_SUBMITTED: listen_for_exam_submitted,
|
||||
EXAM_ATTEMPT_VERIFIED: listen_for_exam_verified,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.course = CourseFactory.create(
|
||||
org='TestX', number='999', display_name='Test Course'
|
||||
)
|
||||
self.subsection_key = UsageKey.from_string('block-v1:edX+TestX+Test_Course+type@sequential+block@subsection')
|
||||
|
||||
self.user = UserFactory()
|
||||
|
||||
# Enable the course for credit
|
||||
CreditCourse.objects.create(
|
||||
course_key=self.course.id,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_exam_event_data(user, course, usage_key):
|
||||
""" create ExamAttemptData object for user """
|
||||
return ExamAttemptData(
|
||||
student_user=UserData(
|
||||
id=user.id,
|
||||
is_active=True,
|
||||
pii=UserPersonalData(
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
),
|
||||
),
|
||||
course_key=course.id,
|
||||
usage_key=usage_key,
|
||||
exam_type='timed',
|
||||
requesting_user=None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_exam_event_metadata(event_signal):
|
||||
""" create metadata object for event """
|
||||
return EventsMetadata(
|
||||
event_type=event_signal.event_type,
|
||||
id=uuid4(),
|
||||
minorversion=0,
|
||||
source='openedx/lms/web',
|
||||
sourcehost='lms.test',
|
||||
time=datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.credit.signals.handlers.remove_credit_requirement_status', autospec=True)
|
||||
def test_exam_reset(self, mock_remove_credit_status):
|
||||
"""
|
||||
Test exam reset event
|
||||
"""
|
||||
event_data = self._get_exam_event_data(self.user, self.course, self.subsection_key)
|
||||
event_metadata = self._get_exam_event_metadata(EXAM_ATTEMPT_RESET)
|
||||
|
||||
listen_for_exam_reset(None, EXAM_ATTEMPT_RESET, event_metadata=event_metadata, exam_attempt=event_data)
|
||||
|
||||
mock_remove_credit_status.assert_called_once_with(
|
||||
self.user.username, self.course.id, 'exam', str(self.subsection_key)
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
(EXAM_ATTEMPT_ERRORED, 'failed'),
|
||||
(EXAM_ATTEMPT_REJECTED, 'failed'),
|
||||
(EXAM_ATTEMPT_VERIFIED, 'satisfied'),
|
||||
(EXAM_ATTEMPT_SUBMITTED, 'submitted'),
|
||||
)
|
||||
@ddt.unpack
|
||||
@mock.patch('openedx.core.djangoapps.credit.signals.handlers.set_credit_requirement_status', autospec=True)
|
||||
def test_exam_update_event(self, event_signal, expected_status, mock_set_credit_status):
|
||||
"""
|
||||
Test exam events that update credit status
|
||||
"""
|
||||
event_data = self._get_exam_event_data(self.user, self.course, self.subsection_key)
|
||||
event_metadata = self._get_exam_event_metadata(event_signal)
|
||||
|
||||
handler = self.HANDLERS.get(event_signal)
|
||||
handler(None, event_signal, event_metadata=event_metadata, exam_attempt=event_data)
|
||||
|
||||
mock_set_credit_status.assert_called_once_with(
|
||||
self.user.username, self.course.id, 'exam', str(self.subsection_key), status=expected_status
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
EXAM_ATTEMPT_RESET,
|
||||
EXAM_ATTEMPT_REJECTED,
|
||||
EXAM_ATTEMPT_ERRORED,
|
||||
EXAM_ATTEMPT_VERIFIED,
|
||||
EXAM_ATTEMPT_SUBMITTED,
|
||||
)
|
||||
def test_exam_event_bad_user(self, event_signal):
|
||||
"""
|
||||
Test exam event with a user that does not exist in the LMS
|
||||
"""
|
||||
self.user.id = 999 # don't save to db so user doesn't exist
|
||||
event_data = self._get_exam_event_data(self.user, self.course, self.subsection_key)
|
||||
event_metadata = self._get_exam_event_metadata(event_signal)
|
||||
handler = self.HANDLERS.get(event_signal)
|
||||
|
||||
with mock.patch('openedx.core.djangoapps.credit.signals.handlers.log.error') as mock_log:
|
||||
handler(None, event_signal, event_metadata=event_metadata, exam_attempt=event_data)
|
||||
mock_log.assert_called_once_with(
|
||||
'Error occurred while handling exam event for '
|
||||
f'{self.user.id} and content_id {self.subsection_key}. '
|
||||
'User does not exist!'
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
EXAM_ATTEMPT_RESET,
|
||||
EXAM_ATTEMPT_REJECTED,
|
||||
EXAM_ATTEMPT_ERRORED,
|
||||
EXAM_ATTEMPT_VERIFIED,
|
||||
EXAM_ATTEMPT_SUBMITTED,
|
||||
)
|
||||
@mock.patch('openedx.core.djangoapps.credit.signals.handlers.remove_credit_requirement_status', autospec=True)
|
||||
@mock.patch('openedx.core.djangoapps.credit.signals.handlers.set_credit_requirement_status', autospec=True)
|
||||
def test_exam_event_non_credit_course(self, event_signal, mock_remove_credit_status, mock_set_credit_status):
|
||||
"""
|
||||
Credit credit logic should not run on non-credit courses
|
||||
"""
|
||||
non_credit_course = CourseFactory.create()
|
||||
event_data = self._get_exam_event_data(self.user, non_credit_course, self.subsection_key)
|
||||
event_metadata = self._get_exam_event_metadata(event_signal)
|
||||
handler = self.HANDLERS.get(event_signal)
|
||||
|
||||
handler(None, event_signal, event_metadata=event_metadata, exam_attempt=event_data)
|
||||
|
||||
mock_remove_credit_status.assert_not_called()
|
||||
mock_set_credit_status.assert_not_called()
|
||||
|
||||
@@ -11,7 +11,7 @@ from edx_proctoring.api import create_exam
|
||||
from openedx.core.djangoapps.credit.api import get_credit_requirements
|
||||
from openedx.core.djangoapps.credit.exceptions import InvalidCreditRequirements
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse
|
||||
from openedx.core.djangoapps.credit.signals import on_course_publish
|
||||
from openedx.core.djangoapps.credit.signals.handlers import on_course_publish
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
@@ -1066,7 +1066,9 @@ class EnrollmentAllowedView(APIView):
|
||||
|
||||
**Example Request**
|
||||
|
||||
POST /api/enrollment/v1/enrollment_allowed
|
||||
POST /api/enrollment/v1/enrollment_allowed/
|
||||
|
||||
Note: The URL for this request must finish with /
|
||||
|
||||
Example request data:
|
||||
```
|
||||
@@ -1086,7 +1088,6 @@ class EnrollmentAllowedView(APIView):
|
||||
- `auto_enroll` (optional, bool: default=false, _body_)
|
||||
|
||||
**Responses**
|
||||
- 200: Success, enrollment allowed found.
|
||||
- 400: Bad request, missing data.
|
||||
- 403: Forbidden, you need to be staff.
|
||||
- 409: Conflict, enrollment allowed already exists.
|
||||
@@ -1122,7 +1123,9 @@ class EnrollmentAllowedView(APIView):
|
||||
|
||||
**Example Request**
|
||||
|
||||
DELETE /api/enrollment/v1/enrollment_allowed
|
||||
DELETE /api/enrollment/v1/enrollment_allowed/
|
||||
|
||||
Note: The URL for this request must finish with /
|
||||
|
||||
Example request data:
|
||||
```
|
||||
|
||||
@@ -344,7 +344,7 @@ def _track_user_login(user, request):
|
||||
segment.identify(
|
||||
user.id,
|
||||
{
|
||||
'email': request.POST.get('email'),
|
||||
'email': user.email,
|
||||
'username': user.username
|
||||
},
|
||||
{
|
||||
|
||||
29
openedx/testing/coverage_context_listener/pytest_plugin.py
Normal file
29
openedx/testing/coverage_context_listener/pytest_plugin.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
A pytest plugin that reports test contexts to coverage running in another process.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class RemoteContextPlugin:
|
||||
"""
|
||||
Pytest plugin for reporting pytests contexts to coverage running in another process
|
||||
"""
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.active = config.getoption("pytest-contexts")
|
||||
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_configure(config):
|
||||
config.pluginmanager.register(RemoteContextPlugin(config), "remotecontextplugin")
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("coverage")
|
||||
group.addoption(
|
||||
"--pytest-remote-contexts",
|
||||
action="store_true",
|
||||
dest="pytest-contexts",
|
||||
help="Capture the pytest contexts that coverage is being captured in in another process",
|
||||
)
|
||||
@@ -26,7 +26,7 @@ django-storages==1.14
|
||||
# The team that owns this package will manually bump this package rather than having it pulled in automatically.
|
||||
# This is to allow them to better control its deployment and to do it in a process that works better
|
||||
# for them.
|
||||
edx-enterprise==4.3.3
|
||||
edx-enterprise==4.5.0
|
||||
|
||||
# django-oauth-toolkit version >=2.0.0 has breaking changes. More details
|
||||
# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884
|
||||
@@ -120,9 +120,6 @@ libsass==0.10.0
|
||||
# greater version breaking upgrade builds
|
||||
click==8.1.6
|
||||
|
||||
# openedx-events 8.6.0 introduces publishing via configuration. Ticket to unpin: https://github.com/edx/edx-arch-experiments/issues/381
|
||||
openedx-events<8.6.0 # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||
|
||||
# pinning this version to avoid updates while the library is being developed
|
||||
openedx-learning==0.1.7
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
cffi==1.15.1
|
||||
cffi==1.16.0
|
||||
# via cryptography
|
||||
chem==1.2.0
|
||||
# via -r requirements/edx-sandbox/py38.in
|
||||
@@ -20,9 +20,9 @@ cryptography==38.0.4
|
||||
# via
|
||||
# -c requirements/edx-sandbox/../constraints.txt
|
||||
# -r requirements/edx-sandbox/py38.in
|
||||
cycler==0.11.0
|
||||
cycler==0.12.0
|
||||
# via matplotlib
|
||||
fonttools==4.42.1
|
||||
fonttools==4.43.0
|
||||
# via matplotlib
|
||||
importlib-resources==6.1.0
|
||||
# via matplotlib
|
||||
@@ -57,7 +57,7 @@ numpy==1.22.4
|
||||
# scipy
|
||||
openedx-calc==3.0.1
|
||||
# via -r requirements/edx-sandbox/py38.in
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via matplotlib
|
||||
pillow==9.5.0
|
||||
# via
|
||||
|
||||
@@ -77,13 +77,13 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
boto3==1.28.53
|
||||
boto3==1.28.58
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.31.53
|
||||
botocore==1.31.58
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# boto3
|
||||
@@ -107,7 +107,7 @@ certifi==2023.7.22
|
||||
# py2neo
|
||||
# requests
|
||||
# snowflake-connector-python
|
||||
cffi==1.15.1
|
||||
cffi==1.16.0
|
||||
# via
|
||||
# cryptography
|
||||
# pynacl
|
||||
@@ -288,7 +288,7 @@ django-filter==23.3
|
||||
# edx-enterprise
|
||||
# lti-consumer-xblock
|
||||
# openedx-blockstore
|
||||
django-ipware==5.0.0
|
||||
django-ipware==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-enterprise
|
||||
@@ -406,7 +406,7 @@ drf-jwt==1.19.2
|
||||
# via edx-drf-extensions
|
||||
drf-nested-routers==0.93.4
|
||||
# via openedx-blockstore
|
||||
drf-spectacular==0.26.4
|
||||
drf-spectacular==0.26.5
|
||||
# via -r requirements/edx/kernel.in
|
||||
drf-yasg==1.21.5
|
||||
# via
|
||||
@@ -480,7 +480,7 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.3
|
||||
edx-enterprise==4.5.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
@@ -492,7 +492,7 @@ edx-i18n-tools==1.2.0
|
||||
# via ora2
|
||||
edx-milestones==0.5.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-name-affirmation==2.3.6
|
||||
edx-name-affirmation==2.3.7
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
@@ -599,7 +599,7 @@ html5lib==1.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# ora2
|
||||
icalendar==5.0.8
|
||||
icalendar==5.0.10
|
||||
# via -r requirements/edx/kernel.in
|
||||
idna==3.4
|
||||
# via
|
||||
@@ -735,7 +735,7 @@ mysqlclient==2.2.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# openedx-blockstore
|
||||
newrelic==9.0.0
|
||||
newrelic==9.1.0
|
||||
# via
|
||||
# -r requirements/edx/bundled.in
|
||||
# edx-django-utils
|
||||
@@ -758,7 +758,7 @@ oauthlib==3.2.2
|
||||
# social-auth-core
|
||||
olxcleaner==0.2.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
openai==0.28.0
|
||||
openai==0.28.1
|
||||
# via edx-enterprise
|
||||
openedx-atlas==0.5.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
@@ -774,9 +774,8 @@ openedx-django-require==2.1.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-django-wiki==2.0.3
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-events==8.5.0
|
||||
openedx-events==8.8.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-event-bus-kafka
|
||||
# edx-event-bus-redis
|
||||
@@ -792,11 +791,11 @@ openedx-mongodbproxy==0.2.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
optimizely-sdk==4.1.1
|
||||
# via -r requirements/edx/bundled.in
|
||||
ora2==5.5.0
|
||||
ora2==5.5.3
|
||||
# via -r requirements/edx/bundled.in
|
||||
oscrypto==1.3.0
|
||||
# via snowflake-connector-python
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via
|
||||
# drf-yasg
|
||||
# gunicorn
|
||||
@@ -977,7 +976,7 @@ random2==1.0.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
recommender-xblock==2.0.1
|
||||
# via -r requirements/edx/bundled.in
|
||||
redis==5.0.0
|
||||
redis==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# walrus
|
||||
@@ -1018,7 +1017,7 @@ rpds-py==0.10.3
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
ruamel-yaml==0.17.32
|
||||
ruamel-yaml==0.17.33
|
||||
# via drf-yasg
|
||||
ruamel-yaml-clib==0.2.7
|
||||
# via ruamel-yaml
|
||||
@@ -1028,7 +1027,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.6.2
|
||||
s3transfer==0.7.0
|
||||
# via boto3
|
||||
sailthru-client==2.2.3
|
||||
# via edx-ace
|
||||
@@ -1159,7 +1158,7 @@ uritemplate==4.1.1
|
||||
# coreapi
|
||||
# drf-spectacular
|
||||
# drf-yasg
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/paver.txt
|
||||
@@ -1181,7 +1180,7 @@ walrus==0.9.3
|
||||
# via edx-event-bus-redis
|
||||
watchdog==3.0.0
|
||||
# via -r requirements/edx/paver.txt
|
||||
wcwidth==0.2.6
|
||||
wcwidth==0.2.8
|
||||
# via prompt-toolkit
|
||||
web-fragments==2.1.0
|
||||
# via
|
||||
@@ -1204,7 +1203,7 @@ wrapt==1.15.0
|
||||
# via
|
||||
# -r requirements/edx/paver.txt
|
||||
# deprecated
|
||||
xblock[django]==1.7.0
|
||||
xblock[django]==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# acid-xblock
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
chardet==5.2.0
|
||||
# via diff-cover
|
||||
coverage==7.3.1
|
||||
coverage==7.3.2
|
||||
# via -r requirements/edx/coverage.in
|
||||
diff-cover==7.7.0
|
||||
# via -r requirements/edx/coverage.in
|
||||
|
||||
@@ -143,14 +143,14 @@ boto==2.39.0
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
boto3==1.28.53
|
||||
boto3==1.28.58
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.31.53
|
||||
botocore==1.31.58
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -185,7 +185,7 @@ certifi==2023.7.22
|
||||
# py2neo
|
||||
# requests
|
||||
# snowflake-connector-python
|
||||
cffi==1.15.1
|
||||
cffi==1.16.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -273,7 +273,7 @@ coreschema==0.0.4
|
||||
# -r requirements/edx/testing.txt
|
||||
# coreapi
|
||||
# drf-yasg
|
||||
coverage[toml]==7.3.1
|
||||
coverage[toml]==7.3.2
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pytest-cov
|
||||
@@ -472,7 +472,7 @@ django-filter==23.3
|
||||
# edx-enterprise
|
||||
# lti-consumer-xblock
|
||||
# openedx-blockstore
|
||||
django-ipware==5.0.0
|
||||
django-ipware==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -653,7 +653,7 @@ drf-nested-routers==0.93.4
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-blockstore
|
||||
drf-spectacular==0.26.4
|
||||
drf-spectacular==0.26.5
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -748,7 +748,7 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.3
|
||||
edx-enterprise==4.5.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -772,7 +772,7 @@ edx-milestones==0.5.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
edx-name-affirmation==2.3.6
|
||||
edx-name-affirmation==2.3.7
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -895,7 +895,7 @@ faker==19.6.2
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# factory-boy
|
||||
fastapi==0.103.1
|
||||
fastapi==0.103.2
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pact-python
|
||||
@@ -982,7 +982,7 @@ httpx==0.23.3
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pact-python
|
||||
icalendar==5.0.8
|
||||
icalendar==5.0.10
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1000,7 +1000,7 @@ imagesize==1.4.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# sphinx
|
||||
import-linter==1.11.1
|
||||
import-linter==1.12.0
|
||||
# via -r requirements/edx/testing.txt
|
||||
importlib-metadata==6.8.0
|
||||
# via
|
||||
@@ -1236,7 +1236,7 @@ mysqlclient==2.2.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-blockstore
|
||||
newrelic==9.0.0
|
||||
newrelic==9.1.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1271,7 +1271,7 @@ olxcleaner==0.2.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
openai==0.28.0
|
||||
openai==0.28.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1302,9 +1302,8 @@ openedx-django-wiki==2.0.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
openedx-events==8.5.0
|
||||
openedx-events==8.8.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-event-bus-kafka
|
||||
@@ -1327,7 +1326,7 @@ optimizely-sdk==4.1.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
ora2==5.5.0
|
||||
ora2==5.5.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1336,7 +1335,7 @@ oscrypto==1.3.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# snowflake-connector-python
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via
|
||||
# -r requirements/edx/../pip-tools.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1472,11 +1471,11 @@ pycryptodomex==3.19.0
|
||||
# lti-consumer-xblock
|
||||
# pyjwkest
|
||||
# snowflake-connector-python
|
||||
pydantic==2.3.0
|
||||
pydantic==2.4.2
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# fastapi
|
||||
pydantic-core==2.6.3
|
||||
pydantic-core==2.10.1
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pydantic
|
||||
@@ -1710,7 +1709,7 @@ recommender-xblock==2.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
redis==5.0.0
|
||||
redis==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1768,7 +1767,7 @@ rpds-py==0.10.3
|
||||
# -r requirements/edx/testing.txt
|
||||
# jsonschema
|
||||
# referencing
|
||||
ruamel-yaml==0.17.32
|
||||
ruamel-yaml==0.17.33
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1785,7 +1784,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.6.2
|
||||
s3transfer==0.7.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -2047,11 +2046,11 @@ tqdm==4.66.1
|
||||
# openai
|
||||
types-pytz==2023.3.1.1
|
||||
# via django-stubs
|
||||
types-pyyaml==6.0.12.11
|
||||
types-pyyaml==6.0.12.12
|
||||
# via
|
||||
# django-stubs
|
||||
# djangorestframework-stubs
|
||||
types-requests==2.31.0.3
|
||||
types-requests==2.31.0.6
|
||||
# via djangorestframework-stubs
|
||||
types-urllib3==1.26.25.14
|
||||
# via types-requests
|
||||
@@ -2101,7 +2100,7 @@ uritemplate==4.1.1
|
||||
# coreapi
|
||||
# drf-spectacular
|
||||
# drf-yasg
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -2148,7 +2147,7 @@ watchdog==3.0.0
|
||||
# -r requirements/edx/development.in
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
wcwidth==0.2.6
|
||||
wcwidth==0.2.8
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -2184,7 +2183,7 @@ wrapt==1.15.0
|
||||
# -r requirements/edx/testing.txt
|
||||
# astroid
|
||||
# deprecated
|
||||
xblock[django]==1.7.0
|
||||
xblock[django]==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
|
||||
@@ -103,13 +103,13 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
boto3==1.28.53
|
||||
boto3==1.28.58
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.31.53
|
||||
botocore==1.31.58
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -133,7 +133,7 @@ certifi==2023.7.22
|
||||
# py2neo
|
||||
# requests
|
||||
# snowflake-connector-python
|
||||
cffi==1.15.1
|
||||
cffi==1.16.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# cryptography
|
||||
@@ -345,7 +345,7 @@ django-filter==23.3
|
||||
# edx-enterprise
|
||||
# lti-consumer-xblock
|
||||
# openedx-blockstore
|
||||
django-ipware==5.0.0
|
||||
django-ipware==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -481,7 +481,7 @@ drf-nested-routers==0.93.4
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-blockstore
|
||||
drf-spectacular==0.26.4
|
||||
drf-spectacular==0.26.5
|
||||
# via -r requirements/edx/base.txt
|
||||
drf-yasg==1.21.5
|
||||
# via
|
||||
@@ -556,7 +556,7 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.3
|
||||
edx-enterprise==4.5.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -570,7 +570,7 @@ edx-i18n-tools==1.2.0
|
||||
# ora2
|
||||
edx-milestones==0.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-name-affirmation==2.3.6
|
||||
edx-name-affirmation==2.3.7
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
@@ -694,7 +694,7 @@ html5lib==1.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# ora2
|
||||
icalendar==5.0.8
|
||||
icalendar==5.0.10
|
||||
# via -r requirements/edx/base.txt
|
||||
idna==3.4
|
||||
# via
|
||||
@@ -869,7 +869,7 @@ mysqlclient==2.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-blockstore
|
||||
newrelic==9.0.0
|
||||
newrelic==9.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-django-utils
|
||||
@@ -895,7 +895,7 @@ oauthlib==3.2.2
|
||||
# social-auth-core
|
||||
olxcleaner==0.2.1
|
||||
# via -r requirements/edx/base.txt
|
||||
openai==0.28.0
|
||||
openai==0.28.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -914,9 +914,8 @@ openedx-django-require==2.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-django-wiki==2.0.3
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-events==8.5.0
|
||||
openedx-events==8.8.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-event-bus-kafka
|
||||
# edx-event-bus-redis
|
||||
@@ -932,13 +931,13 @@ openedx-mongodbproxy==0.2.0
|
||||
# via -r requirements/edx/base.txt
|
||||
optimizely-sdk==4.1.1
|
||||
# via -r requirements/edx/base.txt
|
||||
ora2==5.5.0
|
||||
ora2==5.5.3
|
||||
# via -r requirements/edx/base.txt
|
||||
oscrypto==1.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# snowflake-connector-python
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# drf-yasg
|
||||
@@ -1158,7 +1157,7 @@ random2==1.0.1
|
||||
# via -r requirements/edx/base.txt
|
||||
recommender-xblock==2.0.1
|
||||
# via -r requirements/edx/base.txt
|
||||
redis==5.0.0
|
||||
redis==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# walrus
|
||||
@@ -1204,7 +1203,7 @@ rpds-py==0.10.3
|
||||
# -r requirements/edx/base.txt
|
||||
# jsonschema
|
||||
# referencing
|
||||
ruamel-yaml==0.17.32
|
||||
ruamel-yaml==0.17.33
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# drf-yasg
|
||||
@@ -1218,7 +1217,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.6.2
|
||||
s3transfer==0.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -1416,7 +1415,7 @@ uritemplate==4.1.1
|
||||
# coreapi
|
||||
# drf-spectacular
|
||||
# drf-yasg
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1443,7 +1442,7 @@ walrus==0.9.3
|
||||
# edx-event-bus-redis
|
||||
watchdog==3.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
wcwidth==0.2.6
|
||||
wcwidth==0.2.8
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# prompt-toolkit
|
||||
@@ -1469,7 +1468,7 @@ wrapt==1.15.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# deprecated
|
||||
xblock[django]==1.7.0
|
||||
xblock[django]==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# acid-xblock
|
||||
|
||||
@@ -54,7 +54,7 @@ stevedore==5.1.0
|
||||
# edx-opaque-keys
|
||||
typing-extensions==4.8.0
|
||||
# via edx-opaque-keys
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# requests
|
||||
|
||||
@@ -52,7 +52,7 @@ markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via semgrep
|
||||
peewee==3.16.3
|
||||
# via semgrep
|
||||
@@ -68,17 +68,17 @@ referencing==0.30.2
|
||||
# jsonschema-specifications
|
||||
requests==2.31.0
|
||||
# via semgrep
|
||||
rich==13.5.3
|
||||
rich==13.6.0
|
||||
# via semgrep
|
||||
rpds-py==0.10.3
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
ruamel-yaml==0.17.32
|
||||
ruamel-yaml==0.17.33
|
||||
# via semgrep
|
||||
ruamel-yaml-clib==0.2.7
|
||||
# via ruamel-yaml
|
||||
semgrep==1.41.0
|
||||
semgrep==1.42.0
|
||||
# via -r requirements/edx/semgrep.in
|
||||
tomli==2.0.1
|
||||
# via semgrep
|
||||
@@ -88,7 +88,7 @@ typing-extensions==4.8.0
|
||||
# semgrep
|
||||
ujson==5.8.0
|
||||
# via python-lsp-jsonrpc
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# requests
|
||||
|
||||
@@ -108,13 +108,13 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
boto3==1.28.53
|
||||
boto3==1.28.58
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.31.53
|
||||
botocore==1.31.58
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -140,7 +140,7 @@ certifi==2023.7.22
|
||||
# py2neo
|
||||
# requests
|
||||
# snowflake-connector-python
|
||||
cffi==1.15.1
|
||||
cffi==1.16.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# cryptography
|
||||
@@ -210,7 +210,7 @@ coreschema==0.0.4
|
||||
# -r requirements/edx/base.txt
|
||||
# coreapi
|
||||
# drf-yasg
|
||||
coverage[toml]==7.3.1
|
||||
coverage[toml]==7.3.2
|
||||
# via
|
||||
# -r requirements/edx/coverage.txt
|
||||
# pytest-cov
|
||||
@@ -376,7 +376,7 @@ django-filter==23.3
|
||||
# edx-enterprise
|
||||
# lti-consumer-xblock
|
||||
# openedx-blockstore
|
||||
django-ipware==5.0.0
|
||||
django-ipware==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -506,7 +506,7 @@ drf-nested-routers==0.93.4
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-blockstore
|
||||
drf-spectacular==0.26.4
|
||||
drf-spectacular==0.26.5
|
||||
# via -r requirements/edx/base.txt
|
||||
drf-yasg==1.21.5
|
||||
# via
|
||||
@@ -581,7 +581,7 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.3
|
||||
edx-enterprise==4.5.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -598,7 +598,7 @@ edx-lint==5.3.4
|
||||
# via -r requirements/edx/testing.in
|
||||
edx-milestones==0.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-name-affirmation==2.3.6
|
||||
edx-name-affirmation==2.3.7
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
@@ -689,7 +689,7 @@ factory-boy==3.3.0
|
||||
# via -r requirements/edx/testing.in
|
||||
faker==19.6.2
|
||||
# via factory-boy
|
||||
fastapi==0.103.1
|
||||
fastapi==0.103.2
|
||||
# via pact-python
|
||||
fastavro==1.8.3
|
||||
# via
|
||||
@@ -746,7 +746,7 @@ httpretty==1.1.4
|
||||
# via -r requirements/edx/testing.in
|
||||
httpx==0.23.3
|
||||
# via pact-python
|
||||
icalendar==5.0.8
|
||||
icalendar==5.0.10
|
||||
# via -r requirements/edx/base.txt
|
||||
idna==3.4
|
||||
# via
|
||||
@@ -757,7 +757,7 @@ idna==3.4
|
||||
# rfc3986
|
||||
# snowflake-connector-python
|
||||
# yarl
|
||||
import-linter==1.11.1
|
||||
import-linter==1.12.0
|
||||
# via -r requirements/edx/testing.in
|
||||
importlib-metadata==6.8.0
|
||||
# via
|
||||
@@ -933,7 +933,7 @@ mysqlclient==2.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-blockstore
|
||||
newrelic==9.0.0
|
||||
newrelic==9.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-django-utils
|
||||
@@ -959,7 +959,7 @@ oauthlib==3.2.2
|
||||
# social-auth-core
|
||||
olxcleaner==0.2.1
|
||||
# via -r requirements/edx/base.txt
|
||||
openai==0.28.0
|
||||
openai==0.28.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -978,9 +978,8 @@ openedx-django-require==2.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-django-wiki==2.0.3
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-events==8.5.0
|
||||
openedx-events==8.8.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-event-bus-kafka
|
||||
# edx-event-bus-redis
|
||||
@@ -996,13 +995,13 @@ openedx-mongodbproxy==0.2.0
|
||||
# via -r requirements/edx/base.txt
|
||||
optimizely-sdk==4.1.1
|
||||
# via -r requirements/edx/base.txt
|
||||
ora2==5.5.0
|
||||
ora2==5.5.3
|
||||
# via -r requirements/edx/base.txt
|
||||
oscrypto==1.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# snowflake-connector-python
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# drf-yasg
|
||||
@@ -1105,9 +1104,9 @@ pycryptodomex==3.19.0
|
||||
# lti-consumer-xblock
|
||||
# pyjwkest
|
||||
# snowflake-connector-python
|
||||
pydantic==2.3.0
|
||||
pydantic==2.4.2
|
||||
# via fastapi
|
||||
pydantic-core==2.6.3
|
||||
pydantic-core==2.10.1
|
||||
# via pydantic
|
||||
pygments==2.16.1
|
||||
# via
|
||||
@@ -1287,7 +1286,7 @@ random2==1.0.1
|
||||
# via -r requirements/edx/base.txt
|
||||
recommender-xblock==2.0.1
|
||||
# via -r requirements/edx/base.txt
|
||||
redis==5.0.0
|
||||
redis==5.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# walrus
|
||||
@@ -1335,7 +1334,7 @@ rpds-py==0.10.3
|
||||
# -r requirements/edx/base.txt
|
||||
# jsonschema
|
||||
# referencing
|
||||
ruamel-yaml==0.17.32
|
||||
ruamel-yaml==0.17.33
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# drf-yasg
|
||||
@@ -1349,7 +1348,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.6.2
|
||||
s3transfer==0.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -1546,7 +1545,7 @@ uritemplate==4.1.1
|
||||
# coreapi
|
||||
# drf-spectacular
|
||||
# drf-yasg
|
||||
urllib3==1.26.16
|
||||
urllib3==1.26.17
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1578,7 +1577,7 @@ walrus==0.9.3
|
||||
# edx-event-bus-redis
|
||||
watchdog==3.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
wcwidth==0.2.6
|
||||
wcwidth==0.2.8
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# prompt-toolkit
|
||||
@@ -1605,7 +1604,7 @@ wrapt==1.15.0
|
||||
# -r requirements/edx/base.txt
|
||||
# astroid
|
||||
# deprecated
|
||||
xblock[django]==1.7.0
|
||||
xblock[django]==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# acid-xblock
|
||||
|
||||
@@ -12,7 +12,7 @@ click==8.1.6
|
||||
# pip-tools
|
||||
importlib-metadata==6.8.0
|
||||
# via build
|
||||
packaging==23.1
|
||||
packaging==23.2
|
||||
# via build
|
||||
pip-tools==7.3.0
|
||||
# via -r requirements/pip-tools.in
|
||||
|
||||
141
scripts/reset-test-db.sh
Executable file
141
scripts/reset-test-db.sh
Executable file
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# reset-test-db.sh
|
||||
#
|
||||
# Resets the MySQL test database for the bok-choy acceptance tests.
|
||||
#
|
||||
# If it finds a cached schema and migration history, it will start
|
||||
# from the cached version to speed up migrations.
|
||||
#
|
||||
# If no cached database exists, it will create one. This can be
|
||||
# checked into the repo to speed up future tests.
|
||||
#
|
||||
# Note that we do NOT want to re-use the cache between test runs!
|
||||
# A newer commit could introduce migrations that do not exist
|
||||
# in other commits, which could cause migrations to fail in the other
|
||||
# commits.
|
||||
#
|
||||
# For this reason, we always use a cache that was committed to master
|
||||
# at the time the branch was created.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# Fail fast
|
||||
set -e
|
||||
|
||||
DB_CACHE_DIR="common/test/db_cache"
|
||||
|
||||
if [[ -z "$BOK_CHOY_HOSTNAME" ]]; then
|
||||
MYSQL_HOST=""
|
||||
SETTINGS="bok_choy"
|
||||
else
|
||||
MYSQL_HOST="--host=edx.devstack.mysql80"
|
||||
SETTINGS="bok_choy_docker"
|
||||
fi
|
||||
|
||||
for i in "$@"; do
|
||||
case $i in
|
||||
-r|--rebuild_cache)
|
||||
REBUILD_CACHE=true
|
||||
;;
|
||||
-m|--migrations)
|
||||
APPLY_MIGRATIONS=true
|
||||
;;
|
||||
-c|--calculate_migrations)
|
||||
CALCULATE_MIGRATIONS=true
|
||||
;;
|
||||
-u|--use-existing-db)
|
||||
USE_EXISTING_DB=true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
declare -A databases
|
||||
declare -a database_order
|
||||
databases=(["default"]="edxtest" ["student_module_history"]="student_module_history_test")
|
||||
database_order=("default" "student_module_history")
|
||||
|
||||
calculate_migrations() {
|
||||
echo "Calculating migrations for fingerprinting."
|
||||
output_file="common/test/db_cache/bok_choy_${db}_migrations.yaml"
|
||||
# Redirect stdout to /dev/null because the script will print
|
||||
# out all migrations to both stdout and the output file.
|
||||
./manage.py lms --settings "$SETTINGS" show_unapplied_migrations --database "$db" --output_file "$output_file" 1>/dev/null
|
||||
}
|
||||
|
||||
run_migrations() {
|
||||
echo "Running the lms migrations on the $db bok_choy DB."
|
||||
./manage.py lms --settings "$SETTINGS" migrate --database "$db" --traceback --noinput
|
||||
echo "Running the cms migrations on the $db bok_choy DB."
|
||||
./manage.py cms --settings "$SETTINGS" migrate --database "$db" --traceback --noinput
|
||||
}
|
||||
|
||||
load_cache_into_db() {
|
||||
echo "Loading the schema from the filesystem into the $db MySQL DB."
|
||||
mysql "$MYSQL_HOST" -u root "${databases["$db"]}" < "$DB_CACHE_DIR/bok_choy_schema_$db.sql"
|
||||
echo "Loading the fixture data from the filesystem into the $db MySQL DB."
|
||||
./manage.py lms --settings "$SETTINGS" loaddata --database "$db" "$DB_CACHE_DIR/bok_choy_data_$db.json"
|
||||
echo "Loading the migration data from the filesystem into the $db MySQL DB."
|
||||
mysql "$MYSQL_HOST" -u root "${databases["$db"]}" < "$DB_CACHE_DIR/bok_choy_migrations_data_$db.sql"
|
||||
}
|
||||
|
||||
rebuild_cache_for_db() {
|
||||
# Make sure the DB has all migrations applied
|
||||
run_migrations
|
||||
|
||||
# Dump the schema and data to the cache
|
||||
echo "Using the dumpdata command to save the $db fixture data to the filesystem."
|
||||
./manage.py lms --settings "$SETTINGS" dumpdata --database "$db" > "$DB_CACHE_DIR/bok_choy_data_$db.json" --exclude=api_admin.Catalog
|
||||
echo "Saving the schema of the $db bok_choy DB to the filesystem."
|
||||
mysqldump "$MYSQL_HOST" -u root --no-data --skip-comments --skip-dump-date "${databases[$db]}" > "$DB_CACHE_DIR/bok_choy_schema_$db.sql"
|
||||
|
||||
# dump_data does not dump the django_migrations table so we do it separately.
|
||||
echo "Saving the django_migrations table of the $db bok_choy DB to the filesystem."
|
||||
mysqldump $MYSQL_HOST -u root --no-create-info --skip-comments --skip-dump-date "${databases["$db"]}" django_migrations > "$DB_CACHE_DIR/bok_choy_migrations_data_$db.sql"
|
||||
}
|
||||
|
||||
for db in "${database_order[@]}"; do
|
||||
if ! [[ $USE_EXISTING_DB ]]; then
|
||||
echo "CREATE DATABASE IF NOT EXISTS ${databases[$db]};" | mysql $MYSQL_HOST -u root
|
||||
|
||||
# Clear out the test database
|
||||
#
|
||||
# We are using the reset_db command which uses "DROP DATABASE" and
|
||||
# "CREATE DATABASE" in case the tests are being run in an environment (e.g. devstack
|
||||
# or a jenkins worker environment) that already ran tests on another commit that had
|
||||
# different migrations that created, dropped, or altered tables.
|
||||
echo "Issuing a reset_db command to the $db bok_choy MySQL database."
|
||||
./manage.py lms --settings "$SETTINGS" reset_db --traceback --router "$db"
|
||||
fi
|
||||
|
||||
if ! [[ $CALCULATE_MIGRATIONS ]]; then
|
||||
# If there are cached database schemas/data, then load them.
|
||||
# If they are missing, then we will want to build new cache files even if
|
||||
# not explicitly directed to do so via arguments passed to this script.
|
||||
if [[ ! -f $DB_CACHE_DIR/bok_choy_schema_$db.sql || ! -f $DB_CACHE_DIR/bok_choy_data_$db.json || ! -f $DB_CACHE_DIR/bok_choy_migrations_data_$db.sql ]]; then
|
||||
REBUILD_CACHE=true
|
||||
else
|
||||
load_cache_into_db
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $REBUILD_CACHE ]]; then
|
||||
echo "Cleaning the DB cache directory and building new files."
|
||||
mkdir -p $DB_CACHE_DIR && rm -f $DB_CACHE_DIR/bok_choy*
|
||||
|
||||
for db in "${database_order[@]}"; do
|
||||
rebuild_cache_for_db
|
||||
done
|
||||
elif [[ $APPLY_MIGRATIONS ]]; then
|
||||
for db in "${database_order[@]}"; do
|
||||
run_migrations
|
||||
done
|
||||
elif [[ $CALCULATE_MIGRATIONS ]]; then
|
||||
for db in "${database_order[@]}"; do
|
||||
calculate_migrations
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
#
|
||||
certifi==2023.7.22
|
||||
# via requests
|
||||
charset-normalizer==3.2.0
|
||||
charset-normalizer==3.3.0
|
||||
# via requests
|
||||
idna==3.4
|
||||
# via requests
|
||||
requests==2.31.0
|
||||
# via -r scripts/xblock/requirements.in
|
||||
urllib3==2.0.5
|
||||
urllib3==2.0.6
|
||||
# via requests
|
||||
|
||||
@@ -5,24 +5,26 @@ Tests for vertical block.
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytz
|
||||
import ddt
|
||||
from fs.memoryfs import MemoryFS
|
||||
import pytz
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.test import override_settings
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
from fs.memoryfs import MemoryFS
|
||||
from openedx_filters import PipelineStep
|
||||
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
|
||||
|
||||
from ..vertical_block import XBLOCK_SKILL_TAG_VERIFICATION_SWITCH
|
||||
from ..x_module import AUTHOR_VIEW, PUBLIC_VIEW, STUDENT_VIEW
|
||||
from . import prepare_block_runtime
|
||||
from .helpers import StubUserService
|
||||
from .xml import XModuleXmlImportTest
|
||||
from .xml import factories as xml
|
||||
from ..x_module import STUDENT_VIEW, AUTHOR_VIEW, PUBLIC_VIEW
|
||||
|
||||
COMPLETION_DELAY = 9876
|
||||
|
||||
@@ -360,6 +362,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
},
|
||||
},
|
||||
)
|
||||
@override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True)
|
||||
def test_vertical_block_child_render_started_filter_execution(self):
|
||||
"""
|
||||
Test the VerticalBlockChildRenderStarted filter's effects on student view.
|
||||
@@ -382,6 +385,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
},
|
||||
},
|
||||
)
|
||||
@override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True)
|
||||
def test_vertical_block_child_render_is_skipped_on_filter_exception(self):
|
||||
"""
|
||||
Test VerticalBlockChildRenderStarted filter can be used to skip child blocks.
|
||||
@@ -405,6 +409,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
},
|
||||
},
|
||||
)
|
||||
@override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True)
|
||||
def test_vertical_block_render_completed_filter_execution(self):
|
||||
"""
|
||||
Test the VerticalBlockRenderCompleted filter's execution.
|
||||
@@ -427,6 +432,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
},
|
||||
},
|
||||
)
|
||||
@override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True)
|
||||
def test_vertical_block_render_output_is_changed_on_filter_exception(self):
|
||||
"""
|
||||
Test VerticalBlockRenderCompleted filter can be used to prevent vertical block from rendering.
|
||||
|
||||
@@ -9,17 +9,19 @@ from datetime import datetime
|
||||
from functools import reduce
|
||||
|
||||
import pytz
|
||||
from edx_toggles.toggles import WaffleSwitch
|
||||
from lxml import etree
|
||||
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.core import XBlock # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.fields import Boolean, Scope
|
||||
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
|
||||
|
||||
from xmodule.mako_block import MakoTemplateBlockBase
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.seq_block import SequenceFields
|
||||
from xmodule.studio_editable import StudioEditableBlock
|
||||
from xmodule.util.misc import is_xblock_an_assignment
|
||||
from xmodule.util.builtin_assets import add_webpack_js_to_fragment
|
||||
from xmodule.util.misc import is_xblock_an_assignment
|
||||
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW, XModuleFields
|
||||
from xmodule.xml_block import XmlMixin
|
||||
|
||||
@@ -32,6 +34,17 @@ _ = lambda text: text
|
||||
# HACK: This shouldn't be hard-coded to two types
|
||||
# OBSOLETE: This obsoletes 'type'
|
||||
CLASS_PRIORITY = ['video', 'problem']
|
||||
WAFFLE_NAMESPACE = 'xblocks'
|
||||
# .. toggle_name: xblocks.xblock_skill_tag_verification'
|
||||
# .. toggle_implementation: WaffleSwitch
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Set to True to get learner verification of the skills associated with the xblock.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2023-10-04
|
||||
# .. toggle_target_removal_date: None
|
||||
XBLOCK_SKILL_TAG_VERIFICATION_SWITCH = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation
|
||||
f'{WAFFLE_NAMESPACE}.xblock_skill_tag_verification', __name__
|
||||
)
|
||||
|
||||
|
||||
class VerticalFields:
|
||||
@@ -116,16 +129,16 @@ class VerticalBlock(
|
||||
child_block_context['wrap_xblock_data'] = {
|
||||
'mark-completed-on-view-after-delay': complete_on_view_delay
|
||||
}
|
||||
|
||||
try:
|
||||
# .. filter_implemented_name: VerticalBlockChildRenderStarted
|
||||
# .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1
|
||||
child, child_block_context = VerticalBlockChildRenderStarted.run_filter(
|
||||
block=child, context=child_block_context
|
||||
)
|
||||
except VerticalBlockChildRenderStarted.PreventChildBlockRender as exc:
|
||||
log.info("Skipping %s from vertical block. Reason: %s", child, exc.message)
|
||||
continue
|
||||
if XBLOCK_SKILL_TAG_VERIFICATION_SWITCH.is_enabled():
|
||||
try:
|
||||
# .. filter_implemented_name: VerticalBlockChildRenderStarted
|
||||
# .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1
|
||||
child, child_block_context = VerticalBlockChildRenderStarted.run_filter(
|
||||
block=child, context=child_block_context
|
||||
)
|
||||
except VerticalBlockChildRenderStarted.PreventChildBlockRender as exc:
|
||||
log.info("Skipping %s from vertical block. Reason: %s", child, exc.message)
|
||||
continue
|
||||
|
||||
rendered_child = child.render(view, child_block_context)
|
||||
fragment.add_fragment_resources(rendered_child)
|
||||
@@ -167,15 +180,16 @@ class VerticalBlock(
|
||||
add_webpack_js_to_fragment(fragment, 'VerticalStudentView')
|
||||
fragment.initialize_js('VerticalStudentView')
|
||||
|
||||
try:
|
||||
# .. filter_implemented_name: VerticalBlockRenderCompleted
|
||||
# .. filter_type: org.openedx.learning.vertical_block.render.completed.v1
|
||||
_, fragment, context, view = VerticalBlockRenderCompleted.run_filter(
|
||||
block=self, fragment=fragment, context=context, view=view
|
||||
)
|
||||
except VerticalBlockRenderCompleted.PreventVerticalBlockRender as exc:
|
||||
log.info("VerticalBlock rendering stopped. Reason: %s", exc.message)
|
||||
fragment.content = exc.message
|
||||
if XBLOCK_SKILL_TAG_VERIFICATION_SWITCH.is_enabled():
|
||||
try:
|
||||
# .. filter_implemented_name: VerticalBlockRenderCompleted
|
||||
# .. filter_type: org.openedx.learning.vertical_block.render.completed.v1
|
||||
_, fragment, context, view = VerticalBlockRenderCompleted.run_filter(
|
||||
block=self, fragment=fragment, context=context, view=view
|
||||
)
|
||||
except VerticalBlockRenderCompleted.PreventVerticalBlockRender as exc:
|
||||
log.info("VerticalBlock rendering stopped. Reason: %s", exc.message)
|
||||
fragment.content = exc.message
|
||||
|
||||
return fragment
|
||||
|
||||
|
||||
Reference in New Issue
Block a user