Merge remote-tracking branch 'origin/master' into will/combine-reg-login-form
Conflicts: lms/static/sass/base/_grid-settings.scss lms/static/sass/shared/_footer.scss lms/static/sass/shared/_header.scss
This commit is contained in:
@@ -22,6 +22,7 @@ from xmodule.exceptions import NotFoundError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StaticContentServer(object):
|
||||
def process_request(self, request):
|
||||
# look to see if the request is prefixed with an asset prefix tag
|
||||
|
||||
@@ -322,6 +322,7 @@ def add_cohort(course_key, name):
|
||||
)
|
||||
return cohort
|
||||
|
||||
|
||||
def add_user_to_cohort(cohort, username_or_email):
|
||||
"""
|
||||
Look up the given user, and if successful, add them to the specified cohort.
|
||||
|
||||
@@ -209,6 +209,7 @@ class ListCohortsTestCase(CohortViewsTestCase):
|
||||
actual_cohorts,
|
||||
)
|
||||
|
||||
|
||||
class AddCohortTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
Tests the `add_cohort` view.
|
||||
|
||||
@@ -151,7 +151,6 @@ class CourseModeViewTest(ModuleStoreTestCase):
|
||||
response = self.client.get(choose_track_url)
|
||||
self.assertRedirects(response, reverse('dashboard'))
|
||||
|
||||
|
||||
# Mapping of course modes to the POST parameters sent
|
||||
# when the user chooses that mode.
|
||||
POST_PARAMS_FOR_COURSE_MODE = {
|
||||
|
||||
@@ -94,6 +94,7 @@ class ChooseModeView(View):
|
||||
"error": error,
|
||||
"upgrade": upgrade,
|
||||
"can_audit": "audit" in modes,
|
||||
"responsive": True
|
||||
}
|
||||
if "verified" in modes:
|
||||
context["suggested_prices"] = [
|
||||
|
||||
@@ -144,7 +144,6 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
self.process_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
)
|
||||
|
||||
|
||||
def assertSessionLangEquals(self, value, request):
|
||||
"""
|
||||
Assert that the 'django_language' set in request.session is equal to value
|
||||
|
||||
@@ -4,6 +4,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from django_comment_common.models import Role
|
||||
from student.models import CourseEnrollment, User
|
||||
|
||||
|
||||
class RoleAssignmentTest(TestCase):
|
||||
"""
|
||||
Basic checks to make sure our Roles get assigned and unassigned as students
|
||||
|
||||
@@ -34,7 +34,6 @@ class MakoLoader(object):
|
||||
|
||||
self.module_directory = module_directory
|
||||
|
||||
|
||||
def __call__(self, template_name, template_dirs=None):
|
||||
return self.load_template(template_name, template_dirs)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ def clear_lookups(namespace):
|
||||
if namespace in LOOKUP:
|
||||
del LOOKUP[namespace]
|
||||
|
||||
|
||||
def add_lookup(namespace, directory, package=None, prepend=False):
|
||||
"""
|
||||
Adds a new mako template lookup directory to the given namespace.
|
||||
|
||||
@@ -76,6 +76,7 @@ def marketing_link_context_processor(request):
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def open_source_footer_context_processor(request):
|
||||
"""
|
||||
Checks the site name to determine whether to use the edX.org footer or the Open Source Footer.
|
||||
@@ -97,6 +98,7 @@ def microsite_footer_context_processor(request):
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary, context=None, namespace='main'):
|
||||
|
||||
# see if there is an override template defined in the microsite
|
||||
|
||||
@@ -19,6 +19,7 @@ from edxmako.shortcuts import (
|
||||
from student.tests.factories import UserFactory
|
||||
from util.testing import UrlResetMixin
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShortcutsTests(UrlResetMixin, TestCase):
|
||||
"""
|
||||
|
||||
@@ -28,6 +28,7 @@ from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
# that disables the XML modulestore.
|
||||
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
|
||||
@@ -37,6 +37,7 @@ FEATURES_WITHOUT_SSL_AUTH['AUTH_USE_CERTIFICATES'] = False
|
||||
|
||||
TEST_DATA_MIXED_MODULESTORE = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
|
||||
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH)
|
||||
class SSLClientTest(ModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.db.utils import DatabaseError
|
||||
import mock
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
|
||||
class HeartbeatTestCase(TestCase):
|
||||
"""
|
||||
Test the heartbeat
|
||||
|
||||
@@ -23,6 +23,7 @@ def page_title_breadcrumbs(*crumbs, **kwargs):
|
||||
else:
|
||||
return platform_name()
|
||||
|
||||
|
||||
@register.simple_tag(name="page_title_breadcrumbs", takes_context=True)
|
||||
def page_title_breadcrumbs_tag(context, *crumbs):
|
||||
"""
|
||||
@@ -42,7 +43,7 @@ def platform_name():
|
||||
|
||||
|
||||
@register.simple_tag(name="favicon_path")
|
||||
def favicon_path(default=getattr(settings,'FAVICON_PATH', 'images/favicon.ico')):
|
||||
def favicon_path(default=getattr(settings, 'FAVICON_PATH', 'images/favicon.ico')):
|
||||
"""
|
||||
Django template tag that outputs the configured favicon:
|
||||
{% favicon_path %}
|
||||
|
||||
@@ -78,6 +78,7 @@ def post_save_metrics(sender, **kwargs):
|
||||
tags = _database_tags(action, sender, kwargs)
|
||||
dog_stats_api.increment('edxapp.db.model', tags=tags)
|
||||
|
||||
|
||||
@receiver(post_delete, dispatch_uid='edxapp.monitoring.post_delete_metrics')
|
||||
def post_delete_metrics(sender, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Register signal handlers
|
||||
import signals
|
||||
import exceptions
|
||||
import exceptions
|
||||
|
||||
@@ -3,11 +3,12 @@ import threading
|
||||
_request_cache_threadlocal = threading.local()
|
||||
_request_cache_threadlocal.data = {}
|
||||
|
||||
|
||||
class RequestCache(object):
|
||||
@classmethod
|
||||
def get_request_cache(cls):
|
||||
return _request_cache_threadlocal
|
||||
|
||||
|
||||
def clear_request_cache(self):
|
||||
_request_cache_threadlocal.data = {}
|
||||
|
||||
@@ -17,4 +18,4 @@ class RequestCache(object):
|
||||
|
||||
def process_response(self, request, response):
|
||||
self.clear_request_cache()
|
||||
return response
|
||||
return response
|
||||
|
||||
@@ -36,7 +36,6 @@ class TestStatus(TestCase):
|
||||
"edX/toy/2012_Fall" : "A toy story"
|
||||
}"""
|
||||
|
||||
|
||||
# json to use, expected results for course=None (e.g. homepage),
|
||||
# for toy course, for full course. Note that get_site_status_msg
|
||||
# is supposed to return global message even if course=None. The
|
||||
|
||||
@@ -83,4 +83,3 @@ def _check_caller_authority(caller, role):
|
||||
elif isinstance(role, CourseRole): # instructors can change the roles w/in their course
|
||||
if not has_access(caller, CourseInstructorRole(role.course_key)):
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
@@ -22,4 +22,4 @@ class PasswordResetFormNoActive(PasswordResetForm):
|
||||
if any((user.password == UNUSABLE_PASSWORD)
|
||||
for user in self.users_cache):
|
||||
raise forms.ValidationError(self.error_messages['unusable'])
|
||||
return email
|
||||
return email
|
||||
|
||||
@@ -65,4 +65,3 @@ class Command(BaseCommand):
|
||||
))
|
||||
except IOError:
|
||||
raise CommandError("Error writing to file: %s" % output_filename)
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class Command(TrackedCommand):
|
||||
# Move the Student between the classes.
|
||||
mode = enrollment.mode
|
||||
old_is_active = enrollment.is_active
|
||||
CourseEnrollment.unenroll(user, source_key, emit_unenrollment_event=False)
|
||||
CourseEnrollment.unenroll(user, source_key, skip_refund=True)
|
||||
print(u"Unenrolled {} from {}".format(user.username, unicode(source_key)))
|
||||
|
||||
for dest_key in dest_keys:
|
||||
@@ -98,7 +98,7 @@ class Command(TrackedCommand):
|
||||
# Un-enroll from the new course if the user had un-enrolled
|
||||
# form the old course.
|
||||
if not old_is_active:
|
||||
new_enrollment.update_enrollment(is_active=False, emit_unenrollment_event=False)
|
||||
new_enrollment.update_enrollment(is_active=False, skip_refund=True)
|
||||
|
||||
if transfer_certificates:
|
||||
self._transfer_certificate_item(source_key, enrollment, user, dest_keys, new_enrollment)
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
Tests the transfer student management command
|
||||
"""
|
||||
from django.conf import settings
|
||||
from mock import patch, call
|
||||
from opaque_keys.edx import locator
|
||||
import unittest
|
||||
import ddt
|
||||
|
||||
from shoppingcart.models import Order, CertificateItem # pylint: disable=F0401
|
||||
from course_modes.models import CourseMode
|
||||
from student.management.commands import transfer_students
|
||||
from student.models import CourseEnrollment
|
||||
from student.models import CourseEnrollment, UNENROLL_DONE, EVENT_NAME_ENROLLMENT_DEACTIVATED, \
|
||||
EVENT_NAME_ENROLLMENT_ACTIVATED, EVENT_NAME_ENROLLMENT_MODE_CHANGED
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -18,18 +23,40 @@ class TestTransferStudents(ModuleStoreTestCase):
|
||||
"""Tests for transferring students between courses."""
|
||||
|
||||
PASSWORD = 'test'
|
||||
signal_fired = False
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
"""Connect a stub receiver, and analytics event tracking."""
|
||||
UNENROLL_DONE.connect(self.assert_unenroll_signal)
|
||||
patcher = patch('student.models.tracker')
|
||||
self.mock_tracker = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def tearDown(self):
|
||||
"""Disconnects the UNENROLL stub receiver."""
|
||||
UNENROLL_DONE.disconnect(self.assert_unenroll_signal)
|
||||
|
||||
def assert_unenroll_signal(self, skip_refund=False, **kwargs): # pylint: disable=W0613
|
||||
""" Signal Receiver stub for testing that the unenroll signal was fired. """
|
||||
self.assertFalse(self.signal_fired)
|
||||
self.assertTrue(skip_refund)
|
||||
self.signal_fired = True
|
||||
|
||||
def test_transfer_students(self):
|
||||
student = UserFactory()
|
||||
""" Verify the transfer student command works as intended. """
|
||||
student = UserFactory.create()
|
||||
student.set_password(self.PASSWORD) # pylint: disable=E1101
|
||||
student.save() # pylint: disable=E1101
|
||||
|
||||
mode = 'verified'
|
||||
# Original Course
|
||||
original_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
|
||||
course = self._create_course(original_course_location)
|
||||
# Enroll the student in 'verified'
|
||||
CourseEnrollment.enroll(student, course.id, mode="verified")
|
||||
|
||||
# Create and purchase a verified cert for the original course.
|
||||
self._create_and_purchase_verified(student, course.id)
|
||||
|
||||
# New Course 1
|
||||
course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1')
|
||||
new_course_one = self._create_course(course_location_one)
|
||||
@@ -45,11 +72,55 @@ class TestTransferStudents(ModuleStoreTestCase):
|
||||
transfer_students.Command().handle(
|
||||
source_course=original_key, dest_course_list=new_key_one + "," + new_key_two
|
||||
)
|
||||
self.assertTrue(self.signal_fired)
|
||||
|
||||
# Confirm the analytics event was emitted.
|
||||
self.mock_tracker.emit.assert_has_calls( # pylint: disable=E1103
|
||||
[
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_ACTIVATED,
|
||||
{'course_id': original_key, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_MODE_CHANGED,
|
||||
{'course_id': original_key, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_DEACTIVATED,
|
||||
{'course_id': original_key, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_ACTIVATED,
|
||||
{'course_id': new_key_one, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_MODE_CHANGED,
|
||||
{'course_id': new_key_one, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_ACTIVATED,
|
||||
{'course_id': new_key_two, 'user_id': student.id, 'mode': mode}
|
||||
),
|
||||
call(
|
||||
EVENT_NAME_ENROLLMENT_MODE_CHANGED,
|
||||
{'course_id': new_key_two, 'user_id': student.id, 'mode': mode}
|
||||
)
|
||||
]
|
||||
)
|
||||
self.mock_tracker.reset_mock()
|
||||
|
||||
# Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
|
||||
self.assertEquals(('verified', False), CourseEnrollment.enrollment_mode_for_user(student, course.id))
|
||||
self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_one.id))
|
||||
self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_two.id))
|
||||
self.assertEquals((mode, False), CourseEnrollment.enrollment_mode_for_user(student, course.id))
|
||||
self.assertEquals((mode, True), CourseEnrollment.enrollment_mode_for_user(student, new_course_one.id))
|
||||
self.assertEquals((mode, True), CourseEnrollment.enrollment_mode_for_user(student, new_course_two.id))
|
||||
|
||||
# Confirm the student has not be refunded.
|
||||
target_certs = CertificateItem.objects.filter(
|
||||
course_id=course.id, user_id=student, status='purchased', mode=mode
|
||||
)
|
||||
self.assertTrue(target_certs[0])
|
||||
self.assertFalse(target_certs[0].refund_requested_time)
|
||||
self.assertEquals(target_certs[0].order.status, 'purchased')
|
||||
|
||||
def _create_course(self, course_location):
|
||||
""" Creates a course """
|
||||
@@ -58,3 +129,15 @@ class TestTransferStudents(ModuleStoreTestCase):
|
||||
number=course_location.course,
|
||||
run=course_location.run
|
||||
)
|
||||
|
||||
def _create_and_purchase_verified(self, student, course_id):
|
||||
""" Creates a verified mode for the course and purchases it for the student. """
|
||||
course_mode = CourseMode(course_id=course_id,
|
||||
mode_slug="verified",
|
||||
mode_display_name="verified cert",
|
||||
min_price=50)
|
||||
course_mode.save()
|
||||
# When there is no expiration date on a verified mode, the user can always get a refund
|
||||
cart = Order.get_cart_for_user(user=student)
|
||||
CertificateItem.add_to_order(cart, course_id, 50, 'verified')
|
||||
cart.purchase()
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from student.models import UserStanding
|
||||
|
||||
|
||||
class UserStandingMiddleware(object):
|
||||
"""
|
||||
Checks a user's standing on request. Returns a 403 if the user's
|
||||
|
||||
@@ -18,12 +18,10 @@ class Migration(SchemaMigration):
|
||||
))
|
||||
db.send_create_signal('student', ['DashboardConfiguration'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'DashboardConfiguration'
|
||||
db.delete_table('student_dashboardconfiguration')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
@@ -176,4 +174,4 @@ class Migration(SchemaMigration):
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['student']
|
||||
complete_apps = ['student']
|
||||
|
||||
@@ -56,7 +56,7 @@ from ratelimitbackend import admin
|
||||
|
||||
import analytics
|
||||
|
||||
UNENROLL_DONE = Signal(providing_args=["course_enrollment"])
|
||||
UNENROLL_DONE = Signal(providing_args=["course_enrollment", "skip_refund"])
|
||||
log = logging.getLogger(__name__)
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore # pylint: disable=invalid-name
|
||||
@@ -665,15 +665,19 @@ class LoginFailures(models.Model):
|
||||
class CourseEnrollmentException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NonExistentCourseError(CourseEnrollmentException):
|
||||
pass
|
||||
|
||||
|
||||
class EnrollmentClosedError(CourseEnrollmentException):
|
||||
pass
|
||||
|
||||
|
||||
class CourseFullError(CourseEnrollmentException):
|
||||
pass
|
||||
|
||||
|
||||
class AlreadyEnrolledError(CourseEnrollmentException):
|
||||
pass
|
||||
|
||||
@@ -776,7 +780,7 @@ class CourseEnrollment(models.Model):
|
||||
is_course_full = cls.num_enrolled_in(course.id) >= course.max_student_enrollments_allowed
|
||||
return is_course_full
|
||||
|
||||
def update_enrollment(self, mode=None, is_active=None, emit_unenrollment_event=True):
|
||||
def update_enrollment(self, mode=None, is_active=None, skip_refund=False):
|
||||
"""
|
||||
Updates an enrollment for a user in a class. This includes options
|
||||
like changing the mode, toggling is_active True/False, etc.
|
||||
@@ -814,8 +818,8 @@ class CourseEnrollment(models.Model):
|
||||
u"mode:{}".format(self.mode)]
|
||||
)
|
||||
|
||||
elif emit_unenrollment_event:
|
||||
UNENROLL_DONE.send(sender=None, course_enrollment=self)
|
||||
else:
|
||||
UNENROLL_DONE.send(sender=None, course_enrollment=self, skip_refund=skip_refund)
|
||||
|
||||
self.emit_event(EVENT_NAME_ENROLLMENT_DEACTIVATED)
|
||||
|
||||
@@ -988,7 +992,7 @@ class CourseEnrollment(models.Model):
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def unenroll(cls, user, course_id, emit_unenrollment_event=True):
|
||||
def unenroll(cls, user, course_id, skip_refund=False):
|
||||
"""
|
||||
Remove the user from a given course. If the relevant `CourseEnrollment`
|
||||
object doesn't exist, we log an error but don't throw an exception.
|
||||
@@ -999,11 +1003,11 @@ class CourseEnrollment(models.Model):
|
||||
|
||||
`course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall)
|
||||
|
||||
`emit_unenrollment_events` can be set to False to suppress events firing.
|
||||
`skip_refund` can be set to True to avoid the refund process.
|
||||
"""
|
||||
try:
|
||||
record = CourseEnrollment.objects.get(user=user, course_id=course_id)
|
||||
record.update_enrollment(is_active=False, emit_unenrollment_event=emit_unenrollment_event)
|
||||
record.update_enrollment(is_active=False, skip_refund=skip_refund)
|
||||
|
||||
except cls.DoesNotExist:
|
||||
err_msg = u"Tried to unenroll student {} from {} but they were not enrolled"
|
||||
|
||||
@@ -210,6 +210,7 @@ class CourseFinanceAdminRole(CourseRole):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseFinanceAdminRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class CourseBetaTesterRole(CourseRole):
|
||||
"""A course Beta Tester"""
|
||||
ROLE = 'beta_testers'
|
||||
|
||||
@@ -11,6 +11,7 @@ from opaque_keys.edx.locator import CourseLocator
|
||||
from mock import patch
|
||||
import ddt
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
|
||||
"""
|
||||
|
||||
@@ -105,7 +105,10 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun')
|
||||
course = self._create_course_with_access_groups(course_location)
|
||||
course_db_record = mongo_store._find_one(course.location)
|
||||
course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki" })
|
||||
course_db_record.setdefault('metadata', {}).get('tabs', []).append({
|
||||
"type": "wiko",
|
||||
"name": "Wiki",
|
||||
})
|
||||
mongo_store.collection.update(
|
||||
{'_id': course.location.to_deprecated_son()},
|
||||
{'$set': {
|
||||
|
||||
@@ -485,6 +485,7 @@ class LoginOAuthTokenMixin(object):
|
||||
self._setup_user_response(success=True)
|
||||
response = self.client.post(self.url, {"access_token": "dummy"})
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], self.user.id)
|
||||
|
||||
def test_invalid_token(self):
|
||||
self._setup_user_response(success=False)
|
||||
|
||||
@@ -25,6 +25,7 @@ FAKE_MICROSITE = {
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def fake_site_name(name, default=None): # pylint: disable=W0613
|
||||
"""
|
||||
create a fake microsite site name
|
||||
@@ -34,12 +35,14 @@ def fake_site_name(name, default=None): # pylint: disable=W0613
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def fake_microsite_get_value(name, default=None): # pylint: disable=W0613
|
||||
"""
|
||||
create a fake microsite site name
|
||||
"""
|
||||
return FAKE_MICROSITE.get(name, default)
|
||||
|
||||
|
||||
class TestMicrosite(TestCase):
|
||||
"""Test for Account Creation from a white labeled Micro-Sites"""
|
||||
def setUp(self):
|
||||
|
||||
@@ -15,6 +15,7 @@ from edxmako.tests import mako_middleware_process_request
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from student.views import create_account
|
||||
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True})
|
||||
class TestPasswordPolicy(TestCase):
|
||||
"""
|
||||
|
||||
@@ -265,6 +265,7 @@ class DashboardTest(ModuleStoreTestCase):
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@patch('courseware.views.log.warning')
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
def test_blocked_course_scenario(self, log_warning):
|
||||
|
||||
self.client.login(username="jack", password="test")
|
||||
|
||||
@@ -1115,6 +1115,7 @@ def login_user(request, error=""): # pylint: disable-msg=too-many-statements,un
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
@social_utils.strategy("social:complete")
|
||||
def login_oauth_token(request, backend):
|
||||
@@ -1135,6 +1136,7 @@ def login_oauth_token(request, backend):
|
||||
pass
|
||||
# do_auth can return a non-User object if it fails
|
||||
if user and isinstance(user, User):
|
||||
login(request, user)
|
||||
return JsonResponse(status=204)
|
||||
else:
|
||||
# Ensure user does not re-enter the pipeline
|
||||
@@ -1791,11 +1793,9 @@ def activate_account(request, key):
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def password_reset(request):
|
||||
""" Attempts to send a password reset e-mail. """
|
||||
if request.method != "POST":
|
||||
raise Http404
|
||||
|
||||
# Add some rate limiting here by re-using the RateLimitMixin as a helper class
|
||||
limiter = BadRequestRateLimiter()
|
||||
if limiter.is_rate_limit_exceeded(request):
|
||||
|
||||
@@ -95,7 +95,9 @@ def initial_setup(server):
|
||||
|
||||
if browser_driver == 'chrome':
|
||||
desired_capabilities = DesiredCapabilities.CHROME
|
||||
desired_capabilities['loggingPrefs'] = { 'browser':'ALL' }
|
||||
desired_capabilities['loggingPrefs'] = {
|
||||
'browser': 'ALL',
|
||||
}
|
||||
elif browser_driver == 'firefox':
|
||||
desired_capabilities = DesiredCapabilities.FIREFOX
|
||||
else:
|
||||
@@ -239,7 +241,7 @@ def capture_console_log(scenario):
|
||||
output_dir = '{}/log'.format(settings.TEST_ROOT)
|
||||
file_name = '{}/{}.log'.format(output_dir, scenario.name.replace(' ', '_'))
|
||||
|
||||
with open (file_name, 'w') as output_file:
|
||||
with open(file_name, 'w') as output_file:
|
||||
for line in log:
|
||||
output_file.write("{}{}".format(dumps(line), '\n'))
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import re
|
||||
import urlparse
|
||||
from .http import StubHttpRequestHandler, StubHttpService
|
||||
|
||||
|
||||
class StubCommentsServiceHandler(StubHttpRequestHandler):
|
||||
|
||||
@property
|
||||
|
||||
@@ -123,8 +123,8 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object):
|
||||
# By default, `parse_qs` returns a list of values for each param
|
||||
# For convenience, we replace lists of 1 element with just the element
|
||||
return {
|
||||
k:v[0] if len(v) == 1 else v
|
||||
for k,v in urlparse.parse_qs(query).items()
|
||||
key: value[0] if len(value) == 1 else value
|
||||
for key, value in urlparse.parse_qs(query).items()
|
||||
}
|
||||
|
||||
@lazy
|
||||
|
||||
@@ -21,6 +21,7 @@ import mock
|
||||
import requests
|
||||
from http import StubHttpRequestHandler, StubHttpService
|
||||
|
||||
|
||||
class StubLtiHandler(StubHttpRequestHandler):
|
||||
"""
|
||||
A handler for LTI POST and GET requests.
|
||||
|
||||
@@ -45,7 +45,7 @@ class StudentState(object):
|
||||
|
||||
@property
|
||||
def num_pending(self):
|
||||
return max(self.INITIAL_ESSAYS_AVAILABLE- self.num_graded, 0)
|
||||
return max(self.INITIAL_ESSAYS_AVAILABLE - self.num_graded, 0)
|
||||
|
||||
@property
|
||||
def num_required(self):
|
||||
@@ -300,7 +300,6 @@ class StubOraHandler(StubHttpRequestHandler):
|
||||
"""
|
||||
self._success_response({'problem_list': self.server.problem_list})
|
||||
|
||||
|
||||
@require_params('POST', 'grader_id', 'location', 'submission_id', 'score', 'feedback', 'submission_key')
|
||||
def _save_grade(self):
|
||||
"""
|
||||
@@ -421,7 +420,6 @@ class StubOraHandler(StubHttpRequestHandler):
|
||||
)
|
||||
self.send_response(400)
|
||||
|
||||
|
||||
def _student(self, method, key='student_id'):
|
||||
"""
|
||||
Return the `StudentState` instance for the student ID given
|
||||
|
||||
@@ -25,7 +25,9 @@ class StubHttpServiceTest(unittest.TestCase):
|
||||
'test_empty': '',
|
||||
'test_int': 12345,
|
||||
'test_float': 123.45,
|
||||
'test_dict': { 'test_key': 'test_val' },
|
||||
'test_dict': {
|
||||
'test_key': 'test_val',
|
||||
},
|
||||
'test_empty_dict': {},
|
||||
'test_unicode': u'\u2603 the snowman',
|
||||
'test_none': None,
|
||||
|
||||
@@ -7,6 +7,7 @@ import urllib2
|
||||
import requests
|
||||
from terrain.stubs.lti import StubLtiService
|
||||
|
||||
|
||||
class StubLtiServiceTest(unittest.TestCase):
|
||||
"""
|
||||
A stub of the LTI provider that listens on a local
|
||||
@@ -34,7 +35,7 @@ class StubLtiServiceTest(unittest.TestCase):
|
||||
'launch_presentation_return_url': '',
|
||||
'lis_outcome_service_url': 'http://localhost:8001/test_callback',
|
||||
'lis_result_sourcedid': '',
|
||||
'resource_link_id':'',
|
||||
'resource_link_id': '',
|
||||
}
|
||||
|
||||
def test_invalid_request_url(self):
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
from logging import getLogger
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class VideoSourceRequestHandler(SimpleHTTPRequestHandler):
|
||||
"""
|
||||
Request handler for serving video sources locally.
|
||||
|
||||
@@ -214,6 +214,7 @@ class StubXQueueService(StubHttpService):
|
||||
except for 'default' and 'register_submission_url' which have special meaning
|
||||
"""
|
||||
return {
|
||||
key:val for key, val in self.config.iteritems()
|
||||
key: value
|
||||
for key, value in self.config.iteritems()
|
||||
if key not in self.NON_QUEUE_CONFIG_KEYS
|
||||
}.items()
|
||||
|
||||
@@ -106,7 +106,7 @@ class TrackMiddleware(object):
|
||||
for header_name, context_key in META_KEY_TO_CONTEXT_KEY.iteritems():
|
||||
context[context_key] = request.META.get(header_name, '')
|
||||
|
||||
# Google Analytics uses the clientId to keep track of unique visitors. A GA cookie looks like
|
||||
# Google Analytics uses the clientId to keep track of unique visitors. A GA cookie looks like
|
||||
# this: _ga=GA1.2.1033501218.1368477899. The clientId is this part: 1033501218.1368477899.
|
||||
google_analytics_cookie = request.COOKIES.get('_ga')
|
||||
if google_analytics_cookie is None:
|
||||
|
||||
@@ -5,6 +5,7 @@ from student.tests.factories import UserFactory
|
||||
from user_api.models import UserPreference, UserCourseTag
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
|
||||
# Factories don't have __init__ methods, and are self documenting
|
||||
# pylint: disable=W0232, C0111
|
||||
class UserPreferenceFactory(DjangoModelFactory):
|
||||
|
||||
@@ -298,7 +298,7 @@ class AccountApiTest(TestCase):
|
||||
if create_inactive_account:
|
||||
# Create an account, but do not activate it
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
|
||||
account_api.request_password_change(self.EMAIL, self.ORIG_HOST, self.IS_SECURE)
|
||||
|
||||
# Verify that no email messages have been sent
|
||||
|
||||
@@ -4,6 +4,7 @@ which can be used for rate limiting
|
||||
"""
|
||||
from ratelimitbackend.backends import RateLimitMixin
|
||||
|
||||
|
||||
class BadRequestRateLimiter(RateLimitMixin):
|
||||
"""
|
||||
Use the 3rd party RateLimitMixin to help do rate limiting on the Password Reset flows
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@@ -6,4 +6,4 @@ def use_read_replica_if_available(queryset):
|
||||
"""
|
||||
If there is a database called 'read_replica', use that database for the queryset.
|
||||
"""
|
||||
return queryset.using("read_replica") if "read_replica" in settings.DATABASES else queryset
|
||||
return queryset.using("read_replica") if "read_replica" in settings.DATABASES else queryset
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Utilities for string manipulation.
|
||||
"""
|
||||
|
||||
|
||||
def str_to_bool(str):
|
||||
"""
|
||||
Converts "true" (case-insensitive) to the boolean True.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Tests for string_utils.py
|
||||
from django.test import TestCase
|
||||
from util.string_utils import str_to_bool
|
||||
|
||||
|
||||
class StringUtilsTest(TestCase):
|
||||
"""
|
||||
Tests for str_to_bool.
|
||||
|
||||
Reference in New Issue
Block a user