Merge pull request #1542 from edx/dhm/restful_course
RESTful refactoring of /course access continued
This commit is contained in:
@@ -26,4 +26,4 @@ Feature: CMS.Sign in
|
||||
And I visit the url "/signin?next=http://www.google.com/"
|
||||
When I fill in and submit the signin form
|
||||
And I wait for "2" seconds
|
||||
Then I should see that the path is "/"
|
||||
Then I should see that the path is "/course"
|
||||
|
||||
@@ -133,7 +133,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# just pick one vertical
|
||||
descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0]
|
||||
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
resp = self.client.get_html(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
for expected in expected_types:
|
||||
@@ -158,7 +158,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0]
|
||||
location = descriptor.location.replace(name='.' + descriptor.location.name)
|
||||
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': location.url()}))
|
||||
resp = self.client.get_html(reverse('edit_unit', kwargs={'location': location.url()}))
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def check_edit_unit(self, test_course_name):
|
||||
@@ -167,7 +167,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
for descriptor in modulestore().get_items(Location(None, None, 'vertical', None, None)):
|
||||
print "Checking ", descriptor.location.url()
|
||||
print descriptor.__class__, descriptor.location
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
resp = self.client.get_html(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def lockAnAsset(self, content_store, course_location):
|
||||
@@ -457,7 +457,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# also try a custom response which will trigger the 'is this course in whitelist' logic
|
||||
problem_module_location = Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None])
|
||||
url = reverse('preview_component', kwargs={'location': problem_module_location.url()})
|
||||
resp = self.client.get(url)
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_video_module_caption_asset_path(self):
|
||||
@@ -470,7 +470,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# also try a custom response which will trigger the 'is this course in whitelist' logic
|
||||
video_module_location = Location(['i4x', 'edX', 'toy', 'video', 'sample_video', None])
|
||||
url = reverse('preview_component', kwargs={'location': video_module_location.url()})
|
||||
resp = self.client.get(url)
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, 'data-caption-asset-path="/c4x/edX/toy/asset/subs_"')
|
||||
|
||||
@@ -831,7 +831,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertRaises(InvalidVersionError, draft_store.unpublish, location)
|
||||
|
||||
def test_bad_contentstore_request(self):
|
||||
resp = self.client.get('http://localhost:8001/c4x/CDX/123123/asset/&images_circuits_Lab7Solution2.png')
|
||||
resp = self.client.get_html('http://localhost:8001/c4x/CDX/123123/asset/&images_circuits_Lab7Solution2.png')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def test_rewrite_nonportable_links_on_import(self):
|
||||
@@ -1022,7 +1022,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# the service in non-draft aware
|
||||
if getattr(descriptor, 'is_draft', False):
|
||||
print "Checking {0}....".format(descriptor.location.url())
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
resp = self.client.get_html(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# verify that we have the content in the draft store as well
|
||||
@@ -1205,7 +1205,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
handout_location = Location(['i4x', 'edX', 'toy', 'course_info', 'handouts'])
|
||||
|
||||
# get module info
|
||||
resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location}))
|
||||
resp = self.client.get_html(reverse('module_info', kwargs={'module_location': handout_location}))
|
||||
|
||||
# make sure we got a successful response
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
@@ -1392,7 +1392,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
def test_create_course_duplicate_course(self):
|
||||
"""Test new course creation - error path"""
|
||||
self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
self.client.ajax_post('/course', self.course_data)
|
||||
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
|
||||
|
||||
def assert_course_creation_failed(self, error_message):
|
||||
@@ -1401,7 +1401,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
"""
|
||||
course_id = _get_course_id(self.course_data)
|
||||
initially_enrolled = CourseEnrollment.is_enrolled(self.user, course_id)
|
||||
resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
resp = self.client.ajax_post('/course', self.course_data)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['ErrMsg'], error_message)
|
||||
@@ -1411,7 +1411,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
def test_create_course_duplicate_number(self):
|
||||
"""Test new course creation - error path"""
|
||||
self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
self.client.ajax_post('/course', self.course_data)
|
||||
self.course_data['display_name'] = 'Robot Super Course Two'
|
||||
self.course_data['run'] = '2013_Summer'
|
||||
|
||||
@@ -1420,13 +1420,13 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
def test_create_course_case_change(self):
|
||||
"""Test new course creation - error path due to case insensitive name equality"""
|
||||
self.course_data['number'] = 'capital'
|
||||
self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
self.client.ajax_post('/course', self.course_data)
|
||||
cache_current = self.course_data['org']
|
||||
self.course_data['org'] = self.course_data['org'].lower()
|
||||
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.')
|
||||
self.course_data['org'] = cache_current
|
||||
|
||||
self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
self.client.ajax_post('/course', self.course_data)
|
||||
cache_current = self.course_data['number']
|
||||
self.course_data['number'] = self.course_data['number'].upper()
|
||||
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.')
|
||||
@@ -1435,14 +1435,14 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test that a new course can be created whose name is a substring of an existing course
|
||||
"""
|
||||
self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
self.client.ajax_post('/course', self.course_data)
|
||||
cache_current = self.course_data['number']
|
||||
self.course_data['number'] = '{}a'.format(self.course_data['number'])
|
||||
resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
resp = self.client.ajax_post('/course', self.course_data)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.course_data['number'] = cache_current
|
||||
self.course_data['org'] = 'a{}'.format(self.course_data['org'])
|
||||
resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
resp = self.client.ajax_post('/course', self.course_data)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_create_course_with_bad_organization(self):
|
||||
@@ -1485,13 +1485,13 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Checks that the course did not get created due to a PermissionError.
|
||||
"""
|
||||
resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
|
||||
resp = self.client.ajax_post('/course', self.course_data)
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
|
||||
def test_course_index_view_with_no_courses(self):
|
||||
"""Test viewing the index page with no courses"""
|
||||
# Create a course so there is something to view
|
||||
resp = self.client.get(reverse('index'))
|
||||
resp = self.client.get_html('/course')
|
||||
self.assertContains(
|
||||
resp,
|
||||
'<h1 class="page-header">My Courses</h1>',
|
||||
@@ -1513,7 +1513,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
def test_course_index_view_with_course(self):
|
||||
"""Test viewing the index page with an existing course"""
|
||||
CourseFactory.create(display_name='Robot Super Educational Course')
|
||||
resp = self.client.get(reverse('index'))
|
||||
resp = self.client.get_html('/course')
|
||||
self.assertContains(
|
||||
resp,
|
||||
'<h3 class="course-title">Robot Super Educational Course</h3>',
|
||||
@@ -1590,7 +1590,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
# go to various pages
|
||||
|
||||
# import page
|
||||
resp = self.client.get(new_location.url_reverse('import/', ''), HTTP_ACCEPT='text/html')
|
||||
resp = self.client.get_html(new_location.url_reverse('import/', ''))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# export page
|
||||
@@ -1602,7 +1602,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
# course team
|
||||
url = new_location.url_reverse('course_team/', '')
|
||||
resp = self.client.get(url, HTTP_ACCEPT='text/html')
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# course info
|
||||
@@ -1628,18 +1628,18 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
# assets_handler (HTML for full page content)
|
||||
url = new_location.url_reverse('assets/', '')
|
||||
resp = self.client.get(url, HTTP_ACCEPT='text/html')
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# go look at a subsection page
|
||||
subsection_location = loc.replace(category='sequential', name='test_sequence')
|
||||
resp = self.client.get(reverse('edit_subsection',
|
||||
resp = self.client.get_html(reverse('edit_subsection',
|
||||
kwargs={'location': subsection_location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# go look at the Edit page
|
||||
unit_location = loc.replace(category='vertical', name='test_vertical')
|
||||
resp = self.client.get(reverse('edit_unit',
|
||||
resp = self.client.get_html(reverse('edit_unit',
|
||||
kwargs={'location': unit_location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@@ -1853,7 +1853,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
Show the course overview page.
|
||||
"""
|
||||
new_location = loc_mapper().translate_location(location.course_id, location, False, True)
|
||||
return self.client.get(new_location.url_reverse('course/', ''), HTTP_ACCEPT='text/html')
|
||||
return self.client.get_html(new_location.url_reverse('course/', ''))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_MODULESTORE)
|
||||
@@ -1927,7 +1927,7 @@ def _create_course(test, course_data):
|
||||
course_id = _get_course_id(course_data)
|
||||
new_location = loc_mapper().translate_location(course_id, CourseDescriptor.id_to_location(course_id), False, True)
|
||||
|
||||
response = test.client.ajax_post(reverse('create_new_course'), course_data)
|
||||
response = test.client.ajax_post('/course', course_data)
|
||||
test.assertEqual(response.status_code, 200)
|
||||
data = parse_json(response)
|
||||
test.assertNotIn('ErrMsg', data)
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
"""
|
||||
Test getting the list of courses and then pulling up their outlines
|
||||
"""
|
||||
index_url = reverse('contentstore.views.index')
|
||||
index_url = '/course'
|
||||
index_response = authed_client.get(index_url, {}, HTTP_ACCEPT='text/html')
|
||||
parsed_html = lxml.html.fromstring(index_response.content)
|
||||
course_link_eles = parsed_html.find_class('course-link')
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from unittest import skip
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from contentstore.tests.modulestore_config import TEST_MODULESTORE
|
||||
from contentstore.tests.utils import AjaxEnabledTestClient
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_MODULESTORE)
|
||||
@@ -45,10 +44,10 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
|
||||
def test_course_plain_english(self):
|
||||
"""Test viewing the index page with no courses"""
|
||||
self.client = Client()
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.uname, password=self.password)
|
||||
|
||||
resp = self.client.get(reverse('index'))
|
||||
resp = self.client.get_html('/course')
|
||||
self.assertContains(resp,
|
||||
'<h1 class="page-header">My Courses</h1>',
|
||||
status_code=200,
|
||||
@@ -56,10 +55,10 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
|
||||
def test_course_explicit_english(self):
|
||||
"""Test viewing the index page with no courses"""
|
||||
self.client = Client()
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.uname, password=self.password)
|
||||
|
||||
resp = self.client.get(reverse('index'),
|
||||
resp = self.client.get_html('/course',
|
||||
{},
|
||||
HTTP_ACCEPT_LANGUAGE='en'
|
||||
)
|
||||
@@ -80,10 +79,10 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
@skip
|
||||
def test_course_with_accents(self):
|
||||
"""Test viewing the index page with no courses"""
|
||||
self.client = Client()
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.uname, password=self.password)
|
||||
|
||||
resp = self.client.get(reverse('index'),
|
||||
resp = self.client.get_html('/course',
|
||||
{},
|
||||
HTTP_ACCEPT_LANGUAGE='fr'
|
||||
)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from contentstore.tests.utils import parse_json, user, registration
|
||||
from contentstore.tests.utils import parse_json, user, registration, AjaxEnabledTestClient
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from contentstore.tests.test_course_settings import CourseTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -82,12 +81,12 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
self.email = 'a@b.com'
|
||||
self.pw = 'xyz'
|
||||
self.username = 'testuser'
|
||||
self.client = Client()
|
||||
self.client = AjaxEnabledTestClient()
|
||||
# clear the cache so ratelimiting won't affect these tests
|
||||
cache.clear()
|
||||
|
||||
def check_page_get(self, url, expected):
|
||||
resp = self.client.get(url)
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, expected)
|
||||
return resp
|
||||
|
||||
@@ -152,20 +151,20 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
def test_private_pages_auth(self):
|
||||
"""Make sure pages that do require login work."""
|
||||
auth_pages = (
|
||||
reverse('index'),
|
||||
'/course',
|
||||
)
|
||||
|
||||
# These are pages that should just load when the user is logged in
|
||||
# (no data needed)
|
||||
simple_auth_pages = (
|
||||
reverse('index'),
|
||||
'/course',
|
||||
)
|
||||
|
||||
# need an activated user
|
||||
self.test_create_account()
|
||||
|
||||
# Create a new session
|
||||
self.client = Client()
|
||||
self.client = AjaxEnabledTestClient()
|
||||
|
||||
# Not logged in. Should redirect to login.
|
||||
print('Not logged in')
|
||||
@@ -184,7 +183,7 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
def test_index_auth(self):
|
||||
|
||||
# not logged in. Should return a redirect.
|
||||
resp = self.client.get(reverse('index'))
|
||||
resp = self.client.get_html('/course')
|
||||
self.assertEqual(resp.status_code, 302)
|
||||
|
||||
# Logged in should work.
|
||||
|
||||
@@ -30,12 +30,24 @@ def registration(email):
|
||||
|
||||
|
||||
class AjaxEnabledTestClient(Client):
|
||||
"""
|
||||
Convenience class to make testing easier.
|
||||
"""
|
||||
def ajax_post(self, path, data=None, content_type="application/json", **kwargs):
|
||||
"""
|
||||
Convenience method for client post which serializes the data into json and sets the accept type
|
||||
to json
|
||||
"""
|
||||
if not isinstance(data, basestring):
|
||||
data = json.dumps(data or {})
|
||||
kwargs.setdefault("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
|
||||
return self.post(path=path, data=data, content_type=content_type, **kwargs)
|
||||
|
||||
def get_html(self, path, data=None, follow=False, **extra):
|
||||
"""
|
||||
Convenience method for client.get which sets the accept type to html
|
||||
"""
|
||||
return self.get(path, data or {}, follow, HTTP_ACCEPT="text/html", **extra)
|
||||
|
||||
@override_settings(MODULESTORE=TEST_MODULESTORE)
|
||||
class CourseTestCase(ModuleStoreTestCase):
|
||||
|
||||
@@ -3,9 +3,11 @@ Views related to operations on course objects
|
||||
"""
|
||||
import json
|
||||
import random
|
||||
from django.utils.translation import ugettext as _
|
||||
import string # pylint: disable=W0402
|
||||
import re
|
||||
import bson
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.conf import settings
|
||||
@@ -16,6 +18,7 @@ from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
from util.json_request import JsonResponse
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
from xmodule.modulestore.django import modulestore, loc_mapper
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
@@ -49,9 +52,9 @@ from student.models import CourseEnrollment
|
||||
|
||||
from xmodule.html_module import AboutDescriptor
|
||||
from xmodule.modulestore.locator import BlockUsageLocator
|
||||
import re
|
||||
import bson
|
||||
__all__ = ['create_new_course', 'course_info', 'course_handler',
|
||||
from course_creators.views import get_course_creator_status, add_user_with_status_unrequested
|
||||
|
||||
__all__ = ['course_info', 'course_handler',
|
||||
'course_info_updates', 'get_course_settings',
|
||||
'course_config_graders_page',
|
||||
'course_config_advanced_page',
|
||||
@@ -69,11 +72,12 @@ def course_handler(request, tag=None, course_id=None, branch=None, version_guid=
|
||||
will typically be a 'course' object but may not be especially as we support modules.
|
||||
|
||||
GET
|
||||
html: return html page overview for the given course
|
||||
html: return course listing page if not given a course id
|
||||
html: return html page overview for the given course if given a course id
|
||||
json: return json representing the course branch's index entry as well as dag w/ all of the children
|
||||
replaced w/ json docs where each doc has {'_id': , 'display_name': , 'children': }
|
||||
POST
|
||||
json: create (or update?) this course or branch in this course for this user, return resulting json
|
||||
json: create a course, return resulting json
|
||||
descriptor (same as in GET course/...). Leaving off /branch/draft would imply create the course w/ default
|
||||
branches. Cannot change the structure contents ('_id', 'display_name', 'children') but can change the
|
||||
index entry.
|
||||
@@ -86,13 +90,13 @@ def course_handler(request, tag=None, course_id=None, branch=None, version_guid=
|
||||
if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
|
||||
if request.method == 'GET':
|
||||
raise NotImplementedError('coming soon')
|
||||
elif request.method == 'POST': # not sure if this is only post. If one will have ids, it goes after access
|
||||
return create_new_course(request)
|
||||
elif not has_access(
|
||||
request.user,
|
||||
BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
|
||||
):
|
||||
raise PermissionDenied()
|
||||
elif request.method == 'POST':
|
||||
raise NotImplementedError()
|
||||
elif request.method == 'PUT':
|
||||
raise NotImplementedError()
|
||||
elif request.method == 'DELETE':
|
||||
@@ -100,11 +104,64 @@ def course_handler(request, tag=None, course_id=None, branch=None, version_guid=
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
elif request.method == 'GET': # assume html
|
||||
return course_index(request, course_id, branch, version_guid, block)
|
||||
if course_id is None:
|
||||
return course_listing(request)
|
||||
else:
|
||||
return course_index(request, course_id, branch, version_guid, block)
|
||||
else:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_listing(request):
|
||||
"""
|
||||
List all courses available to the logged in user
|
||||
"""
|
||||
courses = modulestore('direct').get_items(['i4x', None, None, 'course', None])
|
||||
|
||||
# filter out courses that we don't have access too
|
||||
def course_filter(course):
|
||||
"""
|
||||
Get courses to which this user has access
|
||||
"""
|
||||
return (has_access(request.user, course.location)
|
||||
# pylint: disable=fixme
|
||||
# TODO remove this condition when templates purged from db
|
||||
and course.location.course != 'templates'
|
||||
and course.location.org != ''
|
||||
and course.location.course != ''
|
||||
and course.location.name != '')
|
||||
courses = filter(course_filter, courses)
|
||||
|
||||
def format_course_for_view(course):
|
||||
"""
|
||||
return tuple of the data which the view requires for each course
|
||||
"""
|
||||
# published = false b/c studio manipulates draft versions not b/c the course isn't pub'd
|
||||
course_loc = loc_mapper().translate_location(
|
||||
course.location.course_id, course.location, published=False, add_entry_if_missing=True
|
||||
)
|
||||
return (
|
||||
course.display_name,
|
||||
# note, couldn't get django reverse to work; so, wrote workaround
|
||||
course_loc.url_reverse('course/', ''),
|
||||
get_lms_link_for_item(
|
||||
course.location
|
||||
),
|
||||
course.display_org_with_default,
|
||||
course.display_number_with_default,
|
||||
course.location.name
|
||||
)
|
||||
|
||||
return render_to_response('index.html', {
|
||||
'courses': [format_course_for_view(c) for c in courses if not isinstance(c, ErrorDescriptor)],
|
||||
'user': request.user,
|
||||
'request_course_creator_url': reverse('contentstore.views.request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_index(request, course_id, branch, version_guid, block):
|
||||
@@ -142,7 +199,6 @@ def course_index(request, course_id, branch, version_guid, block):
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def create_new_course(request):
|
||||
"""
|
||||
@@ -756,3 +812,28 @@ def textbook_by_id(request, org, course, name, tid):
|
||||
own_metadata(course_module)
|
||||
)
|
||||
return JsonResponse()
|
||||
|
||||
|
||||
def _get_course_creator_status(user):
|
||||
"""
|
||||
Helper method for returning the course creator status for a particular user,
|
||||
taking into account the values of DISABLE_COURSE_CREATION and ENABLE_CREATOR_GROUP.
|
||||
|
||||
If the user passed in has not previously visited the index page, it will be
|
||||
added with status 'unrequested' if the course creator group is in use.
|
||||
"""
|
||||
if user.is_staff:
|
||||
course_creator_status = 'granted'
|
||||
elif settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False):
|
||||
course_creator_status = 'disallowed_for_this_site'
|
||||
elif settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False):
|
||||
course_creator_status = get_course_creator_status(user)
|
||||
if course_creator_status is None:
|
||||
# User not grandfathered in as an existing user, has not previously visited the dashboard page.
|
||||
# Add the user to the course creator admin table with status 'unrequested'.
|
||||
add_user_with_status_unrequested(user)
|
||||
course_creator_status = get_course_creator_status(user)
|
||||
else:
|
||||
course_creator_status = 'granted'
|
||||
|
||||
return course_creator_status
|
||||
|
||||
@@ -9,7 +9,6 @@ from django.conf import settings
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from external_auth.views import ssl_login_shortcut
|
||||
from .user import index
|
||||
|
||||
__all__ = ['signup', 'old_login_redirect', 'login_page', 'howitworks']
|
||||
|
||||
@@ -46,6 +45,6 @@ def login_page(request):
|
||||
def howitworks(request):
|
||||
"Proxy view"
|
||||
if request.user.is_authenticated():
|
||||
return index(request)
|
||||
return redirect('/course')
|
||||
else:
|
||||
return render_to_response('howitworks.html', {})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import json
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
@@ -9,17 +8,12 @@ from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from django.core.context_processors import csrf
|
||||
|
||||
from xmodule.modulestore.django import modulestore, loc_mapper
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
from contentstore.utils import get_lms_link_for_item
|
||||
from util.json_request import JsonResponse
|
||||
from auth.authz import (
|
||||
STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_course_groupname_for_role)
|
||||
from course_creators.views import (
|
||||
get_course_creator_status, add_user_with_status_unrequested,
|
||||
user_requested_access)
|
||||
from course_creators.views import user_requested_access
|
||||
|
||||
from .access import has_access
|
||||
|
||||
@@ -28,51 +22,7 @@ from xmodule.modulestore.locator import BlockUsageLocator
|
||||
from django.http import HttpResponseNotFound
|
||||
|
||||
|
||||
__all__ = ['index', 'request_course_creator', 'course_team_handler']
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
"""
|
||||
List all courses available to the logged in user
|
||||
"""
|
||||
courses = modulestore('direct').get_items(['i4x', None, None, 'course', None])
|
||||
|
||||
# filter out courses that we don't have access too
|
||||
def course_filter(course):
|
||||
return (has_access(request.user, course.location)
|
||||
# TODO remove this condition when templates purged from db
|
||||
and course.location.course != 'templates'
|
||||
and course.location.org != ''
|
||||
and course.location.course != ''
|
||||
and course.location.name != '')
|
||||
courses = filter(course_filter, courses)
|
||||
|
||||
def format_course_for_view(course):
|
||||
# published = false b/c studio manipulates draft versions not b/c the course isn't pub'd
|
||||
course_loc = loc_mapper().translate_location(
|
||||
course.location.course_id, course.location, published=False, add_entry_if_missing=True
|
||||
)
|
||||
return (
|
||||
course.display_name,
|
||||
# note, couldn't get django reverse to work; so, wrote workaround
|
||||
course_loc.url_reverse('course/', ''),
|
||||
get_lms_link_for_item(
|
||||
course.location
|
||||
),
|
||||
course.display_org_with_default,
|
||||
course.display_number_with_default,
|
||||
course.location.name
|
||||
)
|
||||
|
||||
return render_to_response('index.html', {
|
||||
'courses': [format_course_for_view(c) for c in courses if not isinstance(c, ErrorDescriptor)],
|
||||
'user': request.user,
|
||||
'request_course_creator_url': reverse('contentstore.views.request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'csrf': csrf(request)['csrf_token']
|
||||
})
|
||||
__all__ = ['request_course_creator', 'course_team_handler']
|
||||
|
||||
|
||||
@require_POST
|
||||
@@ -254,27 +204,3 @@ def _course_team_user(request, location, email):
|
||||
|
||||
return JsonResponse()
|
||||
|
||||
|
||||
def _get_course_creator_status(user):
|
||||
"""
|
||||
Helper method for returning the course creator status for a particular user,
|
||||
taking into account the values of DISABLE_COURSE_CREATION and ENABLE_CREATOR_GROUP.
|
||||
|
||||
If the user passed in has not previously visited the index page, it will be
|
||||
added with status 'unrequested' if the course creator group is in use.
|
||||
"""
|
||||
if user.is_staff:
|
||||
course_creator_status = 'granted'
|
||||
elif settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False):
|
||||
course_creator_status = 'disallowed_for_this_site'
|
||||
elif settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False):
|
||||
course_creator_status = get_course_creator_status(user)
|
||||
if course_creator_status is None:
|
||||
# User not grandfathered in as an existing user, has not previously visited the dashboard page.
|
||||
# Add the user to the course creator admin table with status 'unrequested'.
|
||||
add_user_with_status_unrequested(user)
|
||||
course_creator_status = get_course_creator_status(user)
|
||||
else:
|
||||
course_creator_status = 'granted'
|
||||
|
||||
return course_creator_status
|
||||
|
||||
@@ -32,7 +32,7 @@ require(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"],
|
||||
'run': run
|
||||
});
|
||||
|
||||
$.postJSON('/create_new_course', {
|
||||
$.postJSON('/course', {
|
||||
'org': org,
|
||||
'number': number,
|
||||
'display_name': display_name,
|
||||
|
||||
@@ -212,7 +212,6 @@ require(["domReady!", "jquery", "jquery.form", "js/index"], function(doc, $) {
|
||||
<h4 class="title">${_('Your Course Creator Request Status:')}</h4>
|
||||
|
||||
<form id="request-coursecreator" action="${request_course_creator_url}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf}">
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="request-coursecreator-submit" name="request-coursecreator-submit" class="action-primary action-request"><i class="icon-cog icon-inline icon-spin"></i> <span class="label">${_('Request the Ability to Create Courses')}</span></button>
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,7 @@ require(["jquery", "jquery.cookie"], function($) {
|
||||
submit_data,
|
||||
function(json) {
|
||||
if(json.success) {
|
||||
location.href = "${reverse('index')}";
|
||||
location.href = "${'/course'}";
|
||||
} else {
|
||||
$('#register_error').html(json.value).stop().addClass('is-shown');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ urlpatterns = patterns('', # nopep8
|
||||
url(r'^create_draft$', 'contentstore.views.create_draft', name='create_draft'),
|
||||
url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'),
|
||||
url(r'^unpublish_unit$', 'contentstore.views.unpublish_unit', name='unpublish_unit'),
|
||||
url(r'^create_new_course', 'contentstore.views.create_new_course', name='create_new_course'),
|
||||
url(r'^reorder_static_tabs', 'contentstore.views.reorder_static_tabs', name='reorder_static_tabs'),
|
||||
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/export/(?P<name>[^/]+)$',
|
||||
@@ -116,12 +115,11 @@ urlpatterns += patterns(
|
||||
urlpatterns += patterns(
|
||||
'contentstore.views',
|
||||
|
||||
url(r'^course$', 'index', name='index'),
|
||||
url(r'^request_course_creator$', 'request_course_creator'),
|
||||
# (?ix) == ignore case and verbose (multiline regex)
|
||||
url(r'(?ix)^course/{}$'.format(parsers.URL_RE_SOURCE), 'course_handler'),
|
||||
url(r'(?ix)^checklists/{}(/)?(?P<checklist_index>\d+)?$'.format(parsers.URL_RE_SOURCE), 'checklists_handler'),
|
||||
url(r'(?ix)^course_team/{}(/)?(?P<email>.+)?$'.format(parsers.URL_RE_SOURCE), 'course_team_handler'),
|
||||
url(r'(?ix)^course($|/){}$'.format(parsers.URL_RE_SOURCE), 'course_handler'),
|
||||
url(r'(?ix)^checklists/{}(/)?(?P<checklist_index>\d+)?$'.format(parsers.URL_RE_SOURCE), 'checklists_handler'),
|
||||
url(r'(?ix)^orphan/{}$'.format(parsers.URL_RE_SOURCE), 'orphan'),
|
||||
url(r'(?ix)^assets/{}(/)?(?P<asset_id>.+)?$'.format(parsers.URL_RE_SOURCE), 'assets_handler'),
|
||||
url(r'(?ix)^import/{}$'.format(parsers.URL_RE_SOURCE), 'import_handler'),
|
||||
|
||||
@@ -53,7 +53,7 @@ class UserStandingTest(TestCase):
|
||||
try:
|
||||
self.some_url = reverse('dashboard')
|
||||
except NoReverseMatch:
|
||||
self.some_url = reverse('index')
|
||||
self.some_url = '/course'
|
||||
|
||||
# since it's only possible to disable accounts from lms, we're going
|
||||
# to skip tests for cms
|
||||
|
||||
Reference in New Issue
Block a user