Skip to content

Commit

Permalink
Merge branch 'master' into submit_button_reset
Browse files Browse the repository at this point in the history
  • Loading branch information
e0d authored Jul 24, 2024
2 parents abd9302 + baf5de2 commit 6de8011
Show file tree
Hide file tree
Showing 27 changed files with 345 additions and 58 deletions.
1 change: 1 addition & 0 deletions .github/workflows/add-remove-label-on-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ on:
jobs:
add_remove_labels:
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master

2 changes: 1 addition & 1 deletion cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 3 additions & 1 deletion common/djangoapps/student/views/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
})

Expand Down
33 changes: 26 additions & 7 deletions lms/djangoapps/course_home_api/outline/tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

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

Expand Down Expand Up @@ -179,17 +183,32 @@ 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 '<p>Welcome</p>')

def test_offer(self):
@ddt.data(
(False, 'EDXWELCOME', 15),
(True, 'NOTEDXWELCOME', 30),
)
@ddt.unpack
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.
"""
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)

# Just a quick spot check that the dictionary looks like what we expect
assert response.data['offer']['code'] == 'EDXWELCOME'
with override_settings(FIRST_PURCHASE_DISCOUNT_OVERRIDE_CODE='NOTEDXWELCOME'):
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)
Expand Down
7 changes: 4 additions & 3 deletions lms/djangoapps/discussion/rest_api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
4 changes: 2 additions & 2 deletions lms/djangoapps/discussion/rest_api/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
14 changes: 10 additions & 4 deletions lms/djangoapps/mobile_api/course_info/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
79 changes: 79 additions & 0 deletions lms/djangoapps/mobile_api/tests/test_course_info_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests for course_info
"""
from datetime import datetime, timedelta
from unittest.mock import patch

import ddt
Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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'),
),
]
6 changes: 5 additions & 1 deletion lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -5382,7 +5386,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 = "[email protected]"
NOTIFICATION_TYPE_ICONS = {}
DEFAULT_NOTIFICATION_ICON_URL = ""
Expand Down
4 changes: 4 additions & 0 deletions openedx/core/djangoapps/discussions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
"""
Expand Down
32 changes: 32 additions & 0 deletions openedx/core/djangoapps/discussions/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -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')
7 changes: 4 additions & 3 deletions openedx/core/djangoapps/notifications/base_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@
'is_core': True,
'info': '',
'non_editable': [],
'content_template': _('<{p}><{strong}>{replier_name}</{strong}> response has been endorsed in your post '
'content_template': _('<{p}><{strong}>{replier_name}\'s</{strong}> response has been endorsed in your post '
'<{strong}>{post_title}</{strong}></{p}>'),
'content_context': {
'post_title': 'Post title',
'replier_name': 'Endorsed by',
'replier_name': 'replier name',
},
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE]
Expand All @@ -161,7 +161,8 @@
'is_core': True,
'info': '',
'non_editable': [],
'content_template': _('<{p}><Your response has been endorsed <{strong}>{post_title}</{strong}></{p}>'),
'content_template': _('<{p}>Your response has been endorsed on the post <{strong}>{post_title}</{strong}></{'
'p}>'),
'content_context': {
'post_title': 'Post title',
},
Expand Down
Loading

0 comments on commit 6de8011

Please sign in to comment.