diff --git a/cms/envs/common.py b/cms/envs/common.py index 0aca432654..e150374cef 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -317,3 +317,7 @@ INSTALLED_APPS = ( 'staticfiles', 'static_replace', ) + +################# EDX MARKETING SITE ################################## + +EDXMKTG_COOKIE_NAME = 'edxloggedin' diff --git a/common/djangoapps/mitxmako/shortcuts.py b/common/djangoapps/mitxmako/shortcuts.py index ebeb0fc180..0766564027 100644 --- a/common/djangoapps/mitxmako/shortcuts.py +++ b/common/djangoapps/mitxmako/shortcuts.py @@ -14,9 +14,57 @@ from django.template import Context from django.http import HttpResponse +import logging from . import middleware from django.conf import settings +from django.core.urlresolvers import reverse +log = logging.getLogger(__name__) + + +def marketing_link(name): + """Returns the correct URL for a link to the marketing site + depending on if the marketing site is enabled + + Since the marketing site is enabled by a setting, we have two + possible URLs for certain links. This function is to decides + which URL should be provided. + """ + + # link_map maps URLs from the marketing site to the old equivalent on + # the Django site + link_map = settings.MKTG_URL_LINK_MAP + if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in settings.MKTG_URLS: + # special case for when we only want the root marketing URL + if name == 'ROOT': + return settings.MKTG_URLS.get('ROOT') + return settings.MKTG_URLS.get('ROOT') + settings.MKTG_URLS.get(name) + # only link to the old pages when the marketing site isn't on + elif not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in link_map: + return reverse(link_map[name]) + else: + log.warning("Cannot find corresponding link for name: {name}".format(name=name)) + return '#' + + +def marketing_link_context_processor(request): + """ + A django context processor to give templates access to marketing URLs + + Returns a dict whose keys are the marketing link names usable with the + marketing_link method (e.g. 'ROOT', 'CONTACT', etc.) prefixed with + 'MKTG_URL_' and whose values are the corresponding URLs as computed by the + marketing_link method. + """ + return dict( + [ + ("MKTG_URL_" + k, marketing_link(k)) + for k in ( + settings.MKTG_URL_LINK_MAP.viewkeys() | + settings.MKTG_URLS.viewkeys() + ) + ] + ) def render_to_string(template_name, dictionary, context=None, namespace='main'): @@ -27,6 +75,7 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'): context_dictionary = {} context_instance['settings'] = settings context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL + context_instance['marketing_link'] = marketing_link # In various testing contexts, there might not be a current request context. if middleware.requestcontext is not None: diff --git a/common/djangoapps/mitxmako/template.py b/common/djangoapps/mitxmako/template.py index 6ef8058c7c..5becfbf1df 100644 --- a/common/djangoapps/mitxmako/template.py +++ b/common/djangoapps/mitxmako/template.py @@ -14,6 +14,7 @@ from django.conf import settings from mako.template import Template as MakoTemplate +from mitxmako.shortcuts import marketing_link from mitxmako import middleware @@ -37,7 +38,6 @@ class Template(MakoTemplate): kwargs.update(overrides) super(Template, self).__init__(*args, **kwargs) - def render(self, context_instance): """ This takes a render call with a context (from Django) and translates @@ -55,5 +55,6 @@ class Template(MakoTemplate): context_dictionary['settings'] = settings context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL context_dictionary['django_context'] = context_instance + context_dictionary['marketing_link'] = marketing_link return super(Template, self).render_unicode(**context_dictionary) diff --git a/common/djangoapps/mitxmako/tests.py b/common/djangoapps/mitxmako/tests.py new file mode 100644 index 0000000000..21866eb9b5 --- /dev/null +++ b/common/djangoapps/mitxmako/tests.py @@ -0,0 +1,27 @@ +from django.test import TestCase +from django.test.utils import override_settings +from django.core.urlresolvers import reverse +from django.conf import settings +from mitxmako.shortcuts import marketing_link +from mock import patch + + +class ShortcutsTests(TestCase): + """ + Test the mitxmako shortcuts file + """ + + @override_settings(MKTG_URLS={'ROOT': 'dummy-root', 'ABOUT': '/about-us'}) + @override_settings(MKTG_URL_LINK_MAP={'ABOUT': 'login'}) + def test_marketing_link(self): + # test marketing site on + with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}): + expected_link = 'dummy-root/about-us' + link = marketing_link('ABOUT') + self.assertEquals(link, expected_link) + # test marketing site off + with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}): + # we are using login because it is common across both cms and lms + expected_link = reverse('login') + link = marketing_link('ABOUT') + self.assertEquals(link, expected_link) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 53d1c72cc4..e8a70d6089 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -7,7 +7,7 @@ import string import sys import urllib import uuid - +import time from django.conf import settings from django.contrib.auth import logout, authenticate, login @@ -20,9 +20,10 @@ from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.core.validators import validate_email, validate_slug, ValidationError from django.db import IntegrityError -from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect, Http404 from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie, csrf_exempt +from django.utils.http import cookie_date from mitxmako.shortcuts import render_to_response, render_to_string from bs4 import BeautifulSoup @@ -212,6 +213,36 @@ def _cert_info(user, course, cert_status): return d +@ensure_csrf_cookie +def signin_user(request): + """ + This view will display the non-modal login form + """ + if request.user.is_authenticated(): + return redirect(reverse('dashboard')) + + context = { + 'course_id': request.GET.get('course_id'), + 'enrollment_action': request.GET.get('enrollment_action') + } + return render_to_response('login.html', context) + + +@ensure_csrf_cookie +def register_user(request): + """ + This view will display the non-modal registration form + """ + if request.user.is_authenticated(): + return redirect(reverse('dashboard')) + + context = { + 'course_id': request.GET.get('course_id'), + 'enrollment_action': request.GET.get('enrollment_action') + } + return render_to_response('register.html', context) + + @login_required @ensure_csrf_cookie def dashboard(request): @@ -250,7 +281,7 @@ def dashboard(request): exam_registrations = {course.id: exam_registration_info(request.user, course) for course in courses} # Get the 3 most recent news - top_news = _get_news(top=3) + top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None context = {'courses': courses, 'message': message, @@ -275,35 +306,47 @@ def try_change_enrollment(request): """ if 'enrollment_action' in request.POST: try: - enrollment_output = change_enrollment(request) + enrollment_response = change_enrollment(request) # There isn't really a way to display the results to the user, so we just log it # We expect the enrollment to be a success, and will show up on the dashboard anyway - log.info("Attempted to automatically enroll after login. Results: {0}".format(enrollment_output)) + log.info( + "Attempted to automatically enroll after login. Response code: {0}; response body: {1}".format( + enrollment_response.status_code, + enrollment_response.content + ) + ) except Exception, e: log.exception("Exception automatically enrolling after login: {0}".format(str(e))) -@login_required -def change_enrollment_view(request): - """Delegate to change_enrollment to actually do the work.""" - return HttpResponse(json.dumps(change_enrollment(request))) - - - def change_enrollment(request): + """ + Modify the enrollment status for the logged-in user. + + The request parameter must be a POST request (other methods return 405) + that specifies course_id and enrollment_action parameters. If course_id or + enrollment_action is not specified, if course_id is not valid, if + enrollment_action is something other than "enroll" or "unenroll", if + enrollment_action is "enroll" and enrollment is closed for the course, or + if enrollment_action is "unenroll" and the user is not enrolled in the + course, a 400 error will be returned. If the user is not logged in, 403 + will be returned; it is important that only this case return 403 so the + front end can redirect the user to a registration or login page when this + happens. This function should only be called from an AJAX request or + as a post-login/registration helper, so the error messages in the responses + should never actually be user-visible. + """ if request.method != "POST": - raise Http404 + return HttpResponseNotAllowed(["POST"]) user = request.user if not user.is_authenticated(): - raise Http404 + return HttpResponseForbidden() - action = request.POST.get("enrollment_action", "") - - course_id = request.POST.get("course_id", None) + action = request.POST.get("enrollment_action") + course_id = request.POST.get("course_id") if course_id is None: - return HttpResponse(json.dumps({'success': False, - 'error': 'There was an error receiving the course id.'})) + return HttpResponseBadRequest("Course id not specified") if action == "enroll": # Make sure the course exists @@ -313,12 +356,10 @@ def change_enrollment(request): except ItemNotFoundError: log.warning("User {0} tried to enroll in non-existent course {1}" .format(user.username, course_id)) - return {'success': False, 'error': 'The course requested does not exist.'} + return HttpResponseBadRequest("Course id is invalid") if not has_access(user, course, 'enroll'): - return {'success': False, - 'error': 'enrollment in {} not allowed at this time' - .format(course.display_name_with_default)} + return HttpResponseBadRequest("Enrollment is closed") org, course_num, run = course_id.split("/") statsd.increment("common.student.enrollment", @@ -332,7 +373,7 @@ def change_enrollment(request): # If we've already created this enrollment in a separate transaction, # then just continue pass - return {'success': True} + return HttpResponse() elif action == "unenroll": try: @@ -345,21 +386,17 @@ def change_enrollment(request): "course:{0}".format(course_num), "run:{0}".format(run)]) - return {'success': True} + return HttpResponse() except CourseEnrollment.DoesNotExist: - return {'success': False, 'error': 'You are not enrolled for this course.'} + return HttpResponseBadRequest("You are not enrolled in this course") else: - return {'success': False, 'error': 'Invalid enrollment_action.'} - - return {'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'} + return HttpResponseBadRequest("Enrollment action is invalid") @ensure_csrf_cookie def accounts_login(request, error=""): - - return render_to_response('accounts_login.html', {'error': error}) - + return render_to_response('login.html', {'error': error}) # Need different levels of logging @@ -403,8 +440,29 @@ def login_user(request, error=""): try_change_enrollment(request) statsd.increment("common.student.successful_login") + response = HttpResponse(json.dumps({'success': True})) - return HttpResponse(json.dumps({'success': True})) + # set the login cookie for the edx marketing site + # we want this cookie to be accessed via javascript + # so httponly is set to None + + if request.session.get_expire_at_browser_close(): + max_age = None + expires = None + else: + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age + expires = cookie_date(expires_time) + + + response.set_cookie(settings.EDXMKTG_COOKIE_NAME, + 'true', max_age=max_age, + expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + path='/', + secure=None, + httponly=None) + + return response log.warning(u"Login failed - Account not active for user {0}, resending activation".format(username)) @@ -418,9 +476,18 @@ def login_user(request, error=""): @ensure_csrf_cookie def logout_user(request): - ''' HTTP request to log out the user. Redirects to marketing page''' + ''' + HTTP request to log out the user. Redirects to marketing page. + Deletes both the CSRF and sessionid cookies so the marketing + site can determine the logged in state of the user + ''' + logout(request) - return redirect('/') + response = redirect('/') + response.delete_cookie(settings.EDXMKTG_COOKIE_NAME, + path='/', + domain=settings.SESSION_COOKIE_DOMAIN) + return response @login_required @@ -615,7 +682,31 @@ def create_account(request, post_override=None): statsd.increment("common.student.account_created") js = {'success': True} - return HttpResponse(json.dumps(js), mimetype="application/json") + HttpResponse(json.dumps(js), mimetype="application/json") + + response = HttpResponse(json.dumps({'success': True})) + + # set the login cookie for the edx marketing site + # we want this cookie to be accessed via javascript + # so httponly is set to None + + if request.session.get_expire_at_browser_close(): + max_age = None + expires = None + else: + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age + expires = cookie_date(expires_time) + + + response.set_cookie(settings.EDXMKTG_COOKIE_NAME, + 'true', max_age=max_age, + expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + path='/', + secure=None, + httponly=None) + return response + def exam_registration_info(user, course): @@ -701,7 +792,6 @@ def create_exam_registration(request, post_override=None): for fieldname in TestCenterUser.user_provided_fields(): if fieldname in post_vars: demographic_data[fieldname] = (post_vars[fieldname]).strip() - try: testcenter_user = TestCenterUser.objects.get(user=user) needs_updating = testcenter_user.needs_update(demographic_data) diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index fdab514177..d2be2eeea8 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -122,6 +122,13 @@ def should_see_a_link_called(step, text): assert len(world.browser.find_link_by_text(text)) > 0 +@step(r'should see (?:the|a) link with the id "([^"]*)" called "([^"]*)"$') +def should_have_link_with_id_and_text(step, link_id, text): + link = world.browser.find_by_id(link_id) + assert len(link) > 0 + assert_equals(link.text, text) + + @step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') def should_see_in_the_page(step, text): assert_in(text, world.css_text('body')) @@ -144,3 +151,8 @@ def i_am_an_edx_user(step): @step(u'User "([^"]*)" is an edX user$') def registered_edx_user(step, uname): world.create_user(uname) + + +@step(u'All dialogs should be closed$') +def dialogs_are_closed(step): + assert world.dialogs_closed() diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 1a85904135..40b839ae24 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -1,7 +1,7 @@ #pylint: disable=C0111 #pylint: disable=W0621 -from lettuce import world, step +from lettuce import world import time from urllib import quote_plus from selenium.common.exceptions import WebDriverException @@ -104,6 +104,17 @@ def css_visible(css_selector): return world.browser.find_by_css(css_selector).visible +@world.absorb +def dialogs_closed(): + def are_dialogs_closed(driver): + ''' + Return True when no modal dialogs are visible + ''' + return not css_visible('.modal') + wait_for(are_dialogs_closed) + return not css_visible('.modal') + + @world.absorb def save_the_html(path='/tmp'): u = world.browser.url @@ -111,4 +122,4 @@ def save_the_html(path='/tmp'): filename = '%s.html' % quote_plus(u) f = open('%s/%s' % (path, filename), 'w') f.write(html) - f.close + f.close() diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 9fe912e947..dd57e8d4d4 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -6,6 +6,7 @@ from django_future.csrf import ensure_csrf_cookie import student.views import branding import courseware.views +from mitxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous @@ -22,6 +23,8 @@ def index(request): if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'): from external_auth.views import ssl_login return ssl_login(request) + if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE'): + return redirect(settings.MKTG_URLS.get('ROOT')) university = branding.get_university(request.META.get('HTTP_HOST')) if university is None: @@ -34,9 +37,12 @@ def index(request): @cache_if_anonymous def courses(request): """ - Render the "find courses" page. If subdomain branding is on, this is the - university profile page, otherwise it's the edX courseware.views.courses page + Render the "find courses" page. If the marketing site is enabled, redirect + to that. Otherwise, if subdomain branding is on, this is the university + profile page. Otherwise, it's the edX courseware.views.courses page """ + if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False): + return redirect(marketing_link('COURSES'), permanent=True) university = branding.get_university(request.META.get('HTTP_HOST')) if university is None: diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature index c0c1c32f02..2c354acd49 100644 --- a/lms/djangoapps/courseware/features/homepage.feature +++ b/lms/djangoapps/courseware/features/homepage.feature @@ -5,29 +5,24 @@ Feature: Homepage for web users Scenario: User can see the "Login" button Given I visit the homepage - Then I should see a link called "Log In" + Then I should see a link called "Log in" - Scenario: User can see the "Sign up" button + Scenario: User can see the "Register Now" button Given I visit the homepage - Then I should see a link called "Sign Up" + Then I should see a link called "Register Now" Scenario Outline: User can see main parts of the page Given I visit the homepage - Then I should see a link called "" - When I click the link with the text "" - Then I should see that the path is "" + Then I should see a link with the id "" called "" Examples: - | Link | Path | - | Find Courses | /courses | - | About | /about | - | Jobs | /jobs | - | Contact | /contact | + | id | Link | + | about | About | + | jobs | Jobs | + | faq | FAQ | + | contact | Contact| + | press | Press | - Scenario: User can visit the blog - Given I visit the homepage - When I click the link with the text "Blog" - Then I should see that the url is "http://blog.edx.org/" # TODO: test according to domain or policy Scenario: User can see the partner institutions diff --git a/lms/djangoapps/courseware/features/login.feature b/lms/djangoapps/courseware/features/login.feature index 23317b4876..a1b788a7b2 100644 --- a/lms/djangoapps/courseware/features/login.feature +++ b/lms/djangoapps/courseware/features/login.feature @@ -7,7 +7,7 @@ Feature: Login in as a registered user Given I am an edX user And I am an unactivated user And I visit the homepage - When I click the link with the text "Log In" + When I click the link with the text "Log in" And I submit my credentials on the login form Then I should see the login error message "This account has not been activated" @@ -15,7 +15,7 @@ Feature: Login in as a registered user Given I am an edX user And I am an activated user And I visit the homepage - When I click the link with the text "Log In" + When I click the link with the text "Log in" And I submit my credentials on the login form Then I should be on the dashboard page @@ -23,5 +23,5 @@ Feature: Login in as a registered user Given I am logged in When I click the dropdown arrow And I click the link with the text "Log Out" - Then I should see a link with the text "Log In" + Then I should see a link with the text "Log in" And I should see that the path is "/" diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py index bc90ea301c..857b70fa5d 100644 --- a/lms/djangoapps/courseware/features/login.py +++ b/lms/djangoapps/courseware/features/login.py @@ -19,13 +19,13 @@ def i_am_an_activated_user(step): def i_submit_my_credentials_on_the_login_form(step): fill_in_the_login_form('email', 'robot@edx.org') fill_in_the_login_form('password', 'test') - login_form = world.browser.find_by_css('form#login_form') - login_form.find_by_value('Access My Courses').click() + login_form = world.browser.find_by_css('form#login-form') + login_form.find_by_name('submit').click() @step(u'I should see the login error message "([^"]*)"$') def i_should_see_the_login_error_message(step, msg): - login_error_div = world.browser.find_by_css('form#login_form #login_error') + login_error_div = world.browser.find_by_css('.submission-error.is-shown') assert (msg in login_error_div.text) @@ -49,6 +49,6 @@ def user_is_an_activated_user(uname): def fill_in_the_login_form(field, value): - login_form = world.browser.find_by_css('form#login_form') + login_form = world.browser.find_by_css('form#login-form') form_field = login_form.find_by_name(field) form_field.fill(value) diff --git a/lms/djangoapps/courseware/features/registration.feature b/lms/djangoapps/courseware/features/registration.feature index 5933f860bb..43b04a5ad0 100644 --- a/lms/djangoapps/courseware/features/registration.feature +++ b/lms/djangoapps/courseware/features/registration.feature @@ -15,4 +15,6 @@ Feature: Register for a course And I visit the dashboard When I click the link with the text "Unregister" And I press the "Unregister" button in the Unenroll dialog - Then I should see "Looks like you haven't registered for any courses yet." somewhere in the page + Then All dialogs should be closed + And I should be on the dashboard page + And I should see "Looks like you haven't registered for any courses yet." somewhere in the page diff --git a/lms/djangoapps/courseware/features/signup.feature b/lms/djangoapps/courseware/features/signup.feature index b28a6819a1..cfc8b6e924 100644 --- a/lms/djangoapps/courseware/features/signup.feature +++ b/lms/djangoapps/courseware/features/signup.feature @@ -5,12 +5,12 @@ Feature: Sign in Scenario: Sign up from the homepage Given I visit the homepage - When I click the link with the text "Sign Up" + When I click the link with the text "Register Now" And I fill in "email" on the registration form with "robot2@edx.org" And I fill in "password" on the registration form with "test" And I fill in "username" on the registration form with "robot2" And I fill in "name" on the registration form with "Robot Two" And I check the checkbox named "terms_of_service" And I check the checkbox named "honor_code" - And I press the "Create My Account" button on the registration form + And I submit the registration form Then I should see "THANKS FOR REGISTERING!" in the dashboard banner diff --git a/lms/djangoapps/courseware/features/signup.py b/lms/djangoapps/courseware/features/signup.py index 5ba385ef54..3dc34d5af8 100644 --- a/lms/djangoapps/courseware/features/signup.py +++ b/lms/djangoapps/courseware/features/signup.py @@ -3,17 +3,18 @@ from lettuce import world, step + @step('I fill in "([^"]*)" on the registration form with "([^"]*)"$') def when_i_fill_in_field_on_the_registration_form_with_value(step, field, value): - register_form = world.browser.find_by_css('form#register_form') + register_form = world.browser.find_by_css('form#register-form') form_field = register_form.find_by_name(field) form_field.fill(value) -@step('I press the "([^"]*)" button on the registration form$') -def i_press_the_button_on_the_registration_form(step, button): - register_form = world.browser.find_by_css('form#register_form') - register_form.find_by_value(button).click() +@step('I submit the registration form$') +def i_press_the_button_on_the_registration_form(step): + register_form = world.browser.find_by_css('form#register-form') + register_form.find_by_name('submit').click() @step('I check the checkbox named "([^"]*)"$') diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index a189160a48..ec3e55b1b8 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -220,25 +220,20 @@ class LoginEnrollmentTestCase(TestCase): # Now make sure that the user is now actually activated self.assertTrue(get_user(email).is_active) - def _enroll(self, course): - """Post to the enrollment view, and return the parsed json response""" + def try_enroll(self, course): + """Try to enroll. Return bool success instead of asserting it.""" resp = self.client.post('/change_enrollment', { 'enrollment_action': 'enroll', 'course_id': course.id, }) - return parse_json(resp) - - def try_enroll(self, course): - """Try to enroll. Return bool success instead of asserting it.""" - data = self._enroll(course) - print ('Enrollment in %s result: %s' - % (course.location.url(), str(data))) - return data['success'] + print ('Enrollment in %s result status code: %s' + % (course.location.url(), str(resp.status_code))) + return resp.status_code == 200 def enroll(self, course): """Enroll the currently logged-in user, and check that it worked.""" - data = self._enroll(course) - self.assertTrue(data['success']) + result = self.try_enroll(course) + self.assertTrue(result) def unenroll(self, course): """Unenroll the currently logged-in user, and check that it worked.""" @@ -246,8 +241,7 @@ class LoginEnrollmentTestCase(TestCase): 'enrollment_action': 'unenroll', 'course_id': course.id, }) - data = parse_json(resp) - self.assertTrue(data['success']) + self.assertTrue(resp.status_code == 200) def check_for_get_code(self, code, url): """ diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 385b27e7f1..9c5a665754 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -515,6 +515,9 @@ def registered_for_course(course, user): @ensure_csrf_cookie @cache_if_anonymous def course_about(request, course_id): + if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False): + raise Http404 + course = get_course_with_access(request.user, course_id, 'see_exists') registered = registered_for_course(course, request.user) @@ -531,6 +534,37 @@ def course_about(request, course_id): 'registered': registered, 'course_target': course_target, 'show_courseware_link': show_courseware_link}) +@ensure_csrf_cookie +@cache_if_anonymous +def mktg_course_about(request, course_id): + + try: + course = get_course_with_access(request.user, course_id, 'see_exists') + except (ValueError, Http404) as e: + # if a course does not exist yet, display a coming + # soon button + return render_to_response('courseware/mktg_coming_soon.html', + {'course_id': course_id}) + + registered = registered_for_course(course, request.user) + + if has_access(request.user, course, 'load'): + course_target = reverse('info', args=[course.id]) + else: + course_target = reverse('about_course', args=[course.id]) + + allow_registration = has_access(request.user, course, 'enroll') + + show_courseware_link = (has_access(request.user, course, 'load') or + settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION')) + + return render_to_response('courseware/mktg_course_about.html', + {'course': course, + 'registered': registered, + 'allow_registration': allow_registration, + 'course_target': course_target, + 'show_courseware_link': show_courseware_link}) + @ensure_csrf_cookie diff --git a/lms/envs/aws.py b/lms/envs/aws.py index d60215bf7b..9e3518ddc7 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -133,6 +133,7 @@ COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY", '') CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull') ZENDESK_URL = ENV_TOKENS.get("ZENDESK_URL") FEEDBACK_SUBMISSION_EMAIL = ENV_TOKENS.get("FEEDBACK_SUBMISSION_EMAIL") +MKTG_URLS = ENV_TOKENS.get('MKTG_URLS', MKTG_URLS) for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items(): oldvalue = CODE_JAIL.get(name) diff --git a/lms/envs/common.py b/lms/envs/common.py index cc03915fb2..a198f010c6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -36,6 +36,7 @@ DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, } + # Features MITX_FEATURES = { 'SAMPLE': False, @@ -182,6 +183,9 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.messages.context_processors.messages', 'sekizai.context_processors.sekizai', 'course_wiki.course_nav.context_processor', + + # Hack to get required link URLs to password reset templates + 'mitxmako.shortcuts.marketing_link_context_processor', ) STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB @@ -691,3 +695,17 @@ INSTALLED_APPS = ( # Student notes 'notes', ) + +######################### MARKETING SITE ############################### +EDXMKTG_COOKIE_NAME = 'edxloggedin' +MKTG_URLS = {} +MKTG_URL_LINK_MAP = { + 'ABOUT': 'about_edx', + 'CONTACT': 'contact', + 'FAQ': 'help_edx', + 'COURSES': 'courses', + 'ROOT': 'root', + 'TOS': 'tos', + 'HONOR': 'honor', + 'PRIVACY': 'privacy_edx', +} diff --git a/lms/static/images/bg-banner-example.png b/lms/static/images/bg-banner-example.png new file mode 100644 index 0000000000..a52ffb6ef2 Binary files /dev/null and b/lms/static/images/bg-banner-example.png differ diff --git a/lms/static/images/bg-banner-login.png b/lms/static/images/bg-banner-login.png new file mode 100644 index 0000000000..4bbba21628 Binary files /dev/null and b/lms/static/images/bg-banner-login.png differ diff --git a/lms/static/images/bg-banner-register.png b/lms/static/images/bg-banner-register.png new file mode 100644 index 0000000000..f1fe626f05 Binary files /dev/null and b/lms/static/images/bg-banner-register.png differ diff --git a/lms/static/images/bg-footer-divider.jpg b/lms/static/images/bg-footer-divider.jpg new file mode 100644 index 0000000000..9d5e6fa6cd Binary files /dev/null and b/lms/static/images/bg-footer-divider.jpg differ diff --git a/lms/static/images/header-logo.png b/lms/static/images/header-logo.png index f1d2357e6b..df8cb13233 100644 Binary files a/lms/static/images/header-logo.png and b/lms/static/images/header-logo.png differ diff --git a/lms/static/images/social/ico-social-facebook.png b/lms/static/images/social/ico-social-facebook.png new file mode 100644 index 0000000000..3588e7f29a Binary files /dev/null and b/lms/static/images/social/ico-social-facebook.png differ diff --git a/lms/static/images/social/ico-social-google.png b/lms/static/images/social/ico-social-google.png new file mode 100644 index 0000000000..f5c39640df Binary files /dev/null and b/lms/static/images/social/ico-social-google.png differ diff --git a/lms/static/images/social/ico-social-meetup.png b/lms/static/images/social/ico-social-meetup.png new file mode 100644 index 0000000000..52a7f447d7 Binary files /dev/null and b/lms/static/images/social/ico-social-meetup.png differ diff --git a/lms/static/images/social/ico-social-twitter.png b/lms/static/images/social/ico-social-twitter.png new file mode 100644 index 0000000000..c812e7dd5c Binary files /dev/null and b/lms/static/images/social/ico-social-twitter.png differ diff --git a/lms/static/images/social/ico-social-youtube.png b/lms/static/images/social/ico-social-youtube.png new file mode 100644 index 0000000000..65f167f742 Binary files /dev/null and b/lms/static/images/social/ico-social-youtube.png differ diff --git a/lms/static/sass/_shame.scss b/lms/static/sass/_shame.scss new file mode 100644 index 0000000000..d3cc0b9a80 --- /dev/null +++ b/lms/static/sass/_shame.scss @@ -0,0 +1,100 @@ +// edX LMS - shame +// shame file - used for any bad-form/orphaned scss that knowingly violate edX FED architecture/standards (see - http://csswizardry.com/2013/04/shame-css/) +// ==================== + +// marketing site - registration iframe band-aid (poor form enough to isolate out) +.view-partial-mktgregister { + background: transparent; + + // dimensions needed for course about page on marketing site + .wrapper-view { + overflow: hidden; + } + + // button elements - not a better place to put these, sadly + .btn { + @include box-sizing('border-box'); + display: block; + padding: $baseline/2; + text-transform: lowercase; + color: $white; + letter-spacing: 0.1rem; + cursor: pointer; + text-align: center; + border: none !important; + text-decoration: none; + text-shadow: none; + letter-spacing: 0.1rem; + font-size: 17px; + font-weight: 300; + box-shadow: 0 !important; + + strong { + font-weight: 400; + text-transform: none; + } + } + + .btn-primary { + @extend .btn; + @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%); + + // no hover state conventions to follow from marketing :/ + &:hover, &:active { + + } + } + + .btn-secondary { + @extend .btn; + @include linear-gradient($m-gray 5%, $m-gray-d1 95%); + + // no hover state conventions to follow from marketing :/ + &:hover, &:active { + + } + } + + .btn-tertiary { + @extend .btn; + background: $m-blue-l1; + color: $m-blue; + + // no hover state conventions to follow from marketing :/ + &:hover, &:active { + + } + } + + // nav list + .list-actions { + list-style: none; + margin: 0; + padding: 0; + + .item { + margin: 0; + } + } + + .action { + + // register or access courseware + &.action-register, &.access-courseware { + @extend .btn-primary; + } + + // already registered but course not started or registration is closed + &.is-registered, &.registration-closed { + @extend .btn-secondary; + pointer-events: none !important; + } + + // coming soon + &.coming-soon { + @extend .btn-tertiary; + pointer-events: none !important; + outline: none; + } + } +} diff --git a/lms/static/sass/application.scss b/lms/static/sass/application.scss index 519118af84..6a1ef8743e 100644 --- a/lms/static/sass/application.scss +++ b/lms/static/sass/application.scss @@ -19,6 +19,7 @@ @import 'multicourse/home'; @import 'multicourse/dashboard'; +@import 'multicourse/account'; @import 'multicourse/testcenter-register'; @import 'multicourse/courses'; @import 'multicourse/course_about'; @@ -33,3 +34,5 @@ @import 'discussion'; @import 'news'; + +@import 'shame'; diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss index d2d4a0564f..e62dd12541 100644 --- a/lms/static/sass/base/_base.scss +++ b/lms/static/sass/base/_base.scss @@ -1,4 +1,8 @@ -html, body { +// html { +// overflow-y: scroll; +// } + +body { background: rgb(250,250,250); font-family: $sans-serif; font-size: 1em; @@ -81,9 +85,10 @@ a:link, a:visited { } .content-wrapper { - background: rgb(255,255,255); - margin: 0 auto 0; width: flex-grid(12); + margin: 0 auto; + padding-bottom: ($baseline*2); + background: rgb(255,255,255); } .container { @@ -92,6 +97,7 @@ a:link, a:visited { padding: 0px 30px; max-width: grid-width(12); min-width: 760px; + width: flex-grid(12); } span.edx { @@ -202,6 +208,10 @@ mark { } } +.sr { + @include text-sr(); +} + .help-tab { @include transform(rotate(-90deg)); @include transform-origin(0 0); diff --git a/lms/static/sass/base/_mixins.scss b/lms/static/sass/base/_mixins.scss index 58a92d1ee6..97703e8f0f 100644 --- a/lms/static/sass/base/_mixins.scss +++ b/lms/static/sass/base/_mixins.scss @@ -7,10 +7,23 @@ @return $body-line-height * $amount; } -@mixin hide-text(){ - text-indent: -9999px; +// image-replacement hidden text +@mixin text-hide() { + text-indent: 100%; + white-space: nowrap; overflow: hidden; - display: block; +} + +// hidden elems - screenreaders +@mixin text-sr() { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } @mixin vertically-and-horizontally-centered ( $height, $width ) { @@ -22,3 +35,10 @@ position: absolute; top: 150px; } + +// sunsetted, but still used mixins +@mixin hide-text(){ + text-indent: -9999px; + overflow: hidden; + display: block; +} diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index 4d27798649..ddbd930323 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -1,3 +1,5 @@ +$baseline: 20px; + $gw-column: 80px; $gw-gutter: 20px; @@ -8,27 +10,48 @@ $fg-max-width: 1400px; $fg-min-width: 810px; $sans-serif: 'Open Sans', $verdana; +$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace; $body-font-family: $sans-serif; $serif: $georgia; -$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace; - -$body-font-size: em(14); -$body-line-height: golden-ratio(.875em, 1); -$base-font-color: rgb(60,60,60); -$baseFontColor: rgb(60,60,60); -$base-font-color: rgb(60,60,60); -$lighter-base-font-color: rgb(100,100,100); +$white: rgb(255,255,255); +$black: rgb(0,0,0); $blue: rgb(29,157,217); $pink: rgb(182,37,104); $yellow: rgb(255, 252, 221); +$red: rgb(178, 6, 16); $error-red: rgb(253, 87, 87); -$border-color: #C8C8C8; -$sidebar-color: #f6f6f6; -$outer-border-color: #aaa; +$light-gray: rgb(221, 221, 221); +$dark-gray: rgb(51, 51, 51); +$border-color: rgb(200, 200, 200); +$sidebar-color: rgb(246, 246, 246); +$outer-border-color: rgb(170, 170, 170); // old variables $light-gray: #ddd; $dark-gray: #333; + +// edx.org-related +$m-gray-l1: rgb(203,203,203); +$m-gray-l2: rgb(246,246,246); +$m-gray: rgb(153,153,153); +$m-gray-d1: rgb(102,102,102); +$m-gray-d2: rgb(51,51,51); +$m-gray-a1: rgb(80,80,80); +$m-blue: rgb(85, 151, 221); +$m-blue-l1: rgb(230,245,252); +$m-blue-d1: shade($m-blue,15%); +$m-blue-s1: saturate($m-blue,15%); +$m-pink: rgb(204,51,102); + +$m-base-font-size: em(15); + + +$base-font-color: rgb(60,60,60); +$baseFontColor: rgb(60,60,60); +$lighter-base-font-color: rgb(100,100,100); $text-color: $dark-gray; +$body-font-family: $sans-serif; +$body-font-size: em(14); +$body-line-height: golden-ratio(.875em, 1); diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index b5c93f8e14..e27a6e99d8 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -61,10 +61,10 @@ nav.course-material { } header.global.slim { - border-bottom: 1px solid $outer-border-color; @include box-shadow(0 1px 2px rgba(0, 0, 0, .1)); height: 50px; - @include linear-gradient(top, #fff, #eee); + border-bottom: 1px solid $outer-border-color; + background: $white; .guest .secondary { margin-right: 0; @@ -111,7 +111,7 @@ header.global.slim { margin-right: 20px; padding-right: 20px; - &::before { + &:before { @extend .faded-vertical-divider; content: ""; display: block; @@ -122,7 +122,7 @@ header.global.slim { width: 1px; } - &::after { + &:after { @extend .faded-vertical-divider-light; content: ""; display: block; @@ -134,7 +134,7 @@ header.global.slim { } } - .find-courses-button { + .nav-global { display: none; } diff --git a/lms/static/sass/multicourse/_account.scss b/lms/static/sass/multicourse/_account.scss new file mode 100644 index 0000000000..eab8cbe66b --- /dev/null +++ b/lms/static/sass/multicourse/_account.scss @@ -0,0 +1,620 @@ +// plus on button +// border radius on inputs + +// Account-Centric (Login/Register) +// ===== + +// page-level +.view-register, .view-login, .view-passwordreset { + background: $white; + + + + // edx.org - marketing typography + .heading-1, .heading-2, .heading-3, .heading-4, .heading-5, .body-text-emphasized, .body-text, .button-primary, .button-secondary { + display: block; + font-family: $sans-serif; + line-height: lh(1); + } + + .heading-2 { + font-size: 25px; + margin: 0 0 $baseline 0; + font-weight: 300; + text-transform: uppercase; + color: $m-blue; + } + + .heading-3 { + font-size: 21px; + margin: 0 0 $baseline 0; + font-weight: 300; + color: $m-gray-d2; + } + + .heading-4 { + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0 !important; + color: $m-blue-s1; + } + + .heading-5 { + } + + // specific examples - body + .body-text-emphasized { + font-size: 18px; + margin: 0 0 $baseline 0; + font-weight: 300; + color: $m-gray-a1; + font-family: 'Open Sans', sans-serif; + line-height: lh(1.1); + } + + .body-text { + font-size: 15px; + margin: 0 0 $baseline 0; + color: $m-gray-a1; + line-height: lh(1); + } + + // specific examples - buttons + .button-primary { + @include border-radius(0); + @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%); + display: inline-block; + padding: $baseline/2 $baseline*2.5; + text-transform: lowercase; + color: $white; + letter-spacing: 0.1rem; + font-weight: 500; + cursor: pointer; + text-align: center; + border: none !important; + text-shadow: none; + letter-spacing: 0; + font-size: 16px; + box-shadow: none !important; + } + + .button-secondary { + @include linear-gradient($m-gray 5%, $m-gray-d1 95%); + display: inline-block; + padding: $baseline/2 $baseline*2.5; + text-transform: lowercase; + color: $white; + letter-spacing: 0.1rem; + font-weight: 600; + cursor: pointer; + text-align: center; + border: none !important; + text-shadow: none; + letter-spacing: 0; + font-size: 16px; + box-shadow: 0 !important; + } + + // layout + .content-wrapper { + background: $m-gray-l2; + padding-bottom: 0; + } + + .container, .introduction { + @include box-sizing(border-box); + @include clearfix; + margin: 0 auto; + width: 960px; + background: $white; + } + + .container { + padding: $baseline $baseline ($baseline*2) $baseline; + } + + .introduction { + padding: ($baseline*2) $baseline 0 $baseline; + + header h1 { + @extend .heading-2; + margin-bottom: $baseline; + padding-bottom: $baseline; + text-align: left; + } + } +} + +// shared +.login, .register, .passwordreset, #forgot-password-modal #password-reset { + + // reset - horrible, but necessary + p, ol, ul, h1, h2, h3, h4, h5, h6, label, input, textarea { + @extend .body-text; + } + + h1, h2, h3, h4, h5, h6 { + letter-spacing: 0; + } + + a { + @include transition(color 0.15s ease-in-out, border 0.15s ease-in-out); + + &:link, &:visited, &:hover, &:active { + color: $m-blue; + text-decoration: none !important; + font-family: $sans-serif; + } + + &:hover, &:active { + border-bottom: 1px dotted $m-blue-s1; + color: $m-blue-s1; + } + } + + strong { + font-weight: 600; + } + + // basic layout + .content, aside { + @include box-sizing(border-box); + margin: $baseline 0 0 0; + } + + .content { + margin-right: ($baseline*2); + width: 600px; + float: left; + } + + aside { + width: 280px; + float: left; + + p, ol, ul { + font-size: 14px !important; + } + } + + // content + .content { + } + + // aside + aside { + + .cta { + margin: 0 0 ($baseline*2) 0; + + &:last-child { + margin-bottom: 0; + } + + h3 { + @extend .heading-4; + margin: 0 0 ($baseline/4) 0; + } + } + } + + // forms + form { + + .instructions { + @extend .body-text-emphasized; + margin-bottom: $baseline; + } + + fieldset { + margin: 0; + padding-top: 0; + padding-bottom: $baseline; + } + + .list-input { + margin: 0; + padding: 0; + list-style: none; + } + + // field groups + .field-group { + @include clearfix(); + margin: 0 0 $baseline 0; + + .field { + display: block; + float: left; + border-bottom: none; + margin: 0 ($baseline*1.5) 0 0; + padding-bottom: 0; + + input, textarea { + width: 100%; + font-weight: 600; + } + } + + &:last-child { + margin-bottom: 0; + } + } + + // individual fields + .field { + margin: 0 0 $baseline 0; + + // elements + label, input, textarea { + @include border-radius(0); + display: block; + height: auto; + font-family: $sans-serif; + font-style: normal; + font-weight: 500; + color: $m-gray-d2; + } + + label { + @include transition(color 0.15s ease-in-out); + margin: 0 0 ($baseline/4) 0; + color: tint($black, 20%); + } + + .tip { + @include transition(color 0.15s ease-in-out); + display: block; + margin-top: ($baseline/4); + color: tint($m-gray, 50%); + font-size: em(13); + } + + input, textarea { + width: 100%; + margin: 0; + padding: ($baseline/2) ($baseline*.75); + + &.long { + width: 100%; + } + + &.short { + width: 25%; + } + } + + textarea.long { + height: ($baseline*5); + } + + &:last-child { + margin-bottom: 0; + } + + // types - password + + // types - select + &.select { + + select { + width: 100%; + } + } + + // types - checkboxes/radio buttons + &.checkbox { + + input[type="checkbox"] { + display: inline-block; + width: auto; + margin-right: ($baseline/4); + } + + label { + display: inline-block; + } + } + + // states - all + &.disabled, &.submitted { + color: rgba(0,0,0,.25); + + label { + cursor: text; + + &:after { + margin-left: ($baseline/4); + } + } + + textarea, input { + background: $white; + color: rgba(0,0,0,.25); + } + } + + // states - focused + &.is-focused { + + label { + color: $m-blue-s1; + } + + .tip { + color: $m-blue-s1; + } + } + + // states - disabled + &.disabled { + label:after { + color: rgba(0,0,0,.35); + content: "(Disabled Currently)"; + } + } + + &.error { + + label { + color: $red; + } + + input, textarea { + border-color: tint($red,50%); + } + } + + &.required { + + label { + font-weight: 600; + + a { + font-weight: 600 !important; + } + } + + label:after { + margin-left: ($baseline/4); + content: "*"; + } + } + } + } + + // forms - actions + .form-actions { + @include clearfix(); + + button[type="submit"] { + @extend .button-primary; + + &:disabled, &.is-disabled { + opacity: 0.3; + cursor: default !important; + } + } + + .action-primary { + float: left; + width: flex-grid(8,8); + margin-right: flex-gutter(0); + } + + .action-secondary { + display: block; + float: right; + width: flex-grid(3,8); + margin: $baseline $baseline 0 0; + font-size: em(14); + text-align: right; + } + + &.error { + + } + } + + // forms - messages/status + .status { + @include box-sizing(border-box); + margin: 0 0 $baseline 0; + border-bottom: 3px solid shade($yellow, 10%); + padding: $baseline $baseline; + background: tint($yellow,20%); + + .message-title { + @extend .heading-4; + margin: 0 0 ($baseline/4) 0; + font-size: em(14); + font-weight: 600; + color: $m-gray-d2 !important; + } + + .message-copy { + @extend .body-text; + margin: 0 !important; + padding: 0; + list-style: none; + + li { + margin: 0 0 ($baseline/4) 0; + } + } + } + + .submission-error, .system-error { + @include box-shadow(inset 0 -1px 2px 0 tint($red, 85%)); + border-bottom: 3px solid shade($red, 10%); + background: tint($red,95%); + + .message-title { + color: shade($red, 10%) !important; + } + + .message-copy { + + } + } + + // misc + .orn-plus { + color: $white; + padding: 0 $baseline/4; + } + + #register-form, #login-form, #passwordreset-form { + + .status.message { + display: none; + + &.is-shown { + display: block; + } + } + } +} + +// ===== + +// login +.view-login { + + header.global .nav-courseware .cta-login { + display: none; + } + + .introduction { + padding: 0; + + header { + height: 120px; + border-bottom: 1px solid $m-gray; + background: transparent url("../images/bg-banner-login.png") 0 0 no-repeat; + } + } +} + +// register +.view-register { + + .introduction { + padding: 0; + + header { + height: 120px; + border-bottom: 1px solid $m-gray; + background: transparent url("../images/bg-banner-register.png") 0 0 no-repeat; + } + } +} + +// password reset +.view-passwordreset { + background: $m-gray-l2; + + header.global { + + h1 { + float: none; + } + } + + .introduction { + width: auto; + padding: 0; + + header h1 { + margin: 0; + } + } + + .content { + margin-top: 0; + } +} + +// modal password reset form +#forgot-password-modal { + @include border-radius(2px); + + + .inner-wrapper { + @include border-radius(2px); + background: $white; + padding-bottom: 0 !important; + } + + #password-reset { + padding: $baseline; + + header { + margin: 0; + padding: 0; + + &:before { + background-image: none; + } + + h2 { + @extend .heading-2; + text-align: left; + } + } + + .message { + margin: $baseline 0 0 0; + } + + fieldset { + margin-bottom: ($baseline/2); + padding: 0; + } + + .instructions p { + margin-bottom: ($baseline/4); + } + + form { + @include border-radius(0); + @include box-shadow(none); + margin: 0; + border: none; + padding: 0; + + .field { + + &.text, &.email, &.textarea { + + input { + background: #fafafa; + margin-bottom: 0; + } + } + } + + .form-actions { + padding: 0 !important; + + .action-primary { + float: none; + display: block !important; + width: 100%; + } + } + } + } + + .modal-form-error { + @extend .body-text; + @include box-shadow(inset 0 -1px 2px 0 tint($red, 85%)); + @include box-sizing(border-box); + margin: $baseline 0 ($baseline/2) 0 !important; + padding: $baseline; + border: none; + border-bottom: 3px solid shade($red, 10%); + background: tint($red,95%); + } +} diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss index 0982577f42..195760721e 100644 --- a/lms/static/sass/multicourse/_course_about.scss +++ b/lms/static/sass/multicourse/_course_about.scss @@ -154,6 +154,15 @@ @include transition(); width: flex-grid(5, 8); } + + #register_error { + background: $error-red; + border: 1px solid rgb(202, 17, 17); + color: rgb(143, 14, 14); + display: none; + padding: 12px; + margin-top: 5px; + } } } diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 4555a426d3..cc54b9b242 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -1,6 +1,6 @@ .dashboard { @include clearfix; - padding: 60px 0px 120px; + padding: 60px 0 0 0; .dashboard-banner { background: $yellow; @@ -327,7 +327,7 @@ color: $lighter-base-font-color; } - h3 a { + h3 a, h3 span { display: block; margin-bottom: 10px; font-family: $sans-serif; diff --git a/lms/static/sass/multicourse/_password_reset.scss b/lms/static/sass/multicourse/_password_reset.scss index a2365e3e3e..9f145351d1 100644 --- a/lms/static/sass/multicourse/_password_reset.scss +++ b/lms/static/sass/multicourse/_password_reset.scss @@ -73,6 +73,7 @@ input[type="email"], input[type="text"], input[type="password"] { + border: 1px solid red !important; background: rgb(255,255,255); display: block; height: 45px; diff --git a/lms/static/sass/multicourse/_testcenter-register.scss b/lms/static/sass/multicourse/_testcenter-register.scss index 6d85fc167f..01405d7fc1 100644 --- a/lms/static/sass/multicourse/_testcenter-register.scss +++ b/lms/static/sass/multicourse/_testcenter-register.scss @@ -1,10 +1,5 @@ -// ========== - -$baseline: 20px; -$yellow: rgb(255, 235, 169); -$red: rgb(178, 6, 16); - -// ========== +// Pearson VUE Test Center Registration +// ===== .testcenter-register { @include clearfix; diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index a418b887ad..d891ff408b 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -1,179 +1,162 @@ -footer { - background: transparent; - border-top: 1px solid rgb(200,200,200); - @include box-shadow(inset 0 1px 3px 0 rgba(0,0,0, 0.1)); - margin: 0 auto; - width: flex-grid(12); +.wrapper-footer { + @include box-shadow(0 -1px 5px 0 rgba(0,0,0, 0.1)); + border-top: 1px solid tint($m-gray,50%); + padding: 25px ($baseline/2) ($baseline*1.5) ($baseline/2); + background: $white; - &.fixed-bottom { - bottom: 0px; - max-width: 100%; - position: absolute; - } - - nav { - max-width: 1200px; - margin: 0 auto; - padding: 30px 10px 0; + footer { + @include clearfix(); max-width: grid-width(12); min-width: 760px; + width: flex-grid(12); + margin: 0 auto; - .top { - border-bottom: 1px solid rgb(200,200,200); - @include clearfix; - padding-bottom: 30px; - width: flex-grid(12); - text-align: center; + p, ol, ul { + font-family: $sans-serif; + } - ol { - float: right; + a { + @include transition(color 0.15s ease-in-out, border 0.15s ease-in-out); + + &:link, &:visited, &:hover, &:active { + border-bottom: none; + color: $m-blue; + text-decoration: none !important; + font-family: $sans-serif; + } + + &:hover, &:active { + border-bottom: 1px dotted $m-blue-s1; + color: $m-blue-s1; + } + } + + // colophon + .colophon { + margin-right: flex-gutter(2); + width: flex-grid(6,12); + float: left; + + .nav-colophon { + @include clearfix(); + margin: ($baseline/4) 0 ($baseline*1.5) 0; li { - @include inline-block; - list-style: none; - padding: 0px 15px; - position: relative; - vertical-align: middle; - - &::after { - @extend .faded-vertical-divider; - content: ""; - display: block; - height: 30px; - right: 0px; - position: absolute; - top: -5px; - width: 1px; - } - - a:link, a:visited { - color: $lighter-base-font-color; - padding: 6px 0px; - } - } - } - - .primary { - @include clearfix; - float: left; - - a.logo { - @include background-image(url('/static/images/logo.png')); - background-position: 0 -24px; - background-repeat: no-repeat; - @include inline-block; - height: 22px; - margin-right: 15px; - margin-top: 2px; - padding-right: 15px; - position: relative; - width: 47px; - vertical-align: middle; - @include transition(none); - - &:hover { - background-position: 0 0; - } - - &::after { - @extend .faded-vertical-divider; - content: ""; - display: block; - height: 30px; - right: 0px; - position: absolute; - top: -3px; - width: 1px; - } - } - - a { - color: $lighter-base-font-color; - @include inline-block; - margin-right: 20px; - padding-top: 2px; - vertical-align: middle; - - &:hover { - color: $base-font-color; - text-decoration: none; - } - } - } - - .social { - float: right; - - &.social { - border: none; - margin: 0 0 0 5px; - padding: 0; + float: left; + margin-right: ($baseline*0.75); a { - @include inline-block; - opacity: 0.3; - @include transition(all, 0.1s, linear); + color: tint($black, 20%); - &:hover { - opacity: 1; + &:hover, &:active { + color: $m-blue-s1; } } + + &:last-child { + margin-right: 0; + } + } + } + + .colophon-about { + @include clearfix(); + + img { + width: 68px; + height: 34px; + margin-right: 0; + float: left; + } + + p { + float: left; + width: 460px; + margin-left: $baseline; + padding-left: $baseline; + font-size: em(13); + background: transparent url(/static/images/bg-footer-divider.jpg) 0 0 no-repeat; } } } - .bottom { - @include clearfix; - opacity: 0.8; - padding: 10px 0px 30px; - @include transition(all, 0.15s, linear); - width: flex-grid(12); + // references + .references { + margin: -10px 0 0 0; + width: flex-grid(4,12); + float: right; - &:hover { - opacity: 1; + .nav-social { + margin: 0; + text-align: right; + + li { + margin-right: ($baseline/10); + display: inline-block; + + &:last-child { + margin-right: 0; + } + + a { + display: block; + + &:hover, &:active { + border: none; + } + } + + img { + display: block; + } + } } .copyright { - float: left; - - p { - color: $lighter-base-font-color; - font-style: italic; - @include inline-block; - margin: 0 auto; - padding-top: 1px; - text-align: center; - vertical-align: middle; - - a { - color: $lighter-base-font-color; - font-style: italic; - margin-left: 5px; - - &:hover { - color: $blue; - } - } - } + margin: -2px 0 8px 0; + font-size: em(11); + color: tint($m-gray,50%); + text-align: right; } - .secondary { - float: right; - text-align: left; + .nav-legal { + @include clearfix(); + text-align: right; - a { - color: $lighter-base-font-color; - font-family: $serif; - font-style: italic; - line-height: 1.6em; - margin-left: 20px; - text-transform: lowercase; + li { + display: inline-block; + font-size: em(11); - &:hover { - color: $blue; + a { + display: block; + } + } + + .nav-legal-01 a { + + &:after { + margin-left: 5px; + content: "-"; } } } } + } } + +// marketing site design syncing +.view-register, .view-login { + + .wrapper-footer footer { + width: 960px; + + .colophon-about img { + margin-top: ($baseline*1.5); + } + + .colophon-about p { + width: 360px; + } + } +} \ No newline at end of file diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 688ffbf57e..5eb453448c 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -1,8 +1,8 @@ header.global { - border-bottom: 1px solid rgb(190,190,190); + border-bottom: 1px solid $m-gray; @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); - @include background-image(linear-gradient(-90deg, rgba(255,255,255, 1), rgba(230,230,230, 0.9))); - height: 68px; + background: $white; + height: 76px; position: relative; width: 100%; z-index: 10; @@ -11,40 +11,17 @@ header.global { @include clearfix; height: 40px; margin: 0 auto; - max-width: 1200px; - padding: 14px 10px 0px; + padding: 18px 10px 0px; max-width: grid-width(12); min-width: 760px; + width: flex-grid(12); } h1.logo { float: left; - margin: 0px 15px 0px 0px; - padding-right: 20px; + margin: -2px 39px 0px 0px; position: relative; - &::before { - @extend .faded-vertical-divider; - content: ""; - display: block; - height: 50px; - position: absolute; - right: 1px; - top: -8px; - width: 1px; - } - - &::after { - @extend .faded-vertical-divider-light; - content: ""; - display: block; - height: 50px; - position: absolute; - right: 0px; - top: -12px; - width: 1px; - } - a { display: block; } @@ -251,4 +228,104 @@ header.global { } } } + + .nav-global { + margin-top: ($baseline/2); + list-style: none; + + li { + display: inline-block; + margin: 0 $baseline+1 0 0; + font-size: em(14); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0 !important; + + &:last-child { + margin-right: 0; + } + + a { + display:block; + padding: ($baseline/4); + color: $m-gray-d1; + font-weight: 600; + + &:hover, &:active { + text-decoration: none; + color: $m-blue-s1; + } + } + + &.active { + + a { + text-decoration: none; + color: $m-blue-s1; + } + } + } + + // logged in + &.authenticated { + + } + } + + .nav-courseware { + float: right; + margin-top: ($baseline/4); + list-style: none; + + li { + display: inline-block; + + a { + @include border-radius(0); + @include linear-gradient($m-blue-s1 5%, $m-blue-d1 95%); + display: inline-block; + padding: $baseline/2 $baseline*2.5; + text-transform: lowercase; + color: $white; + letter-spacing: 0.1rem; + font-weight: 300; + cursor: pointer; + text-align: center; + border: none !important; + text-shadow: none; + letter-spacing: 0.1rem; + font-size: 14px; + box-shadow: none !important; + + &:hover { + text-decoration: none; + } + } + } + + // logged in + &.authenticated { + + } + } } + +// marketing site design syncing +.view-register, .view-login { + + header.global nav { + width: 960px; + } +} + +// page-based nav states +.view-howitworks .nav-global-01, +.view-courses .nav-global-02, +.view-schools .nav-global-03, +.view-register .nav-global-04 { + + a { + text-decoration: none; + color: $m-blue-s1 !important; + } +} \ No newline at end of file diff --git a/lms/templates/accounts_login.html b/lms/templates/accounts_login.html deleted file mode 100644 index db9cca2b22..0000000000 --- a/lms/templates/accounts_login.html +++ /dev/null @@ -1,35 +0,0 @@ -<%! from django.core.urlresolvers import reverse %> -<%inherit file="main.html" /> -<%namespace name='static' file='static_content.html'/> - -<%block name="headextra"> - - - - - - diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index dc1dc17532..941bf61698 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -13,42 +13,26 @@ <%block name="js_extra"> - % if not registered: - %if user.is_authenticated(): - ## If the user is authenticated, clicking the enroll button just submits a form - - %else: - ## If the user is not authenticated, clicking the enroll button pops up the register - ## field. We also slip in the registration fields into the login/register fields so - ## the user is automatically registered after logging in / registering - - %endif - %endif + $('#class_enroll_form').on('ajax:complete', function(event, xhr) { + if(xhr.status == 200) { + location.href = "${reverse('dashboard')}"; + } else if (xhr.status == 403) { + location.href = "${reverse('register_user')}?course_id=${course.id}&enrollment_action=enroll"; + } else { + $('#register_error').html( + (xhr.responseText ? xhr.responseText : 'An error occurred. Please try again later.') + ).css("display", "block"); + } + }); + })(this) + @@ -66,8 +50,7 @@
- %if user.is_authenticated(): - %if registered: + %if user.is_authenticated() and registered: %if show_courseware_link: %endif @@ -76,16 +59,9 @@ View Courseware %endif - %else: - Register for ${course.number} -
- %endif %else: - Log In -% endif - to enroll.'>Register for ${course.number} + Register for ${course.number} +
%endif
@@ -204,5 +180,4 @@ %endif - <%include file="../video_modal.html" /> diff --git a/lms/templates/courseware/mktg_coming_soon.html b/lms/templates/courseware/mktg_coming_soon.html new file mode 100644 index 0000000000..c100c1cb5d --- /dev/null +++ b/lms/templates/courseware/mktg_coming_soon.html @@ -0,0 +1,30 @@ +<%! + from django.core.urlresolvers import reverse + from courseware.courses import course_image_url, get_course_about_section + from courseware.access import has_access +%> +<%namespace name='static' file='../static_content.html'/> + +<%inherit file="../mktg_iframe.html" /> + +<%block name="title">About ${course_id} + +<%block name="bodyclass">view-partial-mktgregister + + +<%block name="headextra"> + <%include file="../google_analytics.html" /> + + +<%block name="content"> + + + + + + + diff --git a/lms/templates/courseware/mktg_course_about.html b/lms/templates/courseware/mktg_course_about.html new file mode 100644 index 0000000000..dc667c850c --- /dev/null +++ b/lms/templates/courseware/mktg_course_about.html @@ -0,0 +1,75 @@ +<%! + from django.core.urlresolvers import reverse + from courseware.courses import course_image_url, get_course_about_section + from courseware.access import has_access +%> +<%namespace name='static' file='../static_content.html'/> + +<%inherit file="../mktg_iframe.html" /> + +<%block name="title">About ${course.number} + +<%block name="bodyclass">view-partial-mktgregister + + +<%block name="headextra"> + <%include file="../google_analytics.html" /> + + +<%block name="js_extra"> + + + +<%block name="content"> + + + + +%if not registered: +
+
+
+ + + +
+
+ +
+
+
+%endif + diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index d23609801f..75c0cafabd 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -59,14 +59,16 @@ $("#unenroll_course_number").text( $(event.target).data("course-number") ); }); - $(document).delegate('#unenroll_form', 'ajax:success', function(data, json, xhr) { - if(json.success) { - location.href="${reverse('dashboard')}"; + $('#unenroll_form').on('ajax:complete', function(event, xhr) { + if(xhr.status == 200) { + location.href = "${reverse('dashboard')}"; + } else if (xhr.status == 403) { + location.href = "${reverse('signin_user')}?course_id=" + + $("#unenroll_course_id").val() + "&enrollment_action=unenroll"; } else { - if($('#unenroll_error').length == 0) { - $('#unenroll_form').prepend(''); - } - $('#unenroll_error').text(json.error).stop().css("display", "block"); + $('#unenroll_error').html( + xhr.responseText ? xhr.responseText : "An error occurred. Please try again later." + ).stop().css("display", "block"); } }); @@ -192,17 +194,20 @@
<% - if has_access(user, course, 'load'): course_target = reverse('info', args=[course.id]) - else: - course_target = reverse('about_course', args=[course.id]) %> - - - + % if course.id in show_courseware_links_for: + + + + % else: +
+ +
+ % endif
@@ -216,7 +221,13 @@ % endif

${get_course_about_section(course, 'university')}

-

${course.number} ${course.display_name_with_default}

+

+ % if course.id in show_courseware_links_for: + ${course.number} ${course.display_name_with_default} + % else: + ${course.number} ${course.display_name_with_default} + % endif +

<% @@ -326,7 +337,9 @@ % else:

Looks like you haven't registered for any courses yet.

- Find courses now! + + Find courses now! +
% endif @@ -355,6 +368,8 @@
+ +
diff --git a/lms/templates/footer.html b/lms/templates/footer.html index 248b1c468c..daad0a2457 100644 --- a/lms/templates/footer.html +++ b/lms/templates/footer.html @@ -2,39 +2,89 @@ <%! from django.core.urlresolvers import reverse %> <%namespace name='static' file='static_content.html'/> -
+
+ - - + + + +
+ + diff --git a/lms/templates/forgot_password_modal.html b/lms/templates/forgot_password_modal.html index f0f571a817..f1b3de7e5b 100644 --- a/lms/templates/forgot_password_modal.html +++ b/lms/templates/forgot_password_modal.html @@ -3,19 +3,28 @@
-

Password reset

-
+

Password Reset

-
-

Enter your e-mail address below, and we will e-mail instructions for setting a new password.

+
+

Please enter your e-mail address below, and we will e-mail instructions for setting a new password.

- - -
- +
+ + +
    +
  1. + + + This is the email address you used to register with edX +
  2. +
+
+ +
+
diff --git a/lms/templates/login.html b/lms/templates/login.html new file mode 100644 index 0000000000..3e33c84b7a --- /dev/null +++ b/lms/templates/login.html @@ -0,0 +1,175 @@ +<%inherit file="main.html" /> + +<%namespace name='static' file='static_content.html'/> +<%! from django.core.urlresolvers import reverse %> +<%block name="title">Log into your edX Account + +<%block name="js_extra"> + + + +
+
+

Log Into Your Account

+
+
+ + + diff --git a/lms/templates/main.html b/lms/templates/main.html index 42d5a71228..313025d09a 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -1,8 +1,18 @@ <%namespace name='static' file='static_content.html'/> +<%! from django.utils import html %> <%block name="title">edX + @@ -48,3 +58,10 @@ <%block name="js_extra"/> + +<%def name="login_query()">${ + "?course_id={0}&enrollment_action={1}".format( + html.escape(course_id), + html.escape(enrollment_action) + ) if course_id and enrollment_action else "" +} diff --git a/lms/templates/mktg_iframe.html b/lms/templates/mktg_iframe.html new file mode 100644 index 0000000000..6d02f3fcc5 --- /dev/null +++ b/lms/templates/mktg_iframe.html @@ -0,0 +1,53 @@ +<%namespace name='static' file='static_content.html'/> + + + + <%block name="title"> + + + + + + <%static:css group='application'/> + <%static:js group='main_vendor'/> + + + + + + <%block name="headextra"/> + + % if not course: + <%include file="google_analytics.html" /> + % endif + + + + + + + + +
+ + <%block name="content"> +
+ + <%static:js group='application'/> + <%block name="js_extra"> + + diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 4bb99d1ebd..82d08f6ca9 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -1,8 +1,6 @@ ## mako -## TODO: Split this into two files, one for people who are authenticated, and -## one for people who aren't. Assume a Course object is passed to the former, -## instead of using settings.COURSE_TITLE <%namespace name='static' file='static_content.html'/> +<%namespace file='main.html' import="login_query"/> <%! from django.core.urlresolvers import reverse @@ -38,19 +36,23 @@ site_status_msg = get_site_status_msg(course_id)
% endif
% if course: @@ -92,8 +107,6 @@ site_status_msg = get_site_status_msg(course_id) % endif %if not user.is_authenticated(): - <%include file="login_modal.html" /> - <%include file="signup_modal.html" /> <%include file="forgot_password_modal.html" /> %endif diff --git a/lms/templates/register.html b/lms/templates/register.html new file mode 100644 index 0000000000..06b6fe169a --- /dev/null +++ b/lms/templates/register.html @@ -0,0 +1,272 @@ +<%inherit file="main.html" /> + +<%namespace name='static' file='static_content.html'/> +<%namespace file='main.html' import="login_query"/> +<%! from django.core.urlresolvers import reverse %> +<%! from django.utils import html %> +<%! from django_countries.countries import COUNTRIES %> +<%! from student.models import UserProfile %> +<%! from datetime import date %> +<%! import calendar %> + +<%block name="title">Register for edX + +<%block name="js_extra"> + + + +
+
+

Register for edX

+
+
+ +
+
+
+

Registration Form

+
+ +
+ + + + + + +

+ Please complete the following fields to register for an edX account.
+ Required fields are noted by bold text and an asterisk (*). +

+ +
+ Required Information + + % if has_extauth_info is UNDEFINED: + +
    +
  1. + + +
  2. +
  3. + + +
  4. +
  5. + + + Will be shown in any discussions or forums you participate in +
  6. +
  7. + + + Needed for any certificates you may earn (cannot be changed later) +
  8. +
+ + % else: + +
+

Welcome ${extauth_email}

+

Enter a public username:

+
+ +
    +
  1. + + + Will be shown in any discussions or forums you participate in +
  2. +
+ + % endif +
+ +
+ Optional Personal Information + +
    +
  1. +
    + + +
    + +
    + + +
    + +
    + + +
    +
  2. +
+
+ +
+ Optional Personal Information + +
    +
  1. + + +
  2. + +
  3. + + +
  4. +
+
+ +
+ Account Acknowledgements + +
    +
  1. +
    + + +
    + +
    + + +
    +
  2. +
+
+ +% if course_id and enrollment_action: + + +% endif + +
+ +
+
+
+ + +
diff --git a/lms/templates/registration/activation_complete.html b/lms/templates/registration/activation_complete.html index 7d3579b34e..7eb805e730 100644 --- a/lms/templates/registration/activation_complete.html +++ b/lms/templates/registration/activation_complete.html @@ -23,7 +23,7 @@ %if user_logged_in: Visit your dashboard to see your courses. %else: - You can now login. + You can now log in. %endif

diff --git a/lms/templates/registration/password_reset_complete.html b/lms/templates/registration/password_reset_complete.html index 0338ce57b0..3847f615b9 100644 --- a/lms/templates/registration/password_reset_complete.html +++ b/lms/templates/registration/password_reset_complete.html @@ -1,9 +1,66 @@ {% load i18n %} +{% load compressed %} +{% load staticfiles %} + + + -

Password reset complete

+ Your Password Reset is Complete -{% block content %} + {% compressed_css 'application' %} -Your password has been set. You may go ahead and log in now. + -{% endblock %} + + + + + + +
+ +
+ +
+
+
+
+

Your Password Reset is Complete

+
+
+ + {% block content %} +
+

Your password has been set. You may go ahead and log in now..

+
+ {% endblock %} +
+
diff --git a/lms/templates/registration/password_reset_confirm.html b/lms/templates/registration/password_reset_confirm.html index e80955180c..5809408dad 100644 --- a/lms/templates/registration/password_reset_confirm.html +++ b/lms/templates/registration/password_reset_confirm.html @@ -1,10 +1,10 @@ {% load compressed %} {% load staticfiles %} - - Reset password - MITx 6.002x + + Reset Your edX Password {% compressed_css 'application' %} @@ -12,55 +12,120 @@ + + - + -
- -
+
+ +
- {% block content %} -
+
+
+
+
+

Reset Your edX Password

+
+
- {% if validlink %} +
+ {% if validlink %} +
+

Password Reset Form

+
-
-

Enter new password

-
-
+
{% csrf_token %} + + -

Please enter your new password twice so we can verify you typed it in correctly.

+ - {% csrf_token %} - {{ form.new_password1.errors }} - - {{ form.new_password1 }} + - {{ form.new_password2.errors }} - - {{ form.new_password2 }} +

+ Please enter your new password twice so we can verify you typed it in correctly.
+ Required fields are noted by bold text and an asterisk (*). +

-
- +
+ Required Information + +
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+ +
+ +
+ + + {% else %} + +
+

Your Password Reset Was Unsuccessful

+
+

The password reset link was invalid, possibly because the link has already been used. Please return to the login page and start the password reset process again.

+ + {% endif %} +
+ +
- {% endblock %} - - - +
diff --git a/lms/urls.py b/lms/urls.py index bf54cfc6c0..3b14b41bd7 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -17,6 +17,8 @@ urlpatterns = ('', # nopep8 url(r'^update_certificate$', 'certificates.views.update_certificate'), url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), + url(r'^login$', 'student.views.signin_user', name="signin_user"), + url(r'^register$', 'student.views.register_user', name="register_user"), url(r'^admin_dashboard$', 'dashboard.views.dashboard'), @@ -35,8 +37,8 @@ urlpatterns = ('', # nopep8 url(r'^accounts/login$', 'student.views.accounts_login', name="accounts_login"), - url(r'^login$', 'student.views.login_user', name="login"), - url(r'^login/(?P[^/]*)$', 'student.views.login_user'), + url(r'^login_ajax$', 'student.views.login_user', name="login"), + url(r'^login_ajax/(?P[^/]*)$', 'student.views.login_user'), url(r'^logout$', 'student.views.logout_user', name='logout'), url(r'^create_account$', 'student.views.create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), @@ -177,11 +179,19 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/?$', 'branding.views.courses', name="courses"), url(r'^change_enrollment$', - 'student.views.change_enrollment_view', name="change_enrollment"), + 'student.views.change_enrollment', name="change_enrollment"), #About the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/about$', 'courseware.views.course_about', name="about_course"), + #View for mktg site (kept for backwards compatibility TODO - remove before merge to master) + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/mktg-about$', + 'courseware.views.mktg_course_about', name="mktg_about_course"), + #View for mktg site + url(r'^mktg/(?P.*)$', + 'courseware.views.mktg_course_about', name="mktg_about_course"), + + #Inside the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/$',