Merge branch 'master' into feature/christina/fields
This commit is contained in:
148
README.md
Normal file
148
README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
This is edX, a platform for online course delivery. The project is primarily
|
||||
written in [Python](http://python.org/), using the
|
||||
[Django](https://www.djangoproject.com/) framework. We also use some
|
||||
[Ruby](http://www.ruby-lang.org/) and some [NodeJS](http://nodejs.org/).
|
||||
|
||||
Installation
|
||||
============
|
||||
The installation process is a bit messy at the moment. Here's a high-level
|
||||
overview of what you should do to get started.
|
||||
|
||||
**TLDR:** There is a `create-dev-env.sh` script that will attempt to set all
|
||||
of this up for you. If you're in a hurry, run that script. Otherwise, I suggest
|
||||
that you understand what the script is doing, and why, by reading this document.
|
||||
|
||||
Directory Hierarchy
|
||||
-------------------
|
||||
This code assumes that it is checked out in a directory that has three sibling
|
||||
directories: `data` (used for XML course data), `db` (used to hold a
|
||||
[sqlite](https://sqlite.org/) database), and `log` (used to hold logs). If you
|
||||
clone the repository into a directory called `edx` inside of a directory
|
||||
called `dev`, here's an example of how the directory hierarchy should look:
|
||||
|
||||
* dev
|
||||
\
|
||||
* data
|
||||
* db
|
||||
* log
|
||||
* edx
|
||||
\
|
||||
README.md
|
||||
|
||||
Language Runtimes
|
||||
-----------------
|
||||
You'll need to be sure that you have Python 2.7, Ruby 1.9.3, and NodeJS
|
||||
(latest stable) installed on your system. Some of these you can install
|
||||
using your system's package manager: [homebrew](http://mxcl.github.io/homebrew/)
|
||||
for Mac, [apt](http://wiki.debian.org/Apt) for Debian-based systems
|
||||
(including Ubuntu), [rpm](http://www.rpm.org/) or [yum](http://yum.baseurl.org/)
|
||||
for Red Hat based systems (including CentOS).
|
||||
|
||||
If your system's package manager gives you the wrong version of a language
|
||||
runtime, then you'll need to use a versioning tool to install the correct version.
|
||||
Usually, you'll need to do this for Ruby: you can use
|
||||
[`rbenv`](https://github.com/sstephenson/rbenv) or [`rvm`](https://rvm.io/), but
|
||||
typically `rbenv` is simpler. For Python, you can use
|
||||
[`pythonz`](http://saghul.github.io/pythonz/),
|
||||
and for Node, you can use [`nvm`](https://github.com/creationix/nvm).
|
||||
|
||||
Virtual Environments
|
||||
--------------------
|
||||
Often, different projects will have conflicting dependencies: for example, two
|
||||
projects depending on two different, incompatible versions of a library. Clearly,
|
||||
you can't have both versions installed and used on your machine simultaneously.
|
||||
Virtual environments were created to solve this problem: by installing libraries
|
||||
into an isolated environment, only projects that live inside the environment
|
||||
will be able to see and use those libraries. Got incompatible dependencies? Use
|
||||
different virtual environments, and your problem is solved.
|
||||
|
||||
Remember, each language has a different implementation. Python has
|
||||
[`virtualenv`](http://www.virtualenv.org/), Ruby has
|
||||
[`bundler`](http://gembundler.com/), and Node's virtual environment support
|
||||
is built into [`npm`](https://npmjs.org/), its library management tool.
|
||||
For each language, decide if you want to use a virtual environment, or if you
|
||||
want to install all the language dependencies globally (and risk conflicts).
|
||||
I suggest you start with installing things globally until and unless things
|
||||
break; you can always switch over to a virtual environment later on.
|
||||
|
||||
Language Packages
|
||||
-----------------
|
||||
The Python libraries we use are listed in `requirements.txt`. The Ruby libraries
|
||||
we use are listed in `Gemfile`. The Node libraries we use are listed in
|
||||
`packages.json`. Python has a library installer called
|
||||
[`pip`](http://www.pip-installer.org/), Ruby has a library installer called
|
||||
[`gem`](https://rubygems.org/) (or `bundle` if you're using a virtual
|
||||
environment), and Node has a library installer called
|
||||
[`npm`](https://npmjs.org/).
|
||||
Once you've got your languages and virtual environments set up, install
|
||||
the libraries like so:
|
||||
|
||||
$ pip install -r pre-requirements.txt
|
||||
$ pip install -r requirements.txt
|
||||
$ bundle install
|
||||
$ npm install
|
||||
|
||||
Other Dependencies
|
||||
------------------
|
||||
You'll also need to install [MongoDB](http://www.mongodb.org/), since our
|
||||
application uses it in addition to sqlite. You can install it through your
|
||||
system package manager, and I suggest that you configure it to start
|
||||
automatically when you boot up your system, so that you never have to worry
|
||||
about it again. For Mac, use
|
||||
[`launchd`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/launchd.8.html)
|
||||
(running `brew info mongodb` will give you some commands you can copy-paste.)
|
||||
For Linux, you can use [`upstart`](http://upstart.ubuntu.com/), `chkconfig`,
|
||||
or any other process management tool.
|
||||
|
||||
Configuring Your Project
|
||||
------------------------
|
||||
We use [`rake`](http://rake.rubyforge.org/) to execute common tasks in our
|
||||
project. The `rake` tasks are defined in the `rakefile`, or you can run `rake -T`
|
||||
to view a summary.
|
||||
|
||||
Before you run your project, you need to create a sqlite database, create
|
||||
tables in that database, run database migrations, and populate templates for
|
||||
CMS templates. Fortunately, `rake` will do all of this for you! Just run:
|
||||
|
||||
$ rake django-admin[syncdb]
|
||||
$ rake django-admin[migrate]
|
||||
$ rake django-admin[update_templates]
|
||||
|
||||
If you are running these commands using the [`zsh`](http://www.zsh.org/) shell,
|
||||
zsh will assume that you are doing
|
||||
[shell globbing](https://en.wikipedia.org/wiki/Glob_(programming)), search for
|
||||
a file in your directory named `django-adminsyncdb` or `django-adminmigrate`,
|
||||
and fail. To fix this, just surround the argument with quotation marks, so that
|
||||
you're running `rake "django-admin[syncdb]"`.
|
||||
|
||||
Run Your Project
|
||||
----------------
|
||||
edX has two components: Studio, the course authoring system; and the LMS
|
||||
(learning management system) used by students. These two systems communicate
|
||||
through the MongoDB database, which stores course information.
|
||||
|
||||
To run Studio, run:
|
||||
|
||||
$ rake cms
|
||||
|
||||
To run the LMS, run:
|
||||
|
||||
$ rake lms[cms.dev]
|
||||
|
||||
Studio runs on port 8001, while LMS runs on port 8000, so you can run both of
|
||||
these commands simultaneously, using two different terminal windows. To view
|
||||
Studio, visit `127.0.0.1:8001` in your web browser; to view the LMS, visit
|
||||
`127.0.0.1:8000`.
|
||||
|
||||
There's also an older version of the LMS that saves its information in XML files
|
||||
in the `data` directory, instead of in Mongo. To run this older version, run:
|
||||
|
||||
$ rake lms
|
||||
|
||||
Further Documentation
|
||||
=====================
|
||||
Once you've got your project up and running, you can check out the `docs`
|
||||
directory to see more documentation about how edX is structured.
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ var $newComponentTypePicker;
|
||||
var $newComponentTemplatePickers;
|
||||
var $newComponentButton;
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(function() {
|
||||
$body = $('body');
|
||||
$modal = $('.history-modal');
|
||||
$modalCover = $('<div class="modal-cover">');
|
||||
@@ -35,7 +35,7 @@ $(document).ready(function () {
|
||||
$('.uploads .upload-button').bind('click', showUploadModal);
|
||||
$('.upload-modal .close-button').bind('click', hideModal);
|
||||
|
||||
$body.on('click', '.embeddable-xml-input', function () {
|
||||
$body.on('click', '.embeddable-xml-input', function() {
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
@@ -45,8 +45,11 @@ $(document).ready(function () {
|
||||
$('.new-unit-item').bind('click', createNewUnit);
|
||||
|
||||
// lean/simple modal
|
||||
$('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' });
|
||||
$('a.action-modal-close').click(function(e){
|
||||
$('a[rel*=modal]').leanModal({
|
||||
overlay: 0.80,
|
||||
closeButton: '.action-modal-close'
|
||||
});
|
||||
$('a.action-modal-close').click(function(e) {
|
||||
(e).preventDefault();
|
||||
});
|
||||
|
||||
@@ -55,12 +58,12 @@ $(document).ready(function () {
|
||||
$('.action-notification-close').bind('click', hideNotification);
|
||||
|
||||
// nav - dropdown related
|
||||
$body.click(function (e) {
|
||||
$body.click(function(e) {
|
||||
$('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
|
||||
$('.nav-dropdown .nav-item .title').removeClass('is-selected');
|
||||
});
|
||||
|
||||
$('.nav-dropdown .nav-item .title').click(function (e) {
|
||||
$('.nav-dropdown .nav-item .title').click(function(e) {
|
||||
|
||||
$subnav = $(this).parent().find('.wrapper-nav-sub');
|
||||
$title = $(this).parent().find('.title');
|
||||
@@ -70,9 +73,7 @@ $(document).ready(function () {
|
||||
if ($subnav.hasClass('is-shown')) {
|
||||
$subnav.removeClass('is-shown');
|
||||
$title.removeClass('is-selected');
|
||||
}
|
||||
|
||||
else {
|
||||
} else {
|
||||
$('.nav-dropdown .nav-item .title').removeClass('is-selected');
|
||||
$('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
|
||||
$title.addClass('is-selected');
|
||||
@@ -84,8 +85,11 @@ $(document).ready(function () {
|
||||
$('a[rel="external"]').attr('title', 'This link will open in a new browser window/tab').bind('click', linkNewWindow);
|
||||
|
||||
// general link management - lean modal window
|
||||
$('a[rel="modal"]').attr('title', 'This link will open in a modal window').leanModal({overlay: 0.50, closeButton: '.action-modal-close' });
|
||||
$('.action-modal-close').click(function (e) {
|
||||
$('a[rel="modal"]').attr('title', 'This link will open in a modal window').leanModal({
|
||||
overlay: 0.50,
|
||||
closeButton: '.action-modal-close'
|
||||
});
|
||||
$('.action-modal-close').click(function(e) {
|
||||
(e).preventDefault();
|
||||
});
|
||||
|
||||
@@ -99,7 +103,7 @@ $(document).ready(function () {
|
||||
$('.cta-show-sock').bind('click', toggleSock);
|
||||
|
||||
// toggling overview section details
|
||||
$(function () {
|
||||
$(function() {
|
||||
if ($('.courseware-section').length > 0) {
|
||||
$('.toggle-button-sections').addClass('is-shown');
|
||||
}
|
||||
@@ -108,7 +112,7 @@ $(document).ready(function () {
|
||||
|
||||
// autosave when leaving input field
|
||||
$body.on('change', '.subsection-display-name-input', saveSubsection);
|
||||
$('.subsection-display-name-input').each(function () {
|
||||
$('.subsection-display-name-input').each(function() {
|
||||
this.val = $(this).val();
|
||||
});
|
||||
$("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput);
|
||||
@@ -129,7 +133,7 @@ $(document).ready(function () {
|
||||
|
||||
// import form setup
|
||||
$('.import .file-input').bind('change', showImportSubmit);
|
||||
$('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function (e) {
|
||||
$('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('.import .file-input').click();
|
||||
});
|
||||
@@ -152,12 +156,12 @@ $(document).ready(function () {
|
||||
$body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
|
||||
$body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
|
||||
$body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal);
|
||||
$body.on('change', '.edit-subsection-publish-settings .start-date', function () {
|
||||
$body.on('change', '.edit-subsection-publish-settings .start-date', function() {
|
||||
if ($('.edit-subsection-publish-settings').find('.start-time').val() == '') {
|
||||
$('.edit-subsection-publish-settings').find('.start-time').val('12:00am');
|
||||
}
|
||||
});
|
||||
$('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function () {
|
||||
$('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function() {
|
||||
$('.edit-subsection-publish-settings').find('.save-button').show();
|
||||
});
|
||||
});
|
||||
@@ -177,7 +181,7 @@ function smoothScrollLink(e) {
|
||||
// On AWS instances, this base.js gets wrapped in a separate scope as part of Django static
|
||||
// pipelining (note, this doesn't happen on local runtimes). So if we set it on window,
|
||||
// when we can access it from other scopes (namely Course Advanced Settings).
|
||||
window.CmsUtils.smoothScrollTop = function (e) {
|
||||
window.CmsUtils.smoothScrollTop = function(e) {
|
||||
(e).preventDefault();
|
||||
|
||||
$.smoothScroll({
|
||||
@@ -260,8 +264,7 @@ function getEdxTimeFromDateTimeVals(date_val, time_val) {
|
||||
var edxTimeStr = null;
|
||||
|
||||
if (date_val != '') {
|
||||
if (time_val == '')
|
||||
time_val = '00:00';
|
||||
if (time_val == '') time_val = '00:00';
|
||||
|
||||
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
|
||||
var date = Date.parse(date_val + " " + time_val);
|
||||
@@ -284,7 +287,7 @@ function autosaveInput(e) {
|
||||
clearTimeout(this.saveTimer);
|
||||
}
|
||||
|
||||
this.saveTimer = setTimeout(function () {
|
||||
this.saveTimer = setTimeout(function() {
|
||||
$changedInput = $(e.target);
|
||||
saveSubsection();
|
||||
self.saveTimer = null;
|
||||
@@ -324,12 +327,15 @@ function saveSubsection() {
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ 'id': id, 'metadata': metadata}),
|
||||
success: function () {
|
||||
data: JSON.stringify({
|
||||
'id': id,
|
||||
'metadata': metadata
|
||||
}),
|
||||
success: function() {
|
||||
$spinner.delay(500).fadeOut(150);
|
||||
$changedInput = null;
|
||||
},
|
||||
error: function () {
|
||||
error: function() {
|
||||
showToastMessage('There has been an error while saving your changes.');
|
||||
}
|
||||
});
|
||||
@@ -348,15 +354,16 @@ function createNewUnit(e) {
|
||||
});
|
||||
|
||||
|
||||
$.post('/clone_item',
|
||||
{'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': 'New Unit'
|
||||
},
|
||||
function (data) {
|
||||
// redirect to the edit page
|
||||
window.location = "/edit/" + data['id'];
|
||||
});
|
||||
$.post('/clone_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': 'New Unit'
|
||||
},
|
||||
|
||||
function(data) {
|
||||
// redirect to the edit page
|
||||
window.location = "/edit/" + data['id'];
|
||||
});
|
||||
}
|
||||
|
||||
function deleteUnit(e) {
|
||||
@@ -375,8 +382,7 @@ function deleteSection(e) {
|
||||
}
|
||||
|
||||
function _deleteItem($el) {
|
||||
if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
|
||||
return;
|
||||
if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!')) return;
|
||||
|
||||
var id = $el.data('id');
|
||||
|
||||
@@ -386,11 +392,15 @@ function _deleteItem($el) {
|
||||
});
|
||||
|
||||
|
||||
$.post('/delete_item',
|
||||
{'id': id, 'delete_children': true, 'delete_all_versions': true},
|
||||
function (data) {
|
||||
$el.remove();
|
||||
});
|
||||
$.post('/delete_item', {
|
||||
'id': id,
|
||||
'delete_children': true,
|
||||
'delete_all_versions': true
|
||||
},
|
||||
|
||||
function(data) {
|
||||
$el.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function showUploadModal(e) {
|
||||
@@ -492,18 +502,16 @@ function toggleSock(e) {
|
||||
$sockContent.toggle('fast');
|
||||
|
||||
$.smoothScroll({
|
||||
offset: -200,
|
||||
easing: 'swing',
|
||||
speed: 1000,
|
||||
scrollElement: null,
|
||||
scrollTarget: $sock
|
||||
offset: -200,
|
||||
easing: 'swing',
|
||||
speed: 1000,
|
||||
scrollElement: null,
|
||||
scrollTarget: $sock
|
||||
});
|
||||
|
||||
if($sock.hasClass('is-shown')) {
|
||||
if ($sock.hasClass('is-shown')) {
|
||||
$btnLabel.text(gettext('Hide Studio Help'));
|
||||
}
|
||||
|
||||
else {
|
||||
} else {
|
||||
$btnLabel.text(gettext('Looking for Help with Studio?'));
|
||||
}
|
||||
}
|
||||
@@ -549,7 +557,7 @@ function removeDateSetter(e) {
|
||||
|
||||
function hideNotification(e) {
|
||||
(e).preventDefault();
|
||||
$(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true');
|
||||
$(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function hideAlert(e) {
|
||||
@@ -580,7 +588,7 @@ function showToastMessage(message, $button, lifespan) {
|
||||
$toast.fadeIn(200);
|
||||
|
||||
if (lifespan) {
|
||||
$toast.timer = setTimeout(function () {
|
||||
$toast.timer = setTimeout(function() {
|
||||
$toast.fadeOut(300);
|
||||
}, lifespan * 1000);
|
||||
}
|
||||
@@ -602,7 +610,9 @@ function addNewSection(e, isTemplate) {
|
||||
$newSection.find('.new-section-name').focus().select();
|
||||
$newSection.find('.section-name-form').bind('submit', saveNewSection);
|
||||
$cancelButton.bind('click', cancelNewSection);
|
||||
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
|
||||
$body.bind('keyup', {
|
||||
$cancelButton: $cancelButton
|
||||
}, checkForCancel);
|
||||
}
|
||||
|
||||
function checkForCancel(e) {
|
||||
@@ -627,15 +637,14 @@ function saveNewSection(e) {
|
||||
});
|
||||
|
||||
$.post('/clone_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': display_name,
|
||||
},
|
||||
function (data) {
|
||||
if (data.id != undefined)
|
||||
location.reload();
|
||||
}
|
||||
);
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': display_name,
|
||||
},
|
||||
|
||||
function(data) {
|
||||
if (data.id != undefined) location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function cancelNewSection(e) {
|
||||
@@ -654,7 +663,9 @@ function addNewCourse(e) {
|
||||
$newCourse.find('.new-course-name').focus().select();
|
||||
$newCourse.find('form').bind('submit', saveNewCourse);
|
||||
$cancelButton.bind('click', cancelNewCourse);
|
||||
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
|
||||
$body.bind('keyup', {
|
||||
$cancelButton: $cancelButton
|
||||
}, checkForCancel);
|
||||
}
|
||||
|
||||
function saveNewCourse(e) {
|
||||
@@ -678,18 +689,19 @@ function saveNewCourse(e) {
|
||||
});
|
||||
|
||||
$.post('/create_new_course', {
|
||||
'template': template,
|
||||
'org': org,
|
||||
'number': number,
|
||||
'display_name': display_name
|
||||
},
|
||||
function (data) {
|
||||
if (data.id != undefined) {
|
||||
window.location = '/' + data.id.replace(/.*:\/\//, '');
|
||||
} else if (data.ErrMsg != undefined) {
|
||||
alert(data.ErrMsg);
|
||||
}
|
||||
});
|
||||
'template': template,
|
||||
'org': org,
|
||||
'number': number,
|
||||
'display_name': display_name
|
||||
},
|
||||
|
||||
function(data) {
|
||||
if (data.id != undefined) {
|
||||
window.location = '/' + data.id.replace(/.*:\/\//, '');
|
||||
} else if (data.ErrMsg != undefined) {
|
||||
alert(data.ErrMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelNewCourse(e) {
|
||||
@@ -715,7 +727,9 @@ function addNewSubsection(e) {
|
||||
|
||||
$newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
|
||||
$cancelButton.bind('click', cancelNewSubsection);
|
||||
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
|
||||
$body.bind('keyup', {
|
||||
$cancelButton: $cancelButton
|
||||
}, checkForCancel);
|
||||
}
|
||||
|
||||
function saveNewSubsection(e) {
|
||||
@@ -732,16 +746,16 @@ function saveNewSubsection(e) {
|
||||
|
||||
|
||||
$.post('/clone_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': display_name
|
||||
},
|
||||
function (data) {
|
||||
if (data.id != undefined) {
|
||||
location.reload();
|
||||
}
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'display_name': display_name
|
||||
},
|
||||
|
||||
function(data) {
|
||||
if (data.id != undefined) {
|
||||
location.reload();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function cancelNewSubsection(e) {
|
||||
@@ -757,7 +771,9 @@ function editSectionName(e) {
|
||||
$(this).children('.section-name-span').hide();
|
||||
$(this).find('.section-name-edit').bind('submit', saveEditSectionName);
|
||||
$(this).find('.edit-section-name-cancel').bind('click', cancelNewSection);
|
||||
$body.bind('keyup', { $cancelButton: $(this).find('.edit-section-name-cancel') }, checkForCancel);
|
||||
$body.bind('keyup', {
|
||||
$cancelButton: $(this).find('.edit-section-name-cancel')
|
||||
}, checkForCancel);
|
||||
}
|
||||
|
||||
function cancelEditSectionName(e) {
|
||||
@@ -798,14 +814,19 @@ function saveEditSectionName(e) {
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ 'id': id, 'metadata': {'display_name': display_name}})
|
||||
}).success(function () {
|
||||
$spinner.delay(250).fadeOut(250);
|
||||
$_this.closest('h3').find('.section-name-span').html(display_name).show();
|
||||
$_this.hide();
|
||||
$_this.closest('.section-name').bind('click', editSectionName);
|
||||
e.stopPropagation();
|
||||
});
|
||||
data: JSON.stringify({
|
||||
'id': id,
|
||||
'metadata': {
|
||||
'display_name': display_name
|
||||
}
|
||||
})
|
||||
}).success(function() {
|
||||
$spinner.delay(250).fadeOut(250);
|
||||
$_this.closest('h3').find('.section-name-span').html(display_name).show();
|
||||
$_this.hide();
|
||||
$_this.closest('.section-name').bind('click', editSectionName);
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
function setSectionScheduleDate(e) {
|
||||
@@ -842,28 +863,36 @@ function saveSetSectionScheduleDate(e) {
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ 'id': id, 'metadata': {'start': start}})
|
||||
}).success(function () {
|
||||
var $thisSection = $('.courseware-section[data-id="' + id + '"]');
|
||||
var format = gettext('<strong>Will Release:</strong> %(date)s at $(time)s UTC');
|
||||
var willReleaseAt = interpolate(format, [input_date, input_time], true);
|
||||
$thisSection.find('.section-published-date').html(
|
||||
'<span class="published-status">' + willReleaseAt + '</span>' +
|
||||
'<a href="#" class="edit-button" ' +
|
||||
'" data-date="' + input_date +
|
||||
'" data-time="' + input_time +
|
||||
'" data-id="' + id + '">' +
|
||||
gettext('Edit') + '</a>');
|
||||
$thisSection.find('.section-published-date').animate({
|
||||
'background-color': 'rgb(182,37,104)'
|
||||
}, 300).animate({
|
||||
'background-color': '#edf1f5'
|
||||
}, 300).animate({
|
||||
'background-color': 'rgb(182,37,104)'
|
||||
}, 300).animate({
|
||||
'background-color': '#edf1f5'
|
||||
}, 300);
|
||||
data: JSON.stringify({
|
||||
'id': id,
|
||||
'metadata': {
|
||||
'start': start
|
||||
}
|
||||
})
|
||||
}).success(function() {
|
||||
var $thisSection = $('.courseware-section[data-id="' + id + '"]');
|
||||
var format = gettext('<strong>Will Release:</strong> %(date)s at %(time)s UTC');
|
||||
var willReleaseAt = interpolate(format, {
|
||||
'date': input_date,
|
||||
'time': input_time
|
||||
},
|
||||
true);
|
||||
$thisSection.find('.section-published-date').html(
|
||||
'<span class="published-status">' + willReleaseAt + '</span>' +
|
||||
'<a href="#" class="edit-button" ' +
|
||||
'" data-date="' + input_date +
|
||||
'" data-time="' + input_time +
|
||||
'" data-id="' + id + '">' + gettext('Edit') + '</a>');
|
||||
$thisSection.find('.section-published-date').animate({
|
||||
'background-color': 'rgb(182,37,104)'
|
||||
}, 300).animate({
|
||||
'background-color': '#edf1f5'
|
||||
}, 300).animate({
|
||||
'background-color': 'rgb(182,37,104)'
|
||||
}, 300).animate({
|
||||
'background-color': '#edf1f5'
|
||||
}, 300);
|
||||
|
||||
hideModal();
|
||||
});
|
||||
}
|
||||
hideModal();
|
||||
});
|
||||
}
|
||||
10
cms/urls.py
10
cms/urls.py
@@ -6,7 +6,7 @@ from . import one_time_startup
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = ('',
|
||||
urlpatterns = ('', # nopep8
|
||||
url(r'^$', 'contentstore.views.howitworks', name='homepage'),
|
||||
url(r'^listing', 'contentstore.views.index', name='index'),
|
||||
url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'),
|
||||
@@ -118,17 +118,17 @@ urlpatterns += (
|
||||
|
||||
# static/proof-of-concept views
|
||||
url(r'^ux-alerts$', 'contentstore.views.ux_alerts', name='ux-alerts')
|
||||
)
|
||||
)
|
||||
|
||||
js_info_dict = {
|
||||
'domain': 'djangojs',
|
||||
'packages': ('cms',),
|
||||
}
|
||||
}
|
||||
|
||||
urlpatterns += (
|
||||
# Serve catalog of localized strings to be rendered by Javascript
|
||||
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if settings.ENABLE_JASMINE:
|
||||
@@ -140,5 +140,3 @@ urlpatterns = patterns(*urlpatterns)
|
||||
# Custom error pages
|
||||
handler404 = 'contentstore.views.render_404'
|
||||
handler500 = 'contentstore.views.render_500'
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.conf.urls import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = patterns('', # nopep8
|
||||
url(r'^$', 'heartbeat.views.heartbeat', name='heartbeat'),
|
||||
)
|
||||
|
||||
@@ -76,8 +76,9 @@ def index(request, extra_context={}, user=None):
|
||||
'''
|
||||
|
||||
# The course selection work is done in courseware.courses.
|
||||
domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
|
||||
if domain == False: # do explicit check, because domain=None is valid
|
||||
domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
|
||||
# do explicit check, because domain=None is valid
|
||||
if domain == False:
|
||||
domain = request.META.get('HTTP_HOST')
|
||||
|
||||
courses = get_courses(None, domain=domain)
|
||||
|
||||
@@ -1147,9 +1147,9 @@ def sympy_check2():
|
||||
messages = []
|
||||
for input_dict in input_list:
|
||||
correct.append('correct'
|
||||
if input_dict['ok'] else 'incorrect')
|
||||
if input_dict['ok'] else 'incorrect')
|
||||
msg = (self.clean_message_html(input_dict['msg'])
|
||||
if 'msg' in input_dict else None)
|
||||
if 'msg' in input_dict else None)
|
||||
messages.append(msg)
|
||||
|
||||
# Otherwise, we do not recognize the dictionary
|
||||
@@ -1174,7 +1174,7 @@ def sympy_check2():
|
||||
|
||||
for k in range(len(idset)):
|
||||
npoints = (self.maxpoints[idset[k]]
|
||||
if correct[k] == 'correct' else 0)
|
||||
if correct[k] == 'correct' else 0)
|
||||
correct_map.set(idset[k], correct[k], msg=messages[k],
|
||||
npoints=npoints)
|
||||
return correct_map
|
||||
@@ -1851,6 +1851,19 @@ class FormulaResponse(LoncapaResponse):
|
||||
'formularesponse: undefined variable in given=%s' % given)
|
||||
raise StudentInputError(
|
||||
"Invalid input: " + uv.message + " not permitted in answer")
|
||||
except ValueError as ve:
|
||||
if 'factorial' in ve.message:
|
||||
# This is thrown when fact() or factorial() is used in a formularesponse answer
|
||||
# that tests on negative and/or non-integer inputs
|
||||
# ve.message will be: `factorial() only accepts integral values` or `factorial() not defined for negative values`
|
||||
log.debug(
|
||||
'formularesponse: factorial function used in response that tests negative and/or non-integer inputs. given={0}'.format(given))
|
||||
raise StudentInputError(
|
||||
"factorial function not permitted in answer for this problem. Provided answer was: {0}".format(given))
|
||||
# If non-factorial related ValueError thrown, handle it the same as any other Exception
|
||||
log.debug('formularesponse: error {0} in formula'.format(ve))
|
||||
raise StudentInputError("Invalid input: Could not parse '%s' as a formula" %
|
||||
cgi.escape(given))
|
||||
except Exception as err:
|
||||
# traceback.print_exc()
|
||||
log.debug('formularesponse: error %s in formula' % err)
|
||||
@@ -1983,7 +1996,6 @@ class ImageResponse(LoncapaResponse):
|
||||
self.ielements = self.inputfields
|
||||
self.answer_ids = [ie.get('id') for ie in self.ielements]
|
||||
|
||||
|
||||
def get_score(self, student_answers):
|
||||
correct_map = CorrectMap()
|
||||
expectedset = self.get_mapped_answers()
|
||||
@@ -2052,7 +2064,7 @@ class ImageResponse(LoncapaResponse):
|
||||
rectangles (dict) - a map of inputs to the defined rectangle for that input
|
||||
regions (dict) - a map of inputs to the defined region for that input
|
||||
'''
|
||||
answers = (
|
||||
answers = (
|
||||
dict([(ie.get('id'), ie.get(
|
||||
'rectangle')) for ie in self.ielements]),
|
||||
dict([(ie.get('id'), ie.get('regions')) for ie in self.ielements]))
|
||||
@@ -2074,8 +2086,6 @@ class ImageResponse(LoncapaResponse):
|
||||
answers[ie_id] = (ie.get('rectangle'), ie.get('regions'))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -162,8 +162,7 @@ class CourseFields(object):
|
||||
discussion_blackouts = List(help="List of pairs of start/end dates for discussion blackouts", scope=Scope.settings)
|
||||
discussion_topics = Object(
|
||||
help="Map of topics names to ids",
|
||||
scope=Scope.settings,
|
||||
computed_default=lambda c: {'General': {'id': c.location.html_id()}},
|
||||
scope=Scope.settings
|
||||
)
|
||||
testcenter_info = Object(help="Dictionary of Test Center info", scope=Scope.settings)
|
||||
announcement = Date(help="Date this course is announced", scope=Scope.settings)
|
||||
@@ -234,6 +233,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
self._grading_policy = {}
|
||||
|
||||
self.set_grading_policy(self.grading_policy)
|
||||
if self.discussion_topics == {}:
|
||||
self.discussion_topics = {'General': {'id': self.location.html_id()}}
|
||||
|
||||
self.test_center_exams = []
|
||||
test_center_info = self.testcenter_info
|
||||
|
||||
@@ -40,34 +40,20 @@ class DummySystem(ImportSystem):
|
||||
)
|
||||
|
||||
|
||||
class IsNewCourseTestCase(unittest.TestCase):
|
||||
"""Make sure the property is_new works on courses"""
|
||||
def get_dummy_course(start, announcement=None, is_new=None, advertised_start=None, end=None):
|
||||
"""Get a dummy course"""
|
||||
|
||||
def setUp(self):
|
||||
# Needed for test_is_newish
|
||||
datetime_patcher = patch.object(
|
||||
xmodule.course_module, 'datetime',
|
||||
Mock(wraps=datetime.datetime)
|
||||
)
|
||||
mocked_datetime = datetime_patcher.start()
|
||||
mocked_datetime.utcnow.return_value = time_to_datetime(NOW)
|
||||
self.addCleanup(datetime_patcher.stop)
|
||||
system = DummySystem(load_error_modules=True)
|
||||
|
||||
@staticmethod
|
||||
def get_dummy_course(start, announcement=None, is_new=None, advertised_start=None, end=None):
|
||||
"""Get a dummy course"""
|
||||
def to_attrb(n, v):
|
||||
return '' if v is None else '{0}="{1}"'.format(n, v).lower()
|
||||
|
||||
system = DummySystem(load_error_modules=True)
|
||||
is_new = to_attrb('is_new', is_new)
|
||||
announcement = to_attrb('announcement', announcement)
|
||||
advertised_start = to_attrb('advertised_start', advertised_start)
|
||||
end = to_attrb('end', end)
|
||||
|
||||
def to_attrb(n, v):
|
||||
return '' if v is None else '{0}="{1}"'.format(n, v).lower()
|
||||
|
||||
is_new = to_attrb('is_new', is_new)
|
||||
announcement = to_attrb('announcement', announcement)
|
||||
advertised_start = to_attrb('advertised_start', advertised_start)
|
||||
end = to_attrb('end', end)
|
||||
|
||||
start_xml = '''
|
||||
start_xml = '''
|
||||
<course org="{org}" course="{course}"
|
||||
graceperiod="1 day" url_name="test"
|
||||
start="{start}"
|
||||
@@ -80,9 +66,23 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
</chapter>
|
||||
</course>
|
||||
'''.format(org=ORG, course=COURSE, start=start, is_new=is_new,
|
||||
announcement=announcement, advertised_start=advertised_start, end=end)
|
||||
announcement=announcement, advertised_start=advertised_start, end=end)
|
||||
|
||||
return system.process_xml(start_xml)
|
||||
return system.process_xml(start_xml)
|
||||
|
||||
|
||||
class IsNewCourseTestCase(unittest.TestCase):
|
||||
"""Make sure the property is_new works on courses"""
|
||||
|
||||
def setUp(self):
|
||||
# Needed for test_is_newish
|
||||
datetime_patcher = patch.object(
|
||||
xmodule.course_module, 'datetime',
|
||||
Mock(wraps=datetime.datetime)
|
||||
)
|
||||
mocked_datetime = datetime_patcher.start()
|
||||
mocked_datetime.utcnow.return_value = time_to_datetime(NOW)
|
||||
self.addCleanup(datetime_patcher.stop)
|
||||
|
||||
@patch('xmodule.course_module.time.gmtime')
|
||||
def test_sorting_score(self, gmtime_mock):
|
||||
@@ -120,8 +120,8 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
]
|
||||
|
||||
for a, b, assertion in dates:
|
||||
a_score = self.get_dummy_course(start=a[0], announcement=a[1], advertised_start=a[2]).sorting_score
|
||||
b_score = self.get_dummy_course(start=b[0], announcement=b[1], advertised_start=b[2]).sorting_score
|
||||
a_score = get_dummy_course(start=a[0], announcement=a[1], advertised_start=a[2]).sorting_score
|
||||
b_score = get_dummy_course(start=b[0], announcement=b[1], advertised_start=b[2]).sorting_score
|
||||
print "Comparing %s to %s" % (a, b)
|
||||
assertion(a_score, b_score)
|
||||
|
||||
@@ -138,36 +138,42 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
]
|
||||
|
||||
for s in settings:
|
||||
d = self.get_dummy_course(start=s[0], advertised_start=s[1])
|
||||
d = get_dummy_course(start=s[0], advertised_start=s[1])
|
||||
print "Checking start=%s advertised=%s" % (s[0], s[1])
|
||||
self.assertEqual(d.start_date_text, s[2])
|
||||
|
||||
def test_is_newish(self):
|
||||
descriptor = self.get_dummy_course(start='2012-12-02T12:00', is_new=True)
|
||||
descriptor = get_dummy_course(start='2012-12-02T12:00', is_new=True)
|
||||
assert(descriptor.is_newish is True)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=False)
|
||||
descriptor = get_dummy_course(start='2013-02-02T12:00', is_new=False)
|
||||
assert(descriptor.is_newish is False)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=True)
|
||||
descriptor = get_dummy_course(start='2013-02-02T12:00', is_new=True)
|
||||
assert(descriptor.is_newish is True)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2013-01-15T12:00')
|
||||
descriptor = get_dummy_course(start='2013-01-15T12:00')
|
||||
assert(descriptor.is_newish is True)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2013-03-01T12:00')
|
||||
descriptor = get_dummy_course(start='2013-03-01T12:00')
|
||||
assert(descriptor.is_newish is True)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2012-10-15T12:00')
|
||||
descriptor = get_dummy_course(start='2012-10-15T12:00')
|
||||
assert(descriptor.is_newish is False)
|
||||
|
||||
descriptor = self.get_dummy_course(start='2012-12-31T12:00')
|
||||
descriptor = get_dummy_course(start='2012-12-31T12:00')
|
||||
assert(descriptor.is_newish is True)
|
||||
|
||||
def test_end_date_text(self):
|
||||
# No end date set, returns empty string.
|
||||
d = self.get_dummy_course('2012-12-02T12:00')
|
||||
d = get_dummy_course('2012-12-02T12:00')
|
||||
self.assertEqual('', d.end_date_text)
|
||||
|
||||
d = self.get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00')
|
||||
d = get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00')
|
||||
self.assertEqual('Sep 04, 2014', d.end_date_text)
|
||||
|
||||
|
||||
class DiscussionTopicsTestCase(unittest.TestCase):
|
||||
def test_default_discussion_topics(self):
|
||||
d = get_dummy_course('2012-12-02T12:00')
|
||||
self.assertEqual({'General': {'id': 'i4x-test_org-test_course-course-test'}}, d.discussion_topics)
|
||||
|
||||
@@ -31,6 +31,14 @@ Check out the course data directories that you want to work with into the
|
||||
|
||||
rake resetdb
|
||||
|
||||
## Installing
|
||||
|
||||
To create your development environment, run the shell script in the root of
|
||||
the repo:
|
||||
|
||||
create-dev-env.sh
|
||||
|
||||
|
||||
## Starting development servers
|
||||
|
||||
Both the LMS and Studio can be started using the following shortcut tasks
|
||||
|
||||
74
install.txt
74
install.txt
@@ -1,74 +0,0 @@
|
||||
This document describes how to set up the MITx development environment
|
||||
for both Linux (Ubuntu) and MacOS (OSX Lion).
|
||||
|
||||
There is also a script "create-dev-env.sh" that automates these steps.
|
||||
|
||||
1) Make an mitx_all directory and clone the repos
|
||||
(download and install git and mercurial if you don't have them already)
|
||||
|
||||
mkdir ~/mitx_all
|
||||
cd ~/mitx_all
|
||||
git clone git@github.com:MITx/mitx.git
|
||||
hg clone ssh://hg-content@gp.mitx.mit.edu/data
|
||||
|
||||
2) Install OSX dependencies (Mac users only)
|
||||
|
||||
a) Install the brew utility if necessary
|
||||
/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/master/Library/Contributions/install_homebrew.rb)"
|
||||
|
||||
b) Install the brew package list
|
||||
cat ~/mitx_all/mitx/brew-formulas.txt | xargs brew install
|
||||
|
||||
c) Install python pip if necessary
|
||||
sudo easy_install pip
|
||||
|
||||
d) Install python virtualenv if necessary
|
||||
sudo pip install virtualenv virtualenvwrapper
|
||||
|
||||
e) Install coffee script
|
||||
curl http://npmjs.org/install.sh | sh
|
||||
npm install -g coffee-script
|
||||
|
||||
3) Install Ubuntu dependencies (Linux users only)
|
||||
|
||||
sudo apt-get install curl python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript
|
||||
|
||||
|
||||
4) Install rvm, ruby, and libraries
|
||||
|
||||
echo "export rvm_path=$HOME/mitx_all/ruby" > $HOME/.rvmrc
|
||||
curl -sL get.rvm.io | bash -s stable
|
||||
source ~/mitx_all/ruby/scripts/rvm
|
||||
rvm install 1.9.3
|
||||
gem install bundler
|
||||
cd ~/mitx_all/mitx
|
||||
bundle install
|
||||
|
||||
5) Install python libraries
|
||||
|
||||
source ~/mitx_all/python/bin/activate
|
||||
cd ~/mitx_all
|
||||
pip install -r mitx/pre-requirements.txt
|
||||
pip install -r mitx/requirements.txt
|
||||
|
||||
6) Create log and db dirs
|
||||
|
||||
mkdir ~/mitx_all/log
|
||||
mkdir ~/mitx_all/db
|
||||
|
||||
7) Start the dev server
|
||||
|
||||
To start using Django you will need
|
||||
to activate the local Python and Ruby
|
||||
environment:
|
||||
|
||||
$ source ~/mitx_all/ruby/scripts/rvm
|
||||
$ source ~/mitx_all/python/bin/activate
|
||||
|
||||
To initialize and start a local instance of Django:
|
||||
|
||||
$ cd ~/mitx_all/mitx
|
||||
$ django-admin.py syncdb --settings=envs.dev --pythonpath=.
|
||||
$ django-admin.py migrate --settings=envs.dev --pythonpath=.
|
||||
$ django-admin.py runserver --settings=envs.dev --pythonpath=.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from django.conf.urls.defaults import url, patterns
|
||||
import django_comment_client.base.views
|
||||
|
||||
urlpatterns = patterns('django_comment_client.base.views',
|
||||
|
||||
urlpatterns = patterns('django_comment_client.base.views', # nopep8
|
||||
url(r'upload$', 'upload', name='upload'),
|
||||
url(r'users/(?P<user_id>\w+)/update_moderator_status$', 'update_moderator_status', name='update_moderator_status'),
|
||||
url(r'threads/tags/autocomplete$', 'tags_autocomplete', name='tags_autocomplete'),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from django.conf.urls.defaults import url, patterns
|
||||
import django_comment_client.forum.views
|
||||
|
||||
urlpatterns = patterns('django_comment_client.forum.views',
|
||||
urlpatterns = patterns('django_comment_client.forum.views', # nopep8
|
||||
url(r'users/(?P<user_id>\w+)/followed$', 'followed_threads', name='followed_threads'),
|
||||
url(r'users/(?P<user_id>\w+)$', 'user_profile', name='user_profile'),
|
||||
url(r'^(?P<discussion_id>[\w\-.]+)/threads/(?P<thread_id>\w+)$', 'single_thread', name='single_thread'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.conf.urls.defaults import url, patterns, include
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = patterns('', # nopep8
|
||||
url(r'forum/?', include('django_comment_client.forum.urls')),
|
||||
url(r'', include('django_comment_client.base.urls')),
|
||||
)
|
||||
|
||||
@@ -4,16 +4,16 @@ namespace_regex = r"[a-zA-Z\d._-]+"
|
||||
article_slug = r'/(?P<article_path>' + namespace_regex + r'/[a-zA-Z\d_-]*)'
|
||||
namespace = r'/(?P<namespace>' + namespace_regex + r')'
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
|
||||
url(r'^view' + article_slug, 'simplewiki.views.view', name='wiki_view'),
|
||||
url(r'^view_revision/(?P<revision_number>[0-9]+)' + article_slug, 'simplewiki.views.view_revision', name='wiki_view_revision'),
|
||||
url(r'^edit' + article_slug, 'simplewiki.views.edit', name='wiki_edit'),
|
||||
url(r'^create' + article_slug, 'simplewiki.views.create', name='wiki_create'),
|
||||
url(r'^history' + article_slug + r'(?:/(?P<page>[0-9]+))?$', 'simplewiki.views.history', name='wiki_history'),
|
||||
url(r'^search_related' + article_slug, 'simplewiki.views.search_add_related', name='search_related'),
|
||||
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
|
||||
url(r'^revision_feed' + namespace + r'/(?P<page>[0-9]+)?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
|
||||
url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
|
||||
url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), # Just an alias for the search, but you usually don't submit a search term
|
||||
urlpatterns = patterns('', # nopep8
|
||||
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
|
||||
url(r'^view' + article_slug, 'simplewiki.views.view', name='wiki_view'),
|
||||
url(r'^view_revision/(?P<revision_number>[0-9]+)' + article_slug, 'simplewiki.views.view_revision', name='wiki_view_revision'),
|
||||
url(r'^edit' + article_slug, 'simplewiki.views.edit', name='wiki_edit'),
|
||||
url(r'^create' + article_slug, 'simplewiki.views.create', name='wiki_create'),
|
||||
url(r'^history' + article_slug + r'(?:/(?P<page>[0-9]+))?$', 'simplewiki.views.history', name='wiki_history'),
|
||||
url(r'^search_related' + article_slug, 'simplewiki.views.search_add_related', name='search_related'),
|
||||
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
|
||||
url(r'^revision_feed' + namespace + r'/(?P<page>[0-9]+)?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
|
||||
url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
|
||||
url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), # Just an alias for the search, but you usually don't submit a search term
|
||||
)
|
||||
|
||||
@@ -60,6 +60,14 @@ LOG_DIR = ENV_TOKENS['LOG_DIR']
|
||||
|
||||
CACHES = ENV_TOKENS['CACHES']
|
||||
|
||||
#Email overrides
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS.get('DEFAULT_FROM_EMAIL', DEFAULT_FROM_EMAIL)
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get('DEFAULT_FEEDBACK_EMAIL', DEFAULT_FEEDBACK_EMAIL)
|
||||
ADMINS = ENV_TOKENS.get('ADMINS', ADMINS)
|
||||
|
||||
#Timezone overrides
|
||||
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE)
|
||||
|
||||
for feature, value in ENV_TOKENS.get('MITX_FEATURES', {}).items():
|
||||
MITX_FEATURES[feature] = value
|
||||
|
||||
|
||||
BIN
lms/static/images/press/cbsnews_178x138.jpg
Normal file
BIN
lms/static/images/press/cbsnews_178x138.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
lms/static/images/press/nytimes_240x180.png
Normal file
BIN
lms/static/images/press/nytimes_240x180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
lms/static/images/press/wash_post_logo_178x138.jpg
Normal file
BIN
lms/static/images/press/wash_post_logo_178x138.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 120 KiB |
@@ -6,9 +6,18 @@
|
||||
<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-04-03T14:00:12-07:00</updated>
|
||||
<updated>2013-05-03T14:00:12-07:00</updated>
|
||||
<entry>
|
||||
<id>tag:www.edx.org,2012:Post/17</id>
|
||||
<id>tag:www.edx.org,2013:Post/18</id>
|
||||
<published>2013-05-02T14:00:00-07:00</published>
|
||||
<updated>2013-05-02T14:00:00-07:00</updated>
|
||||
<link type="text/html" rel="alternate" href="http://www.nytimes.com/2013/04/30/education/colleges-adapt-online-courses-to-ease-burden.html?pagewanted=all"/>
|
||||
<title>edX project at San Jose State featured in New York Times</title>
|
||||
<content type="html"><img src="${static.url('images/press/nytimes_240x180.png')}" />
|
||||
<p></p></content>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>tag:www.edx.org,2013:Post/17</id>
|
||||
<published>2012-12-19T14:00:00-07:00</published>
|
||||
<updated>2012-12-19T14:00:00-07:00</updated>
|
||||
<link type="text/html" rel="alternate" href="${reverse('press_release', args=['stanford-to-work-with-edx'])}"/>
|
||||
|
||||
@@ -190,18 +190,11 @@
|
||||
</section>
|
||||
<section class="press-links">
|
||||
<h3>edX in the News:</h3>
|
||||
<a target="_blank" href="http://www.bbc.co.uk/news/business-19661899">BBC</a>,
|
||||
<a target="_blank" href="http://www.technologyreview.com/news/506351/the-most-important-education-technology-in-200-years/">Technology Review</a>,
|
||||
<a target="_blank" href="http://tech.mit.edu/V132/N48/edxvmware.html">The Tech</a>,
|
||||
<a target="_blank" href="http://www.nytimes.com/2012/11/04/education/edlife/massive-open-online-courses-are-multiplying-at-a-rapid-pace.html">The New York Times</a>,
|
||||
<a target="_blank" href="http://www.reuters.com/article/2012/10/19/us-education-courses-online-idUSBRE89I17120121019">Reuters</a>,
|
||||
<a target="_blank" href="http://www.ft.com/intl/cms/s/2/73030f44-d4dd-11e1-9444-00144feabdc0.html#axzz2A9qvk48A">Financial Times</a>,
|
||||
<a target="_blank" href="http://campustechnology.com/articles/2012/10/25/vmware-offers-free-virtualization-software-for-edx-computer-science-students.aspx">Campus Technology</a>,
|
||||
<a target="_blank" href="http://chronicle.com/blogs/wiredcampus/san-jose-state-u-says-replacing-live-lectures-with-videos-increased-test-scores/40470">Chronicle of Higher Education</a>,
|
||||
<a target="_blank" href="http://www.timeshighereducation.co.uk/story.asp?sectioncode=26&storycode=421577&c=1">Times Higher Education</a>,
|
||||
<a target="_blank" href="http://www.bloomberg.com/news/2012-10-15/university-of-texas-joining-harvard-mit-online-venture.html">Bloomberg.com</a>,
|
||||
<a target="_blank" href="http://www.businessweek.com/news/2012-10-15/university-of-texas-joining-harvard-mit-online-venture">BusinessWeek</a>,
|
||||
<a target="_blank" href="http://news.yahoo.com/univ-texas-joins-online-course-program-edx-172202035--finance.html">Associated Press</a>
|
||||
<a target="_blank" href="http://www.nytimes.com/2013/04/30/education/adapting-to-blended-courses-and-finding-early-benefits.html?ref=education">The New York Times</a>,
|
||||
<a target="_blank" href="http://online.wsj.com/article/SB10001424127887323741004578414861572832182.html?mod=googlenews_wsj">The Wall Street Journal</a>,
|
||||
<a target="_blank" href="http://www.washingtonpost.com/local/education/stanford-to-help-build-edx-mooc-platform/2013/04/02/5b53bb3e-9bbe-11e2-9a79-eb5280c81c63_story.html">The Washington Post</a>,
|
||||
<a target="_blank" href="http://www.cbsnews.com/video/watch/?id=50143164n">CBS Television</a>,
|
||||
<a target="_blank" href="http://bostonglobe.com/2012/12/04/edx/AqnQ808q4IEcaUa8KuZuBO/story.html">The Boston Globe</a>
|
||||
<a href="${reverse('press')}" class="read-more">Read More →</a>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,106 @@
|
||||
[
|
||||
{
|
||||
"title": "Adapting to Blended Courses, and Finding Early Benefits",
|
||||
"url": "http://www.nytimes.com/2013/04/30/education/adapting-to-blended-courses-and-finding-early-benefits.html?ref=education",
|
||||
"author": "Tamar Lewin",
|
||||
"image": "nyt_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The New York Times",
|
||||
"publish_date": "April 29, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Colleges Adapt Online Courses to Ease Burden",
|
||||
"url": "http://www.nytimes.com/2013/04/30/education/colleges-adapt-online-courses-to-ease-burden.html?pagewanted=all",
|
||||
"author": "Tamar Lewin",
|
||||
"image": "nyt_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The New York Times",
|
||||
"publish_date": "April 29, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Online Education Lifts Pass Rates at University",
|
||||
"url": "http://online.wsj.com/article/SB10001424127887323741004578414861572832182.html?mod=googlenews_wsj",
|
||||
"author": "Geoffrey Fowler",
|
||||
"image": "wsj_logo_178x138.jpg",
|
||||
"deck": null,
|
||||
"publication": "The Wall Street Journal",
|
||||
"publish_date": "April 10, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Software Seen Giving Grades on Essay Tests",
|
||||
"url": "http://www.nytimes.com/2013/04/05/science/new-test-for-computers-grading-essays-at-college-level.html?pagewanted=all&_r=0",
|
||||
"author": "John Markoff",
|
||||
"image": "nyt_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The New York Times",
|
||||
"publish_date": "April 4, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Stanford to help build edX MOOC platform",
|
||||
"url": "http://www.washingtonpost.com/local/education/stanford-to-help-build-edx-mooc-platform/2013/04/02/5b53bb3e-9bbe-11e2-9a79-eb5280c81c63_story.html",
|
||||
"author": "Nick Anderson",
|
||||
"image": "wash_post_logo_178x138.jpg",
|
||||
"deck": null,
|
||||
"publication": "The Washington Post",
|
||||
"publish_date": "April 3, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Could online ed end college as we know it?",
|
||||
"url": "http://www.cbsnews.com/video/watch/?id=50143164n",
|
||||
"author": "CBS This Morning",
|
||||
"image": "cbsnews_178x138.jpg",
|
||||
"deck": null,
|
||||
"publication": "CBS Television Network",
|
||||
"publish_date": "March 19, 2013"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "The Professors’ Big Stage",
|
||||
"url": "http://www.nytimes.com/2013/03/06/opinion/friedman-the-professors-big-stage.html?_r=1&#commentsContainer",
|
||||
"author": "Thomas L. Friedman",
|
||||
"image": "nyt_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The New York Times",
|
||||
"publish_date": "March 6, 2013"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"title": "Universities Abroad Join Partnerships On the Web",
|
||||
"url": "http://www.nytimes.com/2013/02/21/education/universities-abroad-join-mooc-course-projects.html",
|
||||
"author": "Tamar Lewin",
|
||||
"image": "nyt_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The New York Times",
|
||||
"publish_date": "February 20, 2013"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"title": "Georgetown to offer free online courses",
|
||||
"url": "http://www.washingtonpost.com/local/education/georgetown-to-offer-free-online-courses/2012/12/09/365c4612-3fd3-11e2-bca3-aadc9b7e29c5_story.html",
|
||||
"author": "Nick Anderson",
|
||||
"image": "wash_post_logo_178x138.jpg",
|
||||
"deck": null,
|
||||
"publication": "The Washington Post",
|
||||
"publish_date": "December 9, 2012"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Wellesley College teams up with online provider edX",
|
||||
"url": "http://bostonglobe.com/2012/12/04/edx/AqnQ808q4IEcaUa8KuZuBO/story.html",
|
||||
"author": "Peter Schworm",
|
||||
"image": "bostonglobe_logo_178x138.jpeg",
|
||||
"deck": null,
|
||||
"publication": "The Boston Globe",
|
||||
"publish_date": "December 4, 2012"
|
||||
},
|
||||
|
||||
{
|
||||
"title": "The Year of the MOOC",
|
||||
"url": "http://www.nytimes.com/2012/11/04/education/edlife/massive-open-online-courses-are-multiplying-at-a-rapid-pace.html",
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
</%block>
|
||||
|
||||
<%block name="university_description">
|
||||
<p>EPFL is one of the two Swiss Federal Institutes of Technology. With the status of a national school since 1969, the young engineering school has grown in many dimensions, to the extent of becoming one of the most famous European institutions of science and technology. It has three core missions: training, research and technology transfer. </p>
|
||||
|
||||
<p>EPFL is located in Lausanne in Switzerland, on the shores of the largest lake in Europe, Lake Geneva and at the foot of the Alps and Mont-Blanc. Its main campus brings together over 11,000 persons, students, researchers and staff in the same magical place. Because of its dynamism and rich student community, EPFL has been able to create a special spirit imbued with curiosity and simplicity. Daily interactions amongst students, researchers and entrepreneurs on campus give rise to new scientific, technological and architectural projects.
|
||||
</p>
|
||||
<p>EPFL is the Swiss Federal Institute of Technology in Lausanne. The past decade has seen EPFL ascend to the very top of European institutions of science and technology: it is ranked #1 in Europe in the field of engineering by the Times Higher Education (based on publications and citations), Leiden Rankings, and the Academic Ranking of World Universities.</p>
|
||||
|
||||
<p>EPFL's main campus brings together 12,600 students, faculty, researchers, and staff in a high-energy, dynamic learning and research environment. It directs the Human Brain Project, an undertaking to simulate the entire human brain using supercomputers, in order to gain new insights into how it operates and to better diagnose brain disorders. The university is building Solar Impulse, a long-range solar-powered plane that aims to be the first piloted fixed-wing aircraft to circumnavigate the Earth using only solar power. EPFL was part of the Alinghi project, developing advanced racing boats that won the America's Cup multiple times. The university operates, for education and research purposes, a Tokamak nuclear fusion reactor. EPFL also houses the Musée Bolo museum and hosts several music festivals, including Balelec, that draws over 15,000 guests every year.</p>
|
||||
|
||||
<p>EPFL is a major force in entrepreneurship, with 2012 bringing in $100M in funding for ten EPFL startups. Both young spin-offs (like Typesafe and Pix4D) and companies that have long grown past the startup stage (like Logitech) actively transfer the results of EPFL's scientific innovation to industry.</p>
|
||||
|
||||
</%block>
|
||||
|
||||
${parent.body()}
|
||||
|
||||
12
lms/urls.py
12
lms/urls.py
@@ -2,7 +2,6 @@ from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from django.conf.urls.static import static
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from . import one_time_startup
|
||||
|
||||
@@ -10,10 +9,9 @@ import django.contrib.auth.views
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
if settings.DEBUG:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = ('',
|
||||
urlpatterns = ('', # nopep8
|
||||
# certificate view
|
||||
|
||||
url(r'^update_certificate$', 'certificates.views.update_certificate'),
|
||||
@@ -299,12 +297,12 @@ if settings.COURSEWARE_ENABLED:
|
||||
'courseware.views.news', name="news"),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
|
||||
include('django_comment_client.urls'))
|
||||
)
|
||||
)
|
||||
urlpatterns += (
|
||||
# This MUST be the last view in the courseware--it's a catch-all for custom tabs.
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/(?P<tab_slug>[^/]+)/$',
|
||||
'courseware.views.static_tab', name="static_tab"),
|
||||
)
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW'):
|
||||
urlpatterns += (
|
||||
@@ -346,13 +344,13 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
|
||||
url(r'^migrate/reload/(?P<reload_dir>[^/]+)/(?P<commit_id>[^/]+)$', 'lms_migration.migrate.manage_modulestores'),
|
||||
url(r'^gitreload$', 'lms_migration.migrate.gitreload'),
|
||||
url(r'^gitreload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.gitreload'),
|
||||
)
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'):
|
||||
urlpatterns += (
|
||||
url(r'^event_logs$', 'track.views.view_tracking_log'),
|
||||
url(r'^event_logs/(?P<args>.+)$', 'track.views.view_tracking_log'),
|
||||
)
|
||||
)
|
||||
|
||||
# FoldIt views
|
||||
urlpatterns += (
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
# We use `scipy` in our project, which relies on `numpy`. `pip` apparently
|
||||
# installs packages in a two-step process, where it will first try to build
|
||||
# all packages, and then try to install all packages. As a result, if we simply
|
||||
# added these packages to the top of `requirements.txt`, `pip` would try to
|
||||
# build `scipy` before `numpy` has been installed, and it would fail. By
|
||||
# separating this out into a `pre-requirements.txt` file, we can make sure
|
||||
# that `numpy` is built *and* installed before we try to build `scipy`.
|
||||
|
||||
numpy==1.6.2
|
||||
distribute>=0.6.28
|
||||
|
||||
Reference in New Issue
Block a user