diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 5d035f506e..44e2a49b1b 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -104,10 +104,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest # Check whether we were correctly redirected if redirect: if has_started: - self.assertRedirects( - response, reverse('openedx.course_experience.course_home', kwargs={'course_id': course.id}), - target_status_code=302, # for follow-on redirection to MFE (ideally we'd just be sent there first) - ) + mfe_url = f'http://learning-mfe/course/{course.id}/home' + self.assertRedirects(response, mfe_url, fetch_redirect_response=False) else: self.assertRedirects(response, reverse('dashboard')) else: diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 4762c89e07..7958a480ed 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -40,6 +40,7 @@ from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_duration_limits.access import get_user_course_duration, get_user_course_expiration_date +from openedx.features.course_experience import course_home_url from openedx.features.enterprise_support.api import enterprise_customer_for_request from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.db import outer_atomic @@ -413,7 +414,7 @@ class ChooseModeView(View): 302 to the course if possible or the dashboard if not. """ if course.has_started() or user.is_staff: - return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) + return redirect(course_home_url(course_key)) else: return redirect(reverse('dashboard')) diff --git a/common/static/common/js/utils/clamp-html.js b/common/static/common/js/utils/clamp-html.js deleted file mode 100644 index 4d123e669b..0000000000 --- a/common/static/common/js/utils/clamp-html.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Used to ellipsize a section of arbitrary HTML after a specified number of words. - * - * Note: this will modify the DOM structure of root in place. - * To keep the original around, you may want to save the result of cloneNode(true) before calling this method. - * - * Known bug: This method will ignore any special whitespace in the source and simply output single spaces. - * Which means that   will not be respected. This is not considered worth solving at time of writing. - * - * Returns how many words remain (or a negative number if the content got clamped) - */ -function clampHtmlByWords(root, wordsLeft) { - 'use strict'; - - if (root.nodeName === 'SCRIPT' || root.nodeName === 'STYLE') { - return wordsLeft; // early exit and ignore - } - - var remaining = wordsLeft; - var nodes = Array.from(root.childNodes ? root.childNodes : []); - var words, chopped; - - // First, cut short any text in our node, as necessary - if (root.nodeName === '#text' && root.data) { - // split on words, ignoring any resulting empty strings - words = root.data.split(/\s+/).filter(Boolean); - if (remaining < 0) { - root.data = ''; // eslint-disable-line no-param-reassign - } else if (remaining > words.length) { - remaining -= words.length; - } else { - // OK, let's add an ellipsis and cut some of root.data - chopped = words.slice(0, remaining).join(' ') + '…'; - // But be careful to get any preceding space too - if (root.data.match(/^\s/)) { - chopped = ' ' + chopped; - } - root.data = chopped; // eslint-disable-line no-param-reassign - remaining = -1; - } - } - - // Now do the same for any child nodes - nodes.forEach(function(node) { - if (remaining < 0) { - root.removeChild(node); - } else { - remaining = clampHtmlByWords(node, remaining); - } - }); - - return remaining; -} - -module.exports = { - clampHtmlByWords: clampHtmlByWords -}; diff --git a/common/static/common/js/utils/clamp-html.test.jsx b/common/static/common/js/utils/clamp-html.test.jsx deleted file mode 100644 index 0f08678446..0000000000 --- a/common/static/common/js/utils/clamp-html.test.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { clampHtmlByWords } from './clamp-html'; - -let container; -const scriptTag = ''; -const styleTag = ''; - -beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); -}); - -afterEach(() => { - document.body.removeChild(container); - container = null; -}); - -describe('ClampHtml', () => { - test.each([ - ['', 0, ''], - ['a b', 0, '…'], - ['a b', 1, 'a…'], - ['a b c', 2, 'a b…'], - ['a aa ab b', 2, 'a aa…'], - ['a aa ab ac', 2, 'a aa…'], - ['a aa aaa', 2, 'a aa…'], - ['a aa aaa ab', 3, 'a aa aaa…'], - ['a aa ab b c', 4, 'a aa ab b…'], - [scriptTag + 'a b c', 2, scriptTag + 'a b…'], - [styleTag + 'a b c', 2, styleTag + 'a b…'], - [scriptTag + styleTag + 'a b c', 2, scriptTag + styleTag + 'a b…'], - ])('clamps by words: %s, %i', (input, wordsLeft, expected) => { - const div = ReactDOM.render(
, container); - clampHtmlByWords(div, wordsLeft); - expect(div.innerHTML).toEqual(expected); - }); -}); diff --git a/lms/djangoapps/course_api/api.py b/lms/djangoapps/course_api/api.py index 4b5eeb942f..726e49d095 100644 --- a/lms/djangoapps/course_api/api.py +++ b/lms/djangoapps/course_api/api.py @@ -25,6 +25,7 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi from openedx.core.djangoapps.content.learning_sequences.api import get_course_outline from openedx.core.djangoapps.content.learning_sequences.data import CourseOutlineData from openedx.core.lib.api.view_utils import LazySequence +from openedx.features.course_experience import course_home_url from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order @@ -285,8 +286,7 @@ def get_course_run_url(request, course_id): Returns: (string): the URL to the course run associated with course_id """ - course_run_url = reverse('openedx.course_experience.course_home', args=[course_id]) - return request.build_absolute_uri(course_run_url) + return request.build_absolute_uri(course_home_url(course_id)) def get_course_members(course_key): diff --git a/lms/djangoapps/course_goals/api.py b/lms/djangoapps/course_goals/api.py index 414eda0ba7..079dfc46b7 100644 --- a/lms/djangoapps/course_goals/api.py +++ b/lms/djangoapps/course_goals/api.py @@ -2,31 +2,9 @@ Course Goals Python API """ - from opaque_keys.edx.keys import CourseKey -from rest_framework.reverse import reverse -from common.djangoapps.course_modes.models import CourseMode -from lms.djangoapps.course_goals.models import CourseGoal, GOAL_KEY_CHOICES -from openedx.features.course_experience import ENABLE_COURSE_GOALS - - -def add_course_goal_deprecated(user, course_id, goal_key): - """ - Add a new course goal for the provided user and course. If the goal - already exists, simply update and save the goal. - This method is for the deprecated version of course goals and will be removed as soon - as the newer number of days version of course goals is fully implemented. - - Arguments: - user: The user that is setting the goal - course_id (string): The id for the course the goal refers to - goal_key (string): The goal key for the new goal. - """ - course_key = CourseKey.from_string(str(course_id)) - CourseGoal.objects.update_or_create( - user=user, course_key=course_key, defaults={'goal_key': goal_key} - ) +from lms.djangoapps.course_goals.models import CourseGoal def add_course_goal(user, course_id, subscribed_to_reminders, days_per_week=None): @@ -61,58 +39,3 @@ def get_course_goal(user, course_key): return None return CourseGoal.objects.filter(user=user, course_key=course_key).first() - - -def get_goal_api_url(request): - """ - Returns the endpoint for accessing REST API. - """ - return reverse('course_goals_api:v0:course_goal-list', request=request) - - -def has_course_goal_permission(request, course_id, user_access): - """ - Returns whether the user can access the course goal functionality. - - Only authenticated users that are enrolled in a verifiable course - can use this feature. - """ - course_key = CourseKey.from_string(course_id) - has_verified_mode = CourseMode.has_verified_mode(CourseMode.modes_for_course_dict(course_key)) - return user_access['is_enrolled'] and has_verified_mode and ENABLE_COURSE_GOALS.is_enabled(course_key) - - -def get_course_goal_options(): - """ - Returns the valid options for goal keys, mapped to their translated - strings, as defined by theCourseGoal model. - """ - return dict(GOAL_KEY_CHOICES) - - -def get_course_goal_text(goal_key): - """ - Returns the translated string for the given goal key - """ - goal_options = get_course_goal_options() - return goal_options[goal_key] - - -def valid_course_goals_ordered(include_unsure=False): - """ - Returns a list of the valid options for goal keys ordered by the level of commitment. - Each option is represented as a tuple, with (goal_key, goal_string). - - This list does not return the unsure option by default since it does not have a relevant commitment level. - """ - goal_options = get_course_goal_options() - - ordered_goal_options = [] - ordered_goal_options.append((GOAL_KEY_CHOICES.certify, goal_options[GOAL_KEY_CHOICES.certify])) - ordered_goal_options.append((GOAL_KEY_CHOICES.complete, goal_options[GOAL_KEY_CHOICES.complete])) - ordered_goal_options.append((GOAL_KEY_CHOICES.explore, goal_options[GOAL_KEY_CHOICES.explore])) - - if include_unsure: - ordered_goal_options.append((GOAL_KEY_CHOICES.unsure, goal_options[GOAL_KEY_CHOICES.unsure])) - - return ordered_goal_options diff --git a/lms/djangoapps/course_goals/handlers.py b/lms/djangoapps/course_goals/handlers.py index 661d3220f5..0c25746958 100644 --- a/lms/djangoapps/course_goals/handlers.py +++ b/lms/djangoapps/course_goals/handlers.py @@ -17,7 +17,6 @@ def emit_course_goal_event(sender, instance, **kwargs): # lint-amnesty, pylint: name = 'edx.course.goal.added' if kwargs.get('created', False) else 'edx.course.goal.updated' properties = { 'courserun_key': str(instance.course_key), - 'goal_key': instance.goal_key, 'days_per_week': instance.days_per_week, 'subscribed_to_reminders': instance.subscribed_to_reminders, } diff --git a/lms/djangoapps/course_goals/models.py b/lms/djangoapps/course_goals/models.py index f2dfc8e4ef..73d69d6bd6 100644 --- a/lms/djangoapps/course_goals/models.py +++ b/lms/djangoapps/course_goals/models.py @@ -8,7 +8,6 @@ from datetime import datetime, timedelta from django.contrib.auth import get_user_model from django.db import models -from django.utils.translation import gettext_lazy as _ from edx_django_utils.cache import TieredCache from model_utils import Choices from model_utils.models import TimeStampedModel @@ -20,12 +19,11 @@ from lms.djangoapps.courseware.context_processor import get_user_timezone_or_las from openedx.core.lib.mobile_utils import is_request_from_mobile_app from openedx.features.course_experience import ENABLE_COURSE_GOALS -# Each goal is represented by a goal key and a string description. -GOAL_KEY_CHOICES = Choices( - ('certify', _('Earn a certificate')), - ('complete', _('Complete the course')), - ('explore', _('Explore the course')), - ('unsure', _('Not sure yet')), +_GOAL_KEY_CHOICES = Choices( + ('certify', 'Earn a certificate'), + ('complete', 'Complete the course'), + ('explore', 'Explore the course'), + ('unsure', 'Not sure yet'), ) User = get_user_model() @@ -57,7 +55,9 @@ class CourseGoal(models.Model): unsubscribe_token = models.UUIDField(null=True, blank=True, unique=True, editable=False, default=uuid.uuid4, help_text='Used to validate unsubscribe requests without requiring a login') - goal_key = models.CharField(max_length=100, choices=GOAL_KEY_CHOICES, default=GOAL_KEY_CHOICES.unsure) + # Deprecated and unused - replaced by days_per_week and its subscription-based approach to goals + goal_key = models.CharField(max_length=100, choices=_GOAL_KEY_CHOICES, default=_GOAL_KEY_CHOICES.unsure) + history = HistoricalRecords() def __str__(self): diff --git a/lms/djangoapps/course_goals/tests/test_views.py b/lms/djangoapps/course_goals/tests/test_views.py deleted file mode 100644 index a5fd30c6d2..0000000000 --- a/lms/djangoapps/course_goals/tests/test_views.py +++ /dev/null @@ -1,114 +0,0 @@ -""" -Unit tests for course_goals.views methods. -""" - - -from unittest import mock - -from django.test.utils import override_settings -from django.urls import reverse -from rest_framework.test import APIClient - -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import UserFactory -from lms.djangoapps.course_goals.models import CourseGoal -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order - -EVENT_NAME_ADDED = 'edx.course.goal.added' -EVENT_NAME_UPDATED = 'edx.course.goal.updated' - - -class TestCourseGoalsAPI(SharedModuleStoreTestCase): - """ - Testing the Course Goals API. - """ - - def setUp(self): - # Create a course with a verified track - super().setUp() - self.course = CourseFactory.create(emit_signals=True) - - self.user = UserFactory.create(username='john', email='lennon@thebeatles.com', password='password') - CourseEnrollment.enroll(self.user, self.course.id) - - self.client = APIClient(enforce_csrf_checks=True) - self.client.login(username=self.user.username, password=self.user.password) - self.client.force_authenticate(user=self.user) - - self.apiUrl = reverse('course_goals_api:v0:course_goal-list') - - @mock.patch('lms.djangoapps.course_goals.handlers.segment.track') - @override_settings(LMS_SEGMENT_KEY="foobar") - def test_add_valid_goal(self, segment_call): - """ Ensures a correctly formatted post succeeds.""" - response = self.post_course_goal(valid=True, goal_key='certify') - segment_call.assert_called_once_with(self.user.id, EVENT_NAME_ADDED, { - 'courserun_key': str(self.course.id), - 'goal_key': 'certify', - 'days_per_week': 0, - 'subscribed_to_reminders': False, - }) - assert response.status_code == 201 - - current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id) - assert len(current_goals) == 1 - assert current_goals[0].goal_key == 'certify' - - def test_add_invalid_goal(self): - """ Ensures an incorrectly formatted post does not succeed. """ - response = self.post_course_goal(valid=False) - assert response.status_code == 400 - assert len(CourseGoal.objects.filter(user=self.user, course_key=self.course.id)) == 0 - - def test_add_without_goal_key(self): - """ Ensures if no goal key provided, post does not succeed. """ - response = self.post_course_goal(goal_key=None) - assert len(CourseGoal.objects.filter(user=self.user, course_key=self.course.id)) == 0 - self.assertContains( - response=response, - text='Please provide a valid goal key from following options.', - status_code=400 - ) - - @mock.patch('lms.djangoapps.course_goals.handlers.segment.track') - @override_settings(LMS_SEGMENT_KEY="foobar") - def test_update_goal(self, segment_call): - """ Ensures that repeated course goal post events do not create new instances of the goal. """ - self.post_course_goal(valid=True, goal_key='explore') - self.post_course_goal(valid=True, goal_key='certify') - self.post_course_goal(valid=True, goal_key='unsure') - - segment_call.assert_any_call(self.user.id, EVENT_NAME_ADDED, { - 'courserun_key': str(self.course.id), 'goal_key': 'explore', - 'days_per_week': 0, - 'subscribed_to_reminders': False, - }) - segment_call.assert_any_call(self.user.id, EVENT_NAME_UPDATED, { - 'courserun_key': str(self.course.id), 'goal_key': 'certify', - 'days_per_week': 0, - 'subscribed_to_reminders': False, - }) - segment_call.assert_any_call(self.user.id, EVENT_NAME_UPDATED, { - 'courserun_key': str(self.course.id), 'goal_key': 'unsure', - 'days_per_week': 0, - 'subscribed_to_reminders': False, - }) - current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id) - assert len(current_goals) == 1 - assert current_goals[0].goal_key == 'unsure' - - def post_course_goal(self, valid=True, goal_key='certify'): - """ - Sends a post request to set a course goal and returns the response. - """ - goal_key = goal_key if valid else 'invalid' - post_data = { - 'course_key': self.course.id, - 'user': self.user.username, - } - if goal_key: - post_data['goal_key'] = goal_key - - response = self.client.post(self.apiUrl, post_data) - return response diff --git a/lms/djangoapps/course_goals/urls.py b/lms/djangoapps/course_goals/urls.py deleted file mode 100644 index fd55934ef6..0000000000 --- a/lms/djangoapps/course_goals/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Course Goals URLs -""" - - -from django.urls import include, path -from rest_framework import routers - -from .views import CourseGoalViewSet - -router = routers.DefaultRouter() -router.register(r'course_goals', CourseGoalViewSet, basename='course_goal') - -urlpatterns = [ - path('v0/', include((router.urls, "api"), namespace='v0')), -] diff --git a/lms/djangoapps/course_goals/views.py b/lms/djangoapps/course_goals/views.py deleted file mode 100644 index a2992a64c5..0000000000 --- a/lms/djangoapps/course_goals/views.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Course Goals Views - includes REST API -""" - - -from django.contrib.auth import get_user_model -from django.http import JsonResponse -from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication -from opaque_keys.edx.keys import CourseKey -from rest_framework import permissions, serializers, status, viewsets -from rest_framework.authentication import SessionAuthentication -from rest_framework.response import Response - -from lms.djangoapps.course_goals.api import get_course_goal_options -from lms.djangoapps.course_goals.models import GOAL_KEY_CHOICES, CourseGoal -from openedx.core.lib.api.permissions import IsStaffOrOwner - -User = get_user_model() - - -class CourseGoalSerializer(serializers.ModelSerializer): - """ - Serializes CourseGoal models. - """ - user = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all()) - - class Meta: - model = CourseGoal - fields = ('user', 'course_key', 'goal_key') - - -class CourseGoalViewSet(viewsets.ModelViewSet): - """ - API calls to create and update a course goal. - - Validates incoming data to ensure that course_key maps to an actual - course and that the goal_key is a valid option. - - **Use Case** - * Create a new goal for a user. - * Update an existing goal for a user - - **Example Requests** - POST /api/course_goals/v0/course_goals/ - Request data: {"course_key": , "goal_key": "", "user": ""} - - Returns Http400 response if the course_key does not map to a known - course or if the goal_key does not map to a valid goal key. - """ - authentication_classes = (JwtAuthentication, SessionAuthentication,) - permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner,) - queryset = CourseGoal.objects.all() - serializer_class = CourseGoalSerializer - - # Another version of this endpoint exists in ../course_home_api/outline/views.py - # This version is used by the legacy frontend and is deprecated - def create(self, post_data): # lint-amnesty, pylint: disable=arguments-differ - """ Create a new goal if one does not exist, otherwise update the existing goal. """ - # Ensure goal_key is valid - goal_options = get_course_goal_options() - goal_key = post_data.data.get('goal_key') - if not goal_key: - return Response( - 'Please provide a valid goal key from following options. (options= {goal_options}).'.format( - goal_options=goal_options, - ), - status=status.HTTP_400_BAD_REQUEST, - ) - elif goal_key not in goal_options: - return Response( - 'Provided goal key, {goal_key}, is not a valid goal key (options= {goal_options}).'.format( - goal_key=goal_key, - goal_options=goal_options, - ), - status=status.HTTP_400_BAD_REQUEST, - ) - - # Ensure course key is valid - course_key = CourseKey.from_string(post_data.data['course_key']) - if not course_key: - return Response( - 'Provided course_key ({course_key}) does not map to a course.'.format( - course_key=course_key - ), - status=status.HTTP_400_BAD_REQUEST, - ) - - user = post_data.user - goal = CourseGoal.objects.filter(user=user.id, course_key=course_key).first() - if goal: - goal.goal_key = goal_key - goal.save(update_fields=['goal_key']) - else: - CourseGoal.objects.create( - user=user, - course_key=course_key, - goal_key=goal_key, - ) - data = { - 'goal_key': str(goal_key), - 'goal_text': str(goal_options[goal_key]), - 'is_unsure': goal_key == GOAL_KEY_CHOICES.unsure, - } - return JsonResponse(data, content_type="application/json", status=(200 if goal else 201)) # lint-amnesty, pylint: disable=redundant-content-type-for-json-response diff --git a/lms/djangoapps/course_home_api/outline/tests/test_goals.py b/lms/djangoapps/course_home_api/outline/tests/test_goals.py index aa7a97585f..699531c93d 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_goals.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_goals.py @@ -72,7 +72,6 @@ class TestCourseGoalsAPI(SharedModuleStoreTestCase): 'courserun_key': str(self.course.id), 'days_per_week': 1, 'subscribed_to_reminders': True, - 'goal_key': 'unsure', }) current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id) @@ -89,7 +88,6 @@ class TestCourseGoalsAPI(SharedModuleStoreTestCase): 'courserun_key': str(self.course.id), 'days_per_week': 1, 'subscribed_to_reminders': True, - 'goal_key': 'unsure', }) self.save_course_goal(3, True) @@ -97,7 +95,6 @@ class TestCourseGoalsAPI(SharedModuleStoreTestCase): 'courserun_key': str(self.course.id), 'days_per_week': 3, 'subscribed_to_reminders': True, - 'goal_key': 'unsure', }) self.save_course_goal(5, False) @@ -105,7 +102,6 @@ class TestCourseGoalsAPI(SharedModuleStoreTestCase): 'courserun_key': str(self.course.id), 'days_per_week': 5, 'subscribed_to_reminders': False, - 'goal_key': 'unsure', }) current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id) 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 2714f3ece5..ecbf803095 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -19,7 +19,6 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import CourseInstructorRole from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests -from lms.djangoapps.course_home_api.toggles import COURSE_HOME_USE_LEGACY_FRONTEND from openedx.core.djangoapps.content.learning_sequences.api import replace_course_outline from openedx.core.djangoapps.content.learning_sequences.data import CourseOutlineData, CourseVisibility from openedx.core.djangoapps.course_date_signals.utils import MIN_DURATION @@ -148,13 +147,6 @@ class OutlineTabTestViews(BaseCourseHomeTests): response = self.client.get(url) assert response.status_code == 404 - @override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) - @ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED) - def test_legacy_view_enabled(self, enrollment_mode): - CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode) - response = self.client.get(self.url) - assert response.status_code == 404 - @ddt.data(True, False) def test_welcome_message(self, welcome_message_is_dismissed): CourseEnrollment.enroll(self.user, self.course.id) diff --git a/lms/djangoapps/course_home_api/outline/views.py b/lms/djangoapps/course_home_api/outline/views.py index 863f9c07dc..69460f13cb 100644 --- a/lms/djangoapps/course_home_api/outline/views.py +++ b/lms/djangoapps/course_home_api/outline/views.py @@ -6,7 +6,6 @@ from datetime import datetime, timezone from completion.exceptions import UnavailableCompletionData # lint-amnesty, pylint: disable=wrong-import-order from completion.utilities import get_key_to_last_completed_block # lint-amnesty, pylint: disable=wrong-import-order from django.conf import settings # lint-amnesty, pylint: disable=wrong-import-order -from django.http.response import Http404 # lint-amnesty, pylint: disable=wrong-import-order from django.shortcuts import get_object_or_404 # lint-amnesty, pylint: disable=wrong-import-order from django.urls import reverse # lint-amnesty, pylint: disable=wrong-import-order from django.utils.translation import gettext as _ # lint-amnesty, pylint: disable=wrong-import-order @@ -29,9 +28,6 @@ from lms.djangoapps.course_goals.api import ( ) from lms.djangoapps.course_goals.models import CourseGoal from lms.djangoapps.course_home_api.outline.serializers import OutlineTabSerializer -from lms.djangoapps.course_home_api.toggles import ( - course_home_legacy_is_active, -) from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_info_section, get_course_with_access @@ -54,7 +50,6 @@ from openedx.features.course_experience.url_helpers import get_learning_mfe_home from openedx.features.course_experience.utils import get_course_outline_block_tree, get_start_block from openedx.features.discounts.utils import generate_offer_data from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order class UnableToDismissWelcomeMessage(APIException): @@ -166,10 +161,6 @@ class OutlineTabView(RetrieveAPIView): def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) - course_usage_key = modulestore().make_course_usage_key(course_key) # pylint: disable=unused-variable - - if course_home_legacy_is_active(course_key): - raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) @@ -384,7 +375,6 @@ def dismiss_welcome_message(request): # pylint: disable=missing-function-docstr @permission_classes((IsAuthenticated,)) def save_course_goal(request): # pylint: disable=missing-function-docstring course_id = request.data.get('course_id') - goal_key = request.data.get('goal_key') days_per_week = request.data.get('days_per_week') subscribed_to_reminders = request.data.get('subscribed_to_reminders') diff --git a/lms/djangoapps/course_home_api/toggles.py b/lms/djangoapps/course_home_api/toggles.py index 8c8b8ad215..bc47acf30b 100644 --- a/lms/djangoapps/course_home_api/toggles.py +++ b/lms/djangoapps/course_home_api/toggles.py @@ -11,29 +11,12 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='course_home') COURSE_HOME_MICROFRONTEND_PROGRESS_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_home_mfe_progress_tab', # lint-amnesty, pylint: disable=toggle-missing-annotation __name__) -# .. toggle_name: course_home.course_home_use_legacy_frontend -# .. toggle_implementation: CourseWaffleFlag -# .. toggle_default: False -# .. toggle_description: This flag enables the use of the legacy view of course home as the default course frontend. -# .. Learning microfrontend (frontend-app-learning) is now an opt-out view, where if this flag is -# .. enabled the default changes from the learning microfrontend to legacy. -# .. toggle_warnings: None -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2021-06-11 -# .. toggle_target_removal_date: 2022-05-15 -# .. toggle_tickets: https://openedx.atlassian.net/browse/AA-797 -COURSE_HOME_USE_LEGACY_FRONTEND = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_home_use_legacy_frontend', __name__) - - -def course_home_legacy_is_active(course_key): - return COURSE_HOME_USE_LEGACY_FRONTEND.is_enabled(course_key) or course_key.deprecated - def course_home_mfe_progress_tab_is_active(course_key): # Avoiding a circular dependency from .models import DisableProgressPageStackedConfig return ( - (not course_home_legacy_is_active(course_key)) and + not course_key.deprecated and COURSE_HOME_MICROFRONTEND_PROGRESS_TAB.is_enabled(course_key) and not DisableProgressPageStackedConfig.current(course_key=course_key).disabled ) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index d9d20018f0..4123da5f38 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -10,8 +10,6 @@ import datetime import crum from babel.dates import format_timedelta from django.conf import settings -from django.urls import reverse -from django.utils.formats import date_format from django.utils.functional import cached_property from django.utils.translation import get_language, to_locale from django.utils.translation import gettext as _ @@ -19,14 +17,14 @@ from django.utils.translation import gettext_lazy from lazy import lazy from pytz import utc -from common.djangoapps.course_modes.models import CourseMode, get_cosmetic_verified_display_price +from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.api import get_active_web_certificate, can_show_certificate_available_date_field from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, can_show_verified_upgrade from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangolib.markup import HTML, Text +from openedx.core.djangolib.markup import HTML from openedx.features.course_duration_limits.access import get_user_course_expiration_date -from openedx.features.course_experience import RELATIVE_DATES_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages +from openedx.features.course_experience import RELATIVE_DATES_FLAG from common.djangoapps.student.models import CourseEnrollment from .context_processor import user_timezone_locale_prefs @@ -79,12 +77,6 @@ class DateSummary: """Extra detail to display as a tooltip.""" return None - def register_alerts(self, request, course): - """ - Registers any relevant course alerts given the current request. - """ - pass # lint-amnesty, pylint: disable=unnecessary-pass - @property def date(self): """This summary's date.""" @@ -280,35 +272,6 @@ class CourseStartDate(DateSummary): return gettext_lazy('Enrollment Date') return gettext_lazy('Course starts') - def register_alerts(self, request, course): - """ - Registers an alert if the course has not started yet. - """ - is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) - if not course.start or not is_enrolled: - return - days_until_start = (course.start - self.current_time).days - if course.start > self.current_time: - if days_until_start > 0: - CourseHomeMessages.register_info_message( - request, - Text(_( - "Don't forget to add a calendar reminder!" - )), - title=Text(_("Course starts in {time_remaining_string} on {course_start_date}.")).format( - time_remaining_string=self.time_remaining_string, - course_start_date=self.long_date_html, - ) - ) - else: - CourseHomeMessages.register_info_message( - request, - Text(_("Course starts in {time_remaining_string} at {course_start_time}.")).format( - time_remaining_string=self.time_remaining_string, - course_start_time=self.short_time_html, - ) - ) - class CourseEndDate(DateSummary): """ @@ -361,34 +324,6 @@ class CourseEndDate(DateSummary): def date_type(self): return 'course-end-date' - def register_alerts(self, request, course): - """ - Registers an alert if the end date is approaching. - """ - is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) - if not course.start or not course.end or self.current_time < course.start or not is_enrolled: - return - days_until_end = (course.end - self.current_time).days - if course.end > self.current_time and days_until_end <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS: - if days_until_end > 0: - CourseHomeMessages.register_info_message( - request, - Text(self.description), - title=Text(_('This course is ending in {time_remaining_string} on {course_end_date}.')).format( - time_remaining_string=self.time_remaining_string, - course_end_date=self.long_date_html, - ) - ) - else: - CourseHomeMessages.register_info_message( - request, - Text(self.description), - title=Text(_('This course is ending in {time_remaining_string} at {course_end_time}.')).format( - time_remaining_string=self.time_remaining_string, - course_end_time=self.short_time_html, - ) - ) - class CourseAssignmentDate(DateSummary): """ @@ -512,31 +447,6 @@ class CertificateAvailableDate(DateSummary): ) if mode.slug != CourseMode.AUDIT ) - def register_alerts(self, request, course): - """ - Registers an alert close to the certificate delivery date. - """ - is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) - if not is_enrolled or not self.is_enabled or (course.end and course.end > self.current_time): - return - if self.date > self.current_time: - CourseHomeMessages.register_info_message( - request, - Text(_( - 'If you have earned a certificate, you will be able to access it {time_remaining_string}' - ' from now. You will also be able to view your certificates on your {learner_profile_link}.' - )).format( - time_remaining_string=self.time_remaining_string, - learner_profile_link=HTML( - '{learner_profile_name}' - ).format( - learner_profile_url=reverse('learner_profile', kwargs={'username': request.user.username}), - learner_profile_name=_('Learner Profile'), - ), - ), - title=Text(_('We are working on generating course certificates.')) - ) - class VerifiedUpgradeDeadlineDate(DateSummary): """ @@ -608,44 +518,6 @@ class VerifiedUpgradeDeadlineDate(DateSummary): # according to their locale. return _('by {date}') - def register_alerts(self, request, course): - """ - Registers an alert if the verification deadline is approaching. - """ - upgrade_price = get_cosmetic_verified_display_price(course) - if not UPGRADE_DEADLINE_MESSAGE.is_enabled(course.id) or not self.is_enabled or not upgrade_price: - return - days_left_to_upgrade = (self.date - self.current_time).days - if self.date > self.current_time and days_left_to_upgrade <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS: - upgrade_message = _( - "Don't forget, you have {time_remaining_string} left to upgrade to a Verified Certificate." - ).format(time_remaining_string=self.time_remaining_string) - if self._dynamic_deadline() is not None: - upgrade_message = _( - "Don't forget to upgrade to a verified certificate by {localized_date}." - ).format(localized_date=date_format(self.date)) - CourseHomeMessages.register_info_message( - request, - Text(_( - 'In order to qualify for a certificate, you must meet all course grading ' - 'requirements, upgrade before the course deadline, and successfully verify ' - 'your identity on {platform_name} if you have not done so already.{button_panel}' - )).format( - platform_name=settings.PLATFORM_NAME, - button_panel=HTML( - '
' - '{upgrade_label}' - '
' - ).format( - upgrade_url=self.link, - upgrade_label=Text(_('Upgrade ({upgrade_price})')).format(upgrade_price=upgrade_price), - ) - ), - title=Text(upgrade_message) - ) - class VerificationDeadlineDate(DateSummary): """ diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 911819e855..7f774faf62 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -7,15 +7,15 @@ perform some LMS-specific tab display gymnastics for the Entrance Exams feature from django.conf import settings from django.utils.translation import gettext as _ from django.utils.translation import gettext_noop +from xmodule.tabs import CourseTab, CourseTabList, key_checker from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.entrance_exams import user_can_skip_entrance_exam -from lms.djangoapps.course_home_api.toggles import course_home_legacy_is_active, course_home_mfe_progress_tab_is_active +from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active from openedx.core.lib.course_tabs import CourseTabPluginManager -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, default_course_url_name +from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, default_course_url from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url from common.djangoapps.student.models import CourseEnrollment -from xmodule.tabs import CourseTab, CourseTabList, course_reverse_func_from_name_func, key_checker # lint-amnesty, pylint: disable=wrong-import-order class EnrolledTab(CourseTab): @@ -41,13 +41,8 @@ class CoursewareTab(EnrolledTab): supports_preview_menu = True def __init__(self, tab_dict): - def link_func(course, reverse_func): - if course_home_legacy_is_active(course.id): - reverse_name_func = lambda course: default_course_url_name(course.id) - url_func = course_reverse_func_from_name_func(reverse_name_func) - return url_func(course, reverse_func) - else: - return get_learning_mfe_home_url(course_key=course.id, url_fragment='home') + def link_func(course, _reverse_func): + return default_course_url(course.id) tab_dict['link_func'] = link_func super().__init__(tab_dict) diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 4b863fbc8e..244a5fd2b3 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -9,7 +9,6 @@ from unittest import mock from unittest.mock import patch import ddt import pytz -from ccx_keys.locator import CCXLocator from django.conf import settings from django.test.utils import override_settings from django.urls import reverse @@ -29,13 +28,11 @@ from xmodule.modulestore.tests.utils import TEST_DATA_DIR from xmodule.modulestore.xml_importer import import_course_from_xml from common.djangoapps.course_modes.models import CourseMode -from lms.djangoapps.ccx.tests.factories import CcxFactory from openedx.core.djangoapps.models.course_details import CourseDetails -from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, course_home_url from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML from openedx.features.course_experience.waffle import WAFFLE_NAMESPACE as COURSE_EXPERIENCE_WAFFLE_NAMESPACE -from lms.djangoapps.course_home_api.toggles import COURSE_HOME_USE_LEGACY_FRONTEND -from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentAllowedFactory, UserFactory +from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory from common.djangoapps.track.tests import EventTrackingTestCase from common.djangoapps.util.milestones_helpers import get_prerequisite_courses_display, set_prerequisite_courses @@ -47,7 +44,6 @@ SHIB_ERROR_STR = "The currently logged-in user account does not have permission @ddt.ddt -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTrackingTestCase, MilestonesTestCaseMixin): """ Tests about xblock. @@ -124,13 +120,7 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra self.setup_user() url = reverse('about_course', args=[str(self.course.id)]) resp = self.client.get(url) - # should be redirected - assert resp.status_code == 302 - # follow this time, and check we're redirected to the course home page - resp = self.client.get(url, follow=True) - target_url = resp.redirect_chain[-1][0] - course_home_url = reverse('openedx.course_experience.course_home', args=[str(self.course.id)]) - assert target_url.endswith(course_home_url) + self.assertRedirects(resp, course_home_url(self.course.id), fetch_redirect_response=False) @patch.dict(settings.FEATURES, {'ENABLE_COURSE_HOME_REDIRECT': False}) @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) @@ -229,7 +219,6 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra self.assertContains(resp, "Enroll Now") -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): """ Tests for the course about page @@ -273,7 +262,6 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): self.assertContains(resp, self.xml_data) -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase): """ This test case will check the About page when a course has a capped enrollment @@ -316,7 +304,6 @@ class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, SharedModuleSt self.assertNotContains(resp, REG_STR) -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutWithInvitationOnly(SharedModuleStoreTestCase): """ This test case will check the About page when a course is invitation only. @@ -356,7 +343,6 @@ class AboutWithInvitationOnly(SharedModuleStoreTestCase): self.assertContains(resp, REG_STR) -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutWithClosedEnrollment(ModuleStoreTestCase): """ This test case will check the About page for a course that has enrollment start/end @@ -393,7 +379,6 @@ class AboutWithClosedEnrollment(ModuleStoreTestCase): @ddt.ddt -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class AboutSidebarHTMLTestCase(SharedModuleStoreTestCase): """ This test case will check the About page for the content in the HTML sidebar. @@ -433,38 +418,3 @@ class AboutSidebarHTMLTestCase(SharedModuleStoreTestCase): self.assertContains(resp, itemfactory_data) else: self.assertNotContains(resp, '
') - - -class CourseAboutTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase): - """ - Test for unenrolled student tries to access ccx. - Note: Only CCX coach can enroll a student in CCX. In sum self-registration not allowed. - """ - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.course = CourseFactory.create() - - def setUp(self): - super().setUp() - - # Create ccx coach account - self.coach = coach = AdminFactory.create(password="test") - self.client.login(username=coach.username, password="test") - - @override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) - def test_redirect_to_dashboard_unenrolled_ccx(self): - """ - Assert that when unenrolled user tries to access CCX do not allow the user to self-register. - Redirect them to their student dashboard - """ - - # create ccx - ccx = CcxFactory(course_id=self.course.id, coach=self.coach) - ccx_locator = CCXLocator.from_course_locator(self.course.id, str(ccx.id)) - - self.setup_user() - url = reverse('openedx.course_experience.course_home', args=[ccx_locator]) - response = self.client.get(url) - expected = reverse('dashboard') - self.assertRedirects(response, expected, status_code=302, target_status_code=200) diff --git a/lms/djangoapps/courseware/tests/test_course_survey.py b/lms/djangoapps/courseware/tests/test_course_survey.py index 5cfae9047c..d729add1b6 100644 --- a/lms/djangoapps/courseware/tests/test_course_survey.py +++ b/lms/djangoapps/courseware/tests/test_course_survey.py @@ -5,20 +5,19 @@ Python tests for the Survey workflows from collections import OrderedDict from copy import deepcopy +from urllib.parse import quote from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.urls import reverse -from edx_toggles.toggles.testutils import override_waffle_flag from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from common.test.utils import XssTestMixin -from lms.djangoapps.course_home_api.toggles import COURSE_HOME_USE_LEGACY_FRONTEND from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.survey.models import SurveyAnswer, SurveyForm +from openedx.features.course_experience import course_home_url -@override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTestMixin): """ All tests for the views.py file @@ -78,7 +77,7 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe """ Helper method to assert that all known redirect points do redirect as expected """ - for view_name in ['courseware', 'openedx.course_experience.course_home', 'progress']: + for view_name in ['courseware', 'progress']: resp = self.client.get( reverse( view_name, @@ -95,7 +94,7 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe Helper method to asswer that all known conditionally redirect points do not redirect as expected """ - for view_name in ['courseware', 'openedx.course_experience.course_home', 'progress']: + for view_name in ['courseware', 'progress']: resp = self.client.get( reverse( view_name, @@ -119,17 +118,20 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe def test_anonymous_user_visiting_course_with_survey(self): """ - Verifies that anonymous user going to the courseware home with an unanswered survey is not - redirected to survey and home page renders without server error. + Verifies that anonymous user going to the course with an unanswered survey is not + redirected to survey. """ self.logout() resp = self.client.get( reverse( - 'openedx.course_experience.course_home', + 'courseware', kwargs={'course_id': str(self.course.id)} ) ) - assert resp.status_code == 200 + self.assertRedirects( + resp, + f'/login?next=/courses/{quote(str(self.course.id))}/courseware' + ) def test_visiting_course_with_existing_answers(self): """ @@ -206,10 +208,10 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe kwargs={'course_id': str(self.course_with_bogus_survey.id)} ) ) - course_home_path = 'openedx.course_experience.course_home' self.assertRedirects( resp, - reverse(course_home_path, kwargs={'course_id': str(self.course_with_bogus_survey.id)}) + course_home_url(self.course_with_bogus_survey.id), + fetch_redirect_response=False, ) def test_visiting_survey_with_no_course_survey(self): @@ -223,10 +225,10 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe kwargs={'course_id': str(self.course_without_survey.id)} ) ) - course_home_path = 'openedx.course_experience.course_home' self.assertRedirects( resp, - reverse(course_home_path, kwargs={'course_id': str(self.course_without_survey.id)}) + course_home_url(self.course_without_survey.id), + fetch_redirect_response=False, ) def test_survey_xss(self): diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 3e914168fd..741a02bea4 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -9,7 +9,6 @@ import crum import ddt import waffle # lint-amnesty, pylint: disable=invalid-django-waffle-import from django.conf import settings -from django.contrib.messages.middleware import MessageMiddleware from django.test import RequestFactory from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag @@ -22,7 +21,6 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory from lms.djangoapps.commerce.models import CommerceConfiguration -from lms.djangoapps.course_home_api.toggles import COURSE_HOME_USE_LEGACY_FRONTEND from lms.djangoapps.courseware.courses import get_course_date_blocks from lms.djangoapps.courseware.date_summary import ( CertificateAvailableDate, @@ -45,14 +43,8 @@ from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVer from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration -from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from openedx.features.course_duration_limits.models import CourseDurationLimitConfig -from openedx.features.course_experience import ( - DISABLE_UNIFIED_COURSE_TAB_FLAG, - RELATIVE_DATES_FLAG, - UPGRADE_DEADLINE_MESSAGE, - CourseHomeMessages -) +from openedx.features.course_experience import RELATIVE_DATES_FLAG from common.djangoapps.student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory @@ -82,13 +74,6 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): response = self.client.get(url) self.assertNotContains(response, 'date-summary', status_code=302) - @override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) - def test_course_home_logged_out(self): - course = create_course_run() - url = reverse('openedx.course_experience.course_home', args=(course.id,)) - response = self.client.get(url) - assert 200 == response.status_code - # Tests for which blocks are enabled def assert_block_types(self, course, user, expected_blocks): """Assert that the enabled block types for this course are as expected.""" @@ -424,53 +409,6 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): assert block.date == datetime.now(utc) assert block.title == 'current_datetime' - @ddt.data( - 'info', - 'openedx.course_experience.course_home', - ) - @override_waffle_flag(COURSE_HOME_USE_LEGACY_FRONTEND, active=True) - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=False) - def test_todays_date_no_timezone(self, url_name): - with freeze_time('2015-01-02'): - course = create_course_run() - user = create_user() - self.client.login(username=user.username, password=TEST_PASSWORD) - - html_elements = [ - '

Upcoming Dates

', - '
Upcoming Dates', - '
', - f'/course/{course.id}/dates', - ] - url = reverse(url_name, args=(course.id,)) - - def assert_html_elements(assert_function, user): - self.client.login(username=user.username, password=TEST_PASSWORD) - response = self.client.get(url, follow=True) - if user.is_staff: - for html in html_elements: - assert_function(response, html) - else: - assert 404 == response.status_code - self.client.logout() - - with freeze_time('2015-01-02'): - unenrolled_user = create_user() - assert_html_elements(self.assertNotContains, unenrolled_user) - - staff_user = create_user() - staff_user.is_staff = True - staff_user.save() - assert_html_elements(self.assertContains, staff_user) - - enrolled_user = create_user() - CourseEnrollmentFactory(course_id=course.id, user=enrolled_user, mode=CourseMode.VERIFIED) - assert_html_elements(self.assertContains, enrolled_user) - - -@ddt.ddt -class TestDateAlerts(SharedModuleStoreTestCase): - """ - Unit tests for date alerts. - """ - def setUp(self): - super().setUp() - with freeze_time('2017-07-01 09:00:00'): - self.course = create_course_run(days_till_start=0) - self.course.certificate_available_date = self.course.start + timedelta(days=21) - enable_course_certificates(self.course) - self.enrollment = CourseEnrollmentFactory(course_id=self.course.id, mode=CourseMode.AUDIT) - self.request = RequestFactory().request() - self.request.session = {} - self.request.user = self.enrollment.user - MessageMiddleware().process_request(self.request) - - @ddt.data( - ['2017-01-01 09:00:00', 'in 6 months on -% if display_reset_dates_banner: - -% endif <%def name="course_name()"> <% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %> diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 9d0cd188d2..b80ec5d77f 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -12,12 +12,10 @@ from django.urls import reverse from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.helpers import enrollment_mode_display from common.djangoapps.student.helpers import user_has_passing_grade_in_course -from lms.djangoapps.course_home_api.toggles import course_home_legacy_is_active from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string from openedx.core.djangolib.markup import HTML, Text -from openedx.features.course_experience import course_home_url_name -from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url +from openedx.features.course_experience import course_home_url from common.djangoapps.student.helpers import ( VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, @@ -82,7 +80,7 @@ from lms.djangoapps.experiments.utils import UPSELL_TRACKING_FLAG % endif >
- <% course_target = reverse(course_home_url_name(course_overview.id), args=[str(course_overview.id)]) if course_home_legacy_is_active(course_overview.id) else get_learning_mfe_home_url(course_key=course_overview.id, url_fragment="home") %> + <% course_target = course_home_url(course_overview.id) %>

${_('Course details')}