Add account creation and login to CMS
* connect up views from student app * Add initial @login_required decorators on the protected views * Add tests for some of the basic functionality
This commit is contained in:
@@ -4,45 +4,165 @@ from django.test import TestCase
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from student.models import Registration
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
def parse_json(response):
|
||||
"""Parse response, which is assumed to be json"""
|
||||
return json.loads(response.content)
|
||||
|
||||
|
||||
def user(email):
|
||||
'''look up a user by email'''
|
||||
return User.objects.get(email=email)
|
||||
|
||||
def registration(email):
|
||||
'''look up registration object by email'''
|
||||
return Registration.objects.get(user__email=email)
|
||||
|
||||
class AuthTestCase(TestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
def test_index(self):
|
||||
"""Make sure the main page loads."""
|
||||
resp = self.client.get('/')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
def setUp(self):
|
||||
self.email = 'a@b.com'
|
||||
self.pw = 'xyz'
|
||||
self.username = 'testuser'
|
||||
|
||||
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):
|
||||
def check_page_get(self, url, expected):
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, expected)
|
||||
return resp
|
||||
|
||||
def test_public_pages_load(self):
|
||||
"""Make sure pages that don't require login load without error."""
|
||||
pages = (
|
||||
reverse('login'),
|
||||
reverse('signup'),
|
||||
)
|
||||
for page in pages:
|
||||
print "Checking '{0}'".format(page)
|
||||
self.check_page_get(page, 200)
|
||||
|
||||
def test_create_account_errors(self):
|
||||
# No post data -- should fail
|
||||
resp = self.client.post('/create_account', {})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['success'], False)
|
||||
|
||||
# Should work
|
||||
|
||||
def _create_account(self, username, email, pw):
|
||||
'''Try to create an account. No error checking'''
|
||||
resp = self.client.post('/create_account', {
|
||||
'username': 'user',
|
||||
'email': 'a@b.com',
|
||||
'password': 'xyz',
|
||||
'username': username,
|
||||
'email': email,
|
||||
'password': pw,
|
||||
'location' : 'home',
|
||||
'language' : 'Franglish',
|
||||
'name' : 'Fred Weasley',
|
||||
'terms_of_service' : 'true',
|
||||
'honor_code' : 'true'})
|
||||
return resp
|
||||
|
||||
def create_account(self, username, email, pw):
|
||||
'''Create the account and check that it worked'''
|
||||
resp = self._create_account(username, email, pw)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# Check both that the user is created, and inactive
|
||||
self.assertFalse(user(self.email).is_active)
|
||||
|
||||
return resp
|
||||
|
||||
def _activate_user(self, email):
|
||||
'''look up the user's activation key in the db, then hit the activate view.
|
||||
No error checking'''
|
||||
activation_key = registration(email).activation_key
|
||||
|
||||
# and now we try to activate
|
||||
resp = self.client.get(reverse('activate', kwargs={'key': activation_key}))
|
||||
return resp
|
||||
|
||||
def activate_user(self, email):
|
||||
resp = self._activate_user(email)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# Now make sure that the user is now actually activated
|
||||
self.assertTrue(user(self.email).is_active)
|
||||
|
||||
def test_create_account(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
self.activate_user(self.email)
|
||||
|
||||
|
||||
def _login(self, email, pw):
|
||||
'''Login. View should always return 200. The success/fail is in the
|
||||
returned json'''
|
||||
resp = self.client.post(reverse('login_post'),
|
||||
{'email': email, 'password': pw})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
return resp
|
||||
|
||||
|
||||
def login(self, email, pw):
|
||||
'''Login, check that it worked.'''
|
||||
resp = self._login(self.email, self.pw)
|
||||
data = parse_json(resp)
|
||||
self.assertTrue(data['success'])
|
||||
return resp
|
||||
|
||||
def test_login(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
|
||||
# Not activated yet. Login should fail.
|
||||
resp = self._login(self.email, self.pw)
|
||||
data = parse_json(resp)
|
||||
self.assertFalse(data['success'])
|
||||
|
||||
self.activate_user(self.email)
|
||||
|
||||
# Now login should work
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
def test_private_pages_auth(self):
|
||||
"""Make sure pages that do require login work."""
|
||||
auth_pages = (
|
||||
reverse('index'),
|
||||
reverse('edit_item'),
|
||||
reverse('save_item'),
|
||||
)
|
||||
|
||||
# These are pages that should just load when the user is logged in
|
||||
# (no data needed)
|
||||
simple_auth_pages = (
|
||||
reverse('index'),
|
||||
)
|
||||
|
||||
# need an activated user
|
||||
self.test_create_account()
|
||||
|
||||
# Not logged in. Should redirect to login.
|
||||
print 'Not logged in'
|
||||
for page in auth_pages:
|
||||
print "Checking '{0}'".format(page)
|
||||
self.check_page_get(page, expected=302)
|
||||
|
||||
# Logged in should work.
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
print 'Logged in'
|
||||
for page in simple_auth_pages:
|
||||
print "Checking '{0}'".format(page)
|
||||
self.check_page_get(page, expected=200)
|
||||
|
||||
|
||||
def test_index_auth(self):
|
||||
|
||||
# not logged in. Should return a redirect.
|
||||
resp = self.client.get(reverse('index'))
|
||||
self.assertEqual(resp.status_code, 302)
|
||||
|
||||
# Logged in should work.
|
||||
|
||||
@@ -2,6 +2,7 @@ from util.json_request import expect_json
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.context_processors import csrf
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -13,7 +14,7 @@ from github_sync import export_to_github
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
courses = modulestore().get_items(['i4x', None, None, 'course', None])
|
||||
@@ -35,6 +36,16 @@ def signup(request):
|
||||
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 })
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_index(request, org, course, name):
|
||||
# TODO (cpennington): These need to be read in from the active user
|
||||
@@ -42,7 +53,7 @@ def course_index(request, org, course, name):
|
||||
weeks = course.get_children()
|
||||
return render_to_response('course_index.html', {'weeks': weeks})
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_item(request):
|
||||
item_id = request.GET['id']
|
||||
item = modulestore().get_item(item_id)
|
||||
@@ -54,6 +65,7 @@ def edit_item(request):
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def save_item(request):
|
||||
item_id = request.POST['id']
|
||||
|
||||
@@ -70,6 +70,10 @@ TEMPLATE_DIRS = (
|
||||
|
||||
MITX_ROOT_URL = ''
|
||||
|
||||
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/login'
|
||||
LOGIN_URL = MITX_ROOT_URL + '/login'
|
||||
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.request',
|
||||
'django.core.context_processors.static',
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
<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>
|
||||
<p> This account has already been activated. <a href="/login">Log in here</a>.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<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>
|
||||
<p>Thanks for activating your account. <a href="/login">Log in here</a>.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,11 +1,76 @@
|
||||
<form name="login" action="login", method="post">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Log in</%block>
|
||||
|
||||
% if next is not None:
|
||||
<input type="hidden" name="next" value="${next}"/>
|
||||
% endif
|
||||
<%block name="content">
|
||||
|
||||
Username: <input type="text" name="username" />
|
||||
Possword: <input type="password" name="password" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
<section class="main-container">
|
||||
|
||||
<section class="main-content">
|
||||
<header>
|
||||
<h3>Log in</h3>
|
||||
<hr>
|
||||
</header>
|
||||
|
||||
<form id="login_form" action="login_post" method="post">
|
||||
<label>E-mail</label>
|
||||
<input name="email" type="email" placeholder="E-mail">
|
||||
<label>Password</label>
|
||||
<input name="password" type="password" placeholder="Password">
|
||||
<label class="remember-me">
|
||||
<input name="remember" type="checkbox">
|
||||
Remember me
|
||||
</label>
|
||||
<div class="submit">
|
||||
<input name="submit" type="submit" value="Submit">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="login-extra">
|
||||
<p>
|
||||
<span>Not enrolled? <a href="#">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>
|
||||
|
||||
15
cms/urls.py
15
cms/urls.py
@@ -11,16 +11,25 @@ urlpatterns = ('',
|
||||
url(r'^$', 'contentstore.views.index', name='index'),
|
||||
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$', 'contentstore.views.course_index', name='course_index'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
|
||||
'contentstore.views.course_index', name='course_index'),
|
||||
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
|
||||
)
|
||||
|
||||
# User creation and updating views
|
||||
urlpatterns += (
|
||||
url(r'^signup$', 'contentstore.views.signup'),
|
||||
url(r'^signup$', 'contentstore.views.signup', name='signup'),
|
||||
|
||||
url(r'^create_account$', 'student.views.create_account'),
|
||||
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account'),
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user