From 3f15d1f2ad24982bbd1e3b01282202540e2f6d70 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 17 Jul 2024 13:41:43 -0400 Subject: [PATCH 01/14] build: Updating workflow `add-remove-label-on-comment.yml`. The .github/workflows/add-remove-label-on-comment.yml workflow is missing or needs an update to stay in sync with the current standard for this workflow as defined in the `.github` repo of the `openedx` GitHub org. --- .github/workflows/add-remove-label-on-comment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/add-remove-label-on-comment.yml b/.github/workflows/add-remove-label-on-comment.yml index a658064f09f0..0f369db7d293 100644 --- a/.github/workflows/add-remove-label-on-comment.yml +++ b/.github/workflows/add-remove-label-on-comment.yml @@ -17,3 +17,4 @@ on: jobs: add_remove_labels: uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master + From b43834979535095eaf790d39eb7ef87a1c503e99 Mon Sep 17 00:00:00 2001 From: Eemaan Amir <57627710+eemaanamir@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:56:05 +0500 Subject: [PATCH 02/14] fix: rephrasing and removing bugs from response endorsed notifications (#35136) * fix: rephrasing and removing bugs from response endorsed notifications * test: updated test for response endorsed notifications --- lms/djangoapps/discussion/rest_api/tasks.py | 7 ++++--- lms/djangoapps/discussion/rest_api/tests/test_tasks.py | 4 ++-- openedx/core/djangoapps/notifications/base_notification.py | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lms/djangoapps/discussion/rest_api/tasks.py b/lms/djangoapps/discussion/rest_api/tasks.py index 5f0bf8ed40ea..45bf41fe905f 100644 --- a/lms/djangoapps/discussion/rest_api/tasks.py +++ b/lms/djangoapps/discussion/rest_api/tasks.py @@ -60,15 +60,16 @@ def send_response_endorsed_notifications(thread_id, response_id, course_key_str, if not ENABLE_NOTIFICATIONS.is_enabled(course_key): return thread = Thread(id=thread_id).retrieve() - creator = User.objects.get(id=endorsed_by) - course = get_course_with_access(creator, 'load', course_key, check_if_enrolled=True) response = Comment(id=response_id).retrieve() + creator = User.objects.get(id=response.user_id) + endorser = User.objects.get(id=endorsed_by) + course = get_course_with_access(creator, 'load', course_key, check_if_enrolled=True) notification_sender = DiscussionNotificationSender(thread, course, creator) # skip sending notification to author of thread if they are the same as the author of the response if response.user_id != thread.user_id: # sends notification to author of thread notification_sender.send_response_endorsed_on_thread_notification() # sends notification to author of response - if int(response.user_id) != creator.id: + if int(response.user_id) != endorser.id: notification_sender.creator = User.objects.get(id=response.user_id) notification_sender.send_response_endorsed_notification() diff --git a/lms/djangoapps/discussion/rest_api/tests/test_tasks.py b/lms/djangoapps/discussion/rest_api/tests/test_tasks.py index 26146857366b..f3f2a68ae668 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_tasks.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_tasks.py @@ -600,10 +600,10 @@ def test_response_endorsed_notifications(self): self.assertEqual(notification_data.notification_type, 'response_endorsed_on_thread') expected_context = { - 'replier_name': self.user_3.username, + 'replier_name': self.user_2.username, 'post_title': 'test thread', 'course_name': self.course.display_name, - 'sender_id': int(self.user_3.id), + 'sender_id': int(self.user_2.id), } self.assertDictEqual(notification_data.context, expected_context) self.assertEqual(notification_data.content_url, _get_mfe_url(self.course.id, thread.id)) diff --git a/openedx/core/djangoapps/notifications/base_notification.py b/openedx/core/djangoapps/notifications/base_notification.py index 2845e9cbe41d..0287d8f73c19 100644 --- a/openedx/core/djangoapps/notifications/base_notification.py +++ b/openedx/core/djangoapps/notifications/base_notification.py @@ -146,11 +146,11 @@ 'is_core': True, 'info': '', 'non_editable': [], - 'content_template': _('<{p}><{strong}>{replier_name} response has been endorsed in your post ' + 'content_template': _('<{p}><{strong}>{replier_name}\'s response has been endorsed in your post ' '<{strong}>{post_title}'), 'content_context': { 'post_title': 'Post title', - 'replier_name': 'Endorsed by', + 'replier_name': 'replier name', }, 'email_template': '', 'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE] @@ -161,7 +161,8 @@ 'is_core': True, 'info': '', 'non_editable': [], - 'content_template': _('<{p}>{post_title}'), + 'content_template': _('<{p}>Your response has been endorsed on the post <{strong}>{post_title}'), 'content_context': { 'post_title': 'Post title', }, From 475b49c3caad559a8cf85c84fffa676de42d899f Mon Sep 17 00:00:00 2001 From: Juliana Kang Date: Fri, 19 Jul 2024 08:59:55 -0400 Subject: [PATCH 03/14] feat: Add waffle flag for First Purchase Discount override (#35133) REV-4097 --- openedx/features/discounts/applicability.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 1bb895f7f90f..b158a5f45a42 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -28,6 +28,18 @@ from common.djangoapps.track import segment +# .. toggle_name: discounts.enable_first_purchase_discount_override +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Waffle flag to enable the First Purchase Discount to be overriden from +# EDXWELCOME/BIENVENIDOAEDX 15% discount to a new code. +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2024-07-18 +# .. toggle_target_removal_date: None +# .. toggle_tickets: REV-4097 +# .. toggle_warning: This feature toggle does not have a target removal date. +FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG = WaffleFlag('discounts.enable_first_purchase_discount_override', __name__) + # .. toggle_name: discounts.enable_discounting # .. toggle_implementation: WaffleFlag # .. toggle_default: False From 8f7496d00e33f86d1abcb3eaf901689782378107 Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Mon, 8 Jul 2024 14:23:19 +0000 Subject: [PATCH 04/14] fix: DiscussionsConfigurations admin error --- openedx/core/djangoapps/discussions/admin.py | 4 +++ .../discussions/tests/test_admin.py | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 openedx/core/djangoapps/discussions/tests/test_admin.py diff --git a/openedx/core/djangoapps/discussions/admin.py b/openedx/core/djangoapps/discussions/admin.py index eb61942abf2a..810f57e95226 100644 --- a/openedx/core/djangoapps/discussions/admin.py +++ b/openedx/core/djangoapps/discussions/admin.py @@ -3,6 +3,7 @@ """ from django.contrib import admin from django.contrib.admin import SimpleListFilter +from django.contrib.admin.utils import quote from simple_history.admin import SimpleHistoryAdmin from openedx.core.djangoapps.config_model_utils.admin import StackedConfigModelAdmin @@ -26,6 +27,9 @@ class DiscussionsConfigurationAdmin(SimpleHistoryAdmin): 'provider_type', ) + def change_view(self, request, object_id=None, form_url="", extra_context=None): + return super().change_view(request, quote(object_id), form_url, extra_context) + class AllowListFilter(SimpleListFilter): """ diff --git a/openedx/core/djangoapps/discussions/tests/test_admin.py b/openedx/core/djangoapps/discussions/tests/test_admin.py new file mode 100644 index 000000000000..d16d73dd8bef --- /dev/null +++ b/openedx/core/djangoapps/discussions/tests/test_admin.py @@ -0,0 +1,32 @@ +""" +Tests for DiscussionsConfiguration admin view +""" +from django.test import TestCase +from django.urls import reverse + +from common.djangoapps.student.tests.factories import UserFactory +from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, Provider + + +class DiscussionsConfigurationAdminTest(TestCase): + """ + Tests for discussion config admin + """ + def setUp(self): + super().setUp() + self.superuser = UserFactory(is_staff=True, is_superuser=True) + self.client.login(username=self.superuser.username, password="Password1234") + + def test_change_view(self): + """ + Test that the DiscussionAdmin's change_view processes the context_key correctly and returns a successful + response. + """ + discussion_config = DiscussionsConfiguration.objects.create( + context_key='course-v1:test+test+06_25_2024', + provider_type=Provider.OPEN_EDX, + ) + url = reverse('admin:discussions_discussionsconfiguration_change', args=[discussion_config.context_key]) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'course-v1:test+test+06_25_2024') From 004cd29cf39ed41d1eee462904d8aa927f6ae4ca Mon Sep 17 00:00:00 2001 From: Muhammad Soban Javed Date: Mon, 22 Jul 2024 18:27:57 +0500 Subject: [PATCH 05/14] chore!: uprgade social-auth-app-django to version 5.4.1 (#35045) * chore!: uprgade social-auth-app-django to version 5.4.1 * chore: add migration from social_django --- ...ricalusersocialauth_extra_data_and_more.py | 23 +++++++++++++++++++ requirements/constraints.txt | 8 ------- requirements/edx/base.txt | 7 +++--- requirements/edx/development.txt | 7 +++--- requirements/edx/doc.txt | 7 +++--- requirements/edx/testing.txt | 7 +++--- 6 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py diff --git a/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py b/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py new file mode 100644 index 000000000000..5f09d0cc493b --- /dev/null +++ b/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.13 on 2024-06-27 20:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('support', '0005_unique_course_id'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalusersocialauth', + name='extra_data', + field=models.JSONField(default=dict), + ), + migrations.AlterField( + model_name='historicalusersocialauth', + name='id', + field=models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID'), + ), + ] diff --git a/requirements/constraints.txt b/requirements/constraints.txt index b404440e04dd..cdb306e6a030 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -59,14 +59,6 @@ pycodestyle<2.9.0 pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticket. -# adding these constraints to minimize boto3 and botocore changeset -social-auth-core==4.3.0 - -# social-auth-app-django versions after 5.2.0 has a problematic migration that will cause issues deployments with large -# `social_auth_usersocialauth` tables. 5.1.0 has missing migration and 5.2.0 has that problematic migration. -social-auth-app-django==5.0.0 - - # urllib3>=2.0.0 conflicts with elastic search && snowflake-connector-python packages # which require urllib3<2 for now. # Issue for unpinning: https://github.com/openedx/edx-platform/issues/32222 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 1259d5bda7fb..5dd6732caeb0 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -227,6 +227,7 @@ django==4.2.14 # openedx-filters # openedx-learning # ora2 + # social-auth-app-django # super-csv # xblock-google-drive # xss-utils @@ -1056,14 +1057,12 @@ slumber==0.7.1 # edx-rest-api-client snowflake-connector-python==3.11.0 # via edx-enterprise -social-auth-app-django==5.0.0 +social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-auth-backends -social-auth-core==4.3.0 +social-auth-core==4.5.4 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-auth-backends # social-auth-app-django diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index f6fa68363577..aeda833921bf 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -395,6 +395,7 @@ django==4.2.14 # openedx-filters # openedx-learning # ora2 + # social-auth-app-django # super-csv # xblock-google-drive # xss-utils @@ -1877,15 +1878,13 @@ snowflake-connector-python==3.11.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise -social-auth-app-django==5.0.0 +social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-auth-backends -social-auth-core==4.3.0 +social-auth-core==4.5.4 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-auth-backends diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 3ab0f8ce8181..05542b5d586e 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -275,6 +275,7 @@ django==4.2.14 # openedx-filters # openedx-learning # ora2 + # social-auth-app-django # super-csv # xblock-google-drive # xss-utils @@ -1246,14 +1247,12 @@ snowflake-connector-python==3.11.0 # via # -r requirements/edx/base.txt # edx-enterprise -social-auth-app-django==5.0.0 +social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-auth-backends -social-auth-core==4.3.0 +social-auth-core==4.5.4 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 9d8c429800cc..8f08a3308a5d 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -310,6 +310,7 @@ django==4.2.14 # openedx-filters # openedx-learning # ora2 + # social-auth-app-django # super-csv # xblock-google-drive # xss-utils @@ -1408,14 +1409,12 @@ snowflake-connector-python==3.11.0 # via # -r requirements/edx/base.txt # edx-enterprise -social-auth-app-django==5.0.0 +social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-auth-backends -social-auth-core==4.3.0 +social-auth-core==4.5.4 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django From a19697786f843850a51271810feccb38263687b0 Mon Sep 17 00:00:00 2001 From: Juliana Kang Date: Mon, 22 Jul 2024 14:01:07 -0400 Subject: [PATCH 06/14] feat: Add First Purchase Discount override (#35143) REV-4098 --- .../outline/tests/test_view.py | 27 ++++++++++++++----- lms/envs/devstack.py | 3 +++ lms/envs/test.py | 3 +++ .../features/discounts/tests/test_utils.py | 14 ++++++++-- openedx/features/discounts/utils.py | 13 ++++++++- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/course_home_api/outline/tests/test_view.py b/lms/djangoapps/course_home_api/outline/tests/test_view.py index 4f40d9a80192..c092e9e2a506 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -11,6 +11,7 @@ import json # lint-amnesty, pylint: disable=wrong-import-order from completion.models import BlockCompletion from django.conf import settings # lint-amnesty, pylint: disable=wrong-import-order +from django.test import override_settings from django.urls import reverse # lint-amnesty, pylint: disable=wrong-import-order from edx_toggles.toggles.testutils import override_waffle_flag # lint-amnesty, pylint: disable=wrong-import-order @@ -33,7 +34,10 @@ DISPLAY_COURSE_SOCK_FLAG, ENABLE_COURSE_GOALS ) -from openedx.features.discounts.applicability import DISCOUNT_APPLICABILITY_FLAG +from openedx.features.discounts.applicability import ( + DISCOUNT_APPLICABILITY_FLAG, + FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG +) from xmodule.course_block import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order @@ -179,17 +183,28 @@ def test_welcome_message(self, welcome_message_is_dismissed): welcome_message_html = self.client.get(self.url).data['welcome_message_html'] assert welcome_message_html == (None if welcome_message_is_dismissed else '

Welcome

') - def test_offer(self): + @ddt.data( + (False, 'EDXWELCOME'), + (True, 'NOTEDXWELCOME'), + ) + @ddt.unpack + def test_offer(self, is_fpd_override_waffle_flag_on, fpd_code): + """ + Test that the offer data contains the correct code for the first purchase discount, + which can be overriden via a waffle flag from the default EDXWELCOME. + """ CourseEnrollment.enroll(self.user, self.course.id) response = self.client.get(self.url) assert response.data['offer'] is None - with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): - response = self.client.get(self.url) + with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE='NOTEDXWELCOME'): + with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): + with override_waffle_flag(FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=is_fpd_override_waffle_flag_on): + response = self.client.get(self.url) - # Just a quick spot check that the dictionary looks like what we expect - assert response.data['offer']['code'] == 'EDXWELCOME' + # Just a quick spot check that the dictionary looks like what we expect + assert response.data['offer']['code'] == fpd_code def test_access_expiration(self): enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 611017962852..7de34f9146e0 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -239,6 +239,9 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing ########################## Authn MFE Context API ####################### ENABLE_DYNAMIC_REGISTRATION_FIELDS = True +########################## Discount/Coupons ####################### +FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE = '' + ############## ECOMMERCE API CONFIGURATION SETTINGS ############### ECOMMERCE_PUBLIC_URL_ROOT = 'http://localhost:18130' ECOMMERCE_API_URL = 'http://edx.devstack.ecommerce:18130/api/v2' diff --git a/lms/envs/test.py b/lms/envs/test.py index 3c4bb9564927..2a41e5499060 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -92,6 +92,9 @@ # Enable a parental consent age limit for testing PARENTAL_CONSENT_AGE_LIMIT = 13 +# Enable First Purchase Discount offer override +FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE = '' + # Local Directories TEST_ROOT = path("test_root") # Want static files in the same dir for running on jenkins. diff --git a/openedx/features/discounts/tests/test_utils.py b/openedx/features/discounts/tests/test_utils.py index 1ff305831d74..d91d61839c8b 100644 --- a/openedx/features/discounts/tests/test_utils.py +++ b/openedx/features/discounts/tests/test_utils.py @@ -5,7 +5,7 @@ import ddt from django.contrib.auth.models import AnonymousUser -from django.test import TestCase +from django.test import TestCase, override_settings from django.utils.translation import override as override_lang from edx_toggles.toggles.testutils import override_waffle_flag @@ -14,7 +14,11 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory -from openedx.features.discounts.applicability import DISCOUNT_APPLICABILITY_FLAG, get_discount_expiration_date +from openedx.features.discounts.applicability import ( + DISCOUNT_APPLICABILITY_FLAG, + FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, + get_discount_expiration_date +) from .. import utils @@ -84,6 +88,12 @@ def test_spanish_code(self): with override_lang('es-419'): assert utils.generate_offer_data(self.user, self.overview)['code'] == 'BIENVENIDOAEDX' + def test_override(self): + with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE='NOTEDXWELCOME'): + with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): + with override_waffle_flag(FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=True): + assert utils.generate_offer_data(self.user, self.overview)['code'] == 'NOTEDXWELCOME' + def test_anonymous(self): assert utils.generate_offer_data(AnonymousUser(), self.overview) is None diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py index f52f821d9a9d..e97524d4381a 100644 --- a/openedx/features/discounts/utils.py +++ b/openedx/features/discounts/utils.py @@ -5,6 +5,7 @@ from datetime import datetime import pytz +from django.conf import settings from django.utils.translation import get_language from django.utils.translation import gettext as _ @@ -13,6 +14,7 @@ from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.core.djangolib.markup import HTML from openedx.features.discounts.applicability import ( + FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, REV1008_EXPERIMENT_ID, can_receive_discount, discount_percentage, @@ -98,8 +100,17 @@ def generate_offer_data(user, course): original, discounted, percentage = _get_discount_prices(user, course, assume_discount=True) + # Override the First Purchase Discount to another code only if flag is enabled + first_purchase_discount_code = 'BIENVENIDOAEDX' if get_language() == 'es-419' else 'EDXWELCOME' + if FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG.is_enabled(): + first_purchase_discount_code = getattr( + settings, + 'FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE', + first_purchase_discount_code + ) + return { - 'code': 'BIENVENIDOAEDX' if get_language() == 'es-419' else 'EDXWELCOME', + 'code': first_purchase_discount_code, 'expiration_date': expiration_date, 'original_price': original, 'discounted_price': discounted, From d35d13b7335201a34ace74e4042cb3c36ddb5e47 Mon Sep 17 00:00:00 2001 From: katrinan029 <71999631+katrinan029@users.noreply.github.com> Date: Mon, 22 Jul 2024 20:35:16 +0000 Subject: [PATCH 07/14] feat: Upgrade Python dependency edx-enterprise version bump Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/common_constraints.txt | 8 ++++++++ requirements/constraints.txt | 2 +- requirements/edx/base.txt | 3 ++- requirements/edx/development.txt | 3 ++- requirements/edx/doc.txt | 3 ++- requirements/edx/testing.txt | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 4abc9ae22cb3..ef8bc86061b7 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -21,6 +21,7 @@ Django<5.0 # elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process. # elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html +# See https://github.com/openedx/edx-platform/issues/35126 for more info elasticsearch<7.14.0 # django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected @@ -33,3 +34,10 @@ elasticsearch<7.14.0 # So we need to pin it globally, for now. # Ticket for unpinning: https://github.com/openedx/edx-lint/issues/407 importlib-metadata<7 + +# Cause: https://github.com/openedx/event-tracking/pull/290 +# event-tracking 2.4.1 upgrades to pymongo 4.4.0 which is not supported on edx-platform. +# We will pin event-tracking to do not break existing installations +# This can be unpinned once https://github.com/openedx/edx-platform/issues/34586 +# has been resolved and edx-platform is running with pymongo>=4.4.0 +event-tracking<2.4.1 diff --git a/requirements/constraints.txt b/requirements/constraints.txt index cdb306e6a030..701841785ae7 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -20,7 +20,7 @@ celery>=5.2.2,<6.0.0 # 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.21.5 +edx-enterprise==4.21.7 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 5dd6732caeb0..db687ef9e7ce 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -451,7 +451,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.5 +edx-enterprise==4.21.7 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -537,6 +537,7 @@ enmerkar-underscore==2.3.0 # via -r requirements/edx/kernel.in event-tracking==2.4.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/kernel.in # edx-completion # edx-proctoring diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index aeda833921bf..10415067b029 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -724,7 +724,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.5 +edx-enterprise==4.21.7 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -854,6 +854,7 @@ enmerkar-underscore==2.3.0 # -r requirements/edx/testing.txt event-tracking==2.4.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-completion diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 05542b5d586e..6c7d3ab8e543 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -523,7 +523,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.5 +edx-enterprise==4.21.7 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -615,6 +615,7 @@ enmerkar-underscore==2.3.0 # via -r requirements/edx/base.txt event-tracking==2.4.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # edx-completion # edx-proctoring diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 8f08a3308a5d..9029ceebebcc 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -555,7 +555,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.5 +edx-enterprise==4.21.7 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -651,6 +651,7 @@ enmerkar-underscore==2.3.0 # via -r requirements/edx/base.txt event-tracking==2.4.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # edx-completion # edx-proctoring From e12ec1b1536ebed4d967d2f354523eb02c05e24d Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:28:56 +0500 Subject: [PATCH 08/14] feat: added group_by_id field in notifications model (#35137) --- cms/envs/common.py | 2 +- lms/envs/common.py | 2 +- .../0006_notification_group_by_id.py | 18 ++++++++++++++++++ .../core/djangoapps/notifications/models.py | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 openedx/core/djangoapps/notifications/migrations/0006_notification_group_by_id.py diff --git a/cms/envs/common.py b/cms/envs/common.py index 895e4c0ed7e9..be837c518981 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2688,7 +2688,7 @@ ############## NOTIFICATIONS EXPIRY ############## NOTIFICATIONS_EXPIRY = 60 EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000 -NOTIFICATION_CREATION_BATCH_SIZE = 83 +NOTIFICATION_CREATION_BATCH_SIZE = 76 ############################ AI_TRANSLATIONS ################################## AI_TRANSLATIONS_API_URL = 'http://localhost:18760/api/v1' diff --git a/lms/envs/common.py b/lms/envs/common.py index 065d059eeda1..9a56680d4da2 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -5382,7 +5382,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring ############## NOTIFICATIONS ############## NOTIFICATIONS_EXPIRY = 60 EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000 -NOTIFICATION_CREATION_BATCH_SIZE = 83 +NOTIFICATION_CREATION_BATCH_SIZE = 76 NOTIFICATIONS_DEFAULT_FROM_EMAIL = "no-reply@example.com" NOTIFICATION_TYPE_ICONS = {} DEFAULT_NOTIFICATION_ICON_URL = "" diff --git a/openedx/core/djangoapps/notifications/migrations/0006_notification_group_by_id.py b/openedx/core/djangoapps/notifications/migrations/0006_notification_group_by_id.py new file mode 100644 index 000000000000..5a45b9c25a8a --- /dev/null +++ b/openedx/core/djangoapps/notifications/migrations/0006_notification_group_by_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.13 on 2024-07-23 07:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0005_notification_email_notification_web'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='group_by_id', + field=models.CharField(db_index=True, default='', max_length=42), + ), + ] diff --git a/openedx/core/djangoapps/notifications/models.py b/openedx/core/djangoapps/notifications/models.py index 2f7da1803bf6..8bf19edce56e 100644 --- a/openedx/core/djangoapps/notifications/models.py +++ b/openedx/core/djangoapps/notifications/models.py @@ -109,6 +109,7 @@ class Notification(TimeStampedModel): email = models.BooleanField(default=False, null=False, blank=False) last_read = models.DateTimeField(null=True, blank=True) last_seen = models.DateTimeField(null=True, blank=True) + group_by_id = models.CharField(max_length=42, db_index=True, null=False, default="") def __str__(self): return f'{self.user.username} - {self.course_id} - {self.app_name} - {self.notification_type}' From 896b011e88eb81fc969856adb39b754a3ef427d4 Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:15:01 -0400 Subject: [PATCH 09/14] fix: downgrade django-storages to 1.14.3 (#35156) * fix: downgrade django-storages to 1.14.3 * fix: change max version * feat: Recompile Python dependencies (#35164) Commit generated by workflow `openedx/edx-platform/.github/workflows/compile-python-requirements.yml@refs/heads/master` Co-authored-by: KristinAoki <42981026+KristinAoki@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- requirements/constraints.txt | 5 +++++ requirements/edx/base.txt | 3 ++- requirements/edx/development.txt | 3 ++- requirements/edx/doc.txt | 3 ++- requirements/edx/testing.txt | 3 ++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 701841785ae7..1478e646ca17 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -121,3 +121,8 @@ backports.zoneinfo;python_version<"3.9" # Newer versions have zoneinfo availabl # Otherwise we see a failure while running the following command: # export DJANGO_SETTINGS_MODULE=cms.envs.test; python manage.py cms check_reserved_keywords --override_file db_keyword_overrides.yml --report_path reports/reserved_keywords --report_file cms_reserved_keyword_report.csv numpy<2.0.0 + +# django-storages==1.14.4 breaks course imports +# Two lines were added in 1.14.4 that make file_exists_in_storage function always return False, +# as the default value of AWS_S3_FILE_OVERWRITE is True +django-storages<1.14.4 \ No newline at end of file diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index db687ef9e7ce..408534bdbb3e 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -334,8 +334,9 @@ django-statici18n==2.5.0 # lti-consumer-xblock # xblock-drag-and-drop-v2 # xblock-poll -django-storages==1.14.4 +django-storages==1.14.3 # via + # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edxval django-user-tasks==3.2.0 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 10415067b029..4dd01d4989cc 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -550,8 +550,9 @@ django-statici18n==2.5.0 # lti-consumer-xblock # xblock-drag-and-drop-v2 # xblock-poll -django-storages==1.14.4 +django-storages==1.14.3 # via + # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edxval diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 6c7d3ab8e543..cc46728bc6e4 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -396,8 +396,9 @@ django-statici18n==2.5.0 # lti-consumer-xblock # xblock-drag-and-drop-v2 # xblock-poll -django-storages==1.14.4 +django-storages==1.14.3 # via + # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edxval django-user-tasks==3.2.0 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 9029ceebebcc..b7bb5f81d546 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -431,8 +431,9 @@ django-statici18n==2.5.0 # lti-consumer-xblock # xblock-drag-and-drop-v2 # xblock-poll -django-storages==1.14.4 +django-storages==1.14.3 # via + # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edxval django-user-tasks==3.2.0 From 726835e2668b8089c8a97fdd1fa4f0e85deb9463 Mon Sep 17 00:00:00 2001 From: MueezKhan246 <93375917+MueezKhan246@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:14:43 +0000 Subject: [PATCH 10/14] feat: Upgrade Python dependency edx-enterprise fixed 500 error for search filter for api request logs in admin view Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 1478e646ca17..df36493234c5 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -20,7 +20,7 @@ celery>=5.2.2,<6.0.0 # 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.21.7 +edx-enterprise==4.21.8 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 408534bdbb3e..b56b280405c5 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -452,7 +452,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.7 +edx-enterprise==4.21.8 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 4dd01d4989cc..360862385c34 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -725,7 +725,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.7 +edx-enterprise==4.21.8 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index cc46728bc6e4..fcc073f09520 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -524,7 +524,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.7 +edx-enterprise==4.21.8 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index b7bb5f81d546..caa5d3c6f9db 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -556,7 +556,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.21.7 +edx-enterprise==4.21.8 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 40ddfeb3b809b6ecf6762d7c4c70f29d86fafcb9 Mon Sep 17 00:00:00 2001 From: Juliana Kang Date: Tue, 23 Jul 2024 15:43:34 -0400 Subject: [PATCH 11/14] feat: Add override on percentage config to the First Purchase Discount (#35167) REV-4098 --- .../outline/tests/test_view.py | 22 +++++++++++-------- lms/envs/common.py | 4 ++++ lms/envs/devstack.py | 3 --- lms/envs/test.py | 3 --- openedx/features/discounts/applicability.py | 8 +++++++ .../features/discounts/tests/test_utils.py | 8 ++++--- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/course_home_api/outline/tests/test_view.py b/lms/djangoapps/course_home_api/outline/tests/test_view.py index c092e9e2a506..76928846f080 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -184,11 +184,11 @@ def test_welcome_message(self, welcome_message_is_dismissed): assert welcome_message_html == (None if welcome_message_is_dismissed else '

Welcome

') @ddt.data( - (False, 'EDXWELCOME'), - (True, 'NOTEDXWELCOME'), + (False, 'EDXWELCOME', 15), + (True, 'NOTEDXWELCOME', 30), ) @ddt.unpack - def test_offer(self, is_fpd_override_waffle_flag_on, fpd_code): + def test_offer(self, is_fpd_override_waffle_flag_on, fpd_code, fpd_percentage): """ Test that the offer data contains the correct code for the first purchase discount, which can be overriden via a waffle flag from the default EDXWELCOME. @@ -199,12 +199,16 @@ def test_offer(self, is_fpd_override_waffle_flag_on, fpd_code): assert response.data['offer'] is None with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE='NOTEDXWELCOME'): - with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): - with override_waffle_flag(FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=is_fpd_override_waffle_flag_on): - response = self.client.get(self.url) - - # Just a quick spot check that the dictionary looks like what we expect - assert response.data['offer']['code'] == fpd_code + with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_PERCENTAGE=fpd_percentage): + with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): + with override_waffle_flag( + FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=is_fpd_override_waffle_flag_on + ): + response = self.client.get(self.url) + + # Just a quick spot check that the dictionary looks like what we expect + assert response.data['offer']['code'] == fpd_code + assert response.data['offer']['percentage'] == fpd_percentage def test_access_expiration(self): enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) diff --git a/lms/envs/common.py b/lms/envs/common.py index 9a56680d4da2..26073335a267 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4295,6 +4295,10 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring } } +# Enable First Purchase Discount offer override +FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE = '' +FIRST_PURCHASE_DISCOUNT_OVERRIDE_PERCENTAGE = 15 + # E-Commerce API Configuration ECOMMERCE_PUBLIC_URL_ROOT = 'http://localhost:8002' ECOMMERCE_API_URL = 'http://localhost:8002/api/v2' diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 7de34f9146e0..611017962852 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -239,9 +239,6 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing ########################## Authn MFE Context API ####################### ENABLE_DYNAMIC_REGISTRATION_FIELDS = True -########################## Discount/Coupons ####################### -FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE = '' - ############## ECOMMERCE API CONFIGURATION SETTINGS ############### ECOMMERCE_PUBLIC_URL_ROOT = 'http://localhost:18130' ECOMMERCE_API_URL = 'http://edx.devstack.ecommerce:18130/api/v2' diff --git a/lms/envs/test.py b/lms/envs/test.py index 2a41e5499060..3c4bb9564927 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -92,9 +92,6 @@ # Enable a parental consent age limit for testing PARENTAL_CONSENT_AGE_LIMIT = 13 -# Enable First Purchase Discount offer override -FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE = '' - # Local Directories TEST_ROOT = path("test_root") # Want static files in the same dir for running on jenkins. diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index b158a5f45a42..2a9bc34c2dc0 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -13,6 +13,7 @@ import pytz from crum import get_current_request, impersonate +from django.conf import settings from django.utils import timezone from django.utils.dateparse import parse_datetime from edx_toggles.toggles import WaffleFlag @@ -227,6 +228,13 @@ def discount_percentage(course): """ Get the configured discount amount. """ + if FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG.is_enabled(): + return getattr( + settings, + 'FIRST_PURCHASE_DISCOUNT_OVERRIDE_PERCENTAGE', + 15 + ) + configured_percentage = DiscountPercentageConfig.current(course_key=course.id).percentage if configured_percentage: return configured_percentage diff --git a/openedx/features/discounts/tests/test_utils.py b/openedx/features/discounts/tests/test_utils.py index d91d61839c8b..6bd9d1cd1593 100644 --- a/openedx/features/discounts/tests/test_utils.py +++ b/openedx/features/discounts/tests/test_utils.py @@ -90,9 +90,11 @@ def test_spanish_code(self): def test_override(self): with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE='NOTEDXWELCOME'): - with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): - with override_waffle_flag(FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=True): - assert utils.generate_offer_data(self.user, self.overview)['code'] == 'NOTEDXWELCOME' + with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_PERCENTAGE=30): + with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True): + with override_waffle_flag(FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG, active=True): + assert utils.generate_offer_data(self.user, self.overview)['code'] == 'NOTEDXWELCOME' + assert utils.generate_offer_data(self.user, self.overview)['percentage'] == 30 def test_anonymous(self): assert utils.generate_offer_data(AnonymousUser(), self.overview) is None From b77b90a344c5aebb833b72cd815483d505f69d6b Mon Sep 17 00:00:00 2001 From: jawad khan Date: Wed, 24 Jul 2024 01:40:41 +0500 Subject: [PATCH 12/14] =?UTF-8?q?fix:=20Enable=20courseware=20access=20api?= =?UTF-8?q?=20for=20all=20types=20of=20course(expired,=20cl=E2=80=A6=20(#3?= =?UTF-8?q?5155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Enable courseware access api for all types of course(expired, closed etc) --- .../mobile_api/course_info/views.py | 14 +++- .../tests/test_course_info_views.py | 79 +++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/mobile_api/course_info/views.py b/lms/djangoapps/mobile_api/course_info/views.py index 0c4d4a96aa01..affefafe5ba0 100644 --- a/lms/djangoapps/mobile_api/course_info/views.py +++ b/lms/djangoapps/mobile_api/course_info/views.py @@ -418,19 +418,25 @@ class CourseEnrollmentDetailsView(APIView): This api works with all versions {api_version}, you can use: v0.5, v1, v2 or v3 - GET /api/mobile/{api_version}/course_info/{course_id}}/enrollment_details + GET /api/mobile/{api_version}/course_info/{course_id}/enrollment_details """ - @mobile_course_access() - def get(self, request, course, *args, **kwargs): + def get(self, request, *args, **kwargs): """ Handle the GET request Returns user enrollment and course details. """ + course_key_string = kwargs.get('course_id') + try: + course_key = CourseKey.from_string(course_key_string) + except InvalidKeyError: + error = {'error': f"'{str(course_key_string)}' is not a valid course key."} + return Response(data=error, status=status.HTTP_400_BAD_REQUEST) + data = { 'api_version': self.kwargs.get('api_version'), - 'course_id': course.id, + 'course_id': course_key, 'user': request.user, 'request': request, } diff --git a/lms/djangoapps/mobile_api/tests/test_course_info_views.py b/lms/djangoapps/mobile_api/tests/test_course_info_views.py index 730943c365e9..56c020ec8fa3 100644 --- a/lms/djangoapps/mobile_api/tests/test_course_info_views.py +++ b/lms/djangoapps/mobile_api/tests/test_course_info_views.py @@ -1,6 +1,7 @@ """ Tests for course_info """ +from datetime import datetime, timedelta from unittest.mock import patch import ddt @@ -11,6 +12,7 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from milestones.tests.utils import MilestonesTestCaseMixin +from pytz import utc from rest_framework import status from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import @@ -26,6 +28,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.django_utils import \ SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.xml_importer import import_course_from_xml # lint-amnesty, pylint: disable=wrong-import-order User = get_user_model() @@ -521,3 +524,79 @@ def verify_certificate(self, response, mock_certificate_downloadable_status): mock_certificate_downloadable_status.assert_called_once() certificate_url = 'https://test_certificate_url' assert response.data['certificate'] == {'url': certificate_url} + + @patch('lms.djangoapps.mobile_api.course_info.utils.certificate_downloadable_status') + def test_course_not_started(self, mock_certificate_downloadable_status): + """ Test course data which has not started yet """ + + certificate_url = 'https://test_certificate_url' + mock_certificate_downloadable_status.return_value = { + 'is_downloadable': True, + 'download_url': certificate_url, + } + now = datetime.now(utc) + course_not_started = CourseFactory.create( + mobile_available=True, + static_asset_path="needed_for_split", + start=now + timedelta(days=5), + ) + + url = reverse('course-enrollment-details', kwargs={ + 'api_version': 'v1', + 'course_id': course_not_started.id + }) + + response = self.client.get(path=url) + assert response.status_code == 200 + assert response.data['id'] == str(course_not_started.id) + + self.verify_course_access_details(response) + + @patch('lms.djangoapps.mobile_api.course_info.utils.certificate_downloadable_status') + def test_course_closed(self, mock_certificate_downloadable_status): + """ Test course data whose end date is in past """ + + certificate_url = 'https://test_certificate_url' + mock_certificate_downloadable_status.return_value = { + 'is_downloadable': True, + 'download_url': certificate_url, + } + now = datetime.now(utc) + course_closed = CourseFactory.create( + mobile_available=True, + static_asset_path="needed_for_split", + start=now - timedelta(days=250), + end=now - timedelta(days=50), + ) + + url = reverse('course-enrollment-details', kwargs={ + 'api_version': 'v1', + 'course_id': course_closed.id + }) + + response = self.client.get(path=url) + assert response.status_code == 200 + assert response.data['id'] == str(course_closed.id) + + self.verify_course_access_details(response) + + @patch('lms.djangoapps.mobile_api.course_info.utils.certificate_downloadable_status') + def test_invalid_course_id(self, mock_certificate_downloadable_status): + """ Test view with invalid course id """ + + certificate_url = 'https://test_certificate_url' + mock_certificate_downloadable_status.return_value = { + 'is_downloadable': True, + 'download_url': certificate_url, + } + + invalid_id = "invalid" + str(self.course.id) + url = reverse('course-enrollment-details', kwargs={ + 'api_version': 'v1', + 'course_id': invalid_id + }) + + response = self.client.get(path=url) + assert response.status_code == 400 + expected_error = "'{}' is not a valid course key.".format(invalid_id) + assert response.data['error'] == expected_error From ce290db4c129263897d8f98286adda9c6c833f30 Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:39:02 +0500 Subject: [PATCH 13/14] feat: added snowflake events for email notifications (#35158) --- .../djangoapps/notifications/email/events.py | 40 +++++++++++++++++++ .../djangoapps/notifications/email/tasks.py | 2 + 2 files changed, 42 insertions(+) create mode 100644 openedx/core/djangoapps/notifications/email/events.py diff --git a/openedx/core/djangoapps/notifications/email/events.py b/openedx/core/djangoapps/notifications/email/events.py new file mode 100644 index 000000000000..165539a018cb --- /dev/null +++ b/openedx/core/djangoapps/notifications/email/events.py @@ -0,0 +1,40 @@ +""" +Events for email notifications +""" +import datetime + +from eventtracking import tracker + +from common.djangoapps.track import segment +from openedx.core.djangoapps.notifications.base_notification import COURSE_NOTIFICATION_APPS + + +EMAIL_DIGEST_SENT = "edx.notifications.email_digest" + + +def send_user_email_digest_sent_event(user, cadence_type, notifications): + """ + Sends tracker and segment email for user email digest + """ + notification_breakdown = {key: 0 for key in COURSE_NOTIFICATION_APPS.keys()} + for notification in notifications: + notification_breakdown[notification.app_name] += 1 + event_data = { + "username": user.username, + "email": user.email, + "cadence_type": cadence_type, + "total_notifications_count": len(notifications), + "count_breakdown": notification_breakdown, + "notification_ids": [notification.id for notification in notifications], + "send_at": str(datetime.datetime.now()) + } + with tracker.get_tracker().context(EMAIL_DIGEST_SENT, event_data): + tracker.emit( + EMAIL_DIGEST_SENT, + event_data, + ) + segment.track( + 'None', + EMAIL_DIGEST_SENT, + event_data, + ) diff --git a/openedx/core/djangoapps/notifications/email/tasks.py b/openedx/core/djangoapps/notifications/email/tasks.py index dc42be585cc7..75af99aa86c3 100644 --- a/openedx/core/djangoapps/notifications/email/tasks.py +++ b/openedx/core/djangoapps/notifications/email/tasks.py @@ -14,6 +14,7 @@ Notification, get_course_notification_preference_config_version ) +from .events import send_user_email_digest_sent_event from .message_type import EmailNotificationMessageType from .utils import ( add_headers_to_email_message, @@ -101,6 +102,7 @@ def send_digest_email_to_user(user, cadence_type, course_language='en', courses_ ).personalize(recipient, course_language, message_context) message = add_headers_to_email_message(message, message_context) ace.send(message) + send_user_email_digest_sent_event(user, cadence_type, notifications) logger.info(f' Email sent to {user.username} ==Temp Log==') From 1fb20b359806155803b4c68719658cf1814134a4 Mon Sep 17 00:00:00 2001 From: Awais Ansari <79941147+awais-ansari@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:40:58 +0500 Subject: [PATCH 14/14] feat: update account verification email context (#35165) --- common/djangoapps/student/views/management.py | 4 +++- openedx/core/djangoapps/user_authn/tests/test_tasks.py | 2 ++ openedx/features/discounts/applicability.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index f66e71a0740f..b06cac7b7e50 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -62,6 +62,7 @@ ) from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser +from openedx.features.discounts.applicability import FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG from openedx.features.enterprise_support.utils import is_enterprise_learner from common.djangoapps.student.email_helpers import generate_activation_email_context from common.djangoapps.student.helpers import DISABLE_UNENROLL_CERT_STATES, cert_info @@ -206,12 +207,13 @@ def compose_activation_email( message_context = generate_activation_email_context(user, user_registration) message_context.update({ 'confirm_activation_link': _get_activation_confirmation_link(message_context['key'], redirect_url), + 'is_enterprise_learner': is_enterprise_learner(user), + 'is_first_purchase_discount_overridden': FIRST_PURCHASE_DISCOUNT_OVERRIDE_FLAG.is_enabled(), 'route_enabled': route_enabled, 'routed_user': user.username, 'routed_user_email': user.email, 'routed_profile_name': profile_name, 'registration_flow': registration_flow, - 'is_enterprise_learner': is_enterprise_learner(user), 'show_auto_generated_username': show_auto_generated_username(user.username), }) diff --git a/openedx/core/djangoapps/user_authn/tests/test_tasks.py b/openedx/core/djangoapps/user_authn/tests/test_tasks.py index 80516f20a39c..5103343a0879 100644 --- a/openedx/core/djangoapps/user_authn/tests/test_tasks.py +++ b/openedx/core/djangoapps/user_authn/tests/test_tasks.py @@ -19,6 +19,7 @@ class SendActivationEmailTestCase(TestCase): """ Test for send activation email to user """ + def setUp(self): """ Setup components used by each test.""" super().setUp() @@ -44,6 +45,7 @@ def test_ComposeEmail(self): assert self.msg.context['routed_profile_name'] == '' assert self.msg.context['registration_flow'] is False assert self.msg.context['is_enterprise_learner'] is False + assert self.msg.context['is_first_purchase_discount_overridden'] is False @mock.patch('time.sleep', mock.Mock(return_value=None)) @mock.patch('openedx.core.djangoapps.user_authn.tasks.log') diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 2a9bc34c2dc0..97d6f74403bd 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -32,7 +32,7 @@ # .. toggle_name: discounts.enable_first_purchase_discount_override # .. toggle_implementation: WaffleFlag # .. toggle_default: False -# .. toggle_description: Waffle flag to enable the First Purchase Discount to be overriden from +# .. toggle_description: Waffle flag to enable the First Purchase Discount to be overridden from # EDXWELCOME/BIENVENIDOAEDX 15% discount to a new code. # .. toggle_use_cases: opt_in # .. toggle_creation_date: 2024-07-18