diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py index cf48119fce..c50ea54d9a 100644 --- a/cms/djangoapps/contentstore/views/certificates.py +++ b/cms/djangoapps/contentstore/views/certificates.py @@ -32,13 +32,19 @@ from django.core.exceptions import PermissionDenied from django.http import HttpResponse from django.shortcuts import redirect from django.utils.translation import gettext as _ +from django.utils.decorators import method_decorator from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_http_methods +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status from common.djangoapps.course_modes.models import CourseMode from eventtracking import tracker from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import AssetKey, CourseKey +from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.student.auth import has_studio_write_access from common.djangoapps.student.roles import GlobalStaff @@ -47,6 +53,10 @@ from common.djangoapps.util.json_request import JsonResponse from xmodule.modulestore import EdxJSONEncoder # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order +from cms.djangoapps.contentstore.views.serializers import CertificateActivationSerializer +from cms.djangoapps.contentstore.views.permissions import HasStudioWriteAccess + + from ..exceptions import AssetNotFoundException from ..toggles import use_new_certificates_page from ..utils import ( @@ -360,39 +370,56 @@ class Certificate: return self._certificate_data -@login_required -@require_http_methods(("POST",)) -@ensure_csrf_cookie -def certificate_activation_handler(request, course_key_string): +class ModulestoreMixin: """ - A handler for Certificate Activation/Deactivation - - POST - json: is_active. update the activation state of certificate + Mixin to provide a get_modulestore() method for views. + Makes it easier to override or patch in tests. """ - course_key = CourseKey.from_string(course_key_string) - store = modulestore() - try: - course = _get_course_and_check_access(course_key, request.user) - except PermissionDenied: - msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user) - return JsonResponse({"error": msg}, status=403) + def get_modulestore(self): + return modulestore() - data = json.loads(request.body.decode('utf8')) - is_active = data.get('is_active', False) - certificates = CertificateManager.get_certificates(course) - # for certificate activation/deactivation, we are assuming one certificate in certificates collection. - for certificate in certificates: - certificate['is_active'] = is_active - break +class CertificateActivationAPIView( + DeveloperErrorViewMixin, + ModulestoreMixin, + APIView +): + """ + View for activating or deactivating course certificates. + This view allows instructors to toggle the activation state of course certificates. + """ + permission_classes = [IsAuthenticated, HasStudioWriteAccess] + serializer_class = CertificateActivationSerializer - store.update_item(course, request.user.id) - cert_event_type = 'activated' if is_active else 'deactivated' - CertificateManager.track_event(cert_event_type, { - 'course_id': str(course.id), - }) - return HttpResponse(status=200) + @method_decorator(ensure_csrf_cookie) + def post(self, request, course_key_string): + """ + A handler for Certificate Activation/Deactivation + + POST + json: is_active. update the activation state of certificate + """ + course_key = CourseKey.from_string(course_key_string) + course = self.get_modulestore().get_course(course_key, depth=0) + + serializer = self.serializer_class(data=request.data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + is_active = serializer.validated_data['is_active'] + certificates = CertificateManager.get_certificates(course) + + # for certificate activation/deactivation, we are assuming one certificate in certificates collection. + for certificate in certificates: + certificate['is_active'] = is_active + break + + self.get_modulestore().update_item(course, request.user.id) + cert_event_type = 'activated' if is_active else 'deactivated' + CertificateManager.track_event(cert_event_type, { + 'course_id': str(course.id), + }) + return Response(status=status.HTTP_200_OK) @login_required diff --git a/cms/djangoapps/contentstore/views/permissions.py b/cms/djangoapps/contentstore/views/permissions.py new file mode 100644 index 0000000000..6d63c7a124 --- /dev/null +++ b/cms/djangoapps/contentstore/views/permissions.py @@ -0,0 +1,22 @@ +""" +Custom permissions for the content store views. +""" + +from rest_framework.permissions import BasePermission + +from common.djangoapps.student.auth import has_studio_write_access +from openedx.core.lib.api.view_utils import validate_course_key + + +class HasStudioWriteAccess(BasePermission): + """ + Check if the user has write access to studio. + """ + + def has_permission(self, request, view): + """ + Check if the user has write access to studio. + """ + course_key_string = view.kwargs.get("course_key_string") + course_key = validate_course_key(course_key_string) + return has_studio_write_access(request.user, course_key) diff --git a/cms/djangoapps/contentstore/views/serializers.py b/cms/djangoapps/contentstore/views/serializers.py new file mode 100644 index 0000000000..c96e716410 --- /dev/null +++ b/cms/djangoapps/contentstore/views/serializers.py @@ -0,0 +1,16 @@ +""" +Serializers for the contentstore.views module. + +This module contains DRF serializers for various features such as certificates, blocks, and others. +Add new serializers here as needed for API endpoints in this module. +""" + +from rest_framework import serializers + + +class CertificateActivationSerializer(serializers.Serializer): + """ + Serializer for activating or deactivating course certificates. + """ + # This field indicates whether the certificate should be activated or deactivated. + is_active = serializers.BooleanField(required=False, default=False) diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index ef190cece1..d75d481ad6 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -1303,13 +1303,12 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements # Update with gating info xblock_info.update(_get_gating_info(course, xblock)) + # Also add upstream info + xblock_info["upstream_info"] = UpstreamLink.try_get_for_block(xblock, log_error=False).to_json() if is_xblock_unit: # if xblock is a Unit we add the discussion_enabled option xblock_info["discussion_enabled"] = xblock.discussion_enabled - # Also add upstream info - xblock_info["upstream_info"] = UpstreamLink.try_get_for_block(xblock, log_error=False).to_json() - if xblock.category == "sequential": # Entrance exam subsection should be hidden. in_entrance_exam is # inherited metadata, all children will have it. diff --git a/cms/envs/analytics_exporter.py b/cms/envs/analytics_exporter.py deleted file mode 100644 index a6b53a2711..0000000000 --- a/cms/envs/analytics_exporter.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Settings for running management commands for the Analytics Exporter. - -The Analytics Exporter jobs run edxapp management commands using production -settings and configuration, however they currently DO NOT use edxapp production -environments (such as edxapp Amazon AMIs or Docker images) where theme files -get installed. As a result we must disable comprehensive theming or else -startup checks from the theming app will throw an error due to missing themes. -""" - -from .production import * # pylint: disable=wildcard-import, unused-wildcard-import - -ENABLE_COMPREHENSIVE_THEMING = False diff --git a/cms/envs/devstack-experimental.yml b/cms/envs/devstack-experimental.yml deleted file mode 100644 index f5d182a82b..0000000000 --- a/cms/envs/devstack-experimental.yml +++ /dev/null @@ -1,553 +0,0 @@ -# This file is an experimental extraction of /edx/etc/studio.yml from -# a CMS devstack container. -# -# When devstack is configured to use the new `openedx/` images -# instead of the old `edxops/edxapp` image, it will use this file -# as input to cms/envs/production.py (and, in turn, cms/envs/devstack.py). -# If you are using devstack with the `edxops/edxapp` image, though, -# this file is NOT used. -# -# Q. Should I update this file when I update devstack.py? -# A. You don't *have* to, because settings in devstack.py -# override these settings. But, it doesn't harm to also make them -# here in order to quell confusion. The hope is that we'll -# adpot OEP-45 eventually, which recommends against having -# a devstack.py at all. -# -# This is part of the effort to move our dev tools off of Ansible and -# Paver, described here: https://github.com/openedx/devstack/pull/866 -# TODO: If the effort described above is abandoned, then this file should -# probably be deleted. -ACTIVATION_EMAIL_SUPPORT_LINK: '' -AFFILIATE_COOKIE_NAME: dev_affiliate_id -ALTERNATE_WORKER_QUEUES: lms -ANALYTICS_DASHBOARD_NAME: Your Platform Name Here Insights -ANALYTICS_DASHBOARD_URL: http://localhost:18110/courses -AUTH_PASSWORD_VALIDATORS: -- NAME: django.contrib.auth.password_validation.UserAttributeSimilarityValidator -- NAME: common.djangoapps.util.password_policy_validators.MinimumLengthValidator - OPTIONS: - min_length: 2 -- NAME: common.djangoapps.util.password_policy_validators.MaximumLengthValidator - OPTIONS: - max_length: 75 -AUTHORING_API_URL: https://example.com -AWS_ACCESS_KEY_ID: null -AWS_QUERYSTRING_AUTH: false -AWS_S3_CUSTOM_DOMAIN: SET-ME-PLEASE (ex. bucket-name.s3.amazonaws.com) -AWS_SECRET_ACCESS_KEY: null -AWS_SES_REGION_ENDPOINT: email.us-east-1.amazonaws.com -AWS_SES_REGION_NAME: us-east-1 -AWS_STORAGE_BUCKET_NAME: SET-ME-PLEASE (ex. bucket-name) -BASE_COOKIE_DOMAIN: localhost -BLOCK_STRUCTURES_SETTINGS: - COURSE_PUBLISH_TASK_DELAY: 30 - TASK_DEFAULT_RETRY_DELAY: 30 - TASK_MAX_RETRIES: 5 -BRANCH_IO_KEY: '' -BUGS_EMAIL: bugs@example.com -BULK_EMAIL_DEFAULT_FROM_EMAIL: no-reply@example.com -BULK_EMAIL_EMAILS_PER_TASK: 500 -BULK_EMAIL_LOG_SENT_EMAILS: false -CACHES: - celery: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: celery - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: '7200' - configuration: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: 78f87108afce - LOCATION: - - edx.devstack.memcached:11211 - course_structure_cache: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: course_structure - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: '604800' - default: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - ignore_exc: true - no_delay: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: default - LOCATION: - - edx.devstack.memcached:11211 - VERSION: '1' - general: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: general - LOCATION: - - edx.devstack.memcached:11211 - mongo_metadata_inheritance: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: mongo_metadata_inheritance - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: 300 - staticfiles: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: 78f87108afce_general - LOCATION: - - edx.devstack.memcached:11211 -CAS_ATTRIBUTE_CALLBACK: '' -CAS_EXTRA_LOGIN_PARAMS: '' -CAS_SERVER_URL: '' -CELERYBEAT_SCHEDULER: celery.beat:PersistentScheduler -CELERY_BROKER_HOSTNAME: localhost -CELERY_BROKER_PASSWORD: '' -CELERY_BROKER_TRANSPORT: redis -CELERY_BROKER_USER: '' -CELERY_BROKER_USE_SSL: false -CELERY_BROKER_VHOST: '' -CELERY_EVENT_QUEUE_TTL: null -CELERY_QUEUES: -- edx.cms.core.default -- edx.cms.core.high -CELERY_TIMEZONE: UTC -CERTIFICATE_TEMPLATE_LANGUAGES: - en: English - es: Español -CERT_QUEUE: certificates -CMS_BASE: edx.devstack.studio:18010 -CODE_JAIL: - limits: - CPU: 1 - FSIZE: 1048576 - PROXY: 0 - REALTIME: 3 - VMEM: 536870912 - python_bin: /edx/app/edxapp/venvs/edxapp-sandbox/bin/python - user: sandbox -COMMENTS_SERVICE_KEY: password -COMMENTS_SERVICE_URL: http://localhost:18080 -COMPREHENSIVE_THEME_DIRS: -- '' -COMPREHENSIVE_THEME_LOCALE_PATHS: [] -CONTACT_EMAIL: info@example.com -CONTENTSTORE: - ADDITIONAL_OPTIONS: {} - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: PRIMARY - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.contentstore.mongo.MongoContentStore - OPTIONS: - auth_source: '' - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - ssl: false - user: edxapp -CORS_ORIGIN_ALLOW_ALL: false -CORS_ORIGIN_WHITELIST: [] -COURSES_WITH_UNSAFE_CODE: [] -COURSE_ABOUT_VISIBILITY_PERMISSION: see_exists -COURSE_AUTHORING_MICROFRONTEND_URL: null -COURSE_CATALOG_API_URL: http://localhost:8008/api/v1 -COURSE_CATALOG_URL_ROOT: http://localhost:8008 -COURSE_CATALOG_VISIBILITY_PERMISSION: see_exists -COURSE_IMPORT_EXPORT_BUCKET: '' -CREDENTIALS_INTERNAL_SERVICE_URL: http://localhost:8005 -CREDENTIALS_PUBLIC_SERVICE_URL: http://localhost:8005 -CREDIT_PROVIDER_SECRET_KEYS: {} -CROSS_DOMAIN_CSRF_COOKIE_DOMAIN: '' -CROSS_DOMAIN_CSRF_COOKIE_NAME: '' -CSRF_COOKIE_SECURE: false -CSRF_TRUSTED_ORIGINS: [] -DASHBOARD_COURSE_LIMIT: null -DATABASES: - default: - ATOMIC_REQUESTS: true - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 - read_replica: - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 - student_module_history: - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp_csmh - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 -DATA_DIR: /edx/var/edxapp -DEFAULT_COURSE_VISIBILITY_IN_CATALOG: both -DEFAULT_FEEDBACK_EMAIL: feedback@example.com -DEFAULT_FILE_STORAGE: django.core.files.storage.FileSystemStorage -DEFAULT_FROM_EMAIL: registration@example.com -DEFAULT_JWT_ISSUER: - AUDIENCE: lms-key - ISSUER: http://edx.devstack.lms:18000/oauth2 - SECRET_KEY: lms-secret -DEFAULT_MOBILE_AVAILABLE: false -DEFAULT_SITE_THEME: '' -DEPRECATED_ADVANCED_COMPONENT_TYPES: [] -DJFS: - directory_root: /edx/var/edxapp/django-pyfs/static/django-pyfs - type: osfs - url_root: /static/django-pyfs -DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: PRIMARY - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp -ECOMMERCE_API_SIGNING_KEY: lms-secret -ECOMMERCE_API_URL: http://localhost:8002/api/v2 -ECOMMERCE_PUBLIC_URL_ROOT: http://localhost:8002 -EDXMKTG_USER_INFO_COOKIE_NAME: edx-user-info -EDX_PLATFORM_REVISION: master -ELASTIC_SEARCH_CONFIG: -- host: edx.devstack.elasticsearch - port: 9200 - use_ssl: false -EMAIL_BACKEND: django.core.mail.backends.smtp.EmailBackend -EMAIL_HOST: localhost -EMAIL_HOST_PASSWORD: '' -EMAIL_HOST_USER: '' -EMAIL_PORT: 25 -EMAIL_USE_TLS: false -ENABLE_COMPREHENSIVE_THEMING: false -ENTERPRISE_API_URL: http://edx.devstack.lms:18000/enterprise/api/v1 -ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS: {} -ENTERPRISE_SERVICE_WORKER_USERNAME: enterprise_worker -EVENT_TRACKING_SEGMENTIO_EMIT_WHITELIST: [] -EXAMS_API_URL: http://localhost:8740/api/v1 -EXTRA_MIDDLEWARE_CLASSES: [] -FACEBOOK_API_VERSION: v2.1 -FACEBOOK_APP_ID: FACEBOOK_APP_ID -FACEBOOK_APP_SECRET: FACEBOOK_APP_SECRET -FEATURES: - AUTH_USE_OPENID_PROVIDER: true - AUTOMATIC_AUTH_FOR_TESTING: false - CUSTOM_COURSES_EDX: false - ENABLE_BULK_ENROLLMENT_VIEW: false - ENABLE_COMBINED_LOGIN_REGISTRATION: true - ENABLE_CORS_HEADERS: false - ENABLE_COUNTRY_ACCESS: false - ENABLE_CREDIT_API: false - ENABLE_CREDIT_ELIGIBILITY: false - ENABLE_CROSS_DOMAIN_CSRF_COOKIE: false - ENABLE_CSMH_EXTENDED: true - ENABLE_DISCUSSION_HOME_PANEL: true - ENABLE_DISCUSSION_SERVICE: true - ENABLE_EDXNOTES: true - ENABLE_ENROLLMENT_RESET: false - ENABLE_EXPORT_GIT: false - ENABLE_GRADE_DOWNLOADS: true - ENABLE_LTI_PROVIDER: false - ENABLE_MKTG_SITE: false - ENABLE_MOBILE_REST_API: false - ENABLE_OAUTH2_PROVIDER: false - ENABLE_PUBLISHER: false - ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES: true - ENABLE_SPECIAL_EXAMS: false - ENABLE_SYSADMIN_DASHBOARD: false - ENABLE_THIRD_PARTY_AUTH: true - ENABLE_VIDEO_UPLOAD_PIPELINE: false - SHOW_FOOTER_LANGUAGE_SELECTOR: false - SHOW_HEADER_LANGUAGE_SELECTOR: false -FEEDBACK_SUBMISSION_EMAIL: '' -FERNET_KEYS: -- DUMMY KEY CHANGE BEFORE GOING TO PRODUCTION -FILE_UPLOAD_STORAGE_BUCKET_NAME: SET-ME-PLEASE (ex. bucket-name) -FILE_UPLOAD_STORAGE_PREFIX: submissions_attachments -FINANCIAL_REPORTS: - BUCKET: null - ROOT_PATH: sandbox - STORAGE_TYPE: localfs -GITHUB_REPO_ROOT: /edx/var/edxapp/data -GIT_REPO_EXPORT_DIR: /edx/var/edxapp/export_course_repos -GOOGLE_ANALYTICS_ACCOUNT: null -GRADES_DOWNLOAD: - BUCKET: '' - ROOT_PATH: '' - STORAGE_CLASS: django.core.files.storage.FileSystemStorage - STORAGE_KWARGS: - location: /tmp/edx-s3/grades - STORAGE_TYPE: '' -HELP_TOKENS_BOOKS: - course_author: http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course - learner: http://edx.readthedocs.io/projects/open-edx-learner-guide -ICP_LICENSE: null -ICP_LICENSE_INFO: {} -IDA_LOGOUT_URI_LIST: [] -ID_VERIFICATION_SUPPORT_LINK: '' -INTEGRATED_CHANNELS_API_CHUNK_TRANSMISSION_LIMIT: - SAP: 1 -JWT_AUTH: - JWT_AUDIENCE: lms-key - JWT_AUTH_COOKIE_HEADER_PAYLOAD: edx-jwt-cookie-header-payload - JWT_AUTH_COOKIE_SIGNATURE: edx-jwt-cookie-signature - JWT_ISSUER: http://edx.devstack.lms:18000/oauth2 - JWT_ISSUERS: - - AUDIENCE: lms-key - ISSUER: http://edx.devstack.lms:18000/oauth2 - SECRET_KEY: lms-secret - JWT_PUBLIC_SIGNING_JWK_SET: '' - JWT_SECRET_KEY: lms-secret - JWT_SIGNING_ALGORITHM: null -JWT_EXPIRATION: 30 -JWT_ISSUER: http://edx.devstack.lms:18000/oauth2 -JWT_PRIVATE_SIGNING_KEY: null -LANGUAGE_CODE: en -LANGUAGE_COOKIE: openedx-language-preference -LEARNER_PORTAL_URL_ROOT: https://learner-portal-edx.devstack.lms:18000 -LMS_BASE: edx.devstack.lms:18000 -LMS_INTERNAL_ROOT_URL: http://edx.devstack.lms:18000 -LMS_ROOT_URL: http://edx.devstack.lms:18000 -LOCAL_LOGLEVEL: INFO -LOGGING_ENV: sandbox -LOGIN_REDIRECT_WHITELIST: [] -LOG_DIR: /edx/var/log/edx -MAINTENANCE_BANNER_TEXT: Sample banner message -MEDIA_ROOT: /edx/var/edxapp/media/ -MEDIA_URL: /media/ -MKTG_URLS: {} -MKTG_URL_LINK_MAP: {} -MOBILE_STORE_ACE_URLS: {} -MODULESTORE: - default: - ENGINE: xmodule.modulestore.mixed.MixedModuleStore - OPTIONS: - mappings: {} - stores: - - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: PRIMARY - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore - NAME: split - OPTIONS: - default_class: xmodule.hidden_block.HiddenBlock - fs_root: /edx/var/edxapp/data - render_template: common.djangoapps.edxmako.shortcuts.render_to_string - - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: PRIMARY - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.modulestore.mongo.DraftMongoModuleStore - NAME: draft - OPTIONS: - default_class: xmodule.hidden_block.HiddenBlock - fs_root: /edx/var/edxapp/data - render_template: common.djangoapps.edxmako.shortcuts.render_to_string -ORA2_FILE_PREFIX: default_env-default_deployment/ora2 -PARSE_KEYS: {} -PARTNER_SUPPORT_EMAIL: '' -PASSWORD_POLICY_COMPLIANCE_ROLLOUT_CONFIG: - ENFORCE_COMPLIANCE_ON_LOGIN: false -PASSWORD_RESET_SUPPORT_LINK: '' -PAYMENT_SUPPORT_EMAIL: billing@example.com -PLATFORM_DESCRIPTION: Your Platform Description Here -PLATFORM_FACEBOOK_ACCOUNT: http://www.facebook.com/YourPlatformFacebookAccount -PLATFORM_NAME: Your Platform Name Here -PLATFORM_TWITTER_ACCOUNT: '@YourPlatformTwitterAccount' -POLICY_CHANGE_GRADES_ROUTING_KEY: edx.lms.core.default -SINGLE_LEARNER_COURSE_REGRADE_ROUTING_KEY: edx.lms.core.default -PREPEND_LOCALE_PATHS: [] -PRESS_EMAIL: press@example.com -PROCTORING_BACKENDS: - DEFAULT: 'null' - 'null': {} -PROCTORING_SETTINGS: {} -REGISTRATION_EXTRA_FIELDS: - city: hidden - confirm_email: hidden - country: required - gender: optional - goals: optional - honor_code: required - level_of_education: optional - mailing_address: hidden - terms_of_service: hidden - year_of_birth: optional -RETIRED_EMAIL_DOMAIN: retired.invalid -RETIRED_EMAIL_PREFIX: retired__user_ -RETIRED_USERNAME_PREFIX: retired__user_ -RETIRED_USER_SALTS: -- OVERRIDE ME WITH A RANDOM VALUE -- ROTATE SALTS BY APPENDING NEW VALUES -RETIREMENT_SERVICE_WORKER_USERNAME: retirement_worker -RETIREMENT_STATES: -- PENDING -- ERRORED -- ABORTED -- COMPLETE -SECRET_KEY: DUMMY KEY ONLY FOR TO DEVSTACK -SEGMENT_KEY: null -SERVER_EMAIL: sre@example.com -SESSION_COOKIE_DOMAIN: '' -SESSION_COOKIE_NAME: sessionid -SESSION_COOKIE_SECURE: false -SESSION_SAVE_EVERY_REQUEST: false -SITE_NAME: localhost -SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '' -SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT: {} -SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '' -SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT: {} -SOCIAL_MEDIA_FOOTER_URLS: {} -SOCIAL_SHARING_SETTINGS: - CERTIFICATE_FACEBOOK: false - CERTIFICATE_TWITTER: false - CUSTOM_COURSE_URLS: false - DASHBOARD_FACEBOOK: false - DASHBOARD_TWITTER: false -STATIC_ROOT_BASE: /edx/var/edxapp/staticfiles -STATIC_URL_BASE: /static/ -STUDIO_NAME: Studio -STUDIO_SHORT_NAME: Studio -SUPPORT_SITE_LINK: '' -SWIFT_AUTH_URL: null -SWIFT_AUTH_VERSION: null -SWIFT_KEY: null -SWIFT_REGION_NAME: null -SWIFT_TEMP_URL_DURATION: 1800 -SWIFT_TEMP_URL_KEY: null -SWIFT_TENANT_ID: null -SWIFT_TENANT_NAME: null -SWIFT_USERNAME: null -SWIFT_USE_TEMP_URLS: false -SYSLOG_SERVER: '' -SYSTEM_WIDE_ROLE_CLASSES: [] -TECH_SUPPORT_EMAIL: technical@example.com -TIME_ZONE: America/New_York -UNIVERSITY_EMAIL: university@example.com -USERNAME_REPLACEMENT_WORKER: OVERRIDE THIS WITH A VALID USERNAME -VIDEO_IMAGE_MAX_AGE: 31536000 -VIDEO_IMAGE_SETTINGS: - DIRECTORY_PREFIX: video-images/ - STORAGE_KWARGS: - location: /edx/var/edxapp/media/ - VIDEO_IMAGE_MAX_BYTES: 2097152 - VIDEO_IMAGE_MIN_BYTES: 2048 - BASE_URL: /media/ -VIDEO_TRANSCRIPTS_MAX_AGE: 31536000 -VIDEO_TRANSCRIPTS_SETTINGS: - DIRECTORY_PREFIX: video-transcripts/ - STORAGE_KWARGS: - location: /edx/var/edxapp/media/ - VIDEO_TRANSCRIPTS_MAX_BYTES: 3145728 - BASE_URL: /media/ -VIDEO_UPLOAD_PIPELINE: - BUCKET: '' - ROOT_PATH: '' -WIKI_ENABLED: true -XBLOCK_FS_STORAGE_BUCKET: null -XBLOCK_FS_STORAGE_PREFIX: null -XBLOCK_SETTINGS: {} -XQUEUE_INTERFACE: - basic_auth: - - edx - - edx - django_auth: - password: password - username: lms - url: http://edx.devstack.xqueue:18040 -X_FRAME_OPTIONS: DENY -YOUTUBE_API_KEY: PUT_YOUR_API_KEY_HERE -ZENDESK_API_KEY: '' -ZENDESK_CUSTOM_FIELDS: {} -ZENDESK_GROUP_ID_MAPPING: {} -ZENDESK_OAUTH_ACCESS_TOKEN: '' -ZENDESK_URL: '' -ZENDESK_USER: '' diff --git a/cms/envs/devstack_docker.py b/cms/envs/devstack_docker.py deleted file mode 100644 index 2eece814de..0000000000 --- a/cms/envs/devstack_docker.py +++ /dev/null @@ -1,3 +0,0 @@ -""" Overrides for Docker-based devstack. """ - -from .devstack import * # pylint: disable=wildcard-import, unused-wildcard-import diff --git a/cms/envs/devstack_optimized.py b/cms/envs/devstack_optimized.py deleted file mode 100644 index b7f4234466..0000000000 --- a/cms/envs/devstack_optimized.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Settings to run Studio in devstack using optimized static assets. - -This configuration changes Studio to use the optimized static assets generated for testing, -rather than picking up the files directly from the source tree. - -The following Paver command can be used to run Studio in optimized mode: - - paver devstack studio --optimized - -You can also generate the assets explicitly and then run Studio: - - paver update_assets cms --settings=test_static_optimized - paver devstack studio --settings=devstack_optimized --fast - -Note that changes to JavaScript assets will not be picked up automatically -as they are for non-optimized devstack. Instead, update_assets must be -invoked each time that changes have been made. -""" - -########################## Devstack settings ################################### - -from .devstack import * # pylint: disable=wildcard-import - -TEST_ROOT = REPO_ROOT / "test_root" - -############################ STATIC FILES ############################# - -# Enable debug so that static assets are served by Django -DEBUG = True - -# Set REQUIRE_DEBUG to false so that it behaves like production -REQUIRE_DEBUG = False - -# Fetch static files out of the pipeline's static root -STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage' - -# Serve static files at /static directly from the staticfiles directory under test root. -# Note: optimized files for testing are generated with settings from test_static_optimized -STATIC_URL = "/static/" -STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', -] -STATICFILES_DIRS = [ - (TEST_ROOT / "staticfiles" / "cms").abspath(), -] diff --git a/cms/envs/devstack_with_worker.py b/cms/envs/devstack_with_worker.py deleted file mode 100644 index 7b2819fe9d..0000000000 --- a/cms/envs/devstack_with_worker.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -This config file follows the devstack enviroment, but adds the -requirement of a celery worker running in the background to process -celery tasks. - -When testing locally, run lms/cms with this settings file as well, to test queueing -of tasks onto the appropriate workers. - -In two separate processes on devstack: - paver devstack studio --settings=devstack_with_worker - DJANGO_SETTINGS_MODULE=cms.envs.devstack_with_worker celery worker --app=cms.celery:APP -""" - -# We intentionally define lots of variables that aren't used, and -# want to import all variables from base settings files -# pylint: disable=wildcard-import, unused-wildcard-import -from cms.envs.devstack import * - -# Require a separate celery worker -CELERY_ALWAYS_EAGER = False -CLEAR_REQUEST_CACHE_ON_TASK_COMPLETION = True -BROKER_URL = 'redis://:password@edx.devstack.redis:6379/' diff --git a/cms/envs/openstack.py b/cms/envs/openstack.py deleted file mode 100644 index 8e35122c76..0000000000 --- a/cms/envs/openstack.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Settings for OpenStack deployments. -""" - -from .production import * # pylint: disable=wildcard-import, unused-wildcard-import - -SWIFT_AUTH_URL = AUTH_TOKENS.get('SWIFT_AUTH_URL') -SWIFT_AUTH_VERSION = AUTH_TOKENS.get('SWIFT_AUTH_VERSION', 1) -SWIFT_USERNAME = AUTH_TOKENS.get('SWIFT_USERNAME') -SWIFT_KEY = AUTH_TOKENS.get('SWIFT_KEY') -SWIFT_TENANT_NAME = AUTH_TOKENS.get('SWIFT_TENANT_NAME') -SWIFT_TENANT_ID = AUTH_TOKENS.get('SWIFT_TENANT_ID') -SWIFT_CONTAINER_NAME = FILE_UPLOAD_STORAGE_BUCKET_NAME -SWIFT_NAME_PREFIX = FILE_UPLOAD_STORAGE_PREFIX -SWIFT_USE_TEMP_URLS = AUTH_TOKENS.get('SWIFT_USE_TEMP_URLS', False) -SWIFT_TEMP_URL_KEY = AUTH_TOKENS.get('SWIFT_TEMP_URL_KEY') -SWIFT_TEMP_URL_DURATION = AUTH_TOKENS.get('SWIFT_TEMP_URL_DURATION', 1800) # seconds -SWIFT_CONTENT_LENGTH_FROM_FD = AUTH_TOKENS.get('SWIFT_CONTENT_LENGTH_FROM_FD', False) -SWIFT_LAZY_CONNECT = AUTH_TOKENS.get('SWIFT_LAZY_CONNECT', True) - -if AUTH_TOKENS.get('SWIFT_REGION_NAME'): - SWIFT_EXTRA_OPTIONS = {'region_name': AUTH_TOKENS['SWIFT_REGION_NAME']} - -if AUTH_TOKENS.get('DEFAULT_FILE_STORAGE'): - DEFAULT_FILE_STORAGE = AUTH_TOKENS.get('DEFAULT_FILE_STORAGE') -elif SWIFT_AUTH_URL and SWIFT_USERNAME and SWIFT_KEY: - DEFAULT_FILE_STORAGE = 'swift.storage.SwiftStorage' -else: - DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' - -# Use default file storage class set above for course import/export -COURSE_IMPORT_EXPORT_STORAGE = DEFAULT_FILE_STORAGE diff --git a/cms/envs/test_static_optimized.py b/cms/envs/test_static_optimized.py deleted file mode 100644 index c92d9a7262..0000000000 --- a/cms/envs/test_static_optimized.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Settings used when generating static assets for use in tests. - -Note: it isn't possible to have a single settings file, because Django doesn't -support both generating static assets to a directory and also serving static -from the same directory. -""" - -# Start with the common settings - - -from openedx.core.lib.derived import derive_settings -from openedx.core.lib.django_require.staticstorage import OptimizedCachedRequireJsStorage - -from .common import * # pylint: disable=wildcard-import, unused-wildcard-import - -# Use an in-memory database since this settings file is only used for updating assets -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'ATOMIC_REQUESTS': True, - }, - -} - - -######################### PIPELINE #################################### - -# Use RequireJS optimized storage -STATICFILES_STORAGE = f"{OptimizedCachedRequireJsStorage.__module__}.{OptimizedCachedRequireJsStorage.__name__}" - -# Revert to the default set of finders as we don't want to dynamically pick up files from the pipeline -STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'openedx.core.lib.xblock_pipeline.finder.XBlockPipelineFinder', -] - -# Redirect to the test_root folder within the repo -TEST_ROOT = REPO_ROOT / "test_root" -LOG_DIR = (TEST_ROOT / "log").abspath() - -# Store the static files under test root so that they don't overwrite existing static assets -STATIC_ROOT = (TEST_ROOT / "staticfiles" / "cms").abspath() -WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json" - -# Disable uglify when tests are running (used by build.js). -# 1. Uglify is by far the slowest part of the build process -# 2. Having full source code makes debugging tests easier for developers -os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none' - -########################## Derive Any Derived Settings ####################### - -derive_settings(__name__) diff --git a/cms/urls.py b/cms/urls.py index f2c2c8b31a..af10c6b619 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -264,7 +264,7 @@ if core_toggles.ENTRANCE_EXAMS.is_enabled(): # Enable Web/HTML Certificates if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'): from cms.djangoapps.contentstore.views.certificates import ( - certificate_activation_handler, + CertificateActivationAPIView, signatory_detail_handler, certificates_detail_handler, certificates_list_handler @@ -272,7 +272,7 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'): urlpatterns += [ re_path(fr'^certificates/activation/{settings.COURSE_KEY_PATTERN}/', - certificate_activation_handler, + CertificateActivationAPIView.as_view(), name='certificate_activation_handler'), re_path(r'^certificates/{}/(?P\d+)/signatories/(?P\d+)?$'.format( settings.COURSE_KEY_PATTERN), signatory_detail_handler, name='signatory_detail_handler'), diff --git a/common/djangoapps/entitlements/management/commands/update_entitlement_mode.py b/common/djangoapps/entitlements/management/commands/update_entitlement_mode.py index eeb26a26f4..3d7b988bde 100644 --- a/common/djangoapps/entitlements/management/commands/update_entitlement_mode.py +++ b/common/djangoapps/entitlements/management/commands/update_entitlement_mode.py @@ -21,7 +21,7 @@ class Command(BaseCommand): Example usage: # Change entitlement_mode for given order_number with course_uuid to new_mode: - $ ./manage.py lms --settings=devstack_docker update_entitlement_mode \ + $ ./manage.py lms --settings=devstack update_entitlement_mode \ ORDER_NUMBER_123,ORDER_NUMBER_456 1234567-0000-1111-2222-123456789012 professional """ help = dedent(__doc__).strip() diff --git a/common/djangoapps/student/management/commands/populate_created_on_site_user_attribute.py b/common/djangoapps/student/management/commands/populate_created_on_site_user_attribute.py index c980bbf171..32350d6d9e 100644 --- a/common/djangoapps/student/management/commands/populate_created_on_site_user_attribute.py +++ b/common/djangoapps/student/management/commands/populate_created_on_site_user_attribute.py @@ -19,7 +19,7 @@ class Command(BaseCommand): """ help = """This command back-populates domain of the site the user account was created on. Example: ./manage.py lms populate_created_on_site_user_attribute --users ,... - '--activation-keys ,... --site-domain --settings=devstack_docker""" + '--activation-keys ,... --site-domain --settings=devstack""" def add_arguments(self, parser): """ diff --git a/docs/conf.py b/docs/conf.py index dfee5aec0a..0e6fa560d5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -320,10 +320,7 @@ def update_settings_module(service='lms'): Set the "DJANGO_SETTINGS_MODULE" environment variable appropriately for the module sphinx-apidoc is about to be run on. """ - if os.environ.get('EDX_PLATFORM_SETTINGS') == 'devstack_docker': - settings_module = f'{service}.envs.devstack_docker' - else: - settings_module = f'{service}.envs.devstack' + settings_module = f'{service}.envs.devstack' os.environ['DJANGO_SETTINGS_MODULE'] = settings_module diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index eacf2424de..5006299451 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -287,6 +287,16 @@ class StudentModuleHistory(BaseStudentModuleHistory): student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.CASCADE) + def __repr__(self): + student_dict = { + "course_id": str(self.student_module.course_id), + "module_type": self.student_module.module_type, + "student_id": self.student_module.student_id, + "grade": self.grade, + } + + return f"StudentModuleHistory<{student_dict!r}>" + def __str__(self): return str(repr(self)) diff --git a/lms/djangoapps/discussion/rest_api/tasks.py b/lms/djangoapps/discussion/rest_api/tasks.py index 54517cf6ce..da2a4a7c6e 100644 --- a/lms/djangoapps/discussion/rest_api/tasks.py +++ b/lms/djangoapps/discussion/rest_api/tasks.py @@ -7,6 +7,7 @@ from celery import shared_task from django.contrib.auth import get_user_model from edx_django_utils.monitoring import set_code_owner_attribute from opaque_keys.edx.locator import CourseKey +from eventtracking import tracker from common.djangoapps.student.roles import CourseStaffRole, CourseInstructorRole from lms.djangoapps.courseware.courses import get_course_with_access @@ -92,12 +93,18 @@ def send_response_endorsed_notifications(thread_id, response_id, course_key_str, @shared_task @set_code_owner_attribute -def delete_course_post_for_user(user_id, username, course_ids): +def delete_course_post_for_user(user_id, username, course_ids, event_data=None): """ Deletes all posts for user in a course. """ + event_data = event_data or {} log.info(f"<> Deleting all posts for {username} in course {course_ids}") threads_deleted = Thread.delete_user_threads(user_id, course_ids) comments_deleted = Comment.delete_user_comments(user_id, course_ids) log.info(f"<> Deleted {threads_deleted} posts and {comments_deleted} comments for {username} " f"in course {course_ids}") + event_data.update({ + "number_of_posts_deleted": threads_deleted, + "number_of_comments_deleted": comments_deleted, + }) + tracker.emit('edx.discussion.bulk_delete_user_posts', event_data) diff --git a/lms/djangoapps/discussion/rest_api/views.py b/lms/djangoapps/discussion/rest_api/views.py index c5c20cde0f..46855d8385 100644 --- a/lms/djangoapps/discussion/rest_api/views.py +++ b/lms/djangoapps/discussion/rest_api/views.py @@ -1585,8 +1585,14 @@ class BulkDeleteUserPosts(DeveloperErrorViewMixin, APIView): thread_count = Thread.get_user_threads_count(user.id, course_ids) if execute_task: + event_data = { + "triggered_by": request.user.username, + "username": username, + "course_or_org": course_or_org, + "course_key": course_id, + } delete_course_post_for_user.apply_async( - args=(user.id, username, course_ids), + args=(user.id, username, course_ids, event_data), ) return Response( {"comment_count": comment_count, "thread_count": thread_count}, diff --git a/lms/envs/analytics_exporter.py b/lms/envs/analytics_exporter.py deleted file mode 100644 index a6b53a2711..0000000000 --- a/lms/envs/analytics_exporter.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Settings for running management commands for the Analytics Exporter. - -The Analytics Exporter jobs run edxapp management commands using production -settings and configuration, however they currently DO NOT use edxapp production -environments (such as edxapp Amazon AMIs or Docker images) where theme files -get installed. As a result we must disable comprehensive theming or else -startup checks from the theming app will throw an error due to missing themes. -""" - -from .production import * # pylint: disable=wildcard-import, unused-wildcard-import - -ENABLE_COMPREHENSIVE_THEMING = False diff --git a/lms/envs/devstack-experimental.yml b/lms/envs/devstack-experimental.yml deleted file mode 100644 index 6811a0e93d..0000000000 --- a/lms/envs/devstack-experimental.yml +++ /dev/null @@ -1,655 +0,0 @@ -# This file is an experimental extraction of /edx/etc/lms.yml from -# a LMS devstack container. -# -# When devstack is configured to use the new `openedx/` images -# instead of the old `edxops/edxapp` image, it will use this file -# as input to lms/envs/production.py (and, in turn, lms/envs/devstack.py). -# If you are using devstack with the `edxops/edxapp` image, though, -# this file is NOT used. -# -# Q. Should I update this file when I update devstack.py? -# A. You don't *have* to, because settings in devstack.py -# override these settings. But, it doesn't harm to also make them -# here in order to quell confusion. The hope is that we'll -# adopt OEP-45 eventually, which recommends against having -# a devstack.py at all. -# -# This is part of the effort to move our dev tools off of Ansible and -# Paver, described here: https://github.com/openedx/devstack/pull/866 -# TODO: If the effort described above is abandoned, then this file should -# probably be deleted. -ACCOUNT_MICROFRONTEND_URL: null -ACE_CHANNEL_DEFAULT_EMAIL: django_email -ACE_CHANNEL_SAILTHRU_API_KEY: '' -ACE_CHANNEL_SAILTHRU_API_SECRET: '' -ACE_CHANNEL_SAILTHRU_DEBUG: true -ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME: null -ACE_CHANNEL_TRANSACTIONAL_EMAIL: django_email -ACE_ENABLED_CHANNELS: -- django_email -ACE_ENABLED_POLICIES: -- bulk_email_optout -ACE_ROUTING_KEY: edx.lms.core.default -ACTIVATION_EMAIL_SUPPORT_LINK: '' -AFFILIATE_COOKIE_NAME: dev_affiliate_id -ALTERNATE_WORKER_QUEUES: cms -ANALYTICS_API_KEY: '' -ANALYTICS_API_URL: http://localhost:18100 -ANALYTICS_DASHBOARD_NAME: Your Platform Name Here Insights -ANALYTICS_DASHBOARD_URL: http://localhost:18110/courses -API_ACCESS_FROM_EMAIL: api-requests@example.com -API_ACCESS_MANAGER_EMAIL: api-access@example.com -API_DOCUMENTATION_URL: http://course-catalog-api-guide.readthedocs.io/en/latest/ -AUTH_DOCUMENTATION_URL: http://course-catalog-api-guide.readthedocs.io/en/latest/authentication/index.html -AUTH_PASSWORD_VALIDATORS: -- NAME: django.contrib.auth.password_validation.UserAttributeSimilarityValidator -- NAME: common.djangoapps.util.password_policy_validators.MinimumLengthValidator - OPTIONS: - min_length: 2 -- NAME: common.djangoapps.util.password_policy_validators.MaximumLengthValidator - OPTIONS: - max_length: 75 -AWS_ACCESS_KEY_ID: null -AWS_QUERYSTRING_AUTH: false -AWS_S3_CUSTOM_DOMAIN: SET-ME-PLEASE (ex. bucket-name.s3.amazonaws.com) -AWS_SECRET_ACCESS_KEY: null -AWS_SES_REGION_ENDPOINT: email.us-east-1.amazonaws.com -AWS_SES_REGION_NAME: us-east-1 -AWS_STORAGE_BUCKET_NAME: SET-ME-PLEASE (ex. bucket-name) -BASE_COOKIE_DOMAIN: localhost -BLOCK_STRUCTURES_SETTINGS: - COURSE_PUBLISH_TASK_DELAY: 30 - TASK_DEFAULT_RETRY_DELAY: 30 - TASK_MAX_RETRIES: 5 -BRANCH_IO_KEY: '' -BUGS_EMAIL: bugs@example.com -BULK_EMAIL_DEFAULT_FROM_EMAIL: no-reply@example.com -BULK_EMAIL_EMAILS_PER_TASK: 500 -BULK_EMAIL_LOG_SENT_EMAILS: false -BULK_EMAIL_ROUTING_KEY_SMALL_JOBS: edx.lms.core.default -CACHES: - celery: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: celery - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: '7200' - configuration: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: 78f87108afce - LOCATION: - - edx.devstack.memcached:11211 - course_structure_cache: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: course_structure - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: '604800' - default: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: default - LOCATION: - - edx.devstack.memcached:11211 - VERSION: '1' - general: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: general - LOCATION: - - edx.devstack.memcached:11211 - mongo_metadata_inheritance: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: mongo_metadata_inheritance - LOCATION: - - edx.devstack.memcached:11211 - TIMEOUT: 300 - staticfiles: - BACKEND: django.core.cache.backends.memcached.PyMemcacheCache - OPTIONS: - no_delay: true - ignore_exc: true - use_pooling: true - connect_timeout: 0.5 - KEY_FUNCTION: common.djangoapps.util.memcache.safe_key - KEY_PREFIX: 78f87108afce_general - LOCATION: - - edx.devstack.memcached:11211 -CAS_ATTRIBUTE_CALLBACK: '' -CAS_EXTRA_LOGIN_PARAMS: '' -CAS_SERVER_URL: '' -CELERYBEAT_SCHEDULER: celery.beat:PersistentScheduler -CELERY_BROKER_HOSTNAME: localhost -CELERY_BROKER_PASSWORD: '' -CELERY_BROKER_TRANSPORT: redis -CELERY_BROKER_USER: '' -CELERY_BROKER_USE_SSL: false -CELERY_BROKER_VHOST: '' -CELERY_EVENT_QUEUE_TTL: null -CELERY_QUEUES: -- edx.lms.core.default -- edx.lms.core.high -- edx.lms.core.high_mem -CELERY_TIMEZONE: UTC -CERTIFICATE_TEMPLATE_LANGUAGES: - en: English - es: Español -CERT_QUEUE: certificates -CMS_BASE: edx.devstack.studio:18010 -CODE_JAIL: - limits: - CPU: 1 - FSIZE: 1048576 - PROXY: 0 - REALTIME: 3 - VMEM: 536870912 - python_bin: /edx/app/edxapp/venvs/edxapp-sandbox/bin/python - user: sandbox -COMMENTS_SERVICE_KEY: password -COMMENTS_SERVICE_URL: http://localhost:18080 -COMPREHENSIVE_THEME_DIRS: -- '' -COMPREHENSIVE_THEME_LOCALE_PATHS: [] -CONTACT_EMAIL: info@example.com -CONTACT_MAILING_ADDRESS: SET-ME-PLEASE -CONTENTSTORE: - ADDITIONAL_OPTIONS: {} - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: SECONDARY_PREFERRED - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.contentstore.mongo.MongoContentStore - OPTIONS: - auth_source: '' - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - ssl: false - user: edxapp -CORS_ORIGIN_ALLOW_ALL: false -CORS_ORIGIN_WHITELIST: [] -COURSES_WITH_UNSAFE_CODE: [] -COURSE_ABOUT_VISIBILITY_PERMISSION: see_exists -COURSE_CATALOG_API_URL: http://localhost:8008/api/v1 -COURSE_CATALOG_URL_ROOT: http://localhost:8008 -COURSE_CATALOG_VISIBILITY_PERMISSION: see_exists -CREDENTIALS_INTERNAL_SERVICE_URL: http://localhost:8005 -CREDENTIALS_PUBLIC_SERVICE_URL: http://localhost:8005 -CREDIT_HELP_LINK_URL: '' -CREDIT_PROVIDER_SECRET_KEYS: {} -CROSS_DOMAIN_CSRF_COOKIE_DOMAIN: '' -CROSS_DOMAIN_CSRF_COOKIE_NAME: '' -CSRF_COOKIE_SECURE: false -CSRF_TRUSTED_ORIGINS: [] -DASHBOARD_COURSE_LIMIT: null -DATABASES: - default: - ATOMIC_REQUESTS: true - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 - read_replica: - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 - student_module_history: - CONN_MAX_AGE: 0 - ENGINE: django.db.backends.mysql - HOST: edx.devstack.mysql80 - NAME: edxapp_csmh - OPTIONS: - isolation_level: read committed - PASSWORD: password - PORT: '3306' - USER: edxapp001 -DATA_DIR: /edx/var/edxapp -DCS_SESSION_COOKIE_SAMESITE: Lax -DCS_SESSION_COOKIE_SAMESITE_FORCE_ALL: true -DEFAULT_COURSE_VISIBILITY_IN_CATALOG: both -DEFAULT_FEEDBACK_EMAIL: feedback@example.com -DEFAULT_FILE_STORAGE: django.core.files.storage.FileSystemStorage -DEFAULT_FROM_EMAIL: registration@example.com -DEFAULT_JWT_ISSUER: - AUDIENCE: lms-key - ISSUER: http://edx.devstack.lms:18000/oauth2 - SECRET_KEY: lms-secret -DEFAULT_MOBILE_AVAILABLE: false -DEFAULT_SITE_THEME: '' -DEPRECATED_ADVANCED_COMPONENT_TYPES: [] -DJFS: - directory_root: /edx/var/edxapp/django-pyfs/static/django-pyfs - type: osfs - url_root: /static/django-pyfs -DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: SECONDARY_PREFERRED - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp -ECOMMERCE_API_SIGNING_KEY: lms-secret -ECOMMERCE_API_URL: http://localhost:8002/api/v2 -ECOMMERCE_PUBLIC_URL_ROOT: http://localhost:8002 -EDXMKTG_USER_INFO_COOKIE_NAME: edx-user-info -EDXNOTES_INTERNAL_API: http://edx.devstack.edx_notes_api:18120/api/v1 -EDXNOTES_PUBLIC_API: http://localhost:18120/api/v1 -EDX_API_KEY: PUT_YOUR_API_KEY_HERE -EDX_PLATFORM_REVISION: master -ELASTIC_SEARCH_CONFIG: -- host: edx.devstack.elasticsearch - port: 9200 - use_ssl: false -EMAIL_BACKEND: django.core.mail.backends.smtp.EmailBackend -EMAIL_HOST: localhost -EMAIL_HOST_PASSWORD: '' -EMAIL_HOST_USER: '' -EMAIL_PORT: 25 -EMAIL_USE_TLS: false -ENABLE_COMPREHENSIVE_THEMING: false -ENTERPRISE_API_URL: http://edx.devstack.lms:18000/enterprise/api/v1 -ENTERPRISE_COURSE_ENROLLMENT_AUDIT_MODES: -- audit -- honor -ENTERPRISE_CUSTOMER_SUCCESS_EMAIL: customersuccess@edx.org -ENTERPRISE_ENROLLMENT_API_URL: http://edx.devstack.lms:18000/api/enrollment/v1/ -ENTERPRISE_INTEGRATIONS_EMAIL: enterprise-integrations@edx.org -ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS: {} -ENTERPRISE_SERVICE_WORKER_USERNAME: enterprise_worker -ENTERPRISE_SUPPORT_URL: '' -ENTERPRISE_TAGLINE: '' -EVENT_TRACKING_SEGMENTIO_EMIT_WHITELIST: [] -EXTRA_MIDDLEWARE_CLASSES: [] -FACEBOOK_API_VERSION: v2.1 -FACEBOOK_APP_ID: FACEBOOK_APP_ID -FACEBOOK_APP_SECRET: FACEBOOK_APP_SECRET -FEATURES: - AUTH_USE_OPENID_PROVIDER: true - AUTOMATIC_AUTH_FOR_TESTING: false - CUSTOM_COURSES_EDX: false - ENABLE_BULK_ENROLLMENT_VIEW: false - ENABLE_COMBINED_LOGIN_REGISTRATION: true - ENABLE_CORS_HEADERS: false - ENABLE_COUNTRY_ACCESS: false - ENABLE_CREDIT_API: false - ENABLE_CREDIT_ELIGIBILITY: false - ENABLE_CROSS_DOMAIN_CSRF_COOKIE: false - ENABLE_CSMH_EXTENDED: true - ENABLE_DISCUSSION_HOME_PANEL: true - ENABLE_DISCUSSION_SERVICE: true - ENABLE_EDXNOTES: true - ENABLE_ENROLLMENT_RESET: false - ENABLE_EXPORT_GIT: false - ENABLE_GRADE_DOWNLOADS: true - ENABLE_LTI_PROVIDER: false - ENABLE_MKTG_SITE: false - ENABLE_MOBILE_REST_API: false - ENABLE_OAUTH2_PROVIDER: false - ENABLE_PUBLISHER: false - ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES: true - ENABLE_SPECIAL_EXAMS: false - ENABLE_SYSADMIN_DASHBOARD: false - ENABLE_THIRD_PARTY_AUTH: true - ENABLE_VIDEO_UPLOAD_PIPELINE: false - SHOW_FOOTER_LANGUAGE_SELECTOR: false - SHOW_HEADER_LANGUAGE_SELECTOR: false -FEEDBACK_SUBMISSION_EMAIL: '' -FERNET_KEYS: -- DUMMY KEY CHANGE BEFORE GOING TO PRODUCTION -FILE_UPLOAD_STORAGE_BUCKET_NAME: SET-ME-PLEASE (ex. bucket-name) -FILE_UPLOAD_STORAGE_PREFIX: submissions_attachments -FINANCIAL_REPORTS: - BUCKET: null - ROOT_PATH: sandbox - STORAGE_TYPE: localfs -GITHUB_REPO_ROOT: /edx/var/edxapp/data -GIT_REPO_DIR: /edx/var/edxapp/course_repos -GOOGLE_ANALYTICS_ACCOUNT: null -GOOGLE_ANALYTICS_LINKEDIN: '' -GOOGLE_ANALYTICS_TRACKING_ID: '' -GOOGLE_SITE_VERIFICATION_ID: '' -GRADES_DOWNLOAD: - BUCKET: '' - ROOT_PATH: '' - STORAGE_CLASS: django.core.files.storage.FileSystemStorage - STORAGE_KWARGS: - location: /tmp/edx-s3/grades - STORAGE_TYPE: '' -HELP_TOKENS_BOOKS: - course_author: http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course - learner: http://edx.readthedocs.io/projects/open-edx-learner-guide -HTTPS: 'on' -ICP_LICENSE: null -ICP_LICENSE_INFO: {} -IDA_LOGOUT_URI_LIST: [] -ID_VERIFICATION_SUPPORT_LINK: '' -INTEGRATED_CHANNELS_API_CHUNK_TRANSMISSION_LIMIT: - SAP: 1 -JWT_AUTH: - JWT_AUDIENCE: lms-key - JWT_AUTH_COOKIE_HEADER_PAYLOAD: edx-jwt-cookie-header-payload - JWT_AUTH_COOKIE_SIGNATURE: edx-jwt-cookie-signature - JWT_ISSUER: http://edx.devstack.lms:18000/oauth2 - JWT_ISSUERS: - - AUDIENCE: lms-key - ISSUER: http://edx.devstack.lms:18000/oauth2 - SECRET_KEY: lms-secret - JWT_PRIVATE_SIGNING_JWK: None - JWT_PUBLIC_SIGNING_JWK_SET: '' - JWT_SECRET_KEY: lms-secret - JWT_SIGNING_ALGORITHM: null -JWT_EXPIRATION: 30 -JWT_ISSUER: http://edx.devstack.lms:18000/oauth2 -JWT_PRIVATE_SIGNING_KEY: null -LANGUAGE_CODE: en -LANGUAGE_COOKIE: openedx-language-preference -LEARNER_PORTAL_URL_ROOT: https://learner-portal-edx.devstack.lms:18000 -LEARNING_MICROFRONTEND_URL: null -LMS_BASE: edx.devstack.lms:18000 -LMS_INTERNAL_ROOT_URL: http://edx.devstack.lms:18000 -LMS_ROOT_URL: http://edx.devstack.lms:18000 -LOCAL_LOGLEVEL: INFO -LOGGING_ENV: sandbox -LOGIN_REDIRECT_WHITELIST: [] -LOG_DIR: /edx/var/log/edx -LTI_AGGREGATE_SCORE_PASSBACK_DELAY: 900 -LTI_USER_EMAIL_DOMAIN: lti.example.com -MAILCHIMP_NEW_USER_LIST_ID: null -MAINTENANCE_BANNER_TEXT: Sample banner message -MEDIA_ROOT: /edx/var/edxapp/media/ -MEDIA_URL: /media/ -MKTG_URLS: {} -MKTG_URL_LINK_MAP: {} -MOBILE_STORE_URLS: {} -MODULESTORE: - default: - ENGINE: xmodule.modulestore.mixed.MixedModuleStore - OPTIONS: - mappings: {} - stores: - - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: SECONDARY_PREFERRED - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore - NAME: split - OPTIONS: - default_class: xmodule.hidden_block.HiddenBlock - fs_root: /edx/var/edxapp/data - render_template: common.djangoapps.edxmako.shortcuts.render_to_string - - DOC_STORE_CONFIG: - authsource: '' - collection: modulestore - connectTimeoutMS: 2000 - db: edxapp - host: - - edx.devstack.mongo - password: password - port: 27017 - read_preference: PRIMARY - replicaSet: '' - socketTimeoutMS: 3000 - ssl: false - user: edxapp - ENGINE: xmodule.modulestore.mongo.DraftMongoModuleStore - NAME: draft - OPTIONS: - default_class: xmodule.hidden_block.HiddenBlock - fs_root: /edx/var/edxapp/data - render_template: common.djangoapps.edxmako.shortcuts.render_to_string -OAUTH_DELETE_EXPIRED: true -OAUTH_ENFORCE_SECURE: false -OAUTH_EXPIRE_CONFIDENTIAL_CLIENT_DAYS: 365 -OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS: 30 -OPTIMIZELY_PROJECT_ID: null -ORA2_FILE_PREFIX: default_env-default_deployment/ora2 -ORDER_HISTORY_MICROFRONTEND_URL: null -ORGANIZATIONS_AUTOCREATE: true -PAID_COURSE_REGISTRATION_CURRENCY: -- usd -- $ -PARENTAL_CONSENT_AGE_LIMIT: 13 -PARTNER_SUPPORT_EMAIL: '' -PASSWORD_POLICY_COMPLIANCE_ROLLOUT_CONFIG: - ENFORCE_COMPLIANCE_ON_LOGIN: false -PASSWORD_RESET_SUPPORT_LINK: '' -PAYMENT_SUPPORT_EMAIL: billing@example.com -PDF_RECEIPT_BILLING_ADDRESS: 'Enter your receipt billing - - address here. - - ' -PDF_RECEIPT_COBRAND_LOGO_PATH: '' -PDF_RECEIPT_DISCLAIMER_TEXT: 'ENTER YOUR RECEIPT DISCLAIMER TEXT HERE. - - ' -PDF_RECEIPT_FOOTER_TEXT: 'Enter your receipt footer text here. - - ' -PDF_RECEIPT_LOGO_PATH: '' -PDF_RECEIPT_TAX_ID: 00-0000000 -PDF_RECEIPT_TAX_ID_LABEL: fake Tax ID -PDF_RECEIPT_TERMS_AND_CONDITIONS: 'Enter your receipt terms and conditions here. - - ' -PLATFORM_DESCRIPTION: Your Platform Description Here -PLATFORM_FACEBOOK_ACCOUNT: http://www.facebook.com/YourPlatformFacebookAccount -PLATFORM_NAME: Your Platform Name Here -PLATFORM_TWITTER_ACCOUNT: '@YourPlatformTwitterAccount' -POLICY_CHANGE_GRADES_ROUTING_KEY: edx.lms.core.default -SINGLE_LEARNER_COURSE_REGRADE_ROUTING_KEY: edx.lms.core.default -PREPEND_LOCALE_PATHS: [] -PRESS_EMAIL: press@example.com -PROCTORING_BACKENDS: - DEFAULT: 'null' - 'null': {} -PROCTORING_SETTINGS: {} -PROFILE_IMAGE_BACKEND: - class: openedx.core.storage.OverwriteStorage - options: - base_url: /media/profile-images/ - location: /edx/var/edxapp/media/profile-images/ -PROFILE_IMAGE_HASH_SEED: placeholder_secret_key -PROFILE_IMAGE_MAX_BYTES: 1048576 -PROFILE_IMAGE_MIN_BYTES: 100 -PROFILE_IMAGE_SIZES_MAP: - full: 500 - large: 120 - medium: 50 - small: 30 -PROFILE_MICROFRONTEND_URL: null -PROGRAM_CERTIFICATES_ROUTING_KEY: edx.lms.core.default -PROGRAM_CONSOLE_MICROFRONTEND_URL: null -RECALCULATE_GRADES_ROUTING_KEY: edx.lms.core.default -REGISTRATION_EXTRA_FIELDS: - city: hidden - confirm_email: hidden - country: required - gender: optional - goals: optional - honor_code: required - level_of_education: optional - mailing_address: hidden - terms_of_service: hidden - year_of_birth: optional -RETIRED_EMAIL_DOMAIN: retired.invalid -RETIRED_EMAIL_PREFIX: retired__user_ -RETIRED_USERNAME_PREFIX: retired__user_ -RETIRED_USER_SALTS: -- OVERRIDE ME WITH A RANDOM VALUE -- ROTATE SALTS BY APPENDING NEW VALUES -RETIREMENT_SERVICE_WORKER_USERNAME: retirement_worker -RETIREMENT_STATES: -- PENDING -- ERRORED -- ABORTED -- COMPLETE -SECRET_KEY: DUMMY KEY ONLY FOR TO DEVSTACK -SEGMENT_KEY: null -SERVER_EMAIL: sre@example.com -SESSION_COOKIE_DOMAIN: '' -SESSION_COOKIE_NAME: sessionid -SESSION_COOKIE_SECURE: false -SESSION_SAVE_EVERY_REQUEST: false -SITE_NAME: localhost -SOCIAL_AUTH_OAUTH_SECRETS: '' -SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '' -SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT: {} -SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '' -SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT: {} -SOCIAL_MEDIA_FOOTER_URLS: {} -SOCIAL_SHARING_SETTINGS: - CERTIFICATE_FACEBOOK: false - CERTIFICATE_TWITTER: false - CUSTOM_COURSE_URLS: false - DASHBOARD_FACEBOOK: false - DASHBOARD_TWITTER: false -STATIC_ROOT_BASE: /edx/var/edxapp/staticfiles -STATIC_URL_BASE: /static/ -STUDIO_NAME: Studio -STUDIO_SHORT_NAME: Studio -SUPPORT_SITE_LINK: '' -SWIFT_AUTH_URL: null -SWIFT_AUTH_VERSION: null -SWIFT_KEY: null -SWIFT_REGION_NAME: null -SWIFT_TEMP_URL_DURATION: 1800 -SWIFT_TEMP_URL_KEY: null -SWIFT_TENANT_ID: null -SWIFT_TENANT_NAME: null -SWIFT_USERNAME: null -SWIFT_USE_TEMP_URLS: false -SYSLOG_SERVER: '' -SYSTEM_WIDE_ROLE_CLASSES: [] -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 -- social_core.backends.azuread.AzureADOAuth2 -- common.djangoapps.third_party_auth.appleid.AppleIdAuth -- common.djangoapps.third_party_auth.identityserver3.IdentityServer3 -- common.djangoapps.third_party_auth.saml.SAMLAuthBackend -- common.djangoapps.third_party_auth.lti.LTIAuthBackend -TIME_ZONE: America/New_York -TRACKING_SEGMENTIO_WEBHOOK_SECRET: '' -UNIVERSITY_EMAIL: university@example.com -USERNAME_REPLACEMENT_WORKER: OVERRIDE THIS WITH A VALID USERNAME -VERIFY_STUDENT: - DAYS_GOOD_FOR: 365 - EXPIRING_SOON_WINDOW: 28 -VIDEO_CDN_URL: - EXAMPLE_COUNTRY_CODE: http://example.com/edx/video?s3_url= -VIDEO_IMAGE_MAX_AGE: 31536000 -VIDEO_IMAGE_SETTINGS: - DIRECTORY_PREFIX: video-images/ - STORAGE_KWARGS: - location: /edx/var/edxapp/media/ - VIDEO_IMAGE_MAX_BYTES: 2097152 - VIDEO_IMAGE_MIN_BYTES: 2048 - BASE_URL: /media/ -VIDEO_TRANSCRIPTS_MAX_AGE: 31536000 -VIDEO_TRANSCRIPTS_SETTINGS: - DIRECTORY_PREFIX: video-transcripts/ - STORAGE_KWARGS: - location: /edx/var/edxapp/media/ - VIDEO_TRANSCRIPTS_MAX_BYTES: 3145728 - BASE_URL: /media/ -VIDEO_UPLOAD_PIPELINE: - BUCKET: '' - ROOT_PATH: '' -WIKI_ENABLED: true -WRITABLE_GRADEBOOK_URL: null -XBLOCK_FS_STORAGE_BUCKET: null -XBLOCK_FS_STORAGE_PREFIX: null -XBLOCK_SETTINGS: {} -XQUEUE_INTERFACE: - basic_auth: - - edx - - edx - django_auth: - password: password - username: lms - url: http://edx.devstack.xqueue:18040 -X_FRAME_OPTIONS: DENY -YOUTUBE_API_KEY: PUT_YOUR_API_KEY_HERE -ZENDESK_API_KEY: '' -ZENDESK_CUSTOM_FIELDS: {} -ZENDESK_GROUP_ID_MAPPING: {} -ZENDESK_OAUTH_ACCESS_TOKEN: '' -ZENDESK_URL: '' -ZENDESK_USER: '' diff --git a/lms/envs/devstack_docker.py b/lms/envs/devstack_docker.py deleted file mode 100644 index 7ac0525113..0000000000 --- a/lms/envs/devstack_docker.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Left over environment file from before the transition of devstack from -vagrant to docker was complete. - -This file should no longer be used, and is only around in case something -still refers to it. -""" - -from .devstack import * # pylint: disable=wildcard-import, unused-wildcard-import diff --git a/lms/envs/devstack_optimized.py b/lms/envs/devstack_optimized.py deleted file mode 100644 index 415e0e6c29..0000000000 --- a/lms/envs/devstack_optimized.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Settings to run LMS in devstack using optimized static assets. - -This configuration changes LMS to use the optimized static assets generated for testing, -rather than picking up the files directly from the source tree. - -The following Paver command can be used to run LMS in optimized mode: - - paver devstack lms --optimized - -You can also generate the assets explicitly and then run Studio: - - paver update_assets lms --settings=test_static_optimized - paver devstack lms --settings=devstack_optimized --fast - -Note that changes to JavaScript assets will not be picked up automatically -as they are for non-optimized devstack. Instead, update_assets must be -invoked each time that changes have been made. -""" - - -########################## Devstack settings ################################### - -from .devstack import * # pylint: disable=wildcard-import - -TEST_ROOT = REPO_ROOT / "test_root" - -############################ STATIC FILES ############################# - -# Enable debug so that static assets are served by Django -DEBUG = True - -# Set REQUIRE_DEBUG to false so that it behaves like production -REQUIRE_DEBUG = False - -# Fetch static files out of the pipeline's static root -STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage' - -# Serve static files at /static directly from the staticfiles directory under test root. -# Note: optimized files for testing are generated with settings from test_static_optimized -STATIC_URL = "/static/" -STATICFILES_FINDERS = ['django.contrib.staticfiles.finders.FileSystemFinder'] -STATICFILES_DIRS = [ - (TEST_ROOT / "staticfiles" / "lms").abspath(), -] diff --git a/lms/envs/devstack_with_worker.py b/lms/envs/devstack_with_worker.py deleted file mode 100644 index 4c865c629a..0000000000 --- a/lms/envs/devstack_with_worker.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This config file follows the devstack environment, but adds the -requirement of a celery worker running in the background to process -celery tasks. - -When testing locally, run lms/cms with this settings file as well, to test queueing -of tasks onto the appropriate workers. - -In two separate processes on devstack: - paver devstack lms --settings=devstack_with_worker - DJANGO_SETTINGS_MODULE=lms.envs.devstack_with_worker celery worker --app=lms.celery:APP -""" - - -# We intentionally define lots of variables that aren't used, and -# want to import all variables from base settings files -# pylint: disable=wildcard-import, unused-wildcard-import -from lms.envs.devstack import * - -# Require a separate celery worker -CELERY_ALWAYS_EAGER = False -CLEAR_REQUEST_CACHE_ON_TASK_COMPLETION = True -BROKER_URL = 'redis://:password@edx.devstack.redis:6379/' diff --git a/lms/envs/docker-production.py b/lms/envs/docker-production.py deleted file mode 100644 index 17a8b7c71f..0000000000 --- a/lms/envs/docker-production.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Specific overrides to the base prod settings for a docker production deployment. -""" - -from .production import * # pylint: disable=wildcard-import, unused-wildcard-import - -from openedx.core.lib.logsettings import get_docker_logger_config - -LOGGING = get_docker_logger_config() diff --git a/lms/envs/openstack.py b/lms/envs/openstack.py deleted file mode 100644 index d19fdb9c44..0000000000 --- a/lms/envs/openstack.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Settings for OpenStack deployments. -""" - -from .production import * # pylint: disable=wildcard-import, unused-wildcard-import - -SWIFT_AUTH_URL = AUTH_TOKENS.get('SWIFT_AUTH_URL') -SWIFT_AUTH_VERSION = AUTH_TOKENS.get('SWIFT_AUTH_VERSION', 1) -SWIFT_USERNAME = AUTH_TOKENS.get('SWIFT_USERNAME') -SWIFT_KEY = AUTH_TOKENS.get('SWIFT_KEY') -SWIFT_TENANT_NAME = AUTH_TOKENS.get('SWIFT_TENANT_NAME') -SWIFT_TENANT_ID = AUTH_TOKENS.get('SWIFT_TENANT_ID') -SWIFT_CONTAINER_NAME = FILE_UPLOAD_STORAGE_BUCKET_NAME -SWIFT_NAME_PREFIX = FILE_UPLOAD_STORAGE_PREFIX -SWIFT_USE_TEMP_URLS = AUTH_TOKENS.get('SWIFT_USE_TEMP_URLS', False) -SWIFT_TEMP_URL_KEY = AUTH_TOKENS.get('SWIFT_TEMP_URL_KEY') -SWIFT_TEMP_URL_DURATION = AUTH_TOKENS.get('SWIFT_TEMP_URL_DURATION', 1800) # seconds -SWIFT_CONTENT_TYPE_FROM_FD = AUTH_TOKENS.get('SWIFT_CONTENT_TYPE_FROM_FD', True) -SWIFT_CONTENT_LENGTH_FROM_FD = AUTH_TOKENS.get('SWIFT_CONTENT_LENGTH_FROM_FD', False) -SWIFT_LAZY_CONNECT = AUTH_TOKENS.get('SWIFT_LAZY_CONNECT', True) - -if AUTH_TOKENS.get('SWIFT_REGION_NAME'): - SWIFT_EXTRA_OPTIONS = {'region_name': AUTH_TOKENS['SWIFT_REGION_NAME']} - -if AUTH_TOKENS.get('DEFAULT_FILE_STORAGE'): - DEFAULT_FILE_STORAGE = AUTH_TOKENS.get('DEFAULT_FILE_STORAGE') -elif SWIFT_AUTH_URL and SWIFT_USERNAME and SWIFT_KEY: - DEFAULT_FILE_STORAGE = 'swift.storage.SwiftStorage' -else: - DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' - -ORA2_FILEUPLOAD_BACKEND = "django" diff --git a/lms/envs/static.py b/lms/envs/static.py deleted file mode 100644 index ab0bea3b48..0000000000 --- a/lms/envs/static.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -This config file runs the simplest dev environment using sqlite, and db-based -sessions. Assumes structure: - -/envroot/ - /db # This is where it'll write the database file - /edx-platform # The location of this repo - /log # Where we're going to write log files -""" - -# We intentionally define lots of variables that aren't used, and -# want to import all variables from base settings files -# pylint: disable=wildcard-import, unused-wildcard-import - - -from openedx.core.lib.derived import derive_settings -from openedx.core.lib.logsettings import get_logger_config - -from .common import * - -STATIC_GRAB = True - -LOGGING = get_logger_config(ENV_ROOT / "log", - logging_env="dev") - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ENV_ROOT / "db" / "edx.db", - 'ATOMIC_REQUESTS': True, - }, - 'student_module_history': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ENV_ROOT / "db" / "student_module_history.db", - 'ATOMIC_REQUESTS': True, - } -} - -CACHES = { - # This is the cache used for most things. - # In staging/prod envs, the sessions also live here. - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'edx_loc_mem_cache', - 'KEY_FUNCTION': 'common.djangoapps.util.memcache.safe_key', - }, - - # The general cache is what you get if you use our util.cache. It's used for - # things like caching the course.xml file for different A/B test groups. - # We set it to be a DummyCache to force reloading of course.xml in dev. - # In staging environments, we would grab VERSION from data uploaded by the - # push process. - 'general': { - 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', - 'KEY_PREFIX': 'general', - 'VERSION': 4, - 'KEY_FUNCTION': 'common.djangoapps.util.memcache.safe_key', - } -} - -# Dummy secret key for dev -SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' - -############################ FILE UPLOADS (for discussion forums) ############################# -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -MEDIA_ROOT = ENV_ROOT / "uploads" -MEDIA_URL = "/discussion/upfiles/" -FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads" -FILE_UPLOAD_HANDLERS = [ - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', -] - -########################## Derive Any Derived Settings ####################### - -derive_settings(__name__) diff --git a/lms/envs/test_static_optimized.py b/lms/envs/test_static_optimized.py deleted file mode 100644 index b57276b040..0000000000 --- a/lms/envs/test_static_optimized.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Settings used when generating static assets for use in tests. - -Note: it isn't possible to have a single settings file, because Django doesn't -support both generating static assets to a directory and also serving static -from the same directory. -""" - -# Start with the common settings - - -from openedx.core.lib.derived import derive_settings -from openedx.core.lib.django_require.staticstorage import OptimizedCachedRequireJsStorage - -from .common import * # pylint: disable=wildcard-import, unused-wildcard-import - -# Use an in-memory database since this settings file is only used for updating assets -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'ATOMIC_REQUESTS': True, - }, - 'student_module_history': { - 'ENGINE': 'django.db.backends.sqlite3', - }, -} - -# Provide a dummy XQUEUE_INTERFACE setting as LMS expects it to exist on start up -XQUEUE_INTERFACE = { - "url": "https://sandbox-xqueue.edx.org", - "django_auth": { - "username": "lms", - "password": "***REMOVED***" - }, - "basic_auth": ('anant', 'agarwal'), -} - -PROCTORING_BACKENDS = { - 'DEFAULT': 'mock', - 'mock': {}, - 'mock_proctoring_without_rules': {}, -} - -######################### PIPELINE #################################### - -# Use RequireJS optimized storage -STATICFILES_STORAGE = f"{OptimizedCachedRequireJsStorage.__module__}.{OptimizedCachedRequireJsStorage.__name__}" - -# Revert to the default set of finders as we don't want to dynamically pick up files from the pipeline -STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'openedx.core.lib.xblock_pipeline.finder.XBlockPipelineFinder', -] - -# Redirect to the test_root folder within the repo -TEST_ROOT = REPO_ROOT / "test_root" -LOG_DIR = (TEST_ROOT / "log").abspath() - -# Store the static files under test root so that they don't overwrite existing static assets -STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath() -WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json" - -# Disable uglify when tests are running (used by build.js). -# 1. Uglify is by far the slowest part of the build process -# 2. Having full source code makes debugging tests easier for developers -os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none' - -########################## Derive Any Derived Settings ####################### - -derive_settings(__name__) diff --git a/manage.py b/manage.py index 07fa6de2f6..9b8518937d 100755 --- a/manage.py +++ b/manage.py @@ -39,7 +39,7 @@ def parse_args(): lms.add_argument( '--settings', help="Which django settings module to use under lms.envs. If not provided, the DJANGO_SETTINGS_MODULE " - "environment variable will be used if it is set, otherwise it will default to lms.envs.devstack_docker") + "environment variable will be used if it is set, otherwise it will default to lms.envs.devstack") lms.add_argument( '--service-variant', choices=['lms', 'lms-xml', 'lms-preview'], @@ -48,7 +48,7 @@ def parse_args(): lms.set_defaults( help_string=lms.format_help(), settings_base='lms/envs', - default_settings='lms.envs.devstack_docker', + default_settings='lms.envs.devstack', ) cms = subparsers.add_parser( @@ -60,12 +60,12 @@ def parse_args(): cms.add_argument( '--settings', help="Which django settings module to use under cms.envs. If not provided, the DJANGO_SETTINGS_MODULE " - "environment variable will be used if it is set, otherwise it will default to cms.envs.devstack_docker") + "environment variable will be used if it is set, otherwise it will default to cms.envs.devstack") cms.add_argument('-h', '--help', action='store_true', help='show this help message and exit') cms.set_defaults( help_string=cms.format_help(), settings_base='cms/envs', - default_settings='cms.envs.devstack_docker', + default_settings='cms.envs.devstack', service_variant='cms', ) diff --git a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py index f79ead24d9..7d28ca5197 100644 --- a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py +++ b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py @@ -41,11 +41,11 @@ class Command(BaseCommand): Example usage: # Process all certs/grades changes for a given course: - $ ./manage.py lms --settings=devstack_docker notify_credentials \ + $ ./manage.py lms --settings=devstack notify_credentials \ --courses course-v1:edX+DemoX+Demo_Course # Process all certs/grades changes in a given time range: - $ ./manage.py lms --settings=devstack_docker notify_credentials \ + $ ./manage.py lms --settings=devstack notify_credentials \ --start-date 2018-06-01 --end-date 2018-07-31 A Dry Run will produce output that looks like: diff --git a/openedx/core/djangoapps/notifications/management/commands/fix_mixed_email_cadence.py b/openedx/core/djangoapps/notifications/management/commands/fix_mixed_email_cadence.py new file mode 100644 index 0000000000..cf1c5c0c5a --- /dev/null +++ b/openedx/core/djangoapps/notifications/management/commands/fix_mixed_email_cadence.py @@ -0,0 +1,57 @@ +""" +Management command to fix NotificationPreference records with invalid 'Mixed' email_cadence values +created during migration. +""" + +import logging +from django.core.management.base import BaseCommand + +from openedx.core.djangoapps.notifications.models import NotificationPreference + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Management command to identify and correct NotificationPreference records + with an invalid 'Mixed' value in the email_cadence field. + + By default, the command runs in dry-run mode and only logs the count of + affected records. Use the `--fix` flag to replace all 'Mixed' values with + 'Daily', ensuring data consistency with defined model choices. + Invoke with: + python manage.py [lms] fix_mixed_email_cadence --fix + """ + help = ( + "Identifies NotificationPreference records with 'Mixed' as email_cadence " + "and optionally replaces it with a valid value (default: 'Daily')." + ) + + def add_arguments(self, parser): + parser.add_argument( + '--fix', + action='store_true', + help='Apply the fix by replacing "Mixed" with "Daily". Default is dry-run mode.' + ) + + def handle(self, *args, **options): + fix_mode = options['fix'] + invalid_records = NotificationPreference.objects.filter(email_cadence='Mixed') + count = invalid_records.count() + + if count == 0: + logger.info("No records found with invalid 'Mixed' value in email_cadence.") + return + + logger.info(f"Found {count} NotificationPreference records with 'Mixed' email_cadence.") + + if fix_mode: + updated_count = invalid_records.update( + email_cadence=NotificationPreference.EmailCadenceChoices.DAILY + ) + logger.info(f"Successfully updated {updated_count} records. 'Mixed' replaced with 'Daily'.") + else: + logger.warning( + "Dry-run mode: no changes were made.\n" + "To apply changes, re-run the command with the --fix flag." + ) diff --git a/openedx/core/djangoapps/schedules/docs/README.rst b/openedx/core/djangoapps/schedules/docs/README.rst index 04982d8971..3004704c9c 100644 --- a/openedx/core/djangoapps/schedules/docs/README.rst +++ b/openedx/core/djangoapps/schedules/docs/README.rst @@ -130,21 +130,21 @@ can use: :: - ./manage.py lms --settings devstack_docker send_recurring_nudge example.com + ./manage.py lms --settings devstack send_recurring_nudge example.com You can override the “current date” when running a command. The app will run, using the date you specify as its "today": :: - ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --date 2017-11-13 + ./manage.py lms --settings devstack send_recurring_nudge example.com --date 2017-11-13 If the app is paired with Sailthru, you can override which email addresses the app sends to. The app will send all emails to the address you specify: :: - ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --override-recipient-email developer@example.com + ./manage.py lms --settings devstack send_recurring_nudge example.com --override-recipient-email developer@example.com These management commands are meant to be run daily. We schedule them to run automatically in a Jenkins job. You can use a similar automation @@ -419,7 +419,7 @@ To begin using Litmus, follow these steps: :: - ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --override-recipient-email PUT-LITMUS-ADDRESS-HERE + ./manage.py lms --settings devstack send_recurring_nudge example.com --override-recipient-email PUT-LITMUS-ADDRESS-HERE Using the Litmus Browser Extenstion to test emails saved as local files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/openedx/core/djangoapps/util/checks.py b/openedx/core/djangoapps/util/checks.py index bcde2fe620..9d06dd58b9 100644 --- a/openedx/core/djangoapps/util/checks.py +++ b/openedx/core/djangoapps/util/checks.py @@ -7,13 +7,7 @@ from django.core import checks _DEVSTACK_SETTINGS_MODULES = [ "lms.envs.devstack", - "lms.envs.devstack_docker", - "lms.envs.devstack_optimized", - "lms.envs.devstack_with_worker", "cms.envs.devstack", - "cms.envs.devstack_docker", - "cms.envs.devstack_optimized", - "cms.envs.devstack_with_worker", ] @@ -26,7 +20,7 @@ def warn_if_devstack_settings(**kwargs): return [ checks.Warning( "Open edX Devstack is deprecated, so the Django settings module you are using " - f"({settings.SETTINGS_MODULE}) will be removed from openedx/edx-platform by October 2024. " + f"({settings.SETTINGS_MODULE}) will be removed from openedx/edx-platform in 2025. " "Please either migrate off of Devstack, or modify your Devstack fork to work with an externally-" "managed Django settings file. " "For details and discussion, see: https://github.com/openedx/public-engineering/issues/247.", diff --git a/requirements/constraints.txt b/requirements/constraints.txt index f23f3c658d..bef19ed18b 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -51,7 +51,7 @@ django-stubs<6 # 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==6.2.11 +edx-enterprise==6.2.13 # Date: 2023-07-26 # Our legacy Sass code is incompatible with anything except this ancient libsass version. @@ -70,7 +70,7 @@ numpy<2.0.0 # Date: 2023-09-18 # pinning this version to avoid updates while the library is being developed # Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269 -openedx-learning==0.26.0 +openedx-learning==0.27.0 # Date: 2023-11-29 # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index f145f8eda1..debd45915f 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -473,7 +473,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.2.11 +edx-enterprise==6.2.13 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -566,7 +566,7 @@ enmerkar==0.7.1 # via enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/kernel.in -enterprise-integrated-channels==0.1.11 +enterprise-integrated-channels==0.1.13 # via -r requirements/edx/bundled.in event-tracking==3.3.0 # via @@ -840,7 +840,7 @@ openedx-filters==2.1.0 # ora2 openedx-forum==0.3.0 # via -r requirements/edx/kernel.in -openedx-learning==0.26.0 +openedx-learning==0.27.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -1174,7 +1174,9 @@ text-unidecode==1.3 tinycss2==1.4.0 # via bleach tomlkit==0.13.2 - # via snowflake-connector-python + # via + # openedx-learning + # snowflake-connector-python tqdm==4.67.1 # via # nltk diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 7d2765a42f..8f2eda3158 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -750,7 +750,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.2.11 +edx-enterprise==6.2.13 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -879,7 +879,7 @@ enmerkar-underscore==2.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -enterprise-integrated-channels==0.1.11 +enterprise-integrated-channels==0.1.13 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1400,7 +1400,7 @@ openedx-forum==0.3.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -openedx-learning==0.26.0 +openedx-learning==0.27.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -2104,6 +2104,7 @@ tomlkit==0.13.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt + # openedx-learning # pylint # snowflake-connector-python tox==4.26.0 diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index d3df369953..f7b686081c 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -555,7 +555,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.2.11 +edx-enterprise==6.2.13 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -653,7 +653,7 @@ enmerkar==0.7.1 # enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/base.txt -enterprise-integrated-channels==0.1.11 +enterprise-integrated-channels==0.1.13 # via -r requirements/edx/base.txt event-tracking==3.3.0 # via @@ -1005,7 +1005,7 @@ openedx-filters==2.1.0 # ora2 openedx-forum==0.3.0 # via -r requirements/edx/base.txt -openedx-learning==0.26.0 +openedx-learning==0.27.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1474,6 +1474,7 @@ tinycss2==1.4.0 tomlkit==0.13.2 # via # -r requirements/edx/base.txt + # openedx-learning # snowflake-connector-python tqdm==4.67.1 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 1961234b01..9c48c3c00a 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -580,7 +580,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.2.11 +edx-enterprise==6.2.13 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -680,7 +680,7 @@ enmerkar==0.7.1 # enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/base.txt -enterprise-integrated-channels==0.1.11 +enterprise-integrated-channels==0.1.13 # via -r requirements/edx/base.txt event-tracking==3.3.0 # via @@ -1064,7 +1064,7 @@ openedx-filters==2.1.0 # ora2 openedx-forum==0.3.0 # via -r requirements/edx/base.txt -openedx-learning==0.26.0 +openedx-learning==0.27.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1563,6 +1563,7 @@ tinycss2==1.4.0 tomlkit==0.13.2 # via # -r requirements/edx/base.txt + # openedx-learning # pylint # snowflake-connector-python tox==4.26.0 diff --git a/xmodule/template_block.py b/xmodule/template_block.py index cdf83a5566..37d2fac905 100644 --- a/xmodule/template_block.py +++ b/xmodule/template_block.py @@ -1,24 +1,21 @@ """ Template block """ - +import logging from string import Template -from xblock.core import XBlock from lxml import etree from web_fragments.fragment import Fragment +from xblock.core import XBlock + from xmodule.editing_block import EditingMixin +from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.raw_block import RawMixin -from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment -from xmodule.x_module import ( - ResourceTemplates, - shim_xmodule_js, - XModuleMixin, - XModuleToXBlockMixin, -) +from xmodule.util.builtin_assets import add_css_to_fragment, add_webpack_js_to_fragment +from xmodule.x_module import ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, shim_xmodule_js from xmodule.xml_block import XmlMixin -from openedx.core.djangolib.markup import Text +log = logging.getLogger(__name__) class CustomTagTemplateBlock( # pylint: disable=abstract-method @@ -76,8 +73,10 @@ class CustomTagBlock(CustomTagTemplateBlock): # pylint: disable=abstract-method def render_template(self, system, xml_data): '''Render the template, given the definition xml_data''' + if not xml_data: + return "Please set the template for this custom tag." xmltree = etree.fromstring(xml_data) - if 'impl' in xmltree.attrib: + if 'impl' in xmltree.attrib and xmltree.attrib['impl']: template_name = xmltree.attrib['impl'] else: # VS[compat] backwards compatibility with old nested customtag structure @@ -86,16 +85,19 @@ class CustomTagBlock(CustomTagTemplateBlock): # pylint: disable=abstract-method template_name = child_impl.text else: # TODO (vshnayder): better exception type - raise Exception("Could not find impl attribute in customtag {}" - .format(self.location)) + return Template("Could not find impl attribute in customtag {}").safe_substitute({}) params = dict(list(xmltree.items())) # cdodge: look up the template as a module template_loc = self.location.replace(category='custom_tag_template', name=template_name) + try: + template_block = system.get_block(template_loc) + template_block_data = template_block.data + except ItemNotFoundError as ex: + template_block_data = f"Could not find template block for custom tag with Id {template_name}" + log.info(template_block_data) - template_block = system.get_block(template_loc) - template_block_data = template_block.data template = Template(template_block_data) return template.safe_substitute(params) @@ -120,8 +122,7 @@ class CustomTagBlock(CustomTagTemplateBlock): # pylint: disable=abstract-method class TranslateCustomTagBlock( # pylint: disable=abstract-method - XModuleToXBlockMixin, - XModuleMixin, + CustomTagBlock, ): """ Converts olx of the form `<$custom_tag attr="" attr=""/>` to CustomTagBlock @@ -129,19 +130,20 @@ class TranslateCustomTagBlock( # pylint: disable=abstract-method """ resources_dir = None - @classmethod - def parse_xml(cls, node, runtime, _keys): - """ - Transforms the xml_data from <$custom_tag attr="" attr=""/> to - - """ - - runtime.error_tracker(Text('WARNING: the <{tag}> tag is deprecated. ' - 'Instead, use . ') - .format(tag=node.tag)) + def render_template(self, system, xml_data): + xml_string = "" + if xml_data: + xmltree = etree.fromstring(xml_data) + xmltree = self.replace_xml(xmltree) + xml_string = etree.tostring(xmltree, pretty_print=True).decode("utf-8") + return super().render_template(system, xml_string or xml_data) + def replace_xml(self, node): + """ + Replaces the xml_data from <$custom_tag attr="" attr=""/> to + . + """ tag = node.tag node.tag = 'customtag' node.attrib['impl'] = tag - - return runtime.process_xml(etree.tostring(node)) + return node