Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
d3d203c
build: kickoff the Quince release
cmltaWt0 Oct 11, 2023
cc6cacd
feat!: upgrading django4.2 version.
awais786 Sep 10, 2023
3937021
feat!: updating default algorithm to sha256.
awais786 Oct 13, 2023
79fad1b
feat!: Upgrading to django42.
awais786 Oct 17, 2023
0fc1759
chore: Updating Python Requirements (#33517)
edx-requirements-bot Oct 17, 2023
8da7b2e
chore: pylint fixing.
awais786 Oct 17, 2023
88c7337
chore: downgrading django-mysql.
awais786 Oct 17, 2023
e41f6b1
feat: Squash Tagging/Taxonomy migrations, bump openedx-learning to 0.2.3
bradenmacdonald Oct 3, 2023
2e55367
Merge pull request #33516 from openedx/quince-django42-upgrade
cmltaWt0 Oct 30, 2023
6725fa6
fix: course mode added to the metadata
Inferato Nov 9, 2023
b8ff3ab
feat: link LTI Provider launches to authenticated users
tecoholic Nov 13, 2023
6482e99
Merge pull request #33656 from open-craft/tecoholic/quince-backport-l…
cmltaWt0 Nov 21, 2023
97b8cb9
fix: CSRF errors in POST requests to LMS (#33790)
regisb Nov 24, 2023
79f95d7
feat: copy/paste unit from within a unit in Studio - feature flagged …
pkulkark Dec 1, 2023
dbdf335
fix: "Copy Unit" button should be behind enable_copy_paste_units flag
bradenmacdonald Dec 1, 2023
19844b9
fix: name length validation (#33615)
Inferato Dec 4, 2023
5c7cf44
fix: error pasting a unit containing a completely blank problem
bradenmacdonald Dec 7, 2023
847f94b
Merge pull request #33866 from open-craft/braden/copy-paste-unit-back…
arbrandes Dec 8, 2023
eb069d7
chore: upgrade Django to 4.2.8
magajh Dec 11, 2023
aec3dec
chore: compile requirements
magajh Dec 11, 2023
4e15e5e
Merge pull request #33764 from magajh/magajh/patch-django-quince
cmltaWt0 Dec 11, 2023
6da7f58
feat: allow switching anonymous user ID hashing algorithm from shake …
kaustavb12 Aug 8, 2022
21571c6
feat: Add toggle to allow redirecting to courseware after enrollment.
xitij2000 Feb 6, 2023
d468a8c
feat: add new endpoint for cloning course
pkulkark Feb 17, 2023
c596bf3
temp: Add configuration option to redirect to external site when TAP …
xitij2000 May 24, 2023
50da901
feat: default grade designations configurable from settings (#541)
kaustavb12 Jun 12, 2023
6de7b64
fix: give superusers all studio permissions
0x29a Jun 26, 2023
0567608
feat: allow oauth configuration per site and backend (#32656)
navinkarkera Oct 10, 2023
a21b4f0
feat: eSHE Instructor role
0x29a Jul 27, 2023
a20e51d
fix: eSHE Instructor should be able to see forum members and enrollments
0x29a Sep 14, 2023
dfb8ac9
feat: set course for wiki based on the wiki_slug
Cup0fCoffee Sep 25, 2023
b9662f4
fix: youtube race condition when GTM loads
viadanna Sep 27, 2023
f553cc4
fix: limited staff cohorts and gradebook access
0x29a Oct 15, 2023
87400e5
fix: hide instructor tab when masquerading as user role with no masqu…
Nov 8, 2023
a46281a
feat: filter courses by user language by default
navinkarkera Nov 2, 2023
022b4a1
fix: Update the date to the max year possible in a test. (#33995)
dianakhuang Jan 2, 2024
14497bf
Merge pull request #614 from open-craft/0x29a/bb8366/quince-shared-br…
0x29a Jan 3, 2024
89f5f69
fix: add `JwtRestrictedApplication` check to XBlock callback
Agrendalath Dec 14, 2023
e7d37ca
Merge pull request #34047 from open-craft/agrendalath/xblock-callback…
Jan 12, 2024
9ca3c69
fix: Incorrect ORA assignment link in Dates tab (#33984) (#34053)
arslanashraf7 Jan 17, 2024
62f720c
Merge pull request #620 from open-craft/sync-open-release/quince.mast…
Agrendalath Jan 29, 2024
78e9cd8
fix: session_language url renamed to update_language
navinkarkera Nov 16, 2023
d9ab77c
Merge pull request #621 from open-craft/backport-fix-url-rename-to-qu…
0x29a Feb 5, 2024
39e6faa
fix: add missing function import in certificate template (#33904) (#3…
kaustavb12 Feb 5, 2024
8290087
fix: youtube race condition when GTM loads (#33650)
viadanna Feb 5, 2024
6a166c5
chore: upgrade Django to 4.2.10
magajh Feb 6, 2024
ba4bcf1
fix: ORA response with attached file (#33676)
DmytroAlipov Feb 7, 2024
43fbbf6
fix: handle countries not recognized by geoip2 DB
Agrendalath Jan 24, 2024
c352d5b
feat: allow disabling user tours
Agrendalath May 25, 2023
c37d7ba
Merge pull request #34195 from magajh/magajh/patch-django-quince-4.2.10
cmltaWt0 Feb 7, 2024
a0c97d6
fix: fixing the prompt view cancel button style for color and cursor …
bogdan3d Feb 8, 2024
ac68aae
Merge pull request #34209 from bogdan3d/quince-backport-fix-prompt-vi…
cmltaWt0 Feb 8, 2024
7ef00c0
feat: Teaching Assistant role
0x29a Feb 13, 2024
9ba77e1
Merge pull request #634 from open-craft/0x29a/bb8580/teaching-assista…
0x29a Feb 16, 2024
2dfdbcb
Merge pull request #630 from open-craft/sync-open-release/quince.mast…
Agrendalath Feb 18, 2024
6888453
refactor: use rerun_course for course clonging
Cup0fCoffee Feb 4, 2024
2846c4b
Merge pull request #636 from open-craft/maxim/bb-7106-clone-course-fi…
Cup0fCoffee Feb 20, 2024
b679152
Merge pull request #33691 from Inferato/lytvynenko/bulk_email_checkbo…
Feb 29, 2024
0d98f85
feat: Allow trusted apps to perform cookie login.
Kelketek Sep 6, 2023
8bcfb1d
fix: removed waffle switch around oep-50 filter
Mar 11, 2024
fb05745
Merge pull request #34348 from openedx/iahmad/ENT-7614-3
irfanuddinahmad Mar 11, 2024
505fb80
Merge pull request #643 from open-craft/fox/port-oauth-trust-fix
Cup0fCoffee Mar 12, 2024
5c2fe16
Merge pull request #644 from open-craft/sync-open-release/quince.mast…
Agrendalath Mar 15, 2024
3da1cdf
fix: handle paste of library content blocks correctly (#34274) (backp…
bradenmacdonald Mar 20, 2024
7aae617
fix: Space around techers images on course about page (#34358)
Lunyachek Mar 20, 2024
e184181
fix: email templates caching (#34409)
DmytroAlipov Mar 22, 2024
620516c
chore: upgrade Django to 4.2.11
magajh Mar 29, 2024
900fad0
chore: compile requirements
magajh Apr 1, 2024
0906265
fix: session_language url renamed to update_language
navinkarkera Nov 16, 2023
49cdbff
fix: decoration of see all children link in wiki pages (#33669)
ihor-romaniuk Apr 4, 2024
08f2769
Merge pull request #648 from open-craft/sync-open-release/quince.mast…
Agrendalath Apr 4, 2024
eb11261
fix: validation error for empty Batch Enrollment/Beta Tester (#34404)
DmytroAlipov Apr 5, 2024
d782803
Merge pull request #34448 from magajh/magajh/patch-django-quince-4.2.11
cmltaWt0 Apr 6, 2024
2eb82ad
feat: Adds disable_progress_graph attribute to the returned course_me…
RafayGhafoor Sep 21, 2023
f3f160f
Merge pull request #34324 from raccoongang/fix-disable-progress-graph
cmltaWt0 Apr 8, 2024
25eb223
feat: add ability to notify credentials about honor certificates (#34…
DmytroAlipov Apr 10, 2024
a6bd0e2
fix: Social link parsing approach changed (#33610)
Inferato Apr 10, 2024
1720dea
feat: DEFAULT_COURSE_INVITATION_ONLY allow for invitation_only new co…
viadanna Apr 11, 2024
525a921
feat: use profile name in navbar if available
navinkarkera Apr 9, 2024
6b5df89
feat: tpa automatic logout with a single redirect
CefBoud Apr 16, 2024
0e8e9f5
feat: add a feature flag to disable dates tab for all courses
Cup0fCoffee Apr 15, 2024
b036cd0
fix: "Course org display str" option doesnt influence certificate (#3…
DmytroAlipov Apr 23, 2024
96efd39
Merge pull request #654 from open-craft/maxim/bb-8271-hiding-dates-ta…
Cup0fCoffee Apr 25, 2024
d91cadf
fix: Course blocks API with param return_type=list error when the new…
qasimgulzar May 10, 2024
3ff69fd
fix: prevent setting user attributes from JWT in Studio
ormsbee May 13, 2024
13eaa68
Merge pull request #658 from open-craft/sync-open-release/quince.mast…
Agrendalath May 20, 2024
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
19 changes: 0 additions & 19 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,3 @@
<!--

🌴🌴
🌴🌴🌴🌴 🌴 Note: Palm is in support. Fixes you make on master may still be needed on Palm.
🌴🌴🌴🌴 If so, make another pull request against the open-release/palm.master branch,
🌴🌴🌴🌴 or ask in the #wg-build-test-release Slack channel if you have any questions or need help.
🌴🌴

Please give your pull request a short but descriptive title.
Use conventional commits to separate and summarize commits logically:
https://open-edx-proposals.readthedocs.io/en/latest/oep-0051-bp-conventional-commits.html

Use this template as a guide. Omit sections that don't apply.
You may link to information rather than copy it, but only if the link is publicly
readable. If you must linked information must be private (because it has secrets),
clearly label the link as private.

-->

## Description

Describe what this pull request changes, and why. Include implications for people using this change.
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/migrations-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
# 'pinned' is used to install the latest patch version of Django
# within the global constraint i.e. Django==3.2.21 in current case
# because we have global constraint of Django<4.2
django-version: ["pinned", "4.2"]
django-version: ["pinned"]
mongo-version: ["4"]
mysql-version: ["5.7", "8"]
mysql-version: ["8"]
# excluding mysql5.7 with Django 4.2 since Django 4.2 has
# dropped support for MySQL<8
exclude:
Expand Down
11 changes: 11 additions & 0 deletions .tx/config
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@ source_file = conf/locale/en/LC_MESSAGES/wiki.po
source_lang = en
type = PO

[o:open-edx:p:open-edx-releases:r:release-quince]
file_filter = conf/locale/<lang>/LC_MESSAGES/django.po
source_file = conf/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO

[o:open-edx:p:open-edx-releases:r:release-quince-js]
file_filter = conf/locale/<lang>/LC_MESSAGES/djangojs.po
source_file = conf/locale/en/LC_MESSAGES/djangojs.po
source_lang = en
type = PO
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile *
@# time someone tries to use the outputs.
sed '/^django-simple-history==/d' requirements/common_constraints.txt > requirements/common_constraints.tmp
mv requirements/common_constraints.tmp requirements/common_constraints.txt
sed 's/Django<4.0//g' requirements/common_constraints.txt > requirements/common_constraints.tmp
mv requirements/common_constraints.tmp requirements/common_constraints.txt
pip-compile -v --allow-unsafe ${COMPILE_OPTS} -o requirements/pip.txt requirements/pip.in
pip install -r requirements/pip.txt

Expand Down
45 changes: 44 additions & 1 deletion cms/djangoapps/api/v1/serializers/course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import serializers
from rest_framework.fields import empty

Expand Down Expand Up @@ -198,8 +199,50 @@ def update(self, instance, validated_data):
'display_name': instance.display_name
}
fields.update(validated_data)
new_course_run_key = rerun_course(user, course_run_key, course_run_key.org, number, run, fields, False)
new_course_run_key = rerun_course(
user, course_run_key, course_run_key.org, number, run, fields, background=False,
)

course_run = get_course_and_check_access(new_course_run_key, user)
self.update_team(course_run, team)
return course_run


class CourseCloneSerializer(serializers.Serializer): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring
source_course_id = serializers.CharField()
destination_course_id = serializers.CharField()

def validate(self, attrs):
source_course_id = attrs.get('source_course_id')
destination_course_id = attrs.get('destination_course_id')
store = modulestore()
source_key = CourseKey.from_string(source_course_id)
dest_key = CourseKey.from_string(destination_course_id)

# Check if the source course exists
if not store.has_course(source_key):
raise serializers.ValidationError('Source course does not exist.')

# Check if the destination course already exists
if store.has_course(dest_key):
raise serializers.ValidationError('Destination course already exists.')
return attrs

def create(self, validated_data):
source_course_id = validated_data.get('source_course_id')
destination_course_id = validated_data.get('destination_course_id')
user = self.context['request'].user
source_course_key = CourseKey.from_string(source_course_id)
destination_course_key = CourseKey.from_string(destination_course_id)
source_course_run = get_course_and_check_access(source_course_key, user)
fields = {
'display_name': source_course_run.display_name,
}

destination_course_run_key = rerun_course(
user, source_course_key, destination_course_key.org, destination_course_key.course,
destination_course_key.run, fields, background=False,
)

destination_course_run = get_course_and_check_access(destination_course_run_key, user)
return destination_course_run
51 changes: 51 additions & 0 deletions cms/djangoapps/api/v1/tests/test_views/test_course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,54 @@ def test_rerun_invalid_number(self):
assert response.data == {'non_field_errors': [
'Invalid key supplied. Ensure there are no special characters in the Course Number.'
]}

def test_clone_course(self):
course = CourseFactory()
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': str(course.id),
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 201
self.assertEqual(response.data, {"message": "Course cloned successfully."})

def test_clone_course_with_missing_source_id(self):
url = reverse('api:v1:course_run-clone')
data = {
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
self.assertEqual(response.data, {'source_course_id': ['This field is required.']})

def test_clone_course_with_missing_dest_id(self):
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': 'course-v1:source+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
self.assertEqual(response.data, {'destination_course_id': ['This field is required.']})

def test_clone_course_with_nonexistent_source_course(self):
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': 'course-v1:nonexistent+source+course_id',
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
assert str(response.data.get('non_field_errors')[0]) == 'Source course does not exist.'

def test_clone_course_with_existing_dest_course(self):
url = reverse('api:v1:course_run-clone')
course = CourseFactory()
existing_dest_course = CourseFactory()
data = {
'source_course_id': str(course.id),
'destination_course_id': str(existing_dest_course.id),
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
assert str(response.data.get('non_field_errors')[0]) == 'Destination course already exists.'
9 changes: 9 additions & 0 deletions cms/djangoapps/api/v1/views/course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from cms.djangoapps.contentstore.views.course import _accessible_courses_iter, get_course_and_check_access

from ..serializers.course_runs import (
CourseCloneSerializer,
CourseRunCreateSerializer,
CourseRunImageSerializer,
CourseRunRerunSerializer,
Expand Down Expand Up @@ -93,3 +94,11 @@ def rerun(self, request, *args, **kwargs): # lint-amnesty, pylint: disable=miss
new_course_run = serializer.save()
serializer = self.get_serializer(new_course_run)
return Response(serializer.data, status=status.HTTP_201_CREATED)

@action(detail=False, methods=['post'])
def clone(self, request, *args, **kwargs): # lint-amnesty, pylint: disable=missing-function-docstring, unused-argument
serializer = CourseCloneSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
new_course_run = serializer.save()
serializer = self.get_serializer(new_course_run)
return Response({"message": "Course cloned successfully."}, status=status.HTTP_201_CREATED)
29 changes: 19 additions & 10 deletions cms/djangoapps/contentstore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from xmodule.library_content_block import LibraryContentBlock
from xmodule.modulestore.django import modulestore
from xmodule.xml_block import XmlMixin

Expand Down Expand Up @@ -302,6 +303,16 @@ def _import_xml_node_to_parent(
# and VAL will thus make the transcript available.

child_nodes = []

if issubclass(xblock_class, XmlMixin):
# Hack: XBlocks that use "XmlMixin" have their own XML parsing behavior, and in particular if they encounter
# an XML node that has no children and has only a "url_name" attribute, they'll try to load the XML data
# from an XML file in runtime.resources_fs. But that file doesn't exist here. So we set at least one
# additional attribute here to make sure that url_name is not the only attribute; otherwise in some cases,
# XmlMixin.parse_xml will try to load an XML file that doesn't exist, giving an error. The name and value
# of this attribute don't matter and should be ignored.
node.attrib["x-is-pointer-node"] = "no"

if not xblock_class.has_children:
# No children to worry about. The XML may contain child nodes, but they're not XBlocks.
temp_xblock = xblock_class.parse_xml(node, runtime, keys, id_generator)
Expand All @@ -314,14 +325,6 @@ def _import_xml_node_to_parent(
# serialization of a child block, in order. For blocks that don't support children, their XML content/nodes
# could be anything (e.g. HTML, capa)
node_without_children = etree.Element(node.tag, **node.attrib)
if issubclass(xblock_class, XmlMixin):
# Hack: XBlocks that use "XmlMixin" have their own XML parsing behavior, and in particular if they encounter
# an XML node that has no children and has only a "url_name" attribute, they'll try to load the XML data
# from an XML file in runtime.resources_fs. But that file doesn't exist here. So we set at least one
# additional attribute here to make sure that url_name is not the only attribute; otherwise in some cases,
# XmlMixin.parse_xml will try to load an XML file that doesn't exist, giving an error. The name and value
# of this attribute don't matter and should be ignored.
node_without_children.attrib["x-is-pointer-node"] = "no"
temp_xblock = xblock_class.parse_xml(node_without_children, runtime, keys, id_generator)
child_nodes = list(node)
if xblock_class.has_children and temp_xblock.children:
Expand All @@ -334,8 +337,14 @@ def _import_xml_node_to_parent(
new_xblock = store.update_item(temp_xblock, user_id, allow_not_found=True)
parent_xblock.children.append(new_xblock.location)
store.update_item(parent_xblock, user_id)
for child_node in child_nodes:
_import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id)
if isinstance(new_xblock, LibraryContentBlock):
# Special case handling for library content. If we need this for other blocks in the future, it can be made into
# an API, and we'd call new_block.studio_post_paste() instead of this code.
# In this case, we want to pull the children from the library and let library_tools assign their IDs.
new_xblock.tools.update_children(new_xblock, version=new_xblock.source_library_version)
else:
for child_node in child_nodes:
_import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id)
return new_xblock


Expand Down
10 changes: 10 additions & 0 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,16 @@ def test_create_course_with_unicode_in_id_disabled(self):
self.course_data['run'] = '����������'
self.assert_create_course_failed(error_message)

@override_settings(DEFAULT_COURSE_INVITATION_ONLY=True)
def test_create_course_invitation_only(self):
"""
Test new course creation with setting: DEFAULT_COURSE_INVITATION_ONLY=True.
"""
test_course_data = self.assert_created_course()
course_id = _get_course_id(self.store, test_course_data)
course = self.store.get_course(course_id)
self.assertEqual(course.invitation_only, True)

def assert_course_permission_denied(self):
"""
Checks that the course did not get created due to a PermissionError.
Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,8 @@ def get_course_grading(course_key):
'grading_url': reverse_course_url('grading_handler', course_key),
'is_credit_course': is_credit_course(course_key),
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_key),
'course_assignment_lists': dict(course_assignment_lists)
'course_assignment_lists': dict(course_assignment_lists),
'default_grade_designations': settings.DEFAULT_GRADE_DESIGNATIONS
}

return grading_context
Expand Down
9 changes: 8 additions & 1 deletion cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,8 +932,9 @@ def create_new_course_in_store(store, user, org, number, run, fields):

# Set default language from settings and enable web certs
fields.update({
'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'),
'cert_html_view_enabled': True,
'invitation_only': getattr(settings, 'DEFAULT_COURSE_INVITATION_ONLY', False),
'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'),
})

with modulestore().default_store(store):
Expand Down Expand Up @@ -971,6 +972,12 @@ def rerun_course(user, source_course_key, org, number, run, fields, background=T
if store.has_course(destination_course_key, ignore_case=True):
raise DuplicateCourseError(source_course_key, destination_course_key)

# if org or name of source course don't match the destination course,
# verify user has access to the destination course
if source_course_key.org != destination_course_key.org or source_course_key.course != destination_course_key.course:
if not has_studio_write_access(user, destination_course_key):
raise PermissionDenied()

# Make sure user has instructor and staff access to the destination course
# so the user can see the updated status for that course
add_instructor(destination_course_key, user, user)
Expand Down
Loading