Commit 3a4a1e94 by Jason Bau Committed by Giulio Gratta

initial commit of sneak peek support for courses

* some more tests

* make preview work with shib classes

* Styled unauth access button and banner.
  - renamed "preview" button to "Explore Course" and styled.
  - moved banner from main navigation to course navigation.
  - moved span tag inside link to make clickable area larger

Conflicts:
	common/djangoapps/student/tests/factories.py
	common/djangoapps/student/views.py
	lms/djangoapps/courseware/tests/test_access.py
parent ddb9e9db
......@@ -259,6 +259,8 @@ class ShibSPTest(ModuleStoreTestCase):
'honor_code': 'true'}
# use RequestFactory instead of TestClient here because we want access to request.user
request2 = self.request_factory.post('/create_account', data=postvars)
# saving session because create_account deletes existing sessions (due to logout then login)
saved_eamap = client.session['ExternalAuthMap']
request2.session = client.session
request2.user = AnonymousUser()
with patch('student.views.AUDIT_LOG') as mock_audit_log:
......@@ -307,14 +309,13 @@ class ShibSPTest(ModuleStoreTestCase):
if sn_empty and given_name_empty:
self.assertEqual(profile.name, postvars['name'])
else:
self.assertEqual(profile.name, request2.session['ExternalAuthMap'].external_name)
self.assertEqual(profile.name, saved_eamap.external_name)
self.assertNotIn(u';', profile.name)
else:
self.assertEqual(profile.name, request2.session['ExternalAuthMap'].external_name)
self.assertEqual(profile.name, saved_eamap.external_name)
self.assertEqual(profile.name, identity.get('displayName'))
# clean up for next loop
request2.session['ExternalAuthMap'].delete()
UserProfile.objects.filter(user=user).delete()
Registration.objects.filter(user=user).delete()
user.delete()
......
......@@ -528,7 +528,9 @@ def course_specific_login(request, course_id):
return _redirect_with_get_querydict('signin_user', request.GET)
# now the dispatching conditionals. Only shib for now
if settings.MITX_FEATURES.get('AUTH_USE_SHIB') and course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX):
if (settings.MITX_FEATURES.get('AUTH_USE_SHIB') and
course.enrollment_domain and
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)):
return _redirect_with_get_querydict('shib-login', request.GET)
# Default fallthrough to normal signin page
......@@ -547,7 +549,9 @@ def course_specific_register(request, course_id):
return _redirect_with_get_querydict('register_user', request.GET)
# now the dispatching conditionals. Only shib for now
if settings.MITX_FEATURES.get('AUTH_USE_SHIB') and course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX):
if (settings.MITX_FEATURES.get('AUTH_USE_SHIB') and
course.enrollment_domain and
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)):
# shib-login takes care of both registration and login flows
return _redirect_with_get_querydict('shib-login', request.GET)
......
......@@ -20,8 +20,9 @@ import uuid
from django.conf import settings
from django.contrib.auth.models import User
from django.utils.crypto import get_random_string
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.db import models
from django.db import models, transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.forms import ModelForm, forms
......@@ -123,6 +124,9 @@ class UserProfile(models.Model):
goals = models.TextField(blank=True, null=True)
allow_certificate = models.BooleanField(default=1)
# "nonregistered" users are auto-created and have no meaningful profile info
nonregistered = models.BooleanField(default=False)
def get_meta(self):
js_str = self.meta
if not js_str:
......@@ -135,6 +139,38 @@ class UserProfile(models.Model):
def set_meta(self, js):
self.meta = json.dumps(js)
@classmethod
def get_random_anon_username(cls):
candidate = "anon__{}".format(get_random_string(24)) # django 1.4 has 30 char usernames
while User.objects.filter(username=candidate).exists():
candidate = "anon__{}".format(get_random_string(24)) # get_random_string output is alphanumeric
return candidate
@classmethod
@transaction.commit_on_success
def create_nonregistered_user(cls):
anon_username = cls.get_random_anon_username()
email_split = settings.ANONYMOUS_USER_EMAIL.split('@')
anon_email = "{}+{}@{}".format(email_split[0],
anon_username,
email_split[-1])
anon_user = User(username=anon_username, email=anon_email, is_active=False)
anon_user.save()
profile = UserProfile(user=anon_user, nonregistered=True)
profile.save()
return anon_user
@classmethod
def has_registered(cls, user):
"""
Handles django anonymous users. SHOULD use this to test whether request.user has registered,
i.e. has a profile that says not nonregistered,
instead of directly accessing user.profile.nonregistered,
because if the user is AnonymousUser it won't have a profile.
"""
return hasattr(user, 'profile') and not user.profile.nonregistered
TEST_CENTER_STATUS_ACCEPTED = "Accepted"
TEST_CENTER_STATUS_ERROR = "Error"
......
......@@ -79,6 +79,30 @@ class UserFactory(DjangoModelFactory):
else:
return None
@post_generation
def groups(self, create, extracted, **kwargs):
if extracted is None:
return
if isinstance(extracted, basestring):
extracted = [extracted]
for group_name in extracted:
self.groups.add(GroupFactory.simple_generate(create, name=group_name))
class NonRegisteredUserFactory(UserFactory):
# only difference from UserFactory is the profile has nonregistered bit set
@classmethod
def _after_postgeneration(cls, obj, create, results=None):
if create:
obj.profile.nonregistered = True
obj.profile.save()
class AnonymousUserFactory(Factory):
FACTORY_FOR = AnonymousUser
class AdminFactory(UserFactory):
is_staff = True
......
......@@ -410,7 +410,7 @@ class PaidRegistrationTest(ModuleStoreTestCase):
self.req_factory = RequestFactory()
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
self.assertIsNotNone(self.course)
self.user = User.objects.create(username="jack", email="jack@fake.edx.org")
self.user = UserFactory(username="jack", email="jack@fake.edx.org")
@unittest.skipUnless(settings.MITX_FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
def test_change_enrollment_add_to_cart(self):
......
......@@ -58,6 +58,7 @@ from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement
from courseware.access import has_access
from courseware.models import CoursePreference
from external_auth.models import ExternalAuthMap
import external_auth.views
......@@ -252,7 +253,7 @@ def signin_user(request):
"""
This view will display the non-modal login form
"""
if request.user.is_authenticated():
if UserProfile.has_registered(request.user):
return redirect(reverse('dashboard'))
context = {
......@@ -270,7 +271,7 @@ def register_user(request, extra_context=None):
if settings.MITX_FEATURES.get('USE_CME_REGISTRATION'):
return cme_register_user(request, extra_context=extra_context)
if request.user.is_authenticated():
if UserProfile.has_registered(request.user):
return redirect(reverse('dashboard'))
context = {
......@@ -313,6 +314,9 @@ def complete_course_mode_info(course_id, enrollment):
def dashboard(request):
user = request.user
if not UserProfile.has_registered(user):
logout(request)
return redirect(reverse('dashboard'))
# Build our courses list for the user, but ignore any courses that no longer
# exist (because the course IDs have changed). Still, we don't delete those
# enrollments, because it could have been a data push snafu.
......@@ -374,6 +378,36 @@ def dashboard(request):
return render_to_response('dashboard.html', context)
def _create_and_login_nonregistered_user(request):
new_student = UserProfile.create_nonregistered_user()
new_student.backend = settings.AUTHENTICATION_BACKENDS[0]
login(request, new_student)
request.session.set_expiry(604800) # set session to very long to reduce number of nonreg users created
@require_POST
def setup_sneakpeek(request, course_id):
if not CoursePreference.course_allows_nonregistered_access(course_id):
return HttpResponseForbidden("Cannot access the course")
if not request.user.is_authenticated():
# if there's no user, create a nonregistered user
_create_and_login_nonregistered_user(request)
elif UserProfile.has_registered(request.user):
# registered users can't sneakpeek, so log them out and create a new nonregistered user
logout(request)
_create_and_login_nonregistered_user(request)
can_enroll, error_msg = _check_can_enroll_in_course(request.user,
course_id,
access_type='within_enrollment_period')
if not can_enroll:
log.error(error_msg)
return HttpResponseBadRequest(error_msg)
CourseEnrollment.enroll(request.user, course_id)
return HttpResponse("OK. Allowed sneakpeek")
def try_change_enrollment(request):
"""
This method calls change_enrollment if the necessary POST
......@@ -424,21 +458,16 @@ def change_enrollment(request):
if course_id is None:
return HttpResponseBadRequest(_("Course id not specified"))
if not user.is_authenticated():
if not UserProfile.has_registered(user):
return HttpResponseForbidden()
if action == "enroll":
# Make sure the course exists
# We don't do this check on unenroll, or a bad course id can't be unenrolled from
try:
course = course_from_id(course_id)
except ItemNotFoundError:
log.warning("User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id))
return HttpResponseBadRequest(_("Course id is invalid"))
can_enroll, error_msg = _check_can_enroll_in_course(user, course_id)
if not has_access(user, course, 'enroll'):
return HttpResponseBadRequest(_("Enrollment is closed"))
if not can_enroll:
return HttpResponseBadRequest(error_msg)
# If this course is available in multiple modes, redirect them to a page
# where they can choose which mode they want.
......@@ -458,7 +487,7 @@ def change_enrollment(request):
"run:{0}".format(run)]
)
CourseEnrollment.enroll(user, course.id, mode=current_mode.slug)
CourseEnrollment.enroll(user, course_id, mode=current_mode.slug)
return HttpResponse()
......@@ -494,6 +523,24 @@ def change_enrollment(request):
return HttpResponseBadRequest(_("Enrollment action is invalid"))
def _check_can_enroll_in_course(user, course_id, access_type="enroll"):
"""
Refactored check for user being able to enroll in course
Returns (bool, error_message), where error message is only applicable if bool == False
"""
try:
course = course_from_id(course_id)
except ItemNotFoundError:
log.warning("User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id))
return False, _("Course id is invalid")
if not has_access(user, course, access_type):
return False, _("Enrollment is closed")
return True, ""
def _parse_course_id_from_string(input_str):
"""
Helper function to determine if input_str (typically the queryparam 'next') contains a course_id.
......@@ -587,6 +634,7 @@ def login_user(request, error=""):
try:
# We do not log here, because we have a handler registered
# to perform logging on successful logins.
logout(request)
login(request, user)
if request.POST.get('remember') == 'true':
request.session.set_expiry(604800)
......@@ -926,6 +974,7 @@ def create_account(request, post_override=None):
# logged in until they close the browser. They can't log in again until they click
# the activation link from the email.
login_user = authenticate(username=post_vars['username'], password=post_vars['password'])
logout(request)
login(request, login_user)
request.session.set_expiry(0)
......@@ -1529,4 +1578,3 @@ def change_email_settings(request):
track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard')
return HttpResponse(json.dumps({'success': True}))
......@@ -8,6 +8,7 @@ from mitxmako.shortcuts import render_to_response
import student.views
import branding
import courseware.views
from student.models import UserProfile
from mitxmako.shortcuts import marketing_link
from util.cache import cache_if_anonymous
......@@ -19,7 +20,7 @@ def index(request):
Redirects to main page -- info page if user authenticated, or marketing if not
'''
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
if settings.COURSEWARE_ENABLED and UserProfile.has_registered(request.user):
return redirect(reverse('dashboard'))
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
......
......@@ -13,7 +13,7 @@ from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore import Location
from xmodule.x_module import XModule, XModuleDescriptor
from student.models import CourseEnrollmentAllowed
from student.models import CourseEnrollmentAllowed, UserProfile
from external_auth.models import ExternalAuthMap
from courseware.masquerade import is_masquerading_as_student
from django.utils.timezone import UTC
......@@ -134,10 +134,22 @@ def _has_access_course_desc(user, course, action):
"""
Can this user access the forums in this course?
"""
return (can_load() and \
(CourseEnrollment.is_enrolled(user, course.id) or \
_has_staff_access_to_descriptor(user, course)
))
return (
can_load() and
UserProfile.has_registered(user) and
(CourseEnrollment.is_enrolled(user, course.id) or
_has_staff_access_to_descriptor(user, course))
)
def within_enrollment_period():
"""
Just a time boundary check, handles if start or stop were set to None
"""
now = datetime.now(UTC())
start = course.enrollment_start
end = course.enrollment_end
return (start is None or now > start) and (end is None or now < end)
def can_enroll():
"""
......@@ -163,11 +175,7 @@ def _has_access_course_desc(user, course, action):
else:
reg_method_ok = True #if not using this access check, it's always OK.
now = datetime.now(UTC())
start = course.enrollment_start
end = course.enrollment_end
if reg_method_ok and (start is None or now > start) and (end is None or now < end):
if reg_method_ok and within_enrollment_period():
# in enrollment period, so any user is allowed to enroll.
debug("Allow: in enrollment period")
return True
......@@ -207,6 +215,7 @@ def _has_access_course_desc(user, course, action):
'load_forum': can_load_forum,
'enroll': can_enroll,
'see_exists': see_exists,
'within_enrollment_period': within_enrollment_period,
'staff': lambda: _has_staff_access_to_descriptor(user, course),
'instructor': lambda: _has_instructor_access_to_descriptor(user, course),
}
......@@ -247,6 +256,47 @@ def _has_access_error_desc(user, descriptor, action, course_context):
return _dispatch(checkers, action, user, descriptor)
NONREGISTERED_CATEGORY_WHITELIST = [
"about",
"chapter",
"course",
"course_info",
"problem",
"sequential",
"vertical",
"videoalpha",
# "combinedopenended",
# "discussion",
"html",
# "peergrading",
"static_tab",
"video",
# "annotatable",
"book",
"conditional",
# "crowdsource_hinter",
"custom_tag_template",
# "discuss",
# "error",
"hidden",
"image",
"problemset",
"randomize",
"raw",
"section",
"slides",
"timelimit",
"videodev",
"videosequence",
"word_cloud",
"wrapper",
]
def _can_load_descriptor_nonregistered(descriptor):
return descriptor.category in NONREGISTERED_CATEGORY_WHITELIST
def _has_access_descriptor(user, descriptor, action, course_context=None):
"""
Check if user has access to this descriptor.
......@@ -266,6 +316,10 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
students to see modules. If not, views should check the course, so we
don't have to hit the enrollments table on every module load.
"""
# nonregistered users shouldn't be able to access certain descriptor types
if not UserProfile.has_registered(user):
return _can_load_descriptor_nonregistered(descriptor)
# If start dates are off, can always load
if settings.MITX_FEATURES['DISABLE_START_DATES'] and not is_masquerading_as_student(user):
debug("Allow: DISABLE_START_DATES")
......
......@@ -2,7 +2,7 @@
django admin pages for courseware model
'''
from courseware.models import StudentModule, OfflineComputedGrade, OfflineComputedGradeLog
from courseware.models import StudentModule, OfflineComputedGrade, OfflineComputedGradeLog, CoursePreference
from ratelimitbackend import admin
from django.contrib.auth.models import User
......@@ -11,3 +11,5 @@ admin.site.register(StudentModule)
admin.site.register(OfflineComputedGrade)
admin.site.register(OfflineComputedGradeLog)
admin.site.register(CoursePreference)
\ No newline at end of file
......@@ -232,3 +232,31 @@ class OfflineComputedGradeLog(models.Model):
def __unicode__(self):
return "[OCGLog] %s: %s" % (self.course_id, self.created)
class CoursePreference(models.Model):
"""
This is a place to keep course preferences that are not inherent to the course. Those should be attributes
of the course xmodule (advanced settings).
A good example is whether this course allows nonregistered users to access it.
"""
course_id = models.CharField(max_length=255, db_index=True)
pref_key = models.CharField(max_length=255)
pref_value = models.CharField(max_length=255, null=True)
class Meta:
unique_together = (('course_id', 'pref_key'))
@classmethod
def get_pref_value(cls, course_id, pref_key):
try:
return cls.objects.get(course_id=course_id, pref_key=pref_key).pref_value
except cls.DoesNotExist:
return None
@classmethod
def course_allows_nonregistered_access(cls, course_id):
return bool(cls.get_pref_value(course_id, 'allow_nonregistered_access'))
def __unicode__(self):
return u"{} : {} : {}".format(self.course_id, self.pref_key, self.pref_value)
\ No newline at end of file
......@@ -22,7 +22,7 @@ from courseware.access import has_access
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from courseware.model_data import FieldDataCache
from student.models import UserProfile
from open_ended_grading import open_ended_notifications
import waffle
......@@ -262,6 +262,7 @@ VALID_TAB_TYPES = {
'syllabus': TabImpl(null_validator, _syllabus)
}
NONREGISTERED_TAB_TYPES=['courseware', 'course_info', 'static_tab', 'syllabus']
### External interface below this.
......@@ -321,6 +322,10 @@ def get_course_tabs(user, course, active_page, request):
else:
course_tabs = course.tabs
# handle nonregistered (and anonymous) users
if not UserProfile.has_registered(user):
course_tabs = [tab for tab in course.tabs if tab['type'] in NONREGISTERED_TAB_TYPES]
for tab in course_tabs:
# expect handlers to return lists--handles things that are turned off
# via feature flags, and things like 'textbook' which might generate
......
......@@ -71,7 +71,8 @@ class AccessTestCase(TestCase):
# TODO: override DISABLE_START_DATES and test the start date branch of the method
u = Mock()
d = Mock()
d.start = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) # make sure the start time is in the past
d.category = 'course'
d.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) # make sure the start time is in the past
# Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(u, d, 'load'))
......
......@@ -16,7 +16,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from student.models import CourseEnrollment
from student.tests.factories import AdminFactory
from student.tests.factories import AdminFactory, NonRegisteredUserFactory
from mitxmako.middleware import MakoMiddleware
from xmodule.modulestore import Location
......@@ -367,3 +367,22 @@ class TestAccordionDueDate(BaseDueDateTests):
return views.render_accordion(
self.request, course, course.get_children()[0].id, None, None
)
class TestNonRegisteredUser(TestCase):
"""
Tests nonregistered (auto-created) users
"""
def setUp(self):
self.request_factory = RequestFactory()
self.user = NonRegisteredUserFactory()
self.course_id = "course/id/doesnt_matter"
def test_nonregistered_user_factory(self):
self.assertTrue(self.user.profile.nonregistered)
def test_nonregistered_progress_404(self):
with self.assertRaises(Http404):
req = self.request_factory.get(reverse('progress', args=[self.course_id]))
req.user = self.user
views.progress(req, self.course_id)
\ No newline at end of file
......@@ -9,7 +9,7 @@ from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
from django_future.csrf import ensure_csrf_cookie
......@@ -22,12 +22,13 @@ from courseware.courses import (get_courses, get_course_with_access,
get_courses_by_university, sort_by_announcement)
import courseware.tabs as tabs
from courseware.masquerade import setup_masquerade
from courseware.models import CoursePreference
from courseware.model_data import FieldDataCache
from .module_render import toc_for_course, get_module_for_descriptor, get_module
from courseware.models import StudentModule, StudentModuleHistory
from course_modes.models import CourseMode
from student.models import UserTestGroup, CourseEnrollment
from student.models import UserTestGroup, CourseEnrollment, UserProfile
from util.cache import cache, cache_if_anonymous
from xblock.fragment import Fragment
from xmodule.modulestore import Location
......@@ -596,7 +597,8 @@ def course_about(request, course_id):
raise Http404
course = get_course_with_access(request.user, course_id, 'see_exists')
registered = registered_for_course(course, request.user)
regularly_registered = (registered_for_course(course, request.user) and
UserProfile.has_registered(request.user))
if has_access(request.user, course, 'load'):
course_target = reverse('info', args=[course.id])
......@@ -621,9 +623,20 @@ def course_about(request, course_id):
reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
reg_url=reverse('register_user'), course_id=course.id)
# only allow course sneak peek if
# 1) within enrollment period
# 2) course specifies it's okay
# 3) request.user is not a registered user.
sneakpeek_allowed = (has_access(request.user, course, 'within_enrollment_period') and
CoursePreference.course_allows_nonregistered_access(course_id) and
not UserProfile.has_registered(request.user))
print(sneakpeek_allowed)
return render_to_response('courseware/course_about.html',
{'course': course,
'registered': registered,
'regularly_registered': regularly_registered,
'sneakpeek_allowed': sneakpeek_allowed,
'course_target': course_target,
'registration_price': registration_price,
'in_cart': in_cart,
......@@ -669,7 +682,6 @@ def mktg_course_about(request, course_id):
'course_modes': course_modes,
})
@login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def progress(request, course_id, student_id=None):
......@@ -677,6 +689,9 @@ def progress(request, course_id, student_id=None):
Course staff are allowed to see the progress of students in their class.
"""
if not UserProfile.has_registered(request.user):
raise Http404
course = get_course_with_access(request.user, course_id, 'load', depth=None)
staff_access = has_access(request.user, course, 'staff')
......
......@@ -215,6 +215,9 @@ GENERATE_PROFILE_SCORES = False
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
# Email to give anonymous users. Should be a black-hole email address, but not cause errors when email is sent there
# This is actually just a base email. We'll make it 'noreply+<username>@example.com' to ensure uniqueness
ANONYMOUS_USER_EMAIL = 'noreply@example.com'
############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /edx-platform/lms
......
p.unauth-warning {
padding: 10px;
text-align: center;
font-weight: bold;
background-color: $black;
color: $white;
}
p.unauth-warning a {
font-weight: bold;
padding: 5px;
background-color: $white;
border-radius: 5px;
}
nav.course-material {
@include clearfix;
@include box-sizing(border-box);
......
......@@ -103,6 +103,8 @@
@include box-sizing(border-box);
border-radius: 3px;
display: block;
float: left;
margin-right: flex-gutter(12);
font: normal 1.2rem/1.6rem $sans-serif;
letter-spacing: 1px;
padding: 10px 0px;
......@@ -139,7 +141,7 @@
}
}
span.register, span.add-to-cart {
span.register, span.add-to-cart, span.sneakpeek {
background: $button-archive-color;
border: 1px solid darken($button-archive-color, 50%);
@include box-sizing(border-box);
......@@ -150,9 +152,9 @@
text-transform: uppercase;
text-align: center;
float: left;
margin: 1px flex-gutter(8) 0 0;
margin: 1px 0 0 0;
@include transition(none);
width: flex-grid(5, 8);
width: flex-grid(6);
}
#register_error {
......
......@@ -4,11 +4,14 @@
from courseware.courses import course_image_url, get_course_about_section
from courseware.access import has_access
from django.conf import settings
from student.models import UserProfile
if settings.MITX_FEATURES.get('ENABLE_SHOPPING_CART'):
cart_link = reverse('shoppingcart.views.show_cart')
else:
cart_link = ""
%>
<%namespace name='static' file='../static_content.html'/>
......@@ -31,6 +34,26 @@
event.preventDefault();
});
sneakpeek_handler = function(jqXHR) {
if (jqXHR.status == 200) {
location.href = "${course_target}";
}
else {
$("#register_error")
.html("${_('An error occurred. Please try again later.')}")
.css("display", "block");
}
};
$(".course_sneakpeek").click(function(event) {
$.ajax({
url: "${reverse('course_sneakpeek', args=[course.id])}",
type: "POST",
complete: sneakpeek_handler
});
event.preventDefault();
});
% if settings.MITX_FEATURES.get('ENABLE_SHOPPING_CART') and settings.MITX_FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION'):
add_course_complete_handler = function(jqXHR, textStatus) {
if (jqXHR.status == 200) {
......@@ -146,7 +169,7 @@
</hgroup>
<div class="main-cta">
%if user.is_authenticated() and registered:
%if regularly_registered:
%if show_courseware_link:
<a href="${course_target}">
%endif
......@@ -182,6 +205,11 @@
<a href="#" class="register">
${_("Register for {course.display_number_with_default}").format(course=course) | h}
</a>
% if sneakpeek_allowed and not regularly_registered:
<a href="#" class="course_sneakpeek"</a>
<span class="sneakpeek">${_("Explore Course")}</span>
</a>
% endif
<div id="register_error"></div>
%endif
</div>
......@@ -308,7 +336,7 @@
</section>
</section>
%if not registered:
%if not regularly_registered:
<div style="display: none;">
<form id="class_enroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
<fieldset class="enroll_fieldset">
......
......@@ -12,9 +12,28 @@ def url_class(is_active):
return ""
%>
<%! from courseware.tabs import get_course_tabs %>
<%! from django.utils.translation import ugettext as _ %>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from student.models import UserProfile
%>
<% import waffle %>
% if course and user.is_authenticated() and not UserProfile.has_registered(user):
<p class="unauth-warning">
<%
if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'):
reg_url = reverse('course-specific-register', args=[course.id])
else:
reg_url = reverse('register_user')
%>
${_("Non-registered mode. {tag_start}Register{tag_end} to save your course progress.")\
.format(platform_name=settings.PLATFORM_NAME,
tag_start="<a href='{}'>".format(reg_url),
tag_end="</a>")}
</p>
% endif
<nav class="${active_page} course-material">
<div class="inner-wrapper">
<ol class="course-tabs">
......
......@@ -4,6 +4,7 @@
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from student.models import UserProfile
# App that handles subdomain specific branding
import branding
......@@ -53,7 +54,7 @@ site_status_msg = get_site_status_msg(course_id)
<h2><span class="provider">${course.display_org_with_default | h}:</span> ${course.display_number_with_default | h} ${course.display_name_with_default}</h2>
% endif
% if user.is_authenticated():
% if UserProfile.has_registered(user):
<ol class="left nav-global authenticated">
<%block name="navigation_global_links_authenticated">
......@@ -120,16 +121,19 @@ site_status_msg = get_site_status_msg(course_id)
</%block>
</ol>
<ol class="right nav-courseware">
<li class="nav-courseware-01">
% if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-login" href="${reverse('course-specific-login', args=[course.id])}${login_query()}">${_("Log in")}</a>
% else:
<a class="cta cta-login" href="/login${login_query()}">${_("Log in")}</a>
% endif
% endif
</li>
<li class="nav-courseware-01">
% if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-login" href="${reverse('course-specific-login', args=[course.id])}${login_query()}">${_("Log in")}</a>
% else:
<a class="cta cta-login" href="/login${login_query()}">${_("Log in")}</a>
% endif
% endif
</li>
</ol>
% endif
</nav>
......
......@@ -179,7 +179,6 @@ if settings.COURSEWARE_ENABLED:
'courseware.module_render.modx_dispatch',
name='modx_dispatch'),
# Software Licenses
# TODO: for now, this is the endpoint of an ajax replay
......@@ -205,6 +204,9 @@ if settings.COURSEWARE_ENABLED:
'student.views.change_enrollment', name="change_enrollment"),
url(r'^change_email_settings$', 'student.views.change_email_settings', name="change_email_settings"),
url(r'^course_sneakpeek/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
'student.views.setup_sneakpeek', name="course_sneakpeek"),
#About the course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$',
'courseware.views.course_about', name="about_course"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment