Commit bde93aea by Bridger Maxwell

Merge remote-tracking branch 'origin/master' into template_caching

Conflicts:
	lms/templates/footer.html
	lms/templates/signup_modal.html
parents 70e2f7c0 c3437342
...@@ -4,45 +4,165 @@ from django.test import TestCase ...@@ -4,45 +4,165 @@ from django.test import TestCase
from mock import patch, Mock from mock import patch, Mock
from override_settings import override_settings from override_settings import override_settings
from django.conf import 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): def parse_json(response):
"""Parse response, which is assumed to be json""" """Parse response, which is assumed to be json"""
return json.loads(response.content) return json.loads(response.content)
class AuthTestCase(TestCase):
"""Check that various permissions-related things work"""
def test_index(self): def user(email):
"""Make sure the main page loads.""" '''look up a user by email'''
resp = self.client.get('/') return User.objects.get(email=email)
self.assertEqual(resp.status_code, 200)
def test_signup_load(self): def registration(email):
"""Make sure the signup page loads.""" '''look up registration object by email'''
resp = self.client.get('/signup') return Registration.objects.get(user__email=email)
self.assertEqual(resp.status_code, 200)
class AuthTestCase(TestCase):
"""Check that various permissions-related things work"""
def test_create_account(self): 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 # No post data -- should fail
resp = self.client.post('/create_account', {}) resp = self.client.post('/create_account', {})
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['success'], False) self.assertEqual(data['success'], False)
# Should work def _create_account(self, username, email, pw):
'''Try to create an account. No error checking'''
resp = self.client.post('/create_account', { resp = self.client.post('/create_account', {
'username': 'user', 'username': username,
'email': 'a@b.com', 'email': email,
'password': 'xyz', 'password': pw,
'location' : 'home', 'location' : 'home',
'language' : 'Franglish', 'language' : 'Franglish',
'name' : 'Fred Weasley', 'name' : 'Fred Weasley',
'terms_of_service' : 'true', 'terms_of_service' : 'true',
'honor_code' : '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) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['success'], True) 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,6 +2,7 @@ from util.json_request import expect_json ...@@ -2,6 +2,7 @@ from util.json_request import expect_json
import json import json
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -13,8 +14,28 @@ from github_sync import export_to_github ...@@ -13,8 +14,28 @@ from github_sync import export_to_github
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
# ==== Public views ==================================================
@ensure_csrf_cookie @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): def index(request):
courses = modulestore().get_items(['i4x', None, None, 'course', None]) courses = modulestore().get_items(['i4x', None, None, 'course', None])
return render_to_response('index.html', { return render_to_response('index.html', {
...@@ -26,26 +47,34 @@ def index(request): ...@@ -26,26 +47,34 @@ def index(request):
for course in courses] for course in courses]
}) })
# ==== Views with per-item permissions================================
@ensure_csrf_cookie def has_access(user, location):
def signup(request): '''Return True if user allowed to access this piece of data'''
""" # TODO (vshnayder): actually check perms
Display the signup form. return user.is_active and user.is_authenticated
"""
csrf_token = csrf(request)['csrf_token']
return render_to_response('signup.html', {'csrf': csrf_token })
@login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def course_index(request, org, course, name): 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 # 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() weeks = course.get_children()
return render_to_response('course_index.html', {'weeks': weeks}) return render_to_response('course_index.html', {'weeks': weeks})
@login_required
def edit_item(request): def edit_item(request):
item_id = request.GET['id'] # TODO (vshnayder): change name from id to location in coffee+html as well.
item = modulestore().get_item(item_id) 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', { return render_to_response('unit.html', {
'contents': item.get_html(), 'contents': item.get_html(),
'js_module': item.js_module_name(), 'js_module': item.js_module_name(),
...@@ -54,18 +83,39 @@ def edit_item(request): ...@@ -54,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 @expect_json
def save_item(request): 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']) data = json.loads(request.POST['data'])
modulestore().update_item(item_id, data) modulestore().update_item(item_location, data)
# Export the course back to github # Export the course back to github
# This uses wildcarding to find the course, which requires handling # This uses wildcarding to find the course, which requires handling
# multiple courses returned, but there should only ever be one # 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) courses = modulestore().get_items(course_location, depth=None)
for course in courses: 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({})) return HttpResponse(json.dumps({}))
...@@ -38,7 +38,12 @@ def import_from_github(repo_settings): ...@@ -38,7 +38,12 @@ def import_from_github(repo_settings):
return git_repo.head.commit.hexsha, module_store.courses[course_dir] 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) repo_path = settings.DATA_DIR / course.metadata.get('course_dir', course.location.course)
fs = OSFS(repo_path) fs = OSFS(repo_path)
xml = course.export_to_xml(fs) xml = course.export_to_xml(fs)
...@@ -49,8 +54,11 @@ def export_to_github(course, commit_message): ...@@ -49,8 +54,11 @@ def export_to_github(course, commit_message):
git_repo = Repo(repo_path) git_repo = Repo(repo_path)
if git_repo.is_dirty(): if git_repo.is_dirty():
git_repo.git.add(A=True) 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 origin = git_repo.remotes.origin
if settings.MITX_FEATURES['GITHUB_PUSH']: if settings.MITX_FEATURES['GITHUB_PUSH']:
push_infos = origin.push() push_infos = origin.push()
......
...@@ -49,4 +49,4 @@ def github_post_receive(request): ...@@ -49,4 +49,4 @@ def github_post_receive(request):
revision, course = import_from_github(repo) revision, course = import_from_github(repo)
export_to_github(course, repo['path'], "Changes from cms import of revision %s" % revision) export_to_github(course, repo['path'], "Changes from cms import of revision %s" % revision)
return HttpResponse('Push recieved') return HttpResponse('Push received')
...@@ -70,6 +70,10 @@ TEMPLATE_DIRS = ( ...@@ -70,6 +70,10 @@ TEMPLATE_DIRS = (
MITX_ROOT_URL = '' MITX_ROOT_URL = ''
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/login'
LOGIN_URL = MITX_ROOT_URL + '/login'
TEMPLATE_CONTEXT_PROCESSORS = ( TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request', 'django.core.context_processors.request',
'django.core.context_processors.static', 'django.core.context_processors.static',
......
...@@ -29,39 +29,37 @@ DATABASES = { ...@@ -29,39 +29,37 @@ DATABASES = {
} }
} }
REPO_ROOT = ENV_ROOT / "content"
REPOS = { REPOS = {
'edx4edx': { 'edx4edx': {
'path': REPO_ROOT / "edx4edx", 'path': DATA_DIR / "edx4edx",
'org': 'edx', 'org': 'edx',
'course': 'edx4edx', 'course': 'edx4edx',
'branch': 'for_cms', 'branch': 'for_cms',
'origin': 'git@github.com:MITx/edx4edx.git', 'origin': 'git@github.com:MITx/edx4edx.git',
}, },
'6002x-fall-2012': { '6002x-fall-2012': {
'path': REPO_ROOT / '6002x-fall-2012', 'path': DATA_DIR / '6002x-fall-2012',
'org': 'mit.edu', 'org': 'mit.edu',
'course': '6.002x', 'course': '6.002x',
'branch': 'for_cms', 'branch': 'for_cms',
'origin': 'git@github.com:MITx/6002x-fall-2012.git', 'origin': 'git@github.com:MITx/6002x-fall-2012.git',
}, },
'6.00x': { '6.00x': {
'path': REPO_ROOT / '6.00x', 'path': DATA_DIR / '6.00x',
'org': 'mit.edu', 'org': 'mit.edu',
'course': '6.00x', 'course': '6.00x',
'branch': 'for_cms', 'branch': 'for_cms',
'origin': 'git@github.com:MITx/6.00x.git', 'origin': 'git@github.com:MITx/6.00x.git',
}, },
'7.00x': { '7.00x': {
'path': REPO_ROOT / '7.00x', 'path': DATA_DIR / '7.00x',
'org': 'mit.edu', 'org': 'mit.edu',
'course': '7.00x', 'course': '7.00x',
'branch': 'for_cms', 'branch': 'for_cms',
'origin': 'git@github.com:MITx/7.00x.git', 'origin': 'git@github.com:MITx/7.00x.git',
}, },
'3.091x': { '3.091x': {
'path': REPO_ROOT / '3.091x', 'path': DATA_DIR / '3.091x',
'org': 'mit.edu', 'org': 'mit.edu',
'course': '3.091x', 'course': '3.091x',
'branch': 'for_cms', 'branch': 'for_cms',
......
<%inherit file="marketing.html" /> <%inherit file="base.html" />
<%block name="content"> <%block name="content">
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
<section class="activation"> <section class="activation">
<h1>Account already active!</h1> <h1>Account already active!</h1>
<p> This account has already been activated. You can log in at <p> This account has already been activated. <a href="/login">Log in here</a>.</p>
the <a href="/">home page</a>.</p>
</div> </div>
</section> </section>
......
<%inherit file="marketing.html" /> <%inherit file="base.html" />
<%block name="content"> <%block name="content">
<section class="tos"> <section class="tos">
<div> <div>
<h1>Activation Complete!</h1> <h1>Activation Complete!</h1>
<p>Thanks for activating your account. You can log in at the <a href="/">home page</a>.</p> <p>Thanks for activating your account. <a href="/login">Log in here</a>.</p>
</div> </div>
</section> </section>
......
<%inherit file="marketing.html" /> <%inherit file="base.html" />
<%block name="content"> <%block name="content">
<section class="tos"> <section class="tos">
......
<form name="login" action="login", method="post"> <%inherit file="base.html" />
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Log in</%block>
% if next is not None: <%block name="content">
<input type="hidden" name="next" value="${next}"/>
% endif
Username: <input type="text" name="username" /> <section class="main-container">
Possword: <input type="password" name="password" />
<input type="submit" value="Submit" /> <section class="main-content">
</form> <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>
<%inherit file="base.html" />
\ No newline at end of file
<h1>Check your email</h1>
<p>An activation link has been sent to ${ email }, along with
instructions for activating your account.</p>
<%! from django.core.urlresolvers import reverse %>
<header> <header>
<nav> <nav>
<h2><a href="/">6.002x circuits and electronics</a></h2> <h2><a href="/">edX CMS: TODO:-course-name-here</a></h2>
<ul> <ul>
<li> <li>
<a href="#" class="new-module wip">New Module</a> <a href="#" class="new-module wip">New Module</a>
...@@ -13,6 +14,12 @@ ...@@ -13,6 +14,12 @@
<ul class="user-nav"> <ul class="user-nav">
<li><a href="#" class="wip">Tasks</a></li> <li><a href="#" class="wip">Tasks</a></li>
<li><a href="#" class="wip">Settings</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> </ul>
</nav> </nav>
</header> </header>
...@@ -11,16 +11,25 @@ urlpatterns = ('', ...@@ -11,16 +11,25 @@ urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'), url(r'^$', 'contentstore.views.index', name='index'),
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'), url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_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'), url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
) )
# User creation and updating views # User creation and updating views
urlpatterns += ( urlpatterns += (
url(r'^signup$', 'contentstore.views.signup'), url(r'^signup$', 'contentstore.views.signup', name='signup'),
url(r'^create_account$', 'student.views.create_account'), url(r'^create_account$', 'student.views.create_account'),
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_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: if settings.DEBUG:
......
...@@ -146,12 +146,12 @@ def create_account(request, post_override=None): ...@@ -146,12 +146,12 @@ def create_account(request, post_override=None):
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)) return HttpResponse(json.dumps(js))
if 'honor_code' not in post_vars or 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) js['value']="To enroll, you must follow the honor code.".format(field=a)
return HttpResponse(json.dumps(js)) return HttpResponse(json.dumps(js))
if 'terms_of_service' not in post_vars or 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) js['value']="You must accept the terms of service.".format(field=a)
return HttpResponse(json.dumps(js)) return HttpResponse(json.dumps(js))
......
...@@ -108,7 +108,7 @@ environments, defined in `cms/envs`. ...@@ -108,7 +108,7 @@ environments, defined in `cms/envs`.
- 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. - 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 a fork called mitxmako (TODO: why did we have to fork mako?) - _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. 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.
......
...@@ -95,7 +95,7 @@ for static_dir in STATICFILES_DIRS: ...@@ -95,7 +95,7 @@ for static_dir in STATICFILES_DIRS:
except ValueError: except ValueError:
data_dir = static_dir data_dir = static_dir
if not data_dir.startswith(REPO_ROOT): if data_dir.startswith(REPO_ROOT):
new_staticfiles_dirs.append(static_dir) new_staticfiles_dirs.append(static_dir)
STATICFILES_DIRS = new_staticfiles_dirs STATICFILES_DIRS = new_staticfiles_dirs
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
} }
.photo { .photo {
@include box-sizing(border-box);
background: rgb(255,255,255); background: rgb(255,255,255);
border: 1px solid rgb(210,210,210); border: 1px solid rgb(210,210,210);
padding: 1px; padding: 1px;
...@@ -64,6 +65,11 @@ ...@@ -64,6 +65,11 @@
} }
} }
> article {
float: left;
width: flex-grid(8);
}
&.left { &.left {
.photo { .photo {
float: left; float: left;
......
...@@ -40,15 +40,15 @@ ...@@ -40,15 +40,15 @@
.title { .title {
@include inline-block; @include inline-block;
margin-right: 50px; //margin-right: 50px;
padding-right: 50px; //padding-right: 50px;
position: relative; position: relative;
text-align: left; text-align: left;
vertical-align: middle; vertical-align: middle;
&::before { &::before {
@extend .faded-vertical-divider; @extend .faded-vertical-divider;
content: ""; //content: "";
display: block; display: block;
height: 170px; height: 170px;
position: absolute; position: absolute;
...@@ -99,66 +99,138 @@ ...@@ -99,66 +99,138 @@
} }
} }
.social-sharing { .secondary-actions {
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix;
float: left; float: left;
height: 44px; height: 47px;
position: relative;
text-align: center;
width: flex-grid(6); width: flex-grid(6);
&:hover { a.intro-video {
.sharing-message { background: rgb(245,245,245);
opacity: 1; @include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(235,235,235)));
top: 56px; border: 1px solid rgb(200,200,200);
} @include border-radius(30px);
}
.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); @include box-sizing(border-box);
color: rgb(255,255,255); @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));
float: right; @include clearfix;
font-family: $serif; display: block;
font-size: 0.9em; float: left;
font-style: italic; height: 100%;
left: 50%; overflow: hidden;
margin-left: -110px; text-align: middle;
opacity: 0; width: flex-grid(6);
padding: 6px 10px;
position: absolute;
text-align: center;
@include transition(all, 0.15s, ease-out);
top: 65px;
width: 220px;
&:hover { &: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; height: 44px;
@include inline-block; margin-right: flex-gutter();
margin-right: 10px; position: relative;
opacity: 0.5; text-align: center;
@include transition(all, 0.15s, linear); width: flex-grid(6);
width: 44px;
&:hover { &:hover {
opacity: 1; .sharing-message {
opacity: 1;
top: 56px;
}
} }
img { .sharing-message {
width: 100%; @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 { .share {
margin-right: 0px; 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 @@ ...@@ -168,15 +240,18 @@
background: #fff; background: #fff;
border: 1px solid rgb(200,200,200); border: 1px solid rgb(200,200,200);
@include box-sizing(border-box); @include box-sizing(border-box);
float: left;
@include inline-block; @include inline-block;
padding: 1px; padding: 1px;
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
width: 210px; //width: 210px;
width: flex-grid(3);
z-index: 2; z-index: 2;
.hero { .hero {
height: 125px; //height: 125px;
height: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -17,10 +17,10 @@ $yellow: rgb(255, 252, 221); ...@@ -17,10 +17,10 @@ $yellow: rgb(255, 252, 221);
html, body { html, body {
background: rgb(250,250,250); background: rgb(250,250,250);
//background: rgb(77, 82, 99);
font-family: $sans-serif; font-family: $sans-serif;
font-size: 1em; font-size: 1em;
line-height: 1em; line-height: 1em;
-webkit-font-smoothing: antialiased;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
......
...@@ -42,5 +42,6 @@ form { ...@@ -42,5 +42,6 @@ form {
letter-spacing: 1px; letter-spacing: 1px;
text-transform: uppercase; text-transform: uppercase;
vertical-align: top; vertical-align: top;
-webkit-font-smoothing: antialiased;
} }
} }
...@@ -115,15 +115,9 @@ ...@@ -115,15 +115,9 @@
padding: 12px; padding: 12px;
} }
//#enroll { .activation-message {
//padding: 0 40px; padding: 0 40px 10px;
}
//h1 {
//font: normal 1em/1.6em $sans-serif;
//margin-bottom: 10px;
//text-align: left;
//}
//}
form { form {
margin-bottom: 12px; margin-bottom: 12px;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<section class="container"> <section class="container">
## I'm removing this for now since we aren't using it for the fall. ## 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"> <section class="courses">
%for course in courses: %for course in courses:
<%include file="course.html" args="course=course" /> <%include file="course.html" args="course=course" />
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<a href="${reverse('about_edx')}">About</a> <a href="${reverse('about_edx')}">About</a>
<a href="#">Blog</a> <a href="#">Blog</a>
<a href="${reverse('jobs')}">Jobs</a> <a href="${reverse('jobs')}">Jobs</a>
<a href="${reverse('contact')}">Contact</a>
</section> </section>
<section class="social"> <section class="social">
......
...@@ -7,31 +7,37 @@ ...@@ -7,31 +7,37 @@
<div class="outer-wrapper"> <div class="outer-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="title"> <div class="title">
<h1>The Future of Online Education</h1> <h1>Online education for anyone, anywhere, at anytime</h1>
<div class="main-cta"> <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>
<div class="social-sharing"> <div class="secondary-actions">
<div class="sharing-message">Share with friends and family!</div> <div class="social-sharing">
<a href="#" class="share"> <div class="sharing-message">Share with friends and family!</div>
<img src="${static.url('images/twitter-sharing.png')}"> <a href="#" class="share">
</a> <img src="${static.url('images/twitter-sharing.png')}">
<a href="#" class="share"> </a>
<img src="${static.url('images/facebook-sharing.png')}"> <a href="#" class="share">
</a> <img src="${static.url('images/facebook-sharing.png')}">
<a href="#" class="share"> </a>
<img src="${static.url('images/email-sharing.png')}"> <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> </a>
</div> </div>
</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>
</div> </div>
</header> </header>
...@@ -87,10 +93,10 @@ ...@@ -87,10 +93,10 @@
<section class="blog-posts"> <section class="blog-posts">
<article> <article>
<a href="#" class="post-graphics"> <a href="#" class="post-graphics">
<img src="${static.url('images/courses/space1.jpg')}" /> <img src="${static.url('images/mongolia_post.jpeg')}" />
</a> </a>
<div class="post-name"> <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> <p class="post-date">7/12/2012</p>
</div> </div>
</article> </article>
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
<a href="${reverse('about_edx')}">About</a> <a href="${reverse('about_edx')}">About</a>
<a href="#">Blog</a> <a href="#">Blog</a>
<a href="${reverse('jobs')}">Jobs</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> <a href="#login-modal" id="login" rel="leanModal">Log In</a>
</li> </li>
<li class="primary"> <li class="primary">
......
<h1>Check your email</h1> <header>
<p>An activation link has been sent to ${ email }, along with <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> instructions for activating your account.</p>
...@@ -3,12 +3,11 @@ ...@@ -3,12 +3,11 @@
<section id="signup-modal" class="modal signup-modal"> <section id="signup-modal" class="modal signup-modal">
<div class="inner-wrapper"> <div class="inner-wrapper">
<header>
<h2>Sign Up for edX</h2>
<hr>
</header>
<div id="enroll"> <div id="enroll">
<header>
<h2>Sign Up for edX</h2>
<hr>
</header>
<form id="enroll_form" method="post"> <form id="enroll_form" method="post">
<div id="enroll_error" name="enroll_error"></div> <div id="enroll_error" name="enroll_error"></div>
...@@ -33,26 +32,27 @@ ...@@ -33,26 +32,27 @@
<input name="honor_code" type="checkbox" value="true"> <input name="honor_code" type="checkbox" value="true">
I agree to the I agree to the
<a href="${reverse('honor')}" target="blank">Honor Code</a> <a href="${reverse('honor')}" target="blank">Honor Code</a>
, sumarized below as:
</label> </label>
<div class="honor-code-summary"> <!--
<ul> -<div class="honor-code-summary">
<li> - <ul>
<p>Complete all mid-terms and final exams with only my own work.</p> - <li>
</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>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 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>
</li> - <p>Not post answers to problems that are being used to assess student performance.</p>
</ul> - </li>
<hr> - </ul>
</div> - <hr>
-</div>
-->
<div class="submit"> <div class="submit">
<input name="submit" type="submit" value="Create My Account"> <input name="submit" type="submit" value="Create My Account">
......
...@@ -13,14 +13,16 @@ ...@@ -13,14 +13,16 @@
<section class="vision"> <section class="vision">
<section class="company-mission message left"> <section class="company-mission message left">
<div class="inner-wrapper"> <div class="photo">
<div class="photo"> <img src="">
<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> </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"> <hr class="fade-right-hr-divider">
</section> </section>
...@@ -28,9 +30,11 @@ ...@@ -28,9 +30,11 @@
<div class="photo"> <div class="photo">
<img src=""> <img src="">
</div> </div>
<h2>Mission: Educate 1 billion people around the world</h2> <article>
<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> <h2>Harvard University</h2>
<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.” <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"> <hr class="fade-left-hr-divider">
</section> </section>
...@@ -38,9 +42,12 @@ ...@@ -38,9 +42,12 @@
<div class="photo"> <div class="photo">
<img src=""> <img src="">
</div> </div>
<h2>Mission: Educate 1 billion people around the world</h2> <article>
<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> <h2>Massachusetts Institute of Technology</h2>
<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.” <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> </section>
</section> </section>
......
...@@ -22,6 +22,14 @@ ...@@ -22,6 +22,14 @@
<h2>We are currently looking for</h2> <h2>We are currently looking for</h2>
<section class="jobs-listing"> <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"> <article id="edx-fellow" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3>edX Fellow</h3> <h3>edX Fellow</h3>
...@@ -49,27 +57,20 @@ ...@@ -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> <p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div> </div>
</article> </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>
<section class="jobs-sidebar"> <section class="jobs-sidebar">
<h2>Positions</h2> <h2>Positions</h2>
<nav> <nav>
<a href="#technology-team">Technology Team</a>
<a href="#edx-fellow">edX Fellow</a> <a href="#edx-fellow">edX Fellow</a>
<a href="#content-engineer">Content Engineer</a> <a href="#content-engineer">Content Engineer</a>
<a href="#technology-team">Technology Team</a>
</nav> </nav>
<h2>How to Apply</h2> <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> <h2>Our Location</h2>
<p>11 Cambridge Center, Cambridge MA USA</p> <p>11 Cambridge Center <br/>
Cambridge, MA 02142</p>
</section> </section>
</section> </section>
......
...@@ -88,9 +88,8 @@ end ...@@ -88,9 +88,8 @@ end
# Per System tasks # Per System tasks
desc "Run all django tests on our djangoapps for the #{system}" desc "Run all django tests on our djangoapps for the #{system}"
task "test_#{system}" => [report_dir, :predjango, "#{system}:collectstatic:test"] do task "test_#{system}" => ["#{system}:collectstatic:test", "fasttest_#{system}"]
run_tests(system, report_dir)
end
# Have a way to run the tests without running collectstatic -- useful when debugging without # Have a way to run the tests without running collectstatic -- useful when debugging without
# messing with static files. # messing with static files.
task "fasttest_#{system}" => [report_dir, :predjango] do task "fasttest_#{system}" => [report_dir, :predjango] do
......
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