Merge pull request #10725 from edx/rc/2015-11-24

Release Candidate rc/2015-11-24 for Dec 1 release
This commit is contained in:
Robert Raposa
2015-12-01 09:59:15 -05:00
490 changed files with 40028 additions and 62929 deletions

View File

@@ -1,2 +1,2 @@
common/static/js/vendor
lms/static/js/vendor
**/vendor
node_modules

View File

@@ -140,6 +140,7 @@
"spyOnEvent",
// Miscellaneous globals
"JSON"
"JSON",
"gettext"
]
}

View File

@@ -253,4 +253,6 @@ Justin Abrahms <abrahms@mit.edu>
Arbab Nazar <arbab@edx.org>
Douglas Hall <dhall@edx.org>
Awais Jibran <awaisdar001@gmail.com>
Muhammad Rehan <muhammadrehan69@gmail.com>
Muhammad Rehan <muhammadrehan69@gmail.com>
Shawn Milochik <shawn@milochik.com>
Afeef Janjua <janjua.afeef@gmail.com>

View File

@@ -640,3 +640,22 @@ class CourseAboutSearchIndexer(object):
"Successfully added %s course to the course discovery index",
course_id
)
@classmethod
def _get_location_info(cls, normalized_structure_key):
""" Builds location info dictionary """
return {"course": unicode(normalized_structure_key), "org": normalized_structure_key.org}
@classmethod
def remove_deleted_items(cls, structure_key):
""" Remove item from Course About Search_index """
searcher = SearchEngine.get_search_engine(cls.INDEX_NAME)
if not searcher:
return
response = searcher.search(
doc_type=cls.DISCOVERY_DOCUMENT_TYPE,
field_dictionary=cls._get_location_info(structure_key)
)
result_ids = [result["data"]["id"] for result in response["results"]]
searcher.remove(cls.DISCOVERY_DOCUMENT_TYPE, result_ids)

View File

@@ -2,7 +2,7 @@
# pylint: disable=redefined-outer-name
from lettuce import world, step
from nose.tools import assert_false, assert_equal, assert_regexp_matches # pylint: disable=no-name-in-module
from nose.tools import assert_false, assert_equal, assert_regexp_matches
from common import type_in_codemirror, press_the_notification_button, get_codemirror_value
KEY_CSS = '.key h3.title'

View File

@@ -2,7 +2,7 @@
# pylint: disable=redefined-outer-name
from lettuce import world, step
from nose.tools import assert_true, assert_equal # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_equal
from selenium.common.exceptions import StaleElementReferenceException

View File

@@ -3,7 +3,7 @@
import os
from lettuce import world, step
from nose.tools import assert_true, assert_in # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_in
from django.conf import settings
from student.roles import CourseStaffRole, CourseInstructorRole, GlobalStaff

View File

@@ -6,7 +6,7 @@
# pylint: disable=unused-argument
from lettuce import world, step
from nose.tools import assert_true, assert_in, assert_equal # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_in, assert_equal
DISPLAY_NAME = "Display Name"

View File

@@ -2,7 +2,7 @@
# pylint: disable=missing-docstring
from lettuce import world
from nose.tools import assert_equal, assert_in # pylint: disable=no-name-in-module
from nose.tools import assert_equal, assert_in
from terrain.steps import reload_the_page
from common import type_in_codemirror
from selenium.webdriver.common.keys import Keys

View File

@@ -3,7 +3,7 @@
from lettuce import world, step
from common import *
from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_false
from logging import getLogger
logger = getLogger(__name__)

View File

@@ -6,7 +6,7 @@ from selenium.webdriver.common.keys import Keys
from common import type_in_codemirror, upload_file
from django.conf import settings
from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_false
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT

View File

@@ -1,10 +1,9 @@
# pylint: disable=missing-docstring
# pylint: disable=redefined-outer-name
from lettuce import world, step
from selenium.webdriver.common.keys import Keys
from common import type_in_codemirror, get_codemirror_value
from nose.tools import assert_in # pylint: disable=no-name-in-module
from nose.tools import assert_in
@step(u'I go to the course updates page')

View File

@@ -6,7 +6,7 @@ from common import *
from terrain.steps import reload_the_page
from selenium.common.exceptions import InvalidElementStateException
from contentstore.utils import reverse_course_url
from nose.tools import assert_in, assert_equal, assert_not_equal # pylint: disable=no-name-in-module
from nose.tools import assert_in, assert_equal, assert_not_equal
@step(u'I am viewing the grading settings')

View File

@@ -4,7 +4,7 @@
from collections import OrderedDict
from lettuce import world, step
from nose.tools import assert_in, assert_false, assert_true, assert_equal # pylint: disable=no-name-in-module
from nose.tools import assert_in, assert_false, assert_true, assert_equal
from common import type_in_codemirror, get_codemirror_value
CODEMIRROR_SELECTOR_PREFIX = "$('iframe').contents().find"

View File

@@ -3,7 +3,7 @@
# pylint: disable=unused-argument
from lettuce import world, step
from nose.tools import assert_equal, assert_in # pylint: disable=no-name-in-module
from nose.tools import assert_equal, assert_in
CSS_FOR_TAB_ELEMENT = "li[data-tab-id='{0}'] input.toggle-checkbox"

View File

@@ -3,7 +3,7 @@
import json
from lettuce import world, step
from nose.tools import assert_equal, assert_true # pylint: disable=no-name-in-module
from nose.tools import assert_equal, assert_true
from common import type_in_codemirror, open_new_course
from advanced_settings import change_value, ADVANCED_MODULES_KEY
from course_import import import_file

View File

@@ -2,7 +2,7 @@
# pylint: disable=redefined-outer-name
from lettuce import world, step
from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_false
@step('I fill in the registration form$')

View File

@@ -1,5 +1,4 @@
# pylint: disable=missing-docstring
# pylint: disable=redefined-outer-name
from lettuce import world, step
from django.conf import settings

View File

@@ -10,7 +10,7 @@ import random
import os
from django.contrib.auth.models import User
from student.models import CourseEnrollment
from nose.tools import assert_equal, assert_not_equal # pylint: disable=no-name-in-module
from nose.tools import assert_equal, assert_not_equal
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
ASSET_NAMES_CSS = 'td.name-col > span.title > a.filename'

View File

@@ -4,7 +4,7 @@
import requests
from lettuce import world, step
from nose.tools import assert_true, assert_equal, assert_in, assert_not_equal # pylint: disable=no-name-in-module
from nose.tools import assert_true, assert_equal, assert_in, assert_not_equal
from terrain.steps import reload_the_page
from django.conf import settings
from common import upload_file, attach_file

View File

@@ -3,7 +3,7 @@
# pylint: disable=missing-docstring
from lettuce import world, step
from nose.tools import assert_true # pylint: disable=no-name-in-module
from nose.tools import assert_true
from video_editor import RequestHandlerWithSessionId, success_upload_file

View File

@@ -11,12 +11,12 @@ class Command(BaseCommand):
help = '''
Delete orphans from a MongoDB backed course. Takes two arguments:
<course_id>: the course id of the course whose orphans you want to delete
|commit|: optional argument. If not provided, will not run task.
|--commit|: optional argument. If not provided, will dry run delete
'''
def add_arguments(self, parser):
parser.add_argument('course_id')
parser.add_argument('--commit', action='store')
parser.add_argument('--commit', action='store_true', help='Commit to deleting the orphans')
def handle(self, *args, **options):
try:
@@ -24,20 +24,16 @@ class Command(BaseCommand):
except InvalidKeyError:
raise CommandError("Invalid course key.")
commit = False
if options['commit']:
commit = options['commit'] == 'commit'
if commit:
print 'Deleting orphans from the course:'
deleted_items = _delete_orphans(
course_key, ModuleStoreEnum.UserID.mgmt_command, commit
course_key, ModuleStoreEnum.UserID.mgmt_command, options['commit']
)
print "Success! Deleted the following orphans from the course:"
print "\n".join(deleted_items)
else:
print 'Dry run. The following orphans would have been deleted from the course:'
deleted_items = _delete_orphans(
course_key, ModuleStoreEnum.UserID.mgmt_command, commit
course_key, ModuleStoreEnum.UserID.mgmt_command, options['commit']
)
print "\n".join(deleted_items)

View File

@@ -17,39 +17,37 @@ class Command(BaseCommand):
help = '''
Force publish a course. Takes two arguments:
<course_id>: the course id of the course you want to publish forcefully
commit: do the force publish
--commit: do the force publish
If you do not specify 'commit', the command will print out what changes would be made.
If you do not specify '--commit', the command will print out what changes would be made.
'''
def add_arguments(self, parser):
parser.add_argument('course_key', help="ID of the Course to force publish")
parser.add_argument('--commit', action='store_true', help="Pull updated metadata from external IDPs")
def handle(self, *args, **options):
"""Execute the command"""
if len(args) not in {1, 2}:
raise CommandError("force_publish requires 1 or more argument: <course_id> |commit|")
try:
course_key = CourseKey.from_string(args[0])
course_key = CourseKey.from_string(options['course_key'])
except InvalidKeyError:
raise CommandError("Invalid course key.")
if not modulestore().get_course(course_key):
raise CommandError("Course not found.")
commit = False
if len(args) == 2:
commit = args[1] == 'commit'
# for now only support on split mongo
owning_store = modulestore()._get_modulestore_for_courselike(course_key) # pylint: disable=protected-access
if hasattr(owning_store, 'force_publish_course'):
versions = get_course_versions(args[0])
versions = get_course_versions(options['course_key'])
print "Course versions : {0}".format(versions)
if commit:
if options['commit']:
if query_yes_no("Are you sure to publish the {0} course forcefully?".format(course_key), default="no"):
# publish course forcefully
updated_versions = owning_store.force_publish_course(
course_key, ModuleStoreEnum.UserID.mgmt_command, commit
course_key, ModuleStoreEnum.UserID.mgmt_command, options['commit']
)
if updated_versions:
# if publish and draft were different

View File

@@ -7,8 +7,8 @@ import mock
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from django.core.management import CommandError
from contentstore.management.commands.delete_course import Command # pylint: disable=import-error
from contentstore.tests.utils import CourseTestCase # pylint: disable=import-error
from contentstore.management.commands.delete_course import Command
from contentstore.tests.utils import CourseTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import modulestore

View File

@@ -24,7 +24,7 @@ class TestDeleteOrphan(TestOrphanBase):
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_delete_orphans_no_commit(self, default_store):
"""
Tests that running the command without a 'commit' argument
Tests that running the command without a '--commit' argument
results in no orphans being deleted
"""
course = self.create_course_with_orphans(default_store)
@@ -37,12 +37,12 @@ class TestDeleteOrphan(TestOrphanBase):
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_delete_orphans_commit(self, default_store):
"""
Tests that running the command WITH the 'commit' argument
Tests that running the command WITH the '--commit' argument
results in the orphans being deleted
"""
course = self.create_course_with_orphans(default_store)
call_command('delete_orphans', unicode(course.id), commit='commit')
call_command('delete_orphans', unicode(course.id), '--commit')
# make sure this module wasn't deleted
self.assertTrue(self.store.has_item(course.id.make_usage_key('html', 'multi_parent_html')))
@@ -66,7 +66,7 @@ class TestDeleteOrphan(TestOrphanBase):
# call delete orphans, specifying the published branch
# of the course
call_command('delete_orphans', unicode(published_branch), commit='commit')
call_command('delete_orphans', unicode(published_branch), '--commit')
# now all orphans should be deleted
self.assertOrphanCount(course.id, 0)

View File

@@ -2,7 +2,7 @@
Tests for the force_publish management command
"""
import mock
from django.core.management.base import CommandError
from django.core.management import call_command, CommandError
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -25,9 +25,9 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
Test 'force_publish' command with no arguments
"""
errstring = "force_publish requires 1 or more argument: <course_id> |commit"
errstring = "Error: too few arguments"
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle()
call_command('force_publish')
def test_invalid_course_key(self):
"""
@@ -35,15 +35,15 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
errstring = "Invalid course key."
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle('TestX/TS01')
call_command('force_publish', 'TestX/TS01')
def test_too_many_arguments(self):
"""
Test 'force_publish' command with more than 2 arguments
"""
errstring = "force_publish requires 1 or more argument: <course_id> |commit"
errstring = "Error: unrecognized arguments: invalid-arg"
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode(self.course.id), 'commit', 'invalid-arg')
call_command('force_publish', unicode(self.course.id), '--commit', 'invalid-arg')
def test_course_key_not_found(self):
"""
@@ -51,7 +51,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
errstring = "Course not found."
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode('course-v1:org+course+run'))
call_command('force_publish', unicode('course-v1:org+course+run'))
def test_force_publish_non_split(self):
"""
@@ -60,7 +60,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
errstring = 'The owning modulestore does not support this command.'
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode(course.id))
call_command('force_publish', unicode(course.id))
@SharedModuleStoreTestCase.modifies_courseware
def test_force_publish(self):
@@ -91,7 +91,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
patched_yes_no.return_value = True
# force publish course
self.command.handle(unicode(self.course.id), 'commit')
call_command('force_publish', unicode(self.course.id), '--commit')
# verify that course has no changes
self.assertFalse(self.store.has_changes(self.store.get_item(self.course.location)))

View File

@@ -1,7 +1,6 @@
"""
Models for contentstore
"""
# pylint: disable=no-member
from django.db.models.fields import TextField

View File

@@ -1,11 +1,15 @@
"""
Test view handler for rerun (and eventually create)
"""
import ddt
from django.test.client import RequestFactory
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
from contentstore.tests.utils import AjaxEnabledTestClient, parse_json
@@ -13,6 +17,7 @@ from datetime import datetime
from xmodule.course_module import CourseFields
@ddt.ddt
class TestCourseListing(ModuleStoreTestCase):
"""
Unit tests for getting the list of courses for a logged in user
@@ -64,3 +69,21 @@ class TestCourseListing(ModuleStoreTestCase):
self.assertEqual(dest_course_key.run, 'copy')
dest_course = self.store.get_course(dest_course_key)
self.assertEqual(dest_course.start, CourseFields.start.default)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_newly_created_course_has_web_certs_enabled(self, store):
"""
Tests newly created course has web certs enabled by default.
"""
with modulestore().default_store(store):
response = self.client.ajax_post('/course/', {
'org': 'orgX',
'number': 'CS101',
'display_name': 'Course with web certs enabled',
'run': '2015_T2'
})
self.assertEqual(response.status_code, 200)
data = parse_json(response)
new_course_key = CourseKey.from_string(data['course_key'])
course = self.store.get_course(new_course_key)
self.assertTrue(course.cert_html_view_enabled)

View File

@@ -17,7 +17,7 @@ from django.conf import settings
from course_modes.models import CourseMode
from xmodule.library_tools import normalize_key_for_search
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SignalHandler
from xmodule.modulestore.django import SignalHandler, modulestore
from xmodule.modulestore.edit_info import EditInfoMixin
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import InheritanceMixin
@@ -335,6 +335,25 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
response = self.search()
self.assertEqual(response["total"], 5)
def _test_delete_course_from_search_index_after_course_deletion(self, store): # pylint: disable=invalid-name
"""
Test that course will also be delete from search_index after course deletion.
"""
self.DOCUMENT_TYPE = 'course_info' # pylint: disable=invalid-name
response = self.search()
self.assertEqual(response["total"], 0)
# index the course in search_index
self.reindex_course(store)
response = self.search()
self.assertEqual(response["total"], 1)
# delete the course and look course in search_index
modulestore().delete_course(self.course.id, self.user_id)
self.assertIsNone(modulestore().get_course(self.course.id))
response = self.search()
self.assertEqual(response["total"], 0)
def _test_deleting_item(self, store):
""" test deleting an item """
# Publish the vertical to start with
@@ -604,6 +623,11 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def test_course_location_null(self, store_type):
self._perform_test_using_store(store_type, self._test_course_location_null)
@ddt.data(*WORKS_WITH_STORES)
def test_delete_course_from_search_index_after_course_deletion(self, store_type):
""" Test for removing course from CourseAboutSearchIndexer """
self._perform_test_using_store(store_type, self._test_delete_course_from_search_index_after_course_deletion)
@patch('django.conf.settings.SEARCH_ENGINE', 'search.tests.utils.ForceRefreshElasticSearchEngine')
@ddt.ddt

View File

@@ -105,7 +105,7 @@ class LibraryTestCase(ModuleStoreTestCase):
if user not in self.session_data:
self.session_data[user] = {}
request = Mock(user=user, session=self.session_data[user])
_load_preview_module(request, descriptor) # pylint: disable=protected-access
_load_preview_module(request, descriptor)
def _update_item(self, usage_key, metadata):
"""

View File

@@ -10,8 +10,8 @@ from django.contrib.auth.models import User
from django.test.client import Client
from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation
from contentstore.utils import reverse_url # pylint: disable=import-error
from student.models import Registration # pylint: disable=import-error
from contentstore.utils import reverse_url
from student.models import Registration
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import ModuleStoreEnum

View File

@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, fixme
# pylint: disable=wildcard-import
"All view functions for contentstore, broken out into submodules"

View File

@@ -245,7 +245,7 @@ class CertificateManager(object):
"""
for cert_index, cert in enumerate(course.certificates['certificates']): # pylint: disable=unused-variable
if int(cert['id']) == int(certificate_id):
for sig_index, signatory in enumerate(cert.get('signatories')): # pylint: disable=unused-variable
for sig_index, signatory in enumerate(cert.get('signatories')):
if int(signatory_id) == int(signatory['id']):
_delete_asset(course.id, signatory['signature_image_path'])
del cert['signatories'][sig_index]

View File

@@ -21,7 +21,6 @@ from django.utils.translation import ugettext
__all__ = ['checklists_handler']
# pylint: disable=unused-argument
@require_http_methods(("GET", "POST", "PUT"))
@login_required
@ensure_csrf_cookie

View File

@@ -82,7 +82,6 @@ def _load_mixed_class(category):
return mixologist.mix(component_class)
# pylint: disable=unused-argument
@require_GET
@login_required
def container_handler(request, usage_key_string):

View File

@@ -6,7 +6,7 @@ from django.shortcuts import redirect
import json
import random
import logging
import string # pylint: disable=deprecated-module
import string
from django.utils.translation import ugettext as _
import django.utils
from django.contrib.auth.decorators import login_required
@@ -219,7 +219,6 @@ def _dismiss_notification(request, course_action_state_id): # pylint: disable=u
return JsonResponse({'success': True})
# pylint: disable=unused-argument
@login_required
def course_handler(request, course_key_string=None):
"""
@@ -741,8 +740,11 @@ def create_new_course_in_store(store, user, org, number, run, fields):
Separated out b/c command line course creation uses this as well as the web interface.
"""
# Set default language from settings
fields.update({'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en')})
# Set default language from settings and enable web certs
fields.update({
'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'),
'cert_html_view_enabled': True,
})
with modulestore().default_store(store):
# Creating the course raises DuplicateCourseError if an existing course with this org/name is found
@@ -803,7 +805,6 @@ def _rerun_course(request, org, number, run, fields):
})
# pylint: disable=unused-argument
@login_required
@ensure_csrf_cookie
@require_http_methods(["GET"])
@@ -836,7 +837,6 @@ def course_info_handler(request, course_key_string):
return HttpResponseBadRequest("Only supports html requests")
# pylint: disable=unused-argument
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT", "DELETE"))

View File

@@ -177,7 +177,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
course = modulestore().get_course(course_key)
if course is None:
return HttpResponse(status=400)
if not getattr(course, 'entrance_exam_id'):
if not course.entrance_exam_id:
return HttpResponse(status=404)
try:
exam_key = UsageKey.from_string(course.entrance_exam_id)
@@ -228,7 +228,7 @@ def _delete_entrance_exam(request, course_key):
# Reset the entrance exam flags on the course
# Reload the course so we have the latest state
course = store.get_course(course_key)
if getattr(course, 'entrance_exam_id'):
if course.entrance_exam_id:
metadata = {
'entrance_exam_enabled': False,
'entrance_exam_minimum_score_pct': None,

View File

@@ -59,7 +59,6 @@ log = logging.getLogger(__name__)
CONTENT_RE = re.compile(r"(?P<start>\d{1,11})-(?P<stop>\d{1,11})/(?P<end>\d{1,11})")
# pylint: disable=unused-argument
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT"))
@@ -359,7 +358,6 @@ def _save_request_status(request, key, status):
request.session.save()
# pylint: disable=unused-argument
@require_GET
@ensure_csrf_cookie
@login_required
@@ -458,7 +456,6 @@ def send_tarball(tarball):
return response
# pylint: disable=unused-argument
@ensure_csrf_cookie
@login_required
@require_http_methods(("GET",))

View File

@@ -88,7 +88,6 @@ def _filter_entrance_exam_grader(graders):
return graders
# pylint: disable=unused-argument
@require_http_methods(("DELETE", "GET", "PUT", "POST", "PATCH"))
@login_required
@expect_json
@@ -196,7 +195,6 @@ def xblock_handler(request, usage_key_string):
)
# pylint: disable=unused-argument
@require_http_methods(("GET"))
@login_required
@expect_json
@@ -259,7 +257,6 @@ def xblock_view_handler(request, usage_key_string, view_name):
'page_size': int(request.REQUEST.get('page_size', 0)),
}
except ValueError:
# pylint: disable=too-many-format-args
return HttpResponse(
content="Couldn't parse paging parameters: enable_paging: "
"{0}, page_number: {1}, page_size: {2}".format(
@@ -312,7 +309,6 @@ def xblock_view_handler(request, usage_key_string, view_name):
return HttpResponse(status=406)
# pylint: disable=unused-argument
@require_http_methods(("GET"))
@login_required
@expect_json
@@ -663,7 +659,6 @@ def _delete_item(usage_key, user):
store.delete_item(usage_key, user.id)
# pylint: disable=unused-argument
@login_required
@require_http_methods(("GET", "DELETE"))
def orphan_handler(request, course_key_string):

View File

@@ -230,7 +230,7 @@ class CertificatesListHandlerTestCase(EventTestMixin, CourseTestCase, Certificat
self.assertEqual(response.status_code, 201)
self.assertIn("Location", response)
content = json.loads(response.content)
certificate_id = self._remove_ids(content) # pylint: disable=unused-variable
certificate_id = self._remove_ids(content)
self.assertEqual(content, expected)
self.assert_event_emitted(
'edx.certificate.configuration.created',

View File

@@ -2,7 +2,6 @@
"""
Unit tests for video-related REST APIs.
"""
# pylint: disable=attribute-defined-outside-init
import csv
import json
import dateutil.parser

View File

@@ -34,7 +34,6 @@ def request_course_creator(request):
return JsonResponse({"Status": "OK"})
# pylint: disable=unused-argument
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT", "DELETE"))

View File

@@ -269,6 +269,8 @@ else:
DATABASES = AUTH_TOKENS['DATABASES']
# Enable automatic transaction management on all databases
# https://docs.djangoproject.com/en/1.8/topics/db/transactions/#tying-transactions-to-http-requests
# This needs to be true for all databases
for database_name in DATABASES:
DATABASES[database_name]['ATOMIC_REQUESTS'] = True

View File

@@ -13,11 +13,6 @@ from the same directory.
import os
from path import Path as path
# Pylint gets confused by path.py instances, which report themselves as class
# objects. As a result, pylint applies the wrong regex in validating names,
# and throws spurious errors. Therefore, we disable invalid-name checking.
# pylint: disable=invalid-name
########################## Prod-like settings ###################################
# These should be as close as possible to the settings we use in production.

View File

@@ -22,12 +22,7 @@ Longer TODO:
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=wildcard-import, unused-import, unused-wildcard-import
# Pylint gets confused by path.py instances, which report themselves as class
# objects. As a result, pylint applies the wrong regex in validating names,
# and throws spurious errors. Therefore, we disable invalid-name checking.
# pylint: disable=invalid-name
# pylint: disable=unused-import
import imp
import os
@@ -62,7 +57,7 @@ from xmodule.mixin import LicenseMixin
# Dummy secret key for dev/test
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
SECRET_KEY = 'dev key'
STUDIO_NAME = "Studio"
STUDIO_SHORT_NAME = "Studio"
@@ -1014,6 +1009,8 @@ ADVANCED_COMPONENT_TYPES = [
'notes',
'schoolyourself_review',
'schoolyourself_lesson',
# Office Mix
'officemix',
# Google Drive embedded components. These XBlocks allow one to
# embed public google drive documents and calendars within edX units

View File

@@ -35,9 +35,6 @@ var CourseDetails = Backbone.Model.extend({
if (newattrs.start_date === null) {
errors.start_date = gettext("The course must have an assigned start date.");
}
if (this.hasChanged("start_date") && this.get("has_cert_config") === false){
errors.start_date = gettext("The course must have at least one active certificate configuration before it can be started.");
}
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = gettext("The course end date must be later than the course start date.");
}

View File

@@ -72,13 +72,6 @@ define([
);
});
it('Changing course start date without active certificate configuration should result in error', function () {
this.view.$el.find('#course-start-date')
.val('10/06/2014')
.trigger('change');
expect(this.view.$el.find('span.message-error').text()).toContain("course must have at least one active certificate configuration");
});
it('Selecting a course in pre-requisite drop down should save it as part of course details', function () {
var pre_requisite_courses = ['test/CSS101/2012_T1'];
var requests = AjaxHelpers.requests(this),

View File

@@ -288,7 +288,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
var template = _.template(
'<li class="list-settings-item">' +
'<input type="text" class="input" value="<%= ele %>">' +
'<a href="#" class="remove-action remove-setting" data-index="<%= index %>"><i class="icon fa fa-remove-sign"></i><span class="sr">Remove</span></a>' +
'<a href="#" class="remove-action remove-setting" data-index="<%= index %>"><i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr">Remove</span></a>' +
'</li>'
);
list.append($(template({'ele': ele, 'index': index})));
@@ -455,7 +455,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
'<li class="list-settings-item">' +
'<input type="text" class="input input-key" value="<%= key %>">' +
'<input type="text" class="input input-value" value="<%= value %>">' +
'<a href="#" class="remove-action remove-setting" data-value="<%= value %>"><i class="icon fa fa-remove-sign"></i><span class="sr">Remove</span></a>' +
'<a href="#" class="remove-action remove-setting" data-value="<%= value %>"><i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr">Remove</span></a>' +
'</li>'
);

View File

@@ -1,7 +1,5 @@
<div class="wrapper wrapper-modal-window wrapper-modal-window-<%= name %>"
aria-describedby="modal-window-description"
aria-labelledby="modal-window-title"
aria-hidden=""
role="dialog">
<div class="modal-window-overlay"></div>
<div class="modal-window <%= viewSpecificClasses %> modal-<%= size %> modal-type-<%= type %>" tabindex="-1" aria-labelledby="modal-window-title">

View File

@@ -1,4 +1,4 @@
<div class="wrapper wrapper-modal-window wrapper-modal-window-edit-xblock" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="wrapper wrapper-modal-window wrapper-modal-window-edit-xblock" aria-labelledby="modal-window-title" role="dialog">
<div class="modal-window-overlay"></div>
<div class="modal-window confirm modal-med modal-type-html modal-editor" style="top: 50px; left: 400px;" tabindex="-1" aria-labelledby="modal-window-title">
<div class="edit-xblock-modal">

View File

@@ -1,6 +1,6 @@
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-section" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-section" aria-labelledby="modal-window-title" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-section-modal">

View File

@@ -1,6 +1,6 @@
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-subsection" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-subsection" aria-labelledby="modal-window-title" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-subsection-modal">

View File

@@ -1,6 +1,6 @@
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-unit" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-unit" aria-labelledby="modal-window-title" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-unit-modal">

View File

@@ -5,8 +5,6 @@ from django.conf.urls import patterns, include, url
from ratelimitbackend import admin
admin.autodiscover()
# pylint: disable=bad-continuation
# Pattern to match a course key or a library key
COURSELIKE_KEY_PATTERN = r'(?P<course_key_string>({}|{}))'.format(
r'[^/]+/[^/]+/[^/]+', r'[^/:]+:[^/+]+\+[^/+]+(\+[^/]+)?'

View File

@@ -344,7 +344,6 @@ class ConfigurationModelAPITests(TestCase):
request = self.factory.get('/config/ExampleConfig')
request.user = self.user
response = self.current_view(request)
# pylint: disable=no-member
self.assertEquals('', response.data['string_field'])
self.assertEquals(10, response.data['int_field'])
self.assertEquals(None, response.data['changed_by'])

View File

@@ -51,7 +51,7 @@ def xdomain_proxy(request): # pylint: disable=unused-argument
return HttpResponseNotFound()
allowed_domains = []
for domain in config.whitelist.split("\n"): # pylint: disable=no-member
for domain in config.whitelist.split("\n"):
if domain.strip():
allowed_domains.append(domain.strip())

View File

@@ -538,7 +538,7 @@ class CourseMode(models.Model):
return False
@classmethod
def min_course_price_for_currency(cls, course_id, currency): # pylint: disable=invalid-name
def min_course_price_for_currency(cls, course_id, currency):
"""
Returns the minimum price of the course in the appropriate currency over all the course's
non-expired modes.

View File

@@ -22,10 +22,10 @@ class DarkLangConfig(ConfigurationModel):
Example: ['it', 'de-at', 'es', 'pt-br']
"""
if not self.released_languages.strip(): # pylint: disable=no-member
if not self.released_languages.strip():
return []
languages = [lang.lower().strip() for lang in self.released_languages.split(',')] # pylint: disable=no-member
languages = [lang.lower().strip() for lang in self.released_languages.split(',')]
# Put in alphabetical order
languages.sort()
return languages

View File

@@ -92,7 +92,7 @@ class Role(models.Model):
if permission_blacked_out(course, {self.name}, permission):
return False
return self.permissions.filter(name=permission).exists() # pylint: disable=no-member
return self.permissions.filter(name=permission).exists()
class Permission(models.Model):

View File

@@ -88,7 +88,7 @@ class EmbargoedState(ConfigurationModel):
"""
if self.embargoed_countries == '':
return []
return [country.strip().upper() for country in self.embargoed_countries.split(',')] # pylint: disable=no-member
return [country.strip().upper() for country in self.embargoed_countries.split(',')]
class RestrictedCourse(models.Model):
@@ -703,7 +703,7 @@ class IPFilter(ConfigurationModel):
"""
if self.whitelist == '':
return []
return self.IPFilterList([addr.strip() for addr in self.whitelist.split(',')]) # pylint: disable=no-member
return self.IPFilterList([addr.strip() for addr in self.whitelist.split(',')])
@property
def blacklist_ips(self):
@@ -712,4 +712,4 @@ class IPFilter(ConfigurationModel):
"""
if self.blacklist == '':
return []
return self.IPFilterList([addr.strip() for addr in self.blacklist.split(',')]) # pylint: disable=no-member
return self.IPFilterList([addr.strip() for addr in self.blacklist.split(',')])

View File

@@ -3,7 +3,7 @@ import json
import logging
import random
import re
import string # pylint: disable=deprecated-module
import string
import fnmatch
import unicodedata
import urllib

View File

@@ -36,10 +36,9 @@ class MicroSiteSessionCookieTests(TestCase):
"""
Tests that non-microsite behaves according to default behavior
"""
response = self.client.get('/')
self.assertNotIn('test_microsite.localhost', str(getattr(response, 'cookies')['sessionid']))
self.assertNotIn('Domain', str(getattr(response, 'cookies')['sessionid']))
self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
self.assertNotIn('Domain', str(response.cookies['sessionid'])) # pylint: disable=no-member
def test_session_cookie_domain(self):
"""
@@ -47,9 +46,8 @@ class MicroSiteSessionCookieTests(TestCase):
is the one specially overridden in configuration,
in this case in test.py
"""
response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertIn('test_microsite.localhost', str(getattr(response, 'cookies')['sessionid']))
self.assertIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
@patch.dict("django.conf.settings.MICROSITE_CONFIGURATION", {'test_microsite': {'SESSION_COOKIE_DOMAIN': None}})
def test_microsite_none_cookie_domain(self):
@@ -57,7 +55,6 @@ class MicroSiteSessionCookieTests(TestCase):
Tests to make sure that a Microsite that specifies None for 'SESSION_COOKIE_DOMAIN' does not
set a domain on the session cookie
"""
response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertNotIn('test_microsite.localhost', str(getattr(response, 'cookies')['sessionid']))
self.assertNotIn('Domain', str(getattr(response, 'cookies')['sessionid']))
self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
self.assertNotIn('Domain', str(response.cookies['sessionid'])) # pylint: disable=no-member

View File

@@ -53,7 +53,7 @@ class Command(TrackedCommand):
)
@transaction.atomic
def handle(self, *args, **options): # pylint: disable=unused-argument
def handle(self, *args, **options):
source_key = CourseKey.from_string(options.get('source_course', ''))
dest_keys = []
for course_key in options.get('dest_course_list', '').split(','):

View File

@@ -1085,7 +1085,7 @@ class CourseEnrollment(models.Model):
log.exception(
u'Unable to emit event %s for user %s and course %s',
event_name,
self.user.username, # pylint: disable=no-member
self.user.username,
self.course_id,
)
@@ -1373,7 +1373,7 @@ class CourseEnrollment(models.Model):
def refund_cutoff_date(self):
""" Calculate and return the refund window end date. """
try:
attribute = self.attributes.get(namespace='order', name='order_number') # pylint: disable=no-member
attribute = self.attributes.get(namespace='order', name='order_number')
except ObjectDoesNotExist:
return None

View File

@@ -27,7 +27,7 @@ def register_access_role(cls):
"""
try:
role_name = getattr(cls, 'ROLE')
role_name = cls.ROLE
REGISTERED_ACCESS_ROLES[role_name] = cls
except AttributeError:
log.exception(u"Unable to register Access Role with attribute 'ROLE'.")
@@ -62,7 +62,7 @@ class AccessRole(object):
__metaclass__ = ABCMeta
@abstractmethod
def has_user(self, user): # pylint: disable=unused-argument
def has_user(self, user):
"""
Return whether the supplied django user has access to this role.
"""

View File

@@ -285,7 +285,7 @@ class LoginTest(TestCase):
self._assert_response(response, success=True)
# Reload the user from the database
self.user = User.objects.get(pk=self.user.pk) # pylint: disable=no-member
self.user = User.objects.get(pk=self.user.pk)
self.assertEqual(self.user.profile.get_meta()['session_id'], client1.session.session_key)

View File

@@ -15,7 +15,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from course_modes.tests.factories import CourseModeFactory
from student.models import CourseEnrollment, DashboardConfiguration
from student.views import get_course_enrollments, _get_recently_enrolled_courses # pylint: disable=protected-access
from student.views import get_course_enrollments, _get_recently_enrolled_courses
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')

View File

@@ -148,7 +148,7 @@ class RefundableTest(SharedModuleStoreTestCase):
)
self.enrollment.course_overview.start = course_start
self.enrollment.attributes.add(CourseEnrollmentAttribute( # pylint: disable=no-member
self.enrollment.attributes.add(CourseEnrollmentAttribute(
enrollment=self.enrollment,
namespace='order',
name='order_number',

View File

@@ -36,7 +36,7 @@ class TestStudentDashboardUnenrollments(ModuleStoreTestCase):
self.cert_status = None
self.client.login(username=self.USERNAME, password=self.PASSWORD)
def mock_cert(self, _user, _course_overview, _course_mode): # pylint: disable=unused-argument
def mock_cert(self, _user, _course_overview, _course_mode):
""" Return a preset certificate status. """
if self.cert_status is not None:
return {

View File

@@ -131,7 +131,7 @@ from openedx.core.djangoapps.programs.utils import is_student_dashboard_programs
log = logging.getLogger("edx.student")
AUDIT_LOG = logging.getLogger("audit")
ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=invalid-name
ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display')
SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated'
@@ -500,7 +500,7 @@ def is_course_blocked(request, redeemed_registration_codes, course_key):
# we have to check only for the invoice generated registration codes
# that their invoice is valid or not
if redeemed_registration.invoice_item:
if not getattr(redeemed_registration.invoice_item.invoice, 'is_valid'):
if not redeemed_registration.invoice_item.invoice.is_valid:
blocked = True
# disabling email notifications for unpaid registration courses
Optout.objects.get_or_create(user=request.user, course_id=course_key)
@@ -1632,7 +1632,7 @@ def create_account_with_params(request, params):
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
identity_args = [
user.id, # pylint: disable=no-member
user.id,
{
'email': user.email,
'username': user.username,
@@ -1895,13 +1895,13 @@ def auto_auth(request):
'username': username,
'email': email,
'password': password,
'user_id': user.id, # pylint: disable=no-member
'user_id': user.id,
'anonymous_id': anonymous_id_for_user(user, None),
})
else:
success_msg = u"{} user {} ({}) with password {} and user_id {}".format(
u"Logged in" if login_when_done else "Created",
username, email, password, user.id # pylint: disable=no-member
username, email, password, user.id
)
response = HttpResponse(success_msg)
response.set_cookie('csrftoken', csrf(request)['csrf_token'])
@@ -2285,7 +2285,7 @@ def change_email_settings(request):
return JsonResponse({"success": True})
def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invalid-name
def _get_course_programs(user, user_enrolled_courses):
""" Returns a dictionary of programs courses data require for the student
dashboard.

View File

@@ -1,5 +1,4 @@
# pylint: disable=missing-docstring
# pylint: disable=redefined-outer-name
import urllib
from lettuce import world

View File

@@ -43,7 +43,7 @@ def start_video_server():
video_source_dir = '{}/data/video'.format(settings.TEST_ROOT)
video_server = VideoSourceHttpService(port_num=settings.VIDEO_SOURCE_PORT)
video_server.config['root_dir'] = video_source_dir
setattr(world, 'video_source', video_server)
world.video_source = video_server
@after.all # pylint: disable=no-member

View File

@@ -255,7 +255,7 @@ class StubLtiHandler(StubHttpRequestHandler):
# Calculate and encode body hash. See http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
sha1 = hashlib.sha1()
sha1.update(body)
oauth_body_hash = unicode(base64.b64encode(sha1.digest())) # pylint: disable=too-many-function-args
oauth_body_hash = unicode(base64.b64encode(sha1.digest()))
params = client.get_oauth_params(None)
params.append((u'oauth_body_hash', oauth_body_hash))
mock_request = mock.Mock(

View File

@@ -1,5 +1,4 @@
# pylint: disable=missing-docstring
# pylint: disable=redefined-outer-name
from lettuce import world
@@ -22,7 +21,7 @@ from selenium.common.exceptions import (
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from nose.tools import assert_true # pylint: disable=no-name-in-module
from nose.tools import assert_true
GLOBAL_WAIT_FOR_TIMEOUT = 60

View File

@@ -124,7 +124,7 @@ admin.site.register(LTIProviderConfig, LTIProviderConfigAdmin)
class ApiPermissionsAdminForm(forms.ModelForm):
""" Django admin form for ApiPermissions model """
class Meta(object): # pylint: disable=missing-docstring
class Meta(object):
model = ProviderApiPermissions
fields = ['client', 'provider_id']

View File

@@ -60,7 +60,7 @@ class AuthNotConfigured(SocialAuthBaseException):
self.provider_name = provider_name
def __str__(self):
return _('Authentication with {} is currently unavailable.').format( # pylint: disable=no-member
return _('Authentication with {} is currently unavailable.').format(
self.provider_name
)
@@ -590,7 +590,7 @@ class ProviderApiPermissions(models.Model):
)
)
class Meta(object): # pylint: disable=missing-docstring
class Meta(object):
app_label = "third_party_auth"
verbose_name = "Provider API Permission"
verbose_name_plural = verbose_name + 's'

View File

@@ -58,7 +58,7 @@ See http://psa.matiasaguirre.net/docs/pipeline.html for more docs.
"""
import random
import string # pylint: disable=deprecated-module
import string
from collections import OrderedDict
import urllib
import analytics
@@ -151,19 +151,6 @@ class AuthEntryError(AuthException):
"""
class NotActivatedException(AuthException):
""" Raised when a user tries to login to an unverified account """
def __init__(self, backend, email):
self.email = email
super(NotActivatedException, self).__init__(backend, email)
def __str__(self):
return (
_('This account has not yet been activated. An activation email has been re-sent to {email_address}.')
.format(email_address=self.email)
)
class ProviderUserState(object):
"""Object representing the provider state (attached or not) for a user.
@@ -514,26 +501,27 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
# This parameter is used by the auth_exchange app, which always allows users to
# login, whether or not their account is validated.
pass
# IF the user has just registered a new account as part of this pipeline, that is fine
# and we allow the login to continue this once, because if we pause again to force the
# user to activate their account via email, the pipeline may get lost (e.g. email takes
# too long to arrive, user opens the activation email on a different device, etc.).
# This is consistent with first party auth and ensures that the pipeline completes
# fully, which is critical.
# But if this is an existing account, we refuse to allow them to login again until they
# check their email and activate the account.
elif social is not None:
# This third party account is already linked to a user account. That means that the
# user's account existed before this pipeline originally began (since the creation
# of the 'social' link entry occurs in one of the following pipeline steps).
# Reject this login attempt and tell the user to validate their account first.
# Send them another activation email:
student.views.reactivation_email_for_user(user)
raise NotActivatedException(backend, user.email)
# else: The user must have just successfully registered their account, so we proceed.
# We know they did not just login, because the login process rejects unverified users.
elif social is None:
# The user has just registered a new account as part of this pipeline. Their account
# is inactive but we allow the login to continue, because if we pause again to force
# the user to activate their account via email, the pipeline may get lost (e.g.
# email takes too long to arrive, user opens the activation email on a different
# device, etc.). This is consistent with first party auth and ensures that the
# pipeline completes fully, which is critical.
pass
else:
# This is an existing account, linked to a third party provider but not activated.
# Double-check these criteria:
assert user is not None
assert social is not None
# We now also allow them to login again, because if they had entered their email
# incorrectly then there would be no way for them to recover the account, nor
# register anew via SSO. See SOL-1324 in JIRA.
# However, we will log a warning for this case:
logger.warning(
'User "%s" is using third_party_auth to login but has not yet activated their account. ',
user.username
)
@partial.partial
@@ -601,7 +589,7 @@ def login_analytics(strategy, auth_entry, *args, **kwargs):
{
'category': "conversion",
'label': None,
'provider': getattr(kwargs['backend'], 'name')
'provider': kwargs['backend'].name
},
context={
'ip': tracking_context.get('ip'),

View File

@@ -67,11 +67,16 @@ def apply_settings(django_settings):
django_settings.SOCIAL_AUTH_RAISE_EXCEPTIONS = False
# Allow users to login using social auth even if their account is not verified yet
# The 'ensure_user_information' step controls this and only allows brand new users
# to login without verification. Repeat logins are not permitted until the account
# gets verified.
django_settings.INACTIVE_USER_LOGIN = True
django_settings.INACTIVE_USER_URL = '/auth/inactive'
# This is required since we [ab]use django's 'is_active' flag to indicate verified
# accounts; without this set to True, python-social-auth won't allow us to link the
# user's account to the third party account during registration (since the user is
# not verified at that point).
# We also generally allow unverified third party auth users to login (see the logic
# in ensure_user_information in pipeline.py) because otherwise users who use social
# auth to register with an invalid email address can become "stuck".
# TODO: Remove the following if/when email validation is separated from the is_active flag.
django_settings.SOCIAL_AUTH_INACTIVE_USER_LOGIN = True
django_settings.SOCIAL_AUTH_INACTIVE_USER_URL = '/auth/inactive'
# Context processors required under Django.
django_settings.SOCIAL_AUTH_UUID_LENGTH = 4

View File

@@ -2,8 +2,8 @@
"""
Code to manage fetching and storing the metadata of IdPs.
"""
#pylint: disable=no-member
from celery.task import task # pylint: disable=import-error,no-name-in-module
from celery.task import task
import datetime
import dateutil.parser
import logging

View File

@@ -10,6 +10,7 @@ from django.contrib import auth
from django.contrib.auth import models as auth_models
from django.contrib.messages.storage import fallback
from django.contrib.sessions.backends import cache
from django.core.urlresolvers import reverse
from django.test import utils as django_utils
from django.conf import settings as django_settings
from edxmako.tests import mako_middleware_process_request
@@ -18,6 +19,7 @@ from social.apps.django_app import utils as social_utils
from social.apps.django_app import views as social_views
from student import models as student_models
from student import views as student_views
from student.tests.factories import UserFactory
from student_account.views import account_settings_context
from third_party_auth import middleware, pipeline
@@ -25,6 +27,176 @@ from third_party_auth import settings as auth_settings
from third_party_auth.tests import testutil
class IntegrationTestMixin(object):
"""
Mixin base class for third_party_auth integration tests.
This class is newer and simpler than the 'IntegrationTest' alternative below, but it is
currently less comprehensive. Some providers are tested with this, others with
IntegrationTest.
"""
# Provider information:
PROVIDER_NAME = "override"
PROVIDER_BACKEND = "override"
PROVIDER_ID = "override"
# Information about the user expected from the provider:
USER_EMAIL = "override"
USER_NAME = "override"
USER_USERNAME = "override"
def setUp(self):
super(IntegrationTestMixin, self).setUp()
self.login_page_url = reverse('signin_user')
self.register_page_url = reverse('register_user')
patcher = testutil.patch_mako_templates()
patcher.start()
self.addCleanup(patcher.stop)
# Override this method in a subclass and enable at least one provider.
def test_register(self):
# The user goes to the register page, and sees a button to register with the provider:
provider_register_url = self._check_register_page()
# The user clicks on the Dummy button:
try_login_response = self.client.get(provider_register_url)
# The user should be redirected to the provider's login page:
self.assertEqual(try_login_response.status_code, 302)
provider_response = self.do_provider_login(try_login_response['Location'])
# We should be redirected to the register screen since this account is not linked to an edX account:
self.assertEqual(provider_response.status_code, 302)
self.assertEqual(provider_response['Location'], self.url_prefix + self.register_page_url)
register_response = self.client.get(self.register_page_url)
tpa_context = register_response.context["data"]["third_party_auth"]
self.assertEqual(tpa_context["errorMessage"], None)
# Check that the "You've successfully signed into [PROVIDER_NAME]" message is shown.
self.assertEqual(tpa_context["currentProvider"], self.PROVIDER_NAME)
# Check that the data (e.g. email) from the provider is displayed in the form:
form_data = register_response.context['data']['registration_form_desc']
form_fields = {field['name']: field for field in form_data['fields']}
self.assertEqual(form_fields['email']['defaultValue'], self.USER_EMAIL)
self.assertEqual(form_fields['name']['defaultValue'], self.USER_NAME)
self.assertEqual(form_fields['username']['defaultValue'], self.USER_USERNAME)
# Now complete the form:
ajax_register_response = self.client.post(
reverse('user_api_registration'),
{
'email': 'email-edited@tpa-test.none',
'name': 'My Customized Name',
'username': 'new_username',
'honor_code': True,
}
)
self.assertEqual(ajax_register_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(tpa_context["finishAuthUrl"])
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
# Now check that we can login again, whether or not we have yet verified the account:
self.client.logout()
self._test_return_login(user_is_activated=False)
self.client.logout()
self.verify_user_email('email-edited@tpa-test.none')
self._test_return_login(user_is_activated=True)
def test_login(self):
user = UserFactory.create()
# The user goes to the login page, and sees a button to login with this provider:
provider_login_url = self._check_login_page()
# The user clicks on the provider's button:
try_login_response = self.client.get(provider_login_url)
# The user should be redirected to the provider's login page:
self.assertEqual(try_login_response.status_code, 302)
complete_response = self.do_provider_login(try_login_response['Location'])
# We should be redirected to the login screen since this account is not linked to an edX account:
self.assertEqual(complete_response.status_code, 302)
self.assertEqual(complete_response['Location'], self.url_prefix + self.login_page_url)
login_response = self.client.get(self.login_page_url)
tpa_context = login_response.context["data"]["third_party_auth"]
self.assertEqual(tpa_context["errorMessage"], None)
# Check that the "You've successfully signed into [PROVIDER_NAME]" message is shown.
self.assertEqual(tpa_context["currentProvider"], self.PROVIDER_NAME)
# Now the user enters their username and password.
# The AJAX on the page will log them in:
ajax_login_response = self.client.post(
reverse('user_api_login_session'),
{'email': user.email, 'password': 'test'}
)
self.assertEqual(ajax_login_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(tpa_context["finishAuthUrl"])
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
# Now check that we can login again:
self.client.logout()
self._test_return_login()
def do_provider_login(self, provider_redirect_url):
"""
mock logging in to the provider
Should end with loading self.complete_url, which should be returned
"""
raise NotImplementedError
def _test_return_login(self, user_is_activated=True):
""" Test logging in to an account that is already linked. """
# Make sure we're not logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 302)
# The user goes to the login page, and sees a button to login with this provider:
provider_login_url = self._check_login_page()
# The user clicks on the provider's login button:
try_login_response = self.client.get(provider_login_url)
# The user should be redirected to the provider:
self.assertEqual(try_login_response.status_code, 302)
login_response = self.do_provider_login(try_login_response['Location'])
# There will be one weird redirect required to set the login cookie:
self.assertEqual(login_response.status_code, 302)
self.assertEqual(login_response['Location'], self.url_prefix + self.complete_url)
# And then we should be redirected to the dashboard:
login_response = self.client.get(login_response['Location'])
self.assertEqual(login_response.status_code, 302)
if user_is_activated:
url_expected = reverse('dashboard')
else:
url_expected = reverse('third_party_inactive_redirect') + '?next=' + reverse('dashboard')
self.assertEqual(login_response['Location'], self.url_prefix + url_expected)
# Now we are logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 200)
def _check_login_page(self):
"""
Load the login form and check that it contains a button for the provider.
Return the URL for logging into that provider.
"""
return self._check_login_or_register_page(self.login_page_url, "loginUrl")
def _check_register_page(self):
"""
Load the registration form and check that it contains a button for the provider.
Return the URL for registering with that provider.
"""
return self._check_login_or_register_page(self.register_page_url, "registerUrl")
def _check_login_or_register_page(self, url, url_to_return):
""" Shared logic for _check_login_page() and _check_register_page() """
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn(self.PROVIDER_NAME, response.content)
context_data = response.context['data']['third_party_auth']
provider_urls = {provider['id']: provider[url_to_return] for provider in context_data['providers']}
self.assertIn(self.PROVIDER_ID, provider_urls)
return provider_urls[self.PROVIDER_ID]
@property
def complete_url(self):
""" Get the auth completion URL for this provider """
return reverse('social:complete', kwargs={'backend': self.PROVIDER_BACKEND})
@unittest.skipUnless(
testutil.AUTH_FEATURES_KEY in django_settings.FEATURES, testutil.AUTH_FEATURES_KEY + ' not in settings.FEATURES')
@django_utils.override_settings() # For settings reversion on a method-by-method basis.

View File

@@ -0,0 +1,33 @@
"""
Use the 'Dummy' auth provider for generic integration tests of third_party_auth.
"""
import unittest
from third_party_auth.tests import testutil
from .base import IntegrationTestMixin
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, 'third_party_auth not enabled')
class GenericIntegrationTest(IntegrationTestMixin, testutil.TestCase):
"""
Basic integration tests of third_party_auth using Dummy provider
"""
PROVIDER_ID = "oa2-dummy"
PROVIDER_NAME = "Dummy"
PROVIDER_BACKEND = "dummy"
USER_EMAIL = "adama@fleet.colonies.gov"
USER_NAME = "William Adama"
USER_USERNAME = "Galactica1"
def setUp(self):
super(GenericIntegrationTest, self).setUp()
self.configure_dummy_provider(enabled=True)
def do_provider_login(self, provider_redirect_url):
"""
Mock logging in to the Dummy provider
"""
# For the Dummy provider, the provider redirect URL is self.complete_url
self.assertEqual(provider_redirect_url, self.url_prefix + self.complete_url)
return self.client.get(provider_redirect_url)

View File

@@ -29,6 +29,8 @@ class IntegrationTestLTI(testutil.TestCase):
def setUp(self):
super(IntegrationTestLTI, self).setUp()
self.client.defaults['SERVER_NAME'] = 'testserver'
self.url_prefix = 'http://testserver'
self.configure_lti_provider(
name='Other Tool Consumer 1', enabled=True,
lti_consumer_key='other1',

View File

@@ -1,38 +1,36 @@
"""
Third_party_auth integration tests using a mock version of the TestShib provider
"""
import json
import unittest
import httpretty
from mock import patch
from django.core.urlresolvers import reverse
from student.tests.factories import UserFactory
from third_party_auth.tasks import fetch_saml_metadata
from third_party_auth.tests import testutil
from openedx.core.lib.js_utils import escape_json_dumps
from .base import IntegrationTestMixin
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml'
TESTSHIB_SSO_URL = 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO'
TPA_TESTSHIB_LOGIN_URL = '/auth/login/tpa-saml/?auth_entry=login&next=%2Fdashboard&idp=testshib'
TPA_TESTSHIB_REGISTER_URL = '/auth/login/tpa-saml/?auth_entry=register&next=%2Fdashboard&idp=testshib'
TPA_TESTSHIB_COMPLETE_URL = '/auth/complete/tpa-saml/'
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, 'third_party_auth not enabled')
class TestShibIntegrationTest(testutil.SAMLTestCase):
class TestShibIntegrationTest(IntegrationTestMixin, testutil.SAMLTestCase):
"""
TestShib provider Integration Test, to test SAML functionality
"""
PROVIDER_ID = "saml-testshib"
PROVIDER_NAME = "TestShib"
PROVIDER_BACKEND = "tpa-saml"
USER_EMAIL = "myself@testshib.org"
USER_NAME = "Me Myself And I"
USER_USERNAME = "myself"
def setUp(self):
super(TestShibIntegrationTest, self).setUp()
self.login_page_url = reverse('signin_user')
self.register_page_url = reverse('register_user')
self.enable_saml(
private_key=self._get_private_key(),
public_key=self._get_public_key(),
@@ -53,13 +51,14 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
uid_patch = patch('onelogin.saml2.utils.OneLogin_Saml2_Utils.generate_unique_id', return_value='TESTID')
uid_patch.start()
self.addCleanup(uid_patch.stop)
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
def test_login_before_metadata_fetched(self):
self._configure_testshib_provider(fetch_metadata=False)
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
testshib_login_url = self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
try_login_response = self.client.get(testshib_login_url)
# The user should be redirected to back to the login page:
self.assertEqual(try_login_response.status_code, 302)
self.assertEqual(try_login_response['Location'], self.url_prefix + self.login_page_url)
@@ -68,115 +67,15 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self.assertEqual(response.status_code, 200)
self.assertIn('Authentication with TestShib is currently unavailable.', response.content)
def test_register(self):
self._configure_testshib_provider()
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
# The user goes to the register page, and sees a button to register with TestShib:
self._check_register_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_REGISTER_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
testshib_response = self._fake_testshib_login_and_return()
# We should be redirected to the register screen since this account is not linked to an edX account:
self.assertEqual(testshib_response.status_code, 302)
self.assertEqual(testshib_response['Location'], self.url_prefix + self.register_page_url)
register_response = self.client.get(self.register_page_url)
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
# Now do a crude check that the data (e.g. email) from the provider is displayed in the form:
self.assertIn('"defaultValue": "myself@testshib.org"', register_response.content)
self.assertIn('"defaultValue": "Me Myself And I"', register_response.content)
# Now complete the form:
ajax_register_response = self.client.post(
reverse('user_api_registration'),
{
'email': 'myself@testshib.org',
'name': 'Myself',
'username': 'myself',
'honor_code': True,
}
)
self.assertEqual(ajax_register_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
# Now check that we can login again:
self.client.logout()
self.verify_user_email('myself@testshib.org')
self._test_return_login()
def test_login(self):
""" Configure TestShib before running the login test """
self._configure_testshib_provider()
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
user = UserFactory.create()
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
testshib_response = self._fake_testshib_login_and_return()
# We should be redirected to the login screen since this account is not linked to an edX account:
self.assertEqual(testshib_response.status_code, 302)
self.assertEqual(testshib_response['Location'], self.url_prefix + self.login_page_url)
login_response = self.client.get(self.login_page_url)
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', login_response.content)
self.assertIn('"errorMessage": null', login_response.content)
# Now the user enters their username and password.
# The AJAX on the page will log them in:
ajax_login_response = self.client.post(
reverse('user_api_login_session'),
{'email': user.email, 'password': 'test'}
)
self.assertEqual(ajax_login_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
super(TestShibIntegrationTest, self).test_login()
# Now check that we can login again:
self.client.logout()
self._test_return_login()
def _test_return_login(self):
""" Test logging in to an account that is already linked. """
# Make sure we're not logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 302)
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
login_response = self._fake_testshib_login_and_return()
# There will be one weird redirect required to set the login cookie:
self.assertEqual(login_response.status_code, 302)
self.assertEqual(login_response['Location'], self.url_prefix + TPA_TESTSHIB_COMPLETE_URL)
# And then we should be redirected to the dashboard:
login_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
self.assertEqual(login_response.status_code, 302)
self.assertEqual(login_response['Location'], self.url_prefix + reverse('dashboard'))
# Now we are logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 200)
def test_register(self):
""" Configure TestShib before running the register test """
self._configure_testshib_provider()
super(TestShibIntegrationTest, self).test_register()
def _freeze_time(self, timestamp):
""" Mock the current time for SAML, so we can replay canned requests/responses """
@@ -184,22 +83,6 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
now_patch.start()
self.addCleanup(now_patch.stop)
def _check_login_page(self):
""" Load the login form and check that it contains a TestShib button """
response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(escape_json_dumps(TPA_TESTSHIB_LOGIN_URL), response.content)
return response
def _check_register_page(self):
""" Load the login form and check that it contains a TestShib button """
response = self.client.get(self.register_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(escape_json_dumps(TPA_TESTSHIB_REGISTER_URL), response.content)
return response
def _configure_testshib_provider(self, **kwargs):
""" Enable and configure the TestShib SAML IdP as a third_party_auth provider """
fetch_metadata = kwargs.pop('fetch_metadata', True)
@@ -219,11 +102,12 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self.assertEqual(num_changed, 1)
self.assertEqual(num_total, 1)
def _fake_testshib_login_and_return(self):
def do_provider_login(self, provider_redirect_url):
""" Mocked: the user logs in to TestShib and then gets redirected back """
# The SAML provider (TestShib) will authenticate the user, then get the browser to POST a response:
self.assertTrue(provider_redirect_url.startswith(TESTSHIB_SSO_URL))
return self.client.post(
TPA_TESTSHIB_COMPLETE_URL,
self.complete_url,
content_type='application/x-www-form-urlencoded',
data=self.read_data_file('testshib_response.txt'),
)

View File

@@ -1,7 +1,7 @@
"""
Test the views served by third_party_auth.
"""
# pylint: disable=no-member
import ddt
from lxml import etree
from onelogin.saml2.errors import OneLogin_Saml2_Error

View File

@@ -10,6 +10,7 @@ from django.contrib.auth.models import User
from provider.oauth2.models import Client as OAuth2Client
from provider import constants
import django.test
from mako.template import Template
import mock
import os.path
@@ -27,6 +28,18 @@ AUTH_FEATURES_KEY = 'ENABLE_THIRD_PARTY_AUTH'
AUTH_FEATURE_ENABLED = AUTH_FEATURES_KEY in settings.FEATURES
def patch_mako_templates():
""" Patch mako so the django test client can access template context """
orig_render = Template.render_unicode
def wrapped_render(*args, **kwargs):
""" Render the template and send the context info to any listeners that want it """
django.test.signals.template_rendered.send(sender=None, template=None, context=kwargs)
return orig_render(*args, **kwargs)
return mock.patch.multiple(Template, render_unicode=wrapped_render, render=wrapped_render)
class FakeDjangoSettings(object):
"""A fake for Django settings."""
@@ -109,6 +122,13 @@ class ThirdPartyAuthTestMixin(object):
kwargs.setdefault("secret", "test")
return cls.configure_oauth_provider(**kwargs)
@classmethod
def configure_dummy_provider(cls, **kwargs):
""" Update the settings for the Twitter third party auth provider/backend """
kwargs.setdefault("name", "Dummy")
kwargs.setdefault("backend_name", "dummy")
return cls.configure_oauth_provider(**kwargs)
@classmethod
def verify_user_email(cls, email):
""" Mark the user with the given email as verified """
@@ -135,19 +155,18 @@ class ThirdPartyAuthTestMixin(object):
class TestCase(ThirdPartyAuthTestMixin, django.test.TestCase):
"""Base class for auth test cases."""
pass
def setUp(self):
super(TestCase, self).setUp()
# Explicitly set a server name that is compatible with all our providers:
# (The SAML lib we use doesn't like the default 'testserver' as a domain)
self.client.defaults['SERVER_NAME'] = 'example.none'
self.url_prefix = 'http://example.none'
class SAMLTestCase(TestCase):
"""
Base class for SAML-related third_party_auth tests
"""
def setUp(self):
super(SAMLTestCase, self).setUp()
self.client.defaults['SERVER_NAME'] = 'example.none' # The SAML lib we use doesn't like testserver' as a domain
self.url_prefix = 'http://example.none'
@classmethod
def _get_public_key(cls, key_name='saml_key'):
""" Get a public key for use in the test. """

View File

@@ -6,7 +6,7 @@ from .views import inactive_user_view, saml_metadata_view, lti_login_and_complet
urlpatterns = patterns(
'',
url(r'^auth/inactive', inactive_user_view),
url(r'^auth/inactive', inactive_user_view, name="third_party_inactive_redirect"),
url(r'^auth/saml/metadata.xml', saml_metadata_view),
url(r'^auth/login/(?P<backend>lti)/$', lti_login_and_complete_view),
url(r'^auth/', include('social.apps.django_app.urls', namespace='social')),

View File

@@ -17,8 +17,13 @@ URL_NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'socia
def inactive_user_view(request):
"""
A newly registered user has completed the social auth pipeline.
Their account is not yet activated, but we let them login this once.
A newly or recently registered user has completed the social auth pipeline.
Their account is not yet activated, but we let them login since the third party auth
provider is trusted to vouch for them. See details in pipeline.py.
The reason this view exists is that if we don't define this as the
SOCIAL_AUTH_INACTIVE_USER_URL, inactive users will get sent to LOGIN_ERROR_URL, which we
don't want.
"""
# 'next' may be set to '/account/finish_auth/.../' if this user needs to be auto-enrolled
# in a course. Otherwise, just redirect them to the dashboard, which displays a message

View File

@@ -11,7 +11,6 @@ from __future__ import absolute_import
import abc
# pylint: disable=unused-argument
class BaseBackend(object):
"""
Abstract Base Class for event tracking backends.

View File

@@ -113,6 +113,5 @@ class DummyBackend(BaseBackend):
self.flag = options.get('flag', False)
self.count = 0
# pylint: disable=unused-argument
def send(self, event):
self.count += 1

View File

@@ -164,7 +164,6 @@ class OuterAtomic(transaction.Atomic):
# The inner atomic starts a savepoint around the test.
# So, for tests only, there should be exactly one savepoint_id and two atomic_for_testcase_calls.
# atomic_for_testcase_calls below is added in a monkey-patch for tests only.
# pylint: disable=no-member
if self.ALLOW_NESTED and (self.atomic_for_testcase_calls - len(connection.savepoint_ids)) < 1:
raise transaction.TransactionManagementError('Cannot be inside an atomic block.')

View File

@@ -1,4 +1,3 @@
# pylint: disable=invalid-name
"""
Utility library for working with the edx-organizations app
"""

View File

@@ -1,4 +1,3 @@
# pylint: disable=no-member
"""
This file exposes a number of password complexity validators which can be optionally added to
account creation
@@ -7,7 +6,7 @@ This file was inspired by the django-passwords project at https://github.com/dst
authored by dstufft (https://github.com/dstufft)
"""
from __future__ import division
import string # pylint: disable=deprecated-module
import string
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

View File

@@ -1,3 +1,8 @@
"""
Utility Mixins for unit tests
"""
import json
import sys
from mock import patch
@@ -95,6 +100,19 @@ class EventTestMixin(object):
self.mock_tracker.reset_mock()
class PatchMediaTypeMixin(object):
"""
Generic mixin for verifying unsupported media type in PATCH
"""
def test_patch_unsupported_media_type(self):
response = self.client.patch(
self.url,
json.dumps({}),
content_type=self.unsupported_media_type
)
self.assertEqual(response.status_code, 415)
def patch_testcase():
"""
Disable commit_on_success decorators for tests in TestCase subclasses.

View File

@@ -18,5 +18,5 @@ def reload_django_url_config():
if urlconf and urlconf in sys.modules:
reload(sys.modules[urlconf])
reloaded = import_module(urlconf)
reloaded_urls = getattr(reloaded, 'urlpatterns')
reloaded_urls = reloaded.urlpatterns
set_urlconf(tuple(reloaded_urls))

View File

@@ -56,13 +56,18 @@ def jsonable_server_error(request, template_name='500.html'):
return server_error(request, template_name=template_name)
def handle_500(template_path, context=None):
def handle_500(template_path, context=None, test_func=None):
"""
Decorator for view specific 500 error handling.
Custom handling will be skipped only if test_func is passed and it returns False
Usage::
Usage:
@handle_500(template_path='certificates/server-error.html', context={'error-info': 'Internal Server Error'})
@handle_500(
template_path='certificates/server-error.html',
context={'error-info': 'Internal Server Error'},
test_func=lambda request: request.GET.get('preview', None)
)
def my_view(request):
# Any unhandled exception in this view would be handled by the handle_500 decorator
# ...
@@ -83,9 +88,15 @@ def handle_500(template_path, context=None):
if settings.DEBUG:
# In debug mode let django process the 500 errors and display debug info for the developer
raise
else:
elif test_func is None or test_func(request):
# Display custom 500 page if either
# 1. test_func is None (meaning nothing to test)
# 2. or test_func(request) returns True
log.exception("Error in django view.")
return render_to_response(template_path, context)
else:
# Do not show custom 500 error when test fails
raise
return inner
return decorator

View File

@@ -29,7 +29,7 @@ class XBlockDisableConfig(ConfigurationModel):
if not config.enabled:
return False
return block_type in config.disabled_blocks.split() # pylint: disable=no-member
return block_type in config.disabled_blocks.split()
@classmethod
def disabled_block_types(cls):
@@ -39,4 +39,4 @@ class XBlockDisableConfig(ConfigurationModel):
if not config.enabled:
return ()
return config.disabled_blocks.split() # pylint: disable=no-member
return config.disabled_blocks.split()

View File

@@ -59,7 +59,10 @@ class DjangoXBlockUserService(UserService):
if django_user is not None and django_user.is_authenticated():
# This full_name is dependent on edx-platform's profile implementation
full_name = getattr(django_user.profile, 'name') if hasattr(django_user, 'profile') else None
if hasattr(django_user, 'profile'):
full_name = django_user.profile.name
else:
full_name = None
xblock_user.full_name = full_name
xblock_user.emails = [django_user.email]
xblock_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED] = True

View File

@@ -152,7 +152,7 @@ class ClarificationRenderer(object):
self.system = system
# Get any text content found inside this tag prior to the first child tag. It may be a string or None type.
initial_text = xml.text if xml.text else ''
self.inner_html = initial_text + ''.join(etree.tostring(element) for element in xml) # pylint: disable=no-member
self.inner_html = initial_text + ''.join(etree.tostring(element) for element in xml)
self.tail = xml.tail
def get_html(self):
@@ -161,7 +161,7 @@ class ClarificationRenderer(object):
"""
context = {'clarification': self.inner_html}
html = self.system.render_template("clarification.html", context)
xml = etree.XML(html) # pylint: disable=no-member
xml = etree.XML(html)
# We must include any text that was following our original <clarification>...</clarification> XML node.:
xml.tail = self.tail
return xml

View File

@@ -467,7 +467,7 @@ class ChoiceGroup(InputTypeBase):
raise Exception(msg)
self.choices = self.extract_choices(self.xml, i18n)
self._choices_map = dict(self.choices,) # pylint: disable=attribute-defined-outside-init
self._choices_map = dict(self.choices,)
@classmethod
def get_attributes(cls):

View File

@@ -55,7 +55,7 @@ log = logging.getLogger(__name__)
registry = TagRegistry()
CorrectMap = correctmap.CorrectMap # pylint: disable=invalid-name
CorrectMap = correctmap.CorrectMap
CORRECTMAP_PY = None
# Make '_' a no-op so we can scrape strings. Using lambda instead of
@@ -2666,7 +2666,7 @@ class SymbolicResponse(CustomResponse):
## score: Points to be assigned (numeric, can be float)
## msg: Message from grader to display to student (string)
ScoreMessage = namedtuple('ScoreMessage', ['valid', 'correct', 'points', 'msg']) # pylint: disable=invalid-name
ScoreMessage = namedtuple('ScoreMessage', ['valid', 'correct', 'points', 'msg'])
@registry.register
@@ -3055,7 +3055,7 @@ class ExternalResponse(LoncapaResponse):
cmap = CorrectMap()
try:
submission = [student_answers[k] for k in idset]
except Exception as err: # pylint: disable=broad-except
except Exception as err:
log.error(
'Error %s: cannot get student answer for %s; student_answers=%s',
err,

Some files were not shown because too many files have changed in this diff Show More