Commit 8365c2fd by Arjun Singh

Merge branch 'master' into ccp0101/capa_pythonpath

parents 06824018 c3437342
import json
from django.test.client import Client
from django.test import TestCase
from mock import patch, Mock
from override_settings import override_settings
from django.conf import settings
from django.core.urlresolvers import reverse
from student.models import Registration
from django.contrib.auth.models import User
def parse_json(response):
"""Parse response, which is assumed to be json"""
return json.loads(response.content)
def user(email):
'''look up a user by email'''
return User.objects.get(email=email)
def registration(email):
'''look up registration object by email'''
return Registration.objects.get(user__email=email)
class AuthTestCase(TestCase):
"""Check that various permissions-related things work"""
def setUp(self):
self.email = 'a@b.com'
self.pw = 'xyz'
self.username = 'testuser'
def check_page_get(self, url, expected):
resp = self.client.get(url)
self.assertEqual(resp.status_code, expected)
return resp
def test_public_pages_load(self):
"""Make sure pages that don't require login load without error."""
pages = (
reverse('login'),
reverse('signup'),
)
for page in pages:
print "Checking '{0}'".format(page)
self.check_page_get(page, 200)
def test_create_account_errors(self):
# No post data -- should fail
resp = self.client.post('/create_account', {})
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['success'], False)
def _create_account(self, username, email, pw):
'''Try to create an account. No error checking'''
resp = self.client.post('/create_account', {
'username': username,
'email': email,
'password': pw,
'location' : 'home',
'language' : 'Franglish',
'name' : 'Fred Weasley',
'terms_of_service' : 'true',
'honor_code' : 'true'})
return resp
def create_account(self, username, email, pw):
'''Create the account and check that it worked'''
resp = self._create_account(username, email, pw)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['success'], True)
# Check both that the user is created, and inactive
self.assertFalse(user(self.email).is_active)
return resp
def _activate_user(self, email):
'''look up the user's activation key in the db, then hit the activate view.
No error checking'''
activation_key = registration(email).activation_key
# and now we try to activate
resp = self.client.get(reverse('activate', kwargs={'key': activation_key}))
return resp
def activate_user(self, email):
resp = self._activate_user(email)
self.assertEqual(resp.status_code, 200)
# Now make sure that the user is now actually activated
self.assertTrue(user(self.email).is_active)
def test_create_account(self):
self.create_account(self.username, self.email, self.pw)
self.activate_user(self.email)
def _login(self, email, pw):
'''Login. View should always return 200. The success/fail is in the
returned json'''
resp = self.client.post(reverse('login_post'),
{'email': email, 'password': pw})
self.assertEqual(resp.status_code, 200)
return resp
def login(self, email, pw):
'''Login, check that it worked.'''
resp = self._login(self.email, self.pw)
data = parse_json(resp)
self.assertTrue(data['success'])
return resp
def test_login(self):
self.create_account(self.username, self.email, self.pw)
# Not activated yet. Login should fail.
resp = self._login(self.email, self.pw)
data = parse_json(resp)
self.assertFalse(data['success'])
self.activate_user(self.email)
# Now login should work
self.login(self.email, self.pw)
def test_private_pages_auth(self):
"""Make sure pages that do require login work."""
auth_pages = (
reverse('index'),
reverse('edit_item'),
reverse('save_item'),
)
# These are pages that should just load when the user is logged in
# (no data needed)
simple_auth_pages = (
reverse('index'),
)
# need an activated user
self.test_create_account()
# Not logged in. Should redirect to login.
print 'Not logged in'
for page in auth_pages:
print "Checking '{0}'".format(page)
self.check_page_get(page, expected=302)
# Logged in should work.
self.login(self.email, self.pw)
print 'Logged in'
for page in simple_auth_pages:
print "Checking '{0}'".format(page)
self.check_page_get(page, expected=200)
def test_index_auth(self):
# not logged in. Should return a redirect.
resp = self.client.get(reverse('index'))
self.assertEqual(resp.status_code, 302)
# Logged in should work.
......@@ -2,16 +2,39 @@ from util.json_request import expect_json
import json
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django_future.csrf import ensure_csrf_cookie
from fs.osfs import OSFS
from django.core.urlresolvers import reverse
from fs.osfs import OSFS
from xmodule.modulestore import Location
from github_sync import export_to_github
from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore
# ==== Public views ==================================================
@ensure_csrf_cookie
def signup(request):
"""
Display the signup form.
"""
csrf_token = csrf(request)['csrf_token']
return render_to_response('signup.html', {'csrf': csrf_token })
@ensure_csrf_cookie
def login_page(request):
"""
Display the login form.
"""
csrf_token = csrf(request)['csrf_token']
return render_to_response('login.html', {'csrf': csrf_token })
# ==== Views for any logged-in user ==================================
@login_required
@ensure_csrf_cookie
def index(request):
courses = modulestore().get_items(['i4x', None, None, 'course', None])
......@@ -24,18 +47,34 @@ def index(request):
for course in courses]
})
# ==== Views with per-item permissions================================
def has_access(user, location):
'''Return True if user allowed to access this piece of data'''
# TODO (vshnayder): actually check perms
return user.is_active and user.is_authenticated
@login_required
@ensure_csrf_cookie
def course_index(request, org, course, name):
location = ['i4x', org, course, 'course', name]
if not has_access(request.user, location):
raise Http404 # TODO (vshnayder): better error
# TODO (cpennington): These need to be read in from the active user
course = modulestore().get_item(['i4x', org, course, 'course', name])
course = modulestore().get_item(location)
weeks = course.get_children()
return render_to_response('course_index.html', {'weeks': weeks})
@login_required
def edit_item(request):
item_id = request.GET['id']
item = modulestore().get_item(item_id)
# TODO (vshnayder): change name from id to location in coffee+html as well.
item_location = request.GET['id']
print item_location, request.GET
if not has_access(request.user, item_location):
raise Http404 # TODO (vshnayder): better error
item = modulestore().get_item(item_location)
return render_to_response('unit.html', {
'contents': item.get_html(),
'js_module': item.js_module_name(),
......@@ -44,18 +83,39 @@ def edit_item(request):
})
def user_author_string(user):
'''Get an author string for commits by this user. Format:
first last <email@email.com>.
If the first and last names are blank, uses the username instead.
Assumes that the email is not blank.
'''
f = user.first_name
l = user.last_name
if f == '' and l == '':
f = user.username
return '{first} {last} <{email}>'.format(first=f,
last=l,
email=user.email)
@login_required
@expect_json
def save_item(request):
item_id = request.POST['id']
item_location = request.POST['id']
if not has_access(request.user, item_location):
raise Http404 # TODO (vshnayder): better error
data = json.loads(request.POST['data'])
modulestore().update_item(item_id, data)
modulestore().update_item(item_location, data)
# Export the course back to github
# This uses wildcarding to find the course, which requires handling
# multiple courses returned, but there should only ever be one
course_location = Location(item_id)._replace(category='course', name=None)
course_location = Location(item_location)._replace(
category='course', name=None)
courses = modulestore().get_items(course_location, depth=None)
for course in courses:
export_to_github(course, "CMS Edit")
author_string = user_author_string(request.user)
export_to_github(course, "CMS Edit", author_string)
return HttpResponse(json.dumps({}))
......@@ -38,7 +38,12 @@ def import_from_github(repo_settings):
return git_repo.head.commit.hexsha, module_store.courses[course_dir]
def export_to_github(course, commit_message):
def export_to_github(course, commit_message, author_str=None):
'''
Commit any changes to the specified course with given commit message,
and push to github (if MITX_FEATURES['GITHUB_PUSH'] is True).
If author_str is specified, uses it in the commit.
'''
repo_path = settings.DATA_DIR / course.metadata.get('course_dir', course.location.course)
fs = OSFS(repo_path)
xml = course.export_to_xml(fs)
......@@ -49,8 +54,11 @@ def export_to_github(course, commit_message):
git_repo = Repo(repo_path)
if git_repo.is_dirty():
git_repo.git.add(A=True)
git_repo.git.commit(m=commit_message)
if author_str is not None:
git_repo.git.commit(m=commit_message, author=author_str)
else:
git_repo.git.commit(m=commit_message)
origin = git_repo.remotes.origin
if settings.MITX_FEATURES['GITHUB_PUSH']:
push_infos = origin.push()
......
from django.test import TestCase
from path import path
import shutil
import os
from github_sync import import_from_github, export_to_github
from git import Repo
from django.conf import settings
......@@ -13,10 +14,18 @@ from github_sync.exceptions import GithubSyncError
@override_settings(DATA_DIR=path('test_root'))
class GithubSyncTestCase(TestCase):
def cleanup(self):
shutil.rmtree(self.repo_dir, ignore_errors=True)
shutil.rmtree(self.remote_dir, ignore_errors=True)
def setUp(self):
self.working_dir = path(settings.TEST_ROOT)
self.repo_dir = self.working_dir / 'local_repo'
self.remote_dir = self.working_dir / 'remote_repo'
# make sure there's no stale data lying around
self.cleanup()
shutil.copytree('common/test/data/toy', self.remote_dir)
remote = Repo.init(self.remote_dir)
......@@ -33,8 +42,7 @@ class GithubSyncTestCase(TestCase):
})
def tearDown(self):
shutil.rmtree(self.repo_dir)
shutil.rmtree(self.remote_dir)
self.cleanup()
def test_initialize_repo(self):
"""
......
......@@ -49,4 +49,4 @@ def github_post_receive(request):
revision, course = import_from_github(repo)
export_to_github(course, repo['path'], "Changes from cms import of revision %s" % revision)
return HttpResponse('Push recieved')
return HttpResponse('Push received')
......@@ -34,6 +34,10 @@ MITX_FEATURES = {
'GITHUB_PUSH': False,
}
# needed to use lms student app
GENERATE_RANDOM_USER_CREDENTIALS = False
############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms
REPO_ROOT = PROJECT_ROOT.dirname()
......@@ -66,6 +70,10 @@ TEMPLATE_DIRS = (
MITX_ROOT_URL = ''
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/login'
LOGIN_URL = MITX_ROOT_URL + '/login'
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'django.core.context_processors.static',
......@@ -97,7 +105,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# Instead of AuthenticationMiddleware, we use a cached backed version
# Instead of AuthenticationMiddleware, we use a cache-backed version
'cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
......@@ -214,7 +222,7 @@ PIPELINE_COMPILERS = [
PIPELINE_SASS_ARGUMENTS = '-t compressed -r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
PIPELINE_CSS_COMPRESSOR = None
PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.yui.YUICompressor'
PIPELINE_JS_COMPRESSOR = None
STATICFILES_IGNORE_PATTERNS = (
"sass/*",
......@@ -239,9 +247,11 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'south',
# For CMS
'contentstore',
'student', # misleading name due to sharing with lms
# For asset pipelining
'pipeline',
......
......@@ -25,43 +25,41 @@ MODULESTORE = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "mitx.db",
'NAME': ENV_ROOT / "db" / "cms.db",
}
}
REPO_ROOT = ENV_ROOT / "content"
REPOS = {
'edx4edx': {
'path': REPO_ROOT / "edx4edx",
'path': DATA_DIR / "edx4edx",
'org': 'edx',
'course': 'edx4edx',
'branch': 'for_cms',
'origin': 'git@github.com:MITx/edx4edx.git',
},
'6002x-fall-2012': {
'path': REPO_ROOT / '6002x-fall-2012',
'path': DATA_DIR / '6002x-fall-2012',
'org': 'mit.edu',
'course': '6.002x',
'branch': 'for_cms',
'origin': 'git@github.com:MITx/6002x-fall-2012.git',
},
'6.00x': {
'path': REPO_ROOT / '6.00x',
'path': DATA_DIR / '6.00x',
'org': 'mit.edu',
'course': '6.00x',
'branch': 'for_cms',
'origin': 'git@github.com:MITx/6.00x.git',
},
'7.00x': {
'path': REPO_ROOT / '7.00x',
'path': DATA_DIR / '7.00x',
'org': 'mit.edu',
'course': '7.00x',
'branch': 'for_cms',
'origin': 'git@github.com:MITx/7.00x.git',
},
'3.091x': {
'path': REPO_ROOT / '3.091x',
'path': DATA_DIR / '3.091x',
'org': 'mit.edu',
'course': '3.091x',
'branch': 'for_cms',
......
......@@ -9,6 +9,8 @@ sessions. Assumes structure:
"""
from .common import *
import os
from path import path
# Nose Test Runner
INSTALLED_APPS += ('django_nose',)
......@@ -17,7 +19,11 @@ for app in os.listdir(PROJECT_ROOT / 'djangoapps'):
NOSE_ARGS += ['--cover-package', app]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
TEST_ROOT = 'test_root'
TEST_ROOT = path('test_root')
# Want static files in the same dir for running on jenkins.
STATIC_ROOT = TEST_ROOT / "staticfiles"
MODULESTORE = {
'default': {
......@@ -34,7 +40,7 @@ MODULESTORE = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "mitx.db",
'NAME': ENV_ROOT / "db" / "cms.db",
}
}
......
......@@ -80,6 +80,6 @@ $(document).ready(function(){
$('section.problem-edit').show();
return false;
});
});
......@@ -6,50 +6,50 @@
.videosequence a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/videosequence.png');
background-image: url('../img/content-types/videosequence.png');
}
.video a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/video.png');
background-image: url('../img/content-types/video.png');
}
.problemset a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/problemset.png');
background-image: url('../img/content-types/problemset.png');
}
.problem a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/problem.png');
background-image: url('../img/content-types/problem.png');
}
.lab a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/lab.png');
background-image: url('../img/content-types/lab.png');
}
.tab a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/lab.png');
background-image: url('../img/content-types/lab.png');
}
.html a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/html.png');
background-image: url('../img/content-types/html.png');
}
.vertical a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/vertical.png');
background-image: url('../img/content-types/vertical.png');
}
.sequential a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/sequential.png');
background-image: url('../img/content-types/sequential.png');
}
.chapter a:first-child {
@extend .content-type;
background-image: url('/static/img/content-types/chapter.png');
background-image: url('../img/content-types/chapter.png');
}
<%inherit file="base.html" />
<%block name="content">
<section class="tos">
<div>
<section class="activation">
<h1>Account already active!</h1>
<p> This account has already been activated. <a href="/login">Log in here</a>.</p>
</div>
</section>
</%block>
\ No newline at end of file
<%inherit file="base.html" />
<%block name="content">
<section class="tos">
<div>
<h1>Activation Complete!</h1>
<p>Thanks for activating your account. <a href="/login">Log in here</a>.</p>
</div>
</section>
</%block>
\ No newline at end of file
<%inherit file="base.html" />
<%block name="content">
<section class="tos">
<div>
<h1>Activation Invalid</h1>
<p>Something went wrong. Check to make sure the URL you went to was
correct -- e-mail programs will sometimes split it into two
lines. If you still have issues, e-mail us to let us know what happened
at <a href="mailto:bugs@mitx.mit.edu">bugs@mitx.mit.edu</a>.</p>
<p>Or you can go back to the <a href="/">home page</a>.</p>
</div>
</section>
</%block>
\ No newline at end of file
......@@ -21,14 +21,12 @@
<%include file="widgets/header.html"/>
<%block name="content"></%block>
<script type="text/javascript" src="${static.url('js/vendor/jquery.min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js"')}></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
<%static:js group='main'/>
% else:
......@@ -40,6 +38,9 @@
<script src="${static.url('js/vendor/jquery.cookie.js')}"></script>
<script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
<script src="${static.url('js/vendor/jquery.tablednd.js')}"></script>
<%block name="content"></%block>
</body>
</html>
......
Someone, hopefully you, signed up for an account for edX's on-line
offering of "${ course_title}" using this email address. If it was
you, and you'd like to activate and use your account, copy and paste
this address into your web browser's address bar:
% if is_secure:
https://${ site }/activate/${ key }
% else:
http://edx4edx.mitx.mit.edu/activate/${ key }
% endif
If you didn't request this, you don't need to do anything; you won't
receive any more email from us. Please do not reply to this e-mail; if
you require assistance, check the help section of the edX web site.
Your account for edX's on-line ${course_title} course
<form name="login" action="login", method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
<%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Log in</%block>
% if next is not None:
<input type="hidden" name="next" value="${next}"/>
% endif
<%block name="content">
Username: <input type="text" name="username" />
Possword: <input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
<section class="main-container">
<section class="main-content">
<header>
<h3>Log in</h3>
<hr>
</header>
<form id="login_form" action="login_post" method="post">
<label>E-mail</label>
<input name="email" type="email" placeholder="E-mail">
<label>Password</label>
<input name="password" type="password" placeholder="Password">
<label class="remember-me">
<input name="remember" type="checkbox">
Remember me
</label>
<div class="submit">
<input name="submit" type="submit" value="Submit">
</div>
</form>
<section class="login-extra">
<p>
<span>Not enrolled? <a href="${reverse('signup')}">Sign up.</a></span>
<a href="#" class="pwd-reset">Forgot password?</a>
</p>
</section>
</section>
</section>
<script type="text/javascript">
(function() {
function getCookie(name) {
return $.cookie(name);
}
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
});
}
$('form#login_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#login_form').serialize();
postJSON('/login_post',
submit_data,
function(json) {
if(json.success) {
location.href="${reverse('index')}";
} else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">Email or password is incorrect.</div>');
} else {
$('#login_error').stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000);
}
}
);
});
})(this)
</script>
</%block>
<h1>Check your email</h1>
<p>An activation link has been sent to ${ email }, along with
instructions for activating your account.</p>
<%inherit file="base.html" />
<%block name="title">Sign up</%block>
<%block name="content">
<section class="main-container">
<section class="main-content">
<header>
<h3>Sign Up for edX</h3>
<hr>
</header>
<div id="enroll">
<form id="enroll_form" method="post">
<div id="enroll_error" name="enroll_error"></div>
<label>E-mail</label>
<input name="email" type="email" placeholder="E-mail">
<label>Password</label>
<input name="password" type="password" placeholder="Password">
<label>Public Username</label>
<input name="username" type="text" placeholder="Public Username">
<label>Full Name</label>
<input name="name" type="text" placeholder="Full Name">
<label>Your Location</label>
<input name="location" type="text" placeholder="Your Location">
<label>Preferred Language</label>
<input name="language" type="text" placeholder="Preferred Language">
<label class="terms-of-service">
<input name="terms_of_service" type="checkbox" value="true">
I agree to the
<a href="#">Terms of Service</a>
</label>
<!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
<div class="submit">
<input name="submit" type="submit" value="Create My Account">
</div>
</form>
<section class="login-extra">
<p>
<span>Already have an account? <a href="#">Login.</a></span>
</p>
</section>
</div>
<script type="text/javascript">
(function() {
function getCookie(name) {
return $.cookie(name);
}
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
});
}
$('form#enroll_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#enroll_form').serialize();
postJSON('/create_account',
submit_data,
function(json) {
if(json.success) {
$('#enroll').html(json.value);
} else {
$('#enroll_error').html(json.value).stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000);
}
}
);
});
})(this)
</script>
</section>
</section>
</%block>
<%! from django.core.urlresolvers import reverse %>
<header>
<nav>
<h2><a href="/">6.002x circuits and electronics</a></h2>
<h2><a href="/">edX CMS: TODO:-course-name-here</a></h2>
<ul>
<li>
<a href="#" class="new-module wip">New Module</a>
......@@ -13,6 +14,12 @@
<ul class="user-nav">
<li><a href="#" class="wip">Tasks</a></li>
<li><a href="#" class="wip">Settings</a></li>
% if user.is_authenticated():
<li><a href="${reverse('logout')}">Log out</a></li>
% else:
<li><a href="${reverse('login')}">Log in</a></li>
% endif
</ul>
</nav>
</header>
from django.conf import settings
from django.conf.urls.defaults import patterns, include, url
import django.contrib.auth.views
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
......@@ -9,10 +11,27 @@ urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'),
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$', 'contentstore.views.course_index', name='course_index'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
'contentstore.views.course_index', name='course_index'),
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
)
# User creation and updating views
urlpatterns += (
url(r'^signup$', 'contentstore.views.signup', name='signup'),
url(r'^create_account$', 'student.views.create_account'),
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name='activate'),
# form page
url(r'^login$', 'contentstore.views.login_page', name='login'),
# ajax view that actually does the work
url(r'^login_post$', 'student.views.login_user', name='login_post'),
url(r'^logout$', 'student.views.logout_user', name='logout'),
)
if settings.DEBUG:
## Jasmine
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
......
......@@ -143,15 +143,15 @@ def create_account(request, post_override=None):
# Confirm we have a properly formed request
for a in ['username', 'email', 'password', 'location', 'language', 'name']:
if a not in post_vars:
js['value']="Error (401 {field}). E-mail us.".format(field=a)
js['value'] = "Error (401 {field}). E-mail us.".format(field=a)
return HttpResponse(json.dumps(js))
if post_vars['honor_code']!=u'true':
if post_vars.get('honor_code', 'false') != u'true':
js['value']="To enroll, you must follow the honor code.".format(field=a)
return HttpResponse(json.dumps(js))
if post_vars['terms_of_service']!=u'true':
if post_vars.get('terms_of_service', 'false') != u'true':
js['value']="You must accept the terms of service.".format(field=a)
return HttpResponse(json.dumps(js))
......@@ -161,7 +161,7 @@ def create_account(request, post_override=None):
# this is a good idea
# TODO: Check password is sane
for a in ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']:
if len(post_vars[a])<2:
if len(post_vars[a]) < 2:
error_str = {'username' : 'Username of length 2 or greater',
'email' : 'Properly formatted e-mail',
'name' : 'Your legal name ',
......@@ -183,25 +183,23 @@ def create_account(request, post_override=None):
js['value']="Username should only consist of A-Z and 0-9.".format(field=a)
return HttpResponse(json.dumps(js))
u=User(username=post_vars['username'],
email=post_vars['email'],
is_active=False)
u = User(username=post_vars['username'],
email=post_vars['email'],
is_active=False)
u.set_password(post_vars['password'])
r=Registration()
r = Registration()
# TODO: Rearrange so that if part of the process fails, the whole process fails.
# Right now, we can have e.g. no registration e-mail sent out and a zombie account
try:
u.save()
except IntegrityError:
# Figure out the cause of the integrity error
if len(User.objects.filter(username=post_vars['username']))>0:
js['value']="An account with this username already exists."
if len(User.objects.filter(username=post_vars['username'])) > 0:
js['value'] = "An account with this username already exists."
return HttpResponse(json.dumps(js))
if len(User.objects.filter(email=post_vars['email']))>0:
js['value']="An account with this e-mail already exists."
if len(User.objects.filter(email=post_vars['email'])) > 0:
js['value'] = "An account with this e-mail already exists."
return HttpResponse(json.dumps(js))
raise
......@@ -209,36 +207,37 @@ def create_account(request, post_override=None):
r.register(u)
up = UserProfile(user=u)
up.name=post_vars['name']
up.language=post_vars['language']
up.location=post_vars['location']
up.name = post_vars['name']
up.language = post_vars['language']
up.location = post_vars['location']
up.save()
d={'name':post_vars['name'],
'key':r.activation_key,
'course_title' : settings.COURSE_TITLE,
}
# TODO (vshnayder): the LMS should probably allow signups without a particular course too
d = {'name': post_vars['name'],
'key': r.activation_key,
'course_title': getattr(settings, 'COURSE_TITLE', ''),
}
subject = render_to_string('emails/activation_email_subject.txt',d)
subject = render_to_string('emails/activation_email_subject.txt', d)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt',d)
message = render_to_string('emails/activation_email.txt', d)
try:
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-'*80 + '\n\n' + message
message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-' * 80 + '\n\n' + message
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
res = u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except:
log.exception(sys.exc_info())
js['value']='Could not send activation e-mail.'
js['value'] = 'Could not send activation e-mail.'
return HttpResponse(json.dumps(js))
js={'success':True,
'value':render_to_string('registration/reg_complete.html', {'email':post_vars['email'],
'csrf':csrf(request)['csrf_token']})}
js={'success': True,
'value': render_to_string('registration/reg_complete.html', {'email': post_vars['email'],
'csrf': csrf(request)['csrf_token']})}
return HttpResponse(json.dumps(js), mimetype="application/json")
def create_random_account(create_account_function):
......
......@@ -180,6 +180,15 @@ class LoncapaProblem(object):
return {'score': correct,
'total': self.get_max_score()}
def update_score(self, score_msg):
newcmap = CorrectMap()
for responder in self.responders.values():
if hasattr(responder,'update_score'): # Is this the best way to implement 'update_score' for CodeResponse?
results = responder.update_score(score_msg)
newcmap.update(results)
self.correct_map = newcmap
return newcmap
def grade_answers(self, answers):
'''
Grade student responses. Called by capa_module.check_problem.
......
......@@ -18,6 +18,7 @@ import re
import requests
import traceback
import abc
import time
# specific library imports
from calc import evaluator, UndefinedVariable
......@@ -693,6 +694,124 @@ class SymbolicResponse(CustomResponse):
#-----------------------------------------------------------------------------
class CodeResponse(LoncapaResponse):
'''
Grade student code using an external server
'''
response_tag = 'coderesponse'
allowed_inputfields = ['textline', 'textbox']
def setup_response(self):
xml = self.xml
self.url = xml.get('url') or "http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/" # FIXME -- hardcoded url
answer = xml.find('answer')
if answer is not None:
answer_src = answer.get('src')
if answer_src is not None:
self.code = self.system.filesystem.open('src/'+answer_src).read()
else:
self.code = answer.text
else: # no <answer> stanza; get code from <script>
self.code = self.context['script_code']
if not self.code:
msg = '%s: Missing answer script code for externalresponse' % unicode(self)
msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
raise LoncapaProblemError(msg)
self.tests = xml.get('tests')
def get_score(self, student_answers):
idset = sorted(self.answer_ids)
try:
submission = [student_answers[k] for k in idset]
except Exception as err:
log.error('Error in CodeResponse %s: cannot get student answer for %s; student_answers=%s' % (err, self.answer_ids, student_answers))
raise Exception(err)
self.context.update({'submission': submission})
extra_payload = {'edX_student_response': json.dumps(submission)}
# Should do something -- like update the problem state -- based on the queue response
r = self._send_to_queue(extra_payload)
return CorrectMap()
def update_score(self, score_msg):
# Parse 'score_msg' as XML
try:
rxml = etree.fromstring(score_msg)
except Exception as err:
msg = 'Error in CodeResponse %s: cannot parse response from xworker r.text=%s' % (err, score_msg)
raise Exception(err)
# The following process is lifted directly from ExternalResponse
idset = sorted(self.answer_ids)
cmap = CorrectMap()
ad = rxml.find('awarddetail').text
admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses
'WRONG_FORMAT': 'incorrect',
}
self.context['correct'] = ['correct']
if ad in admap:
self.context['correct'][0] = admap[ad]
# create CorrectMap
for key in idset:
idx = idset.index(key)
msg = rxml.find('message').text.replace('&nbsp;','&#160;') if idx==0 else None
cmap.set(key, self.context['correct'][idx], msg=msg)
return cmap
# CodeResponse differentiates from ExternalResponse in the behavior of 'get_answers'. CodeResponse.get_answers
# does NOT require a queue submission, and the answer is computed (extracted from problem XML) locally.
def get_answers(self):
# Extract the CodeResponse answer from XML
penv = {}
penv['__builtins__'] = globals()['__builtins__']
try:
exec(self.code,penv,penv)
except Exception as err:
log.error('Error in CodeResponse %s: Error in problem reference code' % err)
raise Exception(err)
try:
ans = penv['answer']
except Exception as err:
log.error('Error in CodeResponse %s: Problem reference code does not define answer in <answer>...</answer>' % err)
raise Exception(err)
anshtml = '<font color="blue"><span class="code-answer"><br/><pre>%s</pre><br/></span></font>' % ans
return dict(zip(self.answer_ids,[anshtml]))
# CodeResponse._send_to_queue implements the same interface as defined for ExternalResponse's 'get_score'
def _send_to_queue(self, extra_payload):
# Prepare payload
xmlstr = etree.tostring(self.xml, pretty_print=True)
header = { 'return_url': self.system.xqueue_callback_url }
header.update({'timestamp': time.time()})
payload = {'xqueue_header': json.dumps(header), # 'xqueue_header' should eventually be derived from xqueue.queue_common.HEADER_TAG or something similar
'xml': xmlstr,
'edX_cmd': 'get_score',
'edX_tests': self.tests,
'processor': self.code,
}
payload.update(extra_payload)
# Contact queue server
try:
r = requests.post(self.url, data=payload)
except Exception as err:
msg = "Error in CodeResponse %s: cannot connect to queue server url=%s" % (err, self.url)
log.error(msg)
raise Exception(msg)
return r
#-----------------------------------------------------------------------------
class ExternalResponse(LoncapaResponse):
'''
Grade the students input using an external server.
......@@ -1072,5 +1191,5 @@ class ImageResponse(LoncapaResponse):
# TEMPORARY: List of all response subclasses
# FIXME: To be replaced by auto-registration
__all__ = [ NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse ]
__all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse ]
......@@ -275,6 +275,7 @@ class CapaModule(XModule):
'problem_reset': self.reset_problem,
'problem_save': self.save_problem,
'problem_show': self.get_answer,
'score_update': self.update_score,
}
if dispatch not in handlers:
......@@ -321,6 +322,12 @@ class CapaModule(XModule):
#TODO: Not 404
raise self.system.exception404
def update_score(self, get):
score_msg = get['response']
self.lcp.update_score(score_msg)
return dict() # No AJAX return is needed
def get_answer(self, get):
'''
For the "show answer" button.
......
......@@ -102,10 +102,25 @@ environments, defined in `cms/envs`.
- Core rendering path: Still TBD
### Static file processing
- CSS -- we use a superset of CSS called SASS. It supports nice things like includes and variables, and compiles to CSS. The compiler is called `sass`.
- javascript -- we use coffeescript, which compiles to js, and is much nicer to work with. Look for `*.coffee` files. We use _jasmine_ for testing js.
- _mako_ -- we use this for templates, and have wrapper called mitxmako that makes mako look like the django templating calls.
We use a fork of django-pipeline to make sure that the js and css always reflect the latest `*.coffee` and `*.sass` files (We're hoping to get our changes merged in the official version soon). This works differently in development and production. Test uses the production settings.
In production, the django `collectstatic` command recompiles everything and puts all the generated static files in a static/ dir. A starting point in the code is `django-pipeline/pipeline/packager.py:pack`.
In development, we don't use collectstatic, instead accessing the files in place. The auto-compilation is run via `common/djangoapps/pipeline_mako/templates/static_content.html`. Details: templates include `<%namespace name='static' file='static_content.html'/>`, then something like `<%static:css group='application'/>` to call the functions in `common/djangoapps/pipeline_mako/__init__.py`, which call the `django-pipeline` compilers.
### Other modules
- Wiki -- in `lms/djangoapps/simplewiki`. Has some markdown extentions for embedding circuits, videos, etc.
## Testing
See `testing.md`.
......
......@@ -28,7 +28,7 @@ class I4xSystem(object):
'''
def __init__(self, ajax_url, track_function,
get_module, render_template, replace_urls,
user=None, filestore=None):
user=None, filestore=None, xqueue_callback_url=None):
'''
Create a closure around the system environment.
......@@ -48,6 +48,7 @@ class I4xSystem(object):
that capa_module can use to fix up the static urls in ajax results.
'''
self.ajax_url = ajax_url
self.xqueue_callback_url = xqueue_callback_url
self.track_function = track_function
self.filestore = filestore
self.get_module = get_module
......@@ -207,6 +208,7 @@ def get_module(user, request, location, student_module_cache, position=None):
# Setup system context for module instance
ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + user.username + '/' + descriptor.location.url() + '/'
def _get_module(location):
(module, _, _, _) = get_module(user, request, location, student_module_cache, position)
......@@ -218,6 +220,7 @@ def get_module(user, request, location, student_module_cache, position=None):
system = I4xSystem(track_function=make_track_function(request),
render_template=render_to_string,
ajax_url=ajax_url,
xqueue_callback_url=xqueue_callback_url,
# TODO (cpennington): Figure out how to share info between systems
filestore=descriptor.system.resources_fs,
get_module=_get_module,
......@@ -321,6 +324,53 @@ def add_histogram(module):
module.get_html = get_html
return module
# THK: TEMPORARY BYPASS OF AUTH!
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
@csrf_exempt
def xqueue_callback(request, username, id, dispatch):
# Parse xqueue response
get = request.POST.copy()
try:
header = json.loads(get.pop('xqueue_header')[0]) # 'dict'
except Exception as err:
msg = "Error in xqueue_callback %s: Invalid return format" % err
raise Exception(msg)
# Should proceed only when the request timestamp is more recent than problem timestamp
timestamp = header['timestamp']
# Retrieve target StudentModule
user = User.objects.get(username=username)
student_module_cache = StudentModuleCache(user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
if instance_module is None:
log.debug("Couldn't find module '%s' for user '%s'",
id, request.user)
raise Http404
oldgrade = instance_module.grade
old_instance_state = instance_module.state
# We go through the "AJAX" path
# So far, the only dispatch from xqueue will be 'score_update'
try:
ajax_return = instance.handle_ajax(dispatch, get) # Can ignore the "ajax" return in 'xqueue_callback'
except:
log.exception("error processing ajax call")
raise
# Save state back to database
instance_module.state = instance.get_instance_state()
if instance.get_score():
instance_module.grade = instance.get_score()['score']
if instance_module.grade != oldgrade or instance_module.state != old_instance_state:
instance_module.save()
return HttpResponse("")
def modx_dispatch(request, dispatch=None, id=None):
''' Generic view for extensions. This is where AJAX calls go.
......@@ -339,7 +389,7 @@ def modx_dispatch(request, dispatch=None, id=None):
student_module_cache = StudentModuleCache(request.user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
if instance_module is None:
log.debug("Couldn't find module '%s' for user '%s'",
id, request.user)
......
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
......@@ -10,6 +10,7 @@ sessions. Assumes structure:
from .common import *
from .logsettings import get_logger_config
import os
from path import path
INSTALLED_APPS = [
app
......@@ -28,6 +29,9 @@ TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
# Local Directories
TEST_ROOT = path("test_root")
# Want static files in the same dir for running on jenkins.
STATIC_ROOT = TEST_ROOT / "staticfiles"
COURSES_ROOT = TEST_ROOT / "data"
DATA_DIR = COURSES_ROOT
MAKO_TEMPLATES['course'] = [DATA_DIR]
......@@ -77,9 +81,24 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
############################ FILE UPLOADS (ASKBOT) #############################
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = PROJECT_ROOT / "uploads"
MEDIA_ROOT = TEST_ROOT / "uploads"
MEDIA_URL = "/static/uploads/"
STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
new_staticfiles_dirs = []
# Strip out any static files that aren't in the repository root
# so that the tests can run with only the mitx directory checked out
for static_dir in STATICFILES_DIRS:
# Handle both tuples and non-tuple directory definitions
try:
_, data_dir = static_dir
except ValueError:
data_dir = static_dir
if data_dir.startswith(REPO_ROOT):
new_staticfiles_dirs.append(static_dir)
STATICFILES_DIRS = new_staticfiles_dirs
FILE_UPLOAD_TEMP_DIR = PROJECT_ROOT / "uploads"
FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.MemoryFileUploadHandler',
......
......@@ -18,6 +18,8 @@
$(this).click(function(e) {
$(".modal").hide();
var modal_id = $(this).attr("href");
$("#lean_overlay").click(function() {
......
......@@ -51,6 +51,7 @@
}
.photo {
@include box-sizing(border-box);
background: rgb(255,255,255);
border: 1px solid rgb(210,210,210);
padding: 1px;
......@@ -64,6 +65,11 @@
}
}
> article {
float: left;
width: flex-grid(8);
}
&.left {
.photo {
float: left;
......
......@@ -96,70 +96,6 @@
}
}
}
.social-sharing {
@include box-sizing(border-box);
float: left;
height: 44px;
position: relative;
text-align: center;
width: flex-grid(6);
&:hover {
.sharing-message {
opacity: 1;
top: 56px;
}
}
.sharing-message {
@include background-image(linear-gradient(-90deg, rgba(0,0,0, 0.9) 0%,
rgba(0,0,0, 0.7) 100%));
border: 1px solid rgba(0,0,0, 0.5);
@include border-radius(4px);
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
@include box-sizing(border-box);
color: rgb(255,255,255);
float: right;
font-family: $serif;
font-size: 0.9em;
font-style: italic;
left: 50%;
margin-left: -110px;
opacity: 0;
padding: 6px 10px;
position: absolute;
text-align: center;
@include transition(all, 0.15s, ease-out);
top: 65px;
width: 220px;
&:hover {
opacity: 0;
}
}
.share {
height: 44px;
@include inline-block;
margin-right: 10px;
opacity: 0.5;
@include transition(all, 0.15s, linear);
width: 44px;
&:hover {
opacity: 1;
}
img {
width: 100%;
}
&:last-child {
margin-right: 0px;
}
}
}
}
.media {
......@@ -307,12 +243,12 @@
border: 1px solid rgb(200,200,200);
border-top: none;
float: left;
padding: 30px 20px;
padding: 20px 20px 30px;
width: flex-grid(4);
header {
margin-bottom: 30px;
padding-bottom: 26px;
padding-bottom: 20px;
position: relative;
text-align: center;
......@@ -324,6 +260,7 @@
position: absolute;
bottom: 0px;
width: 100%;
z-index: 1;
}
a.university-name {
......@@ -341,6 +278,73 @@
color: $lighter-base-font-color;
}
}
.social-sharing {
@include box-sizing(border-box);
float: left;
height: 44px;
position: relative;
text-align: center;
width: flex-grid(12);
z-index: 2;
float: none;
&:hover {
.sharing-message {
opacity: 1;
top: 56px;
}
}
.sharing-message {
@include background-image(linear-gradient(-90deg, rgba(0,0,0, 0.9) 0%,
rgba(0,0,0, 0.7) 100%));
border: 1px solid rgba(0,0,0, 0.5);
@include border-radius(4px);
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
@include box-sizing(border-box);
color: rgb(255,255,255);
float: right;
font-family: $serif;
font-size: 0.9em;
font-style: italic;
left: 50%;
margin-left: -110px;
opacity: 0;
padding: 6px 10px;
position: absolute;
text-align: center;
@include transition(all, 0.15s, ease-out);
top: 65px;
width: 220px;
&:hover {
opacity: 0;
}
}
.share {
height: 44px;
@include inline-block;
margin-right: 10px;
opacity: 0.5;
@include transition(all, 0.15s, linear);
width: 44px;
&:hover {
opacity: 1;
}
img {
width: 100%;
}
&:last-child {
margin-right: 0px;
}
}
}
}
.important-dates {
......
......@@ -40,15 +40,15 @@
.title {
@include inline-block;
margin-right: 50px;
padding-right: 50px;
//margin-right: 50px;
//padding-right: 50px;
position: relative;
text-align: left;
vertical-align: middle;
&::before {
@extend .faded-vertical-divider;
content: "";
//content: "";
display: block;
height: 170px;
position: absolute;
......@@ -99,66 +99,138 @@
}
}
.social-sharing {
.secondary-actions {
@include box-sizing(border-box);
@include clearfix;
float: left;
height: 44px;
position: relative;
text-align: center;
height: 47px;
width: flex-grid(6);
&:hover {
.sharing-message {
opacity: 1;
top: 56px;
}
}
.sharing-message {
@include background-image(linear-gradient(-90deg, rgba(0,0,0, 0.9) 0%,
rgba(0,0,0, 0.7) 100%));
border: 1px solid rgba(0,0,0, 0.5);
@include border-radius(4px);
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
a.intro-video {
background: rgb(245,245,245);
@include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(235,235,235)));
border: 1px solid rgb(200,200,200);
@include border-radius(30px);
@include box-sizing(border-box);
color: rgb(255,255,255);
float: right;
font-family: $serif;
font-size: 0.9em;
font-style: italic;
left: 50%;
margin-left: -110px;
opacity: 0;
padding: 6px 10px;
position: absolute;
text-align: center;
@include transition(all, 0.15s, ease-out);
top: 65px;
width: 220px;
@include box-shadow(inset 0 -1px 0 0 rgba(255,255,255, 0.8), inset 0 1px 0 0 rgba(255,255,255, 0.8));
@include clearfix;
display: block;
float: left;
height: 100%;
overflow: hidden;
text-align: middle;
width: flex-grid(6);
&:hover {
opacity: 0;
text-decoration: none;
p {
color: $base-font-color;
}
.video {
opacity: 1;
}
}
}
.share {
.video {
@include background-image(url('/static/images/shot-2-large.jpg'));
background-size: cover;
border-right: 1px solid rgb(200,200,200);
@include border-left-radius(30px);
@include box-shadow(1px 0 0 0 rgba(255,255,255, 0.6), inset 1px 0 0 0 rgba(255,255,255, 0.8), inset 0 0 0 1px rgba(255,255,255, 0.7));
float: left;
height: 100%;
opacity: 0.8;
position: relative;
@include transition(all, 0.15s, linear);
width: 60px;
vertical-align: middle;
.play {
background: rgba(255,255,255, 0.6);
height: 31px;
margin-left: -13px;
margin-top: -15px;
left: 50%;
position: absolute;
top: 50%;
width: 31px;
}
}
p {
color: $lighter-base-font-color;
font-style: italic;
padding-top: 10px;
text-align: center;
text-shadow: 0 1px rgba(255,255,255, 0.6);
@include transition(all, 0.15s, linear);
vertical-align: middle;
}
}
.social-sharing {
@include box-sizing(border-box);
float: left;
height: 44px;
@include inline-block;
margin-right: 10px;
opacity: 0.5;
@include transition(all, 0.15s, linear);
width: 44px;
margin-right: flex-gutter();
position: relative;
text-align: center;
width: flex-grid(6);
&:hover {
opacity: 1;
.sharing-message {
opacity: 1;
top: 56px;
}
}
img {
width: 100%;
.sharing-message {
@include background-image(linear-gradient(-90deg, rgba(0,0,0, 0.9) 0%,
rgba(0,0,0, 0.7) 100%));
border: 1px solid rgba(0,0,0, 0.5);
@include border-radius(4px);
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
@include box-sizing(border-box);
color: rgb(255,255,255);
float: right;
font-family: $serif;
font-size: 0.9em;
font-style: italic;
left: 50%;
margin-left: -110px;
opacity: 0;
padding: 6px 10px;
position: absolute;
text-align: center;
@include transition(all, 0.15s, ease-out);
top: 65px;
width: 220px;
&:hover {
opacity: 0;
}
}
&:last-child {
margin-right: 0px;
.share {
height: 44px;
@include inline-block;
margin-right: 10px;
opacity: 0.5;
@include transition(all, 0.15s, linear);
width: 44px;
&:hover {
opacity: 1;
}
img {
width: 100%;
}
&:last-child {
margin-right: 0px;
}
}
}
}
......@@ -168,15 +240,18 @@
background: #fff;
border: 1px solid rgb(200,200,200);
@include box-sizing(border-box);
float: left;
@include inline-block;
padding: 1px;
position: relative;
vertical-align: middle;
width: 210px;
//width: 210px;
width: flex-grid(3);
z-index: 2;
.hero {
height: 125px;
//height: 125px;
height: 100%;
overflow: hidden;
position: relative;
......
@import 'bourbon/bourbon';
@import 'reset';
@import 'font_face';
@import 'base';
@import 'base_mixins';
@import 'base_extends';
@import 'base_animations';
@import 'base_styles/reset';
@import 'base_styles/font_face';
@import 'base_styles/base';
@import 'base_styles/base_mixins';
@import 'base_styles/base_extends';
@import 'base_styles/base_animations';
@import 'sass_old/base/variables';
//@import 'sass_old/base/base';
@import 'sass_old/base/extends';
//@import 'sass_old/base/font-face';
@import 'sass_old/base/functions';
//@import 'sass_old/base/reset';
@import 'shared_forms';
@import 'shared_footer';
@import 'shared_header';
@import 'shared_list_of_courses';
@import 'shared_course_filter';
@import 'shared_modal';
@import 'shared_styles/shared_forms';
@import 'shared_styles/shared_footer';
@import 'shared_styles/shared_header';
@import 'shared_styles/shared_list_of_courses';
@import 'shared_styles/shared_course_filter';
@import 'shared_styles/shared_modal';
@import 'index';
@import 'home';
@import 'dashboard';
@import 'course';
@import 'find_courses';
@import 'course_info';
@import 'course_object';
@import 'courses';
@import 'course_about';
@import 'jobs';
@import 'about';
@import 'about_pages';
@import 'press_release';
@import 'sass_old/courseware/courseware';
......@@ -34,3 +31,4 @@
@import 'sass_old/courseware/sidebar';
@import 'sass_old/courseware/video';
@import 'sass_old/courseware/amplifier';
@import 'sass_old/courseware/problems';
......@@ -20,6 +20,7 @@ html, body {
font-family: $sans-serif;
font-size: 1em;
line-height: 1em;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
......@@ -89,7 +90,6 @@ a:link, a:visited {
}
}
.content-wrapper {
background: rgb(255,255,255);
margin: 0 auto 0;
......
......@@ -207,7 +207,6 @@ h1.top-header {
font-size: 12px;
// height:46px;
line-height: 46px;
margin: (-$body-line-height) (-$body-line-height) $body-line-height;
text-shadow: 0 1px 0 #fff;
@media print {
......@@ -215,6 +214,7 @@ h1.top-header {
}
a {
line-height: 46px;
border-bottom: 0;
color: darken($cream, 80%);
......
......@@ -15,101 +15,95 @@ footer {
@include box-sizing(border-box);
max-width: 1200px;
margin: 0 auto;
padding: 20px 10px 0;
padding: 30px 10px 0;
width: flex-grid(12);
.primary-links {
.top {
border-bottom: 1px solid rgb(200,200,200);
@include clearfix;
margin-bottom: 30px;
}
.secondary-links {
padding-bottom: 10px;
text-align: left;
padding-bottom: 30px;
width: flex-grid(12);
text-align: center;
ol {
float: right;
li {
@include inline-block;
list-style: none;
padding: 0px 15px;
position: relative;
vertical-align: middle;
&::after {
@extend .faded-vertical-divider;
content: "";
display: block;
height: 30px;
right: 0px;
position: absolute;
top: -5px;
width: 1px;
}
a {
letter-spacing: 1px;
margin-right: 20px;
a:link, a:visited {
color: $lighter-base-font-color;
letter-spacing: 1px;
padding: 6px 0px;
}
}
}
}
.copyright {
float: left;
padding-top: 2px;
a.logo {
@include background-image(url('/static/images/logo.png'));
background-position: 0 -24px;
background-repeat: no-repeat;
@include inline-block;
.primary {
@include clearfix;
float: left;
height: 23px;
margin-right: 15px;
margin-top: 2px;
padding-right: 15px;
position: relative;
width: 47px;
vertical-align: middle;
&:hover {
background-position: 0 0;
}
&::after {
@extend .faded-vertical-divider;
content: "";
display: block;
height: 30px;
right: 0px;
position: absolute;
top: -2px;
width: 1px;
}
}
a.logo {
@include background-image(url('/static/images/logo.png'));
background-position: 0 -24px;
background-repeat: no-repeat;
@include inline-block;
height: 22px;
margin-right: 15px;
margin-top: 2px;
padding-right: 15px;
position: relative;
width: 47px;
vertical-align: middle;
&:hover {
background-position: 0 0;
}
p {
color: $lighter-base-font-color;
font-style: italic;
@include inline-block;
margin: 0 auto;
padding-top: 1px;
text-align: center;
vertical-align: middle;
&::after {
@extend .faded-vertical-divider;
content: "";
display: block;
height: 30px;
right: 0px;
position: absolute;
top: -3px;
width: 1px;
}
}
a {
color: $lighter-base-font-color;
font-style: italic;
margin-left: 5px;
@include inline-block;
letter-spacing: 1px;
margin-right: 20px;
padding-top: 2px;
vertical-align: middle;
&:hover {
color: $base-font-color;
text-decoration: none;
}
}
}
}
ol {
float: right;
li {
@include inline-block;
list-style: none;
padding: 0px 15px;
position: relative;
vertical-align: middle;
&::after {
@extend .faded-vertical-divider;
content: "";
display: block;
height: 30px;
right: 0px;
position: absolute;
top: -5px;
width: 1px;
}
a:link, a:visited {
color: $lighter-base-font-color;
letter-spacing: 1px;
padding: 6px 0px;
}
.social {
float: right;
&.social {
border: none;
......@@ -128,5 +122,60 @@ footer {
}
}
}
.bottom {
@include clearfix;
opacity: 0.8;
padding: 10px 0px 30px;
@include transition(all, 0.15s, linear);
width: flex-grid(12);
&:hover {
opacity: 1;
}
.copyright {
float: left;
p {
color: $lighter-base-font-color;
font-style: italic;
@include inline-block;
margin: 0 auto;
padding-top: 1px;
text-align: center;
vertical-align: middle;
a {
color: $lighter-base-font-color;
font-style: italic;
margin-left: 5px;
&:hover {
color: $blue;
}
}
}
}
.secondary {
float: right;
text-align: left;
a {
color: $lighter-base-font-color;
font-family: $serif;
font-style: italic;
letter-spacing: 1px;
line-height: 1.6em;
margin-left: 20px;
text-transform: lowercase;
&:hover {
color: $blue;
}
}
}
}
}
}
......@@ -42,5 +42,6 @@ form {
letter-spacing: 1px;
text-transform: uppercase;
vertical-align: top;
-webkit-font-smoothing: antialiased;
}
}
......@@ -19,7 +19,7 @@ header.global {
h1.logo {
float: left;
margin: 9px 15px 0px 0px;
margin: 6px 15px 0px 0px;
padding-right: 20px;
position: relative;
......@@ -30,7 +30,7 @@ header.global {
height: 50px;
position: absolute;
right: 1px;
top: -12px;
top: -8px;
width: 1px;
}
......@@ -46,12 +46,12 @@ header.global {
}
a {
@include background-image(url('/static/images/logo.png'));
@include background-image(url('/static/images/header-logo.png'));
background-position: 0 0;
background-repeat: no-repeat;
display: block;
height: 23px;
width: 47px;
height: 31px;
width: 64px;
}
}
......
......@@ -106,6 +106,19 @@
}
}
#enroll_error, #login_error {
background: rgb(253, 87, 87);
border: 1px solid rgb(202, 17, 17);
color: rgb(143, 14, 14);
display: none;
margin-bottom: 20px;
padding: 12px;
}
.activation-message {
padding: 0 40px 10px;
}
form {
margin-bottom: 12px;
padding: 0px 40px;
......
......@@ -12,14 +12,16 @@
<section class="vision">
<section class="company-mission message left">
<div class="inner-wrapper">
<div class="photo">
<img src="">
</div>
<h2>Mission: Educate 1 billion people around the world</h2>
<p>“EdX represents a unique opportunity to improve education on our own campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide,” MIT President Susan Hockfield said.</p>
<p>Harvard President Drew Faust said, “edX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.”
<div class="photo">
<img src="">
</div>
<article>
<h2>About edX</h2>
<p>EdX is a joint partnership between The Massachusetts Institute of Technology (MIT) and Harvard University to offer online learning to millions of people around the world. EdX offer Harvard and MIT classes online for free. Through this partnership, with other partners to follow, the institutions aim to extend their collective reach to build a global community of online students.</p>
<p>MIT’s Director of the Computer Science and Artificial Intelligence Laboratory Anant Agarwal serves as the first president of edX, and Harvard’s Faculty of Arts and Sciences Dean Michael D. Smith leads faculty in developing courses. Along with offering online courses, the institutions will use edX to research how students learn and how technology can facilitate teaching—both on-campus and online.</p>
<p>EdX is based on an open-source technological platform that provides interactive educational materials designed specifically for the web, and is available to anyone in the world with an internet connection.</p>
<p>Harvard and MIT have created edX open-source software and invite interested institutions to join edX with their own educational content. EdX is a Cambridge-based not-for-profit, equally owned and funded by Harvard and MIT</p>
</article>
<hr class="fade-right-hr-divider">
</section>
......@@ -27,9 +29,11 @@
<div class="photo">
<img src="">
</div>
<h2>Mission: Educate 1 billion people around the world</h2>
<p>“EdX represents a unique opportunity to improve education on our own campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide,” MIT President Susan Hockfield said.</p>
<p>Harvard President Drew Faust said, “edX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.”
<article>
<h2>Harvard University</h2>
<p>Harvard University is devoted to excellence in teaching, learning, and research, and to developing leaders in many disciplines who make a difference globally. Harvard faculty are engaged with teaching and research to push the boundaries of human knowledge. For students who are excited to investigate the biggest issues of the 21st century, Harvard offers an unparalleled student experience and a generous financial aid program, with over $160 million awarded to more than 60% of our undergraduate students. The University has twelve degree-granting Schools in addition to the Radcliffe Institute for Advanced Study, offering a truly global education.</p>
<p>Established in 1636, Harvard is the oldest institution of higher education in the United States. The University, which is based in Cambridge and Boston, Massachusetts, has an enrollment of over 20,000 degree candidates, including undergraduate, graduate, and professional students. Harvard has more than 360,000 alumni around the world.</p>
</article>
<hr class="fade-left-hr-divider">
</section>
......@@ -37,9 +41,12 @@
<div class="photo">
<img src="">
</div>
<h2>Mission: Educate 1 billion people around the world</h2>
<p>“EdX represents a unique opportunity to improve education on our own campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide,” MIT President Susan Hockfield said.</p>
<p>Harvard President Drew Faust said, “edX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.”
<article>
<h2>Massachusetts Institute of Technology</h2>
<p>The Massachusetts Institute of Technology — a coeducational, privately endowed research university founded in 1861 — is dedicated to advancing knowledge and educating students in science, technology, and other areas of scholarship that will best serve the nation and the world in the 21st century. The Institute has close to 1,000 faculty and 10,000 undergraduate and graduate students. It is organized into five Schools: Architecture and Urban Planning; Engineering; Humanities, Arts, and Social Sciences; Sloan School of Management; and Science.</p>
<p>MIT's commitment to innovation has led to a host of scientific breakthroughs and technological advances. Seventy-eight MIT alumni, faculty, researchers and staff have won Nobel Prizes.</p>
<p>Current areas of research and education include neuroscience and the study of the brain and mind, bioengineering, cancer, energy, the environment and sustainable development, information sciences and technology, new media, financial technology, and entrepreneurship.</p>
</article>
</section>
</section>
</section>
......
......@@ -9,14 +9,14 @@
<div class="logo">
<img src="${static.url('images/edx_bw.png')}" />
</div>
<h2>Explore courses from universities around the world.</h2>
<h2>Explore courses from leading universities.</h2>
</hgroup>
</div>
</header>
<section class="container">
## I'm removing this for now since we aren't using it for the fall.
<%include file="course_filter.html" />
## <%include file="course_filter.html" />
<section class="courses">
%for course in courses:
<%include file="course.html" args="course=course" />
......
Your account for edX's on-line ${course_title} course
Your account for edX
......@@ -3,36 +3,35 @@
<footer>
<nav>
<section class="primary-links">
<section class="copyright">
<section class="top">
<section class="primary">
<a href="${reverse('root')}" class="logo"></a>
<p>&copy; 2012 edX, <a href="/t/copyright.html">some rights reserved.</a></p>
<a href="${reverse('courses')}">Find Courses</a>
<a href="/t/about.html">About</a>
<a href="#">Blog</a>
<a href="/t/jobs.html">Jobs</a>
<a href="/t/contact.html">Contact</a>
</section>
<section class="social">
<a href="#"><img src="${static.url('images/linkedin.png')}" /></a>
<a href="#"><img src="${static.url('images/facebook.png')}" /></a>
<a href="#"><img src="${static.url('images/twitter.png')}" /></a>
</section>
<ol>
<li>
<a href="${reverse('courses')}">Find Courses</a>
</li>
<li>
<a href="/t/about.html">About</a>
</li>
<li>
<a href="#">Blog</a>
</li>
<li>
<a href="/t/jobs.html">Jobs</a>
</li>
<li class="social">
<a href="#"><img src="${static.url('images/linkedin.png')}" /></a>
<a href="#"><img src="${static.url('images/facebook.png')}" /></a>
<a href="#"><img src="${static.url('images/twitter.png')}" /></a>
</li>
</ol>
</section>
<section class="secondary-links">
<a href="/t/tos.html">Terms of Service</a>
<a href="/t/privacy.html">Privacy Policy</a>
<a href="/t/honor.html">Honor Code</a>
<a href="/t/help.html">Help</a>
<section class="bottom">
<section class="copyright">
<p>&copy; 2012 edX, <a href="/t/copyright.html">some rights reserved.</a></p>
</section>
<section class="secondary">
<a href="/t/tos.html">Terms of Service</a>
<a href="/t/privacy.html">Privacy Policy</a>
<a href="/t/honor.html">Honor Code</a>
<a href="/t/help.html">Help</a>
</section>
</section>
</nav>
</footer>
......@@ -7,31 +7,37 @@
<div class="outer-wrapper">
<div class="inner-wrapper">
<div class="title">
<h1>The Future of Online Education</h1>
<h1>Online education for anyone, anywhere, at anytime</h1>
<div class="main-cta">
<a href="${reverse('courses')}" class="find-courses">Find Courses</a>
<a href="#signup-modal" class="find-courses" rel="leanModal">Sign Up</a>
</div>
<div class="social-sharing">
<div class="sharing-message">Share with friends and family!</div>
<a href="#" class="share">
<img src="${static.url('images/twitter-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/facebook-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/email-sharing.png')}">
<div class="secondary-actions">
<div class="social-sharing">
<div class="sharing-message">Share with friends and family!</div>
<a href="#" class="share">
<img src="${static.url('images/twitter-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/facebook-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/email-sharing.png')}">
</a>
</div>
<a href="#video-modal" class="intro-video" rel="leanModal">
<!--
-<img src="${static.url('images/courses/space1.jpg')}" />
-->
<div class="video">
<div class="play"></div>
</div>
<p>Play intro video</p>
</a>
</div>
</div>
<a href="#video-modal" class="media" rel="leanModal">
<div class="hero">
<img src="${static.url('images/courses/space1.jpg')}" />
<div class="play-intro"></div>
</div>
</a>
</div>
</div>
</header>
......@@ -87,10 +93,10 @@
<section class="blog-posts">
<article>
<a href="#" class="post-graphics">
<img src="${static.url('images/courses/space1.jpg')}" />
<img src="${static.url('images/mongolia_post.jpeg')}" />
</a>
<div class="post-name">
<a href="">Online Classes Cut Costs, But Do They Dilute Brands?</a>
<a href="">Opening Doors For Exceptional Students: 6.002x in Mongolia.</a>
<p class="post-date">7/12/2012</p>
</div>
</article>
......
......@@ -22,6 +22,14 @@
<h2>We are currently looking for</h2>
<section class="jobs-listing">
<article id="technology-team" class="job">
<div class="inner-wrapper">
<h3>Technology Team</h3>
<p>[Looking for both back-end and front-end developers. Strong backgrounds in machine learning, education, user interaction design, big data, or social network analysis are desirable, but team members do wear many hats. Best candidate would be a masterful hacker who went and did startups after finishing their Ph.D. We should find a way to make some positions that parallel fellows, and can leverage MIT/Harvard prestige]</p>
<p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div>
</article>
<article id="edx-fellow" class="job">
<div class="inner-wrapper">
<h3>edX Fellow</h3>
......@@ -49,27 +57,20 @@
<p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div>
</article>
<article id="technology-team" class="job">
<div class="inner-wrapper">
<h3>Technology Team</h3>
<p>[Looking for both back-end and front-end developers. Strong backgrounds in machine learning, education, user interaction design, big data, or social network analysis are desirable, but team members do wear many hats. Best candidate would be a masterful hacker who went and did startups after finishing their Ph.D. We should find a way to make some positions that parallel fellows, and can leverage MIT/Harvard prestige]</p>
<p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div>
</article>
</section>
<section class="jobs-sidebar">
<h2>Positions</h2>
<nav>
<a href="#technology-team">Technology Team</a>
<a href="#edx-fellow">edX Fellow</a>
<a href="#content-engineer">Content Engineer</a>
<a href="#technology-team">Technology Team</a>
</nav>
<h2>How to Apply</h2>
<p>E-mail your resume, coverletter and any other materials to <a href="#">careers@edxonline.org</a></p>
<p>E-mail your resume, coverletter and any other materials to <a href="#">careers@edx.org</a></p>
<h2>Our Location</h2>
<p>11 Cambridge Center, Cambridge MA USA</p>
<p>11 Cambridge Center <br/>
Cambridge, MA 02142</p>
</section>
</section>
......
......@@ -24,7 +24,7 @@
<section class="login-extra">
<p>
<span>Not enrolled? <a href="#">Sign up.</a></span>
<span>Not enrolled? <a href="#signup-modal" class="close-login" rel="leanModal">Sign up.</a></span>
<a href="#" class="pwd-reset">Forgot password?</a>
</p>
</section>
......@@ -64,7 +64,7 @@
} else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">Email or password is incorrect.</div>');
} else {
$('#login_error').stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000);
$('#login_error').stop().css("display", "block");
}
}
);
......
......@@ -37,6 +37,7 @@
<a href="/t/about.html">About</a>
<a href="#">Blog</a>
<a href="${reverse('jobs')}">Jobs</a>
<a href="/t/contact.html">Contact</a>
<a href="#login-modal" id="login" rel="leanModal">Log In</a>
</li>
<li class="primary">
......
......@@ -18,19 +18,6 @@
<a href="${reverse('enroll', args=[course.id])}" class="register">Register</a>
</div>
<div class="social-sharing">
<div class="sharing-message">Share with friends and family!</div>
<a href="#" class="share">
<img src="${static.url('images/twitter-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/facebook-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/email-sharing.png')}">
</a>
</div>
</section>
<a href="#video-modal" class="media" rel="leanModal">
<div class="hero">
......@@ -111,7 +98,21 @@
<section class="course-sidebar">
<section class="course-summary">
<header>
<a href="#" class="university-name">${course.get_about_section("university")}</a><span>${course.get_about_section("title")}</span>
<!--
-<a href="#" class="university-name">${course.get_about_section("university")}</a><span>${course.get_about_section("title")}</span>
-->
<div class="social-sharing">
<div class="sharing-message">Share with friends and family!</div>
<a href="#" class="share">
<img src="${static.url('images/twitter-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/facebook-sharing.png')}">
</a>
<a href="#" class="share">
<img src="${static.url('images/email-sharing.png')}">
</a>
</div>
</header>
<ol class="important-dates">
......
<h1>Check your email</h1>
<p>An activation link has been sent to ${ email }, along with
<header>
<h2>Thanks For Registering!</h2>
<hr>
</header>
<p class='activation-message'>Please check your email. An activation link has been sent to <strong>${ email }</strong>, along with
instructions for activating your account.</p>
......@@ -2,12 +2,11 @@
<section id="signup-modal" class="modal signup-modal">
<div class="inner-wrapper">
<header>
<h2>Sign Up for edX</h2>
<hr>
</header>
<div id="enroll">
<header>
<h2>Sign Up for edX</h2>
<hr>
</header>
<form id="enroll_form" method="post">
<div id="enroll_error" name="enroll_error"></div>
......@@ -31,28 +30,28 @@
<label class="honor-code">
<input name="honor_code" type="checkbox" value="true">
I agree to the
<a href="#">Honor Code</a>
, sumarized below as:
<a href="/t/honor.html" target="blank">Honor Code</a>
</label>
<div class="honor-code-summary">
<ul>
<li>
<p>Complete all mid-terms and final exams with only my own work.</p>
</li>
<li>
<p>Maintain only one account, and not share the username or password.</p>
</li>
<li>
<p>Not engage in any activity that would dishonestly improve my results, or improve or hurt those of others.</p>
</li>
<li>
<p>Not post answers to problems that are being used to assess student performance.</p>
</li>
</ul>
<hr>
</div>
<!--
-<div class="honor-code-summary">
- <ul>
- <li>
- <p>Complete all mid-terms and final exams with only my own work.</p>
- </li>
- <li>
- <p>Maintain only one account, and not share the username or password.</p>
- </li>
- <li>
- <p>Not engage in any activity that would dishonestly improve my results, or improve or hurt those of others.</p>
- </li>
- <li>
- <p>Not post answers to problems that are being used to assess student performance.</p>
- </li>
- </ul>
- <hr>
-</div>
-->
<div class="submit">
<input name="submit" type="submit" value="Create My Account">
......@@ -61,7 +60,7 @@
<section class="login-extra">
<p>
<span>Already have an account? <a href="#">Login.</a></span>
<span>Already have an account? <a href="#login-modal" class="close-signup" rel="leanModal">Login.</a></span>
</p>
</section>
......@@ -101,7 +100,7 @@
if(json.success) {
$('#enroll').html(json.value);
} else {
$('#enroll_error').html(json.value).stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000);
$('#enroll_error').html(json.value).stop().css("display", "block");
}
}
);
......
......@@ -35,13 +35,16 @@ urlpatterns = ('',
url(r'^password_reset/$', 'student.views.password_reset'),
## Obsolete Django views for password resets
## TODO: Replace with Mako-ized views
url(r'^password_change/$',django.contrib.auth.views.password_change,name='auth_password_change'),
url(r'^password_change_done/$',django.contrib.auth.views.password_change_done,name='auth_password_change_done'),
url(r'^password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',django.contrib.auth.views.password_reset_confirm,
url(r'^password_change/$', django.contrib.auth.views.password_change,
name='auth_password_change'),
url(r'^password_change_done/$', django.contrib.auth.views.password_change_done,
name='auth_password_change_done'),
url(r'^password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
django.contrib.auth.views.password_reset_confirm,
name='auth_password_reset_confirm'),
url(r'^password_reset_complete/$',django.contrib.auth.views.password_reset_complete,
url(r'^password_reset_complete/$', django.contrib.auth.views.password_reset_complete,
name='auth_password_reset_complete'),
url(r'^password_reset_done/$',django.contrib.auth.views.password_reset_done,
url(r'^password_reset_done/$', django.contrib.auth.views.password_reset_done,
name='auth_password_reset_done'),
## Feedback
url(r'^send_feedback$', 'util.views.send_feedback'),
......@@ -55,6 +58,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^masquerade/', include('masquerade.urls')),
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^xqueue/(?P<username>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'),
url(r'^change_setting$', 'student.views.change_setting'),
url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'),
url(r'^book/(?P<page>[^/]*)$', 'staticbook.views.index'),
......@@ -69,15 +73,25 @@ if settings.COURSEWARE_ENABLED:
# Multicourse related:
url(r'^courses/?$', 'courseware.views.courses', name="courses"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$', 'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book$', 'staticbook.views.index', name="book"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll$', 'student.views.enroll', name="enroll"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$', 'courseware.views.index', name="courseware"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$', 'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$',
'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book$',
'staticbook.views.index', name="book"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll$',
'student.views.enroll', name="enroll"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$',
'courseware.views.index', name="courseware"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$',
'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$',
'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
# TODO (vshnayder): there is no student.views.course_info.
# Where should this point instead? same as the info view?
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$',
'student.views.course_info', name="about_course"),
)
# Multicourse wiki
......
......@@ -74,17 +74,28 @@ end
task :pylint => "pylint_#{system}"
end
def run_tests(system, report_dir)
ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml")
ENV['NOSE_COVER_HTML_DIR'] = File.join(report_dir, "cover")
sh(django_admin(system, :test, 'test', *Dir["#{system}/djangoapps/*"].each))
end
[:lms, :cms].each do |system|
report_dir = File.join(REPORT_DIR, system.to_s)
directory report_dir
# Per System tasks
desc "Run all django tests on our djangoapps for the #{system}"
task "test_#{system}" => [report_dir, :predjango] do
ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml")
ENV['NOSE_COVER_HTML_DIR'] = File.join(report_dir, "cover")
sh(django_admin(system, :test, 'test', *Dir["#{system}/djangoapps/*"].each))
task "test_#{system}" => ["#{system}:collectstatic:test", "fasttest_#{system}"]
# Have a way to run the tests without running collectstatic -- useful when debugging without
# messing with static files.
task "fasttest_#{system}" => [report_dir, :predjango] do
run_tests(system, report_dir)
end
task :test => "test_#{system}"
desc <<-desc
......@@ -106,7 +117,7 @@ end
desc "Run collectstatic in the specified environment"
task "#{system}:collectstatic:#{env}" => :predjango do
sh("#{django_admin(system, env, 'collectstatic')}")
sh("#{django_admin(system, env, 'collectstatic', '--noinput')}")
end
end
end
......
This source diff could not be displayed because it is too large. You can view the blob instead.
body {
margin: 0;
padding: 0; }
.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, section.index-content, footer {
margin: 0;
overflow: hidden; }
div#enroll form {
display: none; }
local_repo
remote_repo
staticfiles
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