Merge branch 'master' into BOM-2374-entitlements
This commit is contained in:
@@ -20,7 +20,13 @@ jobs:
|
||||
python-version: ["3.8"]
|
||||
|
||||
steps:
|
||||
- name: setup target branch
|
||||
run: echo "target_branch=$(if ['${{ github.event.inputs.branch }}' = '']; then echo 'master'; else echo '${{ github.event.inputs.branch }}'; fi)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
ref: ${{ env.target_branch }}
|
||||
|
||||
- name: setup python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
@@ -34,9 +40,6 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE
|
||||
make upgrade
|
||||
|
||||
- name: setup target branch
|
||||
run: echo "target_branch=$(if ['${{ github.event.inputs.branch }}' = '']; then echo 'master'; else echo '${{ github.event.inputs.branch }}'; fi)" >> $GITHUB_ENV
|
||||
|
||||
- name: setup testeng-ci
|
||||
run: |
|
||||
git clone https://github.com/edx/testeng-ci.git
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
############################
|
||||
########################
|
||||
Contributing to Open edX
|
||||
############################
|
||||
########################
|
||||
|
||||
Contributions to Open edX are very welcome, and strongly encouraged! We've
|
||||
put together `some documentation that describes our contribution process`_,
|
||||
@@ -99,19 +99,24 @@ to ask clarifying questions on the ticket as needed, too, if anything is unclear
|
||||
Step 1: Sign a Contribution Agreement
|
||||
=====================================
|
||||
|
||||
Before edX can accept any code contributions from you, you'll need to sign
|
||||
the `individual contributor agreement`_ and send it in. This confirms
|
||||
that you have the authority to contribute the code in the pull request and
|
||||
ensures that edX can relicense it.
|
||||
Before edX can accept any code contributions from you, you'll need to sign the
|
||||
`Individual Contributor Agreement`_. This confirms that you have the authority
|
||||
to contribute the code in the pull request and ensures that edX can re-license
|
||||
it.
|
||||
|
||||
You should print out the agreement and sign it. Then scan (or photograph) the
|
||||
signed agreement and email it to the email address indicated on the agreement.
|
||||
Alternatively, you're also free to physically mail the agreement to the street
|
||||
address on the agreement. Once we have your agreement in hand, we can begin
|
||||
reviewing and merging your work.
|
||||
.. _Individual Contributor Agreement: https://openedx.org/cla
|
||||
|
||||
If you will be contributing code on behalf of your employer or another
|
||||
institution you are affiliated with, please reach out by email to legal@edx.org
|
||||
to request the Entity Contributor Agreement.
|
||||
|
||||
Once we have received and processed your agreement, we will reach out to you by
|
||||
email to confirm. (Please make sure to indicate your email correctly in the
|
||||
agreement.) After that we can begin reviewing and merging your work.
|
||||
|
||||
Step 2: Fork, Commit, and Pull Request
|
||||
======================================
|
||||
|
||||
GitHub has some great documentation on `how to fork a git repository`_. Once
|
||||
you've done that, make your changes and `send us a pull request`_! Be sure to
|
||||
include a detailed description for your pull request, so that a community
|
||||
@@ -214,5 +219,3 @@ Expectations You Have of Us
|
||||
|
||||
3. Once we have determined through visual review that your code is not
|
||||
malicious, we will run a Jenkins build on your branch.
|
||||
|
||||
.. _individual contributor agreement: https://open.edx.org/wp-content/uploads/2019/01/individual-contributor-agreement.pdf
|
||||
|
||||
5
Makefile
5
Makefile
@@ -67,7 +67,9 @@ pre-requirements: ## install Python requirements for running pip-tools
|
||||
pip install -qr requirements/edx/pip-tools.txt
|
||||
|
||||
requirements: pre-requirements ## install development environment requirements
|
||||
pip-sync -q requirements/edx/development.txt requirements/edx/private.*
|
||||
# The "$(wildcard..)" is to include private.txt if it exists, and make no mention
|
||||
# of it if it does not. Shell wildcarding can't do that with default options.
|
||||
pip-sync -q requirements/edx/development.txt $(wildcard requirements/edx/private.txt)
|
||||
|
||||
shell: ## launch a bash shell in a Docker container with all edx-platform dependencies installed
|
||||
docker run -it -e "NO_PYTHON_UNINSTALL=1" -e "PIP_INDEX_URL=https://pypi.python.org/simple" -e TERM \
|
||||
@@ -130,4 +132,3 @@ docker_push: docker_tag docker_auth ## push to docker hub
|
||||
docker push "openedx/edx-platform:${GITHUB_SHA}-newrelic"
|
||||
docker push 'openedx/edx-platform:latest-devstack'
|
||||
docker push "openedx/edx-platform:${GITHUB_SHA}-devstack"
|
||||
|
||||
|
||||
@@ -86,5 +86,5 @@ Reporting Security Issues
|
||||
Please do not report security issues in public. Please email
|
||||
security@edx.org.
|
||||
|
||||
.. _individual contributor agreement: https://openedx.org/wp-content/uploads/2019/01/individual-contributor-agreement.pdf
|
||||
.. _individual contributor agreement: https://openedx.org/cla
|
||||
.. _CONTRIBUTING: https://github.com/edx/edx-platform/blob/master/CONTRIBUTING.rst
|
||||
|
||||
@@ -45,7 +45,7 @@ class CourseQualityViewTest(BaseCourseViewTest):
|
||||
'number_with_highlights': 0,
|
||||
'total_visible': 1,
|
||||
'total_number': 1,
|
||||
'highlights_enabled': False,
|
||||
'highlights_enabled': True,
|
||||
'highlights_active_for_course': False,
|
||||
},
|
||||
'subsections': {
|
||||
|
||||
@@ -9,7 +9,6 @@ from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.response import Response
|
||||
from scipy import stats
|
||||
|
||||
from cms.djangoapps.contentstore.views.item import highlights_setting
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
|
||||
from openedx.core.lib.cache_utils import request_cached
|
||||
from openedx.core.lib.graph_traversals import traverse_pre_order
|
||||
@@ -149,7 +148,7 @@ class CourseQualityView(DeveloperErrorViewMixin, GenericAPIView):
|
||||
total_visible=len(visible_sections),
|
||||
number_with_highlights=len(sections_with_highlights),
|
||||
highlights_active_for_course=course.highlights_enabled_for_messaging,
|
||||
highlights_enabled=highlights_setting.is_enabled(),
|
||||
highlights_enabled=True, # used to be controlled by a waffle switch, now just always enabled
|
||||
)
|
||||
|
||||
def _subsections_quality(self, course, request): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Views for items (modules)."""
|
||||
|
||||
|
||||
import hashlib # lint-amnesty, pylint: disable=unused-import
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
@@ -21,7 +20,6 @@ from edx_proctoring.api import (
|
||||
get_exam_configuration_dashboard_url
|
||||
)
|
||||
from edx_proctoring.exceptions import ProctoredExamNotFoundException
|
||||
from edx_toggles.toggles import LegacyWaffleSwitch
|
||||
from help_tokens.core import HelpUrlExpert
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import LibraryUsageLocator
|
||||
@@ -36,7 +34,6 @@ from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
from cms.djangoapps.xblock_config.models import CourseEditLTIFieldsEnabledFlag
|
||||
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.core.lib.xblock_utils import hash_resource, request_token, wrap_xblock, wrap_xblock_aside
|
||||
from openedx.core.djangoapps.bookmarks import api as bookmarks_api
|
||||
@@ -92,9 +89,6 @@ NEVER = lambda x: False
|
||||
ALWAYS = lambda x: True
|
||||
|
||||
|
||||
highlights_setting = LegacyWaffleSwitch('dynamic_pacing', 'studio_course_update', __name__)
|
||||
|
||||
|
||||
def _filter_entrance_exam_grader(graders):
|
||||
"""
|
||||
If the entrance exams feature is enabled we need to hide away the grader from
|
||||
@@ -1262,8 +1256,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
'highlights_enabled_for_messaging': course.highlights_enabled_for_messaging,
|
||||
})
|
||||
xblock_info.update({
|
||||
'highlights_enabled': highlights_setting.is_enabled(),
|
||||
'highlights_preview_only': not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course.id),
|
||||
'highlights_enabled': True, # used to be controlled by a waffle switch, now just always enabled
|
||||
'highlights_preview_only': False, # used to be controlled by a waffle flag, now just always disabled
|
||||
'highlights_doc_url': HelpUrlExpert.the_one().url_for_token('content_highlights'),
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
from edx_proctoring.exceptions import ProctoredExamNotFoundException
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
from mock import Mock, PropertyMock, patch
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.asides import AsideUsageKeyV2
|
||||
@@ -66,7 +65,6 @@ from ..item import (
|
||||
_xblock_type_and_display_name,
|
||||
add_container_page_publishing_info,
|
||||
create_xblock_info,
|
||||
highlights_setting
|
||||
)
|
||||
|
||||
|
||||
@@ -2696,12 +2694,8 @@ class TestXBlockInfo(ItemTest):
|
||||
def test_highlights_enabled(self):
|
||||
self.course.highlights_enabled_for_messaging = True
|
||||
self.store.update_item(self.course, None)
|
||||
chapter = self.store.get_item(self.chapter.location)
|
||||
with override_waffle_switch(highlights_setting, active=True):
|
||||
chapter_xblock_info = create_xblock_info(chapter)
|
||||
course_xblock_info = create_xblock_info(self.course)
|
||||
self.assertTrue(chapter_xblock_info['highlights_enabled'])
|
||||
self.assertTrue(course_xblock_info['highlights_enabled_for_messaging'])
|
||||
course_xblock_info = create_xblock_info(self.course)
|
||||
self.assertTrue(course_xblock_info['highlights_enabled_for_messaging'])
|
||||
|
||||
def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False):
|
||||
"""
|
||||
@@ -2731,7 +2725,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['due'], None)
|
||||
self.assertEqual(xblock_info['format'], None)
|
||||
self.assertEqual(xblock_info['highlights'], self.chapter.highlights)
|
||||
self.assertFalse(xblock_info['highlights_enabled'])
|
||||
self.assertTrue(xblock_info['highlights_enabled'])
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
|
||||
|
||||
@@ -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'
|
||||
@@ -2335,6 +2340,9 @@ DISABLE_DEPRECATED_SIGNUP_URL = False
|
||||
LOGISTRATION_RATELIMIT_RATE = '100/5m'
|
||||
LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '30/5m'
|
||||
LOGISTRATION_API_RATELIMIT = '20/m'
|
||||
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '30/7d'
|
||||
RESET_PASSWORD_API_RATELIMIT = '30/7d'
|
||||
|
||||
|
||||
##### REGISTRATION RATE LIMIT SETTINGS #####
|
||||
REGISTRATION_VALIDATION_RATELIMIT = '30/7d'
|
||||
|
||||
@@ -331,7 +331,8 @@ LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '6/5m'
|
||||
LOGISTRATION_API_RATELIMIT = '5/m'
|
||||
|
||||
REGISTRATION_VALIDATION_RATELIMIT = '5/minute'
|
||||
|
||||
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '2/m'
|
||||
RESET_PASSWORD_API_RATELIMIT = '2/m'
|
||||
# Don't tolerate deprecated edx-platform import usage in tests.
|
||||
ERROR_ON_DEPRECATED_EDX_PLATFORM_IMPORTS = True
|
||||
|
||||
|
||||
@@ -573,8 +573,7 @@ describe('CourseOutlinePage', function() {
|
||||
});
|
||||
|
||||
describe('Content Highlights', function() {
|
||||
var createCourse, createCourseWithHighlights, createCourseWithHighlightsDisabled,
|
||||
clickSaveOnModal, clickCancelOnModal;
|
||||
let createCourse, createCourseWithHighlights, clickSaveOnModal, clickCancelOnModal;
|
||||
|
||||
beforeEach(function() {
|
||||
setSelfPaced();
|
||||
@@ -592,11 +591,6 @@ describe('CourseOutlinePage', function() {
|
||||
createCourse({highlights: highlights});
|
||||
};
|
||||
|
||||
createCourseWithHighlightsDisabled = function() {
|
||||
var highlightsDisabled = {highlights_enabled: false};
|
||||
createCourse(highlightsDisabled, highlightsDisabled);
|
||||
};
|
||||
|
||||
clickSaveOnModal = function() {
|
||||
$('.wrapper-modal-window .action-save').click();
|
||||
};
|
||||
@@ -643,11 +637,6 @@ describe('CourseOutlinePage', function() {
|
||||
$('button.status-highlights-enabled-value').click();
|
||||
};
|
||||
|
||||
it('does not display settings when disabled', function() {
|
||||
createCourseWithHighlightsDisabled();
|
||||
expect(highlightsSetting()).not.toExist();
|
||||
});
|
||||
|
||||
it('displays settings when enabled', function() {
|
||||
createCourseWithHighlights([]);
|
||||
expect(highlightsSetting()).toExist();
|
||||
@@ -778,11 +767,6 @@ describe('CourseOutlinePage', function() {
|
||||
expectHighlightsToBe(updatedHighlights);
|
||||
};
|
||||
|
||||
it('does not display link when disabled', function() {
|
||||
createCourseWithHighlightsDisabled();
|
||||
expect(highlightsLink()).not.toExist();
|
||||
});
|
||||
|
||||
it('displays link when no highlights exist', function() {
|
||||
createCourseWithHighlights([]);
|
||||
expectHighlightLinkNumberToBe(0);
|
||||
|
||||
@@ -212,7 +212,7 @@ if (is_proctored_exam) {
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (xblockInfo.get('highlights_enabled') && xblockInfo.isChapter()) { %>
|
||||
<% if (xblockInfo.isChapter()) { %>
|
||||
<div class="block-highlights">
|
||||
<% var number_of_highlights = (xblockInfo.get('highlights') || []).length; %>
|
||||
<button class="block-highlights-value highlights-button action-button">
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
<h3 class="modal-section-title" id="highlights_label"><%- gettext('Section Highlights') %></h3>
|
||||
<div class="highlights-info">
|
||||
|
||||
<% if (highlights_preview_only) { %>
|
||||
<p><b>
|
||||
<%- gettext('This feature is currently in testing. Course teams can enter highlights, but learners will not receive email messages.') %>
|
||||
</b></p>
|
||||
<% } %>
|
||||
|
||||
<%- gettext('Enter 3-5 highlights to include in the email message that learners receive for this section (250 character limit).') %>
|
||||
|
||||
<p>
|
||||
|
||||
@@ -5,8 +5,6 @@ Add and create new modes for running courses on this particular LMS
|
||||
|
||||
from collections import defaultdict, namedtuple
|
||||
from datetime import timedelta
|
||||
|
||||
import inspect # lint-amnesty, pylint: disable=unused-import
|
||||
import logging
|
||||
import six
|
||||
from config_models.models import ConfigurationModel
|
||||
@@ -21,7 +19,6 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=unused-import
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -17,7 +17,7 @@ import logging
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse # lint-amnesty, pylint: disable=unused-import
|
||||
from django.http import HttpResponse # lint-amnesty, pylint: disable=unused-import
|
||||
from django.template import engines
|
||||
from django.urls import reverse, NoReverseMatch
|
||||
from six.moves.urllib.parse import urljoin
|
||||
@@ -26,7 +26,6 @@ from django.core.exceptions import ValidationError
|
||||
|
||||
from edx_django_utils.monitoring import set_custom_attribute
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site # lint-amnesty, pylint: disable=unused-import
|
||||
from xmodule.util.xmodule_django import get_current_request_hostname
|
||||
|
||||
from . import Engines
|
||||
|
||||
@@ -22,7 +22,6 @@ from common.djangoapps.student.tests.factories import TEST_PASSWORD, CourseEnrol
|
||||
from lms.djangoapps.courseware.models import DynamicUpgradeDeadlineConfiguration
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_api.models import UserOrgTag
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -485,7 +484,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase):
|
||||
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
|
||||
course = CourseFactory.create(self_paced=True)
|
||||
course_uuid = uuid.uuid4()
|
||||
course_mode = CourseModeFactory(
|
||||
CourseModeFactory(
|
||||
course_id=course.id,
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
# This must be in the future to ensure it is returned by downstream code.
|
||||
@@ -499,11 +498,9 @@ class EntitlementViewSetTest(ModuleStoreTestCase):
|
||||
# Add an audit course enrollment for user.
|
||||
enrollment = CourseEnrollment.enroll(self.user, course.id, mode=CourseMode.AUDIT)
|
||||
|
||||
# Set an upgrade schedule so that dynamic upgrade deadlines are used
|
||||
ScheduleFactory.create(
|
||||
enrollment=enrollment,
|
||||
upgrade_deadline=course_mode.expiration_datetime + timedelta(days=-3)
|
||||
)
|
||||
# Set an expired dynamic upgrade deadline
|
||||
enrollment.schedule.upgrade_deadline = now() + timedelta(days=-2)
|
||||
enrollment.schedule.save()
|
||||
|
||||
# The upgrade should complete and ignore the deadline
|
||||
response = self.client.post(
|
||||
|
||||
@@ -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: <str> state of enrollment transition state from _TRANSITIONS_STATES
|
||||
reason: <str> Reason why user was manually enrolled
|
||||
course_run_key: <str> Used to link the audit enrollment to the actual enrollment
|
||||
role: <str> 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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -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')]),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-27 01:44
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-10 12:15
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-19 14:30
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-02-27 20:19
|
||||
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-06-24 19:01
|
||||
|
||||
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.22 on 2019-07-19 13:06
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.27 on 2019-12-27 20:44
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.28 on 2020-02-18 18:36
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.29 on 2020-03-17 11:22
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.29 on 2020-03-25 14:28
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -76,7 +76,7 @@ from lms.djangoapps.courseware.models import (
|
||||
OrgDynamicUpgradeDeadlineConfiguration,
|
||||
)
|
||||
from lms.djangoapps.courseware.toggles import (
|
||||
courseware_mfe_streak_celebration_is_active,
|
||||
streak_celebration_is_active,
|
||||
COURSEWARE_PROCTORING_IMPROVEMENTS,
|
||||
)
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
@@ -2555,9 +2555,10 @@ def log_successful_logout(sender, request, user, **kwargs): # lint-amnesty, pyl
|
||||
"""Handler to log when logouts have occurred successfully."""
|
||||
if hasattr(request, 'user'):
|
||||
if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
|
||||
AUDIT_LOG.info(u"Logout - user.id: {0}".format(request.user.id)) # pylint: disable=logging-format-interpolation
|
||||
AUDIT_LOG.info('Logout - user.id: {0}'.format(request.user.id)) # pylint: disable=logging-format-interpolation
|
||||
else:
|
||||
AUDIT_LOG.info(u"Logout - {0}".format(request.user)) # pylint: disable=logging-format-interpolation
|
||||
AUDIT_LOG.info('Logout - {0}'.format(request.user)) # pylint: disable=logging-format-interpolation
|
||||
segment.track(request.user.id, 'edx.bi.user.account.logout')
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
@@ -3210,9 +3211,8 @@ class UserCelebration(TimeStampedModel):
|
||||
def _get_celebration(cls, user, course_key):
|
||||
""" Retrieve (or create) the celebration for the provided user and course_key """
|
||||
try:
|
||||
# The UI for celebrations is only supported on the MFE right now, so don't turn on
|
||||
# celebrations unless this enrollment's course is MFE-enabled and has milestones enabled.
|
||||
if not courseware_mfe_streak_celebration_is_active(course_key):
|
||||
# Only enable the streak if milestones and the streak are enabled for this course
|
||||
if not streak_celebration_is_active(course_key):
|
||||
return None
|
||||
return user.celebration
|
||||
except (cls.DoesNotExist, User.celebration.RelatedObjectDoesNotExist): # pylint: disable=no-member
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,6 @@ from lms.djangoapps.courseware.toggles import (
|
||||
)
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.schedules.models import Schedule
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from common.djangoapps.student.models import (
|
||||
@@ -139,8 +138,6 @@ class CourseEnrollmentTests(SharedModuleStoreTestCase): # lint-amnesty, pylint:
|
||||
self.assertListEqual([self.user, self.user_2], all_enrolled_users)
|
||||
|
||||
@skip_unless_lms
|
||||
# NOTE: We mute the post_save signal to prevent Schedules from being created for new enrollments
|
||||
@factory.django.mute_signals(signals.post_save)
|
||||
def test_upgrade_deadline(self):
|
||||
""" The property should use either the CourseMode or related Schedule to determine the deadline. """
|
||||
course = CourseFactory(self_paced=True)
|
||||
@@ -151,12 +148,10 @@ class CourseEnrollmentTests(SharedModuleStoreTestCase): # lint-amnesty, pylint:
|
||||
expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1)
|
||||
)
|
||||
enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT)
|
||||
assert Schedule.objects.all().count() == 0
|
||||
Schedule.objects.all().delete()
|
||||
assert enrollment.upgrade_deadline == course_mode.expiration_datetime
|
||||
|
||||
@skip_unless_lms
|
||||
# NOTE: We mute the post_save signal to prevent Schedules from being created for new enrollments
|
||||
@factory.django.mute_signals(signals.post_save)
|
||||
def test_upgrade_deadline_with_schedule(self):
|
||||
""" The property should use either the CourseMode or related Schedule to determine the deadline. """
|
||||
course = CourseFactory(self_paced=True)
|
||||
@@ -167,16 +162,17 @@ class CourseEnrollmentTests(SharedModuleStoreTestCase): # lint-amnesty, pylint:
|
||||
expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30),
|
||||
)
|
||||
course_overview = CourseOverview.load_from_module_store(course.id)
|
||||
enrollment = CourseEnrollmentFactory(
|
||||
CourseEnrollmentFactory(
|
||||
course_id=course.id,
|
||||
mode=CourseMode.AUDIT,
|
||||
course=course_overview,
|
||||
)
|
||||
Schedule.objects.update(upgrade_deadline=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=5))
|
||||
enrollment = CourseEnrollment.objects.first()
|
||||
|
||||
# The schedule's upgrade deadline should be used if a schedule exists
|
||||
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
|
||||
schedule = ScheduleFactory(enrollment=enrollment)
|
||||
assert enrollment.upgrade_deadline == schedule.upgrade_deadline
|
||||
assert enrollment.upgrade_deadline == enrollment.schedule.upgrade_deadline
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.data(*(set(CourseMode.ALL_MODES) - set(CourseMode.AUDIT_MODES)))
|
||||
@@ -197,7 +193,6 @@ class CourseEnrollmentTests(SharedModuleStoreTestCase): # lint-amnesty, pylint:
|
||||
)
|
||||
enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT)
|
||||
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
|
||||
ScheduleFactory(enrollment=enrollment)
|
||||
assert enrollment.schedule is not None
|
||||
assert enrollment.upgrade_deadline == course_upgrade_deadline
|
||||
|
||||
@@ -215,13 +210,10 @@ class CourseEnrollmentTests(SharedModuleStoreTestCase): # lint-amnesty, pylint:
|
||||
)
|
||||
enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT)
|
||||
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
|
||||
ScheduleFactory(enrollment=enrollment)
|
||||
assert enrollment.schedule is not None
|
||||
assert enrollment.upgrade_deadline is None
|
||||
|
||||
@skip_unless_lms
|
||||
# NOTE: We mute the post_save signal to prevent Schedules from being created for new enrollments
|
||||
@factory.django.mute_signals(signals.post_save)
|
||||
def test_enrollments_not_deleted(self):
|
||||
""" Recreating a CourseOverview with an outdated version should not delete the associated enrollment. """
|
||||
course = CourseFactory(self_paced=True)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -17,7 +17,6 @@ from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from mock import patch
|
||||
from opaque_keys import InvalidKeyError
|
||||
@@ -31,8 +30,6 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFact
|
||||
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
@@ -755,7 +752,6 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
|
||||
assert resume_button_html in dashboard_html
|
||||
assert view_button_html not in dashboard_html
|
||||
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
def test_content_gating_course_card_changes(self):
|
||||
"""
|
||||
When a course is expired, the links on the course card should be removed.
|
||||
@@ -775,9 +771,6 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
|
||||
enrollment.created = self.THREE_YEARS_AGO + timedelta(days=1)
|
||||
enrollment.save()
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
schedule = ScheduleFactory(enrollment=enrollment)
|
||||
|
||||
response = self.client.get(reverse('dashboard'))
|
||||
dashboard_html = self._remove_whitespace_from_response(response)
|
||||
access_expired_substring = 'Accessexpired'
|
||||
|
||||
@@ -418,7 +418,7 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
|
||||
Note to future developers:
|
||||
If you break this test so that the "check_mongo_calls(0)" fails,
|
||||
please do NOT change it to "check_mongo_calls(n>1)". Instead, change
|
||||
please do NOT change it to "check_mongo_calls(n>=1)". Instead, change
|
||||
your code to not load courses from the module store. This may
|
||||
involve adding fields to CourseOverview so that loading a full
|
||||
CourseDescriptor isn't necessary.
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -94,6 +94,7 @@ REGISTRATION_UTM_PARAMETERS = {
|
||||
'utm_content': 'registration_utm_content',
|
||||
}
|
||||
REGISTRATION_UTM_CREATED_AT = 'registration_utm_created_at'
|
||||
USER_ACCOUNT_ACTIVATED = 'edx.user.account.activated'
|
||||
|
||||
|
||||
def csrf_token(context):
|
||||
@@ -340,7 +341,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:
|
||||
@@ -529,6 +530,13 @@ def activate_account(request, key):
|
||||
# Success message for logged in users.
|
||||
message = _('{html_start}Success{html_end} You have activated your account.')
|
||||
|
||||
tracker.emit(
|
||||
USER_ACCOUNT_ACTIVATED,
|
||||
{
|
||||
"user_id": registration.user.id,
|
||||
}
|
||||
)
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
# Success message for logged out users
|
||||
message = _(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import json
|
||||
import unittest # lint-amnesty, pylint: disable=unused-import
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,7 +36,7 @@ class ThirdPartyOAuthTestMixin(ThirdPartyAuthTestMixin):
|
||||
def setUp(self): # lint-amnesty, pylint: disable=arguments-differ
|
||||
super(ThirdPartyOAuthTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
if self.CREATE_USER:
|
||||
self.user = UserFactory()
|
||||
self.user = UserFactory.create(password='secret')
|
||||
UserSocialAuth.objects.create(user=self.user, provider=self.BACKEND, uid=self.social_uid)
|
||||
self.oauth_client = self._create_client()
|
||||
if self.BACKEND == 'google-oauth2':
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"""
|
||||
URLs for track app
|
||||
"""
|
||||
|
||||
|
||||
from django.conf import settings # lint-amnesty, pylint: disable=unused-import
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ not migrating so as not to inconvenience users by logging them all out.
|
||||
from functools import wraps
|
||||
|
||||
import six
|
||||
from django.conf import settings # lint-amnesty, pylint: disable=unused-import
|
||||
from django.core import cache
|
||||
# If we can't find a 'general' CACHE defined in settings.py, we simply fall back
|
||||
# to returning the default cache. This will happen with dev machines.
|
||||
|
||||
@@ -8,9 +8,8 @@ import random
|
||||
# TransactionManagementError used below actually *does* derive from the standard "Exception" class.
|
||||
# lint-amnesty, pylint: disable=bad-option-value, nonstandard-exception
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps # lint-amnesty, pylint: disable=unused-import
|
||||
|
||||
from django.db import DEFAULT_DB_ALIAS, DatabaseError, Error, transaction # lint-amnesty, pylint: disable=unused-import
|
||||
from django.db import DEFAULT_DB_ALIAS, transaction # lint-amnesty, pylint: disable=unused-import
|
||||
|
||||
from openedx.core.lib.cache_utils import get_cache
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ so that we can cache any keys, not just ones that memcache would ordinarily acce
|
||||
|
||||
|
||||
import hashlib
|
||||
|
||||
import six.moves.urllib.error
|
||||
import six.moves.urllib.parse
|
||||
import six.moves.urllib.request
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ i.e. those with the <multiplechoiceresponse> 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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1 +1 @@
|
||||
521f88f382acecc86fb7c9b5128d858a14313b07
|
||||
c4a7790413ee6ca1e3eabb9de0024a0b5c2a3ac7
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user