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:
Will Daly
2014-11-13 11:16:16 -05:00
257 changed files with 1195 additions and 440 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -209,6 +209,7 @@ class ListCohortsTestCase(CohortViewsTestCase):
actual_cohorts,
)
class AddCohortTestCase(CohortViewsTestCase):
"""
Tests the `add_cohort` view.

View File

@@ -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 = {

View File

@@ -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"] = [

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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')

View File

@@ -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):
"""

View File

@@ -8,6 +8,7 @@ from django.db.utils import DatabaseError
import mock
from django.test.testcases import TestCase
class HeartbeatTestCase(TestCase):
"""
Test the heartbeat

View File

@@ -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 %}

View File

@@ -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):
"""

View File

@@ -1,3 +1,3 @@
# Register signal handlers
import signals
import exceptions
import exceptions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -65,4 +65,3 @@ class Command(BaseCommand):
))
except IOError:
raise CommandError("Error writing to file: %s" % output_filename)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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']

View File

@@ -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"

View File

@@ -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'

View File

@@ -11,6 +11,7 @@ from opaque_keys.edx.locator import CourseLocator
from mock import patch
import ddt
@ddt.ddt
class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
"""

View File

@@ -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': {

View File

@@ -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)

View File

@@ -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):

View File

@@ -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):
"""

View File

@@ -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")

View File

@@ -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):

View File

@@ -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'))

View File

@@ -6,6 +6,7 @@ import re
import urlparse
from .http import StubHttpRequestHandler, StubHttpService
class StubCommentsServiceHandler(StubHttpRequestHandler):
@property

View File

@@ -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

View File

@@ -21,6 +21,7 @@ import mock
import requests
from http import StubHttpRequestHandler, StubHttpService
class StubLtiHandler(StubHttpRequestHandler):
"""
A handler for LTI POST and GET requests.

View File

@@ -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

View File

@@ -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,

View File

@@ -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):

View File

@@ -9,6 +9,7 @@ import os
from logging import getLogger
LOGGER = getLogger(__name__)
class VideoSourceRequestHandler(SimpleHTTPRequestHandler):
"""
Request handler for serving video sources locally.

View File

@@ -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()

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -1,2 +1 @@
# Create your models here.

View File

@@ -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

View File

@@ -2,6 +2,7 @@
Utilities for string manipulation.
"""
def str_to_bool(str):
"""
Converts "true" (case-insensitive) to the boolean True.

View File

@@ -1 +0,0 @@

View File

@@ -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.