1
cms/djangoapps/contentstore/tests/__init__.py
Normal file
1
cms/djangoapps/contentstore/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
48
cms/djangoapps/contentstore/tests/tests.py
Normal file
48
cms/djangoapps/contentstore/tests/tests.py
Normal file
@@ -0,0 +1,48 @@
|
||||
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
|
||||
|
||||
def parse_json(response):
|
||||
"""Parse response, which is assumed to be json"""
|
||||
return json.loads(response.content)
|
||||
|
||||
class AuthTestCase(TestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
def test_index(self):
|
||||
"""Make sure the main page loads."""
|
||||
resp = self.client.get('/')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_signup_load(self):
|
||||
"""Make sure the signup page loads."""
|
||||
resp = self.client.get('/signup')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
|
||||
def test_create_account(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)
|
||||
|
||||
# Should work
|
||||
resp = self.client.post('/create_account', {
|
||||
'username': 'user',
|
||||
'email': 'a@b.com',
|
||||
'password': 'xyz',
|
||||
'location' : 'home',
|
||||
'language' : 'Franglish',
|
||||
'name' : 'Fred Weasley',
|
||||
'terms_of_service' : 'true',
|
||||
'honor_code' : 'true'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ from util.json_request import expect_json
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
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
|
||||
|
||||
@@ -25,6 +27,14 @@ def index(request):
|
||||
})
|
||||
|
||||
|
||||
@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 course_index(request, org, course, name):
|
||||
# TODO (cpennington): These need to be read in from the active user
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -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()
|
||||
@@ -97,7 +101,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 +218,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 +243,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,7 +25,7 @@ MODULESTORE = {
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "mitx.db",
|
||||
'NAME': ENV_ROOT / "db" / "cms.db",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
15
cms/templates/activation_active.html
Normal file
15
cms/templates/activation_active.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<%inherit file="marketing.html" />
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<section class="tos">
|
||||
<div>
|
||||
|
||||
<section class="activation">
|
||||
<h1>Account already active!</h1>
|
||||
<p> This account has already been activated. You can log in at
|
||||
the <a href="/">home page</a>.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</%block>
|
||||
13
cms/templates/activation_complete.html
Normal file
13
cms/templates/activation_complete.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<%inherit file="marketing.html" />
|
||||
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<section class="tos">
|
||||
<div>
|
||||
<h1>Activation Complete!</h1>
|
||||
<p>Thanks for activating your account. You can log in at the <a href="/">home page</a>.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</%block>
|
||||
16
cms/templates/activation_invalid.html
Normal file
16
cms/templates/activation_invalid.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<%inherit file="marketing.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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
14
cms/templates/emails/activation_email.txt
Normal file
14
cms/templates/emails/activation_email.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
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.
|
||||
1
cms/templates/emails/activation_email_subject.txt
Normal file
1
cms/templates/emails/activation_email_subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
Your account for edX's on-line ${course_title} course
|
||||
1
cms/templates/marketing.html
Normal file
1
cms/templates/marketing.html
Normal file
@@ -0,0 +1 @@
|
||||
<%inherit file="base.html" />
|
||||
88
cms/templates/signup.html
Normal file
88
cms/templates/signup.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<%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>
|
||||
10
cms/urls.py
10
cms/urls.py
@@ -1,6 +1,8 @@
|
||||
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()
|
||||
@@ -13,6 +15,14 @@ urlpatterns = ('',
|
||||
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
|
||||
)
|
||||
|
||||
# User creation and updating views
|
||||
urlpatterns += (
|
||||
url(r'^signup$', 'contentstore.views.signup'),
|
||||
|
||||
url(r'^create_account$', 'student.views.create_account'),
|
||||
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account'),
|
||||
)
|
||||
|
||||
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 'honor_code' not in post_vars or post_vars['honor_code'] != 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 'terms_of_service' not in post_vars or post_vars['terms_of_service'] != 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):
|
||||
@@ -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 a fork called mitxmako (TODO: why did we have to fork mako?)
|
||||
|
||||
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`.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
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,7 +81,7 @@ 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))
|
||||
FILE_UPLOAD_TEMP_DIR = PROJECT_ROOT / "uploads"
|
||||
|
||||
@@ -1 +1 @@
|
||||
Your account for edX's on-line ${course_title} course
|
||||
Your account for edX
|
||||
|
||||
39
lms/urls.py
39
lms/urls.py
@@ -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'),
|
||||
@@ -69,15 +72,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
|
||||
|
||||
22
rakefile
22
rakefile
@@ -74,17 +74,29 @@ 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}" => [report_dir, :predjango, "#{system}:collectstatic:test"] do
|
||||
run_tests(system, report_dir)
|
||||
end
|
||||
# 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 +118,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
|
||||
|
||||
1
test_root/.gitignore
vendored
1
test_root/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
local_repo
|
||||
remote_repo
|
||||
staticfiles
|
||||
|
||||
0
test_root/uploads/.git-keep
Normal file
0
test_root/uploads/.git-keep
Normal file
Reference in New Issue
Block a user