From aa70fea890ecd622d392270cd21453b9ae9fe578 Mon Sep 17 00:00:00 2001
From: Asespinel <79876430+Asespinel@users.noreply.github.com>
Date: Wed, 19 Jun 2024 09:22:25 -0500
Subject: [PATCH 01/18] feat: hide the survey report banner for a month after
clicking the dismiss button (#34914)
This hides the survey report banner from the Django Admin for a
particular user for one month after they click on the "dismiss" button.
This is done completely on the client side using localStorage, so the
same user could see the banner again if they're logging in with a
different browser.
---
.../static/survey_report/js/admin_banner.js | 46 +++++++++++++++++--
.../templates/survey_report/admin_banner.html | 3 +-
2 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/openedx/features/survey_report/static/survey_report/js/admin_banner.js b/openedx/features/survey_report/static/survey_report/js/admin_banner.js
index d8650321ba36..8dd9ba5565e0 100644
--- a/openedx/features/survey_report/static/survey_report/js/admin_banner.js
+++ b/openedx/features/survey_report/static/survey_report/js/admin_banner.js
@@ -1,11 +1,51 @@
$(document).ready(function(){
+ // Function to get user ID
+ function getUserId() {
+ return $('#userIdSurvey').val();
+ }
+
+ // Function to get current time in milliseconds
+ function getCurrentTime() {
+ return new Date().getTime();
+ }
+
+ // Function to set dismissal time and expiration time in local storage
+ function setDismissalAndExpirationTime(userId, dismissalTime) {
+ let expirationTime = dismissalTime + (30 * 24 * 60 * 60 * 1000); // 30 days
+ localStorage.setItem('bannerDismissalTime_' + userId, dismissalTime);
+ localStorage.setItem('bannerExpirationTime_' + userId, expirationTime);
+ }
+
+ // Function to check if banner should be shown or hidden
+ function checkBannerVisibility() {
+ let userId = getUserId();
+ let bannerDismissalTime = localStorage.getItem('bannerDismissalTime_' + userId);
+ let bannerExpirationTime = localStorage.getItem('bannerExpirationTime_' + userId);
+ let currentTime = getCurrentTime();
+
+ if (bannerDismissalTime && bannerExpirationTime && currentTime > bannerExpirationTime) {
+ // Banner was dismissed and it's not within the expiration period, so show it
+ $('#originalContent').show();
+ } else if (bannerDismissalTime && bannerExpirationTime && currentTime < bannerExpirationTime) {
+ // Banner was dismissed and it's within the expiration period, so hide it
+ $('#originalContent').hide();
+ } else {
+ // Banner has not been dismissed ever so we need to show it.
+ $('#originalContent').show();
+ }
+ }
+
+ // Click event for dismiss button
$('#dismissButton').click(function() {
$('#originalContent').slideUp('slow', function() {
- // If you want to do something after the slide-up, do it here.
- // For example, you can hide the entire div:
- // $(this).hide();
+ let userId = getUserId();
+ let dismissalTime = getCurrentTime();
+ setDismissalAndExpirationTime(userId, dismissalTime);
});
});
+
+ // Check banner visibility on page load
+ checkBannerVisibility();
// When the form is submitted
$("#survey_report_form").submit(function(event){
event.preventDefault(); // Prevent the form from submitting traditionally
diff --git a/openedx/features/survey_report/templates/survey_report/admin_banner.html b/openedx/features/survey_report/templates/survey_report/admin_banner.html
index fa0b37ecf751..e13eb655f63c 100644
--- a/openedx/features/survey_report/templates/survey_report/admin_banner.html
+++ b/openedx/features/survey_report/templates/survey_report/admin_banner.html
@@ -1,7 +1,7 @@
{% block survey_report_banner %}
{% load static %}
{% if show_survey_report_banner %}
-
+
Join the Open edX Data Sharing Initiative and shape the future of learning
@@ -15,6 +15,7 @@
Join the Open edX Data Sh
From e5f074b45d94741a6476b3e471d2bf9042be7efe Mon Sep 17 00:00:00 2001
From: magajh
Date: Mon, 15 Jul 2024 09:23:25 -0400
Subject: [PATCH 02/18] chore: upgrade Django to 4.2.14
---
requirements/edx/base.txt | 2 +-
requirements/edx/development.txt | 2 +-
requirements/edx/doc.txt | 2 +-
requirements/edx/testing.txt | 2 +-
scripts/user_retirement/requirements/base.txt | 2 +-
scripts/user_retirement/requirements/testing.txt | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 2ae5ba235a89..db14ceee890f 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -177,7 +177,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
-django==4.2.13
+django==4.2.14
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 8b1bb858efe2..cda3abc1db76 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -347,7 +347,7 @@ distlib==0.3.8
# via
# -r requirements/edx/testing.txt
# virtualenv
-django==4.2.13
+django==4.2.14
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt
index 9e1dee112300..d766c8f0298a 100644
--- a/requirements/edx/doc.txt
+++ b/requirements/edx/doc.txt
@@ -227,7 +227,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
-django==4.2.13
+django==4.2.14
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 452fec9a8643..67cdbe56435b 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -263,7 +263,7 @@ dill==0.3.8
# via pylint
distlib==0.3.8
# via virtualenv
-django==4.2.13
+django==4.2.14
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt
index 934f4293d699..1e510e645926 100644
--- a/scripts/user_retirement/requirements/base.txt
+++ b/scripts/user_retirement/requirements/base.txt
@@ -39,7 +39,7 @@ click==8.1.6
# edx-django-utils
cryptography==42.0.7
# via pyjwt
-django==4.2.13
+django==4.2.14
# via
# -c scripts/user_retirement/requirements/../../../requirements/common_constraints.txt
# -c scripts/user_retirement/requirements/../../../requirements/constraints.txt
diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt
index 2bae9bb1a272..15cc2d9849bf 100644
--- a/scripts/user_retirement/requirements/testing.txt
+++ b/scripts/user_retirement/requirements/testing.txt
@@ -56,7 +56,7 @@ cryptography==42.0.7
# pyjwt
ddt==1.7.2
# via -r scripts/user_retirement/requirements/testing.in
-django==4.2.13
+django==4.2.14
# via
# -r scripts/user_retirement/requirements/base.txt
# django-crum
From 4744ea1c39f22d397a0206a4761d833496a2851d Mon Sep 17 00:00:00 2001
From: Muhammad Soban Javed
Date: Mon, 10 Jun 2024 23:31:34 +0500
Subject: [PATCH 03/18] chore!: uprgade social-auth-app-django to version 5.4.1
---
requirements/constraints.txt | 8 --------
requirements/edx/base.txt | 7 +++----
requirements/edx/development.txt | 7 +++----
requirements/edx/doc.txt | 7 +++----
requirements/edx/testing.txt | 7 +++----
5 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/requirements/constraints.txt b/requirements/constraints.txt
index 8f7a66f9cc2f..866ac9e3bf7d 100644
--- a/requirements/constraints.txt
+++ b/requirements/constraints.txt
@@ -62,14 +62,6 @@ pycodestyle<2.9.0
pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticket.
-# adding these constraints to minimize boto3 and botocore changeset
-social-auth-core==4.3.0
-
-# social-auth-app-django versions after 5.2.0 has a problematic migration that will cause issues deployments with large
-# `social_auth_usersocialauth` tables. 5.1.0 has missing migration and 5.2.0 has that problematic migration.
-social-auth-app-django==5.0.0
-
-
# urllib3>=2.0.0 conflicts with elastic search && snowflake-connector-python packages
# which require urllib3<2 for now.
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/32222
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index db14ceee890f..44fbf21df2eb 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -246,6 +246,7 @@ django==4.2.14
# openedx-filters
# openedx-learning
# ora2
+ # social-auth-app-django
# super-csv
# xblock-google-drive
# xss-utils
@@ -1097,14 +1098,12 @@ slumber==0.7.1
# edx-rest-api-client
snowflake-connector-python==3.10.0
# via edx-enterprise
-social-auth-app-django==5.0.0
+social-auth-app-django==5.4.1
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
# edx-auth-backends
-social-auth-core==4.3.0
+social-auth-core==4.5.4
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
# edx-auth-backends
# social-auth-app-django
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index cda3abc1db76..93e4b117a852 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -420,6 +420,7 @@ django==4.2.14
# openedx-filters
# openedx-learning
# ora2
+ # social-auth-app-django
# super-csv
# xblock-google-drive
# xss-utils
@@ -1945,15 +1946,13 @@ snowflake-connector-python==3.10.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-enterprise
-social-auth-app-django==5.0.0
+social-auth-app-django==5.4.1
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-auth-backends
-social-auth-core==4.3.0
+social-auth-core==4.5.4
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-auth-backends
diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt
index d766c8f0298a..d5d3136349f1 100644
--- a/requirements/edx/doc.txt
+++ b/requirements/edx/doc.txt
@@ -296,6 +296,7 @@ django==4.2.14
# openedx-filters
# openedx-learning
# ora2
+ # social-auth-app-django
# super-csv
# xblock-google-drive
# xss-utils
@@ -1300,14 +1301,12 @@ snowflake-connector-python==3.10.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
-social-auth-app-django==5.0.0
+social-auth-app-django==5.4.1
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
-social-auth-core==4.3.0
+social-auth-core==4.5.4
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
# social-auth-app-django
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 67cdbe56435b..3c109ddaf98f 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -332,6 +332,7 @@ django==4.2.14
# openedx-filters
# openedx-learning
# ora2
+ # social-auth-app-django
# super-csv
# xblock-google-drive
# xss-utils
@@ -1466,14 +1467,12 @@ snowflake-connector-python==3.10.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
-social-auth-app-django==5.0.0
+social-auth-app-django==5.4.1
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
-social-auth-core==4.3.0
+social-auth-core==4.5.4
# via
- # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
# social-auth-app-django
From 670772b2b9a677560775f9a5b4959de00f7571ab Mon Sep 17 00:00:00 2001
From: Muhammad Soban Javed
Date: Fri, 28 Jun 2024 01:49:59 +0500
Subject: [PATCH 04/18] chore: add migration from social_django
---
...ricalusersocialauth_extra_data_and_more.py | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py
diff --git a/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py b/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py
new file mode 100644
index 000000000000..5f09d0cc493b
--- /dev/null
+++ b/lms/djangoapps/support/migrations/0006_alter_historicalusersocialauth_extra_data_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.13 on 2024-06-27 20:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('support', '0005_unique_course_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='historicalusersocialauth',
+ name='extra_data',
+ field=models.JSONField(default=dict),
+ ),
+ migrations.AlterField(
+ model_name='historicalusersocialauth',
+ name='id',
+ field=models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID'),
+ ),
+ ]
From 2165da0a344db6f1e18af530aa6c699135570793 Mon Sep 17 00:00:00 2001
From: Feanil Patel
Date: Tue, 23 Jul 2024 10:51:32 -0400
Subject: [PATCH 05/18] chore: Re-compile requirements.
---
requirements/common_constraints.txt | 8 ++++++++
requirements/edx/base.txt | 1 +
requirements/edx/development.txt | 1 +
requirements/edx/doc.txt | 1 +
requirements/edx/testing.txt | 1 +
5 files changed, 12 insertions(+)
diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt
index 4abc9ae22cb3..ef8bc86061b7 100644
--- a/requirements/common_constraints.txt
+++ b/requirements/common_constraints.txt
@@ -21,6 +21,7 @@ Django<5.0
# elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process.
# elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html
+# See https://github.com/openedx/edx-platform/issues/35126 for more info
elasticsearch<7.14.0
# django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected
@@ -33,3 +34,10 @@ elasticsearch<7.14.0
# So we need to pin it globally, for now.
# Ticket for unpinning: https://github.com/openedx/edx-lint/issues/407
importlib-metadata<7
+
+# Cause: https://github.com/openedx/event-tracking/pull/290
+# event-tracking 2.4.1 upgrades to pymongo 4.4.0 which is not supported on edx-platform.
+# We will pin event-tracking to do not break existing installations
+# This can be unpinned once https://github.com/openedx/edx-platform/issues/34586
+# has been resolved and edx-platform is running with pymongo>=4.4.0
+event-tracking<2.4.1
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 44fbf21df2eb..d68c66cc6ef4 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -567,6 +567,7 @@ enmerkar-underscore==2.3.0
# via -r requirements/edx/kernel.in
event-tracking==2.4.0
# via
+ # -c requirements/edx/../common_constraints.txt
# -r requirements/edx/kernel.in
# edx-completion
# edx-proctoring
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 93e4b117a852..f78a27d03813 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -895,6 +895,7 @@ enmerkar-underscore==2.3.0
# -r requirements/edx/testing.txt
event-tracking==2.4.0
# via
+ # -c requirements/edx/../common_constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-completion
diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt
index d5d3136349f1..de65c64de287 100644
--- a/requirements/edx/doc.txt
+++ b/requirements/edx/doc.txt
@@ -651,6 +651,7 @@ enmerkar-underscore==2.3.0
# via -r requirements/edx/base.txt
event-tracking==2.4.0
# via
+ # -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
# edx-completion
# edx-proctoring
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 3c109ddaf98f..0baf5e170300 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -688,6 +688,7 @@ enmerkar-underscore==2.3.0
# via -r requirements/edx/base.txt
event-tracking==2.4.0
# via
+ # -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
# edx-completion
# edx-proctoring
From ed72248eb6acc785acc989148bf8f12d518d296e Mon Sep 17 00:00:00 2001
From: connorhaugh
Date: Thu, 15 Feb 2024 18:04:49 +0000
Subject: [PATCH 06/18] fix: libraries across orgs
---
cms/djangoapps/contentstore/utils.py | 4 +-
cms/djangoapps/contentstore/views/library.py | 53 +++++++++++++++----
.../contentstore/views/tests/test_library.py | 39 +++++++++-----
3 files changed, 70 insertions(+), 26 deletions(-)
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index 9bcd77c3eea9..1b5af4f769bd 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -1670,7 +1670,7 @@ def get_home_context(request, no_course=False):
LIBRARY_AUTHORING_MICROFRONTEND_URL,
LIBRARIES_ENABLED,
should_redirect_to_library_authoring_mfe,
- user_can_create_library,
+ user_can_view_create_library_button,
)
active_courses = []
@@ -1699,7 +1699,7 @@ def get_home_context(request, no_course=False):
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
'taxonomy_list_mfe_url': get_taxonomy_list_url(),
'libraries': libraries,
- 'show_new_library_button': user_can_create_library(user) and not should_redirect_to_library_authoring_mfe(),
+ 'show_new_library_button': user_can_view_create_library_button(user) and not should_redirect_to_library_authoring_mfe(),
'user': user,
'request_course_creator_url': reverse('request_course_creator'),
'course_creator_status': _get_course_creator_status(user),
diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py
index 17aa24c5712a..8ea6bf1e3657 100644
--- a/cms/djangoapps/contentstore/views/library.py
+++ b/cms/djangoapps/contentstore/views/library.py
@@ -10,7 +10,7 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
-from django.http import Http404, HttpResponseForbidden, HttpResponseNotAllowed
+from django.http import Http404, HttpResponseNotAllowed
from django.utils.translation import gettext as _
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
@@ -68,13 +68,10 @@ def should_redirect_to_library_authoring_mfe():
REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND.is_enabled()
)
-
-def user_can_create_library(user, org=None):
+def user_can_view_create_library_button(user):
"""
- Helper method for returning the library creation status for a particular user,
- taking into account the value LIBRARIES_ENABLED.
+ Helper method for displaying the visibilty of the create_library_button.
"""
-
if not LIBRARIES_ENABLED:
return False
elif user.is_staff:
@@ -84,8 +81,48 @@ def user_can_create_library(user, org=None):
has_org_staff_role = OrgStaffRole().get_orgs_for_user(user).exists()
has_course_staff_role = UserBasedRole(user=user, role=CourseStaffRole.ROLE).courses_with_role().exists()
has_course_admin_role = UserBasedRole(user=user, role=CourseInstructorRole.ROLE).courses_with_role().exists()
+ return is_course_creator or has_org_staff_role or has_course_staff_role or has_course_admin_role
+ else:
+ # EDUCATOR-1924: DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION, if present.
+ disable_library_creation = settings.FEATURES.get('DISABLE_LIBRARY_CREATION', None)
+ disable_course_creation = settings.FEATURES.get('DISABLE_COURSE_CREATION', False)
+ if disable_library_creation is not None:
+ return not disable_library_creation
+ else:
+ return not disable_course_creation
+
+
+def user_can_create_library(user, org):
+ """
+ Helper method for returning the library creation status for a particular user,
+ taking into account the value LIBRARIES_ENABLED.
+
+ users can only create libraries in orgs they are a part of.
+ """
+ if org is None:
+ return False
+ if not LIBRARIES_ENABLED:
+ return False
+ elif user.is_staff:
+ return True
+ if settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
+ is_course_creator = get_course_creator_status(user) == 'granted'
+ has_org_staff_role = org in OrgStaffRole().get_orgs_for_user(user)
+ has_course_staff_role = (
+ UserBasedRole(user=user, role=CourseStaffRole.ROLE)
+ .courses_with_role()
+ .filter(org=org)
+ .exists()
+ )
+ has_course_admin_role = (
+ UserBasedRole(user=user, role=CourseInstructorRole.ROLE)
+ .courses_with_role()
+ .filter(org=org)
+ .exists()
+ )
return is_course_creator or has_org_staff_role or has_course_staff_role or has_course_admin_role
+
else:
# EDUCATOR-1924: DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION, if present.
disable_library_creation = settings.FEATURES.get('DISABLE_LIBRARY_CREATION', None)
@@ -108,12 +145,8 @@ def library_handler(request, library_key_string=None):
raise Http404 # Should never happen because we test the feature in urls.py also
if request.method == 'POST':
- if not user_can_create_library(request.user):
- return HttpResponseForbidden()
-
if library_key_string is not None:
return HttpResponseNotAllowed(("POST",))
-
return _create_library(request)
else:
diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py
index f6b7a48a68e1..fdbb9d905c62 100644
--- a/cms/djangoapps/contentstore/views/tests/test_library.py
+++ b/cms/djangoapps/contentstore/views/tests/test_library.py
@@ -59,55 +59,66 @@ def setUp(self):
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", False)
def test_library_creator_status_libraries_not_enabled(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
- self.assertEqual(user_can_create_library(nostaff_user), False)
+ self.assertEqual(user_can_create_library(nostaff_user, None), False)
# When creator group is disabled, non-staff users can create libraries
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_no_course_creator_role(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
- self.assertEqual(user_can_create_library(nostaff_user), True)
+ self.assertEqual(user_can_create_library(nostaff_user, 'An Org'), True)
# When creator group is enabled, Non staff users cannot create libraries
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_for_enabled_creator_group_setting_for_non_staff_users(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
- self.assertEqual(user_can_create_library(nostaff_user), False)
+ self.assertEqual(user_can_create_library(nostaff_user, None), False)
- # Global staff can create libraries
+ # Global staff can create libraries for any org, even ones that don't exist.
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_is_staff_user(self):
- self.assertEqual(user_can_create_library(self.user), True)
+ print(self.user.is_staff)
+ self.assertEqual(user_can_create_library(self.user, 'aNyOrg'), True)
- # When creator groups are enabled, global staff can create libraries
+ # Global staff can create libraries for any org, but an org has to be supplied.
+ @mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
+ def test_library_creator_status_with_is_staff_user_no_org(self):
+ print(self.user.is_staff)
+ self.assertEqual(user_can_create_library(self.user, None), False)
+
+ # When creator groups are enabled, global staff can create libraries in any org
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_for_enabled_creator_group_setting_with_is_staff_user(self):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
- self.assertEqual(user_can_create_library(self.user), True)
+ self.assertEqual(user_can_create_library(self.user, 'RandomOrg'), True)
- # When creator groups are enabled, course creators can create libraries
+ # When creator groups are enabled, course creators can create libraries in any org.
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_course_creator_role_for_enabled_creator_group_setting(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
grant_course_creator_status(self.user, nostaff_user)
- self.assertEqual(user_can_create_library(nostaff_user), True)
+ self.assertEqual(user_can_create_library(nostaff_user, 'soMeRandOmoRg'), True)
# When creator groups are enabled, course staff members can create libraries
+ # but only in the org they are course staff for.
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_course_staff_role_for_enabled_creator_group_setting(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
auth.add_users(self.user, CourseStaffRole(self.course.id), nostaff_user)
- self.assertEqual(user_can_create_library(nostaff_user), True)
+ self.assertEqual(user_can_create_library(nostaff_user, self.course.org), True)
+ self.assertEqual(user_can_create_library(nostaff_user, 'SomEOtherOrg'), False)
# When creator groups are enabled, course instructor members can create libraries
+ # but only in the org they are course staff for.
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_course_instructor_role_for_enabled_creator_group_setting(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
auth.add_users(self.user, CourseInstructorRole(self.course.id), nostaff_user)
- self.assertEqual(user_can_create_library(nostaff_user), True)
+ self.assertEqual(user_can_create_library(nostaff_user, self.course.org), True)
+ self.assertEqual(user_can_create_library(nostaff_user, 'SomEOtherOrg'), False)
@ddt.data(
(False, False, True),
@@ -131,7 +142,7 @@ def test_library_creator_status_settings(self, disable_course, disable_library,
"DISABLE_LIBRARY_CREATION": disable_library
}
):
- self.assertEqual(user_can_create_library(nostaff_user), expected_status)
+ self.assertEqual(user_can_create_library(nostaff_user, 'SomEOrg'), expected_status)
@mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_COURSE_CREATION': True})
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
@@ -140,7 +151,7 @@ def test_library_creator_status_with_no_course_creator_role_and_disabled_nonstaf
Ensure that `DISABLE_COURSE_CREATION` feature works with libraries as well.
"""
nostaff_client, nostaff_user = self.create_non_staff_authed_user_client()
- self.assertFalse(user_can_create_library(nostaff_user))
+ self.assertFalse(user_can_create_library(nostaff_user, 'SomEOrg'))
# To be explicit, this user can GET, but not POST
get_response = nostaff_client.get_json(LIBRARY_REST_URL)
@@ -251,7 +262,7 @@ def test_lib_create_permission_course_staff_role(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), ns_user)
self.assertTrue(auth.user_has_role(ns_user, CourseStaffRole(self.course.id)))
response = self.client.ajax_post(LIBRARY_REST_URL, {
- 'org': 'org', 'library': 'lib', 'display_name': "New Library",
+ 'org': self.course.org, 'library': 'lib', 'display_name': "New Library",
})
self.assertEqual(response.status_code, 200)
From d23b41e2898cda5a19bd2e26ffda854e75bd7829 Mon Sep 17 00:00:00 2001
From: connorhaugh
Date: Thu, 15 Feb 2024 21:26:45 +0000
Subject: [PATCH 07/18] docs: imporved comment
---
cms/djangoapps/contentstore/views/library.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py
index 8ea6bf1e3657..7ba80bcab9f6 100644
--- a/cms/djangoapps/contentstore/views/library.py
+++ b/cms/djangoapps/contentstore/views/library.py
@@ -98,7 +98,16 @@ def user_can_create_library(user, org):
Helper method for returning the library creation status for a particular user,
taking into account the value LIBRARIES_ENABLED.
- users can only create libraries in orgs they are a part of.
+ if the ENABLE_CREATOR_GROUP value is False, then any user can create a library (in any org),
+ if library creation is enabled.
+
+ if the ENABLE_CREATOR_GROUP value is true, then what a user can do varies by thier role.
+
+ Global Staff: can make libraries in any org.
+ Course Creator Group Members: can make libraries in any org.
+ Organization Staff: Can make libraries in the organization for which they are staff.
+ Course Staff: Can make libraries in the organization which has courses of which they are staff.
+ Course Admin: Can make libraries in the organization which has courses of which they are Admin.
"""
if org is None:
return False
From d757cfa2c7564afc479b2dfdab25b57adc068109 Mon Sep 17 00:00:00 2001
From: connorhaugh
Date: Wed, 4 Oct 2023 12:46:36 -0400
Subject: [PATCH 08/18] fix: cohorts data can be private
---
common/djangoapps/util/file.py | 11 ++++++++++-
lms/djangoapps/instructor/views/api.py | 3 ++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/common/djangoapps/util/file.py b/common/djangoapps/util/file.py
index 66fc2a6f5f35..b884ca46a703 100644
--- a/common/djangoapps/util/file.py
+++ b/common/djangoapps/util/file.py
@@ -13,6 +13,7 @@
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from pytz import UTC
+from storages.backends.s3boto3 import S3Boto3Storage
class FileValidationException(Exception):
@@ -23,7 +24,7 @@ class FileValidationException(Exception):
def store_uploaded_file(
- request, file_key, allowed_file_types, base_storage_filename, max_file_size, validator=None,
+ request, file_key, allowed_file_types, base_storage_filename, max_file_size, validator=None, is_private=False,
):
"""
Stores an uploaded file to django file storage.
@@ -45,6 +46,8 @@ def store_uploaded_file(
a `FileValidationException` if the file is not properly formatted. If any exception is thrown, the stored
file will be deleted before the exception is re-raised. Note that the implementor of the validator function
should take care to close the stored file if they open it for reading.
+ is_private (Boolean): an optional boolean which if True and the storage backend is S3,
+ sets the ACL for the file object to be private.
Returns:
Storage: the file storage object where the file can be retrieved from
@@ -75,6 +78,12 @@ def store_uploaded_file(
file_storage = DefaultStorage()
# If a file already exists with the supplied name, file_storage will make the filename unique.
stored_file_name = file_storage.save(stored_file_name, uploaded_file)
+ if is_private and settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage':
+ S3Boto3Storage().connection.meta.client.put_object_acl(
++ ACL='private',
++ Bucket=settings.AWS_STORAGE_BUCKET_NAME,
++ Key=stored_file_name,
++ )
if validator:
try:
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 6d7dfe17a9d7..02a91e7d84de 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -1603,7 +1603,8 @@ def post(self, request, course_key_string):
request, 'uploaded-file', ['.csv'],
course_and_time_based_filename_generator(course_key, 'cohorts'),
max_file_size=2000000, # limit to 2 MB
- validator=_cohorts_csv_validator
+ validator=_cohorts_csv_validator,
+ is_private=True
)
task_api.submit_cohort_students(request, course_key, file_name)
except (FileValidationException, ValueError) as e:
From 62269f8c956e25f36d83e06299a90018a535b79f Mon Sep 17 00:00:00 2001
From: Feanil Patel
Date: Thu, 25 Jul 2024 10:24:36 -0400
Subject: [PATCH 09/18] fix: Remove errant pluses from a bad merge.
---
common/djangoapps/util/file.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/common/djangoapps/util/file.py b/common/djangoapps/util/file.py
index b884ca46a703..b2892e6f42c9 100644
--- a/common/djangoapps/util/file.py
+++ b/common/djangoapps/util/file.py
@@ -80,10 +80,10 @@ def store_uploaded_file(
stored_file_name = file_storage.save(stored_file_name, uploaded_file)
if is_private and settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage':
S3Boto3Storage().connection.meta.client.put_object_acl(
-+ ACL='private',
-+ Bucket=settings.AWS_STORAGE_BUCKET_NAME,
-+ Key=stored_file_name,
-+ )
+ ACL='private',
+ Bucket=settings.AWS_STORAGE_BUCKET_NAME,
+ Key=stored_file_name,
+ )
if validator:
try:
From 8813e8b02cf30f2e5d619db72c4a2cdd80834eb8 Mon Sep 17 00:00:00 2001
From: Feanil Patel
Date: Thu, 25 Jul 2024 10:49:14 -0400
Subject: [PATCH 10/18] style: Fix a pylint and style violations.
---
cms/djangoapps/contentstore/utils.py | 3 ++-
cms/djangoapps/contentstore/views/library.py | 2 +-
cms/djangoapps/contentstore/views/tests/test_library.py | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index 1b5af4f769bd..21f605a9e440 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -1699,7 +1699,8 @@ def get_home_context(request, no_course=False):
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
'taxonomy_list_mfe_url': get_taxonomy_list_url(),
'libraries': libraries,
- 'show_new_library_button': user_can_view_create_library_button(user) and not should_redirect_to_library_authoring_mfe(),
+ 'show_new_library_button': user_can_view_create_library_button(user)
+ and not should_redirect_to_library_authoring_mfe(),
'user': user,
'request_course_creator_url': reverse('request_course_creator'),
'course_creator_status': _get_course_creator_status(user),
diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py
index 7ba80bcab9f6..870c192653d2 100644
--- a/cms/djangoapps/contentstore/views/library.py
+++ b/cms/djangoapps/contentstore/views/library.py
@@ -68,6 +68,7 @@ def should_redirect_to_library_authoring_mfe():
REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND.is_enabled()
)
+
def user_can_view_create_library_button(user):
"""
Helper method for displaying the visibilty of the create_library_button.
@@ -92,7 +93,6 @@ def user_can_view_create_library_button(user):
return not disable_course_creation
-
def user_can_create_library(user, org):
"""
Helper method for returning the library creation status for a particular user,
diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py
index fdbb9d905c62..fa6505419725 100644
--- a/cms/djangoapps/contentstore/views/tests/test_library.py
+++ b/cms/djangoapps/contentstore/views/tests/test_library.py
@@ -111,7 +111,7 @@ def test_library_creator_status_with_course_staff_role_for_enabled_creator_group
self.assertEqual(user_can_create_library(nostaff_user, 'SomEOtherOrg'), False)
# When creator groups are enabled, course instructor members can create libraries
- # but only in the org they are course staff for.
+ # but only in the org they are course staff for.
@mock.patch("cms.djangoapps.contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_course_instructor_role_for_enabled_creator_group_setting(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
From 1e0d575f5ff686ccc600e1efc1aab9e812a059ee Mon Sep 17 00:00:00 2001
From: Fateme Khodayari <55655542+FatemeKhodayari@users.noreply.github.com>
Date: Tue, 30 Jul 2024 18:16:09 +0330
Subject: [PATCH 11/18] fix: course progress url returned based on
course_home_mfe_progress_tab_is_active (#35149)
---
lms/djangoapps/learner_home/serializers.py | 3 ++-
.../learner_home/test_serializers.py | 26 ++++++++++++++++++-
lms/djangoapps/learner_home/utils.py | 16 ++++++++++++
3 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/lms/djangoapps/learner_home/serializers.py b/lms/djangoapps/learner_home/serializers.py
index 0ce7bf9c6977..b3471715b9dc 100644
--- a/lms/djangoapps/learner_home/serializers.py
+++ b/lms/djangoapps/learner_home/serializers.py
@@ -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):
@@ -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])
diff --git a/lms/djangoapps/learner_home/test_serializers.py b/lms/djangoapps/learner_home/test_serializers.py
index f588af58aee4..ac11a8b2990d 100644
--- a/lms/djangoapps/learner_home/test_serializers.py
+++ b/lms/djangoapps/learner_home/test_serializers.py
@@ -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,
@@ -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):
diff --git a/lms/djangoapps/learner_home/utils.py b/lms/djangoapps/learner_home/utils.py
index 28e4479f9439..96af6a64452b 100644
--- a/lms/djangoapps/learner_home/utils.py
+++ b/lms/djangoapps/learner_home/utils.py
@@ -4,6 +4,7 @@
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
@@ -11,6 +12,8 @@
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()
@@ -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})
From c5d750713e006cf9577caffb81ae89167fdbd0f5 Mon Sep 17 00:00:00 2001
From: Mariagabriela Jaimes
Date: Tue, 6 Aug 2024 18:11:47 -0400
Subject: [PATCH 12/18] chore: upgrade Django to 4.2.15 (#35240)
---
requirements/edx/base.txt | 2 +-
requirements/edx/development.txt | 2 +-
requirements/edx/doc.txt | 2 +-
requirements/edx/testing.txt | 2 +-
scripts/user_retirement/requirements/base.txt | 2 +-
scripts/user_retirement/requirements/testing.txt | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index d68c66cc6ef4..1ef346dd0380 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -177,7 +177,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
-django==4.2.14
+django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index f78a27d03813..6bd8eaaa7671 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -347,7 +347,7 @@ distlib==0.3.8
# via
# -r requirements/edx/testing.txt
# virtualenv
-django==4.2.14
+django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt
index de65c64de287..eda0c2ef66d0 100644
--- a/requirements/edx/doc.txt
+++ b/requirements/edx/doc.txt
@@ -227,7 +227,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
-django==4.2.14
+django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 0baf5e170300..93ea32b23099 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -263,7 +263,7 @@ dill==0.3.8
# via pylint
distlib==0.3.8
# via virtualenv
-django==4.2.14
+django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt
index 1e510e645926..eff125066bc4 100644
--- a/scripts/user_retirement/requirements/base.txt
+++ b/scripts/user_retirement/requirements/base.txt
@@ -39,7 +39,7 @@ click==8.1.6
# edx-django-utils
cryptography==42.0.7
# via pyjwt
-django==4.2.14
+django==4.2.15
# via
# -c scripts/user_retirement/requirements/../../../requirements/common_constraints.txt
# -c scripts/user_retirement/requirements/../../../requirements/constraints.txt
diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt
index 15cc2d9849bf..712e1ba73463 100644
--- a/scripts/user_retirement/requirements/testing.txt
+++ b/scripts/user_retirement/requirements/testing.txt
@@ -56,7 +56,7 @@ cryptography==42.0.7
# pyjwt
ddt==1.7.2
# via -r scripts/user_retirement/requirements/testing.in
-django==4.2.14
+django==4.2.15
# via
# -r scripts/user_retirement/requirements/base.txt
# django-crum
From d5c84e9d21b407717e6b6b47551e36f4b0605cc8 Mon Sep 17 00:00:00 2001
From: Muhammad Anas <88967643+Anas12091101@users.noreply.github.com>
Date: Thu, 8 Aug 2024 11:12:19 +0500
Subject: [PATCH 13/18] backport fix: disable submit button for archived
courses (#34920) to redwood (#35248)
* fix: disable submit button for archived courses (#34920)
---
xmodule/capa_block.py | 15 ++++++++++++++-
xmodule/tests/test_capa_block.py | 33 +++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py
index e7d917fee601..5779ed367fe8 100644
--- a/xmodule/capa_block.py
+++ b/xmodule/capa_block.py
@@ -795,12 +795,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
diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py
index 42494547822e..5da29ac3d20e 100644
--- a/xmodule/tests/test_capa_block.py
+++ b/xmodule/tests/test_capa_block.py
@@ -11,7 +11,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
@@ -656,6 +656,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
From fa97f13196bf920286c1122f71c726c31427099d Mon Sep 17 00:00:00 2001
From: Tim McCormack
Date: Wed, 31 Jul 2024 17:36:08 +0000
Subject: [PATCH 14/18] fix: Prevent error page recursion (#35209)
We sometimes see rendering errors in the error page itself, which then
cause another attempt at rendering the error page. I'm not sure _exactly_
how the loop is occurring, but it looks something like this:
1. An error is raised in a view or middleware and is not caught by
application code
2. Django catches the error and calls the registered uncaught error
handler
3. Our handler tries to render an error page
4. The rendering code raises an error
5. GOTO 2 (until some sort of server limit is reached)
By catching all errors raised during error-page render and substituting in
a hardcoded string, we can reduce server resources, avoid logging massive
sequences of recursive stack traces, and still give the user *some*
indication that yes, there was a problem.
This should help address https://github.com/openedx/edx-platform/issues/35151
At least one of these rendering errors is known to be due to a translation
error. There's a separate issue for restoring translation quality so that
we avoid those issues in the future (https://github.com/openedx/openedx-translations/issues/549)
but in general we should catch all rendering errors, including unknown
ones.
Testing:
- In `lms/envs/devstack.py` change `DEBUG` to `False` to ensure that the
usual error page is displayed (rather than the debug error page).
- Add line `1/0` to the top of the `student_dashboard` function in
`common/djangoapps/student/views/dashboard.py` to make that view error.
- In `lms/templates/static_templates/server-error.html` replace
`static.get_platform_name()` with `None * 7` to make the error template
itself produce an error.
- Visit .
Without the fix, the response takes 10 seconds and produces a 6 MB, 85k
line set of stack traces and the page displays "A server error occurred.
Please contact the administrator."
With the fix, the response takes less than a second and produces three
stack traces (one of which contains the error page's rendering error).
---
lms/djangoapps/static_template_view/views.py | 23 ++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/lms/djangoapps/static_template_view/views.py b/lms/djangoapps/static_template_view/views.py
index 01b99d51c861..a788f77a95fd 100644
--- a/lms/djangoapps/static_template_view/views.py
+++ b/lms/djangoapps/static_template_view/views.py
@@ -5,7 +5,7 @@
# List of valid templates is explicitly managed for (short-term)
# security reasons.
-
+import logging
import mimetypes
from django.conf import settings
@@ -23,6 +23,8 @@
from common.djangoapps.util.views import fix_crum_request
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
+log = logging.getLogger(__name__)
+
valid_templates = []
if settings.STATIC_GRAB:
@@ -122,4 +124,21 @@ def render_429(request, exception=None): # lint-amnesty, pylint: disable=unused
@fix_crum_request
def render_500(request):
- return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}, request=request))
+ """
+ Render the generic error page when we have an uncaught error.
+ """
+ try:
+ return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}, request=request))
+ except BaseException as e:
+ # If we can't render the error page, ensure we don't raise another
+ # exception -- because if we do, we'll probably just end up back
+ # at the same rendering error.
+ #
+ # This is an attempt at working around the recursive error handling issues
+ # observed in , which
+ # were triggered by Mako and translation errors.
+
+ log.error("Encountered error while rendering error page.", exc_info=True)
+ # This message is intentionally hardcoded and does not involve
+ # any translation, templating, etc. Do not translate.
+ return HttpResponseServerError("Encountered error while rendering error page.")
From 5ea3b9851238ef886e215e00eb0fd6394fbcd6ed Mon Sep 17 00:00:00 2001
From: Ihor Romaniuk
Date: Mon, 9 Sep 2024 21:30:58 +0200
Subject: [PATCH 15/18] feat: course about page markup and styles improvements
(#34892)
* feat: course about page markup and styles improvements
* test: update tests according to changes
* fix: relocate course organization and return removed prerequisites info
* fix: display org info above the course title
---------
Co-authored-by: oleksandr.buhaienko
Co-authored-by: Eugene Dyudyunov
---
lms/djangoapps/courseware/tests/test_about.py | 10 ++++++--
.../sass/multicourse/_course_about.scss | 24 +++++++++++++++++++
lms/templates/courseware/course_about.html | 19 ++++++++++-----
3 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py
index d53d620d3e34..bd0c1854ab76 100644
--- a/lms/djangoapps/courseware/tests/test_about.py
+++ b/lms/djangoapps/courseware/tests/test_about.py
@@ -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 '{}'.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 '
+ f'{pre_requisite_courses[0]["display"]} 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):
@@ -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 '{}'.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 '
+ f'{pre_requisite_courses[0]["display"]} 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)
diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss
index 629b3065778e..a8a34ccec589 100644
--- a/lms/static/sass/multicourse/_course_about.scss
+++ b/lms/static/sass/multicourse/_course_about.scss
@@ -44,6 +44,11 @@
> div.table {
display: table;
width: 100%;
+
+ @include media-breakpoint-down(sm) {
+ display: flex;
+ flex-direction: column;
+ }
}
.intro {
@@ -51,6 +56,11 @@
@include clearfix();
+ @include media-breakpoint-down(sm) {
+ width: auto;
+ order: 2;
+ }
+
display: table-cell;
vertical-align: middle;
padding: $baseline;
@@ -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;
@@ -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);
}
@@ -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%;
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index 91d7a2a28e57..eec9caeadbec 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -62,11 +62,10 @@