From 4fc7b2f640d90c714097837a5fd87e6eb3e875e3 Mon Sep 17 00:00:00 2001 From: Jawayria Date: Fri, 12 Feb 2021 18:31:22 +0500 Subject: [PATCH 01/74] BOM-2351: Removed unused imports from common/lib/{capa, safe_lxml, symmath, conftest.py} --- common/lib/capa/capa/tests/test_html_render.py | 3 --- common/lib/capa/capa/tests/test_targeted_feedback.py | 3 --- common/lib/conftest.py | 2 -- common/lib/symmath/symmath/formula.py | 1 - 4 files changed, 9 deletions(-) diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py index 17bc9878dd..c4768221d7 100644 --- a/common/lib/capa/capa/tests/test_html_render.py +++ b/common/lib/capa/capa/tests/test_html_render.py @@ -10,9 +10,6 @@ import unittest import ddt import mock from lxml import etree - -# Changes formatting of empty elements; import here to avoid test order dependence -import xmodule.modulestore.xml # pylint: disable=unused-import from capa.tests.helpers import new_loncapa_problem, test_capa_system from openedx.core.djangolib.markup import HTML diff --git a/common/lib/capa/capa/tests/test_targeted_feedback.py b/common/lib/capa/capa/tests/test_targeted_feedback.py index 09df597b65..343825533d 100644 --- a/common/lib/capa/capa/tests/test_targeted_feedback.py +++ b/common/lib/capa/capa/tests/test_targeted_feedback.py @@ -6,9 +6,6 @@ i.e. those with the element import textwrap import unittest - -# Changes formatting of empty elements; import here to avoid test order dependence -import xmodule.modulestore.xml # pylint: disable=unused-import from capa.tests.helpers import load_fixture, new_loncapa_problem, test_capa_system diff --git a/common/lib/conftest.py b/common/lib/conftest.py index 03bd8596e5..daab9ba264 100644 --- a/common/lib/conftest.py +++ b/common/lib/conftest.py @@ -7,8 +7,6 @@ import pytest from safe_lxml import defuse_xml_libs -from openedx.core.pytest_hooks import pytest_configure # pylint: disable=unused-import - defuse_xml_libs() diff --git a/common/lib/symmath/symmath/formula.py b/common/lib/symmath/symmath/formula.py index d72389b72c..1169113251 100644 --- a/common/lib/symmath/symmath/formula.py +++ b/common/lib/symmath/symmath/formula.py @@ -21,7 +21,6 @@ import unicodedata #import subprocess from copy import deepcopy from functools import reduce -from xml.sax.saxutils import unescape # lint-amnesty, pylint: disable=unused-import import six import sympy From 62ca4742c12056a20254b47b61acb3cb9465c624 Mon Sep 17 00:00:00 2001 From: Jawayria Date: Mon, 15 Feb 2021 19:30:40 +0500 Subject: [PATCH 02/74] BOM-2351: Removed unused imports from common/test --- common/test/acceptance/fixtures/base.py | 1 - common/test/acceptance/pages/lms/video/video.py | 2 +- common/test/acceptance/pages/studio/overview.py | 2 +- common/test/acceptance/tests/discussion/test_discussion.py | 4 +++- common/test/acceptance/tests/helpers.py | 4 ++-- common/test/conftest.py | 5 ----- common/test/utils.py | 2 -- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/common/test/acceptance/fixtures/base.py b/common/test/acceptance/fixtures/base.py index f415e11f48..6b4a3b15f2 100644 --- a/common/test/acceptance/fixtures/base.py +++ b/common/test/acceptance/fixtures/base.py @@ -6,7 +6,6 @@ Common code shared by course and library fixtures. import json import requests -import six # lint-amnesty, pylint: disable=unused-import from lazy import lazy from common.test.acceptance.fixtures import STUDIO_BASE_URL diff --git a/common/test/acceptance/pages/lms/video/video.py b/common/test/acceptance/pages/lms/video/video.py index 55ce59e76f..91a234d0c2 100644 --- a/common/test/acceptance/pages/lms/video/video.py +++ b/common/test/acceptance/pages/lms/video/video.py @@ -7,7 +7,7 @@ import logging from bok_choy.javascript import js_defined, wait_for_js from bok_choy.page_object import PageObject -from bok_choy.promise import EmptyPromise, Promise # lint-amnesty, pylint: disable=unused-import +from bok_choy.promise import EmptyPromise # lint-amnesty, pylint: disable=unused-import log = logging.getLogger('VideoPage') diff --git a/common/test/acceptance/pages/studio/overview.py b/common/test/acceptance/pages/studio/overview.py index 409e5e12cb..bc6d7b5ff0 100644 --- a/common/test/acceptance/pages/studio/overview.py +++ b/common/test/acceptance/pages/studio/overview.py @@ -3,7 +3,7 @@ Course Outline page in Studio. """ -from bok_choy.javascript import js_defined, wait_for_js # lint-amnesty, pylint: disable=unused-import +from bok_choy.javascript import js_defined # lint-amnesty, pylint: disable=unused-import from bok_choy.page_object import PageObject from bok_choy.promise import EmptyPromise from selenium.webdriver.support.ui import Select diff --git a/common/test/acceptance/tests/discussion/test_discussion.py b/common/test/acceptance/tests/discussion/test_discussion.py index b37ae7180a..a0cbd83e49 100644 --- a/common/test/acceptance/tests/discussion/test_discussion.py +++ b/common/test/acceptance/tests/discussion/test_discussion.py @@ -7,17 +7,19 @@ from uuid import uuid4 import pytest -from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc # lint-amnesty, pylint: disable=unused-import +from common.test.acceptance.fixtures.course import CourseFixture # lint-amnesty, pylint: disable=unused-import from common.test.acceptance.fixtures.discussion import ( Comment, Response, SingleThreadViewFixture, Thread, + ) from common.test.acceptance.pages.common.auto_auth import AutoAuthPage from common.test.acceptance.pages.lms.discussion import ( DiscussionTabHomePage, DiscussionTabSingleThreadPage, + ) from common.test.acceptance.tests.discussion.helpers import BaseDiscussionMixin, BaseDiscussionTestCase from common.test.acceptance.tests.helpers import UniqueCourseTest diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py index de03eef29d..f3f4c8cff7 100644 --- a/common/test/acceptance/tests/helpers.py +++ b/common/test/acceptance/tests/helpers.py @@ -19,12 +19,12 @@ from bok_choy.promise import EmptyPromise, Promise from bok_choy.web_app_test import WebAppTest from opaque_keys.edx.locator import CourseLocator from path import Path as path -from pymongo import ASCENDING, MongoClient # lint-amnesty, pylint: disable=unused-import +from pymongo import MongoClient # lint-amnesty, pylint: disable=unused-import from selenium.common.exceptions import StaleElementReferenceException from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.select import Select from selenium.webdriver.support.ui import WebDriverWait -from six.moves import range, zip # lint-amnesty, pylint: disable=unused-import +from six.moves import range # lint-amnesty, pylint: disable=unused-import from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory from common.test.acceptance.fixtures.course import XBlockFixtureDesc diff --git a/common/test/conftest.py b/common/test/conftest.py index 055da4ea8a..32b122bbca 100644 --- a/common/test/conftest.py +++ b/common/test/conftest.py @@ -1,9 +1,4 @@ """Code run by pylint before running any tests.""" - -# Patch the xml libs before anything else. - - -from openedx.core.pytest_hooks import pytest_configure # pylint: disable=unused-import from safe_lxml import defuse_xml_libs defuse_xml_libs() diff --git a/common/test/utils.py b/common/test/utils.py index d65677e2fc..c996e1017b 100644 --- a/common/test/utils.py +++ b/common/test/utils.py @@ -6,8 +6,6 @@ General testing utilities. import functools import sys from contextlib import contextmanager - -import pytest # lint-amnesty, pylint: disable=unused-import from django.dispatch import Signal from markupsafe import escape from mock import Mock, patch From 9420423e1a699bd75b2f672a5f662f18570b3804 Mon Sep 17 00:00:00 2001 From: "zia.fazal@arbisoft.com" Date: Tue, 16 Feb 2021 15:19:17 +0500 Subject: [PATCH 03/74] Added annotations for EMBARGO feature flag --- lms/envs/common.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index 0a1824ba91..a45c8003b2 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -404,8 +404,18 @@ FEATURES = { # Hide any Personally Identifiable Information from application logs 'SQUELCH_PII_IN_LOGS': True, - # Toggles the embargo functionality, which blocks users from - # the site or courses based on their location. + # .. toggle_name: FEATURES['EMBARGO'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Turns on embargo functionality, which blocks users from + # the site or courses based on their location. Embargo can restrict users by states + # and whitelist/blacklist (IP Addresses (ie. 10.0.0.0), Networks (ie. 10.0.0.0/24)), or the user profile country. + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2014-02-27 + # .. toggle_target_removal_date: None + # .. toggle_warnings: reverse proxy should be configured appropriately for example Client IP address headers + # (e.g HTTP_X_FORWARDED_FOR) should be configured. + # .. toggle_tickets: https://github.com/edx/edx-platform/pull/2749 'EMBARGO': False, # Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means From caebb910f0e6eb8f2938f075d71ff03f9e40f659 Mon Sep 17 00:00:00 2001 From: Jawayria Date: Fri, 12 Feb 2021 13:28:07 +0500 Subject: [PATCH 04/74] BOM-2351: Removed unused imports from common/djangoapps/student --- common/djangoapps/student/forms.py | 10 ---------- .../student/management/commands/bulk_unenroll.py | 1 - common/djangoapps/student/tests/test_admin_views.py | 1 - common/djangoapps/student/tests/test_course_listing.py | 2 +- .../djangoapps/student/tests/test_password_policy.py | 5 ----- .../student/tests/test_recent_enrollments.py | 2 -- common/djangoapps/student/tests/test_views.py | 2 +- common/djangoapps/student/views/management.py | 2 +- 8 files changed, 3 insertions(+), 22 deletions(-) diff --git a/common/djangoapps/student/forms.py b/common/djangoapps/student/forms.py index b8bbd2e5e4..15f17dbc35 100644 --- a/common/djangoapps/student/forms.py +++ b/common/djangoapps/student/forms.py @@ -2,17 +2,10 @@ Utility functions for validating forms """ - -import re # lint-amnesty, pylint: disable=unused-import -from importlib import import_module # lint-amnesty, pylint: disable=unused-import - from django.conf import settings -from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user, unused-import from django.contrib.auth.tokens import default_token_generator -from django.core.exceptions import ValidationError # lint-amnesty, pylint: disable=unused-import from django.urls import reverse from django.utils.http import int_to_base36 -from django.utils.translation import ugettext_lazy as _ # lint-amnesty, pylint: disable=unused-import from edx_ace import ace from edx_ace.recipient import Recipient @@ -20,12 +13,9 @@ from openedx.core.djangoapps.ace_common.template_context import get_base_templat from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming.helpers import get_current_site -from openedx.core.djangoapps.user_api import accounts as accounts_settings # lint-amnesty, pylint: disable=unused-import -from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_feature_enabled # lint-amnesty, pylint: disable=unused-import from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from common.djangoapps.student.message_types import AccountRecovery as AccountRecoveryMessage -from common.djangoapps.student.models import CourseEnrollmentAllowed, email_exists_or_retired # lint-amnesty, pylint: disable=unused-import def send_account_recovery_email_for_user(user, request, email=None): diff --git a/common/djangoapps/student/management/commands/bulk_unenroll.py b/common/djangoapps/student/management/commands/bulk_unenroll.py index 19bbf271b8..451ccefbe0 100644 --- a/common/djangoapps/student/management/commands/bulk_unenroll.py +++ b/common/djangoapps/student/management/commands/bulk_unenroll.py @@ -4,7 +4,6 @@ Un-enroll Bulk users course wide as well as specified in csv import logging import unicodecsv -from django.core.exceptions import ObjectDoesNotExist # lint-amnesty, pylint: disable=unused-import from django.core.management.base import BaseCommand from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey diff --git a/common/djangoapps/student/tests/test_admin_views.py b/common/djangoapps/student/tests/test_admin_views.py index 7e986e32e3..c03cdf840e 100644 --- a/common/djangoapps/student/tests/test_admin_views.py +++ b/common/djangoapps/student/tests/test_admin_views.py @@ -9,7 +9,6 @@ import datetime import ddt import pytest import six -from django.conf import settings # lint-amnesty, pylint: disable=unused-import from django.contrib.admin.sites import AdminSite from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.forms import ValidationError diff --git a/common/djangoapps/student/tests/test_course_listing.py b/common/djangoapps/student/tests/test_course_listing.py index 7314be6678..ceaf25f30c 100644 --- a/common/djangoapps/student/tests/test_course_listing.py +++ b/common/djangoapps/student/tests/test_course_listing.py @@ -13,7 +13,7 @@ from django.test.client import Client from milestones.tests.utils import MilestonesTestCaseMixin from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from common.djangoapps.student.models import CourseEnrollment, DashboardConfiguration # lint-amnesty, pylint: disable=unused-import +from common.djangoapps.student.models import CourseEnrollment # lint-amnesty, pylint: disable=unused-import from common.djangoapps.student.roles import GlobalStaff from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.student.views import get_course_enrollments diff --git a/common/djangoapps/student/tests/test_password_policy.py b/common/djangoapps/student/tests/test_password_policy.py index 62980aa7b3..11bbf38b5a 100644 --- a/common/djangoapps/student/tests/test_password_policy.py +++ b/common/djangoapps/student/tests/test_password_policy.py @@ -5,15 +5,10 @@ This test file will verify proper password policy enforcement, which is an optio import json - -from django.contrib.auth.models import AnonymousUser # lint-amnesty, pylint: disable=unused-import from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings from django.urls import reverse -from mock import patch # lint-amnesty, pylint: disable=unused-import - -from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory # lint-amnesty, pylint: disable=unused-import from common.djangoapps.util.password_policy_validators import create_validator_config diff --git a/common/djangoapps/student/tests/test_recent_enrollments.py b/common/djangoapps/student/tests/test_recent_enrollments.py index bfc0016631..bc0d71ac0d 100644 --- a/common/djangoapps/student/tests/test_recent_enrollments.py +++ b/common/djangoapps/student/tests/test_recent_enrollments.py @@ -15,8 +15,6 @@ from pytz import UTC from six.moves import range, zip from common.test.utils import XssTestMixin -from common.djangoapps.course_modes.tests.factories import CourseModeFactory # lint-amnesty, pylint: disable=unused-import -from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context # lint-amnesty, pylint: disable=unused-import from common.djangoapps.student.models import CourseEnrollment, DashboardConfiguration from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.student.views import get_course_enrollments diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 7f26e201fc..1c1a443316 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -7,7 +7,7 @@ import itertools import json import re import unittest -from datetime import datetime, timedelta # lint-amnesty, pylint: disable=unused-import +from datetime import timedelta # lint-amnesty, pylint: disable=unused-import import ddt import six diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 0a62a604de..bd470d444e 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -58,7 +58,7 @@ from common.djangoapps.student.message_types import AccountActivation, EmailChan from common.djangoapps.student.models import ( # lint-amnesty, pylint: disable=unused-import AccountRecovery, CourseEnrollment, - PendingEmailChange, + PendingEmailChange, # unimport:skip PendingSecondaryEmailChange, Registration, RegistrationCookieConfiguration, From 310da0d6a70176fe65988ed8a1cc506ec6ac8faa Mon Sep 17 00:00:00 2001 From: Jawayria Date: Fri, 12 Feb 2021 14:00:39 +0500 Subject: [PATCH 05/74] BOM-2351: Removed unused imports from common/djangoapps/third_party_auth --- .../djangoapps/third_party_auth/api/tests/test_permissions.py | 1 - common/djangoapps/third_party_auth/decorators.py | 4 +--- .../saml_configuration/tests/test_saml_configuration.py | 3 --- .../samlproviderconfig/tests/test_samlproviderconfig.py | 3 --- .../samlproviderdata/tests/test_samlproviderdata.py | 3 --- .../djangoapps/third_party_auth/tests/specs/test_generic.py | 3 --- .../djangoapps/third_party_auth/tests/specs/test_testshib.py | 1 - common/djangoapps/third_party_auth/tests/test_admin.py | 3 --- common/djangoapps/third_party_auth/tests/test_decorators.py | 4 ---- .../djangoapps/third_party_auth/tests/test_identityserver3.py | 2 -- common/djangoapps/third_party_auth/tests/test_pipeline.py | 1 - common/djangoapps/third_party_auth/tests/test_settings.py | 3 --- 12 files changed, 1 insertion(+), 30 deletions(-) diff --git a/common/djangoapps/third_party_auth/api/tests/test_permissions.py b/common/djangoapps/third_party_auth/api/tests/test_permissions.py index 87528cf0e0..4120b06fc5 100644 --- a/common/djangoapps/third_party_auth/api/tests/test_permissions.py +++ b/common/djangoapps/third_party_auth/api/tests/test_permissions.py @@ -10,7 +10,6 @@ from django.conf import settings from django.test import RequestFactory, TestCase from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.jwt.tests.utils import generate_jwt -from mock import patch # lint-amnesty, pylint: disable=unused-import from rest_framework.authentication import SessionAuthentication from rest_framework.response import Response from rest_framework.views import APIView diff --git a/common/djangoapps/third_party_auth/decorators.py b/common/djangoapps/third_party_auth/decorators.py index 0ac563d902..1cab617795 100644 --- a/common/djangoapps/third_party_auth/decorators.py +++ b/common/djangoapps/third_party_auth/decorators.py @@ -6,12 +6,10 @@ Decorators that can be used to interact with third_party_auth. from functools import wraps from django.conf import settings -from django.shortcuts import redirect # lint-amnesty, pylint: disable=unused-import from django.utils.decorators import available_attrs -from six.moves.urllib.parse import urlencode, urlparse # lint-amnesty, pylint: disable=unused-import +from six.moves.urllib.parse import urlparse # lint-amnesty, pylint: disable=unused-import from common.djangoapps.third_party_auth.models import LTIProviderConfig -from common.djangoapps.third_party_auth.provider import Registry # lint-amnesty, pylint: disable=unused-import def xframe_allow_whitelisted(view_func): diff --git a/common/djangoapps/third_party_auth/saml_configuration/tests/test_saml_configuration.py b/common/djangoapps/third_party_auth/saml_configuration/tests/test_saml_configuration.py index 49e57ca546..540d73ad86 100644 --- a/common/djangoapps/third_party_auth/saml_configuration/tests/test_saml_configuration.py +++ b/common/djangoapps/third_party_auth/saml_configuration/tests/test_saml_configuration.py @@ -1,8 +1,6 @@ """ Tests for SAMLConfiguration endpoints """ - -import unittest # lint-amnesty, pylint: disable=unused-import from django.urls import reverse from django.contrib.sites.models import Site from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -10,7 +8,6 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imp from rest_framework import status from rest_framework.test import APITestCase from common.djangoapps.third_party_auth.models import SAMLConfiguration -from common.djangoapps.third_party_auth.tests import testutil # lint-amnesty, pylint: disable=unused-import from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth SAML_CONFIGURATIONS = [ { diff --git a/common/djangoapps/third_party_auth/samlproviderconfig/tests/test_samlproviderconfig.py b/common/djangoapps/third_party_auth/samlproviderconfig/tests/test_samlproviderconfig.py index 91ef4c2b2a..29ccf53bb1 100644 --- a/common/djangoapps/third_party_auth/samlproviderconfig/tests/test_samlproviderconfig.py +++ b/common/djangoapps/third_party_auth/samlproviderconfig/tests/test_samlproviderconfig.py @@ -1,8 +1,6 @@ """ Tests for SAMLProviderConfig endpoints """ - -import unittest import copy from uuid import uuid4 from django.urls import reverse @@ -16,7 +14,6 @@ from enterprise.models import EnterpriseCustomerIdentityProvider, EnterpriseCust from enterprise.constants import ENTERPRISE_ADMIN_ROLE, ENTERPRISE_LEARNER_ROLE from common.djangoapps.third_party_auth.tests.samlutils import set_jwt_cookie from common.djangoapps.third_party_auth.models import SAMLProviderConfig, SAMLConfiguration -from common.djangoapps.third_party_auth.tests import testutil from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth from common.djangoapps.third_party_auth.utils import convert_saml_slug_provider_id diff --git a/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py b/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py index ad9d14770f..8208d8ae88 100644 --- a/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py +++ b/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py @@ -1,4 +1,3 @@ -import unittest import copy import pytz from uuid import uuid4 @@ -12,8 +11,6 @@ from rest_framework.test import APITestCase from enterprise.models import EnterpriseCustomer, EnterpriseCustomerIdentityProvider from enterprise.constants import ENTERPRISE_ADMIN_ROLE, ENTERPRISE_LEARNER_ROLE - -from common.djangoapps.third_party_auth.tests import testutil from common.djangoapps.third_party_auth.models import SAMLProviderData, SAMLProviderConfig from common.djangoapps.third_party_auth.tests.samlutils import set_jwt_cookie from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth diff --git a/common/djangoapps/third_party_auth/tests/specs/test_generic.py b/common/djangoapps/third_party_auth/tests/specs/test_generic.py index bb05947e63..f02ccd54d2 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_generic.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_generic.py @@ -1,9 +1,6 @@ """ Use the 'Dummy' auth provider for generic integration tests of third_party_auth. """ - - -import unittest # lint-amnesty, pylint: disable=unused-import from common.djangoapps.third_party_auth.tests import testutil from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth from .base import IntegrationTestMixin diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py index 12290c4ada..30c8c7c391 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -7,7 +7,6 @@ import datetime import json import logging import os -import unittest # lint-amnesty, pylint: disable=unused-import from unittest import skip import ddt diff --git a/common/djangoapps/third_party_auth/tests/test_admin.py b/common/djangoapps/third_party_auth/tests/test_admin.py index 102bba22df..7099911e55 100644 --- a/common/djangoapps/third_party_auth/tests/test_admin.py +++ b/common/djangoapps/third_party_auth/tests/test_admin.py @@ -2,9 +2,6 @@ Tests third_party_auth admin views """ - -import unittest # lint-amnesty, pylint: disable=unused-import - from django.contrib.admin.sites import AdminSite from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import models diff --git a/common/djangoapps/third_party_auth/tests/test_decorators.py b/common/djangoapps/third_party_auth/tests/test_decorators.py index 97d6945f22..0a2c732c4d 100644 --- a/common/djangoapps/third_party_auth/tests/test_decorators.py +++ b/common/djangoapps/third_party_auth/tests/test_decorators.py @@ -2,11 +2,7 @@ Tests for third_party_auth decorators. """ - -import unittest # lint-amnesty, pylint: disable=unused-import - import ddt -from django.conf import settings # lint-amnesty, pylint: disable=unused-import from django.http import HttpResponse from django.test import RequestFactory diff --git a/common/djangoapps/third_party_auth/tests/test_identityserver3.py b/common/djangoapps/third_party_auth/tests/test_identityserver3.py index c64f4f1d80..40796070d2 100644 --- a/common/djangoapps/third_party_auth/tests/test_identityserver3.py +++ b/common/djangoapps/third_party_auth/tests/test_identityserver3.py @@ -3,8 +3,6 @@ Unit tests for the IdentityServer3 OAuth2 Backend """ import json import ddt -import pytest # pylint: disable=unused-import - from common.djangoapps.third_party_auth.identityserver3 import IdentityServer3 from common.djangoapps.third_party_auth.tests import testutil from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth diff --git a/common/djangoapps/third_party_auth/tests/test_pipeline.py b/common/djangoapps/third_party_auth/tests/test_pipeline.py index be85047d8d..9c3c265de9 100644 --- a/common/djangoapps/third_party_auth/tests/test_pipeline.py +++ b/common/djangoapps/third_party_auth/tests/test_pipeline.py @@ -2,7 +2,6 @@ import json -import unittest # lint-amnesty, pylint: disable=unused-import import ddt import mock diff --git a/common/djangoapps/third_party_auth/tests/test_settings.py b/common/djangoapps/third_party_auth/tests/test_settings.py index af3d280a3c..a56273759b 100644 --- a/common/djangoapps/third_party_auth/tests/test_settings.py +++ b/common/djangoapps/third_party_auth/tests/test_settings.py @@ -1,8 +1,5 @@ """Unit tests for settings.py.""" - -import unittest # lint-amnesty, pylint: disable=unused-import - from mock import patch from common.djangoapps.third_party_auth import provider, settings from common.djangoapps.third_party_auth.tests import testutil From a383845b0116cc63f744938802df0c7ff59af829 Mon Sep 17 00:00:00 2001 From: Jawayria Date: Mon, 15 Feb 2021 21:32:26 +0500 Subject: [PATCH 06/74] BOM-2352: Removed unused imports from lms/djangoapps/{course_blocks, course_goals, course_home_api, courseware} --- lms/djangoapps/course_blocks/transformers/user_partitions.py | 3 +-- lms/djangoapps/course_home_api/progress/v1/serializers.py | 2 -- lms/djangoapps/courseware/access_utils.py | 1 - lms/djangoapps/courseware/tests/test_about.py | 1 - lms/djangoapps/courseware/tests/test_module_render.py | 3 --- lms/djangoapps/courseware/tests/test_views.py | 2 +- 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/course_blocks/transformers/user_partitions.py b/lms/djangoapps/course_blocks/transformers/user_partitions.py index d8baeaaf0e..11151926bb 100644 --- a/lms/djangoapps/course_blocks/transformers/user_partitions.py +++ b/lms/djangoapps/course_blocks/transformers/user_partitions.py @@ -5,8 +5,7 @@ User Partitions Transformer from lms.djangoapps.courseware.access import has_access from openedx.core.djangoapps.content.block_structure.transformer import ( # lint-amnesty, pylint: disable=unused-import - BlockStructureTransformer, - FilteringTransformerMixin + BlockStructureTransformer ) from xmodule.partitions.partitions_service import ( get_all_partitions_for_course, diff --git a/lms/djangoapps/course_home_api/progress/v1/serializers.py b/lms/djangoapps/course_home_api/progress/v1/serializers.py index 7de399d3b1..bc6895d11b 100644 --- a/lms/djangoapps/course_home_api/progress/v1/serializers.py +++ b/lms/djangoapps/course_home_api/progress/v1/serializers.py @@ -4,8 +4,6 @@ Progress Tab Serializers from rest_framework import serializers from rest_framework.reverse import reverse -from lms.djangoapps.certificates.models import CertificateStatuses - class GradedTotalSerializer(serializers.Serializer): earned = serializers.FloatField() diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py index 403f074216..d817af3432 100644 --- a/lms/djangoapps/courseware/access_utils.py +++ b/lms/djangoapps/courseware/access_utils.py @@ -8,7 +8,6 @@ from datetime import datetime, timedelta from logging import getLogger from django.conf import settings -from django.utils.translation import ugettext as _ # lint-amnesty, pylint: disable=unused-import from pytz import UTC from lms.djangoapps.courseware.access_response import ( AccessResponse, diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index fa51481d77..0dc0ab385c 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -24,7 +24,6 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML from openedx.features.course_experience.waffle import WAFFLE_NAMESPACE as COURSE_EXPERIENCE_WAFFLE_NAMESPACE -from common.djangoapps.student.models import CourseEnrollment # lint-amnesty, pylint: disable=unused-import from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentAllowedFactory, UserFactory from common.djangoapps.track.tests import EventTrackingTestCase from common.djangoapps.util.milestones_helpers import get_prerequisite_courses_display, set_prerequisite_courses diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index f61d099b46..9e62d0eca4 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -14,10 +14,8 @@ import ddt import pytz import six from bson import ObjectId -from capa.tests.response_xml_factory import OptionResponseXMLFactory from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH # lint-amnesty, pylint: disable=wrong-import-order from completion.models import BlockCompletion # lint-amnesty, pylint: disable=wrong-import-order -from common.djangoapps.course_modes.models import CourseMode from django.conf import settings # lint-amnesty, pylint: disable=wrong-import-order from django.contrib.auth.models import AnonymousUser # lint-amnesty, pylint: disable=wrong-import-order from django.http import Http404, HttpResponse # lint-amnesty, pylint: disable=wrong-import-order @@ -28,7 +26,6 @@ from django.urls import reverse # lint-amnesty, pylint: disable=wrong-import-or from edx_proctoring.api import create_exam, create_exam_attempt, update_attempt_status # lint-amnesty, pylint: disable=wrong-import-order from edx_proctoring.runtime import set_runtime_service # lint-amnesty, pylint: disable=wrong-import-order from edx_proctoring.tests.test_services import MockCertificateService, MockCreditService, MockGradesService # lint-amnesty, pylint: disable=wrong-import-order -from edx_toggles.toggles import LegacyWaffleSwitch # lint-amnesty, pylint: disable=unused-import, wrong-import-order from edx_toggles.toggles.testutils import override_waffle_switch # lint-amnesty, pylint: disable=wrong-import-order from edx_when.field_data import DateLookupFieldData # lint-amnesty, pylint: disable=wrong-import-order from freezegun import freeze_time # lint-amnesty, pylint: disable=wrong-import-order diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 6cfe09c645..bc8293171c 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -7,7 +7,6 @@ Tests courseware views.py import html import itertools import json -import unittest # lint-amnesty, pylint: disable=unused-import from datetime import datetime, timedelta from uuid import uuid4 @@ -60,6 +59,7 @@ from lms.djangoapps.courseware.toggles import ( COURSEWARE_MICROFRONTEND_COURSE_TEAM_PREVIEW, COURSEWARE_OPTIMIZED_RENDER_XBLOCK, REDIRECT_TO_COURSEWARE_MICROFRONTEND, + ) from lms.djangoapps.courseware.url_helpers import get_microfrontend_url, get_redirect_url from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient From bd08f0d755eb0056bd4d48052d0604010f9fdfcf Mon Sep 17 00:00:00 2001 From: Jawayria Date: Tue, 16 Feb 2021 14:14:57 +0500 Subject: [PATCH 07/74] BOM-2352: Removed unused imports from lms/djangoapps/{email_marketing, experiments} --- lms/djangoapps/email_marketing/tests/test_signals.py | 1 - lms/djangoapps/experiments/tests/test_views.py | 9 ++------- lms/djangoapps/experiments/utils.py | 3 --- lms/djangoapps/experiments/views.py | 10 +++++----- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/email_marketing/tests/test_signals.py b/lms/djangoapps/email_marketing/tests/test_signals.py index aef30e561a..45a5305a48 100644 --- a/lms/djangoapps/email_marketing/tests/test_signals.py +++ b/lms/djangoapps/email_marketing/tests/test_signals.py @@ -29,7 +29,6 @@ from lms.djangoapps.email_marketing.tasks import ( # lint-amnesty, pylint: disa _get_list_from_email_marketing_provider, _get_or_create_user_list, get_email_cookies_via_sailthru, - update_course_enrollment, update_user, update_user_email ) diff --git a/lms/djangoapps/experiments/tests/test_views.py b/lms/djangoapps/experiments/tests/test_views.py index b82cb1fca2..b1d6624947 100644 --- a/lms/djangoapps/experiments/tests/test_views.py +++ b/lms/djangoapps/experiments/tests/test_views.py @@ -4,12 +4,10 @@ Tests for experimentation views import unittest -from datetime import timedelta from unittest.mock import patch -import six.moves.urllib.error import six.moves.urllib.parse -import six.moves.urllib.request +from datetime import timedelta from django.conf import settings from django.core.handlers.wsgi import WSGIRequest from django.test.utils import override_settings @@ -20,10 +18,7 @@ from rest_framework.test import APITestCase from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase from lms.djangoapps.experiments.factories import ExperimentDataFactory, ExperimentKeyValueFactory -from lms.djangoapps.experiments.models import ( # lint-amnesty, pylint: disable=unused-import - ExperimentData, - ExperimentKeyValue -) +from lms.djangoapps.experiments.models import ExperimentData # lint-amnesty, pylint: disable=unused-import from lms.djangoapps.experiments.serializers import ExperimentDataSerializer from xmodule.modulestore.tests.factories import CourseFactory diff --git a/lms/djangoapps/experiments/utils.py b/lms/djangoapps/experiments/utils.py index 76a0ed5732..e6e657ff4c 100644 --- a/lms/djangoapps/experiments/utils.py +++ b/lms/djangoapps/experiments/utils.py @@ -23,9 +23,6 @@ from openedx.core.djangoapps.schedules.models import Schedule from openedx.features.course_duration_limits.access import get_user_course_duration, get_user_course_expiration_date from xmodule.partitions.partitions_service import get_all_partitions_for_course, get_user_partition_groups -# Import this for backwards compatibility (so that anyone importing this function from here doesn't break) -from .stable_bucketing import stable_bucketing_hash_group # pylint: disable=unused-import - logger = logging.getLogger(__name__) diff --git a/lms/djangoapps/experiments/views.py b/lms/djangoapps/experiments/views.py index e25c593904..6382fccd7d 100644 --- a/lms/djangoapps/experiments/views.py +++ b/lms/djangoapps/experiments/views.py @@ -4,15 +4,15 @@ Experimentation views from django.contrib.auth import get_user_model -from django.db import transaction # lint-amnesty, pylint: disable=unused-import from django.http import Http404 from django_filters.rest_framework import DjangoFilterBackend from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser -from opaque_keys.edx.keys import CourseKey -from rest_framework import permissions, viewsets -from rest_framework.response import Response # lint-amnesty, pylint: disable=unused-import, wrong-import-order -from rest_framework.views import APIView +from lms.djangoapps.courseware import courses +from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order +from rest_framework import permissions, viewsets # lint-amnesty, pylint: disable=wrong-import-order +from rest_framework.views import APIView # lint-amnesty, pylint: disable=wrong-import-order +from common.djangoapps.util.json_request import JsonResponse from common.djangoapps.student.models import get_user_by_username_or_email from common.djangoapps.util.json_request import JsonResponse From f43f1635dfd3dbef77724b1a97c2e7c2e3c2e52e Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Tue, 2 Jul 2019 12:23:51 +0930 Subject: [PATCH 08/74] Add new functionality to generate ora summary report --- lms/djangoapps/instructor/tests/test_api.py | 22 ++++++++++ lms/djangoapps/instructor/views/api.py | 18 ++++++++ lms/djangoapps/instructor/views/api_urls.py | 1 + .../instructor/views/instructor_dashboard.py | 1 + lms/djangoapps/instructor_task/api.py | 13 ++++++ lms/djangoapps/instructor_task/tasks.py | 12 ++++++ .../instructor_task/tasks_helper/misc.py | 36 ++++++++++++++-- .../instructor_task/tests/test_tasks.py | 33 +++++++++++++- .../tests/test_tasks_helper.py | 43 ++++++++++++++++--- .../instructor_dashboard_2/data_download.html | 1 + 10 files changed, 167 insertions(+), 13 deletions(-) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 7123b9758e..05832f8ebd 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -154,6 +154,7 @@ INSTRUCTOR_POST_ENDPOINTS = { 'change_due_date', 'export_ora2_data', 'export_ora2_submission_files', + 'export_ora2_summary', 'get_grading_config', 'get_problem_responses', 'get_proctored_exam_results', @@ -426,6 +427,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest ('get_problem_responses', {}), ('export_ora2_data', {}), ('export_ora2_submission_files', {}), + ('export_ora2_summary', {}), ('rescore_problem', {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), ('override_problem_score', @@ -2853,6 +2855,26 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.assertContains(response, already_running_status, status_code=400) + def test_get_ora2_summary_responses_success(self): + url = reverse('export_ora2_summary', kwargs={'course_id': str(self.course.id)}) + + with patch('lms.djangoapps.instructor_task.api.submit_export_ora2_summary') as mock_submit_ora2_task: + mock_submit_ora2_task.return_value = True + response = self.client.post(url, {}) + success_status = "The ORA summary report is being created." + self.assertContains(response, success_status) + + def test_get_ora2_summary_responses_already_running(self): + url = reverse('export_ora2_summary', kwargs={'course_id': str(self.course.id)}) + task_type = 'export_ora2_summary' + already_running_status = generate_already_running_error_message(task_type) + + with patch('lms.djangoapps.instructor_task.api.submit_export_ora2_summary') as mock_submit_ora2_task: + mock_submit_ora2_task.side_effect = AlreadyRunningError(already_running_status) + response = self.client.post(url, {}) + + self.assertContains(response, already_running_status, status_code=400) + def test_get_student_progress_url(self): """ Test that progress_url is in the successful response. """ url = reverse('get_student_progress_url', kwargs={'course_id': str(self.course.id)}) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 0213c06cfc..2ee85149ad 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -2028,6 +2028,24 @@ def export_ora2_data(request, course_id): return JsonResponse({"status": success_status}) +@transaction.non_atomic_requests +@require_POST +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_course_permission(permissions.CAN_RESEARCH) +@common_exceptions_400 +def export_ora2_summary(request, course_id): + """ + Pushes a Celery task which will aggregate a summary students' progress in ora2 tasks for a course into a .csv + """ + course_key = CourseKey.from_string(course_id) + report_type = _('ORA summary') + task_api.submit_export_ora2_summary(request, course_key) + success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) + + return JsonResponse({"status": success_status}) + + @transaction.non_atomic_requests @require_POST @ensure_csrf_cookie diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index b7cf4dfa10..8fc74f80df 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -53,6 +53,7 @@ urlpatterns = [ # Reports.. url(r'^get_course_survey_results$', api.get_course_survey_results, name='get_course_survey_results'), url(r'^export_ora2_data', api.export_ora2_data, name='export_ora2_data'), + url(r'^export_ora2_summary', api.export_ora2_summary, name='export_ora2_summary'), url(r'^export_ora2_submission_files', api.export_ora2_submission_files, name='export_ora2_submission_files'), diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 9ee10ab442..3401724aa3 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -633,6 +633,7 @@ def _section_data_download(course, access): 'export_ora2_submission_files_url': reverse( 'export_ora2_submission_files', kwargs={'course_id': str(course_key)} ), + 'export_ora2_summary_url': reverse('export_ora2_summary', kwargs={'course_id': str(course_key)}), } if not access.get('data_researcher'): section_data['is_hidden'] = True diff --git a/lms/djangoapps/instructor_task/api.py b/lms/djangoapps/instructor_task/api.py index 8870e25a34..2f2a963590 100644 --- a/lms/djangoapps/instructor_task/api.py +++ b/lms/djangoapps/instructor_task/api.py @@ -36,6 +36,7 @@ from lms.djangoapps.instructor_task.tasks import ( delete_problem_state, export_ora2_data, export_ora2_submission_files, + export_ora2_summary, generate_certificates, override_problem_score, proctored_exam_results_csv, @@ -463,6 +464,18 @@ def submit_export_ora2_submission_files(request, course_key): return submit_task(request, task_type, task_class, course_key, task_input, task_key) +def submit_export_ora2_summary(request, course_key): + """ + AlreadyRunningError is raised if an ora2 report is already being generated. + """ + task_type = 'export_ora2_summary' + task_class = export_ora2_summary + task_input = {} + task_key = '' + + return submit_task(request, task_type, task_class, course_key, task_input, task_key) + + def generate_certificates_for_students(request, course_key, student_set=None, specific_student_id=None): """ Submits a task to generate certificates for given students enrolled in the course. diff --git a/lms/djangoapps/instructor_task/tasks.py b/lms/djangoapps/instructor_task/tasks.py index 085684511d..93b2ee5f9c 100644 --- a/lms/djangoapps/instructor_task/tasks.py +++ b/lms/djangoapps/instructor_task/tasks.py @@ -38,6 +38,7 @@ from lms.djangoapps.instructor_task.tasks_helper.misc import ( upload_course_survey_report, upload_ora2_data, upload_ora2_submission_files, + upload_ora2_summary, upload_proctored_exam_results_report ) from lms.djangoapps.instructor_task.tasks_helper.module_state import ( @@ -316,3 +317,14 @@ def export_ora2_submission_files(entry_id, xmodule_instance_args): action_name = ugettext_noop('compressed') task_fn = partial(upload_ora2_submission_files, xmodule_instance_args) return run_main_task(entry_id, task_fn, action_name) + + +@shared_task(base=BaseInstructorTask) +@set_code_owner_attribute +def export_ora2_summary(entry_id, xmodule_instance_args): + """ + Generate a CSV of ora2/student summaries and push it to S3. + """ + action_name = ugettext_noop('generated') + task_fn = partial(upload_ora2_summary, xmodule_instance_args) + return run_main_task(entry_id, task_fn, action_name) diff --git a/lms/djangoapps/instructor_task/tasks_helper/misc.py b/lms/djangoapps/instructor_task/tasks_helper/misc.py index ad124b10d3..f9b0619239 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/misc.py +++ b/lms/djangoapps/instructor_task/tasks_helper/misc.py @@ -275,6 +275,32 @@ def upload_ora2_data( Collect ora2 responses and upload them to S3 as a CSV """ + return _upload_ora2_data_common( + _xmodule_instance_args, _entry_id, course_id, _task_input, action_name, + 'data', OraAggregateData.collect_ora2_data + ) + + +def upload_ora2_summary( + _xmodule_instance_args, _entry_id, course_id, _task_input, action_name +): + """ + Collect ora2/student summaries and upload them to file storage as a CSV + """ + + return _upload_ora2_data_common( + _xmodule_instance_args, _entry_id, course_id, _task_input, action_name, + 'summary', OraAggregateData.collect_ora2_summary + ) + + +def _upload_ora2_data_common( + _xmodule_instance_args, _entry_id, course_id, _task_input, action_name, + report_name, csv_gen_func +): + """ + Common code for uploading data or summary csv report. + """ start_date = datetime.now(UTC) start_time = time() @@ -304,8 +330,10 @@ def upload_ora2_data( task_progress.update_task_state(extra_meta=curr_step) try: - header, datarows = OraAggregateData.collect_ora2_data(course_id) - rows = [header] + [row for row in datarows] # lint-amnesty, pylint: disable=unnecessary-comprehension + header, datarows = csv_gen_func(course_id) + rows = [header] + for row in datarows: + rows.append(row) # Update progress to failed regardless of error type except Exception: # pylint: disable=broad-except TASK_LOG.exception('Failed to get ORA data.') @@ -326,9 +354,9 @@ def upload_ora2_data( ) task_progress.update_task_state(extra_meta=curr_step) - upload_csv_to_report_store(rows, 'ORA_data', course_id, start_date) + upload_csv_to_report_store(rows, 'ORA_{}'.format(report_name), course_id, start_date) - curr_step = {'step': 'Finalizing ORA data report'} + curr_step = {'step': 'Finalizing ORA {} report'.format(report_name)} task_progress.update_task_state(extra_meta=curr_step) TASK_LOG.info('%s, Task type: %s, Upload complete.', task_info_string, action_name) diff --git a/lms/djangoapps/instructor_task/tests/test_tasks.py b/lms/djangoapps/instructor_task/tests/test_tasks.py index 0d8c4d536c..dec7e0c84b 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks.py @@ -25,13 +25,12 @@ from lms.djangoapps.instructor_task.tasks import ( delete_problem_state, export_ora2_data, export_ora2_submission_files, + export_ora2_summary, generate_certificates, override_problem_score, rescore_problem, reset_problem_attempts ) -from lms.djangoapps.instructor_task.tasks_helper.misc import \ - upload_ora2_data # lint-amnesty, pylint: disable=unused-import from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory from lms.djangoapps.instructor_task.tests.test_base import InstructorTaskModuleTestCase from xmodule.modulestore.exceptions import ItemNotFoundError @@ -715,3 +714,33 @@ class TestOra2ExportSubmissionFilesInstructorTask(TestInstructorTasks): assert args[0] == task_entry.id assert callable(args[1]) assert args[2] == action_name + + +class TestOra2SummaryInstructorTask(TestInstructorTasks): + """Tests instructor task that fetches ora2 response summary.""" + + def test_ora2_missing_current_task(self): + self._test_missing_current_task(export_ora2_summary) + + def test_ora2_with_failure(self): + self._test_run_with_failure(export_ora2_summary, 'We expected this to fail') + + def test_ora2_with_long_error_msg(self): + self._test_run_with_long_error_msg(export_ora2_summary) + + def test_ora2_with_short_error_msg(self): + self._test_run_with_short_error_msg(export_ora2_summary) + + def test_ora2_runs_task(self): + task_entry = self._create_input_entry() + task_xmodule_args = self._get_xmodule_instance_args() + + with patch('lms.djangoapps.instructor_task.tasks.run_main_task') as mock_main_task: + export_ora2_summary(task_entry.id, task_xmodule_args) + action_name = ugettext_noop('generated') + + assert mock_main_task.call_count == 1 + args = mock_main_task.call_args[0] + assert args[0] == task_entry.id + assert callable(args[1]) + assert args[2] == action_name diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 2a6c26fa57..dcec0f3fb1 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -50,7 +50,8 @@ from lms.djangoapps.instructor_task.tasks_helper.misc import ( cohort_students_and_upload, upload_course_survey_report, upload_ora2_data, - upload_ora2_submission_files + upload_ora2_submission_files, + upload_ora2_summary ) from lms.djangoapps.instructor_task.tests.test_base import ( InstructorTaskCourseTestCase, @@ -2537,6 +2538,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): ] +@ddt.ddt class TestInstructorOra2Report(SharedModuleStoreTestCase): """ Tests that ORA2 response report generation works. @@ -2557,17 +2559,20 @@ class TestInstructorOra2Report(SharedModuleStoreTestCase): if os.path.exists(settings.GRADES_DOWNLOAD['ROOT_PATH']): shutil.rmtree(settings.GRADES_DOWNLOAD['ROOT_PATH']) - def test_report_fails_if_error(self): - with patch( - 'lms.djangoapps.instructor_task.tasks_helper.misc.OraAggregateData.collect_ora2_data' - ) as mock_collect_data: + @ddt.data( + ('lms.djangoapps.instructor_task.tasks_helper.misc.OraAggregateData.collect_ora2_data', upload_ora2_data), + ('lms.djangoapps.instructor_task.tasks_helper.misc.OraAggregateData.collect_ora2_summary', upload_ora2_summary), + ) + @ddt.unpack + def test_report_fails_if_error(self, data_collector_module, upload_func): + with patch(data_collector_module) as mock_collect_data: mock_collect_data.side_effect = KeyError with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task') as mock_current_task: mock_current_task.return_value = self.current_task - response = upload_ora2_data(None, None, self.course.id, None, 'generated') - assert response == UPDATE_STATUS_FAILED + response = upload_func(None, None, self.course.id, None, 'generated') + self.assertEqual(response, UPDATE_STATUS_FAILED) def test_report_stores_results(self): with ExitStack() as stack: @@ -2628,6 +2633,30 @@ class TestInstructorOra2AttachmentsExport(SharedModuleStoreTestCase): response = upload_ora2_submission_files(None, None, self.course.id, None, 'compressed') assert response == UPDATE_STATUS_FAILED + def test_summary_report_stores_results(self): + with freeze_time('2001-01-01 00:00:00'): + test_header = ['field1', 'field2'] + test_rows = [['row1_field1', 'row1_field2'], ['row2_field1', 'row2_field2']] + + with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task') as mock_current_task: + mock_current_task.return_value = self.current_task + + with patch( + 'lms.djangoapps.instructor_task.tasks_helper.misc.OraAggregateData.collect_ora2_summary' + ) as mock_collect_summary: + mock_collect_summary.return_value = (test_header, test_rows) + with patch( + 'lms.djangoapps.instructor_task.models.DjangoStorageReportStore.store_rows' + ) as mock_store_rows: + return_val = upload_ora2_summary(None, None, self.course.id, None, 'generated') + + timestamp_str = datetime.now(UTC).strftime('%Y-%m-%d-%H%M') + course_id_string = quote(str(self.course.id).replace('/', '_')) + filename = '{}_ORA_summary_{}.csv'.format(course_id_string, timestamp_str) + + self.assertEqual(return_val, UPDATE_STATUS_SUCCEEDED) + mock_store_rows.assert_called_once_with(self.course.id, filename, [test_header] + test_rows) + def test_export_fails_if_error_on_create_zip_step(self): with ExitStack() as stack: mock_current_task = stack.enter_context( diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download.html b/lms/templates/instructor/instructor_dashboard_2/data_download.html index 4170763439..fefcfef668 100644 --- a/lms/templates/instructor/instructor_dashboard_2/data_download.html +++ b/lms/templates/instructor/instructor_dashboard_2/data_download.html @@ -95,6 +95,7 @@ from openedx.core.djangolib.markup import HTML, Text +

${_("Click to generate a ZIP file that contains all submission texts and attachments.")}

From 809c57f0890c9587b949ddec93bb1a034e929a1e Mon Sep 17 00:00:00 2001 From: adeelehsan Date: Fri, 19 Feb 2021 18:30:31 +0500 Subject: [PATCH 09/74] authn mfe url added in recover account command VAN-319 --- .../management/commands/recover_account.py | 9 ++++-- .../management/tests/test_recover_account.py | 29 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/student/management/commands/recover_account.py b/common/djangoapps/student/management/commands/recover_account.py index a2b11e7784..55cb3ea6d8 100644 --- a/common/djangoapps/student/management/commands/recover_account.py +++ b/common/djangoapps/student/management/commands/recover_account.py @@ -18,6 +18,7 @@ from edx_ace import ace from edx_ace.recipient import Recipient from common.djangoapps.student.models import AccountRecoveryConfiguration +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.ace_common.template_context import get_base_template_context from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -104,12 +105,16 @@ class Command(BaseCommand): """ message_context = get_base_template_context(site) email = user.email + if should_redirect_to_authn_microfrontend(): + site_url = settings.AUTHN_MICROFRONTEND_URL + else: + site_url = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) message_context.update({ 'email': email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), - 'reset_link': '{protocol}://{site}{link}?track=pwreset'.format( + 'reset_link': '{protocol}://{site_url}{link}?track=pwreset'.format( protocol='http', - site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), + site_url=site_url, link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), diff --git a/common/djangoapps/student/management/tests/test_recover_account.py b/common/djangoapps/student/management/tests/test_recover_account.py index 15a0770fd2..cb7333f8fe 100644 --- a/common/djangoapps/student/management/tests/test_recover_account.py +++ b/common/djangoapps/student/management/tests/test_recover_account.py @@ -7,17 +7,24 @@ import pytest import six from django.core import mail +from django.conf import settings from django.contrib.auth import get_user_model from django.core.management import call_command, CommandError from django.core.files.uploadedfile import SimpleUploadedFile -from django.test import TestCase, RequestFactory +from django.test import TestCase, RequestFactory, override_settings + +from edx_toggles.toggles.testutils import override_waffle_flag from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory - from common.djangoapps.student.models import AccountRecoveryConfiguration +from openedx.core.djangolib.testing.utils import skip_unless_lms +from openedx.core.djangoapps.user_authn.toggles import REDIRECT_TO_AUTHN_MICROFRONTEND + LOGGER_NAME = 'common.djangoapps.student.management.commands.recover_account' +FEATURES_WITH_AUTHN_MFE_ENABLED = settings.FEATURES.copy() +FEATURES_WITH_AUTHN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True class RecoverAccountTests(TestCase): @@ -65,6 +72,24 @@ class RecoverAccountTests(TestCase): # try to login with previous password assert not self.client.login(username=self.user.username, password='password') + @override_settings(FEATURES=FEATURES_WITH_AUTHN_MFE_ENABLED) + @override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True) + @skip_unless_lms + def test_authn_mfe_url_in_reset_link(self): + """ + send password reset link to learner with authn mfe. + :return: + """ + + with NamedTemporaryFile() as csv: + csv = self._write_test_csv(csv, lines=['amy,amy@edx.com,amy@newemail.com\n']) + call_command("recover_account", "--csv_file_path={}".format(csv.name)) + + assert len(mail.outbox) == 1 + + authn_mfe_url = re.findall(settings.AUTHN_MICROFRONTEND_URL, mail.outbox[0].body)[0] + self.assertEqual(authn_mfe_url, settings.AUTHN_MICROFRONTEND_URL) + def test_file_not_found_error(self): """ Test command error raised when csv path is invalid From 8bef10208af04b00479f6fb18f45bf3d1599033d Mon Sep 17 00:00:00 2001 From: Awais Qureshi Date: Tue, 23 Feb 2021 14:55:02 +0500 Subject: [PATCH 10/74] BOM-2375 Run Pyupgrade on student folder. --- .../student/migrations/0001_initial.py | 35 +++++++++---------- .../0001_squashed_0031_auto_20200317_1122.py | 20 +++++------ .../migrations/0002_auto_20151208_1034.py | 7 ++-- .../migrations/0003_auto_20160516_0938.py | 5 +-- .../migrations/0004_auto_20160531_1422.py | 3 -- .../migrations/0005_auto_20160531_1653.py | 3 -- .../0006_logoutviewconfiguration.py | 3 -- .../0007_registrationcookieconfiguration.py | 3 -- .../migrations/0008_auto_20161117_1209.py | 5 +-- .../migrations/0009_auto_20170111_0422.py | 3 -- .../migrations/0010_auto_20170207_0458.py | 3 -- .../0011_course_key_field_to_foreign_key.py | 5 +-- .../student/migrations/0012_sociallink.py | 3 -- ...13_delete_historical_enrollment_records.py | 3 -- .../0014_courseenrollmentallowed_user.py | 3 -- .../0015_manualenrollmentaudit_add_role.py | 3 -- ...senrollment_course_on_delete_do_nothing.py | 1 - .../migrations/0017_accountrecovery.py | 1 - .../0018_remove_password_history.py | 1 - .../migrations/0019_auto_20181221_0540.py | 3 +- .../migrations/0020_auto_20190227_2019.py | 1 - .../0021_historicalcourseenrollment.py | 3 +- .../0022_indexing_in_courseenrollment.py | 1 - .../0023_bulkunenrollconfiguration.py | 7 ++-- .../migrations/0024_fbeenrollmentexclusion.py | 3 +- .../migrations/0025_auto_20191101_1846.py | 3 +- .../migrations/0026_allowedauthuser.py | 3 +- ..._courseenrollment_mode_callable_default.py | 1 - .../0028_historicalmanualenrollmentaudit.py | 1 - .../migrations/0029_add_data_researcher.py | 2 +- .../0030_userprofile_phone_number.py | 1 - .../migrations/0031_auto_20200317_1122.py | 1 - .../0032_removed_logout_view_configuration.py | 1 - .../0034_courseenrollmentcelebration.py | 2 +- .../0035_bulkchangeenrollmentconfiguration.py | 4 +-- .../0036_userpasswordtogglehistory.py | 4 +-- .../migrations/0039_anon_id_context.py | 2 +- .../migrations/0040_usercelebration.py | 4 +-- 38 files changed, 47 insertions(+), 110 deletions(-) diff --git a/common/djangoapps/student/migrations/0001_initial.py b/common/djangoapps/student/migrations/0001_initial.py index fbb2de5736..6cabce5cdd 100644 --- a/common/djangoapps/student/migrations/0001_initial.py +++ b/common/djangoapps/student/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion import django.utils.timezone import django_countries.fields @@ -75,7 +72,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), - ('recent_enrollment_time_delta', models.PositiveIntegerField(default=0, help_text=u"The number of seconds in which a new enrollment is considered 'recent'. Used to display notifications.")), + ('recent_enrollment_time_delta', models.PositiveIntegerField(default=0, help_text="The number of seconds in which a new enrollment is considered 'recent'. Used to display notifications.")), ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), ], options={ @@ -141,9 +138,9 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), - ('company_identifier', models.TextField(help_text=u'The company identifier for the LinkedIn Add-to-Profile button e.g 0_0dPSPyS070e0HsE9HNz_13_d11_')), - ('dashboard_tracking_code', models.TextField(default=u'', blank=True)), - ('trk_partner_name', models.CharField(default=u'', help_text=u"Short identifier for the LinkedIn partner used in the tracking code. (Example: 'edx') If no value is provided, tracking codes will not be sent to LinkedIn.", max_length=10, blank=True)), + ('company_identifier', models.TextField(help_text='The company identifier for the LinkedIn Add-to-Profile button e.g 0_0dPSPyS070e0HsE9HNz_13_d11_')), + ('dashboard_tracking_code', models.TextField(default='', blank=True)), + ('trk_partner_name', models.CharField(default='', help_text="Short identifier for the LinkedIn partner used in the tracking code. (Example: 'edx') If no value is provided, tracking codes will not be sent to LinkedIn.", max_length=10, blank=True)), ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), ], options={ @@ -166,7 +163,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('enrolled_email', models.CharField(max_length=255, db_index=True)), ('time_stamp', models.DateTimeField(auto_now_add=True, null=True)), - ('state_transition', models.CharField(max_length=255, choices=[(u'from unenrolled to allowed to enroll', u'from unenrolled to allowed to enroll'), (u'from allowed to enroll to enrolled', u'from allowed to enroll to enrolled'), (u'from enrolled to enrolled', u'from enrolled to enrolled'), (u'from enrolled to unenrolled', u'from enrolled to unenrolled'), (u'from unenrolled to enrolled', u'from unenrolled to enrolled'), (u'from allowed to enroll to enrolled', u'from allowed to enroll to enrolled'), (u'from unenrolled to unenrolled', u'from unenrolled to unenrolled'), (u'N/A', u'N/A')])), + ('state_transition', models.CharField(max_length=255, choices=[('from unenrolled to allowed to enroll', 'from unenrolled to allowed to enroll'), ('from allowed to enroll to enrolled', 'from allowed to enroll to enrolled'), ('from enrolled to enrolled', 'from enrolled to enrolled'), ('from enrolled to unenrolled', 'from enrolled to unenrolled'), ('from unenrolled to enrolled', 'from unenrolled to enrolled'), ('from allowed to enroll to enrolled', 'from allowed to enroll to enrolled'), ('from unenrolled to unenrolled', 'from unenrolled to unenrolled'), ('N/A', 'N/A')])), ('reason', models.TextField(null=True)), ('enrolled_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), ('enrollment', models.ForeignKey(to='student.CourseEnrollment', null=True, on_delete=models.CASCADE)), @@ -186,7 +183,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('new_email', models.CharField(db_index=True, max_length=255, blank=True)), - ('activation_key', models.CharField(unique=True, max_length=32, verbose_name=u'activation key', db_index=True)), + ('activation_key', models.CharField(unique=True, max_length=32, verbose_name='activation key', db_index=True)), ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), @@ -203,7 +200,7 @@ class Migration(migrations.Migration): name='Registration', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('activation_key', models.CharField(unique=True, max_length=32, verbose_name=u'activation key', db_index=True)), + ('activation_key', models.CharField(unique=True, max_length=32, verbose_name='activation key', db_index=True)), ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ @@ -216,12 +213,12 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(db_index=True, max_length=255, blank=True)), ('meta', models.TextField(blank=True)), - ('courseware', models.CharField(default=u'course.xml', max_length=255, blank=True)), + ('courseware', models.CharField(default='course.xml', max_length=255, blank=True)), ('language', models.CharField(db_index=True, max_length=255, blank=True)), ('location', models.CharField(db_index=True, max_length=255, blank=True)), ('year_of_birth', models.IntegerField(db_index=True, null=True, blank=True)), - ('gender', models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[(u'm', u'Male'), (u'f', u'Female'), (u'o', u'Other/Prefer Not to Say')])), - ('level_of_education', models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[(u'p', u'Doctorate'), (u'm', u"Master's or professional degree"), (u'b', u"Bachelor's degree"), (u'a', u'Associate degree'), (u'hs', u'Secondary/high school'), (u'jhs', u'Junior secondary/junior high/middle school'), (u'el', u'Elementary/primary school'), (u'none', u'No Formal Education'), (u'other', u'Other Education')])), + ('gender', models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[('m', 'Male'), ('f', 'Female'), ('o', 'Other/Prefer Not to Say')])), + ('level_of_education', models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[('p', 'Doctorate'), ('m', "Master's or professional degree"), ('b', "Bachelor's degree"), ('a', 'Associate degree'), ('hs', 'Secondary/high school'), ('jhs', 'Junior secondary/junior high/middle school'), ('el', 'Elementary/primary school'), ('none', 'No Formal Education'), ('other', 'Other Education')])), ('mailing_address', models.TextField(null=True, blank=True)), ('city', models.TextField(null=True, blank=True)), ('country', django_countries.fields.CountryField(blank=True, max_length=2, null=True)), @@ -247,7 +244,7 @@ class Migration(migrations.Migration): name='UserStanding', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('account_status', models.CharField(blank=True, max_length=31, choices=[(u'disabled', u'Account Disabled'), (u'enabled', u'Account Enabled')])), + ('account_status', models.CharField(blank=True, max_length=31, choices=[('disabled', 'Account Disabled'), ('enabled', 'Account Enabled')])), ('standing_last_changed_at', models.DateTimeField(auto_now=True)), ('changed_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, blank=True, on_delete=models.CASCADE)), ('user', models.OneToOneField(related_name='standing', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), @@ -269,22 +266,22 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='courseenrollmentallowed', - unique_together=set([('email', 'course_id')]), + unique_together={('email', 'course_id')}, ), migrations.AlterUniqueTogether( name='languageproficiency', - unique_together=set([('code', 'user_profile')]), + unique_together={('code', 'user_profile')}, ), migrations.AlterUniqueTogether( name='entranceexamconfiguration', - unique_together=set([('user', 'course_id')]), + unique_together={('user', 'course_id')}, ), migrations.AlterUniqueTogether( name='courseenrollment', - unique_together=set([('user', 'course_id')]), + unique_together={('user', 'course_id')}, ), migrations.AlterUniqueTogether( name='courseaccessrole', - unique_together=set([('user', 'org', 'course_id', 'role')]), + unique_together={('user', 'org', 'course_id', 'role')}, ), ] diff --git a/common/djangoapps/student/migrations/0001_squashed_0031_auto_20200317_1122.py b/common/djangoapps/student/migrations/0001_squashed_0031_auto_20200317_1122.py index c54433a088..400c74e137 100644 --- a/common/djangoapps/student/migrations/0001_squashed_0031_auto_20200317_1122.py +++ b/common/djangoapps/student/migrations/0001_squashed_0031_auto_20200317_1122.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.29 on 2020-04-13 17:34 @@ -11,14 +10,13 @@ import opaque_keys.edx.django.models import simple_history.models from django.conf import settings from django.db import migrations, models -from lms.djangoapps.experiments.models import ExperimentData -from common.djangoapps.student.models import CourseEnrollment, FBEEnrollmentExclusion import openedx.core.djangolib.model_mixins from common.djangoapps.course_modes import models as course_modes_models +from common.djangoapps.student.models import CourseEnrollment, FBEEnrollmentExclusion +from lms.djangoapps.experiments.models import ExperimentData from openedx.features.course_duration_limits.config import EXPERIMENT_DATA_HOLDBACK_KEY, EXPERIMENT_ID - # These data migrations do not require changes when building from scratch. # student.migrations.0029_add_data_researcher # student.migrations.0011_course_key_field_to_foreign_key @@ -287,15 +285,15 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='courseenrollmentallowed', - unique_together=set([('email', 'course_id')]), + unique_together={('email', 'course_id')}, ), migrations.AlterUniqueTogether( name='languageproficiency', - unique_together=set([('code', 'user_profile')]), + unique_together={('code', 'user_profile')}, ), migrations.AlterUniqueTogether( name='entranceexamconfiguration', - unique_together=set([('user', 'course_id')]), + unique_together={('user', 'course_id')}, ), migrations.AlterField( model_name='courseenrollment', @@ -304,11 +302,11 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='courseenrollment', - unique_together=set([('user', 'course_id')]), + unique_together={('user', 'course_id')}, ), migrations.AlterUniqueTogether( name='courseaccessrole', - unique_together=set([('user', 'org', 'course_id', 'role')]), + unique_together={('user', 'org', 'course_id', 'role')}, ), migrations.CreateModel( name='UserAttribute', @@ -323,7 +321,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userattribute', - unique_together=set([('user', 'name')]), + unique_together={('user', 'name')}, ), migrations.AlterField( model_name='userattribute', @@ -364,7 +362,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='courseenrollment', - unique_together=set([('user', 'course')]), + unique_together={('user', 'course')}, ), migrations.CreateModel( name='SocialLink', diff --git a/common/djangoapps/student/migrations/0002_auto_20151208_1034.py b/common/djangoapps/student/migrations/0002_auto_20151208_1034.py index 7c0a9519f4..8ea695fb1e 100644 --- a/common/djangoapps/student/migrations/0002_auto_20151208_1034.py +++ b/common/djangoapps/student/migrations/0002_auto_20151208_1034.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -14,11 +11,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='courseenrollment', name='mode', - field=models.CharField(default=u'audit', max_length=100), + field=models.CharField(default='audit', max_length=100), ), migrations.AlterField( model_name='historicalcourseenrollment', name='mode', - field=models.CharField(default=u'audit', max_length=100), + field=models.CharField(default='audit', max_length=100), ), ] diff --git a/common/djangoapps/student/migrations/0003_auto_20160516_0938.py b/common/djangoapps/student/migrations/0003_auto_20160516_0938.py index 73b77a7261..cc97f760e9 100644 --- a/common/djangoapps/student/migrations/0003_auto_20160516_0938.py +++ b/common/djangoapps/student/migrations/0003_auto_20160516_0938.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.utils.timezone import model_utils.fields from django.conf import settings @@ -28,6 +25,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userattribute', - unique_together=set([('user', 'name')]), + unique_together={('user', 'name')}, ), ] diff --git a/common/djangoapps/student/migrations/0004_auto_20160531_1422.py b/common/djangoapps/student/migrations/0004_auto_20160531_1422.py index fc43adc288..0e253f80f2 100644 --- a/common/djangoapps/student/migrations/0004_auto_20160531_1422.py +++ b/common/djangoapps/student/migrations/0004_auto_20160531_1422.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0005_auto_20160531_1653.py b/common/djangoapps/student/migrations/0005_auto_20160531_1653.py index afeea332db..08ac8b7b75 100644 --- a/common/djangoapps/student/migrations/0005_auto_20160531_1653.py +++ b/common/djangoapps/student/migrations/0005_auto_20160531_1653.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0006_logoutviewconfiguration.py b/common/djangoapps/student/migrations/0006_logoutviewconfiguration.py index e124c59d38..1752c6c393 100644 --- a/common/djangoapps/student/migrations/0006_logoutviewconfiguration.py +++ b/common/djangoapps/student/migrations/0006_logoutviewconfiguration.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion from django.conf import settings from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0007_registrationcookieconfiguration.py b/common/djangoapps/student/migrations/0007_registrationcookieconfiguration.py index 7b491063c2..37fbb0b811 100644 --- a/common/djangoapps/student/migrations/0007_registrationcookieconfiguration.py +++ b/common/djangoapps/student/migrations/0007_registrationcookieconfiguration.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion from django.conf import settings from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0008_auto_20161117_1209.py b/common/djangoapps/student/migrations/0008_auto_20161117_1209.py index 37d0cac36c..591dc99229 100644 --- a/common/djangoapps/student/migrations/0008_auto_20161117_1209.py +++ b/common/djangoapps/student/migrations/0008_auto_20161117_1209.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -14,6 +11,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userprofile', name='level_of_education', - field=models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[(u'p', u'Doctorate'), (u'm', u"Master's or professional degree"), (u'b', u"Bachelor's degree"), (u'a', u'Associate degree'), (u'hs', u'Secondary/high school'), (u'jhs', u'Junior secondary/junior high/middle school'), (u'el', u'Elementary/primary school'), (u'none', u'No formal education'), (u'other', u'Other education')]), + field=models.CharField(blank=True, max_length=6, null=True, db_index=True, choices=[('p', 'Doctorate'), ('m', "Master's or professional degree"), ('b', "Bachelor's degree"), ('a', 'Associate degree'), ('hs', 'Secondary/high school'), ('jhs', 'Junior secondary/junior high/middle school'), ('el', 'Elementary/primary school'), ('none', 'No formal education'), ('other', 'Other education')]), ), ] diff --git a/common/djangoapps/student/migrations/0009_auto_20170111_0422.py b/common/djangoapps/student/migrations/0009_auto_20170111_0422.py index a093a4a108..412480e94f 100644 --- a/common/djangoapps/student/migrations/0009_auto_20170111_0422.py +++ b/common/djangoapps/student/migrations/0009_auto_20170111_0422.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0010_auto_20170207_0458.py b/common/djangoapps/student/migrations/0010_auto_20170207_0458.py index 7e67311af7..93f0038f13 100644 --- a/common/djangoapps/student/migrations/0010_auto_20170207_0458.py +++ b/common/djangoapps/student/migrations/0010_auto_20170207_0458.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0011_course_key_field_to_foreign_key.py b/common/djangoapps/student/migrations/0011_course_key_field_to_foreign_key.py index 7c29455628..00cd4eadf0 100644 --- a/common/djangoapps/student/migrations/0011_course_key_field_to_foreign_key.py +++ b/common/djangoapps/student/migrations/0011_course_key_field_to_foreign_key.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion from django.db import migrations, models from opaque_keys.edx.django.models import CourseKeyField @@ -71,6 +68,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='courseenrollment', - unique_together=set([('user', 'course')]), + unique_together={('user', 'course')}, ), ] diff --git a/common/djangoapps/student/migrations/0012_sociallink.py b/common/djangoapps/student/migrations/0012_sociallink.py index 73e929d130..4337d11e94 100644 --- a/common/djangoapps/student/migrations/0012_sociallink.py +++ b/common/djangoapps/student/migrations/0012_sociallink.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0013_delete_historical_enrollment_records.py b/common/djangoapps/student/migrations/0013_delete_historical_enrollment_records.py index 9ad7793789..d4e924b721 100644 --- a/common/djangoapps/student/migrations/0013_delete_historical_enrollment_records.py +++ b/common/djangoapps/student/migrations/0013_delete_historical_enrollment_records.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0014_courseenrollmentallowed_user.py b/common/djangoapps/student/migrations/0014_courseenrollmentallowed_user.py index 261d810127..1f8b315ef4 100644 --- a/common/djangoapps/student/migrations/0014_courseenrollmentallowed_user.py +++ b/common/djangoapps/student/migrations/0014_courseenrollmentallowed_user.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.conf import settings from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0015_manualenrollmentaudit_add_role.py b/common/djangoapps/student/migrations/0015_manualenrollmentaudit_add_role.py index 7d2c2c1dfc..a0d523c448 100644 --- a/common/djangoapps/student/migrations/0015_manualenrollmentaudit_add_role.py +++ b/common/djangoapps/student/migrations/0015_manualenrollmentaudit_add_role.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/common/djangoapps/student/migrations/0016_coursenrollment_course_on_delete_do_nothing.py b/common/djangoapps/student/migrations/0016_coursenrollment_course_on_delete_do_nothing.py index b4dc388729..1d1c39b667 100644 --- a/common/djangoapps/student/migrations/0016_coursenrollment_course_on_delete_do_nothing.py +++ b/common/djangoapps/student/migrations/0016_coursenrollment_course_on_delete_do_nothing.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-27 01:44 diff --git a/common/djangoapps/student/migrations/0017_accountrecovery.py b/common/djangoapps/student/migrations/0017_accountrecovery.py index 8b505462cd..8347d04357 100644 --- a/common/djangoapps/student/migrations/0017_accountrecovery.py +++ b/common/djangoapps/student/migrations/0017_accountrecovery.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-12-10 12:15 diff --git a/common/djangoapps/student/migrations/0018_remove_password_history.py b/common/djangoapps/student/migrations/0018_remove_password_history.py index 9a232f6124..45e8af5a08 100644 --- a/common/djangoapps/student/migrations/0018_remove_password_history.py +++ b/common/djangoapps/student/migrations/0018_remove_password_history.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-12-19 14:30 diff --git a/common/djangoapps/student/migrations/0019_auto_20181221_0540.py b/common/djangoapps/student/migrations/0019_auto_20181221_0540.py index efe99de60d..4ee9c71e8a 100644 --- a/common/djangoapps/student/migrations/0019_auto_20181221_0540.py +++ b/common/djangoapps/student/migrations/0019_auto_20181221_0540.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-12-21 10:40 @@ -22,7 +21,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('new_secondary_email', models.CharField(blank=True, db_index=True, max_length=255)), - ('activation_key', models.CharField(db_index=True, max_length=32, unique=True, verbose_name=u'activation key')), + ('activation_key', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='activation key')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], bases=(openedx.core.djangolib.model_mixins.DeletableByUserValue, models.Model), diff --git a/common/djangoapps/student/migrations/0020_auto_20190227_2019.py b/common/djangoapps/student/migrations/0020_auto_20190227_2019.py index c670436c63..94fcb996ee 100644 --- a/common/djangoapps/student/migrations/0020_auto_20190227_2019.py +++ b/common/djangoapps/student/migrations/0020_auto_20190227_2019.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-02-27 20:19 diff --git a/common/djangoapps/student/migrations/0021_historicalcourseenrollment.py b/common/djangoapps/student/migrations/0021_historicalcourseenrollment.py index 6871558dc1..2c63f9cea3 100644 --- a/common/djangoapps/student/migrations/0021_historicalcourseenrollment.py +++ b/common/djangoapps/student/migrations/0021_historicalcourseenrollment.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-25 20:18 @@ -25,7 +24,7 @@ class Migration(migrations.Migration): ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), ('created', models.DateTimeField(blank=True, db_index=True, editable=False, null=True)), ('is_active', models.BooleanField(default=True)), - ('mode', models.CharField(default=u'audit', max_length=100)), + ('mode', models.CharField(default='audit', max_length=100)), ('history_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('history_date', models.DateTimeField()), ('history_change_reason', models.CharField(max_length=100, null=True)), diff --git a/common/djangoapps/student/migrations/0022_indexing_in_courseenrollment.py b/common/djangoapps/student/migrations/0022_indexing_in_courseenrollment.py index 5a17f586fd..634725e153 100644 --- a/common/djangoapps/student/migrations/0022_indexing_in_courseenrollment.py +++ b/common/djangoapps/student/migrations/0022_indexing_in_courseenrollment.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-06-24 19:01 diff --git a/common/djangoapps/student/migrations/0023_bulkunenrollconfiguration.py b/common/djangoapps/student/migrations/0023_bulkunenrollconfiguration.py index 476fa1b7aa..9ff534d159 100644 --- a/common/djangoapps/student/migrations/0023_bulkunenrollconfiguration.py +++ b/common/djangoapps/student/migrations/0023_bulkunenrollconfiguration.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.24 on 2019-09-19 19:51 -from django.conf import settings import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -22,7 +21,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), - ('csv_file', models.FileField(help_text='It expect that the data will be provided in a csv file format with first row being the header and columns will be as follows: user_id, username, email, course_id, is_verified, verification_date', upload_to=u'', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=[u'csv'])])), + ('csv_file', models.FileField(help_text='It expect that the data will be provided in a csv file format with first row being the header and columns will be as follows: user_id, username, email, course_id, is_verified, verification_date', upload_to='', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['csv'])])), ('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')), ], options={ diff --git a/common/djangoapps/student/migrations/0024_fbeenrollmentexclusion.py b/common/djangoapps/student/migrations/0024_fbeenrollmentexclusion.py index 8448dad173..72316d8e4a 100644 --- a/common/djangoapps/student/migrations/0024_fbeenrollmentexclusion.py +++ b/common/djangoapps/student/migrations/0024_fbeenrollmentexclusion.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.25 on 2019-11-01 15:56 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0025_auto_20191101_1846.py b/common/djangoapps/student/migrations/0025_auto_20191101_1846.py index 1bdfaee558..d3dfb8dd21 100644 --- a/common/djangoapps/student/migrations/0025_auto_20191101_1846.py +++ b/common/djangoapps/student/migrations/0025_auto_20191101_1846.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.25 on 2019-11-01 18:46 from django.db import migrations +from common.djangoapps.student.models import CourseEnrollment, FBEEnrollmentExclusion from lms.djangoapps.experiments.models import ExperimentData from openedx.features.course_duration_limits.config import EXPERIMENT_DATA_HOLDBACK_KEY, EXPERIMENT_ID -from common.djangoapps.student.models import CourseEnrollment, FBEEnrollmentExclusion def populate_fbeenrollmentexclusion(apps, schema_editor): diff --git a/common/djangoapps/student/migrations/0026_allowedauthuser.py b/common/djangoapps/student/migrations/0026_allowedauthuser.py index 4c88e0e424..7d08c1ab23 100644 --- a/common/djangoapps/student/migrations/0026_allowedauthuser.py +++ b/common/djangoapps/student/migrations/0026_allowedauthuser.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.26 on 2019-11-14 14:12 -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0027_courseenrollment_mode_callable_default.py b/common/djangoapps/student/migrations/0027_courseenrollment_mode_callable_default.py index aceb9b6acc..b83e4277d0 100644 --- a/common/djangoapps/student/migrations/0027_courseenrollment_mode_callable_default.py +++ b/common/djangoapps/student/migrations/0027_courseenrollment_mode_callable_default.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.22 on 2019-07-19 13:06 diff --git a/common/djangoapps/student/migrations/0028_historicalmanualenrollmentaudit.py b/common/djangoapps/student/migrations/0028_historicalmanualenrollmentaudit.py index abf8df2d53..9efaed2703 100644 --- a/common/djangoapps/student/migrations/0028_historicalmanualenrollmentaudit.py +++ b/common/djangoapps/student/migrations/0028_historicalmanualenrollmentaudit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.27 on 2019-12-27 20:44 diff --git a/common/djangoapps/student/migrations/0029_add_data_researcher.py b/common/djangoapps/student/migrations/0029_add_data_researcher.py index 90f9ff98ac..2f5f7fc87a 100644 --- a/common/djangoapps/student/migrations/0029_add_data_researcher.py +++ b/common/djangoapps/student/migrations/0029_add_data_researcher.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.27 on 2020-01-27 19:02 from django.db import migrations + from common.djangoapps.student.models import CourseAccessRole diff --git a/common/djangoapps/student/migrations/0030_userprofile_phone_number.py b/common/djangoapps/student/migrations/0030_userprofile_phone_number.py index 81732fc5f8..9b6662dfe2 100644 --- a/common/djangoapps/student/migrations/0030_userprofile_phone_number.py +++ b/common/djangoapps/student/migrations/0030_userprofile_phone_number.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.28 on 2020-02-18 18:36 diff --git a/common/djangoapps/student/migrations/0031_auto_20200317_1122.py b/common/djangoapps/student/migrations/0031_auto_20200317_1122.py index 4e9ba1866a..b09eb4aa2a 100644 --- a/common/djangoapps/student/migrations/0031_auto_20200317_1122.py +++ b/common/djangoapps/student/migrations/0031_auto_20200317_1122.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.29 on 2020-03-17 11:22 diff --git a/common/djangoapps/student/migrations/0032_removed_logout_view_configuration.py b/common/djangoapps/student/migrations/0032_removed_logout_view_configuration.py index 151968ead5..6c58e370df 100644 --- a/common/djangoapps/student/migrations/0032_removed_logout_view_configuration.py +++ b/common/djangoapps/student/migrations/0032_removed_logout_view_configuration.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.29 on 2020-03-25 14:28 diff --git a/common/djangoapps/student/migrations/0034_courseenrollmentcelebration.py b/common/djangoapps/student/migrations/0034_courseenrollmentcelebration.py index 7bf6f3c857..1b420df022 100644 --- a/common/djangoapps/student/migrations/0034_courseenrollmentcelebration.py +++ b/common/djangoapps/student/migrations/0034_courseenrollmentcelebration.py @@ -1,9 +1,9 @@ # Generated by Django 2.2.12 on 2020-06-08 15:12 -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0035_bulkchangeenrollmentconfiguration.py b/common/djangoapps/student/migrations/0035_bulkchangeenrollmentconfiguration.py index d7f2fac075..0f61b03b7b 100644 --- a/common/djangoapps/student/migrations/0035_bulkchangeenrollmentconfiguration.py +++ b/common/djangoapps/student/migrations/0035_bulkchangeenrollmentconfiguration.py @@ -1,9 +1,9 @@ # Generated by Django 2.2.14 on 2020-07-16 10:33 -from django.conf import settings import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py b/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py index 2bcef73f76..2a35dffd58 100644 --- a/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py +++ b/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py @@ -1,10 +1,10 @@ # Generated by Django 2.2.16 on 2020-10-15 10:19 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone import model_utils.fields +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0039_anon_id_context.py b/common/djangoapps/student/migrations/0039_anon_id_context.py index f241d3623c..a646c4e3e4 100644 --- a/common/djangoapps/student/migrations/0039_anon_id_context.py +++ b/common/djangoapps/student/migrations/0039_anon_id_context.py @@ -3,8 +3,8 @@ # # This migration does not produce any changes at the database level. -from django.db import migrations import opaque_keys.edx.django.models +from django.db import migrations class Migration(migrations.Migration): diff --git a/common/djangoapps/student/migrations/0040_usercelebration.py b/common/djangoapps/student/migrations/0040_usercelebration.py index aea99c39c8..3fc75618c2 100644 --- a/common/djangoapps/student/migrations/0040_usercelebration.py +++ b/common/djangoapps/student/migrations/0040_usercelebration.py @@ -1,10 +1,10 @@ # Generated by Django 2.2.18 on 2021-02-18 22:49 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone import model_utils.fields +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): From 42fc6aef035334524b8caaba064ac3cfb6756fe3 Mon Sep 17 00:00:00 2001 From: "M. Zulqarnain" Date: Tue, 23 Feb 2021 18:14:17 +0500 Subject: [PATCH 11/74] Upgrade django-ipware (#24827) --- common/djangoapps/course_modes/views.py | 4 ++-- common/djangoapps/student/views/management.py | 4 ++-- common/djangoapps/track/middleware.py | 4 ++-- common/djangoapps/track/views/__init__.py | 4 ++-- lms/djangoapps/courseware/views/views.py | 4 ++-- lms/djangoapps/verify_student/views.py | 4 ++-- openedx/core/djangoapps/embargo/api.py | 4 ++-- openedx/core/djangoapps/embargo/middleware.py | 4 ++-- openedx/core/djangoapps/geoinfo/middleware.py | 9 ++++----- .../djangoapps/geoinfo/tests/test_middleware.py | 14 -------------- openedx/core/djangoapps/util/ratelimit.py | 6 ++---- requirements/constraints.txt | 5 +---- requirements/edx/base.txt | 4 ++-- requirements/edx/development.txt | 4 ++-- requirements/edx/testing.txt | 4 ++-- 15 files changed, 29 insertions(+), 49 deletions(-) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 517d05afa6..389c5a59f7 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -21,7 +21,7 @@ from django.utils.translation import get_language, to_locale from django.utils.translation import ugettext as _ from django.views.generic.base import View from edx_django_utils.monitoring.utils import increment -from ipware.ip import get_ip +from ipware.ip import get_client_ip from opaque_keys.edx.keys import CourseKey from six import text_type @@ -90,7 +90,7 @@ class ChooseModeView(View): embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, - ip_address=get_ip(request), + ip_address=get_client_ip(request)[0], url=request.path ) if embargo_redirect: diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 0a62a604de..ea2ce14465 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -29,7 +29,7 @@ from edx_ace import ace from edx_ace.recipient import Recipient from edx_django_utils import monitoring as monitoring_utils from eventtracking import tracker -from ipware.ip import get_ip +from ipware.ip import get_client_ip # Note that this lives in LMS, so this dependency should be refactored. from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -340,7 +340,7 @@ def change_enrollment(request, check_access=True): # or if the user is enrolling in a country in which the course # is not available. redirect_url = embargo_api.redirect_if_blocked( - course_id, user=user, ip_address=get_ip(request), + course_id, user=user, ip_address=get_client_ip(request)[0], url=request.path ) if redirect_url: diff --git a/common/djangoapps/track/middleware.py b/common/djangoapps/track/middleware.py index 20a84f104a..df125afea3 100644 --- a/common/djangoapps/track/middleware.py +++ b/common/djangoapps/track/middleware.py @@ -16,7 +16,7 @@ import six from django.conf import settings from django.utils.deprecation import MiddlewareMixin from eventtracking import tracker -from ipware.ip import get_ip +from ipware.ip import get_client_ip from common.djangoapps.track import contexts, views @@ -229,7 +229,7 @@ class TrackMiddleware(MiddlewareMixin): def get_request_ip_address(self, request): """Gets the IP address of the request""" - ip_address = get_ip(request) + ip_address = get_client_ip(request)[0] if ip_address is not None: return ip_address else: diff --git a/common/djangoapps/track/views/__init__.py b/common/djangoapps/track/views/__init__.py index 011bb9cc4d..0d6552b527 100644 --- a/common/djangoapps/track/views/__init__.py +++ b/common/djangoapps/track/views/__init__.py @@ -6,7 +6,7 @@ import six from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.http import HttpResponse from eventtracking import tracker as eventtracker -from ipware.ip import get_ip +from ipware.ip import get_client_ip from common.djangoapps.track import contexts, shim, tracker @@ -22,7 +22,7 @@ def _get_request_header(request, header_name, default=''): def _get_request_ip(request, default=''): """Helper method to get IP from a request's META dict, if present.""" if request is not None and hasattr(request, 'META'): - return get_ip(request) + return get_client_ip(request)[0] else: return default diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index acecda81cd..096445c64b 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -34,7 +34,7 @@ from django.views.decorators.http import require_GET, require_http_methods, requ from django.views.generic import View from edx_django_utils import monitoring as monitoring_utils from edx_django_utils.monitoring import set_custom_attribute, set_custom_attributes_for_course_key -from ipware.ip import get_ip +from ipware.ip import get_client_ip from markupsafe import escape from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey @@ -1911,7 +1911,7 @@ def financial_assistance_request(request): goals = data['goals'] effort = data['effort'] marketing_permission = data['mktg-permission'] - ip_address = get_ip(request) + ip_address = get_client_ip(request)[0] except ValueError: # Thrown if JSON parsing fails return HttpResponseBadRequest(u'Could not parse request JSON.') diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index d8f32afe3e..8b9195238c 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -23,7 +23,7 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.views.generic.base import View from edx_rest_api_client.exceptions import SlumberBaseException -from ipware.ip import get_ip +from ipware.ip import get_client_ip from opaque_keys.edx.keys import CourseKey from rest_framework.response import Response from rest_framework.views import APIView @@ -243,7 +243,7 @@ class PayAndVerifyView(View): redirect_url = embargo_api.redirect_if_blocked( course_key, user=request.user, - ip_address=get_ip(request), + ip_address=get_client_ip(request)[0], url=request.path ) if redirect_url: diff --git a/openedx/core/djangoapps/embargo/api.py b/openedx/core/djangoapps/embargo/api.py index 7c9a73ad00..73d29d11ad 100644 --- a/openedx/core/djangoapps/embargo/api.py +++ b/openedx/core/djangoapps/embargo/api.py @@ -10,7 +10,7 @@ import logging from django.conf import settings from django.core.cache import cache -from ipware.ip import get_ip +from ipware.ip import get_client_ip from rest_framework import status from rest_framework.response import Response @@ -197,7 +197,7 @@ def get_embargo_response(request, course_id, user): """ redirect_url = redirect_if_blocked( - course_id, user=user, ip_address=get_ip(request), url=request.path) + course_id, user=user, ip_address=get_client_ip(request)[0], url=request.path) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, diff --git a/openedx/core/djangoapps/embargo/middleware.py b/openedx/core/djangoapps/embargo/middleware.py index 94051c518e..feb476fc9c 100644 --- a/openedx/core/djangoapps/embargo/middleware.py +++ b/openedx/core/djangoapps/embargo/middleware.py @@ -34,7 +34,7 @@ from django.core.exceptions import MiddlewareNotUsed from django.urls import reverse from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect -from ipware.ip import get_ip +from ipware.ip import get_client_ip from openedx.core.lib.request_utils import course_id_from_url @@ -83,7 +83,7 @@ class EmbargoMiddleware(MiddlewareMixin): if pattern.match(request.path) is not None: return None - ip_address = get_ip(request) + ip_address = get_client_ip(request)[0] ip_filter = IPFilter.current() if ip_filter.enabled and ip_address in ip_filter.blacklist_ips: diff --git a/openedx/core/djangoapps/geoinfo/middleware.py b/openedx/core/djangoapps/geoinfo/middleware.py index dbc01ec95a..f465dd1654 100644 --- a/openedx/core/djangoapps/geoinfo/middleware.py +++ b/openedx/core/djangoapps/geoinfo/middleware.py @@ -9,14 +9,13 @@ Usage: decorator `django.utils.decorators.decorator_from_middleware(middleware_class)` """ - - import logging import geoip2.database from django.conf import settings from django.utils.deprecation import MiddlewareMixin -from ipware.ip import get_real_ip +from ipware.ip import get_client_ip +from ipware.utils import is_public_ip log = logging.getLogger(__name__) @@ -31,13 +30,13 @@ class CountryMiddleware(MiddlewareMixin): Store country code in session. """ - new_ip_address = get_real_ip(request) + new_ip_address = get_client_ip(request)[0] old_ip_address = request.session.get('ip_address', None) if not new_ip_address and old_ip_address: del request.session['ip_address'] del request.session['country_code'] - elif new_ip_address != old_ip_address: + elif new_ip_address != old_ip_address and is_public_ip(new_ip_address): reader = geoip2.database.Reader(settings.GEOIP_PATH) try: response = reader.country(new_ip_address) diff --git a/openedx/core/djangoapps/geoinfo/tests/test_middleware.py b/openedx/core/djangoapps/geoinfo/tests/test_middleware.py index 4e3baa3f3e..6a5e82ae8b 100644 --- a/openedx/core/djangoapps/geoinfo/tests/test_middleware.py +++ b/openedx/core/djangoapps/geoinfo/tests/test_middleware.py @@ -107,20 +107,6 @@ class CountryMiddlewareTests(TestCase): assert 'CN' == request.session.get('country_code') assert '117.79.83.100' == request.session.get('ip_address') - def test_ip_address_is_none(self): - # IP address is not defined in request. - request = self.request_factory.get('/somewhere') - request.user = self.anonymous_user - # Run process_request to set up the session in the request - # to be able to override it. - self.session_middleware.process_request(request) - request.session['country_code'] = 'CN' - request.session['ip_address'] = '117.79.83.1' - self.country_middleware.process_request(request) - # No country code exists after request processing. - assert 'country_code' not in request.session - assert 'ip_address' not in request.session - def test_ip_address_is_ipv6(self): request = self.request_factory.get( '/somewhere', diff --git a/openedx/core/djangoapps/util/ratelimit.py b/openedx/core/djangoapps/util/ratelimit.py index 9dc1ea858c..6fd7266f55 100644 --- a/openedx/core/djangoapps/util/ratelimit.py +++ b/openedx/core/djangoapps/util/ratelimit.py @@ -1,15 +1,13 @@ """ Code to get ip from request. """ - - from uuid import uuid4 -from ipware.ip import get_ip +from ipware.ip import get_client_ip def real_ip(group, request): # pylint: disable=unused-argument - return get_ip(request) + return get_client_ip(request)[0] def request_post_email(group, request) -> str: # pylint: disable=unused-argument diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 2deab70c36..667c146d00 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -24,9 +24,6 @@ django-cors-headers<3.0.0 # It seems like django-countries > 5.5 may cause performance issues for us. django-countries==5.5 -# Removes deprecated get_ip function, which we still use (ARCHBOM-1329 for unpinning) -django-ipware<3.0.0 - # django-storages version 1.9 drops support for boto storage backend. django-storages<1.9 @@ -34,7 +31,7 @@ django-storages<1.9 # 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==3.17.40 +edx-enterprise==3.17.41 # Upgrading to 2.12.0 breaks several test classes due to API changes, need to update our code accordingly factory-boy==2.8.1 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 1f7c091740..8124ca532a 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -58,7 +58,7 @@ django-countries==5.5 # via -c requirements/edx/../constraints.txt, -r requi django-crum==0.7.9 # via -r requirements/edx/base.in, edx-django-utils, edx-enterprise, edx-proctoring, edx-rbac, edx-toggles, super-csv django-fernet-fields==0.6 # via -r requirements/edx/base.in, edx-enterprise, edx-event-routing-backends, edxval django-filter==2.4.0 # via -r requirements/edx/base.in, edx-enterprise, lti-consumer-xblock -django-ipware==2.1.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in, edx-enterprise, edx-proctoring +django-ipware==3.0.2 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in, edx-enterprise, edx-proctoring django-js-asset==1.2.2 # via django-mptt django-method-override==1.0.4 # via -r requirements/edx/base.in django-model-utils==4.1.1 # via -r requirements/edx/base.in, django-user-tasks, edx-bulk-grades, edx-celeryutils, edx-completion, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-rbac, edx-submissions, edx-when, edxval, ora2, super-csv @@ -99,7 +99,7 @@ edx-django-release-util==1.0.0 # via -r requirements/edx/base.in edx-django-sites-extensions==3.0.0 # via -r requirements/edx/base.in edx-django-utils==3.13.0 # via -r requirements/edx/base.in, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-toggles, edx-when, ora2, super-csv edx-drf-extensions==6.5.0 # via -r requirements/edx/base.in, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.17.40 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in +edx-enterprise==3.17.41 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in edx-event-routing-backends==4.0.1 # via -r requirements/edx/base.in edx-i18n-tools==0.5.3 # via ora2 edx-milestones==0.3.0 # via -r requirements/edx/base.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 692df62038..4f4e6be2d1 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -69,7 +69,7 @@ django-crum==0.7.9 # via -r requirements/edx/testing.txt, edx-django-util django-debug-toolbar==3.2 # via -r requirements/edx/development.in django-fernet-fields==0.6 # via -r requirements/edx/testing.txt, edx-enterprise, edx-event-routing-backends, edxval django-filter==2.4.0 # via -r requirements/edx/testing.txt, edx-enterprise, lti-consumer-xblock -django-ipware==2.1.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt, edx-enterprise, edx-proctoring +django-ipware==3.0.2 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt, edx-enterprise, edx-proctoring django-js-asset==1.2.2 # via -r requirements/edx/testing.txt, django-mptt django-method-override==1.0.4 # via -r requirements/edx/testing.txt django-model-utils==4.1.1 # via -r requirements/edx/testing.txt, django-user-tasks, edx-bulk-grades, edx-celeryutils, edx-completion, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-rbac, edx-submissions, edx-when, edxval, ora2, super-csv @@ -110,7 +110,7 @@ edx-django-release-util==1.0.0 # via -r requirements/edx/testing.txt edx-django-sites-extensions==3.0.0 # via -r requirements/edx/testing.txt edx-django-utils==3.13.0 # via -r requirements/edx/testing.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-toggles, edx-when, ora2, super-csv edx-drf-extensions==6.5.0 # via -r requirements/edx/testing.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.17.40 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt +edx-enterprise==3.17.41 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt edx-event-routing-backends==4.0.1 # via -r requirements/edx/testing.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/testing.txt, ora2 edx-lint==4.0.1 # via -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 992efa764e..cdb2c27c56 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -67,7 +67,7 @@ django-countries==5.5 # via -c requirements/edx/../constraints.txt, -r requi django-crum==0.7.9 # via -r requirements/edx/base.txt, edx-django-utils, edx-enterprise, edx-proctoring, edx-rbac, edx-toggles, super-csv django-fernet-fields==0.6 # via -r requirements/edx/base.txt, edx-enterprise, edx-event-routing-backends, edxval django-filter==2.4.0 # via -r requirements/edx/base.txt, edx-enterprise, lti-consumer-xblock -django-ipware==2.1.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt, edx-enterprise, edx-proctoring +django-ipware==3.0.2 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt, edx-enterprise, edx-proctoring django-js-asset==1.2.2 # via -r requirements/edx/base.txt, django-mptt django-method-override==1.0.4 # via -r requirements/edx/base.txt django-model-utils==4.1.1 # via -r requirements/edx/base.txt, django-user-tasks, edx-bulk-grades, edx-celeryutils, edx-completion, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-rbac, edx-submissions, edx-when, edxval, ora2, super-csv @@ -107,7 +107,7 @@ edx-django-release-util==1.0.0 # via -r requirements/edx/base.txt edx-django-sites-extensions==3.0.0 # via -r requirements/edx/base.txt edx-django-utils==3.13.0 # via -r requirements/edx/base.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-toggles, edx-when, ora2, super-csv edx-drf-extensions==6.5.0 # via -r requirements/edx/base.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.17.40 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt +edx-enterprise==3.17.41 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt edx-event-routing-backends==4.0.1 # via -r requirements/edx/base.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/base.txt, -r requirements/edx/testing.in, ora2 edx-lint==4.0.1 # via -r requirements/edx/testing.in From e3b0bffa503f7942b9e3aa31b3eee0be293b0dec Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Tue, 23 Feb 2021 09:03:31 -0500 Subject: [PATCH 12/74] Revert "Remove the "role of users being enrolled" field from Instructor Dashboard" --- cms/envs/common.py | 5 ++ common/djangoapps/student/api.py | 12 ++++- common/djangoapps/student/models_api.py | 4 +- lms/djangoapps/instructor/tests/test_api.py | 7 +-- .../tests/views/test_instructor_dashboard.py | 47 +++++++++++++++++++ lms/djangoapps/instructor/views/api.py | 13 ++++- .../instructor/views/instructor_dashboard.py | 3 ++ lms/envs/common.py | 4 ++ .../instructor_dashboard_2/membership.html | 11 +++++ 9 files changed, 100 insertions(+), 6 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 7a4194a92c..04c8ee5e52 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2023,6 +2023,11 @@ INTEGRATED_CHANNELS_API_CHUNK_TRANSMISSION_LIMIT = {} BASE_COOKIE_DOMAIN = 'localhost' +# This limits the type of roles that are submittable via the `student` app's manual enrollment +# audit API. While this isn't used in CMS, it is used via Enterprise which is installed in +# the CMS. Without this, we get errors. +MANUAL_ENROLLMENT_ROLE_CHOICES = ['Learner', 'Support', 'Partner'] + ############## Settings for the Discovery App ###################### COURSE_CATALOG_URL_ROOT = 'http://localhost:8008' diff --git a/common/djangoapps/student/api.py b/common/djangoapps/student/api.py index 4ae91a43d1..6ccdcef748 100644 --- a/common/djangoapps/student/api.py +++ b/common/djangoapps/student/api.py @@ -45,6 +45,11 @@ TRANSITION_STATES = ( DEFAULT_TRANSITION_STATE, ) +MANUAL_ENROLLMENT_ROLE_CHOICES = configuration_helpers.get_value( + 'MANUAL_ENROLLMENT_ROLE_CHOICES', + settings.MANUAL_ENROLLMENT_ROLE_CHOICES +) + COURSE_DASHBOARD_PLUGIN_VIEW_NAME = "course_dashboard" @@ -54,6 +59,7 @@ def create_manual_enrollment_audit( transition_state, reason, course_run_key=None, + role=None ): """ Creates an audit item for a manual enrollment. @@ -63,10 +69,13 @@ def create_manual_enrollment_audit( transition_state: state of enrollment transition state from _TRANSITIONS_STATES reason: Reason why user was manually enrolled course_run_key: Used to link the audit enrollment to the actual enrollment + role: role of the enrolled user from MANUAL_ENROLLMENT_ROLE_CHOICES Note: We purposefully *exclude* passing items like CourseEnrollment objects to prevent callers from needed to know about model level code. """ + if role and role not in MANUAL_ENROLLMENT_ROLE_CHOICES: + raise ValueError("Role `{}` not in allowed roles: `{}".format(role, MANUAL_ENROLLMENT_ROLE_CHOICES)) if transition_state not in TRANSITION_STATES: raise ValueError("State `{}` not in allow states: `{}`".format(transition_state, TRANSITION_STATES)) @@ -86,7 +95,8 @@ def create_manual_enrollment_audit( user_email, transition_state, reason, - enrollment + enrollment, + role ) diff --git a/common/djangoapps/student/models_api.py b/common/djangoapps/student/models_api.py index 20e0363f46..e5a7e51ce5 100644 --- a/common/djangoapps/student/models_api.py +++ b/common/djangoapps/student/models_api.py @@ -35,7 +35,8 @@ def create_manual_enrollment_audit( # lint-amnesty, pylint: disable=missing-fun user_email, state_transition, reason, - course_enrollment + course_enrollment, + role ): _ManualEnrollmentAudit.create_manual_enrollment_audit( user=enrolled_by, @@ -43,6 +44,7 @@ def create_manual_enrollment_audit( # lint-amnesty, pylint: disable=missing-fun state_transition=state_transition, reason=reason, enrollment=course_enrollment, + role=role, ) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 7123b9758e..5a74c5f81c 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -1776,18 +1776,19 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest ) assert course_enrollment.mode == CourseMode.DEFAULT_MODE_SLUG - def test_reason_is_persisted(self): + def test_role_and_reason_are_persisted(self): """ - test that reason field is persisted in the database + test that role and reason fields are persisted in the database """ paid_course = self.create_paid_course() url = reverse('students_update_enrollment', kwargs={'course_id': str(paid_course.id)}) params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': False, - 'auto_enroll': False, 'reason': 'testing'} + 'auto_enroll': False, 'reason': 'testing', 'role': 'Learner'} response = self.client.post(url, params) manual_enrollment = ManualEnrollmentAudit.objects.first() assert manual_enrollment.reason == 'testing' + assert manual_enrollment.role == 'Learner' assert response.status_code == 200 def _change_student_enrollment(self, user, course, action): diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index fa1477d722..783f5da8f7 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -245,6 +245,53 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT else: self.assertNotContains(response, reason_field) + def test_membership_site_configuration_role(self): + """ + Verify that the role choices set via site configuration are loaded in the membership tab + of the instructor dashboard + """ + + configuration_values = { + "MANUAL_ENROLLMENT_ROLE_CHOICES": [ + "role1", + "role2", + ] + } + site = Site.objects.first() + SiteConfiguration.objects.create( + site=site, + site_values=configuration_values, + enabled=True + ) + url = reverse( + 'instructor_dashboard', + kwargs={ + 'course_id': str(self.course_info.id) + } + ) + + response = self.client.get(url) + self.assertContains(response, '') + self.assertContains(response, '') + + def test_membership_default_role(self): + """ + Verify that in the absence of site configuration role choices, default values of role choices are loaded + in the membership tab of the instructor dashboard + """ + + url = reverse( + 'instructor_dashboard', + kwargs={ + 'course_id': str(self.course_info.id) + } + ) + + response = self.client.get(url) + self.assertContains(response, '') + self.assertContains(response, '') + self.assertContains(response, '') + def test_student_admin_staff_instructor(self): """ Verify that staff users are not able to see course-wide options, while still diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 0213c06cfc..88cb6c9310 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -649,6 +649,17 @@ def students_update_enrollment(request, course_id): # lint-amnesty, pylint: dis auto_enroll = _get_boolean_param(request, 'auto_enroll') email_students = _get_boolean_param(request, 'email_students') reason = request.POST.get('reason') + role = request.POST.get('role') + + allowed_role_choices = configuration_helpers.get_value('MANUAL_ENROLLMENT_ROLE_CHOICES', + settings.MANUAL_ENROLLMENT_ROLE_CHOICES) + if role and role not in allowed_role_choices: + return JsonResponse( + { + 'action': action, + 'results': [{'error': True, 'message': 'Not a valid role choice'}], + 'auto_enroll': auto_enroll, + }, status=400) enrollment_obj = None state_transition = DEFAULT_TRANSITION_STATE @@ -741,7 +752,7 @@ def students_update_enrollment(request, course_id): # lint-amnesty, pylint: dis else: ManualEnrollmentAudit.create_manual_enrollment_audit( - request.user, email, state_transition, reason, enrollment_obj + request.user, email, state_transition, reason, enrollment_obj, role ) results.append({ 'identifier': identifier, diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 9ee10ab442..29d83fdb9e 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -456,6 +456,8 @@ def _section_membership(course, access): """ Provide data for the corresponding dashboard section """ course_key = course.id ccx_enabled = settings.FEATURES.get('CUSTOM_COURSES_EDX', False) and course.enable_ccx + enrollment_role_choices = configuration_helpers.get_value('MANUAL_ENROLLMENT_ROLE_CHOICES', + settings.MANUAL_ENROLLMENT_ROLE_CHOICES) section_data = { 'section_key': 'membership', @@ -482,6 +484,7 @@ def _section_membership(course, access): 'update_forum_role_membership', kwargs={'course_id': str(course_key)} ), + 'enrollment_role_choices': enrollment_role_choices, 'is_reason_field_enabled': configuration_helpers.get_value('ENABLE_MANUAL_ENROLLMENT_REASON_FIELD', False) } return section_data diff --git a/lms/envs/common.py b/lms/envs/common.py index 0e126d2c6f..51f3a82380 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -86,6 +86,10 @@ LMS_ROOT_URL = 'https://localhost:18000' LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/" +# Default choices for role dropdown in the membership tab of the instructor dashboard +# This setting is used when a site does not define its own choices via site configuration +MANUAL_ENROLLMENT_ROLE_CHOICES = ['Learner', 'Support', 'Partner'] + # List of logout URIs for each IDA that the learner should be logged out of when they logout of the LMS. Only applies to # IDA for which the social auth flow uses DOT (Django OAuth Toolkit). IDA_LOGOUT_URI_LIST = [] diff --git a/lms/templates/instructor/instructor_dashboard_2/membership.html b/lms/templates/instructor/instructor_dashboard_2/membership.html index baa11767a9..7945827b67 100644 --- a/lms/templates/instructor/instructor_dashboard_2/membership.html +++ b/lms/templates/instructor/instructor_dashboard_2/membership.html @@ -13,6 +13,17 @@ from openedx.core.djangolib.markup import HTML, Text +
+ +
+ % if section_data['is_reason_field_enabled']: