Commit 54b97c60 by Adam

Merge pull request #8966 from…

Merge pull request #8966 from edx/revert-7404-waheed/plat407-decorate-instructor-dashboard-with-sudo-required

Revert "Decorated instructor dashboard with sudo_required."
parents b12d3f63 2735b2b5
......@@ -33,7 +33,6 @@ Feature: CMS.Help
Then I should see online help for "grading"
And I am viewing the course team settings
And I get sudo access with password "test"
Then I should see online help for "course-team"
And I select the Advanced Settings
......
......@@ -1343,7 +1343,6 @@ class ContentStoreTest(ContentStoreTestCase):
resp = self._show_course_overview(course_key)
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'Chapter 2')
self.grant_sudo_access(unicode(course_key), self.user_password)
# go to various pages
test_get_html('import_handler')
......
......@@ -22,10 +22,10 @@ class TestCourseAccess(ModuleStoreTestCase):
Create a pool of users w/o granting them any permissions
"""
self.user_password = super(TestCourseAccess, self).setUp()
user_password = super(TestCourseAccess, self).setUp()
self.client = AjaxEnabledTestClient()
self.client.login(username=self.user.username, password=self.user_password)
self.client.login(username=self.user.username, password=user_password)
# create a course via the view handler which has a different strategy for permissions than the factory
self.course_key = self.store.make_course_key('myu', 'mydept.mycourse', 'myrun')
......@@ -93,7 +93,6 @@ class TestCourseAccess(ModuleStoreTestCase):
user_by_role[role].append(user)
self.assertTrue(auth.has_course_author_access(user, self.course_key), "{} does not have access".format(user))
self.grant_sudo_access(unicode(self.course_key), self.user_password)
course_team_url = reverse_course_url('course_team_handler', self.course_key)
response = self.client.get_html(course_team_url)
for role in [CourseInstructorRole, CourseStaffRole]: # Global and org-based roles don't appear on this page
......
......@@ -29,7 +29,6 @@ from opaque_keys.edx.keys import UsageKey
from student.auth import has_course_author_access
from django.utils.translation import ugettext as _
from sudo.utils import revoke_sudo_privileges
from models.settings.course_grading import CourseGradingModel
__all__ = ['OPEN_ENDED_COMPONENT_TYPES',
......@@ -164,12 +163,6 @@ def container_handler(request, usage_key_string):
with modulestore().bulk_operations(usage_key.course_key):
try:
course, xblock, lms_link, preview_lms_link = _get_item_in_course(request, usage_key)
# Revoke sudo privileges from a request explicitly
region = unicode(course.id)
if request.is_sudo(region=region):
revoke_sudo_privileges(request, region=region)
except ItemNotFoundError:
return HttpResponseBadRequest()
......
......@@ -17,8 +17,6 @@ from django.conf import settings
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django_sudo_helpers.decorators import sudo_required
from sudo.utils import revoke_sudo_privileges
from edxmako.shortcuts import render_to_response
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
......@@ -70,11 +68,6 @@ def _display_library(library_key_string, request):
"""
Displays single library
"""
# Revoke sudo privileges from a request explicitly
if request.is_sudo(region=library_key_string):
revoke_sudo_privileges(request, region=library_key_string)
library_key = CourseKey.from_string(library_key_string)
if not isinstance(library_key, LibraryLocator):
log.exception("Non-library key passed to content libraries API.") # Should never happen due to url regex
......@@ -204,7 +197,6 @@ def library_blocks_view(library, user, response_format):
})
@sudo_required
def manage_library_users(request, library_key_string):
"""
Studio UI for editing the users within a library.
......
......@@ -12,7 +12,6 @@ from django.utils import http
import contentstore.views.component as views
from contentstore.views.tests.utils import StudioPageTestCase
from django_sudo_helpers.tests.utils import sudo_middleware_process_request
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import ItemFactory
......@@ -172,7 +171,6 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
request = RequestFactory().get('foo')
request.user = self.user
sudo_middleware_process_request(request)
# Check for invalid 'usage_key_strings'
self.assertRaises(
......
......@@ -114,7 +114,6 @@ class TestCourseIndex(CourseTestCase):
"""
course_staff_client, course_staff = self.create_non_staff_authed_user_client()
for course in [self.course, self.odd_course]:
self.grant_sudo_access(unicode(course.id), 'foo')
permission_url = reverse_course_url('course_team_handler', course.id, kwargs={'email': course_staff.email})
self.client.post(
......
......@@ -30,10 +30,10 @@ class UnitTestLibraries(ModuleStoreTestCase):
"""
def setUp(self):
self.user_password = super(UnitTestLibraries, self).setUp()
user_password = super(UnitTestLibraries, self).setUp()
self.client = AjaxEnabledTestClient()
self.client.login(username=self.user.username, password=self.user_password)
self.client.login(username=self.user.username, password=user_password)
######################################################
# Tests for /library/ - list and create libraries:
......@@ -207,7 +207,6 @@ class UnitTestLibraries(ModuleStoreTestCase):
"""
library = LibraryFactory.create()
extra_user, _ = self.create_non_staff_user()
self.grant_sudo_access(unicode(library.location.library_key), self.user_password)
manage_users_url = reverse_library_url('manage_library_users', unicode(library.location.library_key))
response = self.client.get(manage_users_url)
......
......@@ -14,7 +14,6 @@ from student import auth
class UsersTestCase(CourseTestCase):
def setUp(self):
super(UsersTestCase, self).setUp()
self.grant_sudo_access(unicode(self.course.id), self.user_password)
self.ext_user = User.objects.create_user(
"joe", "joe@comedycentral.com", "haha")
self.ext_user.is_active = True
......
......@@ -11,7 +11,6 @@ from xmodule.modulestore.django import modulestore
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import LibraryLocator
from util.json_request import JsonResponse, expect_json
from django_sudo_helpers.decorators import sudo_required
from student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
from course_creators.views import user_requested_access
......@@ -39,7 +38,6 @@ def request_course_creator(request):
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT", "DELETE"))
@sudo_required
def course_team_handler(request, course_key_string=None, email=None):
"""
The restful handler for course team users.
......
......@@ -5,7 +5,7 @@ django admin page for the course creators table
from course_creators.models import CourseCreator, update_creator_state, send_user_notification, send_admin_notification
from course_creators.views import update_course_creator_group
from django.contrib import admin
from ratelimitbackend import admin
from django.conf import settings
from django.dispatch import receiver
from edxmako.shortcuts import render_to_string
......
......@@ -11,7 +11,6 @@ import mock
from course_creators.admin import CourseCreatorAdmin
from course_creators.models import CourseCreator
from django.core import mail
from sudo.utils import region_name
from student.roles import CourseCreatorRole
from student import auth
......@@ -47,16 +46,6 @@ class CourseCreatorAdminTest(TestCase):
"STUDIO_REQUEST_EMAIL": self.studio_request_email
}
def grant_sudo_access(self, region, password):
"""
Grant sudo access to staff or instructor user.
"""
self.client.post(
'/sudo/?region={}'.format(region_name(region)),
{'password': password},
follow=True
)
@mock.patch('course_creators.admin.render_to_string', mock.Mock(side_effect=mock_render_to_string, autospec=True))
@mock.patch('django.contrib.auth.models.User.email_user')
def test_change_status(self, email_user):
......@@ -172,7 +161,6 @@ class CourseCreatorAdminTest(TestCase):
self.assertFalse(self.creator_admin.has_change_permission(self.request))
def test_rate_limit_login(self):
self.grant_sudo_access('django_admin', 'foo')
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
post_params = {'username': self.user.username, 'password': 'wrong_password'}
# try logging in 30 times, the default limit in the number of failed
......
......@@ -319,9 +319,6 @@ MIDDLEWARE_CLASSES = (
# catches any uncaught RateLimitExceptions and returns a 403 instead of a 500
'ratelimitbackend.middleware.RateLimitMiddleware',
# force re-authentication before activating administrative functions
'sudo.middleware.SudoMiddleware',
# for expiring inactive sessions
'session_inactivity_timeout.middleware.SessionInactivityTimeout',
......@@ -764,9 +761,6 @@ INSTALLED_APPS = (
'openedx.core.djangoapps.credit',
'xblock_django',
# Allows sudo-mode
'sudo',
)
......
......@@ -70,129 +70,3 @@
width: 100%;
background: $black;
}
.sudo-modal {
@extend .modal;
background: $shadow-d2;
border: 1px solid rgba(0, 0, 0, 0.9);
border-radius: 0;
box-shadow: 0 15px 80px 15px rgba(0,0,0, 0.5);
color: $white;
display: none;
left: 50%;
padding: 8px;
position: absolute;
width: 480px;
height: auto;
.inner-wrapper {
@extend %ui-depth1;
background: rgb(245,245,245);
border-radius: 0;
border: 1px solid rgba(0, 0, 0, 0.9);
box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.7);
overflow: hidden;
padding-left: ($baseline/2);
padding-right: ($baseline/2);
padding-bottom: ($baseline/2);
position: relative;
header {
@extend %ui-depth1;
overflow: hidden;
padding: 28px $baseline 0;
position: relative;
&::before {
@include background-image(radial-gradient(50% 50%, circle closest-side, rgba(255,255,255, 0.8) 0%, rgba(255,255,255, 0) 100%));
content: "";
display: block;
height: 400px;
left: 0;
margin: 0 auto;
position: absolute;
top: -140px;
width: 100%;
z-index: 1;
}
hr {
border: none;
margin: 0;
position: relative;
z-index: 2;
&::after {
bottom: 0;
content: "";
display: block;
position: absolute;
top: -1px;
}
}
h2 {
position: relative;
text-align: center;
text-shadow: 0 1px rgba(255,255,255, 0.4);
z-index: 2;
}
}
form {
margin-bottom: 12px;
padding: 0 ($baseline*2) $baseline;
position: relative;
z-index: 2;
label {
color: rgb(51, 51, 51);
&.field-error {
display: block;
color: #8F0E0E;
+ input, + textarea {
border: 1px solid #CA1111;
color: #8F0E0E;
}
}
}
input[type="password"] {
background: rgb(255,255,255);
display: block;
height: 45px;
margin-bottom: $baseline;
width: 100%;
}
input[type="submit"] {
border: 1px solid #CFC6C6;
border-radius: 3px;
box-shadow: 0px 1px 0px 0px #FFF inset;
color: #333;
display: inline-block;
font-weight: bold;
background-color: #EEE;
background-image: linear-gradient(#EEE, #D6CECE);
padding: 12px 18px;
text-decoration: none;
text-shadow: 0px 1px 0px #F9F8F8;
background-clip: padding-box;
font-size: 0.8125em;
}
}
}
}
#sudo_overlay {
position: fixed;
top: 0px;
left: 0px;
display: block;
height: 100%;
width: 100%;
background: #000;
opacity: 0.5;
}
{% block body %}
{% load i18n %}
{% load compressed %}
{% compressed_css 'style-main' %}
<a href="#sudo-modal" id="sudo-modal-trig" style="display: none;"></a>
<section aria-hidden="true" class="modal sudo-modal" id="sudo-modal" style="overflow:auto; display: none;" >
<div class="inner-wrapper" style="color:black">
<header>
<h2>{% trans "Confirm Your Password to Access the Course Team Settings" %}</h2>
</header>
<hr />
<div>
<form class="sudo-form" method="post" action="">{% csrf_token %}
{{ form.as_p }}
<p>
<input type="submit" id="sudo-button" class="sudo-button" value="{% trans 'Submit' %}">
</p>
</form>
</div>
</div>
</section>
<script type="text/javascript">
window.baseUrl = "{{STATIC_URL}}";
var require = {baseUrl: window.baseUrl};
</script>
<script type="text/javascript" src="{{STATIC_URL}}js/vendor/require.js"></script>
<script type="text/javascript" src="{{STATIC_URL}}require-config.js"></script>
<script type = "text/javascript">
require(['domReady', "jquery"], function (domReady, $) {
domReady(function () {
require(["jquery.leanModal"], function () {
var sudoModalTrig = $("#sudo-modal-trig");
sudoModalTrig.leanModal();
sudoModalTrig.click();
$("#lean_overlay").remove();
});
});
});
</script>
<div id="sudo_overlay"></div>
{% endblock %}
\ No newline at end of file
......@@ -2,8 +2,7 @@ from django.conf import settings
from django.conf.urls import patterns, include, url
# There is a course creators admin table.
from edx_admin import admin
from ratelimitbackend import admin
admin.autodiscover()
# pylint: disable=bad-continuation
......@@ -51,8 +50,6 @@ urlpatterns = patterns(
url(r'^heartbeat$', include('heartbeat.urls')),
url(r'^user_api/', include('openedx.core.djangoapps.user_api.legacy_urls')),
url(r'^sudo/$', 'sudo.views.sudo'),
)
# User creation and updating views
......
......@@ -3,7 +3,7 @@ Django admin page for course modes
"""
from django.conf import settings
from pytz import timezone, UTC
from django.contrib import admin
from ratelimitbackend import admin
from course_modes.models import CourseMode
from django import forms
......
......@@ -380,7 +380,6 @@ class AdminCourseModePageTest(ModuleStoreTestCase):
}
self.client.login(username=user.username, password='test')
self.grant_sudo_access('django_admin', 'test')
# creating new course mode from django admin page
response = self.client.post(reverse('admin:course_modes_coursemode_add'), data=data)
......
"""
Custom decorator for django-sudo.
"""
from functools import wraps
from sudo.settings import RESET_TOKEN
from sudo.utils import new_sudo_token_on_activity
from sudo.views import redirect_to_sudo
from util.json_request import JsonResponse
def sudo_required(func_or_region):
"""
Enforces a view to have elevated privileges.
Should likely be paired with ``@login_required``.
>>> @sudo_required
>>> def secure_page(request):
>>> ...
Can also specify a particular sudo region (to only
allow access to that region).
Also get course_id, course_key_string and library_key_string
from kwargs and set as region if region itself is None.
>>> @sudo_required('admin_page')
>>> def secure_admin_page(request):
>>> ...
"""
def wrapper(func): # pylint: disable=missing-docstring
@wraps(func)
def inner(request, *args, **kwargs): # pylint: disable=missing-docstring
course_specific_region = kwargs.get('course_id')
if 'course_key_string' in kwargs:
course_specific_region = kwargs.get('course_key_string')
if 'library_key_string' in kwargs:
course_specific_region = kwargs.get('library_key_string')
# N.B. region is captured from the enclosing sudo_required function
if not request.is_sudo(region=region or course_specific_region):
response_format = request.REQUEST.get('format', 'html')
if (response_format == 'json' or
'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')):
return JsonResponse({'error': 'Unauthorized'}, status=401)
return redirect_to_sudo(request.get_full_path(), region=region or course_specific_region)
if RESET_TOKEN is True:
# Provide new sudo token content and reset timeout on activity
new_sudo_token_on_activity(request, region=region or course_specific_region)
return func(request, *args, **kwargs)
return inner
if callable(func_or_region):
region = None
return wrapper(func_or_region)
else:
region = func_or_region
return wrapper
"""
django_sudo_heplers.utils
"""
import django.contrib.sessions.middleware
import sudo.middleware
def sudo_middleware_process_request(request):
"""
Initialize the session and is_sudo on request object.
"""
session_middleware = django.contrib.sessions.middleware.SessionMiddleware()
session_middleware.process_request(request)
sudo_middleware = sudo.middleware.SudoMiddleware()
sudo_middleware.process_request(request)
"""
RatelimitSudoAdminSite
"""
from django.contrib.admin import * # pylint: disable=wildcard-import, unused-wildcard-import
from django.contrib.admin import (site as django_site,
autodiscover as django_autodiscover)
from ratelimitbackend.admin import RateLimitAdminSite
from sudo.admin import SudoAdminSite
class RatelimitSudoAdminSite(RateLimitAdminSite, SudoAdminSite):
"""
A class that includes the features of both RateLimitAdminSite and SudoAdminSite
"""
pass
site = RatelimitSudoAdminSite() # pylint: disable=invalid-name
def autodiscover(): # pylint: disable=function-redefined
"""
Auto-Discover admin models.
"""
django_autodiscover()
# pylint: disable=protected-access
for model, modeladmin in django_site._registry.items():
if model not in site._registry:
site.register(model, modeladmin.__class__)
"""
This space intentionally left blank
"""
......@@ -3,7 +3,7 @@ django admin pages for courseware model
'''
from external_auth.models import *
from django.contrib import admin
from ratelimitbackend import admin
class ExternalAuthMapAdmin(admin.ModelAdmin):
......
......@@ -9,7 +9,7 @@ from student.models import UserProfile, UserTestGroup, CourseEnrollmentAllowed,
from student.models import (
CourseEnrollment, Registration, PendingNameChange, CourseAccessRole, LinkedInAddToProfileConfiguration
)
from django.contrib import admin
from ratelimitbackend import admin
from student.roles import REGISTERED_ACCESS_ROLES
from xmodule.modulestore.django import modulestore
......
......@@ -17,9 +17,6 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
self.user.save()
self.course = CourseFactory.create(org='edx')
self.client.login(username=self.user.username, password='test')
self.grant_sudo_access('django_admin', 'test')
def test_save_valid_data(self):
data = {
......@@ -29,6 +26,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
'email': self.user.email
}
self.client.login(username=self.user.username, password='test')
# # adding new role from django admin page
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertRedirects(response, reverse('admin:student_courseaccessrole_changelist'))
......@@ -52,6 +51,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
'course_id': unicode(self.course.id)
}
self.client.login(username=self.user.username, password='test')
# # adding new role from django admin page
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertRedirects(response, reverse('admin:student_courseaccessrole_changelist'))
......@@ -68,6 +69,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
}
self.client.login(username=self.user.username, password='test')
# # adding new role from django admin page
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertRedirects(response, reverse('admin:student_courseaccessrole_changelist'))
......@@ -85,6 +88,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
}
self.client.login(username=self.user.username, password='test')
# # adding new role from django admin page
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertRedirects(response, reverse('admin:student_courseaccessrole_changelist'))
......@@ -104,6 +109,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
'email': email
}
self.client.login(username=self.user.username, password='test')
# Adding new role with invalid data
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertContains(
......@@ -129,6 +136,8 @@ class AdminCourseRolesPageTest(ModuleStoreTestCase):
'email': self.user.email
}
self.client.login(username=self.user.username, password='test')
# # adding new role from django admin page
response = self.client.post(reverse('admin:student_courseaccessrole_add'), data=data)
self.assertContains(
......
......@@ -241,23 +241,3 @@ def view_course_team_settings(_step, whom):
world.click_course_settings()
link_css = 'li.nav-course-settings-team a'
world.css_click(link_css)
@step('I get sudo access with password "([^"]*)"$')
def i_get_sudo_access(_step, password):
"""
Get sudo access for instructor or staff user.
Set the password value of the element to the specified password.
Note that wait_for empty is due to password field
It will return password like this **** not text.
"""
sudo_form = world.css_find('form.sudo-form')
# check if sudo form is available then submit password to get sudo access
# otherwise return True because sudo access already given.
if len(sudo_form) > 0:
css_selector = 'input[id=id_password]'
world.retry_on_exception(lambda: world.css_find(css_selector)[0].fill(password))
world.wait_for(lambda _: not world.css_has_value(css_selector, '', index=0))
world.css_click('input[type=submit]')
return True
......@@ -3,6 +3,6 @@ django admin pages for courseware model
'''
from track.models import TrackingLog
from django.contrib import admin
from ratelimitbackend import admin
admin.site.register(TrackingLog)
"""Admin interface for the util app. """
from django.contrib import admin
from ratelimitbackend import admin
from util.models import RateLimitConfiguration
......
......@@ -17,7 +17,6 @@ from request_cache.middleware import RequestCache
from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error
from openedx.core.lib.tempdir import mkdtemp_clean
from sudo.utils import region_name
from xmodule.contentstore.django import _CONTENTSTORE
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore, clear_existing_modulestores
......@@ -423,13 +422,3 @@ class ModuleStoreTestCase(TestCase):
fields={"display_name": "Syllabus"},
)
return self.toy_loc
def grant_sudo_access(self, region, password):
"""
Grant sudo access to staff or instructor user.
"""
self.client.post(
'/sudo/?region={}'.format(region_name(region)),
{'password': password},
follow=True
)
"""
Django sudo page to get sudo access.
"""
from bok_choy.javascript import wait_for_js
from bok_choy.page_object import PageObject
class SudoPage(PageObject):
"""
Sudo page to get sudo access
"""
SUDO_FORM = 'form.sudo-form'
def __init__(self, browser, redirect_page):
super(SudoPage, self).__init__(browser)
self.redirect_page = redirect_page
def is_browser_on_page(self):
return self.q(css=self.SUDO_FORM).present
@property
def url(self):
"""
Construct a URL to the page which needs sudo access.
"""
return self.redirect_page.url
@property
def sudo_password_input(self):
"""
Returns sudo password input box.
"""
return self.q(css='{} input[id=id_password]'.format(self.SUDO_FORM))
@property
def submit_button(self):
"""
Returns submit button.
"""
return self.q(css='{} input[type=submit]'.format(self.SUDO_FORM))
@wait_for_js
def submit_sudo_password_and_get_access(self, password):
"""
Fill password in input field and click submit.
"""
input_box = self.sudo_password_input.first.results[0]
input_box.send_keys(password)
self.click_submit()
self.redirect_page.wait_for_page()
def click_submit(self):
"""
Click on submit button.
"""
return self.submit_button.click()
......@@ -759,14 +759,12 @@ class DataDownloadPage(PageObject):
return self.report_download_links.map(lambda el: el.text)
# pylint: disable=invalid-name
class StudentAdminPage(PageObject):
"""
Student admin section of the Instructor dashboard.
"""
url = None
ENTRANCE_EXAM_CONTAINER = ".entrance-exam-grade-container"
SG_CONTAINER = ".student-grade-container"
EE_CONTAINER = ".entrance-exam-grade-container"
def is_browser_on_page(self):
"""
......@@ -775,182 +773,127 @@ class StudentAdminPage(PageObject):
return self.q(css='a[data-section=student_admin].active-section').present
@property
def entrance_exam_student_email_input(self):
def student_email_input(self):
"""
Returns email address/username input box for entrance exam.
Returns email address/username input box.
"""
return self.q(css='{} input[name=entrance-exam-student-select-grade]'.format(self.ENTRANCE_EXAM_CONTAINER))
return self.q(css='{} input[name=entrance-exam-student-select-grade]'.format(self.EE_CONTAINER))
@property
def entrance_exam_reset_attempts_button(self):
def reset_attempts_button(self):
"""
Returns reset student attempts button for entrance exam.
Returns reset student attempts button.
"""
return self.q(css='{} input[name=reset-entrance-exam-attempts]'.format(self.ENTRANCE_EXAM_CONTAINER))
return self.q(css='{} input[name=reset-entrance-exam-attempts]'.format(self.EE_CONTAINER))
@property
def entrance_exam_rescore_submission_button(self):
def rescore_submission_button(self):
"""
Returns rescore student submission button for entrance exam.
Returns rescore student submission button.
"""
return self.q(css='{} input[name=rescore-entrance-exam]'.format(self.ENTRANCE_EXAM_CONTAINER))
return self.q(css='{} input[name=rescore-entrance-exam]'.format(self.EE_CONTAINER))
@property
def skip_entrance_exam_button(self):
"""
Return Let Student Skip Entrance Exam button.
"""
return self.q(css='{} input[name=skip-entrance-exam]'.format(self.ENTRANCE_EXAM_CONTAINER))
return self.q(css='{} input[name=skip-entrance-exam]'.format(self.EE_CONTAINER))
@property
def entrance_exam_delete_student_state_button(self):
def delete_student_state_button(self):
"""
Returns delete student state button for entrance exam.
Returns delete student state button.
"""
return self.q(css='{} input[name=delete-entrance-exam-state]'.format(self.ENTRANCE_EXAM_CONTAINER))
return self.q(css='{} input[name=delete-entrance-exam-state]'.format(self.EE_CONTAINER))
@property
def background_task_history_button(self):
"""
Returns show background task history for student button for entrance exam.
"""
return self.q(css='{} input[name=entrance-exam-task-history]'.format(self.ENTRANCE_EXAM_CONTAINER))
@property
def entrance_exam_top_notification(self):
"""
Returns show background task history for student button for entrance exam.
"""
return self.q(css='{} .request-response-error'.format(self.ENTRANCE_EXAM_CONTAINER)).first
@property
def reset_attempts_button(self):
"""
Returns reset student attempts button.
"""
return self.q(css='{} input[name=reset-attempts-single]'.format(self.SG_CONTAINER))
@property
def rescore_submission_button(self):
"""
Returns rescore student submission button.
"""
return self.q(css='{} input[name=rescore-problem-single]'.format(self.SG_CONTAINER))
@property
def delete_student_state_button(self):
"""
Returns delete student state button.
Returns show background task history for student button.
"""
return self.q(css='{} input[name=delete-state-single]'.format(self.SG_CONTAINER))
return self.q(css='{} input[name=entrance-exam-task-history]'.format(self.EE_CONTAINER))
@property
def top_notification(self):
"""
Returns show background task history for student button.
"""
return self.q(css='{} .request-response-error'.format(self.SG_CONTAINER)).first
return self.q(css='{} .request-response-error'.format(self.EE_CONTAINER)).first
def is_entrance_exam_student_email_input_visible(self):
def is_student_email_input_visible(self):
"""
Returns True if student email address/username input box is present
for entrance exam.
Returns True if student email address/username input box is present.
"""
return self.entrance_exam_student_email_input.is_present()
return self.student_email_input.is_present()
def is_entrance_exam_reset_attempts_button_visible(self):
def is_reset_attempts_button_visible(self):
"""
Returns True if reset student attempts button is present
for entrance exam.
Returns True if reset student attempts button is present.
"""
return self.entrance_exam_reset_attempts_button.is_present()
return self.reset_attempts_button.is_present()
def is_entrance_exam_rescore_submission_button_visible(self):
def is_rescore_submission_button_visible(self):
"""
Returns True if rescore student submission button is present
for entrance exam.
Returns True if rescore student submission button is present.
"""
return self.entrance_exam_rescore_submission_button.is_present()
return self.rescore_submission_button.is_present()
def is_entrance_exam_delete_student_state_button_visible(self):
def is_delete_student_state_button_visible(self):
"""
Returns True if delete student state for entrance exam button is present
for entrance exam.
Returns True if delete student state for entrance exam button is present.
"""
return self.entrance_exam_delete_student_state_button.is_present()
return self.delete_student_state_button.is_present()
def is_background_task_history_button_visible(self):
"""
Returns True if show background task history for student button is present
for entrance exam.
Returns True if show background task history for student button is present.
"""
return self.background_task_history_button.is_present()
def is_background_task_history_table_visible(self):
"""
Returns True if background task history table is present
for entrance exam.
Returns True if background task history table is present.
"""
return self.q(css='{} .entrance-exam-task-history-table'.format(self.ENTRANCE_EXAM_CONTAINER)).is_present()
return self.q(css='{} .entrance-exam-task-history-table'.format(self.EE_CONTAINER)).is_present()
def entrance_exam_click_reset_attempts_button(self):
def click_reset_attempts_button(self):
"""
clicks reset student attempts button for entrance exam.
clicks reset student attempts button.
"""
return self.entrance_exam_reset_attempts_button.click()
return self.reset_attempts_button.click()
def entrance_exam_click_rescore_submissions_button(self):
def click_rescore_submissions_button(self):
"""
clicks rescore submissions button for entrance exam.
clicks rescore submissions button.
"""
return self.entrance_exam_rescore_submission_button.click()
return self.rescore_submission_button.click()
def click_skip_entrance_exam_button(self):
"""
clicks let student skip entrance exam button for entrance exam.
clicks let student skip entrance exam button.
"""
return self.skip_entrance_exam_button.click()
def entrance_exam_click_delete_student_state_button(self):
def click_delete_student_state_button(self):
"""
clicks delete student state button for entrance exam.
clicks delete student state button.
"""
return self.entrance_exam_delete_student_state_button.click()
return self.delete_student_state_button.click()
def entrance_exam_click_task_history_button(self):
def click_task_history_button(self):
"""
clicks background task history button for entrance exam.
clicks background task history button.
"""
return self.background_task_history_button.click()
def set_student_email_for_ee(self, email_addres):
def set_student_email(self, email_addres):
"""
Sets given email address as value of student email address/username input box
for entrance exam.
Sets given email address as value of student email address/username input box.
"""
input_box = self.entrance_exam_student_email_input.first.results[0]
input_box = self.student_email_input.first.results[0]
input_box.send_keys(email_addres)
def click_reset_attempts_button(self):
"""
clicks reset student attempts button.
"""
return self.reset_attempts_button.click()
def click_rescore_submissions_button(self):
"""
clicks rescore submissions button.
"""
return self.rescore_submission_button.click()
def click_delete_student_state_button(self):
"""
clicks delete student state button and confirm the action.
"""
with self.handle_alert(confirm=True):
self.delete_student_state_button.click()
self.wait_for_ajax()
class CertificatesPage(PageObject):
"""
......
......@@ -68,14 +68,31 @@ class StaffDebugPage(PageObject):
def is_browser_on_page(self):
return self.q(css='section.staff-modal').present
def click_student_grade_adjustments(self, user=None):
def reset_attempts(self, user=None):
"""
This clicks on the reset attempts link with an optionally
specified user.
"""
if user:
self.q(css='input[id^=sd_fu_]').first.fill(user)
self.q(css='section.staff-modal a.staff-debug-grade-adjustments').click()
self.q(css='section.staff-modal a.staff-debug-reset').click()
def delete_state(self, user=None):
"""
This delete's a student's state for the problem
"""
if user:
self.q(css='input[id^=sd_fu_]').fill(user)
self.q(css='section.staff-modal a.staff-debug-sdelete').click()
def rescore(self, user=None):
"""
This clicks on the reset attempts link with an optionally
specified user.
"""
if user:
self.q(css='input[id^=sd_fu_]').first.fill(user)
self.q(css='section.staff-modal a.staff-debug-rescore').click()
@property
def idash_msg(self):
......
......@@ -9,7 +9,7 @@ from pytz import UTC, utc
from bok_choy.promise import EmptyPromise
from nose.plugins.attrib import attr
from .helpers import CohortTestMixin
from ..helpers import UniqueCourseTest, EventsTestMixin, create_user_partition_json, get_sudo_access
from ..helpers import UniqueCourseTest, EventsTestMixin, create_user_partition_json
from xmodule.partitions.partitions import Group
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
from ...pages.lms.auto_auth import AutoAuthPage
......@@ -53,16 +53,14 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
).visit().get_user_id()
# login as an instructor
instructor_password = 'test'
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True, password=instructor_password
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, self.instructor_dashboard_page, instructor_password)
self.instructor_dashboard_page.visit()
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
......@@ -650,16 +648,14 @@ class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
# login as an instructor
self.instructor_password = 'test'
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True, password=self.instructor_password
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, self.instructor_dashboard_page, self.instructor_password)
self.instructor_dashboard_page.visit()
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
self.cohort_management_page.wait_for_page()
......@@ -944,16 +940,14 @@ class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
})
# login as an instructor
instructor_password = 'test'
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True, password=instructor_password
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, self.instructor_dashboard_page, instructor_password)
self.instructor_dashboard_page.visit()
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
......
......@@ -25,7 +25,6 @@ from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from unittest import TestCase
from ..pages.common.sudo_page import SudoPage
from ..pages.common import BASE_URL
......@@ -685,12 +684,3 @@ class TestWithSearchIndexMixin(object):
def _cleanup_index_file(self):
""" Removes search index backing file """
remove_file(self.TEST_INDEX_FILENAME)
def get_sudo_access(browser, redirect_page, password):
"""
Get sudo access for instructor or staff user.
"""
sudo_password_page = SudoPage(browser, redirect_page)
sudo_password_page.visit()
sudo_password_page.submit_sudo_password_and_get_access(password)
......@@ -9,7 +9,6 @@ from ...pages.studio.overview import CourseOutlinePage
from ...pages.lms.courseware_search import CoursewareSearchPage
from ...pages.lms.staff_view import StaffPage
from ...fixtures.course import XBlockFixtureDesc
from ..helpers import get_sudo_access
from nose.plugins.attrib import attr
......@@ -84,13 +83,13 @@ class CoursewareSearchCohortTest(ContainerBase):
super(CoursewareSearchCohortTest, self).tearDown()
os.remove(self.TEST_INDEX_FILENAME)
def _auto_auth(self, username, email, staff, password='test'):
def _auto_auth(self, username, email, staff):
"""
Logout and login with given credentials.
"""
LogoutPage(self.browser).visit()
StudioAutoAuthPage(self.browser, username=username, email=email,
course_id=self.course_id, staff=staff, password=password).visit()
course_id=self.course_id, staff=staff).visit()
def _studio_reindex(self):
"""
......@@ -194,7 +193,7 @@ class CoursewareSearchCohortTest(ContainerBase):
Each cohort is assigned one student.
"""
instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, instructor_dashboard_page, 'test')
instructor_dashboard_page.visit()
cohort_management_page = instructor_dashboard_page.select_cohort_management()
def add_cohort_with_student(cohort_name, content_group, student):
......
......@@ -6,7 +6,7 @@ End-to-end tests for the LMS Instructor Dashboard.
from nose.plugins.attrib import attr
from bok_choy.promise import EmptyPromise
from ..helpers import UniqueCourseTest, get_modal_alert, EventsTestMixin, get_sudo_access
from ..helpers import UniqueCourseTest, get_modal_alert, EventsTestMixin
from ...pages.common.logout import LogoutPage
from ...pages.lms.auto_auth import AutoAuthPage
from ...pages.lms.instructor_dashboard import InstructorDashboardPage
......@@ -22,9 +22,7 @@ class BaseInstructorDashboardTest(EventsTestMixin, UniqueCourseTest):
Logs in as an instructor and returns the id.
"""
username = "test_instructor_{uuid}".format(uuid=self.unique_id[0:6])
auto_auth_page = AutoAuthPage(
self.browser, username=username, course_id=self.course_id, staff=True, password="test"
)
auto_auth_page = AutoAuthPage(self.browser, username=username, course_id=self.course_id, staff=True)
return username, auto_auth_page.visit().get_user_id()
def visit_instructor_dashboard(self):
......@@ -32,7 +30,6 @@ class BaseInstructorDashboardTest(EventsTestMixin, UniqueCourseTest):
Visits the instructor dashboard.
"""
instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, instructor_dashboard_page, "test")
instructor_dashboard_page.visit()
return instructor_dashboard_page
......@@ -145,10 +142,10 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
Then I see Student Email input box, Reset Student Attempt, Rescore Student Submission,
Delete Student State for entrance exam and Show Background Task History for Student buttons
"""
self.assertTrue(self.student_admin_section.is_entrance_exam_student_email_input_visible())
self.assertTrue(self.student_admin_section.is_entrance_exam_reset_attempts_button_visible())
self.assertTrue(self.student_admin_section.is_entrance_exam_rescore_submission_button_visible())
self.assertTrue(self.student_admin_section.is_entrance_exam_delete_student_state_button_visible())
self.assertTrue(self.student_admin_section.is_student_email_input_visible())
self.assertTrue(self.student_admin_section.is_reset_attempts_button_visible())
self.assertTrue(self.student_admin_section.is_rescore_submission_button_visible())
self.assertTrue(self.student_admin_section.is_delete_student_state_button_visible())
self.assertTrue(self.student_admin_section.is_background_task_history_button_visible())
def test_clicking_reset_student_attempts_button_without_email_shows_error(self):
......@@ -161,10 +158,10 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
Then I should be shown an Error Notification
And The Notification message should read 'Please enter a student email address or username.'
"""
self.student_admin_section.entrance_exam_click_reset_attempts_button()
self.student_admin_section.click_reset_attempts_button()
self.assertEqual(
'Please enter a student email address or username.',
self.student_admin_section.entrance_exam_top_notification.text[0]
self.student_admin_section.top_notification.text[0]
)
def test_clicking_reset_student_attempts_button_with_success(self):
......@@ -177,8 +174,8 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
email address or username
Then I should be shown an alert with success message
"""
self.student_admin_section.set_student_email_for_ee(self.student_identifier)
self.student_admin_section.entrance_exam_click_reset_attempts_button()
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_reset_attempts_button()
alert = get_modal_alert(self.student_admin_section.browser)
alert.dismiss()
......@@ -191,10 +188,10 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
Adjustment after non existing student email address or username
Then I should be shown an error message
"""
self.student_admin_section.set_student_email_for_ee('non_existing@example.com')
self.student_admin_section.entrance_exam_click_reset_attempts_button()
self.student_admin_section.set_student_email('non_existing@example.com')
self.student_admin_section.click_reset_attempts_button()
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.entrance_exam_top_notification.text[0]), 0)
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_rescore_submission_button_with_success(self):
"""
......@@ -205,8 +202,8 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
Adjustment after entering a valid student email address or username
Then I should be shown an alert with success message
"""
self.student_admin_section.set_student_email_for_ee(self.student_identifier)
self.student_admin_section.entrance_exam_click_rescore_submissions_button()
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_rescore_submissions_button()
alert = get_modal_alert(self.student_admin_section.browser)
alert.dismiss()
......@@ -219,10 +216,10 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
Adjustment after non existing student email address or username
Then I should be shown an error message
"""
self.student_admin_section.set_student_email_for_ee('non_existing@example.com')
self.student_admin_section.entrance_exam_click_rescore_submissions_button()
self.student_admin_section.set_student_email('non_existing@example.com')
self.student_admin_section.click_rescore_submissions_button()
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.entrance_exam_top_notification.text[0]), 0)
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_skip_entrance_exam_button_with_success(self):
"""
......@@ -234,7 +231,7 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
email address or username
Then I should be shown an alert with success message
"""
self.student_admin_section.set_student_email_for_ee(self.student_identifier)
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_skip_entrance_exam_button()
#first we have window.confirm
alert = get_modal_alert(self.student_admin_section.browser)
......@@ -254,14 +251,14 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
student email address or username
Then I should be shown an error message
"""
self.student_admin_section.set_student_email_for_ee('non_existing@example.com')
self.student_admin_section.set_student_email('non_existing@example.com')
self.student_admin_section.click_skip_entrance_exam_button()
#first we have window.confirm
alert = get_modal_alert(self.student_admin_section.browser)
alert.accept()
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.entrance_exam_top_notification.text[0]), 0)
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_delete_student_attempts_button_with_success(self):
"""
......@@ -273,8 +270,8 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
email address or username
Then I should be shown an alert with success message
"""
self.student_admin_section.set_student_email_for_ee(self.student_identifier)
self.student_admin_section.entrance_exam_click_delete_student_state_button()
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_delete_student_state_button()
alert = get_modal_alert(self.student_admin_section.browser)
alert.dismiss()
......@@ -289,10 +286,10 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
email address or username
Then I should be shown an error message
"""
self.student_admin_section.set_student_email_for_ee('non_existing@example.com')
self.student_admin_section.entrance_exam_click_delete_student_state_button()
self.student_admin_section.set_student_email('non_existing@example.com')
self.student_admin_section.click_delete_student_state_button()
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.entrance_exam_top_notification.text[0]), 0)
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_task_history_button_with_success(self):
"""
......@@ -304,8 +301,8 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
email address or username
Then I should be shown an table listing all background tasks
"""
self.student_admin_section.set_student_email_for_ee(self.student_identifier)
self.student_admin_section.entrance_exam_click_task_history_button()
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_task_history_button()
self.assertTrue(self.student_admin_section.is_background_task_history_table_visible())
......
......@@ -3,12 +3,10 @@
Tests the "preview" selector in the LMS that allows changing between Staff, Student, and Content Groups.
"""
from ..helpers import UniqueCourseTest, create_user_partition_json, get_modal_alert
from ..helpers import UniqueCourseTest, create_user_partition_json
from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.lms.courseware import CoursewarePage
from ...pages.lms.instructor_dashboard import InstructorDashboardPage, StudentAdminPage
from ...pages.lms.staff_view import StaffPage
from ...pages.common.sudo_page import SudoPage
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
from xmodule.partitions.partitions import Group
from textwrap import dedent
......@@ -38,9 +36,8 @@ class StaffViewTest(UniqueCourseTest):
# Auto-auth register for the course.
# Do this as global staff so that you will see the Staff View
self.staff_password = 'test'
AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL,
course_id=self.course_id, staff=True, password=self.staff_password).visit()
course_id=self.course_id, staff=True).visit()
def _goto_staff_page(self):
"""
......@@ -102,41 +99,26 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
"""
Tests that verify the staff debug info.
"""
def _goto_student_admin_section(self):
"""
Get sudo access and return student admin section.
"""
instructor_page = InstructorDashboardPage(self.browser, self.course_id)
sudo_page = SudoPage(self.browser, instructor_page)
sudo_page.wait_for_page()
sudo_page.submit_sudo_password_and_get_access(self.staff_password)
student_admin_section = StudentAdminPage(self.browser)
student_admin_section.wait_for_page()
return student_admin_section
def test_reset_attempts_empty(self):
"""
Test that we reset even when there is no student state
"""
staff_debug_page = self._goto_staff_page().open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_reset_attempts_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.reset_attempts()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully reset the attempts '
'for user {}'.format(self.USERNAME), msg)
def test_delete_state_empty(self):
"""
Test that we delete properly even when there isn't state to delete.
"""
staff_debug_page = self._goto_staff_page().open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_delete_student_state_button()
self.assertEqual(len(student_admin_section.top_notification.text[0]), 0)
staff_debug_page.delete_state()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully deleted student state '
'for user {}'.format(self.USERNAME), msg)
def test_reset_attempts_state(self):
"""
......@@ -146,11 +128,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_reset_attempts_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.reset_attempts()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully reset the attempts '
'for user {}'.format(self.USERNAME), msg)
def test_rescore_state(self):
"""
......@@ -160,11 +141,9 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_rescore_submissions_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.rescore()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully rescored problem for user STAFF_TESTER', msg)
def test_student_state_delete(self):
"""
......@@ -174,10 +153,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_delete_student_state_button()
self.assertEqual(len(student_admin_section.top_notification.text[0]), 0)
staff_debug_page.delete_state()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully deleted student state '
'for user {}'.format(self.USERNAME), msg)
def test_student_by_email(self):
"""
......@@ -187,11 +166,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments(self.EMAIL)
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_reset_attempts_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.reset_attempts(self.EMAIL)
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully reset the attempts '
'for user {}'.format(self.EMAIL), msg)
def test_bad_student(self):
"""
......@@ -201,10 +179,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments('INVALIDUSER')
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_delete_student_state_button()
self.assertGreater(len(student_admin_section.top_notification.text[0]), 0)
staff_debug_page.delete_state('INVALIDUSER')
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Failed to delete student state. '
'User does not exist.', msg)
def test_reset_attempts_for_problem_loaded_via_ajax(self):
"""
......@@ -215,11 +193,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_reset_attempts_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.reset_attempts()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully reset the attempts '
'for user {}'.format(self.USERNAME), msg)
def test_rescore_state_for_problem_loaded_via_ajax(self):
"""
......@@ -230,11 +207,9 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_rescore_submissions_button()
alert = get_modal_alert(student_admin_section.browser)
alert.dismiss()
staff_debug_page.rescore()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully rescored problem for user STAFF_TESTER', msg)
def test_student_state_delete_for_problem_loaded_via_ajax(self):
"""
......@@ -245,10 +220,10 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
staff_page.answer_problem()
staff_debug_page = staff_page.open_staff_debug_info()
staff_debug_page.click_student_grade_adjustments()
student_admin_section = self._goto_student_admin_section()
student_admin_section.click_delete_student_state_button()
self.assertEqual(len(student_admin_section.top_notification.text[0]), 0)
staff_debug_page.delete_state()
msg = staff_debug_page.idash_msg[0]
self.assertEqual(u'Successfully deleted student state '
'for user {}'.format(self.USERNAME), msg)
class CourseWithContentGroupsTest(StaffViewTest):
......
......@@ -5,7 +5,6 @@ from flaky import flaky
from nose.plugins.attrib import attr
from .base_studio_test import StudioCourseTest
from ..helpers import get_sudo_access
from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.studio.users import CourseTeamPage
......@@ -39,7 +38,6 @@ class CourseTeamPageTest(StudioCourseTest):
self.page = CourseTeamPage( # pylint:disable=attribute-defined-outside-init
self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
)
get_sudo_access(self.browser, self.page, self.user.get('password'))
self._go_to_course_team_page()
def _go_to_course_team_page(self):
......@@ -127,7 +125,6 @@ class CourseTeamPageTest(StudioCourseTest):
self.page.add_user_to_course(self.other_user.get('email'))
self._assert_user_present(self.other_user, present=True)
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._assert_current_course(visible=True)
@flaky # TODO fix this, see TNL-2667
......@@ -146,7 +143,6 @@ class CourseTeamPageTest(StudioCourseTest):
self._assert_user_present(self.other_user, present=True)
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._assert_current_course(visible=True)
self._go_to_course_team_page()
......@@ -208,7 +204,6 @@ class CourseTeamPageTest(StudioCourseTest):
self._assert_is_admin(other)
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._go_to_course_team_page()
other = self.page.get_user(self.other_user.get('email'))
self.assertTrue(other.is_current_user)
......@@ -240,14 +235,12 @@ class CourseTeamPageTest(StudioCourseTest):
# precondition check - frank is an admin and can add/delete/promote/demote users
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._go_to_course_team_page()
other = self.page.get_user(self.other_user.get('email'))
self.assertTrue(other.is_current_user)
self._assert_can_manage_users()
self.log_in(self.user)
get_sudo_access(self.browser, self.page, self.user.get('password'))
self._go_to_course_team_page()
other = self.page.get_user(self.other_user.get('email'))
other.click_demote()
......@@ -256,7 +249,6 @@ class CourseTeamPageTest(StudioCourseTest):
self._assert_is_staff(other)
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._go_to_course_team_page()
other = self.page.get_user(self.other_user.get('email'))
self.assertTrue(other.is_current_user)
......@@ -342,7 +334,6 @@ class CourseTeamPageTest(StudioCourseTest):
self.assertFalse(current.can_promote)
self.log_in(self.other_user)
get_sudo_access(self.browser, self.page, self.other_user.get('password'))
self._go_to_course_team_page()
current = self.page.get_user(self.user.get('email'))
......
......@@ -7,7 +7,6 @@ from flaky import flaky
from .base_studio_test import StudioLibraryTest
from ...fixtures.course import XBlockFixtureDesc
from ..helpers import get_sudo_access
from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.studio.utils import add_component
from ...pages.studio.library import LibraryEditPage
......@@ -515,7 +514,6 @@ class LibraryUsersPageTest(StudioLibraryTest):
AutoAuthPage(self.browser, username="second", email="second@example.com", no_login=True).visit()
self.page = LibraryUsersPage(self.browser, self.library_key)
get_sudo_access(self.browser, self.page, self.user.get("password"))
self.page.visit()
def _refresh_page(self):
......
......@@ -11,7 +11,6 @@ from ..pages.studio.settings_group_configurations import GroupConfigurationsPage
from ..pages.studio.auto_auth import AutoAuthPage as StudioAutoAuthPage
from ..fixtures.course import XBlockFixtureDesc
from ..fixtures import LMS_BASE_URL
from .helpers import get_sudo_access
from ..pages.studio.component_editor import ComponentVisibilityEditorView
from ..pages.lms.instructor_dashboard import InstructorDashboardPage
from ..pages.lms.courseware import CoursewarePage
......@@ -55,12 +54,8 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
).visit()
# Start logged in as the staff user.
self.instructor_password = 'test'
StudioAutoAuthPage(
self.browser,
username=self.staff_user["username"],
email=self.staff_user["email"],
password=self.instructor_password
self.browser, username=self.staff_user["username"], email=self.staff_user["email"]
).visit()
def populate_course_fixture(self, course_fixture):
......@@ -143,7 +138,6 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
Each cohort is assigned one student.
"""
instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
get_sudo_access(self.browser, instructor_dashboard_page, self.instructor_password)
instructor_dashboard_page.visit()
cohort_management_page = instructor_dashboard_page.select_cohort_management()
......
......@@ -27,7 +27,7 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
def setUp(self):
super(TestOptoutCourseEmails, self).setUp()
course_title = u"ẗëṡẗ title イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ"
self.course = CourseFactory.create(display_name=course_title, run='T12015')
self.course = CourseFactory.create(display_name=course_title)
self.instructor = AdminFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
......@@ -47,7 +47,6 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
"""Navigate to the instructor dash's email view"""
# Pull up email view on instructor dashboard
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.get(url)
email_section = '<div class="vert-left send-email" id="section-send-email">'
# If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False
......
......@@ -53,7 +53,7 @@ class EmailSendFromDashboardTestCase(ModuleStoreTestCase):
def setUp(self):
super(EmailSendFromDashboardTestCase, self).setUp()
course_title = u"ẗëṡẗ title イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ"
self.course = CourseFactory.create(display_name=course_title, run="1T2015")
self.course = CourseFactory.create(display_name=course_title)
self.instructor = InstructorFactory(course_key=self.course.id)
......@@ -75,7 +75,6 @@ class EmailSendFromDashboardTestCase(ModuleStoreTestCase):
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
# Response loads the whole instructor dashboard, so no need to explicitly
# navigate to a particular email section
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.get(self.url)
email_section = '<div class="vert-left send-email" id="section-send-email">'
# If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False
......
......@@ -47,10 +47,9 @@ class TestEmailErrors(ModuleStoreTestCase):
def setUp(self):
super(TestEmailErrors, self).setUp()
course_title = u"ẗëṡẗ title イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ"
self.course = CourseFactory.create(display_name=course_title, run="1T2015")
self.course = CourseFactory.create(display_name=course_title)
self.instructor = AdminFactory.create()
self.client.login(username=self.instructor.username, password="test")
self.grant_sudo_access(unicode(self.course.id), 'test')
# load initial content (since we don't run migrations as part of tests):
call_command("loaddata", "course_email_template.json")
......
......@@ -3,7 +3,7 @@ django admin pages for courseware model
'''
from courseware.models import StudentModule, OfflineComputedGrade, OfflineComputedGradeLog
from django.contrib import admin
from ratelimitbackend import admin
admin.site.register(StudentModule)
......
......@@ -51,7 +51,6 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 5/10"
And I see graph with total progress "5%"
Then I click on the "Instructor" tab
Then I get sudo access with password "test"
And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "50"
......@@ -91,7 +90,6 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 8/10"
And I see graph with total progress "8%"
Then I click on the "Instructor" tab
Then I get sudo access with password "test"
And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "80"
......@@ -118,7 +116,6 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 0/10"
And I see graph with total progress "0%"
Then I click on the "Instructor" tab
Then I get sudo access with password "test"
And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "0"
......
@shard_1
Feature: LMS.Debug staff info links
As a course staff in an edX course
In order to test my understanding of the material
I want to click on staff debug info links
Scenario: I can reset student attempts
When i am staff member for the course "model_course"
And I am viewing a "multiple choice" problem
And I can view staff debug info
Then I can reset student attempts
Then I cannot see delete student state link
Then I cannot see rescore student submission link
"""
Steps for staff_debug_info.feature lettuce tests
"""
from django.contrib.auth.models import User
from lettuce import world, step
from common import create_course, course_id
from courseware.courses import get_course_by_id
from instructor.access import allow_access
@step(u'i am staff member for the course "([^"]*)"$')
def i_am_staff_member_for_the_course(step, course_number):
# Create the course
create_course(step, course_number)
course = get_course_by_id(course_id(course_number))
# Create the user
world.create_user('robot', 'test')
user = User.objects.get(username='robot')
# Add user as a course staff.
allow_access(course, user, "staff")
world.log_in(username='robot', password='test')
@step(u'I can view staff debug info')
def view_staff_debug_info(step):
css_selector = "a.instructor-info-action"
world.css_click(css_selector)
world.wait_for_visible("section.staff-modal")
@step(u'I can reset student attempts')
def view_staff_debug_info(step):
css_selector = "a.staff-debug-reset"
world.css_click(css_selector)
world.wait_for_ajax_complete()
@step(u'I cannot see delete student state link')
def view_staff_debug_info(step):
css_selector = "a.staff-debug-sdelete"
world.is_css_not_present(css_selector)
@step(u'I cannot see rescore student submission link')
def view_staff_debug_info(step):
css_selector = "a.staff-debug-rescore"
world.is_css_not_present(css_selector)
......@@ -386,7 +386,6 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase):
# hit skip entrance exam api in instructor app
instructor = InstructorFactory(course_key=self.course.id)
self.client.login(username=instructor.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)})
response = self.client.post(url, {
'unique_student_identifier': self.request.user.email,
......
......@@ -383,7 +383,6 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
instructor = InstructorFactory(course_key=self.course.id)
self.client.logout()
self.client.login(username=instructor.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)})
response = self.client.post(url, {
......
......@@ -67,7 +67,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
'book_index': index})
for index, __ in enumerate(course.textbooks)
])
self.grant_sudo_access(unicode(course.id), 'test')
for url in urls:
self.assert_request_status_code(404, url)
......@@ -82,7 +81,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
'book_index': index})
for index in xrange(len(course.textbooks))
])
self.grant_sudo_access(unicode(course.id), 'test')
for url in urls:
self.assert_request_status_code(200, url)
......@@ -209,8 +207,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}),
reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})]
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
# Shouldn't be able to get to the instructor pages
for url in urls:
self.assert_request_status_code(404, url)
......@@ -222,8 +218,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self.login(self.staff_user)
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
# Now should be able to get to self.course, but not self.test_course
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
self.assert_request_status_code(200, url)
......@@ -238,8 +232,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self.login(self.instructor_user)
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
# Now should be able to get to self.course, but not self.test_course
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
self.assert_request_status_code(200, url)
......@@ -253,9 +245,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
and student profile pages for course in their org.
"""
self.login(self.org_staff_user)
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
self.grant_sudo_access(unicode(self.other_org_course.id), 'test')
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
self.assert_request_status_code(200, url)
......@@ -271,9 +260,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
and student profile pages for course in their org.
"""
self.login(self.org_instructor_user)
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
self.grant_sudo_access(unicode(self.other_org_course.id), 'test')
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
self.assert_request_status_code(200, url)
......@@ -289,8 +275,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self.login(self.global_staff_user)
self.grant_sudo_access(unicode(self.course.id), 'test')
self.grant_sudo_access(unicode(self.test_course.id), 'test')
# and now should be able to load both
urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}),
reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})]
......@@ -403,26 +387,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.global_staff_user)
self.assertTrue(self.enroll(self.course))
def test_org_instructor_cannot_access_without_sudo(self):
"""
Test that org instructor cannot load the instructor dashboard without sudo access
and it redirect org instructor to sudo password page.
"""
self.login(self.org_instructor_user)
url = reverse('instructor_dashboard', kwargs={'course_id': unicode(self.course.id)})
response = self.assert_request_status_code(401, url)
self.assertIn('Unauthorized', response.content)
def test_org_staff_cannot_access_without_sudo(self):
"""
Test that org staff cannot load the instructor dashboard without sudo access
and it redirect org staff to sudo password page.
"""
self.login(self.org_staff_user)
url = reverse('instructor_dashboard', kwargs={'course_id': unicode(self.course.id)})
response = self.assert_request_status_code(401, url)
self.assertIn('Unauthorized', response.content)
@attr('shard_1')
class TestBetatesterAccess(ModuleStoreTestCase, CourseAccessTestMixin):
......
......@@ -32,7 +32,6 @@ from course_modes.models import CourseMode
from courseware.testutils import RenderXBlockTestMixin
from courseware.tests.factories import StudentModuleFactory
from edxmako.tests import mako_middleware_process_request
from django_sudo_helpers.tests.utils import sudo_middleware_process_request
from student.models import CourseEnrollment
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
from util.tests.test_date_utils import fake_ugettext, fake_pgettext
......@@ -1153,7 +1152,6 @@ class TestIndexView(ModuleStoreTestCase):
)
request.user = user
mako_middleware_process_request(request)
sudo_middleware_process_request(request)
# Trigger the assertions embedded in the ViewCheckerBlocks
response = views.index(request, unicode(course.id), chapter=chapter.url_name, section=section.url_name)
......
......@@ -25,7 +25,6 @@ from django.shortcuts import redirect
from certificates import api as certs_api
from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
from django.views.decorators.csrf import ensure_csrf_cookie
from sudo.utils import revoke_sudo_privileges
from django.views.decorators.cache import cache_control
from django.db import transaction
from markupsafe import escape
......@@ -341,10 +340,6 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse
"""
# Revoke sudo privileges from a request explicitly
if request.is_sudo(region=course_id):
revoke_sudo_privileges(request, region=course_id)
course_key = CourseKey.from_string(course_id)
user = User.objects.prefetch_related("groups").get(id=request.user.id)
......
......@@ -7,8 +7,6 @@ Feature: LMS.Instructor Dash Bulk Email
Scenario: Send bulk email
Given there is a course with a staff, instructor and student
And I am logged in to the course as "<Role>"
Then I go to instructor tab
Then I get sudo access with password "test"
When I send email to "<Recipient>"
Then Email is sent to "<Recipient>"
......
......@@ -96,13 +96,6 @@ def log_into_the_course(step, role): # pylint: disable=unused-argument
world.expected_addresses['myself'] = [my_email]
@step("I go to instructor tab")
def i_got_to_instructor_tab(step): # pylint: disable=unused-argument
url = '/courses/{}'.format(world.bulk_email_course_key)
world.visit(url)
world.css_click('a[href="{}/instructor"]'.format(url))
@step(u'I send email to "([^"]*)"')
def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument
......@@ -122,6 +115,9 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument
call_command('loaddata', 'course_email_template.json')
# Go to the email section of the instructor dash
url = '/courses/{}'.format(world.bulk_email_course_key)
world.visit(url)
world.css_click('a[href="{}/instructor"]'.format(url))
world.css_click('a[data-section="send_email"]')
# Select the recipient
......
......@@ -12,7 +12,6 @@ from mock import patch
from nose.tools import assert_in # pylint: disable=no-name-in-module
from courseware.tests.factories import StaffFactory, InstructorFactory
from terrain.steps import i_get_sudo_access
@step(u'Given I am "([^"]*)" for a very large course')
......@@ -72,17 +71,11 @@ def i_am_staff_or_instructor(step, role): # pylint: disable=unused-argument
)
def go_to_instructor_tab(step):
def go_to_section(section_name):
# section name should be one of
# course_info, membership, student_admin, data_download, analytics, send_email
world.visit(u'/courses/{}'.format(world.course_key))
world.css_click(u'a[href="/courses/{}/instructor"]'.format(world.course_key))
i_get_sudo_access(step, 'test')
def go_to_section(section_name):
# section name should be one of
# course_info, membership, student_admin, data_download, analytics, send_email
world.css_click('a[data-section="{0}"]'.format(section_name))
......@@ -91,7 +84,6 @@ def click_a_button(step, button): # pylint: disable=unused-argument
if button == "Generate Grade Report":
# Go to the data download section of the instructor dash
go_to_instructor_tab(step)
go_to_section("data_download")
# Click generate grade report button
......@@ -109,21 +101,18 @@ def click_a_button(step, button): # pylint: disable=unused-argument
elif button == "Grading Configuration":
# Go to the data download section of the instructor dash
go_to_instructor_tab(step)
go_to_section("data_download")
world.css_click('input[name="dump-gradeconf"]')
elif button == "List enrolled students' profile information":
# Go to the data download section of the instructor dash
go_to_instructor_tab(step)
go_to_section("data_download")
world.css_click('input[name="list-profiles"]')
elif button == "Download profile information as a CSV":
# Go to the data download section of the instructor dash
go_to_instructor_tab(step)
go_to_section("data_download")
world.css_click('input[name="list-profiles-csv"]')
......@@ -143,5 +132,4 @@ def click_a_button(step, tab_name): # pylint: disable=unused-argument
'Analytics': 'analytics',
'Email': 'send_email',
}
go_to_instructor_tab(step)
go_to_section(tab_name_dict[tab_name])
......@@ -33,7 +33,6 @@ class TestInstructorAPIEnrollmentEmailLocalization(ModuleStoreTestCase):
self.instructor = InstructorFactory(course_key=self.course.id)
set_user_preference(self.instructor, LANGUAGE_KEY, 'zh-cn')
self.client.login(username=self.instructor.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
self.student = UserFactory.create()
set_user_preference(self.student, LANGUAGE_KEY, 'fr')
......
......@@ -96,7 +96,6 @@ class CertificatesInstructorDashTest(ModuleStoreTestCase):
def _assert_certificates_visible(self, is_visible):
"""Check that the certificates section is visible on the instructor dash. """
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.get(self.url)
if is_visible:
self.assertContains(response, "Certificates")
......@@ -123,7 +122,6 @@ class CertificatesInstructorDashTest(ModuleStoreTestCase):
def _assert_certificate_status(self, cert_name, expected_status):
"""Check the certificate status display on the instructor dash. """
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.get(self.url)
if expected_status == 'started':
......@@ -140,7 +138,6 @@ class CertificatesInstructorDashTest(ModuleStoreTestCase):
def _assert_enable_certs_button_is_disabled(self):
"""Check that the "enable student-generated certificates" button is disabled. """
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.get(self.url)
expected_html = '<button class="is-disabled" disabled>Enable Student-Generated Certificates</button>'
self.assertContains(response, expected_html)
......@@ -182,13 +179,11 @@ class CertificatesInstructorApiTest(ModuleStoreTestCase):
# Global staff have access
self.client.login(username=self.global_staff.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
response = self.client.post(url)
self.assertEqual(response.status_code, 302)
def test_generate_example_certificates(self):
self.client.login(username=self.global_staff.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
url = reverse(
'generate_example_certificates',
kwargs={'course_id': unicode(self.course.id)}
......@@ -207,7 +202,6 @@ class CertificatesInstructorApiTest(ModuleStoreTestCase):
@ddt.data(True, False)
def test_enable_certificate_generation(self, is_enabled):
self.client.login(username=self.global_staff.username, password='test')
self.grant_sudo_access(unicode(self.course.id), 'test')
url = reverse(
'enable_certificate_generation',
kwargs={'course_id': unicode(self.course.id)}
......
......@@ -29,7 +29,6 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
# Create instructor account
self.instructor = AdminFactory.create()
self.client.login(username=self.instructor.username, password="test")
self.grant_sudo_access(unicode(self.course.id), "test")
mode = CourseMode(
course_id=self.course.id.to_deprecated_string(), mode_slug='honor',
mode_display_name='honor', min_price=10, currency='usd'
......
......@@ -29,7 +29,6 @@ class TestNewInstructorDashboardEmailViewMongoBacked(ModuleStoreTestCase):
# Create instructor account
instructor = AdminFactory.create()
self.client.login(username=instructor.username, password="test")
self.grant_sudo_access(unicode(self.course.id), "test")
# URL for instructor dash
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
......
......@@ -35,8 +35,6 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
self.course = CourseFactory.create()
self.grant_sudo_access(unicode(self.course.id), "test")
self.users = [
UserFactory.create(username="student%d" % i, email="student%d@test.com" % i)
for i in xrange(USER_COUNT)
......@@ -54,6 +52,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
"""
course = self.course
# Run the Un-enroll students command
url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
response = self.client.post(
......
......@@ -44,7 +44,6 @@ class TestRawGradeCSV(TestSubmittingProblems):
"""
# Answer second problem correctly with 2nd user to expose bug
self.login(self.instructor, self.password)
self.grant_sudo_access(unicode(self.course.id), self.password)
resp = self.submit_question_answer('p2', {'2_1': 'Correct'})
self.assertEqual(resp.status_code, 200)
......
......@@ -52,10 +52,9 @@ class TestXss(ModuleStoreTestCase):
)
req.user = self._instructor
req.session = {}
req.is_sudo = lambda region=None: True
mako_middleware_process_request(req)
resp = legacy.instructor_dashboard(request=req, course_id=self._course.id.to_deprecated_string())
resp = legacy.instructor_dashboard(req, self._course.id.to_deprecated_string())
respUnicode = resp.content.decode(settings.DEFAULT_CHARSET)
self.assertNotIn(self._evil_student.profile.name, respUnicode)
self.assertIn(escape(self._evil_student.profile.name), respUnicode)
......
......@@ -39,7 +39,6 @@ class TestGradebook(ModuleStoreTestCase):
kwargs['grading_policy'] = self.grading_policy
self.course = CourseFactory.create(**kwargs)
self.grant_sudo_access(unicode(self.course.id), 'test')
chapter = ItemFactory.create(
parent_location=self.course.location,
category="sequential",
......
......@@ -43,7 +43,6 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
# Create instructor account
self.instructor = AdminFactory.create()
self.client.login(username=self.instructor.username, password="test")
self.grant_sudo_access(unicode(self.course.id), 'test')
# URL for instructor dash
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
......@@ -203,7 +202,6 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
student_cart.purchase()
self.client.login(username=self.instructor.username, password="test")
self.grant_sudo_access(unicode(self.course.id), 'test')
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
single_purchase_total = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course.id)
bulk_purchase_total = CourseRegCodeItem.get_total_amount_of_purchased_item(self.course.id)
......@@ -236,13 +234,3 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
expected_result,
'CCX Coaches are able to create their own Custom Courses based on this course' in response.content
)
def test_sudo_required_on_dashboard(self):
"""
Test that sudo_required redirect user to password page.
"""
# Logout to remove sudo access.
self.client.logout()
self.client.login(username=self.instructor.username, password="test")
response = self.client.get(self.url, content_type='html', HTTP_ACCEPT='html')
self.assertEqual(response.status_code, 302)
......@@ -34,7 +34,6 @@ from courseware.courses import get_course_by_id, get_studio_url
from django_comment_client.utils import has_forum_access
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from student.models import CourseEnrollment
from django_sudo_helpers.decorators import sudo_required
from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem
from course_modes.models import CourseMode, CourseModesArchive
from student.roles import CourseFinanceAdminRole, CourseSalesAdminRole
......@@ -66,33 +65,8 @@ class InstructorDashboardTab(CourseTab):
return user and has_access(user, 'staff', course, course.id)
def check_staff_or_404():
"""
Decorator with argument that requires an access level of the requesting
user. If the requirement is not satisfied, returns an
Http404 (404).
Assumes that request is in args[0].
Assumes that course_id is in kwargs['course_id'].
"""
def decorator(func): # pylint: disable=missing-docstring
def wrapped(*args, **kwargs): # pylint: disable=missing-docstring
request = args[0]
course = get_course_by_id(CourseKey.from_string(kwargs['course_id']))
user_is_staff = has_access(request.user, "staff", course)
if user_is_staff:
return func(*args, **kwargs)
else:
raise Http404
return wrapped
return decorator
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@check_staff_or_404()
@sudo_required
def instructor_dashboard_2(request, course_id):
""" Display the instructor dashboard for a course. """
try:
......@@ -112,16 +86,16 @@ def instructor_dashboard_2(request, course_id):
'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
}
is_white_label = CourseMode.is_white_label(course_key)
if not access['staff']:
raise Http404()
unique_student_identifier = request.GET.get("unique_student_identifier", "")
problem_to_reset = request.GET.get("problem_to_reset", "")
is_white_label = CourseMode.is_white_label(course_key)
sections = [
_section_course_info(course, access),
_section_membership(course, access, is_white_label),
_section_cohort_management(course, access),
_section_student_admin(course, access, unique_student_identifier, problem_to_reset),
_section_student_admin(course, access),
_section_data_download(course, access),
]
......@@ -433,21 +407,11 @@ def _is_small_course(course_key):
return is_small_course
def _section_student_admin(course, access, unique_student_identifier, problem_to_reset):
def _section_student_admin(course, access):
""" Provide data for the corresponding dashboard section """
course_key = course.id
is_small_course = _is_small_course(course_key)
problem_url = None
if problem_to_reset:
problem_url = reverse(
'jump_to',
kwargs={
'course_id': unicode(course_key),
'location': problem_to_reset
}
)
section_data = {
'section_key': 'student_admin',
'section_display_name': _('Student Admin'),
......@@ -470,9 +434,6 @@ def _section_student_admin(course, access, unique_student_identifier, problem_to
'list_entrace_exam_instructor_tasks_url': reverse('list_entrance_exam_instructor_tasks',
kwargs={'course_id': unicode(course_key)}),
'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': unicode(course_key)}),
'unique_student_identifier': unique_student_identifier,
'problem_to_reset': problem_to_reset,
'problem_url': problem_url,
}
return section_data
......
......@@ -52,7 +52,6 @@ from student.models import (
CourseEnrollment,
CourseEnrollmentAllowed,
)
from django_sudo_helpers.decorators import sudo_required
import track.views
from django.utils.translation import ugettext as _
......@@ -80,7 +79,6 @@ def split_by_comma_and_whitespace(a_str):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@sudo_required
def instructor_dashboard(request, course_id):
"""Display the instructor dashboard for a course."""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
......
"""Django admin interface for the shopping cart models. """
from django.contrib import admin
from ratelimitbackend import admin
from shoppingcart.models import (
PaidCourseRegistrationAnnotation,
Coupon,
......
"""
django admin pages for verify_student models
"""
from django.contrib import admin
from ratelimitbackend import admin
from config_models.admin import ConfigurationModelAdmin
from verify_student.models import (
SoftwareSecurePhotoVerification,
......
......@@ -1176,10 +1176,6 @@ MIDDLEWARE_CLASSES = (
# catches any uncaught RateLimitExceptions and returns a 403 instead of a 500
'ratelimitbackend.middleware.RateLimitMiddleware',
# force re-authentication before activating administrative functions
'sudo.middleware.SudoMiddleware',
# needs to run after locale middleware (or anything that modifies the request context)
'edxmako.middleware.MakoMiddleware',
......@@ -1901,9 +1897,6 @@ INSTALLED_APPS = (
# Surveys
'survey',
# Allows sudo-mode
'sudo',
'lms.djangoapps.lms_xblock',
'openedx.core.djangoapps.content.course_overviews',
......
......@@ -27,13 +27,13 @@ class DataDownload
@$problem_grade_report_csv_btn = @$section.find("input[name='problem-grade-report']'")
# response areas
@$download = @$section.find '.data-download-container'
@$download_display_text = @$download.find '.data-display-text'
@$download = @$section.find '.data-download-container'
@$download_display_text = @$download.find '.data-display-text'
@$download_request_response_error = @$download.find '.request-response-error'
@$reports = @$section.find '.reports-download-container'
@$download_display_table = @$reports.find '.data-display-table'
@$reports_request_response = @$reports.find '.request-response'
@$reports_request_response_error = @$reports.find '.request-response-error'
@$reports = @$section.find '.reports-download-container'
@$download_display_table = @$reports.find '.data-display-table'
@$reports_request_response = @$reports.find '.request-response'
@$reports_request_response_error = @$reports.find '.request-response-error'
@report_downloads = new (ReportDownloads()) @$section
@instructor_tasks = new (PendingInstructorTasks()) @$section
......@@ -58,14 +58,12 @@ class DataDownload
$.ajax
dataType: 'json'
url: url
error: std_ajax_err((=>
error: (std_ajax_err) =>
@$reports_request_response_error.text gettext("Error generating student profile information. Please try again.")
$(".msg-error").css({"display": "block"})
), true)
$(".msg-error").css({"display":"block"})
success: (data) =>
@$reports_request_response.text data['status']
$(".msg-confirm").css({"display": "block"})
$(".msg-confirm").css({"display":"block"})
@$list_studs_btn.click (e) =>
url = @$list_studs_btn.data 'endpoint'
......@@ -78,11 +76,9 @@ class DataDownload
$.ajax
dataType: 'json'
url: url
error: std_ajax_err((=>
error: (std_ajax_err) =>
@clear_display()
@$download_request_response_error.text gettext("Error getting student list.")
), true)
success: (data) =>
@clear_display()
......@@ -99,7 +95,7 @@ class DataDownload
$table_placeholder = $ '<div/>', class: 'slickgrid'
@$download_display_table.append $table_placeholder
grid = new Slick.Grid($table_placeholder, grid_data, columns, options)
# grid.autosizeColumns()
# grid.autosizeColumns()
@$list_may_enroll_csv_btn.click (e) =>
@clear_display()
......@@ -108,14 +104,12 @@ class DataDownload
$.ajax
dataType: 'json'
url: url
error: std_ajax_err((=>
error: (std_ajax_err) =>
@$reports_request_response_error.text gettext("Error generating list of students who may enroll. Please try again.")
$(".msg-error").css({"display": "block"})
), true)
$(".msg-error").css({"display":"block"})
success: (data) =>
@$reports_request_response.text data['status']
$(".msg-confirm").css({"display": "block"})
$(".msg-confirm").css({"display":"block"})
@$grade_config_btn.click (e) =>
url = @$grade_config_btn.data 'endpoint'
......@@ -123,10 +117,9 @@ class DataDownload
$.ajax
dataType: 'json'
url: url
error: std_ajax_err((=>
error: (std_ajax_err) =>
@clear_display()
@$download_request_response_error.text gettext("Error retrieving grading configuration.")
), true)
success: (data) =>
@clear_display()
@$download_display_text.html data['grading_config_summary']
......@@ -138,22 +131,20 @@ class DataDownload
@onClickGradeDownload @$problem_grade_report_csv_btn, gettext("Error generating problem grade report. Please try again.")
onClickGradeDownload: (button, errorMessage) ->
# Clear any CSS styling from the request-response areas
#$(".msg-confirm").css({"display":"none"})
#$(".msg-error").css({"display":"none"})
@clear_display()
url = button.data 'endpoint'
$.ajax
dataType: 'json'
url: url
error: std_ajax_err((=>
@.$reports_request_response_error.text gettext('Error generating student profile information. Please try again.')
$('.msg-error').css 'display': 'block'
), true
)
success: (data) =>
@$reports_request_response.text data['status']
$(".msg-confirm").css({"display": "block"})
# Clear any CSS styling from the request-response areas
#$(".msg-confirm").css({"display":"none"})
#$(".msg-error").css({"display":"none"})
@clear_display()
url = button.data 'endpoint'
$.ajax
dataType: 'json'
url: url
error: (std_ajax_err) =>
@$reports_request_response_error.text errorMessage
$(".msg-error").css({"display":"block"})
success: (data) =>
@$reports_request_response.text data['status']
$(".msg-confirm").css({"display":"block"})
# handler for when the section title is clicked.
onClickTitle: ->
......@@ -175,8 +166,8 @@ class DataDownload
@$reports_request_response.empty()
@$reports_request_response_error.empty()
# Clear any CSS styling from the request-response areas
$(".msg-confirm").css({"display": "none"})
$(".msg-error").css({"display": "none"})
$(".msg-confirm").css({"display":"none"})
$(".msg-error").css({"display":"none"})
# export for use
# create parent namespaces if they do not already exist.
......
......@@ -79,9 +79,9 @@ class SendEmail
data: send_data
success: (data) =>
@display_response success_message
error: std_ajax_err((=>
error: std_ajax_err =>
@fail_with_error gettext('Error sending email.')
), true)
else
@$task_response.empty()
......@@ -99,41 +99,38 @@ class SendEmail
else
@$history_request_response_error.text gettext("There is no email history for this course.")
# Enable the msg-warning css display
@$history_request_response_error.css({"display": "block"})
error: std_ajax_err((=>
@$history_request_response_error.css({"display":"block"})
error: std_ajax_err =>
@$history_request_response_error.text gettext("There was an error obtaining email task history for this course.")
), true)
# List content history for emails sent
@$btn_task_history_email_content.click =>
url = @$btn_task_history_email_content.data 'endpoint'
$.ajax
dataType: 'json'
url: url
url : url
success: (data) =>
if data.emails.length
create_email_content_table @$table_email_content_history, @$email_content_table_inner, data.emails
create_email_message_views @$email_messages_wrapper, data.emails
else
@$content_request_response_error.text gettext("There is no email history for this course.")
@$content_request_response_error.css({"display": "block"})
error: std_ajax_err((=>
@$content_request_response_error.css({"display":"block"})
error: std_ajax_err =>
@$content_request_response_error.text gettext("There was an error obtaining email content history for this course.")
), true)
fail_with_error: (msg) ->
console.warn msg
@$task_response.empty()
@$request_response_error.empty()
@$request_response_error.text msg
$(".msg-confirm").css({"display": "none"})
$(".msg-confirm").css({"display":"none"})
display_response: (data_from_server) ->
@$task_response.empty()
@$request_response_error.empty()
@$task_response.text(data_from_server)
$(".msg-confirm").css({"display": "block"})
$(".msg-confirm").css({"display":"block"})
# Email Section
......
......@@ -19,12 +19,10 @@ find_and_assert = ($root, selector) ->
#
# wraps a `handler` function so that first
# it prints basic error information to the console.
@std_ajax_err = (handler, sudo_reload=false) -> (jqXHR, textStatus, errorThrown) ->
@std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) ->
console.warn """ajax error
textStatus: #{textStatus}
errorThrown: #{errorThrown}"""
if sudo_reload == true and jqXHR.status == 401
window.location.reload()
handler.apply this, arguments
......@@ -299,10 +297,7 @@ class @PendingInstructorTasks
@$no_tasks_message.empty()
@$no_tasks_message.append $('<p>').text gettext("No tasks currently running.")
@$no_tasks_message.show()
error: std_ajax_err((=>
console.error "Error finding pending tasks to display"
), true)
error: std_ajax_err => console.error "Error finding pending tasks to display"
### /Pending Instructor Tasks Section ####
class KeywordValidator
......
......@@ -4,7 +4,6 @@ define(['backbone', 'jquery', 'js/staff_debug_actions'],
describe('StaffDebugActions', function () {
var location = 'i4x://edX/Open_DemoX/edx_demo_course/problem/test_loc';
var locationName = 'test_loc';
var action = {location: location, locationName: locationName};
var fixture_id = 'sd_fu_' + locationName;
var fixture = $('<input>', { id: fixture_id, placeholder: "userman" });
var escapableLocationName = 'test\.\*\+\?\^\:\$\{\}\(\)\|\]\[loc';
......@@ -14,11 +13,7 @@ define(['backbone', 'jquery', 'js/staff_debug_actions'],
describe('get_url ', function () {
it('defines url to courseware ajax entry point', function () {
spyOn(StaffDebug, "get_current_url").andReturn("/courses/edX/Open_DemoX/edx_demo_course/courseware/stuff");
$('body').append(fixture);
var expected_url = '/courses/edX/Open_DemoX/edx_demo_course/instructor?unique_student_identifier=userman&problem_to_reset=' + encodeURIComponent(action.location);
expect(StaffDebug.get_url(action)).toBe(expected_url);
$('#' + fixture_id).remove();
expect(StaffDebug.get_url('rescore_problem')).toBe('/courses/edX/Open_DemoX/edx_demo_course/instructor/api/rescore_problem');
});
});
......@@ -49,18 +44,61 @@ define(['backbone', 'jquery', 'js/staff_debug_actions'],
$('#' + escapableFixture_id).remove();
});
});
describe('student_grade_adjustemnts', function () {
describe('reset', function () {
it('makes an ajax call with the expected parameters', function () {
$('body').append(fixture);
spyOn(StaffDebug, 'goto_student_admin');
spyOn($, 'ajax');
StaffDebug.reset(locationName, location);
StaffDebug.student_grade_adjustemnts(locationName, location);
expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET');
expect($.ajax.mostRecentCall.args[0]['data']).toEqual({
'problem_to_reset': location,
'unique_student_identifier': 'userman',
'delete_module': false
});
expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor/api/reset_student_attempts'
);
$('#' + fixture_id).remove();
});
});
describe('sdelete', function () {
it('makes an ajax call with the expected parameters', function () {
$('body').append(fixture);
var expected_url = get_url(action) + '#view-student_admin';
spyOn($, 'ajax');
StaffDebug.sdelete(locationName, location);
expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET');
expect($.ajax.mostRecentCall.args[0]['data']).toEqual({
'problem_to_reset': location,
'unique_student_identifier': 'userman',
'delete_module': true
});
expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor/api/reset_student_attempts'
);
$('#' + fixture_id).remove();
});
});
describe('rescore', function () {
it('makes an ajax call with the expected parameters', function () {
$('body').append(fixture);
expect(StaffDebug.goto_student_admin).toHaveBeenCalledWith(expected_url);
spyOn($, 'ajax');
StaffDebug.rescore(locationName, location);
expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET');
expect($.ajax.mostRecentCall.args[0]['data']).toEqual({
'problem_to_reset': location,
'unique_student_identifier': 'userman',
'delete_module': false
});
expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor/api/rescore_problem'
);
$('#' + fixture_id).remove();
});
});
......
......@@ -3,15 +3,13 @@ var StaffDebug = (function(){
get_current_url = function() {
return window.location.pathname;
};
}
get_url = function(action){
var problem_to_reset = encodeURIComponent(action.location);
var unique_student_identifier = get_user(action.locationName);
var pathname = this.get_current_url();
var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/instructor'+ '?unique_student_identifier=' + unique_student_identifier + '&problem_to_reset=' + problem_to_reset;
var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/instructor/api/' + action;
return url;
};
}
sanitized_string = function(string) {
return string.replace(/[.*+?^:${}()|[\]\\]/g, "\\$&");
......@@ -24,21 +22,95 @@ var StaffDebug = (function(){
uname = $('#sd_fu_' + locname).attr('placeholder');
}
return uname;
};
}
do_idash_action = function(action){
var pdata = {
'problem_to_reset': action.location,
'unique_student_identifier': get_user(action.locationName),
'delete_module': action.delete_module
}
$.ajax({
type: "GET",
url: get_url(action.method),
data: pdata,
success: function(data){
var text = _.template(
action.success_msg,
{user: data.student},
{interpolate: /\{(.+?)\}/g}
)
var html = _.template(
'<p id="idash_msg" class="success">{text}</p>',
{text: text},
{interpolate: /\{(.+?)\}/g}
)
$("#result_"+action.locationName).html(html);
},
error: function(request, status, error) {
var response_json;
try {
response_json = $.parseJSON(request.responseText);
} catch(e) {
response_json = { error: gettext('Unknown Error Occurred.') };
}
var text = _.template(
'{error_msg} {error}',
{
error_msg: action.error_msg,
error: response_json.error
},
{interpolate: /\{(.+?)\}/g}
)
var html = _.template(
'<p id="idash_msg" class="error">{text}</p>',
{text: text},
{interpolate: /\{(.+?)\}/g}
)
$("#result_"+action.locationName).html(html);
},
dataType: 'json'
});
}
reset = function(locname, location){
this.do_idash_action({
locationName: locname,
location: location,
method: 'reset_student_attempts',
success_msg: gettext('Successfully reset the attempts for user {user}'),
error_msg: gettext('Failed to reset attempts.'),
delete_module: false
});
}
goto_student_admin = function(location) {
window.location = location;
};
sdelete = function(locname, location){
this.do_idash_action({
locationName: locname,
location: location,
method: 'reset_student_attempts',
success_msg: gettext('Successfully deleted student state for user {user}'),
error_msg: gettext('Failed to delete student state.'),
delete_module: true
});
}
student_grade_adjustemnts = function(locname, location){
var action = {locationName: locname, location: location};
var instructor_tab_url = get_url(action);
this.goto_student_admin(instructor_tab_url + '#view-student_admin');
};
rescore = function(locname, location){
this.do_idash_action({
locationName: locname,
location: location,
method: 'rescore_problem',
success_msg: gettext('Successfully rescored problem for user {user}'),
error_msg: gettext('Failed to rescore problem.'),
delete_module: false
});
}
return {
student_grade_adjustemnts: student_grade_adjustemnts,
goto_student_admin: goto_student_admin,
reset: reset,
sdelete: sdelete,
rescore: rescore,
do_idash_action: do_idash_action,
get_current_url: get_current_url,
get_url: get_url,
get_user: get_user,
......@@ -49,8 +121,16 @@ var StaffDebug = (function(){
// Register click handlers
$(document).ready(function() {
var $courseContent = $('.course-content');
$courseContent.on("click", '.staff-debug-grade-adjustments', function() {
StaffDebug.student_grade_adjustemnts($(this).parent().data('location-name'), $(this).parent().data('location'));
$courseContent.on("click", '.staff-debug-reset', function() {
StaffDebug.reset($(this).parent().data('location-name'), $(this).parent().data('location'));
return false;
});
$courseContent.on("click", '.staff-debug-sdelete', function() {
StaffDebug.sdelete($(this).parent().data('location-name'), $(this).parent().data('location'));
return false;
});
$courseContent.on("click", '.staff-debug-rescore', function() {
StaffDebug.rescore($(this).parent().data('location-name'), $(this).parent().data('location'));
return false;
});
});
......@@ -39,22 +39,17 @@
<div class="student-grade-container action-type-container">
<h2>${_("Student-specific grade adjustment")}</h2>
%if section_data['problem_url']:
<div class="wrap-instructor-info" aria-hidden="true">
<a href="${ section_data['problem_url'] }" class="instructor-info-action">${_("Go Back To Problem")}</a>
</div>
%endif
<div class="request-response-error"></div>
<p>
<label>
${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
<input type="text" name="student-select-grade" value="${ section_data['unique_student_identifier'] }" placeholder="${_("Student Email or Username")}">
<input type="text" name="student-select-grade" placeholder="${_("Student Email or Username")}">
</label>
</p>
<br>
<label> ${_("Specify a problem in the course here with its complete location:")}
<input type="text" name="problem-select-single" value="${ section_data['problem_to_reset'] }" placeholder="${_("Problem location")}">
<input type="text" name="problem-select-single" placeholder="${_("Problem location")}">
</label>
## Translators: A location (string of text) follows this sentence.
......
<%! from django.utils.translation import ugettext as _ %>
<%! from django.template.defaultfilters import escapejs %>
<%namespace name='static' file='/static_content.html'/>
<%!
from django.utils.translation import ugettext as _
......@@ -70,7 +67,15 @@ ${block_content}
<input type="text" id="sd_fu_${location.name | h}" placeholder="${user.username}"/>
</div>
<div data-location="${location | h}" data-location-name="${location.name | h}">
[<a href="#" class="staff-debug-grade-adjustments">${_("Modify Student's State for Problem")}</a>]
[
<a href="#" class="staff-debug-reset">${_('Reset Student Attempts')}</a>
% if has_instructor_access:
|
<a href="#" class="staff-debug-sdelete">${_('Delete Student State')}</a>
|
<a href="#" class="staff-debug-rescore">${_('Rescore Student Submission')}</a>
% endif
]
</div>
<div id="result_${location.name | h}"/>
</div>
......
{% extends "main_django.html" %}
{% load i18n %}
{% block body %}
<section style="margin: 0 auto; width: 480px; padding: 50px;">
<div class="inner-wrapper">
<header>
<h2 style="text-align: center;">{% trans "Confirm Your Password to Access the Instructor Dashboard" %}</h2>
</header>
<hr />
<div style="margin: 0px auto; width: 218px;">
<form class="sudo-form" method="post">{% csrf_token %}
{{ form.as_p }}
<p>
<input type="submit" value="Submit" />
</p>
</form>
</div>
</div>
</section>
{% endblock %}
\ No newline at end of file
from django.conf import settings
from django.conf.urls import patterns, include, url
from ratelimitbackend import admin
from django.conf.urls.static import static
import django.contrib.auth.views
from microsite_configuration import microsite
import auth_exchange.views
from edx_admin import admin
# Uncomment the next two lines to enable the admin:
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
......@@ -80,8 +80,6 @@ urlpatterns = (
# Course content API
url(r'^api/course_structure/', include('course_structure_api.urls', namespace='course_structure_api')),
url(r'^sudo/$', 'sudo.views.sudo'),
# User API endpoints
url(r'^api/user/', include('openedx.core.djangoapps.user_api.urls')),
......
"""
django admin pages for course_structures model
"""
from django.contrib import admin
from ratelimitbackend import admin
from .models import CourseStructure
......
"""
Django admin page for credit eligibility
"""
from ratelimitbackend import admin
from openedx.core.djangoapps.credit.models import (
CreditCourse, CreditProvider, CreditEligibility, CreditRequest
)
from django.contrib import admin
class CreditCourseAdmin(admin.ModelAdmin):
......
......@@ -12,7 +12,6 @@
-e git+https://github.com/edx/django-pipeline.git@88ec8a011e481918fdc9d2682d4017c835acd8be#egg=django-pipeline
-e git+https://github.com/edx/django-wiki.git@cd0b2b31997afccde519fe5b3365e61a9edb143f#egg=django-wiki
-e git+https://github.com/edx/django-oauth2-provider.git@0.2.7-fork-edx-5#egg=django-oauth2-provider
-e git+https://github.com/edx/django-sudo.git@5ceb91236b477ce2726c538a2d8631884bda2684#egg=django-sudo
-e git+https://github.com/edx/MongoDBProxy.git@25b99097615bda06bd7cdfe5669ed80dc2a7fed0#egg=mongodb_proxy
git+https://github.com/edx/nltk.git@2.0.6#egg=nltk==2.0.6
-e git+https://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment