fix merge conflict with _variables.scss
This commit is contained in:
111
common/djangoapps/student/tests/test_auto_auth.py
Normal file
111
common/djangoapps/student/tests/test_auto_auth.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
from util.testing import UrlResetMixin
|
||||
from mock import patch
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
|
||||
class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
|
||||
"""
|
||||
Tests for the Auto auth view that we have for load testing.
|
||||
"""
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": True})
|
||||
def setUp(self):
|
||||
# Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING']
|
||||
# value affects the contents of urls.py,
|
||||
# so we need to call super.setUp() which reloads urls.py (because
|
||||
# of the UrlResetMixin)
|
||||
super(AutoAuthEnabledTestCase, self).setUp()
|
||||
self.url = '/auto_auth'
|
||||
self.cms_csrf_url = "signup"
|
||||
self.lms_csrf_url = "signin_user"
|
||||
self.client = Client()
|
||||
|
||||
def test_create_user(self):
|
||||
"""
|
||||
Test that user gets created when visiting the page.
|
||||
"""
|
||||
|
||||
self.client.get(self.url)
|
||||
|
||||
qset = User.objects.all()
|
||||
|
||||
# assert user was created and is active
|
||||
self.assertEqual(qset.count(), 1)
|
||||
user = qset[0]
|
||||
assert user.is_active
|
||||
|
||||
@patch('student.views.random.randint')
|
||||
def test_create_multiple_users(self, randint):
|
||||
"""
|
||||
Test to make sure multiple users are created.
|
||||
"""
|
||||
randint.return_value = 1
|
||||
self.client.get(self.url)
|
||||
|
||||
randint.return_value = 2
|
||||
self.client.get(self.url)
|
||||
|
||||
qset = User.objects.all()
|
||||
|
||||
# make sure that USER_1 and USER_2 were created
|
||||
self.assertEqual(qset.count(), 2)
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 1})
|
||||
def test_login_already_created_user(self):
|
||||
"""
|
||||
Test that when we have reached the limit for automatic users
|
||||
a subsequent request results in an already existant one being
|
||||
logged in.
|
||||
"""
|
||||
# auto-generate 1 user (the max)
|
||||
url = '/auto_auth'
|
||||
self.client.get(url)
|
||||
|
||||
# go to the site again
|
||||
self.client.get(url)
|
||||
qset = User.objects.all()
|
||||
|
||||
# make sure it is the same user
|
||||
self.assertEqual(qset.count(), 1)
|
||||
|
||||
|
||||
class AutoAuthDisabledTestCase(UrlResetMixin, TestCase):
|
||||
"""
|
||||
Test that the page is inaccessible with default settings
|
||||
"""
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": False})
|
||||
def setUp(self):
|
||||
# Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING']
|
||||
# value affects the contents of urls.py,
|
||||
# so we need to call super.setUp() which reloads urls.py (because
|
||||
# of the UrlResetMixin)
|
||||
super(AutoAuthDisabledTestCase, self).setUp()
|
||||
self.url = '/auto_auth'
|
||||
self.client = Client()
|
||||
|
||||
def test_auto_auth_disabled(self):
|
||||
"""
|
||||
Make sure automatic authentication is disabled.
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_csrf_enabled(self):
|
||||
"""
|
||||
test that when not load testing, csrf protection is on
|
||||
"""
|
||||
cms_csrf_url = "signup"
|
||||
lms_csrf_url = "signin_user"
|
||||
self.client = Client(enforce_csrf_checks=True)
|
||||
try:
|
||||
csrf_protected_url = reverse(cms_csrf_url)
|
||||
response = self.client.post(csrf_protected_url)
|
||||
except NoReverseMatch:
|
||||
csrf_protected_url = reverse(lms_csrf_url)
|
||||
response = self.client.post(csrf_protected_url)
|
||||
|
||||
self.assertEqual(response.status_code, 403)
|
||||
@@ -19,6 +19,7 @@ from django.core.context_processors import csrf
|
||||
from django.core.mail import send_mail
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import validate_email, validate_slug, ValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, Http404
|
||||
from django.shortcuts import redirect
|
||||
@@ -674,18 +675,20 @@ def create_account(request, post_override=None):
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = render_to_string('emails/activation_email.txt', d)
|
||||
|
||||
try:
|
||||
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
|
||||
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
|
||||
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
|
||||
'-' * 80 + '\n\n' + message)
|
||||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
|
||||
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
|
||||
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
|
||||
except:
|
||||
log.warning('Unable to send activation email to user', exc_info=True)
|
||||
js['value'] = _('Could not send activation e-mail.')
|
||||
return HttpResponse(json.dumps(js))
|
||||
# dont send email if we are doing load testing or random user generation for some reason
|
||||
if not (settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')):
|
||||
try:
|
||||
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
|
||||
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
|
||||
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
|
||||
'-' * 80 + '\n\n' + message)
|
||||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
|
||||
else:
|
||||
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
|
||||
except:
|
||||
log.warning('Unable to send activation email to user', exc_info=True)
|
||||
js['value'] = _('Could not send activation e-mail.')
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
# Immediately after a user creates an account, we log them in. They are only
|
||||
# logged in until they close the browser. They can't log in again until they click
|
||||
@@ -902,32 +905,51 @@ def create_exam_registration(request, post_override=None):
|
||||
return HttpResponse(json.dumps(js), mimetype="application/json")
|
||||
|
||||
|
||||
def get_random_post_override():
|
||||
def auto_auth(request):
|
||||
"""
|
||||
Return a dictionary suitable for passing to post_vars of _do_create_account or post_override
|
||||
of create_account, with random user info.
|
||||
Automatically logs the user in with a generated random credentials
|
||||
This view is only accessible when
|
||||
settings.MITX_SETTINGS['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] is true.
|
||||
"""
|
||||
def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
|
||||
return ''.join(random.choice(chars) for x in range(size))
|
||||
|
||||
return {'username': "random_" + id_generator(),
|
||||
'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu",
|
||||
'password': id_generator(),
|
||||
'name': (id_generator(size=5, chars=string.ascii_lowercase) + " " +
|
||||
id_generator(size=7, chars=string.ascii_lowercase)),
|
||||
'honor_code': u'true',
|
||||
'terms_of_service': u'true', }
|
||||
def get_dummy_post_data(username, password):
|
||||
"""
|
||||
Return a dictionary suitable for passing to post_vars of _do_create_account or post_override
|
||||
of create_account, with specified username and password.
|
||||
"""
|
||||
|
||||
return {'username': username,
|
||||
'email': username + "_dummy_test@mitx.mit.edu",
|
||||
'password': password,
|
||||
'name': username + " " + username,
|
||||
'honor_code': u'true',
|
||||
'terms_of_service': u'true', }
|
||||
|
||||
def create_random_account(create_account_function):
|
||||
def inner_create_random_account(request):
|
||||
return create_account_function(request, post_override=get_random_post_override())
|
||||
# generate random user ceredentials from a small name space (determined by settings)
|
||||
name_base = 'USER_'
|
||||
pass_base = 'PASS_'
|
||||
|
||||
return inner_create_random_account
|
||||
max_users = settings.MITX_FEATURES.get('MAX_AUTO_AUTH_USERS', 200)
|
||||
number = random.randint(1, max_users)
|
||||
|
||||
# TODO (vshnayder): do we need GENERATE_RANDOM_USER_CREDENTIALS for anything?
|
||||
if settings.GENERATE_RANDOM_USER_CREDENTIALS:
|
||||
create_account = create_random_account(create_account)
|
||||
username = name_base + str(number)
|
||||
password = pass_base + str(number)
|
||||
|
||||
# if they already are a user, log in
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
user = authenticate(username=username, password=password)
|
||||
login(request, user)
|
||||
|
||||
# else create and activate account info
|
||||
except ObjectDoesNotExist:
|
||||
post_override = get_dummy_post_data(username, password)
|
||||
create_account(request, post_override=post_override)
|
||||
request.user.is_active = True
|
||||
request.user.save()
|
||||
|
||||
# return empty success
|
||||
return HttpResponse('')
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
|
||||
@@ -75,7 +75,7 @@ def initial_setup(server):
|
||||
# If we were unable to get a valid session within the limit of attempts,
|
||||
# then we cannot run the tests.
|
||||
if not success:
|
||||
raise IOError("Could not acquire valid ChromeDriver browser session.")
|
||||
raise IOError("Could not acquire valid {driver} browser session.".format(driver=browser_driver))
|
||||
|
||||
# Set the browser size to 1280x1024
|
||||
world.browser.driver.set_window_size(1280, 1024)
|
||||
|
||||
@@ -216,22 +216,19 @@ def save_the_html(path='/tmp'):
|
||||
@world.absorb
|
||||
def click_course_content():
|
||||
course_content_css = 'li.nav-course-courseware'
|
||||
if world.browser.is_element_present_by_css(course_content_css):
|
||||
world.css_click(course_content_css)
|
||||
world.css_click(course_content_css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def click_course_settings():
|
||||
course_settings_css = 'li.nav-course-settings'
|
||||
if world.browser.is_element_present_by_css(course_settings_css):
|
||||
world.css_click(course_settings_css)
|
||||
world.css_click(course_settings_css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def click_tools():
|
||||
tools_css = 'li.nav-course-tools'
|
||||
if world.browser.is_element_present_by_css(tools_css):
|
||||
world.css_click(tools_css)
|
||||
world.css_click(tools_css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
|
||||
@@ -211,6 +211,8 @@ nav.sequence-nav {
|
||||
@include transition(all .1s $ease-in-out-quart 0s);
|
||||
white-space: pre;
|
||||
z-index: 99;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
&:empty {
|
||||
background: none;
|
||||
@@ -238,6 +240,7 @@ nav.sequence-nav {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
opacity: 1.0;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,6 +266,7 @@ nav.sequence-nav {
|
||||
border: 1px solid #ccc;
|
||||
@include linear-gradient(top, #eee, #ddd);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .7) inset;
|
||||
z-index: 1;
|
||||
|
||||
&.prev, &.next {
|
||||
|
||||
@@ -270,7 +274,7 @@ nav.sequence-nav {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
height: 34px;
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
text-indent: -9999px;
|
||||
@include transition(all .2s $ease-in-out-quad 0s);
|
||||
|
||||
@@ -12,10 +12,14 @@ class DiscussionFields(object):
|
||||
display_name = String(
|
||||
display_name="Display Name",
|
||||
help="Display name for this module",
|
||||
default="Discussion Tag",
|
||||
scope=Scope.settings)
|
||||
data = String(help="XML data for the problem", scope=Scope.content,
|
||||
default="<discussion></discussion>")
|
||||
default="Discussion",
|
||||
scope=Scope.settings
|
||||
)
|
||||
data = String(
|
||||
help="XML data for the problem",
|
||||
scope=Scope.content,
|
||||
default="<discussion></discussion>"
|
||||
)
|
||||
discussion_category = String(
|
||||
display_name="Category",
|
||||
default="Week 1",
|
||||
|
||||
@@ -25,7 +25,7 @@ class HtmlFields(object):
|
||||
scope=Scope.settings,
|
||||
# it'd be nice to have a useful default but it screws up other things; so,
|
||||
# use display_name_with_default for those
|
||||
default="Blank HTML Page"
|
||||
default="Text"
|
||||
)
|
||||
data = String(help="Html contents to display for this module", default=u"", scope=Scope.content)
|
||||
source_code = String(help="Source code for LaTeX documents. This feature is not well-supported.", scope=Scope.settings)
|
||||
|
||||
@@ -225,6 +225,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
non_draft_loc = location.replace(revision=None)
|
||||
metadata_to_inherit = self.cached_metadata.get(non_draft_loc.url(), {})
|
||||
inherit_metadata(module, metadata_to_inherit)
|
||||
# decache any computed pending field settings
|
||||
module.save()
|
||||
return module
|
||||
except:
|
||||
log.warning("Failed to load descriptor", exc_info=True)
|
||||
@@ -630,6 +632,8 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
definition_data = {}
|
||||
dbmodel = self._create_new_model_data(location.category, location, definition_data, metadata)
|
||||
xmodule = xblock_class(system, dbmodel)
|
||||
# decache any pending field settings from init
|
||||
xmodule.save()
|
||||
return xmodule
|
||||
|
||||
def save_xmodule(self, xmodule):
|
||||
|
||||
@@ -116,4 +116,6 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
module.previous_version = json_data.get('previous_version')
|
||||
module.update_version = json_data.get('update_version')
|
||||
module.definition_locator = self.modulestore.definition_locator(definition)
|
||||
# decache any pending field settings
|
||||
module.save()
|
||||
return module
|
||||
|
||||
@@ -146,7 +146,7 @@ class Progress(object):
|
||||
sending Progress objects to js to limit dependencies.
|
||||
'''
|
||||
if progress is None:
|
||||
return "NA"
|
||||
return "0"
|
||||
return progress.ternary_str()
|
||||
|
||||
@staticmethod
|
||||
@@ -157,5 +157,5 @@ class Progress(object):
|
||||
passing Progress objects to js to limit dependencies.
|
||||
'''
|
||||
if progress is None:
|
||||
return "NA"
|
||||
return "0"
|
||||
return str(progress)
|
||||
|
||||
@@ -90,15 +90,15 @@ class ProgressTest(unittest.TestCase):
|
||||
self.assertEqual(Progress.to_js_status_str(self.not_started), "none")
|
||||
self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress")
|
||||
self.assertEqual(Progress.to_js_status_str(self.done), "done")
|
||||
self.assertEqual(Progress.to_js_status_str(None), "NA")
|
||||
self.assertEqual(Progress.to_js_status_str(None), "0")
|
||||
|
||||
def test_to_js_detail_str(self):
|
||||
'''Test the Progress.to_js_detail_str() method'''
|
||||
f = Progress.to_js_detail_str
|
||||
for p in (self.not_started, self.half_done, self.done):
|
||||
self.assertEqual(f(p), str(p))
|
||||
# But None should be encoded as NA
|
||||
self.assertEqual(f(None), "NA")
|
||||
# But None should be encoded as 0
|
||||
self.assertEqual(f(None), "0")
|
||||
|
||||
def test_add(self):
|
||||
'''Test the Progress.add_counts() method'''
|
||||
|
||||
@@ -27,11 +27,13 @@ class VideoFields(object):
|
||||
scope=Scope.settings,
|
||||
# it'd be nice to have a useful default but it screws up other things; so,
|
||||
# use display_name_with_default for those
|
||||
default="Video Title"
|
||||
default="Video"
|
||||
)
|
||||
data = String(help="XML data for the problem",
|
||||
data = String(
|
||||
help="XML data for the problem",
|
||||
default='',
|
||||
scope=Scope.content)
|
||||
scope=Scope.content
|
||||
)
|
||||
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
|
||||
show_captions = Boolean(help="This controls whether or not captions are shown by default.", display_name="Show Captions", scope=Scope.settings, default=True)
|
||||
youtube_id_1_0 = String(help="This is the Youtube ID reference for the normal speed video.", display_name="Default Speed", scope=Scope.settings, default="OEoXaMPEzfM")
|
||||
|
||||
@@ -537,11 +537,14 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
|
||||
system: Module system
|
||||
"""
|
||||
return self.module_class(
|
||||
# save any field changes
|
||||
module = self.module_class(
|
||||
system,
|
||||
self,
|
||||
system.xblock_model_data(self),
|
||||
)
|
||||
module.save()
|
||||
return module
|
||||
|
||||
def has_dynamic_children(self):
|
||||
"""
|
||||
@@ -613,7 +616,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
|
||||
new_block = system.xblock_from_json(cls, usage_id, json_data)
|
||||
if parent_xblock is not None:
|
||||
parent_xblock.children.append(new_block)
|
||||
children = parent_xblock.children
|
||||
children.append(new_block)
|
||||
# trigger setter method by using top level field access
|
||||
parent_xblock.children = children
|
||||
# decache pending children field settings (Note, truly persisting at this point would break b/c
|
||||
# persistence assumes children is a list of ids not actual xblocks)
|
||||
parent_xblock.save()
|
||||
return new_block
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
}
|
||||
|
||||
&.disabled, &[disabled] {
|
||||
&.disabled, &[disabled], &.is-disabled {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user