Commit ab9d30cb by Chris Dodge

Merge branch 'master' of github.com:MITx/mitx into fix/cdodge/export-static-tabs

parents fc57e7af bc1452f2
......@@ -352,6 +352,33 @@ class ContentStoreTest(TestCase):
def test_edit_unit_full(self):
self.check_edit_unit('full')
def test_static_tab_reordering(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
ms = modulestore('direct')
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
# reverse the ordering
reverse_tabs = []
for tab in course.tabs:
if tab['type'] == 'static_tab':
reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json")
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
# compare to make sure that the tabs information is in the expected order after the server call
course_tabs = []
for tab in course.tabs:
if tab['type'] == 'static_tab':
course_tabs.append('i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
self.assertEqual(reverse_tabs, course_tabs)
def test_about_overrides(self):
'''
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
......
......@@ -903,6 +903,52 @@ def static_pages(request, org, course, coursename):
def edit_static(request, org, course, coursename):
return render_to_response('edit-static-page.html', {})
@login_required
@expect_json
def reorder_static_tabs(request):
tabs = request.POST['tabs']
course = get_course_for_item(tabs[0])
if not has_access(request.user, course.location):
raise PermissionDenied()
# get list of existing static tabs in course
# make sure they are the same lengths (i.e. the number of passed in tabs equals the number
# that we know about) otherwise we can drop some!
existing_static_tabs = [t for t in course.tabs if t['type'] == 'static_tab']
if len(existing_static_tabs) != len(tabs):
return HttpResponseBadRequest()
# load all reference tabs, return BadRequest if we can't find any of them
tab_items =[]
for tab in tabs:
item = modulestore('direct').get_item(Location(tab))
if item is None:
return HttpResponseBadRequest()
tab_items.append(item)
# now just go through the existing course_tabs and re-order the static tabs
reordered_tabs = []
static_tab_idx = 0
for tab in course.tabs:
if tab['type'] == 'static_tab':
reordered_tabs.append({'type': 'static_tab',
'name' : tab_items[static_tab_idx].metadata.get('display_name'),
'url_slug' : tab_items[static_tab_idx].location.name})
static_tab_idx += 1
else:
reordered_tabs.append(tab)
# OK, re-assemble the static tabs in the new order
course.tabs = reordered_tabs
modulestore('direct').update_metadata(course.location, course.metadata)
return HttpResponse()
@login_required
@ensure_csrf_cookie
def edit_tabs(request, org, course, coursename):
......@@ -914,12 +960,19 @@ def edit_tabs(request, org, course, coursename):
if not has_access(request.user, location):
raise PermissionDenied()
static_tabs = modulestore('direct').get_items(static_tabs_loc)
# see tabs have been uninitialized (e.g. supporing courses created before tab support in studio)
if course_item.tabs is None or len(course_item.tabs) == 0:
initialize_course_tabs(course_item)
# first get all static tabs from the tabs list
# we do this because this is also the order in which items are displayed in the LMS
static_tabs_refs = [t for t in course_item.tabs if t['type'] == 'static_tab']
static_tabs = []
for static_tab_ref in static_tabs_refs:
static_tab_loc = Location(location)._replace(category='static_tab', name=static_tab_ref['url_slug'])
static_tabs.append(modulestore('direct').get_item(static_tab_loc))
components = [
static_tab.location.url()
for static_tab
......@@ -1326,7 +1379,6 @@ def import_course(request, org, course, name):
@login_required
def generate_export_course(request, org, course, name):
location = ['i4x', org, course, 'course', name]
course_module = modulestore().get_item(location)
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
......@@ -1373,3 +1425,10 @@ def export_course(request, org, course, name):
'active_tab': 'export',
'successful_import_redirect_url' : ''
})
def event(request):
'''
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
console logs don't get distracted :-)
'''
return HttpResponse(True)
\ No newline at end of file
......@@ -15,7 +15,7 @@ class CMS.Views.TabsEdit extends Backbone.View
@$('.components').sortable(
handle: '.drag-handle'
update: (event, ui) => alert 'not yet implemented!'
update: @tabMoved
helper: 'clone'
opacity: '0.5'
placeholder: 'component-placeholder'
......@@ -24,6 +24,20 @@ class CMS.Views.TabsEdit extends Backbone.View
items: '> .component'
)
tabMoved: (event, ui) =>
tabs = []
@$('.component').each((idx, element) =>
tabs.push($(element).data('id'))
)
$.ajax({
type:'POST',
url: '/reorder_static_tabs',
data: JSON.stringify({
tabs : tabs
}),
contentType: 'application/json'
})
addNewTab: (event) =>
event.preventDefault()
......
......@@ -17,6 +17,7 @@ urlpatterns = ('',
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>[^/]+)/course/(?P<name>[^/]+)$',
'contentstore.views.course_index', name='course_index'),
......@@ -67,6 +68,8 @@ urlpatterns = ('',
# temporary landing page for edge
url(r'^edge$', 'contentstore.views.edge', name='edge'),
# noop to squelch ajax errors
url(r'^event$', 'contentstore.views.event', name='event'),
url(r'^heartbeat$', include('heartbeat.urls')),
)
......
from optparse import make_option
from json import dump
from datetime import datetime
from django.core.management.base import BaseCommand, CommandError
......@@ -32,10 +33,9 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if len(args) < 1:
raise CommandError("Missing single argument: output JSON file")
# get output location:
outputfile = args[0]
outputfile = datetime.utcnow().strftime("pearson-dump-%Y%m%d-%H%M%S.json")
else:
outputfile = args[0]
# construct the query object to dump:
registrations = TestCenterRegistration.objects.all()
......@@ -65,6 +65,8 @@ class Command(BaseCommand):
}
if len(registration.upload_error_message) > 0:
record['registration_error'] = registration.upload_error_message
if len(registration.testcenter_user.upload_error_message) > 0:
record['demographics_error'] = registration.testcenter_user.upload_error_message
if registration.needs_uploading:
record['needs_uploading'] = True
......@@ -72,5 +74,5 @@ class Command(BaseCommand):
# dump output:
with open(outputfile, 'w') as outfile:
dump(output, outfile)
dump(output, outfile, indent=2)
......@@ -8,6 +8,7 @@
{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "static_tab", "url_slug": "syllabus", "name": "Syllabus"},
{"type": "static_tab", "url_slug": "resources", "name": "Resources"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}
......
import factory
from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed)
from django.contrib.auth.models import Group
from datetime import datetime
import uuid
class UserProfileFactory(factory.Factory):
FACTORY_FOR = UserProfile
user = None
name = 'Robot Studio'
courseware = 'course.xml'
class RegistrationFactory(factory.Factory):
FACTORY_FOR = Registration
user = None
activation_key = uuid.uuid4().hex
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = 'robot'
email = 'robot@edx.org'
password = 'test'
first_name = 'Robot'
last_name = 'Tester'
is_staff = False
is_active = True
is_superuser = False
last_login = datetime.now()
date_joined = datetime.now()
class GroupFactory(factory.Factory):
FACTORY_FOR = Group
name = 'test_group'
class CourseEnrollmentAllowedFactory(factory.Factory):
FACTORY_FOR = CourseEnrollmentAllowed
email = 'test@edx.org'
course_id = 'edX/test/2012_Fall'
import unittest
import time
from mock import Mock
from django.test import TestCase
from xmodule.modulestore import Location
from factories import CourseEnrollmentAllowedFactory
import courseware.access as access
class AccessTestCase(TestCase):
def test__has_global_staff_access(self):
u = Mock(is_staff=False)
self.assertFalse(access._has_global_staff_access(u))
u = Mock(is_staff=True)
self.assertTrue(access._has_global_staff_access(u))
def test__has_access_to_location(self):
location = Location('i4x://edX/toy/course/2012_Fall')
self.assertFalse(access._has_access_to_location(None, location,
'staff', None))
u = Mock()
u.is_authenticated.return_value = False
self.assertFalse(access._has_access_to_location(u, location,
'staff', None))
u = Mock(is_staff=True)
self.assertTrue(access._has_access_to_location(u, location,
'instructor', None))
# A user has staff access if they are in the staff group
u = Mock(is_staff=False)
g = Mock()
g.name = 'staff_edX/toy/2012_Fall'
u.groups.all.return_value = [g]
self.assertTrue(access._has_access_to_location(u, location,
'staff', None))
# A user has staff access if they are in the instructor group
g.name = 'instructor_edX/toy/2012_Fall'
self.assertTrue(access._has_access_to_location(u, location,
'staff', None))
# A user has instructor access if they are in the instructor group
g.name = 'instructor_edX/toy/2012_Fall'
self.assertTrue(access._has_access_to_location(u, location,
'instructor', None))
# A user does not have staff access if they are
# not in either the staff or the the instructor group
g.name = 'student_only'
self.assertFalse(access._has_access_to_location(u, location,
'staff', None))
# A user does not have instructor access if they are
# not in the instructor group
g.name = 'student_only'
self.assertFalse(access._has_access_to_location(u, location,
'instructor', None))
def test__has_access_string(self):
u = Mock(is_staff=True)
self.assertFalse(access._has_access_string(u, 'not_global', 'staff', None))
u._has_global_staff_access.return_value = True
self.assertTrue(access._has_access_string(u, 'global', 'staff', None))
self.assertRaises(ValueError, access._has_access_string, u, 'global', 'not_staff', None)
def test__has_access_descriptor(self):
# TODO: override DISABLE_START_DATES and test the start date branch of the method
u = Mock()
d = Mock()
d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past
# Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(u, d, 'load'))
self.assertRaises(ValueError, access._has_access_descriptor, u, d, 'not_load_or_staff')
def test__has_access_course_desc_can_enroll(self):
u = Mock()
yesterday = time.gmtime(time.time() - 86400)
tomorrow = time.gmtime(time.time() + 86400)
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow)
c.metadata.get = 'is_public'
# User can enroll if it is between the start and end dates
self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
# User can enroll if authenticated and specifically allowed for that course
# even outside the open enrollment period
u = Mock(email='test@edx.org', is_staff=False)
u.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/2012_Fall')
c.metadata.get = 'is_public'
allowed = CourseEnrollmentAllowedFactory(email=u.email, course_id=c.id)
self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
# Staff can always enroll even outside the open enrollment period
u = Mock(email='test@edx.org', is_staff=True)
u.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/Whenever')
c.metadata.get = 'is_public'
self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
# TODO:
# Non-staff cannot enroll outside the open enrollment period if not specifically allowed
......@@ -6,7 +6,16 @@
<link type="text/html" rel="alternate" href="http://blog.edx.org/"/>
##<link type="application/atom+xml" rel="self" href="https://github.com/blog.atom"/>
<title>EdX Blog</title>
<updated>2013-01-21T14:00:12-07:00</updated>
<updated>2013-01-30T14:00:12-07:00</updated>
<entry>
<id>tag:www.edx.org,2012:Post/13</id>
<published>2013-01-30T10:00:00-07:00</published>
<updated>2013-01-30T10:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/eric-lander-secret-of-life')}"/>
<title>New biology course from human genome pioneer Eric Lander</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/eric-lander_240x180.jpg')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/12</id>
<published>2013-01-29T10:00:00-07:00</published>
......
......@@ -39,7 +39,14 @@
</article>
<article class="response">
<h3>Will certificates be awarded?</h3>
<p>Yes. Online learners who demonstrate mastery of subjects can earn a certificate of mastery. Certificates will be issued by edX under the name of the underlying "X University" from where the course originated, i.e. HarvardX, <em>MITx</em> or BerkeleyX. For the courses in Fall 2012, those certificates will be free. There is a plan to charge a modest fee for certificates in the future.</p>
<p>Yes. Online learners who demonstrate mastery of subjects can earn a certificate
of mastery. Certificates will be issued at the discretion of edX and the underlying
X University that offered the course under the name of the underlying "X
University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX.
For the courses in Fall 2012, those certificates will be free. There is a plan to
charge a modest fee for certificates in the future. Note: At this time, edX is
holding certificates for learners connected with Cuba, Iran, Syria and Sudan
pending confirmation that the issuance is in compliance with U.S. embargoes.</p>
</article>
<article class="response">
<h3>What will the scope of the online courses be? How many? Which faculty?</h3>
......
......@@ -180,8 +180,17 @@
<article class="response">
<h3 class="question">Will I get a certificate for taking an edX course?</h3>
<div class="answer" id="certificates_and_credits_faq_answer_0">
<p>Online learners who receive a passing grade for a course will receive a certificate of mastery from edX and the underlying X University that offered the course. For example, a certificate of mastery for MITx’s 6.002x Circuits & Electronics will come from edX and MITx.</p>
<div class ="answer" id="certificates_and_credits_faq_answer_0">
<p>Online learners who receive a passing grade for a course will receive a certificate
of mastery at the discretion of edX and the underlying X University that offered
the course. For example, a certificate of mastery for MITx’s 6.002x Circuits &amp;
Electronics will come from edX and MITx.</p>
<p>If you passed the course, your certificate of mastery will be delivered online
through edx.org. So be sure to check your email in the weeks following the final
grading – you will be able to download and print your certificate. Note: At this
time, edX is holding certificates for learners connected with Cuba, Iran, Syria
and Sudan pending confirmation that the issuance is in compliance with U.S.
embargoes.</p>
</div>
</article>
<article class="response">
......
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../../main.html" />
<%namespace name='static' file='../../static_content.html'/>
<%block name="title"><title>TITLE</title></%block>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<section class="pressrelease">
<section class="container">
<h1>TITLE</h1>
<hr class="horizontal-divider">
<article>
<h2>SUBTITLE</h2>
<p><strong>CAMBRIDGE, MA &ndash; MONTH DAY, YEAR &ndash;</strong>
Text</p>
<p>more text</p>
<h2>About edX</h2>
<p><a href="https://www.edx.org/">EdX</a> is a not-for-profit enterprise of its founding partners <a href="http://www.harvard.edu">Harvard University</a> and the <a href="http://www.mit.edu">Massachusetts Institute of Technology</a> focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.</p>
<section class="contact">
<p><strong>Contact:</strong></p>
<p>Brad Baker, Weber Shandwick for edX</p>
<p>BBaker@webershandwick.com</p>
<p>(617) 520-7043</p>
</section>
<section class="footer">
<hr class="horizontal-divider">
<div class="logo"></div><h3 class="date">DATE: 01 - 29 - 2013</h3>
<div class="social-sharing">
<hr class="horizontal-divider">
<p>Share with friends and family:</p>
<a href="http://twitter.com/intent/tweet?text=:BLAH+BLAH+BLAH+http://www.edx.org/press/LINK" class="share">
<img src="${static.url('images/social/twitter-sharing.png')}">
</a>
</a>
<a href="mailto:?subject=BLAH%BLAH%BLAH…http://edx.org/press/LINK" class="share">
<img src="${static.url('images/social/email-sharing.png')}">
</a>
<div class="fb-like" data-href="http://edx.org/press/LINK" data-send="true" data-width="450" data-show-faces="true"></div>
</div>
</section>
</article>
</section>
</section>
......@@ -125,11 +125,14 @@ urlpatterns = ('',
url(r'^press/bostonx-announcement$', 'static_template_view.views.render',
{'template': 'press_releases/bostonx_announcement.html'},
name="press/bostonx-announcement"),
url(r'^press/eric-lander-secret-of-life$', 'static_template_view.views.render',
{'template': 'press_releases/eric_lander_secret_of_life.html'},
name="press/eric-lander-secret-of-life"),
# Should this always update to point to the latest press release?
(r'^pressrelease$', 'django.views.generic.simple.redirect_to',
{'url': '/press/bostonx-announcement'}),
{'url': '/press/eric-lander-secret-of-life'}),
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),
......
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