Merge remote-tracking branch 'upstream/master' into salman/remove-bok-choy
This commit is contained in:
@@ -7,13 +7,6 @@ name: Consistent Python dependencies
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'requirements/**'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'requirements/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -25,16 +18,37 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
# Only run remaining steps if there are changes to requirements/**
|
||||
- name: "Decide whether to short-circuit"
|
||||
env:
|
||||
GH_TOKEN: "${{ github.token }}"
|
||||
PR_URL: "${{ github.event.pull_request.html_url }}"
|
||||
run: |
|
||||
paths=$(gh pr diff "$PR_URL" --name-only)
|
||||
echo $'Paths touched in PR:\n'"$paths"
|
||||
|
||||
# The ^"? is because git may quote weird file paths
|
||||
matched="$(echo "$paths" | grep -P '^"?requirements/' || true)"
|
||||
echo $'Relevant paths:\n'"$matched"
|
||||
if [[ -n "$matched" ]]; then
|
||||
echo "RELEVANT=true" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
if: ${{ env.RELEVANT == 'true' }}
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
if: ${{ env.RELEVANT == 'true' }}
|
||||
with:
|
||||
python-version: '3.8'
|
||||
|
||||
- run: |
|
||||
- name: "Recompile requirements"
|
||||
if: ${{ env.RELEVANT == 'true' }}
|
||||
run: |
|
||||
make compile-requirements
|
||||
|
||||
- name: Fail if compiling requirements caused changes
|
||||
if: ${{ env.RELEVANT == 'true' }}
|
||||
run: |
|
||||
SUMMARY_HELP=$(cat <<'EOMARKDOWN'
|
||||
# Inconsistent Python dependencies
|
||||
|
||||
@@ -32,7 +32,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disa
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from .exceptions import AssetNotFoundException, AssetSizeTooLargeException
|
||||
from .utils import reverse_course_url, get_files_uploads_url
|
||||
from .utils import reverse_course_url, get_files_uploads_url, get_response_format, request_response_format_is_json
|
||||
from .toggles import use_new_files_uploads_page
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ def handle_assets(request, course_key_string=None, asset_key_string=None):
|
||||
if not has_course_author_access(request.user, course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
response_format = _get_response_format(request)
|
||||
if _request_response_format_is_json(request, response_format):
|
||||
response_format = get_response_format(request)
|
||||
if request_response_format_is_json(request, response_format):
|
||||
if request.method == 'GET':
|
||||
return _assets_json(request, course_key)
|
||||
|
||||
@@ -133,14 +133,6 @@ def get_asset_usage_path(request, course_key, asset_key_string):
|
||||
return JsonResponse({'usage_locations': usage_locations})
|
||||
|
||||
|
||||
def _get_response_format(request):
|
||||
return request.GET.get('format') or request.POST.get('format') or 'html'
|
||||
|
||||
|
||||
def _request_response_format_is_json(request, response_format):
|
||||
return response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')
|
||||
|
||||
|
||||
def _asset_index(request, course_key):
|
||||
'''
|
||||
Display an editable asset library.
|
||||
|
||||
@@ -5,7 +5,7 @@ Unit tests for video utils.
|
||||
|
||||
from datetime import datetime
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
import pytz
|
||||
@@ -144,7 +144,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
|
||||
return mocked_response
|
||||
|
||||
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
|
||||
@patch('requests.get')
|
||||
@mock.patch('requests.get')
|
||||
@ddt.data(
|
||||
(
|
||||
{
|
||||
@@ -228,7 +228,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
|
||||
self.assertEqual(thumbnail_content_type, 'image/jpeg')
|
||||
|
||||
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
|
||||
@patch('requests.get')
|
||||
@mock.patch('requests.get')
|
||||
def test_scrape_youtube_thumbnail(self, mocked_request):
|
||||
"""
|
||||
Test that youtube thumbnails are correctly scrapped.
|
||||
@@ -273,8 +273,8 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
|
||||
)
|
||||
)
|
||||
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
|
||||
@patch('cms.djangoapps.contentstore.video_utils.LOGGER')
|
||||
@patch('requests.get')
|
||||
@mock.patch('cms.djangoapps.contentstore.video_utils.LOGGER')
|
||||
@mock.patch('requests.get')
|
||||
@ddt.unpack
|
||||
def test_scrape_youtube_thumbnail_logging(
|
||||
self,
|
||||
@@ -333,8 +333,8 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
|
||||
)
|
||||
),
|
||||
)
|
||||
@patch('cms.djangoapps.contentstore.video_utils.LOGGER')
|
||||
@patch('cms.djangoapps.contentstore.video_utils.download_youtube_video_thumbnail')
|
||||
@mock.patch('cms.djangoapps.contentstore.video_utils.LOGGER')
|
||||
@mock.patch('cms.djangoapps.contentstore.video_utils.download_youtube_video_thumbnail')
|
||||
@ddt.unpack
|
||||
def test_no_video_thumbnail_downloaded(
|
||||
self,
|
||||
@@ -376,7 +376,7 @@ class S3Boto3TestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.storage = S3Boto3Storage()
|
||||
self.storage._connections.connection = MagicMock() # pylint: disable=protected-access
|
||||
self.storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access
|
||||
|
||||
def order_dict(self, dictionary):
|
||||
"""
|
||||
@@ -417,18 +417,18 @@ class S3Boto3TestCase(TestCase):
|
||||
content = ContentFile('new content')
|
||||
|
||||
storage = S3Boto3Storage(**{'bucket_name': 'test'})
|
||||
storage._connections.connection = MagicMock() # pylint: disable=protected-access
|
||||
storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access
|
||||
|
||||
storage.save(name, content)
|
||||
storage.bucket.Object.assert_called_once_with(name)
|
||||
|
||||
obj = storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content,
|
||||
mock.ANY,
|
||||
ExtraArgs=self.order_dict({
|
||||
'ContentType': 'text/plain',
|
||||
}),
|
||||
Config=storage._transfer_config # pylint: disable=protected-access
|
||||
Config=storage.transfer_config # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
@override_settings(AWS_DEFAULT_ACL='public-read')
|
||||
@@ -445,7 +445,7 @@ class S3Boto3TestCase(TestCase):
|
||||
name = 'test_storage_save.txt'
|
||||
content = ContentFile('new content')
|
||||
storage = S3Boto3Storage(**{'bucket_name': 'test', 'default_acl': default_acl})
|
||||
storage._connections.connection = MagicMock() # pylint: disable=protected-access
|
||||
storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access
|
||||
|
||||
storage.save(name, content)
|
||||
storage.bucket.Object.assert_called_once_with(name)
|
||||
@@ -461,9 +461,9 @@ class S3Boto3TestCase(TestCase):
|
||||
del ExtraArgs['ACL']
|
||||
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content,
|
||||
mock.ANY,
|
||||
ExtraArgs=self.order_dict(ExtraArgs),
|
||||
Config=storage._transfer_config # pylint: disable=protected-access
|
||||
Config=storage.transfer_config # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
@ddt.data('public-read', 'private')
|
||||
@@ -476,16 +476,16 @@ class S3Boto3TestCase(TestCase):
|
||||
content = ContentFile('new content')
|
||||
|
||||
storage = S3Boto3Storage(**{'bucket_name': 'test', 'default_acl': None})
|
||||
storage._connections.connection = MagicMock() # pylint: disable=protected-access
|
||||
storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access
|
||||
|
||||
storage.save(name, content)
|
||||
storage.bucket.Object.assert_called_once_with(name)
|
||||
|
||||
obj = storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content,
|
||||
ExtraArgs=self.order_dict({
|
||||
mock.ANY,
|
||||
Config=storage.transfer_config, # pylint: disable=protected-access
|
||||
ExtraArgs={
|
||||
'ContentType': 'text/plain',
|
||||
}),
|
||||
Config=storage._transfer_config # pylint: disable=protected-access
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1390,9 +1390,63 @@ def get_help_urls():
|
||||
return help_tokens
|
||||
|
||||
|
||||
def get_response_format(request):
|
||||
return request.GET.get('format') or request.POST.get('format') or 'html'
|
||||
|
||||
|
||||
def request_response_format_is_json(request, response_format):
|
||||
return response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')
|
||||
|
||||
|
||||
def get_library_context(request, request_is_json=False):
|
||||
"""
|
||||
Utils is used to get context of course home library tab.
|
||||
It is used for both DRF and django views.
|
||||
"""
|
||||
from cms.djangoapps.contentstore.views.course import (
|
||||
get_allowed_organizations,
|
||||
get_allowed_organizations_for_libraries,
|
||||
user_can_create_organizations,
|
||||
_accessible_libraries_iter,
|
||||
_get_course_creator_status,
|
||||
_format_library_for_view,
|
||||
)
|
||||
from cms.djangoapps.contentstore.views.library import (
|
||||
LIBRARIES_ENABLED,
|
||||
)
|
||||
|
||||
libraries = _accessible_libraries_iter(request.user) if LIBRARIES_ENABLED else []
|
||||
data = {
|
||||
'libraries': [_format_library_for_view(lib, request) for lib in libraries],
|
||||
}
|
||||
|
||||
if not request_is_json:
|
||||
return {
|
||||
**data,
|
||||
'in_process_course_actions': [],
|
||||
'courses': [],
|
||||
'libraries_enabled': LIBRARIES_ENABLED,
|
||||
'show_new_library_button': LIBRARIES_ENABLED and request.user.is_active,
|
||||
'user': request.user,
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'archived_courses': True,
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'rerun_creator_status': GlobalStaff().has_user(request.user),
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'active_tab': 'libraries',
|
||||
'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(request.user),
|
||||
'allowed_organizations': get_allowed_organizations(request.user),
|
||||
'can_create_organizations': user_can_create_organizations(request.user),
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_home_context(request):
|
||||
"""
|
||||
Utils is used to get context of course grading.
|
||||
Utils is used to get context of course home.
|
||||
It is used for both DRF and django views.
|
||||
"""
|
||||
|
||||
@@ -1420,8 +1474,14 @@ def get_home_context(request):
|
||||
courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org)
|
||||
user = request.user
|
||||
libraries = []
|
||||
response_format = get_response_format(request)
|
||||
|
||||
if not split_library_view_on_dashboard() and LIBRARIES_ENABLED:
|
||||
libraries = _accessible_libraries_iter(request.user)
|
||||
accessible_libraries = _accessible_libraries_iter(user)
|
||||
libraries = [_format_library_for_view(lib, request) for lib in accessible_libraries]
|
||||
|
||||
if split_library_view_on_dashboard() and request_response_format_is_json(request, response_format):
|
||||
libraries = get_library_context(request, True)['libraries']
|
||||
|
||||
def format_in_process_course_view(uca):
|
||||
"""
|
||||
@@ -1456,7 +1516,7 @@ def get_home_context(request):
|
||||
'libraries_enabled': LIBRARIES_ENABLED,
|
||||
'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(),
|
||||
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
'libraries': [_format_library_for_view(lib, request) for lib in libraries],
|
||||
'libraries': libraries,
|
||||
'show_new_library_button': user_can_create_library(user) and not should_redirect_to_library_authoring_mfe(),
|
||||
'user': user,
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
|
||||
@@ -89,7 +89,6 @@ from ..courseware_index import CoursewareSearchIndexer, SearchIndexingError
|
||||
from ..tasks import rerun_course as rerun_course_task
|
||||
from ..toggles import (
|
||||
default_enable_flexible_peer_openassessments,
|
||||
split_library_view_on_dashboard,
|
||||
use_new_course_outline_page,
|
||||
use_new_home_page,
|
||||
use_new_updates_page,
|
||||
@@ -102,6 +101,7 @@ from ..utils import (
|
||||
get_course_settings,
|
||||
get_course_grading,
|
||||
get_home_context,
|
||||
get_library_context,
|
||||
get_lms_link_for_item,
|
||||
get_proctored_exam_settings_url,
|
||||
get_course_outline_url,
|
||||
@@ -121,7 +121,6 @@ from ..utils import (
|
||||
update_course_discussions_settings,
|
||||
)
|
||||
from .component import ADVANCED_COMPONENT_TYPES
|
||||
from .library import LIBRARIES_ENABLED
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
@@ -551,26 +550,7 @@ def library_listing(request):
|
||||
"""
|
||||
List all Libraries available to the logged in user
|
||||
"""
|
||||
libraries = _accessible_libraries_iter(request.user) if LIBRARIES_ENABLED else []
|
||||
data = {
|
||||
'in_process_course_actions': [],
|
||||
'courses': [],
|
||||
'libraries_enabled': LIBRARIES_ENABLED,
|
||||
'libraries': [_format_library_for_view(lib, request) for lib in libraries],
|
||||
'show_new_library_button': LIBRARIES_ENABLED and request.user.is_active,
|
||||
'user': request.user,
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'archived_courses': True,
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'rerun_creator_status': GlobalStaff().has_user(request.user),
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'active_tab': 'libraries',
|
||||
'allowed_organizations': get_allowed_organizations(request.user),
|
||||
'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(request.user),
|
||||
'can_create_organizations': user_can_create_organizations(request.user),
|
||||
}
|
||||
data = get_library_context(request)
|
||||
return render_to_response('index.html', data)
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +313,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
'selected_groups_label': selected_groups_label,
|
||||
'can_add': context.get('can_add', True),
|
||||
'can_move': context.get('can_move', is_course),
|
||||
'language': getattr(course, 'language', None)
|
||||
'language': getattr(course, 'language', None),
|
||||
'is_course': is_course
|
||||
}
|
||||
|
||||
add_webpack_js_to_fragment(frag, "js/factories/xblock_validation")
|
||||
|
||||
@@ -48,6 +48,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
|
||||
BasePage.prototype.initialize.call(this, options);
|
||||
this.viewClass = options.viewClass || this.defaultViewClass;
|
||||
this.isLibraryPage = (this.model.attributes.category === 'library');
|
||||
this.isLibraryContentPage = (this.model.attributes.category === 'library_content');
|
||||
this.nameEditor = new XBlockStringFieldEditor({
|
||||
el: this.$('.wrapper-xblock-field'),
|
||||
model: this.model
|
||||
@@ -154,7 +155,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
|
||||
self.delegateEvents();
|
||||
|
||||
// Show/hide the paste button
|
||||
if (!self.isLibraryPage) {
|
||||
if (!self.isLibraryPage && !self.isLibraryContentPage) {
|
||||
self.initializePasteButton();
|
||||
}
|
||||
},
|
||||
@@ -208,32 +209,36 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
|
||||
* Given the latest information about the user's clipboard, hide or show the Paste button as appropriate.
|
||||
*/
|
||||
refreshPasteButton(data) {
|
||||
// 'data' is the same data returned by the "get clipboard status" API endpoint
|
||||
// i.e. /api/content-staging/v1/clipboard/
|
||||
if (this.options.canEdit && data.content) {
|
||||
if (["vertical", "sequential", "chapter", "course"].includes(data.content.block_type)) {
|
||||
// This is not suitable for pasting into a unit.
|
||||
this.$(".paste-component").hide();
|
||||
} else if (data.content.status === "expired") {
|
||||
// This has expired and can no longer be pasted.
|
||||
this.$(".paste-component").hide();
|
||||
} else {
|
||||
// The thing in the clipboard can be pasted into this unit:
|
||||
const detailsPopupEl = this.$(".clipboard-details-popup")[0];
|
||||
detailsPopupEl.querySelector(".detail-block-name").innerText = data.content.display_name;
|
||||
detailsPopupEl.querySelector(".detail-block-type").innerText = data.content.block_type_display;
|
||||
detailsPopupEl.querySelector(".detail-course-name").innerText = data.source_context_title;
|
||||
if (data.source_edit_url) {
|
||||
detailsPopupEl.setAttribute("href", data.source_edit_url);
|
||||
detailsPopupEl.classList.remove("no-edit-link");
|
||||
// Do not perform any changes on paste button since they are not
|
||||
// rendered on Library or LibraryContent pages
|
||||
if (!this.isLibraryPage && !this.isLibraryContentPage) {
|
||||
// 'data' is the same data returned by the "get clipboard status" API endpoint
|
||||
// i.e. /api/content-staging/v1/clipboard/
|
||||
if (this.options.canEdit && data.content) {
|
||||
if (["vertical", "sequential", "chapter", "course"].includes(data.content.block_type)) {
|
||||
// This is not suitable for pasting into a unit.
|
||||
this.$(".paste-component").hide();
|
||||
} else if (data.content.status === "expired") {
|
||||
// This has expired and can no longer be pasted.
|
||||
this.$(".paste-component").hide();
|
||||
} else {
|
||||
detailsPopupEl.setAttribute("href", "#");
|
||||
detailsPopupEl.classList.add("no-edit-link");
|
||||
// The thing in the clipboard can be pasted into this unit:
|
||||
const detailsPopupEl = this.$(".clipboard-details-popup")[0];
|
||||
detailsPopupEl.querySelector(".detail-block-name").innerText = data.content.display_name;
|
||||
detailsPopupEl.querySelector(".detail-block-type").innerText = data.content.block_type_display;
|
||||
detailsPopupEl.querySelector(".detail-course-name").innerText = data.source_context_title;
|
||||
if (data.source_edit_url) {
|
||||
detailsPopupEl.setAttribute("href", data.source_edit_url);
|
||||
detailsPopupEl.classList.remove("no-edit-link");
|
||||
} else {
|
||||
detailsPopupEl.setAttribute("href", "#");
|
||||
detailsPopupEl.classList.add("no-edit-link");
|
||||
}
|
||||
this.$(".paste-component").show();
|
||||
}
|
||||
this.$(".paste-component").show();
|
||||
} else {
|
||||
this.$(".paste-component").hide();
|
||||
}
|
||||
} else {
|
||||
this.$(".paste-component").hide();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -104,9 +104,15 @@ block_is_unit = is_unit(xblock)
|
||||
<div class="nav-sub">
|
||||
<ul>
|
||||
% if not show_inline:
|
||||
<li class="nav-item">
|
||||
<a class="copy-button" href="#" role="button">${_("Copy to Clipboard")}</a>
|
||||
</li>
|
||||
% if is_course:
|
||||
<!--
|
||||
Only show the "Copy to Clipboard" button for xblocks inside courses since
|
||||
the copy/paste functionality is not yet implemented for LibraryContent.
|
||||
-->
|
||||
<li class="nav-item">
|
||||
<a class="copy-button" href="#" role="button">${_("Copy to Clipboard")}</a>
|
||||
</li>
|
||||
% endif
|
||||
% if can_add:
|
||||
<li class="nav-item">
|
||||
<a class="duplicate-button" href="#" role="button">${_("Duplicate")}</a>
|
||||
|
||||
@@ -28,3 +28,16 @@ AUTO_CERTIFICATE_GENERATION = WaffleSwitch(f"{WAFFLE_NAMESPACE}.auto_certificate
|
||||
# .. toggle_target_removal_date: 2023-07-31
|
||||
# .. toggle_tickets: TODO
|
||||
SEND_CERTIFICATE_CREATED_SIGNAL = SettingToggle('SEND_CERTIFICATE_CREATED_SIGNAL', default=False, module_name=__name__)
|
||||
|
||||
|
||||
# .. toggle_name: SEND_CERTIFICATE_REVOKED_SIGNAL
|
||||
# .. toggle_implementation: SettingToggle
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: When True, the system will publish `CERTIFICATE_REVOKED` signals to the event bus. The
|
||||
# `CERTIFICATE_REVOKED` signal is emit when a certificate has been revoked from a learner and the revocation process
|
||||
# has completed.
|
||||
# .. toggle_use_cases: temporary
|
||||
# .. toggle_creation_date: 2023-09-15
|
||||
# .. toggle_target_removal_date: 2024-01-01
|
||||
# .. toggle_tickets: TODO
|
||||
SEND_CERTIFICATE_REVOKED_SIGNAL = SettingToggle('SEND_CERTIFICATE_REVOKED_SIGNAL', default=False, module_name=__name__)
|
||||
|
||||
@@ -378,6 +378,10 @@ class GeneratedCertificate(models.Model):
|
||||
|
||||
if not grade:
|
||||
grade = ''
|
||||
# the grade can come through revocation as a float, so we must convert it to a string to be compatible with the
|
||||
# `CERTIFICATE_REVOKED` event definition
|
||||
elif isinstance(grade, float):
|
||||
grade = str(grade)
|
||||
|
||||
if not mode:
|
||||
mode = self.mode
|
||||
|
||||
@@ -11,7 +11,7 @@ from openedx_events.event_bus import get_producer
|
||||
from common.djangoapps.course_modes import api as modes_api
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.signals import ENROLLMENT_TRACK_UPDATED
|
||||
from lms.djangoapps.certificates.config import SEND_CERTIFICATE_CREATED_SIGNAL
|
||||
from lms.djangoapps.certificates.config import SEND_CERTIFICATE_CREATED_SIGNAL, SEND_CERTIFICATE_REVOKED_SIGNAL
|
||||
from lms.djangoapps.certificates.generation_handler import (
|
||||
CertificateGenerationNotAllowed,
|
||||
generate_allowlist_certificate_task,
|
||||
@@ -32,7 +32,7 @@ from openedx.core.djangoapps.signals.signals import (
|
||||
COURSE_GRADE_NOW_PASSED,
|
||||
LEARNER_NOW_VERIFIED
|
||||
)
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -162,7 +162,7 @@ def _listen_for_enrollment_mode_change(sender, user, course_key, mode, **kwargs)
|
||||
|
||||
|
||||
@receiver(CERTIFICATE_CREATED)
|
||||
def listen_for_certificate_created_event(sender, signal, **kwargs):
|
||||
def listen_for_certificate_created_event(sender, signal, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Publish `CERTIFICATE_CREATED` events to the event bus.
|
||||
"""
|
||||
@@ -174,3 +174,18 @@ def listen_for_certificate_created_event(sender, signal, **kwargs):
|
||||
event_data={'certificate': kwargs['certificate']},
|
||||
event_metadata=kwargs['metadata']
|
||||
)
|
||||
|
||||
|
||||
@receiver(CERTIFICATE_REVOKED)
|
||||
def listen_for_certificate_revoked_event(sender, signal, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Publish `CERTIFICATE_REVOKED` events to the event bus.
|
||||
"""
|
||||
if SEND_CERTIFICATE_REVOKED_SIGNAL.is_enabled():
|
||||
get_producer().send(
|
||||
signal=CERTIFICATE_REVOKED,
|
||||
topic='learning-certificate-lifecycle',
|
||||
event_key_field='certificate.course.course_key',
|
||||
event_data={'certificate': kwargs['certificate']},
|
||||
event_metadata=kwargs['metadata']
|
||||
)
|
||||
|
||||
@@ -21,13 +21,16 @@ from lms.djangoapps.certificates.models import (
|
||||
CertificateGenerationConfiguration,
|
||||
GeneratedCertificate
|
||||
)
|
||||
from lms.djangoapps.certificates.signals import listen_for_certificate_created_event
|
||||
from lms.djangoapps.certificates.signals import (
|
||||
listen_for_certificate_created_event,
|
||||
listen_for_certificate_revoked_event
|
||||
)
|
||||
from lms.djangoapps.certificates.tests.factories import CertificateAllowlistFactory, GeneratedCertificateFactory
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.grades.tests.utils import mock_passing_grade
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from openedx_events.data import EventsMetadata
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED
|
||||
from openedx_events.learning.data import CourseData, UserData, UserPersonalData, CertificateData
|
||||
|
||||
|
||||
@@ -458,22 +461,9 @@ class CertificateEventBusTests(ModuleStoreTestCase):
|
||||
mode='verified',
|
||||
)
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=False)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_event_disabled(self, mock_producer):
|
||||
def _create_event_data(self, event_type, certificate_status):
|
||||
"""
|
||||
Test to verify that we do not push `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled.
|
||||
"""
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED)
|
||||
mock_producer.assert_not_called()
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=True)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_event_enabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we push `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is enabled.
|
||||
Utility function to create test data for unit tests.
|
||||
"""
|
||||
expected_course_data = CourseData(course_key=self.course.id)
|
||||
expected_user_data = UserData(
|
||||
@@ -490,12 +480,12 @@ class CertificateEventBusTests(ModuleStoreTestCase):
|
||||
course=expected_course_data,
|
||||
mode='verified',
|
||||
grade='',
|
||||
current_status='downloadable',
|
||||
current_status=certificate_status,
|
||||
download_url='',
|
||||
name='',
|
||||
)
|
||||
event_metadata = EventsMetadata(
|
||||
event_type=CERTIFICATE_CREATED.event_type,
|
||||
expected_event_metadata = EventsMetadata(
|
||||
event_type=event_type.event_type,
|
||||
id=uuid4(),
|
||||
minorversion=0,
|
||||
source='openedx/lms/web',
|
||||
@@ -503,15 +493,59 @@ class CertificateEventBusTests(ModuleStoreTestCase):
|
||||
time=datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
event_kwargs = {
|
||||
return {
|
||||
'certificate': expected_certificate_data,
|
||||
'metadata': event_metadata
|
||||
'metadata': expected_event_metadata,
|
||||
}
|
||||
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED, **event_kwargs)
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=False)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_created_event_disabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we do not publish `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled.
|
||||
"""
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED)
|
||||
mock_producer.assert_not_called()
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=False)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_revoked_event_disabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we do not publish `CERTIFICATE_REVOKED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_REVOKED_SIGNAL` setting is disabled.
|
||||
"""
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_REVOKED)
|
||||
mock_producer.assert_not_called()
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=True)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_created_event_enabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we push `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is enabled.
|
||||
"""
|
||||
event_data = self._create_event_data(CERTIFICATE_CREATED, CertificateStatuses.downloadable)
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED, **event_data)
|
||||
# verify that the data sent to the event bus matches what we expect
|
||||
data = mock_producer.return_value.send.call_args.kwargs
|
||||
assert data['signal'].event_type == CERTIFICATE_CREATED.event_type
|
||||
assert data['event_data']['certificate'] == expected_certificate_data
|
||||
assert data['event_data']['certificate'] == event_data['certificate']
|
||||
assert data['topic'] == 'learning-certificate-lifecycle'
|
||||
assert data['event_key_field'] == 'certificate.course.course_key'
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=True)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_revoked_event_enabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we push `CERTIFICATE_REVOKED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_REVOKED_SIGNAL` setting is enabled.
|
||||
"""
|
||||
event_data = self._create_event_data(CERTIFICATE_REVOKED, CertificateStatuses.notpassing)
|
||||
listen_for_certificate_revoked_event(None, CERTIFICATE_REVOKED, **event_data)
|
||||
# verify that the data sent to the event bus matches what we expect
|
||||
data = mock_producer.return_value.send.call_args.kwargs
|
||||
assert data['signal'].event_type == CERTIFICATE_REVOKED.event_type
|
||||
assert data['event_data']['certificate'] == event_data['certificate']
|
||||
assert data['topic'] == 'learning-certificate-lifecycle'
|
||||
assert data['event_key_field'] == 'certificate.course.course_key'
|
||||
|
||||
@@ -281,6 +281,13 @@
|
||||
@include text-align(center);
|
||||
}
|
||||
|
||||
@media print{
|
||||
.list-signatories{
|
||||
justify-content: center;
|
||||
display: inline-flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
.signatory {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
@@ -735,8 +735,8 @@ class TestObjectTagViewSet(TestTaxonomyObjectsMixin, APITestCase):
|
||||
|
||||
assert response.status_code == expected_status
|
||||
if status.is_success(expected_status):
|
||||
assert len(response.data.get("results")) == len(tag_values)
|
||||
assert set(t["value"] for t in response.data["results"]) == set(tag_values)
|
||||
assert len(response.data) == len(tag_values)
|
||||
assert set(t["value"] for t in response.data) == set(tag_values)
|
||||
|
||||
@ddt.data(
|
||||
# Can't add invalid tags to a object using a closed taxonomy
|
||||
@@ -803,8 +803,8 @@ class TestObjectTagViewSet(TestTaxonomyObjectsMixin, APITestCase):
|
||||
|
||||
assert response.status_code == expected_status
|
||||
if status.is_success(expected_status):
|
||||
assert len(response.data.get("results")) == len(tag_values)
|
||||
assert set(t["value"] for t in response.data["results"]) == set(tag_values)
|
||||
assert len(response.data) == len(tag_values)
|
||||
assert set(t["value"] for t in response.data) == set(tag_values)
|
||||
|
||||
@ddt.data(
|
||||
# Can't add invalid tags to a object using a closed taxonomy
|
||||
|
||||
@@ -11,8 +11,6 @@ from openedx_events.learning.signals import (
|
||||
COURSE_UNENROLLMENT_COMPLETED,
|
||||
USER_NOTIFICATION_REQUESTED
|
||||
)
|
||||
from django.db.models.signals import post_save
|
||||
import traceback
|
||||
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
|
||||
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
|
||||
@@ -61,16 +59,3 @@ def generate_user_notifications(signal, sender, notification_data, metadata, **k
|
||||
notification_data = notification_data.__dict__
|
||||
notification_data['course_key'] = str(notification_data['course_key'])
|
||||
send_notifications.delay(**notification_data)
|
||||
|
||||
|
||||
@receiver(post_save, sender=CourseNotificationPreference)
|
||||
def notification_post_save(signal, sender, instance, created, **kwargs):
|
||||
"""
|
||||
Watches for post_save signal for update on the CourseNotificationPreference table.
|
||||
Generate a log with traceback if CourseNotificationPreference is updated
|
||||
"""
|
||||
if not created:
|
||||
# Get the stack trace
|
||||
stack_trace = traceback.format_stack()
|
||||
# Log the update along with the stack trace
|
||||
log.info(f"{sender.__name__} (ID: {instance.pk}) was updated. Update induced by:\n{stack_trace}")
|
||||
|
||||
@@ -74,6 +74,7 @@ from common.djangoapps.student.tests.factories import (
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory, AccessTokenFactory
|
||||
|
||||
from ...tests.factories import UserOrgTagFactory
|
||||
from ..views import USER_PROFILE_PII, AccountRetirementView
|
||||
@@ -263,6 +264,22 @@ class TestDeactivateLogout(RetirementTestCase):
|
||||
response = self.client.post(self.url, self.build_post(self.test_password), **headers)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_bearer_auth(self):
|
||||
"""
|
||||
Test the account deactivation/logout endpoint using Bearer auth
|
||||
"""
|
||||
# testing with broken token
|
||||
headers = {'HTTP_AUTHORIZATION': 'Bearer broken_token'}
|
||||
response = self.client.post(self.url, self.build_post(self.test_password), **headers)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
# testing with correct token
|
||||
access_token = AccessTokenFactory(user=self.test_user,
|
||||
application=ApplicationFactory(name="test_bearer",
|
||||
user=self.test_user)).token
|
||||
headers = {'HTTP_AUTHORIZATION': f'Bearer {access_token}'}
|
||||
response = self.client.post(self.url, self.build_post(self.test_password), **headers)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class TestPartnerReportingCleanup(ModuleStoreTestCase):
|
||||
|
||||
@@ -21,6 +21,7 @@ from django.utils.translation import gettext as _
|
||||
from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from openedx.core.lib.api.authentication import BearerAuthentication
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomerUser, PendingEnterpriseCustomerUser
|
||||
from integrated_channels.degreed.models import DegreedLearnerDataTransmissionAudit
|
||||
@@ -567,7 +568,10 @@ class DeactivateLogoutView(APIView):
|
||||
- Log the user out
|
||||
- Create a row in the retirement table for that user
|
||||
"""
|
||||
authentication_classes = (JwtAuthentication, SessionAuthentication,)
|
||||
# BearerAuthentication is added here to support account deletion
|
||||
# from the mobile app until it moves to JWT Auth.
|
||||
# See mobile roadmap issue https://github.com/openedx/edx-platform/issues/33307.
|
||||
authentication_classes = (JwtAuthentication, SessionAuthentication, BearerAuthentication)
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def post(self, request):
|
||||
|
||||
@@ -20,20 +20,17 @@ celery>=5.2.2,<6.0.0
|
||||
# required for celery>=5.2.0;<5.3.0
|
||||
click>=8.0,<9.0
|
||||
|
||||
# django-storages version upgrade
|
||||
django-storages==1.13.2
|
||||
|
||||
# each version upgrade need release notes review.
|
||||
django-storages==1.14
|
||||
|
||||
# The team that owns this package will manually bump this package rather than having it pulled in automatically.
|
||||
# This is to allow them to better control its deployment and to do it in a process that works better
|
||||
# for them.
|
||||
edx-enterprise==4.3.1
|
||||
edx-enterprise==4.3.2
|
||||
|
||||
# 1. django-oauth-toolkit version >=2.0.0 has breaking changes. More details
|
||||
# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884
|
||||
# 2. Versions from 1.5.0 to 2.0.0 have some migrations related changes.
|
||||
# so we're upgrading minor versions one by one.
|
||||
django-oauth-toolkit==1.6.2
|
||||
# django-oauth-toolkit version >=2.0.0 has breaking changes. More details
|
||||
# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884
|
||||
django-oauth-toolkit==1.7.1
|
||||
|
||||
|
||||
# constrained in opaque_keys. migration guide here: https://pymongo.readthedocs.io/en/4.0/migrate-to-pymongo4.html
|
||||
@@ -78,8 +75,7 @@ pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticke
|
||||
# Deprecated version of the AWS SDK;
|
||||
# we should stop using this
|
||||
boto==2.39.0
|
||||
boto3==1.7.0 # Amazon Web Services SDK for Python
|
||||
botocore==1.10.84 # via boto3, s3transfer
|
||||
|
||||
|
||||
# adding these constraints to minimize boto3 and botocore changeset
|
||||
social-auth-core==4.3.0
|
||||
@@ -128,7 +124,7 @@ click==8.1.6
|
||||
openedx-events<8.6.0 # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||
|
||||
# pinning this version to avoid updates while the library is being developed
|
||||
openedx-learning==0.1.6
|
||||
openedx-learning==0.1.7
|
||||
|
||||
# lti-consumer-xblock 9.6.2 contains a breaking change that makes
|
||||
# existing custom parameter configurations unusable.
|
||||
|
||||
@@ -77,16 +77,14 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
boto3==1.7.0
|
||||
boto3==1.28.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.10.84
|
||||
botocore==1.31.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
# boto3
|
||||
# s3transfer
|
||||
@@ -259,7 +257,7 @@ django-celery-results==2.5.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
django-classy-tags==4.1.0
|
||||
# via django-sekizai
|
||||
django-config-models==2.5.0
|
||||
django-config-models==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-enterprise
|
||||
@@ -325,7 +323,7 @@ django-multi-email-field==0.7.0
|
||||
# via edx-enterprise
|
||||
django-mysql==4.11.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
django-oauth-toolkit==1.6.2
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
@@ -357,7 +355,7 @@ django-statici18n==2.4.0
|
||||
# -r requirements/edx/kernel.in
|
||||
# lti-consumer-xblock
|
||||
# xblock-drag-and-drop-v2
|
||||
django-storages==1.13.2
|
||||
django-storages==1.14
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
@@ -402,10 +400,6 @@ djangorestframework==3.14.0
|
||||
# super-csv
|
||||
djangorestframework-xml==2.0.0
|
||||
# via edx-enterprise
|
||||
docutils==0.19
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# botocore
|
||||
done-xblock==2.1.0
|
||||
# via -r requirements/edx/bundled.in
|
||||
drf-jwt==1.19.2
|
||||
@@ -486,11 +480,11 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.1
|
||||
edx-enterprise==4.3.2
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
edx-event-bus-kafka==5.4.0
|
||||
edx-event-bus-kafka==5.5.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-event-bus-redis==0.3.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
@@ -500,7 +494,7 @@ edx-milestones==0.5.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-name-affirmation==2.3.6
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-opaque-keys[django]==2.5.0
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# -r requirements/edx/paver.txt
|
||||
@@ -636,7 +630,7 @@ jinja2==3.1.2
|
||||
# via
|
||||
# code-annotations
|
||||
# coreschema
|
||||
jmespath==0.10.0
|
||||
jmespath==1.0.1
|
||||
# via
|
||||
# boto3
|
||||
# botocore
|
||||
@@ -790,7 +784,7 @@ openedx-filters==1.6.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# lti-consumer-xblock
|
||||
openedx-learning==0.1.6
|
||||
openedx-learning==0.1.7
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
@@ -1034,7 +1028,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.1.13
|
||||
s3transfer==0.6.2
|
||||
# via boto3
|
||||
sailthru-client==2.2.3
|
||||
# via edx-ace
|
||||
@@ -1169,6 +1163,7 @@ urllib3==1.26.16
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/paver.txt
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# py2neo
|
||||
# requests
|
||||
|
||||
@@ -143,17 +143,15 @@ boto==2.39.0
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
boto3==1.7.0
|
||||
boto3==1.28.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.10.84
|
||||
botocore==1.31.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# boto3
|
||||
@@ -429,7 +427,7 @@ django-classy-tags==4.1.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# django-sekizai
|
||||
django-config-models==2.5.0
|
||||
django-config-models==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -522,7 +520,7 @@ django-mysql==4.11.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
django-oauth-toolkit==1.6.2
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -569,7 +567,7 @@ django-statici18n==2.4.0
|
||||
# -r requirements/edx/testing.txt
|
||||
# lti-consumer-xblock
|
||||
# xblock-drag-and-drop-v2
|
||||
django-storages==1.13.2
|
||||
django-storages==1.14
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -638,8 +636,6 @@ docutils==0.19
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# botocore
|
||||
# pydata-sphinx-theme
|
||||
# sphinx
|
||||
# sphinx-mdinclude
|
||||
@@ -752,12 +748,12 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.1
|
||||
edx-enterprise==4.3.2
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
edx-event-bus-kafka==5.4.0
|
||||
edx-event-bus-kafka==5.5.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -780,7 +776,7 @@ edx-name-affirmation==2.3.6
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
edx-opaque-keys[django]==2.5.0
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -948,7 +944,7 @@ gitdb==4.0.10
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# gitpython
|
||||
gitpython==3.1.36
|
||||
gitpython==3.1.37
|
||||
# via -r requirements/edx/doc.txt
|
||||
glob2==0.7
|
||||
# via
|
||||
@@ -1062,7 +1058,7 @@ jinja2==3.1.2
|
||||
# coreschema
|
||||
# diff-cover
|
||||
# sphinx
|
||||
jmespath==0.10.0
|
||||
jmespath==1.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1318,7 +1314,7 @@ openedx-filters==1.6.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# lti-consumer-xblock
|
||||
openedx-learning==0.1.6
|
||||
openedx-learning==0.1.7
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1789,7 +1785,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.1.13
|
||||
s3transfer==0.6.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -2110,6 +2106,7 @@ urllib3==1.26.16
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# pact-python
|
||||
# py2neo
|
||||
|
||||
@@ -103,16 +103,14 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
boto3==1.7.0
|
||||
boto3==1.28.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.10.84
|
||||
botocore==1.31.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
# s3transfer
|
||||
@@ -312,7 +310,7 @@ django-classy-tags==4.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-sekizai
|
||||
django-config-models==2.5.0
|
||||
django-config-models==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -386,7 +384,7 @@ django-multi-email-field==0.7.0
|
||||
# edx-enterprise
|
||||
django-mysql==4.11.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-oauth-toolkit==1.6.2
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -420,7 +418,7 @@ django-statici18n==2.4.0
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
# xblock-drag-and-drop-v2
|
||||
django-storages==1.13.2
|
||||
django-storages==1.14
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -470,8 +468,6 @@ djangorestframework-xml==2.0.0
|
||||
docutils==0.19
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# botocore
|
||||
# pydata-sphinx-theme
|
||||
# sphinx
|
||||
# sphinx-mdinclude
|
||||
@@ -560,11 +556,11 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.1
|
||||
edx-enterprise==4.3.2
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
edx-event-bus-kafka==5.4.0
|
||||
edx-event-bus-kafka==5.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-event-bus-redis==0.3.1
|
||||
# via -r requirements/edx/base.txt
|
||||
@@ -576,7 +572,7 @@ edx-milestones==0.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-name-affirmation==2.3.6
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-opaque-keys[django]==2.5.0
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-bulk-grades
|
||||
@@ -686,7 +682,7 @@ geoip2==4.7.0
|
||||
# via -r requirements/edx/base.txt
|
||||
gitdb==4.0.10
|
||||
# via gitpython
|
||||
gitpython==3.1.36
|
||||
gitpython==3.1.37
|
||||
# via -r requirements/edx/doc.in
|
||||
glob2==0.7
|
||||
# via -r requirements/edx/base.txt
|
||||
@@ -744,7 +740,7 @@ jinja2==3.1.2
|
||||
# code-annotations
|
||||
# coreschema
|
||||
# sphinx
|
||||
jmespath==0.10.0
|
||||
jmespath==1.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -928,7 +924,7 @@ openedx-filters==1.6.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
openedx-learning==0.1.6
|
||||
openedx-learning==0.1.7
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1222,7 +1218,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.1.13
|
||||
s3transfer==0.6.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -1424,6 +1420,7 @@ urllib3==1.26.16
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# py2neo
|
||||
# requests
|
||||
|
||||
@@ -10,7 +10,7 @@ charset-normalizer==2.0.12
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# requests
|
||||
edx-opaque-keys==2.5.0
|
||||
edx-opaque-keys==2.5.1
|
||||
# via -r requirements/edx/paver.in
|
||||
idna==3.4
|
||||
# via requests
|
||||
|
||||
@@ -108,16 +108,14 @@ boto==2.39.0
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
boto3==1.7.0
|
||||
boto3==1.28.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
botocore==1.10.84
|
||||
botocore==1.31.53
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
# s3transfer
|
||||
@@ -343,7 +341,7 @@ django-classy-tags==4.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-sekizai
|
||||
django-config-models==2.5.0
|
||||
django-config-models==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -417,7 +415,7 @@ django-multi-email-field==0.7.0
|
||||
# edx-enterprise
|
||||
django-mysql==4.11.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-oauth-toolkit==1.6.2
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -451,7 +449,7 @@ django-statici18n==2.4.0
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
# xblock-drag-and-drop-v2
|
||||
django-storages==1.13.2
|
||||
django-storages==1.14
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -498,11 +496,6 @@ djangorestframework-xml==2.0.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
docutils==0.19
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# botocore
|
||||
done-xblock==2.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
drf-jwt==1.19.2
|
||||
@@ -588,11 +581,11 @@ edx-drf-extensions==8.10.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# openedx-learning
|
||||
edx-enterprise==4.3.1
|
||||
edx-enterprise==4.3.2
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
edx-event-bus-kafka==5.4.0
|
||||
edx-event-bus-kafka==5.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-event-bus-redis==0.3.1
|
||||
# via -r requirements/edx/base.txt
|
||||
@@ -607,7 +600,7 @@ edx-milestones==0.5.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-name-affirmation==2.3.6
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-opaque-keys[django]==2.5.0
|
||||
edx-opaque-keys[django]==2.5.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-bulk-grades
|
||||
@@ -808,7 +801,7 @@ jinja2==3.1.2
|
||||
# code-annotations
|
||||
# coreschema
|
||||
# diff-cover
|
||||
jmespath==0.10.0
|
||||
jmespath==1.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -995,7 +988,7 @@ openedx-filters==1.6.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
openedx-learning==0.1.6
|
||||
openedx-learning==0.1.7
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1356,7 +1349,7 @@ rules==3.3
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
# openedx-learning
|
||||
s3transfer==0.1.13
|
||||
s3transfer==0.6.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -1557,6 +1550,7 @@ urllib3==1.26.16
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# pact-python
|
||||
# py2neo
|
||||
|
||||
Reference in New Issue
Block a user