From 0f6e4688636878023d53b27c1fbf13b6444856cd Mon Sep 17 00:00:00 2001 From: Taimoor Ahmed <68893403+taimoor-ahmed-1@users.noreply.github.com> Date: Thu, 20 Mar 2025 12:17:39 +0500 Subject: [PATCH 1/5] fix: discussion xblock not compatible with forum v2 (#36315) fix all endpoints that were currently breaking with the discussion xblock. Co-authored-by: Taimoor Ahmed --- .../django_comment_client/base/tests.py | 2 +- .../django_comment_client/base/views.py | 24 +++++++++---------- lms/djangoapps/discussion/views.py | 2 +- .../comment_client/comment.py | 8 +++---- .../comment_client/models.py | 4 ++-- .../comment_client/thread.py | 8 ++++--- .../comment_client/user.py | 14 +++++------ 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lms/djangoapps/discussion/django_comment_client/base/tests.py b/lms/djangoapps/discussion/django_comment_client/base/tests.py index df087fdc533e..d2a4f921f28b 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/tests.py +++ b/lms/djangoapps/discussion/django_comment_client/base/tests.py @@ -812,7 +812,7 @@ def test_update_comment_basic(self, mock_is_forum_v2_enabled, mock_request): headers=ANY, params=ANY, timeout=ANY, - data={"body": updated_body} + data={"body": updated_body, "course_id": str(self.course_id)} ) def test_flag_thread_open(self, mock_is_forum_v2_enabled, mock_request): diff --git a/lms/djangoapps/discussion/django_comment_client/base/views.py b/lms/djangoapps/discussion/django_comment_client/base/views.py index 3df362bdf6d2..55057060d310 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/views.py +++ b/lms/djangoapps/discussion/django_comment_client/base/views.py @@ -673,7 +673,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None): parent_id=parent_id, body=sanitize_body(post["body"]), ) - comment.save() + comment.save(params={"course_id": str(course_key)}) comment_created.send(sender=None, user=user, post=comment) @@ -736,7 +736,7 @@ def update_comment(request, course_id, comment_id): if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) comment.body = sanitize_body(request.POST["body"]) - comment.save() + comment.save(params={"course_id": course_id}) comment_edited.send(sender=None, user=request.user, post=comment) @@ -762,7 +762,7 @@ def endorse_comment(request, course_id, comment_id): endorsed = request.POST.get('endorsed', 'false').lower() == 'true' comment.endorsed = endorsed comment.endorsement_user_id = user.id - comment.save() + comment.save(params={"course_id": course_id}) comment_endorsed.send(sender=None, user=user, post=comment) track_forum_response_mark_event(request, course, comment, endorsed) return JsonResponse(prepare_content(comment.to_dict(), course_key)) @@ -814,7 +814,7 @@ def delete_comment(request, course_id, comment_id): course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key) comment = cc.Comment.find(comment_id) - comment.delete() + comment.delete(course_id=course_id) comment_deleted.send(sender=None, user=request.user, post=comment) track_comment_deleted_event(request, course, comment) return JsonResponse(prepare_content(comment.to_dict(), course_key)) @@ -828,12 +828,12 @@ def _vote_or_unvote(request, course_id, obj, value='up', undo_vote=False): course = get_course_with_access(request.user, 'load', course_key) user = cc.User.from_django_user(request.user) if undo_vote: - user.unvote(obj) + user.unvote(obj, course_id) # TODO(smarnach): Determine the value of the vote that is undone. Currently, you can # only cast upvotes in the user interface, so it is assumed that the vote value is 'up'. # (People could theoretically downvote by handcrafting AJAX requests.) else: - user.vote(obj, value) + user.vote(obj, value, course_id) thread_voted.send(sender=None, user=request.user, post=obj) track_voted_event(request, course, obj, value, undo_vote) return JsonResponse(prepare_content(obj.to_dict(), course_key)) @@ -899,7 +899,7 @@ def flag_abuse_for_thread(request, course_id, thread_id): user = cc.User.from_django_user(request.user) course = get_course_by_id(course_key) thread = cc.Thread.find(thread_id) - thread.flagAbuse(user, thread) + thread.flagAbuse(user, thread, course_id) track_discussion_reported_event(request, course, thread) thread_flagged.send(sender='flag_abuse_for_thread', user=request.user, post=thread) return JsonResponse(prepare_content(thread.to_dict(), course_key)) @@ -921,7 +921,7 @@ def un_flag_abuse_for_thread(request, course_id, thread_id): has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course) ) - thread.unFlagAbuse(user, thread, remove_all) + thread.unFlagAbuse(user, thread, remove_all, course_id) track_discussion_unreported_event(request, course, thread) return JsonResponse(prepare_content(thread.to_dict(), course_key)) @@ -938,7 +938,7 @@ def flag_abuse_for_comment(request, course_id, comment_id): user = cc.User.from_django_user(request.user) course = get_course_by_id(course_key) comment = cc.Comment.find(comment_id) - comment.flagAbuse(user, comment) + comment.flagAbuse(user, comment, course_id) track_discussion_reported_event(request, course, comment) comment_flagged.send(sender='flag_abuse_for_comment', user=request.user, post=comment) return JsonResponse(prepare_content(comment.to_dict(), course_key)) @@ -960,7 +960,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id): has_access(request.user, 'staff', course) ) comment = cc.Comment.find(comment_id) - comment.unFlagAbuse(user, comment, remove_all) + comment.unFlagAbuse(user, comment, remove_all, course_id) track_discussion_unreported_event(request, course, comment) return JsonResponse(prepare_content(comment.to_dict(), course_key)) @@ -1005,7 +1005,7 @@ def follow_thread(request, course_id, thread_id): # lint-amnesty, pylint: disab course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) thread = cc.Thread.find(thread_id) - user.follow(thread) + user.follow(thread, course_id=course_id) thread_followed.send(sender=None, user=request.user, post=thread) track_thread_followed_event(request, course, thread, True) return JsonResponse({}) @@ -1037,7 +1037,7 @@ def unfollow_thread(request, course_id, thread_id): # lint-amnesty, pylint: dis course = get_course_by_id(course_key) user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) - user.unfollow(thread) + user.unfollow(thread, course_id=course_id) thread_unfollowed.send(sender=None, user=request.user, post=thread) track_thread_followed_event(request, course, thread, False) return JsonResponse({}) diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py index bfa511a575bd..54ac13c68ae2 100644 --- a/lms/djangoapps/discussion/views.py +++ b/lms/djangoapps/discussion/views.py @@ -146,7 +146,7 @@ def get_threads(request, course, user_info, discussion_id=None, per_page=THREADS # If the user clicked a sort key, update their default sort key cc_user = cc.User.from_django_user(request.user) cc_user.default_sort_key = request.GET.get('sort_key') - cc_user.save() + cc_user.save(params={"course_id": course.id}) #there are 2 dimensions to consider when executing a search with respect to group id #is user a moderator diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/comment.py b/openedx/core/djangoapps/django_comment_common/comment_client/comment.py index 39b8680b6377..5f6348547efb 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/comment.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/comment.py @@ -63,14 +63,14 @@ def url(cls, action, params=None): else: return super().url(action, params) - def flagAbuse(self, user, voteable): + def flagAbuse(self, user, voteable, course_id=None): if voteable.type == 'thread': url = _url_for_flag_abuse_thread(voteable.id) elif voteable.type == 'comment': url = _url_for_flag_abuse_comment(voteable.id) else: raise CommentClientRequestError("Can only flag/unflag threads or comments") - course_key = get_course_key(self.attributes.get("course_id")) + course_key = get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): if voteable.type == 'thread': response = forum_api.update_thread_flag( @@ -91,14 +91,14 @@ def flagAbuse(self, user, voteable): ) voteable._update_from_response(response) - def unFlagAbuse(self, user, voteable, removeAll): + def unFlagAbuse(self, user, voteable, removeAll, course_id=None): if voteable.type == 'thread': url = _url_for_unflag_abuse_thread(voteable.id) elif voteable.type == 'comment': url = _url_for_unflag_abuse_comment(voteable.id) else: raise CommentClientRequestError("Can flag/unflag for threads or comments") - course_key = get_course_key(self.attributes.get("course_id")) + course_key = get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): if voteable.type == "thread": response = forum_api.update_thread_flag( diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/models.py b/openedx/core/djangoapps/django_comment_common/comment_client/models.py index 9b6c9ca03f3d..fd8c10220d3a 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/models.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/models.py @@ -175,8 +175,8 @@ def save(self, params=None): self._update_from_response(response) self.after_save(self) - def delete(self): - course_key = get_course_key(self.attributes.get("course_id")) + def delete(self, course_id=None): + course_key = get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): response = None if self.type == "comment": diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/thread.py b/openedx/core/djangoapps/django_comment_common/comment_client/thread.py index b0d9a00f4063..0313d29daae9 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/thread.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/thread.py @@ -80,6 +80,8 @@ def search(cls, query_params): search_params.pop('commentable_id', None) response = forum_api.search_threads(**search_params) else: + if user_id := params.get('user_id'): + params['user_id'] = str(user_id) response = forum_api.get_user_threads(**params) else: response = utils.perform_request( @@ -196,12 +198,12 @@ def _retrieve(self, *args, **kwargs): ) self._update_from_response(response) - def flagAbuse(self, user, voteable): + def flagAbuse(self, user, voteable, course_id=None): if voteable.type == 'thread': url = _url_for_flag_abuse_thread(voteable.id) else: raise utils.CommentClientRequestError("Can only flag/unflag threads or comments") - course_key = utils.get_course_key(self.attributes.get("course_id")) + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): response = forum_api.update_thread_flag(voteable.id, "flag", user_id=user.id, course_id=str(course_key)) else: @@ -215,7 +217,7 @@ def flagAbuse(self, user, voteable): ) voteable._update_from_response(response) - def unFlagAbuse(self, user, voteable, removeAll): + def unFlagAbuse(self, user, voteable, removeAll, course_id=None): if voteable.type == 'thread': url = _url_for_unflag_abuse_thread(voteable.id) else: diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/user.py b/openedx/core/djangoapps/django_comment_common/comment_client/user.py index 2de4fbbfa95a..4fd8bac18203 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/user.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/user.py @@ -50,8 +50,8 @@ def read(self, source): metric_tags=self._metric_tags + [f'target.type:{source.type}'], ) - def follow(self, source): - course_key = utils.get_course_key(self.attributes.get("course_id")) + def follow(self, source, course_id=None): + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): forum_api.create_subscription( user_id=self.id, @@ -68,8 +68,8 @@ def follow(self, source): metric_tags=self._metric_tags + [f'target.type:{source.type}'], ) - def unfollow(self, source): - course_key = utils.get_course_key(self.attributes.get("course_id")) + def unfollow(self, source, course_id=None): + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): forum_api.delete_subscription( user_id=self.id, @@ -86,14 +86,14 @@ def unfollow(self, source): metric_tags=self._metric_tags + [f'target.type:{source.type}'], ) - def vote(self, voteable, value): + def vote(self, voteable, value, course_id=None): if voteable.type == 'thread': url = _url_for_vote_thread(voteable.id) elif voteable.type == 'comment': url = _url_for_vote_comment(voteable.id) else: raise utils.CommentClientRequestError("Can only vote / unvote for threads or comments") - course_key = utils.get_course_key(self.attributes.get("course_id")) + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): if voteable.type == 'thread': response = forum_api.update_thread_votes( @@ -120,7 +120,7 @@ def vote(self, voteable, value): ) voteable._update_from_response(response) - def unvote(self, voteable): + def unvote(self, voteable, course_id=None): if voteable.type == 'thread': url = _url_for_vote_thread(voteable.id) elif voteable.type == 'comment': From adf089b4a35d91195ab8757ab5ad676f1a7c58c7 Mon Sep 17 00:00:00 2001 From: Ali Salman <88362079+Ali-Salman29@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:26:22 +0500 Subject: [PATCH 2/5] fix: legacy discussion issues (#36433) Explicitly passed course_id to all views --- .../discussion/django_comment_client/base/views.py | 12 ++++++------ .../django_comment_common/comment_client/thread.py | 10 +++++----- .../django_comment_common/comment_client/user.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/discussion/django_comment_client/base/views.py b/lms/djangoapps/discussion/django_comment_client/base/views.py index 55057060d310..238ef0271214 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/views.py +++ b/lms/djangoapps/discussion/django_comment_client/base/views.py @@ -715,7 +715,7 @@ def delete_thread(request, course_id, thread_id): course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key) thread = cc.Thread.find(thread_id) - thread.delete() + thread.delete(course_id=course_id) thread_deleted.send(sender=None, user=request.user, post=thread) track_thread_deleted_event(request, course, thread) @@ -781,7 +781,7 @@ def openclose_thread(request, course_id, thread_id): thread = cc.Thread.find(thread_id) close_thread = request.POST.get('closed', 'false').lower() == 'true' thread.closed = close_thread - thread.save() + thread.save(params={"course_id": course_id}) track_thread_lock_unlock_event(request, course, thread, None, close_thread) return JsonResponse({ @@ -976,7 +976,7 @@ def pin_thread(request, course_id, thread_id): course_key = CourseKey.from_string(course_id) user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) - thread.pin(user, thread_id) + thread.pin(user, thread_id, course_id) return JsonResponse(prepare_content(thread.to_dict(), course_key)) @@ -992,7 +992,7 @@ def un_pin_thread(request, course_id, thread_id): course_key = CourseKey.from_string(course_id) user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) - thread.un_pin(user, thread_id) + thread.un_pin(user, thread_id, course_id) return JsonResponse(prepare_content(thread.to_dict(), course_key)) @@ -1021,7 +1021,7 @@ def follow_commentable(request, course_id, commentable_id): # lint-amnesty, pyl """ user = cc.User.from_django_user(request.user) commentable = cc.Commentable.find(commentable_id) - user.follow(commentable) + user.follow(commentable, course_id=course_id) return JsonResponse({}) @@ -1053,7 +1053,7 @@ def unfollow_commentable(request, course_id, commentable_id): # lint-amnesty, p """ user = cc.User.from_django_user(request.user) commentable = cc.Commentable.find(commentable_id) - user.unfollow(commentable) + user.unfollow(commentable, course_id=course_id) return JsonResponse({}) diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/thread.py b/openedx/core/djangoapps/django_comment_common/comment_client/thread.py index 0313d29daae9..b5f227f7ccfb 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/thread.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/thread.py @@ -222,7 +222,7 @@ def unFlagAbuse(self, user, voteable, removeAll, course_id=None): url = _url_for_unflag_abuse_thread(voteable.id) else: raise utils.CommentClientRequestError("Can only flag/unflag for threads or comments") - course_key = utils.get_course_key(self.attributes.get("course_id")) + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): response = forum_api.update_thread_flag( thread_id=voteable.id, @@ -246,8 +246,8 @@ def unFlagAbuse(self, user, voteable, removeAll, course_id=None): ) voteable._update_from_response(response) - def pin(self, user, thread_id): - course_key = utils.get_course_key(self.attributes.get("course_id")) + def pin(self, user, thread_id, course_id=None): + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): response = forum_api.pin_thread( user_id=user.id, @@ -266,8 +266,8 @@ def pin(self, user, thread_id): ) self._update_from_response(response) - def un_pin(self, user, thread_id): - course_key = utils.get_course_key(self.attributes.get("course_id")) + def un_pin(self, user, thread_id, course_id): + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): response = forum_api.unpin_thread( user_id=user.id, diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/user.py b/openedx/core/djangoapps/django_comment_common/comment_client/user.py index 4fd8bac18203..eaac6b408659 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/user.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/user.py @@ -127,7 +127,7 @@ def unvote(self, voteable, course_id=None): url = _url_for_vote_comment(voteable.id) else: raise utils.CommentClientRequestError("Can only vote / unvote for threads or comments") - course_key = utils.get_course_key(self.attributes.get("course_id")) + course_key = utils.get_course_key(self.attributes.get("course_id") or course_id) if is_forum_v2_enabled(course_key): if voteable.type == 'thread': response = forum_api.delete_thread_vote( From ef8e9dc1df173f0346b59eaae95de19974af2d7a Mon Sep 17 00:00:00 2001 From: Taimoor Ahmed <68893403+taimoor-ahmed-1@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:04:14 +0500 Subject: [PATCH 3/5] fix: legacy forum issues (#36470) Co-authored-by: Taimoor Ahmed --- .../discussion/django_comment_client/base/views.py | 2 +- lms/djangoapps/discussion/views.py | 8 ++++---- .../django_comment_common/comment_client/models.py | 6 +++--- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/discussion/django_comment_client/base/views.py b/lms/djangoapps/discussion/django_comment_client/base/views.py index 238ef0271214..458b0a02857e 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/views.py +++ b/lms/djangoapps/discussion/django_comment_client/base/views.py @@ -584,7 +584,7 @@ def create_thread(request, course_id, commentable_id): if follow: cc_user = cc.User.from_django_user(user) - cc_user.follow(thread) + cc_user.follow(thread, course_id) thread_followed.send(sender=None, user=user, post=thread) data = thread.to_dict() diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py index 54ac13c68ae2..3a3c8c133471 100644 --- a/lms/djangoapps/discussion/views.py +++ b/lms/djangoapps/discussion/views.py @@ -146,7 +146,7 @@ def get_threads(request, course, user_info, discussion_id=None, per_page=THREADS # If the user clicked a sort key, update their default sort key cc_user = cc.User.from_django_user(request.user) cc_user.default_sort_key = request.GET.get('sort_key') - cc_user.save(params={"course_id": course.id}) + cc_user.save(params={"course_id": str(course.id)}) #there are 2 dimensions to consider when executing a search with respect to group id #is user a moderator @@ -218,7 +218,7 @@ def inline_discussion(request, course_key, discussion_id): with function_trace('get_course_and_user_info'): course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) cc_user = cc.User.from_django_user(request.user) - user_info = cc_user.to_dict() + user_info = cc_user.to_dict(course_key=str(course_key)) try: with function_trace('get_threads'): @@ -356,7 +356,7 @@ def single_thread(request, course_key, discussion_id, thread_id): if request.headers.get('x-requested-with') == 'XMLHttpRequest': cc_user = cc.User.from_django_user(request.user) - user_info = cc_user.to_dict() + user_info = cc_user.to_dict(course_key=str(course_key)) is_staff = has_permission(request.user, 'openclose_thread', course.id) try: @@ -471,7 +471,7 @@ def _create_base_discussion_view_context(request, course_key): """ user = request.user cc_user = cc.User.from_django_user(user) - user_info = cc_user.to_dict() + user_info = cc_user.to_dict(course_key=str(course_key)) course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True) course_settings = make_course_settings(course, user) return { diff --git a/openedx/core/djangoapps/django_comment_common/comment_client/models.py b/openedx/core/djangoapps/django_comment_common/comment_client/models.py index fd8c10220d3a..1812d24dec0a 100644 --- a/openedx/core/djangoapps/django_comment_common/comment_client/models.py +++ b/openedx/core/djangoapps/django_comment_common/comment_client/models.py @@ -61,8 +61,8 @@ def items(self, *args, **kwargs): def get(self, *args, **kwargs): return self.attributes.get(*args, **kwargs) - def to_dict(self): - self.retrieve() + def to_dict(self, course_key=None): + self.retrieve(course_key=course_key) return self.attributes def retrieve(self, *args, **kwargs): @@ -72,7 +72,7 @@ def retrieve(self, *args, **kwargs): return self def _retrieve(self, *args, **kwargs): - course_id = self.attributes.get("course_id") or kwargs.get("course_id") + course_id = self.attributes.get("course_id") or kwargs.get("course_key") if course_id: course_key = get_course_key(course_id) use_forumv2 = is_forum_v2_enabled(course_key) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index b665260a7910..323b1f8b7e38 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -836,7 +836,7 @@ openedx-filters==1.11.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-forum==0.1.5 +openedx-forum==0.2.0 # via -r requirements/edx/kernel.in openedx-learning==0.18.1 # via diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 5f30322f02f1..ff64170840e4 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1388,7 +1388,7 @@ openedx-filters==1.11.0 # -r requirements/edx/testing.txt # lti-consumer-xblock # ora2 -openedx-forum==0.1.5 +openedx-forum==0.2.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 26fbe0615b44..4af7e8354c68 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1003,7 +1003,7 @@ openedx-filters==1.11.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-forum==0.1.5 +openedx-forum==0.2.0 # via -r requirements/edx/base.txt openedx-learning==0.18.1 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index f282363707c4..205b54216f70 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1048,7 +1048,7 @@ openedx-filters==1.11.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-forum==0.1.5 +openedx-forum==0.2.0 # via -r requirements/edx/base.txt openedx-learning==0.18.1 # via From 48fd730d441f80390c64aa6c722ad0de87596e9a Mon Sep 17 00:00:00 2001 From: Kyle McCormick Date: Tue, 8 Apr 2025 13:00:56 -0400 Subject: [PATCH 4/5] build: Switch off deprecated C-Hive NPM cache (#36502) JS tests are failing because we are using a discontinued GHA caching service: https://github.blog/changelog/2025-03-20-notification-of-upcoming-breaking-changes-in-github-actions/#decommissioned-cache-service-brownouts This service is used by the unsupported C-Hive caching action which we are relying on: https://github.com/c-hive/gha-npm-cache We are switching to the supported caching mechanims which is provided by setup-node: https://github.com/actions/setup-node?tab=readme-ov-file#caching-global-packages-data --- .github/workflows/js-tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index c9d2d7ab1191..20f097d9ef65 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -26,6 +26,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + cache: 'npm' - name: Setup npm run: npm i -g npm@10.5.x @@ -63,7 +64,9 @@ jobs: run: | make base-requirements - - uses: c-hive/gha-npm-cache@v1 + - name: Install npm + run: npm ci + - name: Run JS Tests env: TEST_SUITE: js-unit From 31e60cb91052d5c0c949bfef31366f8036436e69 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 7 Nov 2024 11:20:17 -0500 Subject: [PATCH 5/5] Merge pull request #35713 from openedx/feanil/ubuntu-24.04 feanil/ubuntu 24.04 --- .../check-consistent-dependencies.yml | 2 +- .github/workflows/ci-static-analysis.yml | 2 +- .github/workflows/migrations-check.yml | 4 +- .github/workflows/pylint-checks.yml | 4 +- .github/workflows/quality-checks.yml | 2 +- .github/workflows/static-assets-check.yml | 2 +- .github/workflows/unit-tests.yml | 62 +++++++++---------- requirements/constraints.txt | 12 ---- requirements/edx-sandbox/base.in | 2 +- requirements/edx-sandbox/base.txt | 6 +- requirements/edx/base.txt | 12 ++-- requirements/edx/development.txt | 12 ++-- requirements/edx/doc.txt | 11 ++-- requirements/edx/kernel.in | 2 +- requirements/edx/testing.txt | 11 ++-- scripts/user_retirement/requirements/base.txt | 6 +- .../user_retirement/requirements/testing.txt | 2 +- 17 files changed, 74 insertions(+), 80 deletions(-) diff --git a/.github/workflows/check-consistent-dependencies.yml b/.github/workflows/check-consistent-dependencies.yml index 048cbe6b006b..82298af70a54 100644 --- a/.github/workflows/check-consistent-dependencies.yml +++ b/.github/workflows/check-consistent-dependencies.yml @@ -15,7 +15,7 @@ defaults: jobs: check-requirements: name: Compile requirements - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: # Only run remaining steps if there are changes to requirements/** diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 458e00fc6b1f..d989ff9db288 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -10,7 +10,7 @@ jobs: matrix: python-version: - "3.11" - os: ["ubuntu-22.04"] + os: ["ubuntu-24.04"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index 624caddd5309..84e334d68872 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" # 'pinned' is used to install the latest patch version of Django @@ -126,7 +126,7 @@ jobs: if: always() needs: - check_migrations - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 8860aced7f92..9a654e09e711 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -8,7 +8,7 @@ on: jobs: run-pylint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -75,7 +75,7 @@ jobs: if: always() needs: - run-pylint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 84610123493c..310f9f83bf3d 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" node-version: [20] diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 4fe66e2a7778..e08b2dce8127 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" node-version: [18, 20] diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 854677b93cff..e691e16e47f1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,7 @@ concurrency: jobs: run-tests: name: ${{ matrix.shard_name }}(py=${{ matrix.python-version }},dj=${{ matrix.django-version }},mongo=${{ matrix.mongo-version }}) - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os-version }} strategy: matrix: python-version: @@ -43,22 +43,27 @@ jobs: - "xmodule-with-cms" mongo-version: - "7.0" + os-version: + - ubuntu-24.04 - # We only need to test older versions of Mongo with modules that directly interface with Mongo (that is: xmodule.modulestore) - # This code is left here as an example for future refernce in case we need to reduce the number of shards we're - # testing but still have good confidence with older versions of mongo. We use Mongo 4.4 in the example. + # It's useful to run some subset of the tests on the older version of Ubuntu + # so that we don't spend too many resources on this but can find major issues quickly + # while we're in a situation where we support two versions. This section may be commented + # out when not in use to easily add/drop future support for any given major dependency. # - # exclude: - # - mongo-version: "4.4" - # include: - # - shard_name: "xmodule-with-cms" - # python-version: "3.11" - # django-version: "pinned" - # mongo-version: "4.4" - # - shard_name: "xmodule-with-lms" - # python-version: "3.11" - # django-version: "pinned" - # mongo-version: "4.4" + # We're testing the older version of Ubuntu and running the xmodule tests since those rely on many + # dependent complex libraries and will hopefully catch most issues quickly. + include: + - shard_name: "xmodule-with-cms" + python-version: "3.11" + django-version: "pinned" + mongo-version: "7.0" + os-version: "ubuntu-22.04" + - shard_name: "xmodule-with-lms" + python-version: "3.11" + django-version: "pinned" + mongo-version: "7.0" + os-version: "ubuntu-22.04" steps: - name: checkout repo @@ -90,19 +95,10 @@ jobs: activate = 1 EOF - - name: install mongo version - run: | - if [[ "${{ matrix.mongo-version }}" != "4.4" ]]; then - wget -qO - https://www.mongodb.org/static/pgp/server-${{ matrix.mongo-version }}.asc | sudo apt-key add - - echo "deb https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/${{ matrix.mongo-version }} multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-${{ matrix.mongo-version }}.list - sudo apt-get update && sudo apt-get install -y mongodb-org="${{ matrix.mongo-version }}.*" - fi - - - name: start mongod server for tests - run: | - sudo mkdir -p /data/db - sudo chmod -R a+rw /data/db - mongod & + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.11.0 + with: + mongodb-version: ${{ matrix.mongo-version }} - name: Setup Python uses: actions/setup-python@v5 @@ -164,7 +160,7 @@ jobs: overwrite: true collect-and-verify: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Setup Python @@ -229,7 +225,7 @@ jobs: # https://github.com/orgs/community/discussions/33579 success: name: Unit tests successful - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: always() needs: [run-tests] steps: @@ -240,7 +236,7 @@ jobs: jobs: ${{ toJSON(needs) }} compile-warnings-report: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [run-tests] steps: - uses: actions/checkout@v4 @@ -268,7 +264,7 @@ jobs: overwrite: true merge-artifacts: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [compile-warnings-report] steps: - name: Merge Pytest Warnings JSON Artifacts @@ -288,7 +284,7 @@ jobs: # Combine and upload coverage reports. coverage: if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false)) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [run-tests] strategy: matrix: diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 969c20e114de..9a9207bd8eef 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -105,13 +105,6 @@ event-tracking==3.0.0 # https://github.com/openedx/edx-platform/issues/31616 libsass==0.10.0 -# Date: 2024-04-30 -# lxml>=5.0 introduced breaking changes related to system dependencies -# lxml==5.2.1 introduced new extra so we'll nee to rename lxml --> lxml[html-clean] -# This constraint can be removed once we upgrade to Python 3.11 -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35272 -lxml<5.0 - # Date: 2018-12-14 # markdown>=3.4.0 has failures due to internal refactorings which causes the tests to fail # pinning the version untill the issue gets resolved in the package itself @@ -192,8 +185,3 @@ social-auth-app-django<=5.4.1 # # Date: 2024-10-14 # # The edx-enterprise is currently using edx-rest-api-client==5.7.1, which needs to be updated first. # edx-rest-api-client==5.7.1 - -# Date: 2024-04-24 -# xmlsec==1.3.14 breaking tests or all builds, can be removed once a fix is available -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35264 -xmlsec<1.3.14 diff --git a/requirements/edx-sandbox/base.in b/requirements/edx-sandbox/base.in index 4c1a61bc2723..0c331ce95d62 100644 --- a/requirements/edx-sandbox/base.in +++ b/requirements/edx-sandbox/base.in @@ -2,7 +2,7 @@ chem # A helper library for chemistry calculations cryptography # Implementations of assorted cryptography algorithms -lxml # XML parser +lxml[html_clean] # XML parser matplotlib # 2D plotting library networkx # Utilities for creating, manipulating, and studying network graphs nltk # Natural language processing; used by the chem package diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index 991ea0efcf6a..92ee57f8b24b 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -26,11 +26,13 @@ joblib==1.4.2 # via nltk kiwisolver==1.4.7 # via matplotlib -lxml==4.9.4 +lxml[html-clean,html_clean]==5.3.0 # via - # -c requirements/edx-sandbox/../constraints.txt # -r requirements/edx-sandbox/base.in + # lxml-html-clean # openedx-calc +lxml-html-clean==0.3.1 + # via lxml markupsafe==3.0.2 # via # chem diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 323b1f8b7e38..94084345ad47 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -715,19 +715,21 @@ loremipsum==1.0.5 # via ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/kernel.in -lxml==4.9.4 +lxml[html-clean,html_clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via lxml mailsnake==1.6.4 # via -r requirements/edx/bundled.in mako==1.3.6 @@ -1301,10 +1303,8 @@ xblock-utils==4.0.0 # via # edx-sga # xblock-poll -xmlsec==1.3.13 - # via - # -c requirements/edx/../constraints.txt - # python3-saml +xmlsec==1.3.14 + # via python3-saml xss-utils==0.6.0 # via -r requirements/edx/kernel.in yarl==1.16.0 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index ff64170840e4..c0667cf4959c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1193,14 +1193,14 @@ lti-consumer-xblock==9.11.3 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 @@ -1208,6 +1208,11 @@ lxml==4.9.4 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/doc.txt + # -r requirements/edx/testing.txt + # lxml mailsnake==1.6.4 # via # -r requirements/edx/doc.txt @@ -2293,9 +2298,8 @@ xblock-utils==4.0.0 # -r requirements/edx/testing.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # python3-saml diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 4af7e8354c68..a5ed7ac21f28 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -863,19 +863,23 @@ loremipsum==1.0.5 # ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/base.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/base.txt + # lxml mailsnake==1.6.4 # via -r requirements/edx/base.txt mako==1.3.6 @@ -1607,9 +1611,8 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.6.0 diff --git a/requirements/edx/kernel.in b/requirements/edx/kernel.in index 575c621a85d7..60f49c5917e1 100644 --- a/requirements/edx/kernel.in +++ b/requirements/edx/kernel.in @@ -103,7 +103,7 @@ importlib_metadata # Used to access entry_points in i18n_api pl jsonfield # Django model field for validated JSON; used in several apps laboratory # Library for testing that code refactors/infrastructure changes produce identical results importlib_metadata # Used to access entry_points in i18n_api plugin -lxml # XML parser +lxml[html_clean] # XML parser lti-consumer-xblock>=7.3.0 mako # Primary template language used for server-side page rendering Markdown # Convert text markup to HTML; used in capa problems, forums, and course wikis diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 205b54216f70..309887480f96 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -906,13 +906,13 @@ loremipsum==1.0.5 # ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/base.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 @@ -920,6 +920,10 @@ lxml==4.9.4 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/base.txt + # lxml mailsnake==1.6.4 # via -r requirements/edx/base.txt mako==1.3.6 @@ -1694,9 +1698,8 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.6.0 diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt index 46f18b2415f6..3d81950df7e7 100644 --- a/scripts/user_retirement/requirements/base.txt +++ b/scripts/user_retirement/requirements/base.txt @@ -77,10 +77,8 @@ jmespath==1.0.1 # via # boto3 # botocore -lxml==4.9.4 - # via - # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt - # zeep +lxml==5.3.0 + # via zeep more-itertools==10.5.0 # via simple-salesforce newrelic==10.2.0 diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt index ab56b97801ed..16f374375185 100644 --- a/scripts/user_retirement/requirements/testing.txt +++ b/scripts/user_retirement/requirements/testing.txt @@ -116,7 +116,7 @@ jmespath==1.0.1 # -r scripts/user_retirement/requirements/base.txt # boto3 # botocore -lxml==4.9.4 +lxml==5.3.0 # via # -r scripts/user_retirement/requirements/base.txt # zeep