Commit 6b9b0925 by mikedikan Committed by GitHub

Merge pull request #13141 from edx/adam/revert-commit

Revert "Basic architecture of Maintenance App - SUST-19, SUST-42. Imp…
parents 2c4ab116 6c743c54
......@@ -26,7 +26,7 @@ from course_modes.tests.factories import CourseModeFactory
from contentstore.views.certificates import CertificateManager
from django.test.utils import override_settings
from contentstore.utils import get_lms_link_for_certificate_web_view
from util.testing import EventTestMixin, UrlResetMixin
from util.testing import EventTestMixin
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
......@@ -197,8 +197,7 @@ class CertificatesBaseTestCase(object):
@ddt.ddt
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
class CertificatesListHandlerTestCase(
EventTestMixin, CourseTestCase, CertificatesBaseTestCase, HelperMethods, UrlResetMixin):
class CertificatesListHandlerTestCase(EventTestMixin, CourseTestCase, CertificatesBaseTestCase, HelperMethods):
"""
Test cases for certificates_list_handler.
"""
......@@ -207,7 +206,6 @@ class CertificatesListHandlerTestCase(
Set up CertificatesListHandlerTestCase.
"""
super(CertificatesListHandlerTestCase, self).setUp('contentstore.views.certificates.tracker')
self.reset_urls()
def _url(self):
"""
......@@ -422,8 +420,7 @@ class CertificatesListHandlerTestCase(
@ddt.ddt
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
class CertificatesDetailHandlerTestCase(
EventTestMixin, CourseTestCase, CertificatesBaseTestCase, HelperMethods, UrlResetMixin):
class CertificatesDetailHandlerTestCase(EventTestMixin, CourseTestCase, CertificatesBaseTestCase, HelperMethods):
"""
Test cases for CertificatesDetailHandlerTestCase.
"""
......@@ -435,7 +432,6 @@ class CertificatesDetailHandlerTestCase(
Set up CertificatesDetailHandlerTestCase.
"""
super(CertificatesDetailHandlerTestCase, self).setUp('contentstore.views.certificates.tracker')
self.reset_urls()
def _url(self, cid=-1):
"""
......
......@@ -8,14 +8,13 @@ from django.test.utils import override_settings
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url
from util.testing import UrlResetMixin
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
class TestHeaderMenu(CourseTestCase, UrlResetMixin):
class TestHeaderMenu(CourseTestCase):
"""
Unit tests for the course header menu.
"""
......@@ -24,7 +23,6 @@ class TestHeaderMenu(CourseTestCase, UrlResetMixin):
Set up the for the course header menu tests.
"""
super(TestHeaderMenu, self).setUp()
self.reset_urls()
def test_header_menu_without_web_certs_enabled(self):
"""
......
"""
Tests for the maintenance app views.
"""
import ddt
import json
from django.conf import settings
from django.core.urlresolvers import reverse
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from contentstore.management.commands.utils import get_course_versions
from student.tests.factories import AdminFactory, UserFactory
from .views import COURSE_KEY_ERROR_MESSAGES, MAINTENANCE_VIEWS
# This list contains URLs of all maintenance app views.
MAINTENANCE_URLS = [reverse(view['url']) for view in MAINTENANCE_VIEWS.values()]
class TestMaintenanceIndex(ModuleStoreTestCase):
"""
Tests for maintenance index view.
"""
def setUp(self):
super(TestMaintenanceIndex, self).setUp()
self.user = AdminFactory()
login_success = self.client.login(username=self.user.username, password='test')
self.assertTrue(login_success)
self.view_url = reverse('maintenance:maintenance_index')
def test_maintenance_index(self):
"""
Test that maintenance index view lists all the maintenance app views.
"""
response = self.client.get(self.view_url)
self.assertContains(response, 'Maintenance', status_code=200)
# Check that all the expected links appear on the index page.
for url in MAINTENANCE_URLS:
self.assertContains(response, url, status_code=200)
@ddt.ddt
class MaintenanceViewTestCase(ModuleStoreTestCase):
"""
Base class for maintenance view tests.
"""
view_url = ''
def setUp(self):
super(MaintenanceViewTestCase, self).setUp()
self.user = AdminFactory()
login_success = self.client.login(username=self.user.username, password='test')
self.assertTrue(login_success)
def verify_error_message(self, data, error_message):
"""
Verify the response contains error message.
"""
response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertContains(response, error_message, status_code=200)
def tearDown(self):
"""
Reverse the setup.
"""
self.client.logout()
super(MaintenanceViewTestCase, self).tearDown()
@ddt.ddt
class MaintenanceViewAccessTests(MaintenanceViewTestCase):
"""
Tests for access control of maintenance views.
"""
@ddt.data(MAINTENANCE_URLS)
@ddt.unpack
def test_require_login(self, url):
"""
Test that maintenance app requires user login.
"""
# Log out then try to retrieve the page
self.client.logout()
response = self.client.get(url)
# Expect a redirect to the login page
redirect_url = '{login_url}?next={original_url}'.format(
login_url=reverse('login'),
original_url=url,
)
self.assertRedirects(response, redirect_url)
@ddt.data(MAINTENANCE_URLS)
@ddt.unpack
def test_global_staff_access(self, url):
"""
Test that all maintenance app views are accessible to global staff user.
"""
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@ddt.data(MAINTENANCE_URLS)
@ddt.unpack
def test_non_global_staff_access(self, url):
"""
Test that all maintenance app views are not accessible to non-global-staff user.
"""
user = UserFactory(username='test', email='test@example.com', password='test')
login_success = self.client.login(username=user.username, password='test')
self.assertTrue(login_success)
response = self.client.get(url)
self.assertContains(
response,
'Must be {platform_name} staff to perform this action.'.format(platform_name=settings.PLATFORM_NAME),
status_code=403
)
@ddt.ddt
class TestForcePublish(MaintenanceViewTestCase):
"""
Tests for the force publish view.
"""
def setUp(self):
super(TestForcePublish, self).setUp()
self.view_url = reverse('maintenance:force_publish_course')
def setup_test_course(self):
"""
Creates the course and add some changes to it.
Returns:
course: a course object
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
# Add some changes to course
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
self.store.create_child(
self.user.id, # pylint: disable=no-member
chapter.location,
'html',
block_id='html_component'
)
# verify that course has changes.
self.assertTrue(self.store.has_changes(self.store.get_item(course.location)))
return course
@ddt.data(
('', COURSE_KEY_ERROR_MESSAGES['empty_course_key']),
('edx', COURSE_KEY_ERROR_MESSAGES['invalid_course_key']),
('course-v1:e+d+X', COURSE_KEY_ERROR_MESSAGES['course_key_not_found']),
)
@ddt.unpack
def test_invalid_course_key_messages(self, course_key, error_message):
"""
Test all error messages for invalid course keys.
"""
# validate that course key contains error message
self.verify_error_message(
data={'course-id': course_key},
error_message=error_message
)
def test_mongo_course(self):
"""
Test that we get a error message on old mongo courses.
"""
# validate non split error message
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
self.verify_error_message(
data={'course-id': unicode(course.id)},
error_message='Force publishing course is not supported with old mongo courses.'
)
def test_already_published(self):
"""
Test that when a course is forcefully publish, we get a 'course is already published' message.
"""
course = self.setup_test_course()
# publish the course
source_store = modulestore()._get_modulestore_for_courselike(course.id) # pylint: disable=protected-access
source_store.force_publish_course(course.id, self.user.id, commit=True) # pylint: disable=no-member
# now course is published, we should get `already published course` error.
self.verify_error_message(
data={'course-id': unicode(course.id)},
error_message='Course is already in published state.'
)
def verify_versions_are_different(self, course):
"""
Verify draft and published versions point to different locations.
Arguments:
course (object): a course object.
"""
# get draft and publish branch versions
versions = get_course_versions(unicode(course.id))
# verify that draft and publish point to different versions
self.assertNotEqual(versions['draft-branch'], versions['published-branch'])
def get_force_publish_course_response(self, course):
"""
Get force publish the course response.
Arguments:
course (object): a course object.
Returns:
response : response from force publish post view.
"""
# Verify versions point to different locations initially
self.verify_versions_are_different(course)
# force publish course view
data = {
'course-id': unicode(course.id)
}
response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
response_data = json.loads(response.content)
return response_data
def test_force_publish_dry_run(self):
"""
Test that dry run does not publishes the course but shows possible outcome if force published is executed.
"""
course = self.setup_test_course()
response = self.get_force_publish_course_response(course)
self.assertIn('current_versions', response)
# verify that course still has changes as we just dry ran force publish course.
self.assertTrue(self.store.has_changes(self.store.get_item(course.location)))
# verify that both branch versions are still different
self.verify_versions_are_different(course)
"""
URLs for the maintenance app.
"""
from django.conf.urls import patterns, url
from .views import MaintenanceIndexView, ForcePublishCourseView
urlpatterns = patterns(
'',
url(r'^$', MaintenanceIndexView.as_view(), name='maintenance_index'),
url(r'^force_publish_course/?$', ForcePublishCourseView.as_view(), name='force_publish_course'),
)
"""
Views for the maintenance app.
"""
import logging
from django.db import transaction
from django.core.validators import ValidationError
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views.generic import View
from edxmako.shortcuts import render_to_response
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from contentstore.management.commands.utils import get_course_versions
from util.json_request import JsonResponse
from util.views import require_global_staff
log = logging.getLogger(__name__)
# This dict maintains all the views that will be used Maintenance app.
MAINTENANCE_VIEWS = {
'force_publish_course': {
'url': 'maintenance:force_publish_course',
'name': _('Force Publish Course'),
'slug': 'force_publish_course',
'description': _(
'Sometimes the draft and published branches of a course can get out of sync. Force publish course command '
'resets the published branch of a course to point to the draft branch, effectively force publishing the '
'course. This view dry runs the force publish command'
),
},
}
COURSE_KEY_ERROR_MESSAGES = {
'empty_course_key': _('Please provide course id.'),
'invalid_course_key': _('Invalid course key.'),
'course_key_not_found': _('No matching course found.')
}
class MaintenanceIndexView(View):
"""
Index view for maintenance dashboard, used by global staff.
This view lists some commands/tasks that can be used to dry run or execute directly.
"""
@method_decorator(require_global_staff)
def get(self, request):
"""Render the maintenance index view. """
return render_to_response('maintenance/index.html', {
'views': MAINTENANCE_VIEWS,
})
class MaintenanceBaseView(View):
"""
Base class for Maintenance views.
"""
template = 'maintenance/container.html'
def __init__(self, view=None):
self.context = {
'view': view if view else '',
'form_data': {},
'error': False,
'msg': ''
}
def render_response(self):
"""
A short method to render_to_response that renders response.
"""
if self.request.is_ajax():
return JsonResponse(self.context)
return render_to_response(self.template, self.context)
@method_decorator(require_global_staff)
def get(self, request):
"""
Render get view.
"""
return self.render_response()
def validate_course_key(self, course_key, branch=ModuleStoreEnum.BranchName.draft):
"""
Validates the course_key that would be used by maintenance app views.
Arguments:
course_key (string): a course key
branch: a course locator branch, default value is ModuleStoreEnum.BranchName.draft .
values can be either ModuleStoreEnum.BranchName.draft or ModuleStoreEnum.BranchName.published.
Returns:
course_usage_key (CourseLocator): course usage locator
"""
if not course_key:
raise ValidationError(COURSE_KEY_ERROR_MESSAGES['empty_course_key'])
course_usage_key = CourseKey.from_string(course_key)
if not modulestore().has_course(course_usage_key):
raise ItemNotFoundError(COURSE_KEY_ERROR_MESSAGES['course_key_not_found'])
# get branch specific locator
course_usage_key = course_usage_key.for_branch(branch)
return course_usage_key
class ForcePublishCourseView(MaintenanceBaseView):
"""
View for force publishing state of the course, used by the global staff.
This view uses `force_publish_course` method of modulestore which publishes the draft state of the course. After
the course has been forced published, both draft and publish draft point to same location.
"""
def __init__(self):
super(ForcePublishCourseView, self).__init__(MAINTENANCE_VIEWS['force_publish_course'])
self.context.update({
'current_versions': [],
'updated_versions': [],
'form_data': {
'course_id': '',
'is_dry_run': True
}
})
def get_course_branch_versions(self, versions):
"""
Returns a dict containing unicoded values of draft and published draft versions.
"""
return {
'draft-branch': unicode(versions['draft-branch']),
'published-branch': unicode(versions['published-branch'])
}
@transaction.atomic
@method_decorator(require_global_staff)
def post(self, request):
"""
This method force publishes a course if dry-run argument is not selected. If dry-run is selected, this view
shows possible outcome if the `force_publish_course` modulestore method is executed.
Arguments:
course_id (string): a request parameter containing course id
is_dry_run (string): a request parameter containing dry run value.
It is obtained from checkbox so it has either values 'on' or ''.
"""
course_id = request.POST.get('course-id')
self.context.update({
'form_data': {
'course_id': course_id
}
})
try:
course_usage_key = self.validate_course_key(course_id)
except InvalidKeyError:
self.context['error'] = True
self.context['msg'] = COURSE_KEY_ERROR_MESSAGES['invalid_course_key']
except ItemNotFoundError as exc:
self.context['error'] = True
self.context['msg'] = exc.message
except ValidationError as exc:
self.context['error'] = True
self.context['msg'] = exc.message
if self.context['error']:
return self.render_response()
source_store = modulestore()._get_modulestore_for_courselike(course_usage_key) # pylint: disable=protected-access
if not hasattr(source_store, 'force_publish_course'):
self.context['msg'] = _('Force publishing course is not supported with old mongo courses.')
log.warning(
'Force publishing course is not supported with old mongo courses. \
%s attempted to force publish the course %s.',
request.user,
course_id,
exc_info=True
)
return self.render_response()
current_versions = self.get_course_branch_versions(get_course_versions(course_id))
# if publish and draft are NOT different
if current_versions['published-branch'] == current_versions['draft-branch']:
self.context['msg'] = _('Course is already in published state.')
log.warning(
'Course is already in published state. %s attempted to force publish the course %s.',
request.user,
course_id,
exc_info=True
)
return self.render_response()
self.context['current_versions'] = current_versions
log.info(
'%s dry ran force publish the course %s.',
request.user,
course_id,
exc_info=True
)
return self.render_response()
......@@ -828,9 +828,6 @@ INSTALLED_APPS = (
'openedx.core.djangoapps.coursetalk', # not used in cms (yet), but tests run
'xblock_config',
# Maintenance tools
'maintenance',
# Tracking
'track',
'eventtracking.django.apps.EventTrackingConfig',
......
define([ // jshint ignore:line
'jquery',
'underscore',
'gettext',
'common/js/components/utils/view_utils',
'edx-ui-toolkit/js/utils/string-utils',
"edx-ui-toolkit/js/utils/html-utils",
'text!templates/maintenance/force-published-course-response.underscore'
],
function($, _, gettext, ViewUtils, StringUtils, HtmlUtils, ForcePublishedTemplate) {
'use strict';
return function (maintenanceViewURL) {
// Reset values
$('#reset-button').click(function (e) {
e.preventDefault();
$('#course-id').val('');
$('#dry-run').prop('checked', true);
// clear out result container
$('#result-container').html('');
});
var showError = function(containerElSelector, error){
var errorWrapperElSelector = containerElSelector + ' .wrapper-error';
var errorHtml = '<div class="error" aria-live="polite" id="course-id-error">' + error + '</div>';
HtmlUtils.setHtml(
$(errorWrapperElSelector),
HtmlUtils.HTML(errorHtml)
);
$(errorWrapperElSelector).css('display', 'inline-block');
$(errorWrapperElSelector).fadeOut(5000);
};
$('form#force_publish').submit(function(event) {
event.preventDefault();
// clear out result container
$('#result-container').html('');
var submitButton = $('#submit_force_publish'),
deferred = new $.Deferred(),
promise = deferred.promise();
ViewUtils.disableElementWhileRunning(submitButton, function() { return promise; });
var data = $('#force_publish').serialize();
$.ajax({
type:'POST',
url: maintenanceViewURL,
dataType: 'json',
data: data,
})
.done(function(response) {
if(response.error){
showError('#course-id-container', response.msg);
}
else {
if(response.msg) {
showError('#result-error', response.msg);
}
else{
var attrs = $.extend({}, response, {StringUtils: StringUtils});
HtmlUtils.setHtml(
$('#result-container'),
HtmlUtils.template(ForcePublishedTemplate)(attrs)
);
}
}
})
.fail(function(response) { // jshint ignore:line
// response.responseText here because it would show some strange output, it may output Traceback
// sometimes if unexpected issue arises. Better to show just internal error when getting 500 error.
showError('#result-error', gettext('Internal Server Error.'));
})
.always(function(response) { // jshint ignore:line
deferred.resolve();
});
});
};
});
......@@ -68,7 +68,6 @@
@import 'views/group-configuration';
@import 'views/video-upload';
@import 'views/certificates';
@import 'views/maintenance';
// +Base - Contexts
// ====================
......
.maintenance-header {
text-align: center;
margin-top: 50px;
h2 {
margin-bottom: 10px;
}
}
.maintenance-content {
padding: 3rem 0;
.maintenance-list {
max-width: 1280px;
margin: 0 auto;
.view-list-container {
padding: 10px 15px;
background-color: #fff;
border-bottom: 1px solid #ddd;
&:hover {
background-color: #fafafa;
}
.view-name {
display: inline-block;
width: 20%;
float: left;
}
.view-desc {
display: inline-block;
width: 80%;
font-size: 15px;
}
}
}
.maintenance-form {
width: 60%;
margin: auto;
.result-list {
height: calc(100vh - 200px);
overflow: auto;
}
.result{
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
margin-top: 15px;
padding: 15px 30px;
background: #f9f9f9;
}
li {
font-size: 13px;
line-height: 9px;
}
.actions {
text-align: right;
}
.field-radio div {
display: inline-block;
margin-right: 10px;
}
div.error {
color: #F00;
margin-top: 10px;
font-size: 13px;
}
div.head-output {
font-size: 13px;
margin-bottom: 10px;
}
div.main-output {
color: #0A0;
font-size: 15px;
}
}
}
<div class="result">
<div class="head-output">
<%- gettext('You have done a dry run of force publishing the course. Nothing has changed. Had you run it, the following course versions would have been change.') %>
</div>
<div class="main-output">
<%= StringUtils.interpolate(
gettext('The published branch version, {published}, was reset to the draft branch version, {draft}.'),
{
published: current_versions['published-branch'],
draft: current_versions['draft-branch']
})
%>
</div>
</div>
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML, Text
%>
<div id="force-published-form" class="wrap-instructor-info studio-view maintenance-form">
<form id="force_publish" class="form-create" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
<div class="wrapper-form">
<fieldset>
<legend class="sr">${_("Required data to force publish course.")}</legend>
<div class="list-input">
<div id="course-id-container" class="field text required">
<label for="course-id">${_('Course ID')}</label>
<input id="course-id" type="text" name="course-id" aria-describedby="course-id-desc" required />
<div id="course-id-desc" class="tip tip-stacked">${_('course-v1:edX+DemoX+Demo_Course')}</div>
<div class="wrapper-error"></div>
</div>
</div>
</fieldset>
</div>
<div class="actions">
<button type="submit" id="submit_force_publish" class="action action-primary">${_('Force Publish Course')}
</button>
<button id="reset-button" class="action action-secondary action-cancel"
aria-describedby="reset-values-desc">${_('Reset')}</button>
<span id="reset-values-desc" class="is-hidden">${_('Reset values')}</span>
</div>
</form>
<div id="result-error"><div class="wrapper-error"></div></div>
<div id="result-container" class="result-container"></div>
</div>
<%page expression_filter="h"/>
<%inherit file="../base.html" />
<%def name='online_help_token()'><% return 'maintenance' %></%def>
<%namespace name='static' file='../static_content.html'/>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
%>
<%block name="content">
<div class="wrapper-content wrapper">
<div class="maintenance-header">
<h2>
<a href="${reverse('maintenance:maintenance_index')}">
<span>${_('Maintenance Dashboard')}</span>
</a>
</h2>
<%block name="viewtitle">
</%block>
</div>
<%block name="viewcontent"></%block>
</%block>
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%!
from django.core.urlresolvers import reverse
from openedx.core.djangolib.js_utils import js_escaped_string
%>
<%block name="title">${view['name']}</%block>
<%block name="viewtitle">
<h3 class="info-course">
<span>${view['name']}</span>
</h3>
</%block>
<%block name="js_extra">
<script src="${static.url('js/maintenance/force_publish.js')}"></script>
</%block>
<%block name="viewcontent">
<section class="container maintenance-content">
<%include file="_${view['slug']}.html"/>
<%block name="requirejs">
require(["js/maintenance/${view['slug'] | n, js_escaped_string}"], function(MaintenanceFactory) {
MaintenanceFactory("${reverse(view['url']) | n, js_escaped_string}");
});
</%block>
</section>
</%block>
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<%block name="title">${_('Maintenance Dashboard')}</%block>
<%block name="viewcontent">
<div class="container maintenance-content">
<ul class="maintenance-list">
% for view in views.values():
<li class="view-list-container">
<a class="view-name" href='${reverse(view["url"])}'>${view['name']}</a>
<span class="view-desc">${view['description']}</span>
</li>
% endfor
</ul>
</div>
</%block>
......@@ -4,7 +4,6 @@
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from student.roles import GlobalStaff
%>
% if uses_pattern_library:
......@@ -46,11 +45,6 @@
<li class="nav-item nav-account-dashboard">
<a href="/">${_("{studio_name} Home").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
</li>
% if GlobalStaff().has_user(user):
<li class="nav-item">
<a href="${reverse('maintenance:maintenance_index')}">${_("Maintenance")}</a>
</li>
% endif
<li class="nav-item nav-account-signout">
<a class="action action-signout" href="${reverse('logout')}">${_("Sign Out")}</a>
</li>
......
......@@ -184,12 +184,6 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'):
'contentstore.views.certificates.certificates_list_handler')
)
# Maintenance Dashboard
urlpatterns += patterns(
'',
url(r'^maintenance/', include('maintenance.urls', namespace='maintenance')),
)
urlpatterns += (
# These views use a configuration model to determine whether or not to
# display the Programs authoring app. If disabled, a 404 is returned.
......
......@@ -4,13 +4,12 @@ import sys
from functools import wraps
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.cache import caches
from django.core.validators import ValidationError, validate_email
from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import server_error
from django.http import (Http404, HttpResponse, HttpResponseNotAllowed,
HttpResponseServerError, HttpResponseForbidden)
HttpResponseServerError)
import dogstats_wrapper as dog_stats_api
from edxmako.shortcuts import render_to_response
import zendesk
......@@ -22,8 +21,6 @@ import track.views
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from student.roles import GlobalStaff
log = logging.getLogger(__name__)
......@@ -47,21 +44,6 @@ def ensure_valid_course_key(view_func):
return inner
def require_global_staff(func):
"""View decorator that requires that the user have global staff permissions. """
@wraps(func)
def wrapped(request, *args, **kwargs): # pylint: disable=missing-docstring
if GlobalStaff().has_user(request.user):
return func(request, *args, **kwargs)
else:
return HttpResponseForbidden(
u"Must be {platform_name} staff to perform this action.".format(
platform_name=settings.PLATFORM_NAME
)
)
return login_required(wrapped)
@requires_csrf_token
def jsonable_server_error(request, template_name='500.html'):
"""
......
......@@ -10,10 +10,12 @@ import json
import logging
import re
import time
from functools import wraps
from django.conf import settings
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST, require_http_methods
from django.views.decorators.cache import cache_control
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError, PermissionDenied
from django.core.mail.message import EmailMessage
from django.core.exceptions import ObjectDoesNotExist
......@@ -35,7 +37,6 @@ from util.file import (
FileValidationException, UniversalNewlineIterator
)
from util.json_request import JsonResponse, JsonResponseBadRequest
from util.views import require_global_staff
from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features
from courseware.access import has_access
......@@ -206,6 +207,21 @@ def require_level(level):
return decorator
def require_global_staff(func):
"""View decorator that requires that the user have global staff permissions. """
@wraps(func)
def wrapped(request, *args, **kwargs): # pylint: disable=missing-docstring
if GlobalStaff().has_user(request.user):
return func(request, *args, **kwargs)
else:
return HttpResponseForbidden(
u"Must be {platform_name} staff to perform this action.".format(
platform_name=settings.PLATFORM_NAME
)
)
return login_required(wrapped)
def require_sales_admin(func):
"""
Decorator for checking sales administrator access before executing an HTTP endpoint. This decorator
......
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