diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 46d7c24d2dcdca2ef66efa86c16199e520d96fb3..1464ccddcfaa0b81b23a05b2a4813d8ad5a34a7a 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -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 diff --git a/cms/envs/common.py b/cms/envs/common.py index 8d3e2672daab0a85da9979b3ab652254912f3714..7563ea873144c6bb2529dc66fe6093b6fef84d40 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -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', @@ -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', diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 465d542c5be66e8084d51f567683dd16ebba6117..7bc4e427d21b8ebdee6733ff609811fc76e9b1bd 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -25,7 +25,7 @@ MODULESTORE = { DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ENV_ROOT / "db" / "mitx.db", + 'NAME': ENV_ROOT / "db" / "cms.db", } } diff --git a/cms/static/js/main.js b/cms/static/js/main.js index 2d72edc4bf49c448091adce915dbd45d51b82792..4a9b5d23746eb6a09e3ff9a8b9617a3023a07ff3 100644 --- a/cms/static/js/main.js +++ b/cms/static/js/main.js @@ -80,6 +80,6 @@ $(document).ready(function(){ $('section.problem-edit').show(); return false; }); - + }); diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html new file mode 100644 index 0000000000000000000000000000000000000000..79ba2e39f18a2d763568fdfd36c1481b2a9b004a --- /dev/null +++ b/cms/templates/activation_active.html @@ -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> \ No newline at end of file diff --git a/cms/templates/activation_complete.html b/cms/templates/activation_complete.html new file mode 100644 index 0000000000000000000000000000000000000000..ede9b372ee1346e5459f7eea605fb1905882045a --- /dev/null +++ b/cms/templates/activation_complete.html @@ -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> \ No newline at end of file diff --git a/cms/templates/activation_invalid.html b/cms/templates/activation_invalid.html new file mode 100644 index 0000000000000000000000000000000000000000..f8b9e585de25d757aea93feab15e1bfefaa1ab7b --- /dev/null +++ b/cms/templates/activation_invalid.html @@ -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> \ No newline at end of file diff --git a/cms/templates/base.html b/cms/templates/base.html index dbae876eca438cb31a7ccf1b43d81e9d07551902..1092147efd067ca2e87d9d90e6a4373ef73653a6 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -21,8 +21,6 @@ <%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> @@ -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> diff --git a/cms/templates/emails/activation_email.txt b/cms/templates/emails/activation_email.txt new file mode 100644 index 0000000000000000000000000000000000000000..eca5effdd6ebb79f0246f2b8d25bd8cd68c0f50b --- /dev/null +++ b/cms/templates/emails/activation_email.txt @@ -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. diff --git a/cms/templates/emails/activation_email_subject.txt b/cms/templates/emails/activation_email_subject.txt new file mode 100644 index 0000000000000000000000000000000000000000..c25c006a817d4a299a34a1e2b21ba70906de52f0 --- /dev/null +++ b/cms/templates/emails/activation_email_subject.txt @@ -0,0 +1 @@ +Your account for edX's on-line ${course_title} course diff --git a/cms/templates/marketing.html b/cms/templates/marketing.html new file mode 100644 index 0000000000000000000000000000000000000000..8731b41e15a86200c6653dba0d49d1b6104b2a99 --- /dev/null +++ b/cms/templates/marketing.html @@ -0,0 +1 @@ +<%inherit file="base.html" /> \ No newline at end of file diff --git a/cms/templates/signup.html b/cms/templates/signup.html new file mode 100644 index 0000000000000000000000000000000000000000..d3eedc8070863f7cdad4b556b90da42fb69f23b1 --- /dev/null +++ b/cms/templates/signup.html @@ -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> diff --git a/cms/urls.py b/cms/urls.py index eb925a7069bf69a349850ef7b7fdff9763d00cfd..dc86535decf1c7ee0524c2ded9637d188ad0dccb 100644 --- a/cms/urls.py +++ b/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')),) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index ae64549f308ad4721c5ee805d0c80c55ac0d063b..59692f91bae16ac54acc39c395b51ebb7b88786c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -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):