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/palm.1 with Upstream 20230911-1694390586 #584

Merged
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
5 changes: 2 additions & 3 deletions lms/djangoapps/discussion/rest_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,9 @@ def get_unread_comment_count(self, obj):

def get_preview_body(self, obj):
"""
Returns a cleaned and truncated version of the thread's body to display in a
preview capacity.
Returns a cleaned version of the thread's body to display in a preview capacity.
"""
return strip_tags(self.get_rendered_body(obj)).replace('\n', ' ')
return strip_tags(self.get_rendered_body(obj)).replace('\n', ' ').replace(' ', ' ')

def get_close_reason(self, obj):
"""
Expand Down
15 changes: 15 additions & 0 deletions lms/djangoapps/discussion/rest_api/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,21 @@ def test_response_count_missing(self):
serialized = self.serialize(thread_data)
assert 'response_count' not in serialized

def test_get_preview_body(self):
"""
Test for the 'get_preview_body' method.

This test verifies that the 'get_preview_body' method returns a cleaned
version of the thread's body that is suitable for display as a preview.
The test specifically focuses on handling the presence of multiple
spaces within the body.
"""
thread_data = self.make_cs_content(
{"body": "<p>This is a test thread body with some text.</p>"}
)
serialized = self.serialize(thread_data)
assert serialized['preview_body'] == "This is a test thread body with some text."


@ddt.ddt
class CommentSerializerTest(SerializerTestMixin, SharedModuleStoreTestCase):
Expand Down
8 changes: 4 additions & 4 deletions lms/templates/wiki/includes/editor_widget.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% load i18n %}
{% load django_markup %}
<p id="hint_id_content" class="help-block">
{% filter force_escape %}
{% blocktrans with start_link="<a id='cheatsheetLink' href='#cheatsheetModal' rel='leanModal'>" end_link="</a>" trimmed %}
Markdown syntax is allowed. See the {{ start_link }}cheatsheet{{ end_link }} for help.
{% blocktrans trimmed asvar tmsg %}
Markdown syntax is allowed. See the {start_link}cheatsheet{end_link} for help.
{% endblocktrans %}
{% endfilter %}
{% interpolate_html tmsg start_link='<a id="cheatsheetLink" href="#cheatsheetModal" rel="leanModal">'|safe end_link='</a>'|safe %}
</p>
<textarea {{ attrs }}>{{ content }}</textarea>
53 changes: 32 additions & 21 deletions openedx/core/djangoapps/models/tests/test_course_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


import datetime
from django.test import override_settings
import pytest
import ddt
from pytz import UTC
Expand All @@ -29,27 +30,37 @@ class CourseDetailsTestCase(ModuleStoreTestCase):

def setUp(self):
super().setUp()
self.course = CourseFactory.create()

def test_virgin_fetch(self):
details = CourseDetails.fetch(self.course.id)
assert details.org == self.course.location.org, 'Org not copied into'
assert details.course_id == self.course.location.course, 'Course_id not copied into'
assert details.run == self.course.location.run, 'Course run not copied into'
assert details.course_image_name == self.course.course_image
assert details.start_date.tzinfo is not None
assert details.end_date is None, ('end date somehow initialized ' + str(details.end_date))
assert details.enrollment_start is None,\
('enrollment_start date somehow initialized ' + str(details.enrollment_start))
assert details.enrollment_end is None,\
('enrollment_end date somehow initialized ' + str(details.enrollment_end))
assert details.certificate_available_date is None,\
('certificate_available_date date somehow initialized ' + str(details.certificate_available_date))
assert details.syllabus is None, ('syllabus somehow initialized' + str(details.syllabus))
assert details.intro_video is None, ('intro_video somehow initialized' + str(details.intro_video))
assert details.effort is None, ('effort somehow initialized' + str(details.effort))
assert details.language is None, ('language somehow initialized' + str(details.language))
assert not details.self_paced
self.course = CourseFactory.create(default_enrollment_start=True)

@ddt.data(True, False)
def test_virgin_fetch(self, should_have_default_enroll_start):
features = settings.FEATURES.copy()
features['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE'] = should_have_default_enroll_start

with override_settings(FEATURES=features):
course = CourseFactory.create(default_enrollment_start=should_have_default_enroll_start)
details = CourseDetails.fetch(course.id)
wrong_enrollment_start_msg = (
'enrollment_start not copied into'
if should_have_default_enroll_start
else f'enrollment_start date somehow initialized {str(details.enrollment_start)}'
)
assert details.org == course.location.org, 'Org not copied into'
assert details.course_id == course.location.course, 'Course_id not copied into'
assert details.run == course.location.run, 'Course run not copied into'
assert details.course_image_name == course.course_image
assert details.start_date.tzinfo is not None
assert details.end_date is None, ('end date somehow initialized ' + str(details.end_date))
assert details.enrollment_start == course.enrollment_start, wrong_enrollment_start_msg
assert details.enrollment_end is None,\
('enrollment_end date somehow initialized ' + str(details.enrollment_end))
assert details.certificate_available_date is None,\
('certificate_available_date date somehow initialized ' + str(details.certificate_available_date))
assert details.syllabus is None, ('syllabus somehow initialized' + str(details.syllabus))
assert details.intro_video is None, ('intro_video somehow initialized' + str(details.intro_video))
assert details.effort is None, ('effort somehow initialized' + str(details.effort))
assert details.language is None, ('language somehow initialized' + str(details.language))
assert not details.self_paced

def test_update_and_fetch(self):
jsondetails = CourseDetails.fetch(self.course.id)
Expand Down
23 changes: 22 additions & 1 deletion xmodule/course_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import requests
from django.conf import settings
from django.core.validators import validate_email
from edx_toggles.toggles import SettingDictToggle
from lazy import lazy
from lxml import etree
from path import Path as path
Expand Down Expand Up @@ -54,6 +55,22 @@
COURSE_VISIBILITY_PUBLIC_OUTLINE = 'public_outline'
COURSE_VISIBILITY_PUBLIC = 'public'

# .. toggle_name: FEATURES['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE']
# .. toggle_implementation: SettingDictToggle
# .. toggle_default: False
# .. toggle_description: The default behavior, when this is disabled, is that a newly created course has no
# enrollment_start date set. When the feature is enabled - the newly created courses will have the
# enrollment_start_date set to DEFAULT_START_DATE. This is intended to be a permanent option.
# This toggle affects the course listing pages (platform's index page, /courses page) when course search is
# performed using the `lms.djangoapp.branding.get_visible_courses` method and the
# COURSE_CATALOG_VISIBILITY_PERMISSION setting is set to 'see_exists'. Switching the toggle to True will prevent
# the newly created (empty) course from appearing in the course listing.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2023-06-22
CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE = SettingDictToggle(
"FEATURES", "CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE", default=False, module_name=__name__
)


class StringOrDate(Date): # lint-amnesty, pylint: disable=missing-class-docstring
def from_json(self, value): # lint-amnesty, pylint: disable=arguments-differ
Expand Down Expand Up @@ -311,7 +328,11 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring
)

wiki_slug = String(help=_("Slug that points to the wiki for this course"), scope=Scope.content)
enrollment_start = Date(help=_("Date that enrollment for this class is opened"), scope=Scope.settings)
enrollment_start = Date(
help=_("Date that enrollment for this class is opened"),
default=DEFAULT_START_DATE if CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE.is_enabled() else None,
scope=Scope.settings
)
enrollment_end = Date(help=_("Date that enrollment for this class is closed"), scope=Scope.settings)
start = Date(
help=_("Start time when this block is visible"),
Expand Down
5 changes: 5 additions & 0 deletions xmodule/modulestore/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ def _create(cls, target_class, **kwargs): # lint-amnesty, pylint: disable=argum
run = kwargs.pop('run', name)
user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test)
emit_signals = kwargs.pop('emit_signals', False)
# By default course has enrollment_start in the future which means course is closed for enrollment.
# We're setting the 'enrollment_start' field to None to reduce number of arguments needed to setup course.
# Use the 'default_enrollment_start=True' kwarg to skip this and use the default enrollment_start date.
if not kwargs.get('enrollment_start', kwargs.pop('default_enrollment_start', False)):
kwargs['enrollment_start'] = None

# Pass the metadata just as field=value pairs
kwargs.update(kwargs.pop('metadata', {}))
Expand Down
20 changes: 17 additions & 3 deletions xmodule/tests/test_course_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
import itertools
import unittest
from datetime import datetime, timedelta
import sys
from unittest.mock import Mock, patch

import pytest
import ddt
from dateutil import parser
from django.conf import settings
from django.test import override_settings
from fs.memoryfs import MemoryFS
from opaque_keys.edx.keys import CourseKey
import pytest
from pytz import utc
from xblock.runtime import DictKeyValueStore, KvsFieldData

from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TEAM_SIZE
import xmodule.course_block
from xmodule.course_metadata_utils import DEFAULT_START_DATE
from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.modulestore.exceptions import InvalidProctoringProvider
Expand All @@ -32,10 +34,22 @@
_NEXT_WEEK = _TODAY + timedelta(days=7)


class CourseFieldsTestCase(unittest.TestCase):
@ddt.ddt()
class CourseFieldsTestCase(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring

def test_default_start_date(self):
assert xmodule.course_block.CourseFields.start.default == datetime(2030, 1, 1, tzinfo=utc)
assert xmodule.course_block.CourseFields.start.default == DEFAULT_START_DATE

@ddt.data(True, False)
def test_default_enrollment_start_date(self, should_have_default_enroll_start):
features = settings.FEATURES.copy()
features['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE'] = should_have_default_enroll_start
with override_settings(FEATURES=features):
# reimport, so settings override could take effect
del sys.modules['xmodule.course_block']
import xmodule.course_block # lint-amnesty, pylint: disable=redefined-outer-name, reimported
expected = DEFAULT_START_DATE if should_have_default_enroll_start else None
assert xmodule.course_block.CourseFields.enrollment_start.default == expected


class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring
Expand Down
Loading