Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync opencraft-release/quince.1 with Upstream 20241125-1732494129 #713

Open
wants to merge 5 commits into
base: opencraft-release/quince.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lms/djangoapps/courseware/tests/test_about.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ def test_pre_requisite_course(self):
assert resp.status_code == 200
pre_requisite_courses = get_prerequisite_courses_display(course)
pre_requisite_course_about_url = reverse('about_course', args=[str(pre_requisite_courses[0]['key'])])
assert '<span class="important-dates-item-text pre-requisite"><a href="{}">{}</a></span>'.format(pre_requisite_course_about_url, pre_requisite_courses[0]['display']) in resp.content.decode(resp.charset).strip('\n') # pylint: disable=line-too-long
assert (
f'You must successfully complete <a href="{pre_requisite_course_about_url}">'
f'{pre_requisite_courses[0]["display"]}</a> before you begin this course.'
) in resp.content.decode(resp.charset).strip('\n')

@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
def test_about_page_unfulfilled_prereqs(self):
Expand Down Expand Up @@ -190,7 +193,10 @@ def test_about_page_unfulfilled_prereqs(self):
assert resp.status_code == 200
pre_requisite_courses = get_prerequisite_courses_display(course)
pre_requisite_course_about_url = reverse('about_course', args=[str(pre_requisite_courses[0]['key'])])
assert '<span class="important-dates-item-text pre-requisite"><a href="{}">{}</a></span>'.format(pre_requisite_course_about_url, pre_requisite_courses[0]['display']) in resp.content.decode(resp.charset).strip('\n') # pylint: disable=line-too-long
assert (
f'You must successfully complete <a href="{pre_requisite_course_about_url}">'
f'{pre_requisite_courses[0]["display"]}</a> before you begin this course.'
) in resp.content.decode(resp.charset).strip('\n')

url = reverse('about_course', args=[str(pre_requisite_course.id)])
resp = self.client.get(url)
Expand Down
2 changes: 2 additions & 0 deletions lms/djangoapps/instructor/enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
get_event_transaction_id,
set_event_transaction_type
)
from lms.djangoapps.branding.api import get_logo_url_for_email
from lms.djangoapps.courseware.models import StudentModule
from lms.djangoapps.grades.api import constants as grades_constants
from lms.djangoapps.grades.api import disconnect_submissions_signal_receiver
Expand Down Expand Up @@ -456,6 +457,7 @@ def get_email_params(course, auto_enroll, secure=True, course_key=None, display_
'contact_mailing_address': contact_mailing_address,
'platform_name': platform_name,
'site_configuration_values': configuration_helpers.get_current_site_configuration_values(),
'logo_url': get_logo_url_for_email(),
}
return email_params

Expand Down
16 changes: 16 additions & 0 deletions lms/djangoapps/instructor/tests/test_enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAllowed, anonymous_id_for_user
from common.djangoapps.student.roles import CourseCcxCoachRole
from common.djangoapps.student.tests.factories import AdminFactory, UserFactory
from lms.djangoapps.branding.api import get_logo_url_for_email
from lms.djangoapps.ccx.tests.factories import CcxFactory
from lms.djangoapps.course_blocks.api import get_course_blocks
from lms.djangoapps.courseware.models import StudentModule
Expand Down Expand Up @@ -937,6 +938,7 @@ def setUpClass(cls):
)
cls.course_about_url = cls.course_url + 'about'
cls.registration_url = f'https://{site}/register'
cls.logo_url = get_logo_url_for_email()

def test_normal_params(self):
# For a normal site, what do we expect to get for the URLs?
Expand All @@ -947,6 +949,7 @@ def test_normal_params(self):
assert result['course_about_url'] == self.course_about_url
assert result['registration_url'] == self.registration_url
assert result['course_url'] == self.course_url
assert result['logo_url'] == self.logo_url

def test_marketing_params(self):
# For a site with a marketing front end, what do we expect to get for the URLs?
Expand All @@ -959,6 +962,19 @@ def test_marketing_params(self):
assert result['course_about_url'] is None
assert result['registration_url'] == self.registration_url
assert result['course_url'] == self.course_url
assert result['logo_url'] == self.logo_url

@patch('lms.djangoapps.instructor.enrollment.get_logo_url_for_email', return_value='https://www.logo.png')
def test_logo_url_params(self, mock_get_logo_url_for_email):
# Verify that the logo_url is correctly set in the email params
result = get_email_params(self.course, False)

assert result['auto_enroll'] is False
assert result['course_about_url'] == self.course_about_url
assert result['registration_url'] == self.registration_url
assert result['course_url'] == self.course_url
mock_get_logo_url_for_email.assert_called_once()
assert result['logo_url'] == 'https://www.logo.png'


@ddt.ddt
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/learner_home/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from common.djangoapps.course_modes.models import CourseMode
from openedx.features.course_experience import course_home_url
from xmodule.data import CertificatesDisplayBehaviors
from lms.djangoapps.learner_home.utils import course_progress_url


class LiteralField(serializers.Field):
Expand Down Expand Up @@ -116,7 +117,7 @@ def get_homeUrl(self, instance):
return course_home_url(instance.course_id)

def get_progressUrl(self, instance):
return reverse("progress", kwargs={"course_id": instance.course_id})
return course_progress_url(instance.course_id)

def get_unenrollUrl(self, instance):
return reverse("course_run_refund_status", args=[instance.course_id])
Expand Down
26 changes: 25 additions & 1 deletion lms/djangoapps/learner_home/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
SuggestedCourseSerializer,
UnfulfilledEntitlementSerializer,
)

from lms.djangoapps.learner_home.utils import course_progress_url
from lms.djangoapps.learner_home.test_utils import (
datetime_to_django_format,
random_bool,
Expand Down Expand Up @@ -224,6 +224,30 @@ def test_missing_resume_url(self):
# Then the resumeUrl is None, which is allowed
self.assertIsNone(output_data["resumeUrl"])

def is_progress_url_matching_course_home_mfe_progress_tab_is_active(self):
"""
Compares the progress URL generated by CourseRunSerializer to the expected progress URL.

:return: True if the generated progress URL matches the expected, False otherwise.
"""
input_data = self.create_test_enrollment()
input_context = self.create_test_context(input_data.course.id)
output_data = CourseRunSerializer(input_data, context=input_context).data
return output_data['progressUrl'] == course_progress_url(input_data.course.id)

@mock.patch('lms.djangoapps.learner_home.utils.course_home_mfe_progress_tab_is_active')
def test_progress_url(self, mock_course_home_mfe_progress_tab_is_active):
"""
Tests the progress URL generated by the CourseRunSerializer. When course_home_mfe_progress_tab_is_active
is true, the generated progress URL must point to the progress page of the course home (learning) MFE.
Otherwise, it must point to the legacy progress page.
"""
mock_course_home_mfe_progress_tab_is_active.return_value = True
self.assertTrue(self.is_progress_url_matching_course_home_mfe_progress_tab_is_active())

mock_course_home_mfe_progress_tab_is_active.return_value = False
self.assertTrue(self.is_progress_url_matching_course_home_mfe_progress_tab_is_active())


@ddt.ddt
class TestCoursewareAccessSerializer(LearnerDashboardBaseTest):
Expand Down
16 changes: 16 additions & 0 deletions lms/djangoapps/learner_home/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

import logging

from django.urls import reverse
from django.contrib.auth import get_user_model
from django.core.exceptions import MultipleObjectsReturned
from rest_framework.exceptions import PermissionDenied, NotFound

from common.djangoapps.student.models import (
get_user_by_username_or_email,
)
from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url

log = logging.getLogger(__name__)
User = get_user_model()
Expand Down Expand Up @@ -54,3 +57,16 @@ def get_masquerade_user(request):
)
log.info(success_msg)
return masquerade_user


def course_progress_url(course_key) -> str:
"""
Returns the course progress page's URL for the current user.

:param course_key: The course key for which the home url is being requested.

:return: The course progress page URL.
"""
if course_home_mfe_progress_tab_is_active(course_key):
return get_learning_mfe_home_url(course_key, url_fragment='progress')
return reverse('progress', kwargs={'course_id': course_key})
24 changes: 24 additions & 0 deletions lms/static/sass/multicourse/_course_about.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,23 @@
> div.table {
display: table;
width: 100%;

@include media-breakpoint-down(sm) {
display: flex;
flex-direction: column;
}
}

.intro {
box-sizing: border-box;

@include clearfix();

@include media-breakpoint-down(sm) {
width: auto;
order: 2;
}

display: table-cell;
vertical-align: middle;
padding: $baseline;
Expand Down Expand Up @@ -127,6 +137,10 @@
a.add-to-cart {
@include button(shiny, $button-color);

@include media-breakpoint-down(md) {
width: 100%;
}

box-sizing: border-box;
border-radius: 3px;
display: block;
Expand Down Expand Up @@ -189,6 +203,11 @@
@include float(left);
@include margin(1px, flex-gutter(8), 0, 0);
@include transition(none);
@include media-breakpoint-down(md) {
width: 100%;
margin-right: 0;
margin-bottom: 10px;
}

width: flex-grid(5, 8);
}
Expand All @@ -213,6 +232,11 @@
width: flex-grid(4);
z-index: 2;

@include media-breakpoint-down(sm) {
width: auto;
order: 1;
}

.hero {
border: 1px solid $border-color-3;
height: 100%;
Expand Down
19 changes: 13 additions & 6 deletions lms/templates/courseware/course_about.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,10 @@
<div class="table">
<section class="intro">
<div class="heading-group">
<h1>
${course.display_name_with_default}
</h1>
<p><small>${course.display_org_with_default}</small></p>
<h1>${course.display_name_with_default}</h1>
<br />
<span>${course.display_org_with_default}</span>
<p>${get_course_about_section(request, course, 'short_description')}</p>
</div>

<div class="main-cta">
Expand Down Expand Up @@ -160,7 +159,11 @@ <h1>

<%block name="course_about_important_dates">
<ol class="important-dates">
<li class="important-dates-item"><span class="icon fa fa-info-circle" aria-hidden="true"></span><p class="important-dates-item-title">${_("Course Number")}</p><span class="important-dates-item-text course-number">${course.display_number_with_default}</span></li>
<li class="important-dates-item">
<span class="icon fa fa-info-circle" aria-hidden="true"></span>
<p class="important-dates-item-title">${_("Course Number")}</p>
<span class="important-dates-item-text course-number">${course.display_number_with_default}</span>
</li>
% if not course.start_date_is_still_default:
<%
course_start_date = course.advertised_start or course.start
Expand Down Expand Up @@ -231,7 +234,11 @@ <h1>
% endif

% if get_course_about_section(request, course, "prerequisites"):
<li class="important-dates-item"><span class="icon fa fa-book" aria-hidden="true"></span><p class="important-dates-item-title">${_("Requirements")}</p><span class="important-dates-item-text prerequisites">${get_course_about_section(request, course, "prerequisites")}</span></li>
<li class="important-dates-item">
<span class="icon fa fa-book" aria-hidden="true"></span>
<p class="important-dates-item-title">${_("Requirements")}</p>
<span class="important-dates-item-text prerequisites">${get_course_about_section(request, course, "prerequisites")}</span>
</li>
% endif
</ol>
</%block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<tr>
<td width="70">
<a href="{% with_link_tracking homepage_url %}"><img
src="http://localhost:18000/static/red-theme/images/logo.png"
src="{{ logo_url }}"
alt="{% filter force_escape %} {% blocktrans %}Go to {{ platform_name }} Home Page{% endblocktrans %} {% endfilter %}"/></a>
</td>
<td align="right" style="text-align: {{ LANGUAGE_BIDI|yesno:"left,right" }};">
Expand Down
15 changes: 14 additions & 1 deletion xmodule/capa_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,12 +733,25 @@ def generate_report_data(self, user_state_iterator, limit_responses=None):
}
yield (user_state.username, report)

@property
def course_end_date(self):
"""
Return the end date of the problem's course
"""

try:
course_block_key = self.runtime.course_entry.structure['root']
return self.runtime.course_entry.structure['blocks'][course_block_key].fields['end']
except (AttributeError, KeyError):
return None

@property
def close_date(self):
"""
Return the date submissions should be closed from.
"""
due_date = self.due

due_date = self.due or self.course_end_date

if self.graceperiod is not None and due_date:
return due_date + self.graceperiod
Expand Down
33 changes: 32 additions & 1 deletion xmodule/tests/test_capa_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import random
import textwrap
import unittest
from unittest.mock import DEFAULT, Mock, patch
from unittest.mock import DEFAULT, Mock, patch, PropertyMock

import pytest
import ddt
Expand Down Expand Up @@ -648,6 +648,37 @@ def test_closed(self):
due=self.yesterday_str)
assert block.closed()

@patch.object(ProblemBlock, 'course_end_date', new_callable=PropertyMock)
def test_closed_for_archive(self, mock_course_end_date):

# Utility to create a datetime object in the past
def past_datetime(days):
return (datetime.datetime.now(UTC) - datetime.timedelta(days=days))

# Utility to create a datetime object in the future
def future_datetime(days):
return (datetime.datetime.now(UTC) + datetime.timedelta(days=days))

block = CapaFactory.create(max_attempts="1", attempts="0")

# For active courses without graceperiod
mock_course_end_date.return_value = future_datetime(10)
assert not block.closed()

# For archive courses without graceperiod
mock_course_end_date.return_value = past_datetime(10)
assert block.closed()

# For active courses with graceperiod
mock_course_end_date.return_value = future_datetime(10)
block.graceperiod = datetime.timedelta(days=2)
assert not block.closed()

# For archive courses with graceperiod
mock_course_end_date.return_value = past_datetime(2)
block.graceperiod = datetime.timedelta(days=3)
assert not block.closed()

def test_parse_get_params(self):

# Valid GET param dict
Expand Down
Loading