Commit 4489a045 by Calen Pennington

Merge pull request #178 from MITx/victor/cms_auth

Victor/cms auth
parents 1c027c43 1f1fab49
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
......@@ -26,6 +28,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
course = modulestore().get_item(['i4x', org, course, 'course', name])
......
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",
}
}
......
......@@ -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="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>
\ No newline at end of file
<%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>
\ No newline at end of file
<%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>
\ 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
<%inherit file="base.html" />
\ No newline at end of file
<%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.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'],
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`.
......
"""
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"
......
Your account for edX's on-line ${course_title} course
Your account for edX
......@@ -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
......
......@@ -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
......
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