chore: resolve conflicts

This commit is contained in:
salman2013
2023-10-04 18:45:02 +05:00
41 changed files with 867 additions and 213 deletions

View File

@@ -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:

View File

@@ -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)

View File

@@ -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,

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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}

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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.

View 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: ''

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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.',

View File

@@ -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(

View File

@@ -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
);
}

View File

@@ -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,
});

View File

@@ -1,4 +1 @@
<% if (isUserB2CSubscriptionsEnabled) { %>
<aside class="aside js-subscription-upsell"></aside>
<% } %>
<div class="aside program-advertise"></div>

View File

@@ -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())

View 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')

View File

@@ -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()

View File

@@ -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

View File

@@ -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:
```

View File

@@ -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
},
{

View 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",
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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.

View File

@@ -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