Commit a815003b by Andy Armstrong

Handle anonymous and unenrolled users on the new course home page

LEARNER-1696
parent 65f876d7
......@@ -134,6 +134,8 @@ def anonymous_id_for_user(user, course_id, save=True):
save -- Whether the id should be saved in an AnonymousUserId object.
"""
# This part is for ability to get xblock instance in xblock_noauth handlers, where user is unauthenticated.
assert user
if user.is_anonymous():
return None
......@@ -681,6 +683,8 @@ class PasswordHistory(models.Model):
Returns whether a password has 'expired' and should be reset. Note there are two different
expiry policies for staff and students
"""
assert user
if not settings.FEATURES['ADVANCED_SECURITY']:
return False
......@@ -736,6 +740,8 @@ class PasswordHistory(models.Model):
"""
Verifies that the password adheres to the reuse policies
"""
assert user
if not settings.FEATURES['ADVANCED_SECURITY']:
return True
......@@ -1082,6 +1088,10 @@ class CourseEnrollment(models.Model):
Returns:
Course enrollment object or None
"""
assert user
if user.is_anonymous():
return None
try:
return cls.objects.get(
user=user,
......@@ -1397,9 +1407,6 @@ class CourseEnrollment(models.Model):
`course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall)
"""
if not user.is_authenticated():
return False
else:
enrollment_state = cls._get_enrollment_state(user, course_key)
return enrollment_state.is_active or False
......@@ -1497,6 +1504,8 @@ class CourseEnrollment(models.Model):
Returns:
str: Hash of the user's active enrollments. If the user is anonymous, `None` is returned.
"""
assert user
if user.is_anonymous():
return None
......@@ -1704,6 +1713,10 @@ class CourseEnrollment(models.Model):
Returns the CourseEnrollmentState for the given user
and course_key, caching the result for later retrieval.
"""
assert user
if user.is_anonymous():
return CourseEnrollmentState(None, None)
enrollment_state = cls._get_enrollment_in_request_cache(user, course_key)
if not enrollment_state:
try:
......
......@@ -6,15 +6,19 @@ import copy
import functools
import os
from contextlib import contextmanager
from enum import Enum
from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error
from courseware.tests.factories import StaffFactory
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase
from django.test.utils import override_settings
from mock import patch
from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase, FilteredQueryCountMixin
from openedx.core.lib.tempdir import mkdtemp_clean
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.contentstore.django import _CONTENTSTORE
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SignalHandler, clear_existing_modulestores, modulestore
......@@ -22,6 +26,18 @@ from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK
from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM
class CourseUserType(Enum):
"""
Types of users to be used when testing a course.
"""
ANONYMOUS = 'anonymous'
COURSE_STAFF = 'course_staff'
ENROLLED = 'enrolled'
GLOBAL_STAFF = 'global_staff'
UNENROLLED = 'unenrolled'
UNENROLLED_STAFF = 'unenrolled_staff'
class StoreConstructors(object):
"""Enumeration of store constructor types."""
draft, split = range(2)
......@@ -308,7 +324,36 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin, SignalIsolationMixin):
cls.enable_all_signals()
class SharedModuleStoreTestCase(FilteredQueryCountMixin, ModuleStoreIsolationMixin, CacheIsolationTestCase):
class ModuleStoreTestUsersMixin():
"""
A mixin to help manage test users.
"""
TEST_PASSWORD = 'test'
def create_user_for_course(self, course, user_type=CourseUserType.ENROLLED):
"""
Create a test user for a course.
"""
if user_type is CourseUserType.ANONYMOUS:
return AnonymousUser()
is_enrolled = user_type is CourseUserType.ENROLLED
is_unenrolled_staff = user_type is CourseUserType.UNENROLLED_STAFF
# Set up the test user
if is_unenrolled_staff:
user = StaffFactory(course_key=course.id, password=self.TEST_PASSWORD)
else:
user = UserFactory(password=self.TEST_PASSWORD)
self.client.login(username=user.username, password=self.TEST_PASSWORD)
if is_enrolled:
CourseEnrollment.enroll(user, course.id)
return user
class SharedModuleStoreTestCase(
ModuleStoreTestUsersMixin, FilteredQueryCountMixin, ModuleStoreIsolationMixin, CacheIsolationTestCase
):
"""
Subclass for any test case that uses a ModuleStore that can be shared
between individual tests. This class ensures that the ModuleStore is cleaned
......@@ -391,7 +436,9 @@ class SharedModuleStoreTestCase(FilteredQueryCountMixin, ModuleStoreIsolationMix
super(SharedModuleStoreTestCase, self).setUp()
class ModuleStoreTestCase(FilteredQueryCountMixin, ModuleStoreIsolationMixin, TestCase):
class ModuleStoreTestCase(
ModuleStoreTestUsersMixin, FilteredQueryCountMixin, ModuleStoreIsolationMixin, TestCase
):
"""
Subclass for any test case that uses a ModuleStore.
Ensures that the ModuleStore is cleaned before/after each test.
......
......@@ -113,21 +113,21 @@ def check_course_access(course, user, action, check_if_enrolled=False):
Check that the user has the access to perform the specified action
on the course (CourseDescriptor|CourseOverview).
check_if_enrolled: If true, additionally verifies that the user is either
enrolled in the course or has staff access.
check_if_enrolled: If true, additionally verifies that the user is enrolled.
"""
access_response = has_access(user, action, course, course.id)
# Allow staff full access to the course even if not enrolled
if has_access(user, 'staff', course.id):
return
access_response = has_access(user, action, course, course.id)
if not access_response:
# Deliberately return a non-specific error message to avoid
# leaking info about access control settings
raise CoursewareAccessException(access_response)
if check_if_enrolled:
# Verify that the user is either enrolled in the course or a staff
# member. If the user is not enrolled, raise a Redirect exception
# that will be handled by middleware.
if not ((user.id and CourseEnrollment.is_enrolled(user, course.id)) or has_access(user, 'staff', course)):
# If the user is not enrolled, redirect them to the about page
if not CourseEnrollment.is_enrolled(user, course.id):
raise CourseAccessRedirect(reverse('about_course', args=[unicode(course.id)]))
......
......@@ -115,7 +115,7 @@ class DateSummary(object):
future.
"""
if self.date is not None:
return datetime.now(utc) <= self.date
return datetime.now(utc).date() <= self.date.date()
return False
def deadline_has_passed(self):
......
......@@ -36,6 +36,16 @@ class CoursewareTab(EnrolledTab):
is_default = False
supports_preview_menu = True
@classmethod
def is_enabled(cls, course, user=None):
"""
Returns true if this tab is enabled.
"""
# If this is the unified course tab then it is always enabled
if UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id):
return True
return super(CoursewareTab, cls).is_enabled(course, user)
@property
def link_func(self):
"""
......
"""
integration tests for xmodule
Contains:
1. BaseTestXmodule class provides course and users
for testing Xmodules with mongo store.
"""
from django.core.urlresolvers import reverse
from django.test.client import Client
from edxmako.shortcuts import render_to_string
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from openedx.core.lib.url_utils import quote_slashes
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xblock.field_data import DictFieldData
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE
from xmodule.tests import get_test_system, get_test_descriptor_system
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class BaseTestXmodule(ModuleStoreTestCase):
"""Base class for testing Xmodules with mongo store.
This class prepares course and users for tests:
1. create test course;
2. create, enroll and login users for this course;
Any xmodule should overwrite only next parameters for test:
1. CATEGORY
2. DATA or METADATA
3. MODEL_DATA
4. COURSE_DATA and USER_COUNT if needed
This class should not contain any tests, because CATEGORY
should be defined in child class.
"""
MODULESTORE = TEST_DATA_MONGO_MODULESTORE
USER_COUNT = 2
COURSE_DATA = {}
# Data from YAML common/lib/xmodule/xmodule/templates/NAME/default.yaml
CATEGORY = "vertical"
DATA = ''
# METADATA must be overwritten for every instance that uses it. Otherwise,
# if we'll change it in the tests, it will be changed for all other instances
# of parent class.
METADATA = {}
MODEL_DATA = {'data': '<some_module></some_module>'}
def new_module_runtime(self):
"""
Generate a new ModuleSystem that is minimally set up for testing
"""
return get_test_system(course_id=self.course.id)
def new_descriptor_runtime(self):
runtime = get_test_descriptor_system()
runtime.get_block = modulestore().get_item
return runtime
def initialize_module(self, **kwargs):
kwargs.update({
'parent_location': self.section.location,
'category': self.CATEGORY
})
self.item_descriptor = ItemFactory.create(**kwargs)
self.runtime = self.new_descriptor_runtime()
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
self.item_descriptor._field_data = LmsFieldData(self.item_descriptor._field_data, student_data)
self.item_descriptor.xmodule_runtime = self.new_module_runtime()
self.item_url = unicode(self.item_descriptor.location)
def setup_course(self):
self.course = CourseFactory.create(data=self.COURSE_DATA)
# Turn off cache.
modulestore().request_cache = None
modulestore().metadata_inheritance_cache_subsystem = None
chapter = ItemFactory.create(
parent_location=self.course.location,
category="sequential",
)
self.section = ItemFactory.create(
parent_location=chapter.location,
category="sequential"
)
# username = robot{0}, password = 'test'
self.users = [
UserFactory.create()
for dummy0 in range(self.USER_COUNT)
]
for user in self.users:
CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
# login all users for acces to Xmodule
self.clients = {user.username: Client() for user in self.users}
self.login_statuses = [
self.clients[user.username].login(
username=user.username, password='test')
for user in self.users
]
self.assertTrue(all(self.login_statuses))
def setUp(self):
super(BaseTestXmodule, self).setUp()
self.setup_course()
self.initialize_module(metadata=self.METADATA, data=self.DATA)
def get_url(self, dispatch):
"""Return item url with dispatch."""
return reverse(
'xblock_handler',
args=(unicode(self.course.id), quote_slashes(self.item_url), 'xmodule_handler', dispatch)
)
class XModuleRenderingTestBase(BaseTestXmodule):
def new_module_runtime(self):
"""
Create a runtime that actually does html rendering
"""
runtime = super(XModuleRenderingTestBase, self).new_module_runtime()
runtime.render_template = render_to_string
return runtime
......@@ -7,12 +7,140 @@ from django.contrib import messages
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.client import Client, RequestFactory
from courseware.access import has_access
from courseware.masquerade import handle_ajax, setup_masquerade
from edxmako.shortcuts import render_to_string
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.url_utils import quote_slashes
from student.models import Registration
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xblock.field_data import DictFieldData
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.tests import get_test_descriptor_system, get_test_system
class BaseTestXmodule(ModuleStoreTestCase):
"""Base class for testing Xmodules with mongo store.
This class prepares course and users for tests:
1. create test course;
2. create, enroll and login users for this course;
Any xmodule should overwrite only next parameters for test:
1. CATEGORY
2. DATA or METADATA
3. MODEL_DATA
4. COURSE_DATA and USER_COUNT if needed
This class should not contain any tests, because CATEGORY
should be defined in child class.
"""
MODULESTORE = TEST_DATA_MONGO_MODULESTORE
USER_COUNT = 2
COURSE_DATA = {}
# Data from YAML common/lib/xmodule/xmodule/templates/NAME/default.yaml
CATEGORY = "vertical"
DATA = ''
# METADATA must be overwritten for every instance that uses it. Otherwise,
# if we'll change it in the tests, it will be changed for all other instances
# of parent class.
METADATA = {}
MODEL_DATA = {'data': '<some_module></some_module>'}
def new_module_runtime(self):
"""
Generate a new ModuleSystem that is minimally set up for testing
"""
return get_test_system(course_id=self.course.id)
def new_descriptor_runtime(self):
runtime = get_test_descriptor_system()
runtime.get_block = modulestore().get_item
return runtime
def initialize_module(self, **kwargs):
kwargs.update({
'parent_location': self.section.location,
'category': self.CATEGORY
})
self.item_descriptor = ItemFactory.create(**kwargs)
self.runtime = self.new_descriptor_runtime()
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
self.item_descriptor._field_data = LmsFieldData(self.item_descriptor._field_data, student_data)
self.item_descriptor.xmodule_runtime = self.new_module_runtime()
self.item_url = unicode(self.item_descriptor.location)
def setup_course(self):
self.course = CourseFactory.create(data=self.COURSE_DATA)
# Turn off cache.
modulestore().request_cache = None
modulestore().metadata_inheritance_cache_subsystem = None
chapter = ItemFactory.create(
parent_location=self.course.location,
category="sequential",
)
self.section = ItemFactory.create(
parent_location=chapter.location,
category="sequential"
)
# username = robot{0}, password = 'test'
self.users = [
UserFactory.create()
for dummy0 in range(self.USER_COUNT)
]
for user in self.users:
CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
# login all users for acces to Xmodule
self.clients = {user.username: Client() for user in self.users}
self.login_statuses = [
self.clients[user.username].login(
username=user.username, password='test')
for user in self.users
]
self.assertTrue(all(self.login_statuses))
def setUp(self):
super(BaseTestXmodule, self).setUp()
self.setup_course()
self.initialize_module(metadata=self.METADATA, data=self.DATA)
def get_url(self, dispatch):
"""Return item url with dispatch."""
return reverse(
'xblock_handler',
args=(unicode(self.course.id), quote_slashes(self.item_url), 'xmodule_handler', dispatch)
)
class XModuleRenderingTestBase(BaseTestXmodule):
def new_module_runtime(self):
"""
Create a runtime that actually does html rendering
"""
runtime = super(XModuleRenderingTestBase, self).new_module_runtime()
runtime.render_template = render_to_string
return runtime
class LoginEnrollmentTestCase(TestCase):
......
......@@ -17,7 +17,7 @@ from xblock.fragment import Fragment
from course_api.blocks.tests.helpers import deserialize_usage_key
from courseware.module_render import get_module_for_descriptor_internal
from lms.djangoapps.courseware.tests import XModuleRenderingTestBase
from lms.djangoapps.courseware.tests.helpers import XModuleRenderingTestBase
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xblock_discussion import DiscussionXBlock, loader
from xmodule.modulestore import ModuleStoreEnum
......
......@@ -10,7 +10,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from nose.plugins.attrib import attr
from courseware.tests import BaseTestXmodule
from courseware.tests.helpers import BaseTestXmodule
from courseware.views.views import get_course_lti_endpoints
from openedx.core.lib.url_utils import quote_slashes
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
......
......@@ -22,7 +22,7 @@ from xmodule.modulestore.django import modulestore
from xmodule.video_module.transcripts_utils import TranscriptException, TranscriptsGenerationException
from xmodule.x_module import STUDENT_VIEW
from . import BaseTestXmodule
from .helpers import BaseTestXmodule
from .test_video_xml import SOURCE_XML
TRANSCRIPT = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]}
......
......@@ -25,7 +25,7 @@ from xmodule.video_module import VideoDescriptor, bumper_utils, rewrite_video_ur
from xmodule.video_module.transcripts_utils import Transcript, save_to_store
from xmodule.x_module import STUDENT_VIEW
from . import BaseTestXmodule
from .helpers import BaseTestXmodule
from .test_video_handlers import TestVideo
from .test_video_xml import SOURCE_XML
......
......@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
from xmodule.x_module import STUDENT_VIEW
from . import BaseTestXmodule
from .helpers import BaseTestXmodule
@attr(shard=1)
......
"""
View for Courseware Index
"""
# pylint: disable=attribute-defined-outside-init
import logging
import urllib
# pylint: disable=attribute-defined-outside-init
from datetime import datetime
import waffle
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.utils.timezone import UTC
from django.views.decorators.cache import cache_control
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import View
......
......@@ -87,6 +87,7 @@ from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, course_home_url_name
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
from openedx.features.enterprise_support.api import data_sharing_consent_required
from shoppingcart.utils import is_shopping_cart_enabled
......@@ -327,6 +328,9 @@ def course_info(request, course_id):
# Decide whether or not to show the reviews link in the course tools bar
show_reviews_link = CourseReviewsModuleFragmentView.is_configured()
# Get the course tools enabled for this user and course
course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
context = {
'request': request,
'masquerade_user': user,
......@@ -342,6 +346,8 @@ def course_info(request, course_id):
'dates_fragment': dates_fragment,
'url_to_enroll': url_to_enroll,
'show_reviews_link': show_reviews_link,
'course_tools': course_tools,
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
......
......@@ -370,18 +370,18 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
# course is outside the context manager that is verifying the number of queries,
# and with split mongo, that method ends up querying disabled_xblocks (which is then
# cached and hence not queried as part of call_single_thread).
(ModuleStoreEnum.Type.mongo, False, 1, 5, 3, 13, 1),
(ModuleStoreEnum.Type.mongo, False, 50, 5, 3, 13, 1),
(ModuleStoreEnum.Type.mongo, False, 1, 5, 3, 14, 1),
(ModuleStoreEnum.Type.mongo, False, 50, 5, 3, 14, 1),
# split mongo: 3 queries, regardless of thread response size.
(ModuleStoreEnum.Type.split, False, 1, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, False, 50, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, False, 1, 3, 3, 13, 1),
(ModuleStoreEnum.Type.split, False, 50, 3, 3, 13, 1),
# Enabling Enterprise integration should have no effect on the number of mongo queries made.
(ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 13, 1),
(ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 13, 1),
(ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 14, 1),
(ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 14, 1),
# split mongo: 3 queries, regardless of thread response size.
(ModuleStoreEnum.Type.split, True, 1, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, True, 50, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, True, 1, 3, 3, 13, 1),
(ModuleStoreEnum.Type.split, True, 50, 3, 3, 13, 1),
)
@ddt.unpack
def test_number_of_mongo_queries(
......
......@@ -383,8 +383,8 @@ class ViewsQueryCountTestCase(
return inner
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 4, 31),
(ModuleStoreEnum.Type.split, 3, 12, 31),
(ModuleStoreEnum.Type.mongo, 3, 4, 32),
(ModuleStoreEnum.Type.split, 3, 12, 32),
)
@ddt.unpack
@count_queries
......@@ -392,8 +392,8 @@ class ViewsQueryCountTestCase(
self.create_thread_helper(mock_request)
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 3, 27),
(ModuleStoreEnum.Type.split, 3, 9, 27),
(ModuleStoreEnum.Type.mongo, 3, 3, 28),
(ModuleStoreEnum.Type.split, 3, 9, 28),
)
@ddt.unpack
@count_queries
......
......@@ -681,7 +681,7 @@ def students_update_enrollment(request, course_id):
)
before_enrollment = before.to_dict()['enrollment']
before_allowed = before.to_dict()['allowed']
enrollment_obj = CourseEnrollment.get_enrollment(user, course_id)
enrollment_obj = CourseEnrollment.get_enrollment(user, course_id) if user else None
if before_enrollment:
state_transition = ENROLLED_TO_UNENROLLED
......
......@@ -63,17 +63,25 @@
.list-inline {
&.nav-global {
margin-top: 12px;
margin-bottom: 0;
}
@include margin(0, 0, 0, $baseline/2);
.btn {
text-transform: uppercase;
border: none;
padding: 0;
color: $lms-active-color;
background: transparent;
&.nav-courseware {
margin-top: 5px;
&:hover {
background: transparent;
color: $link-hover;
text-decoration: underline;
}
}
}
.item {
font-weight: font-weight(semi-bold);
text-transform: uppercase;
&.active {
a {
......
......@@ -12,7 +12,6 @@ from django.utils.translation import ugettext as _
from courseware.courses import get_course_info_section, get_course_date_blocks
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG
%>
<%block name="pagetitle">${_("{course_number} Course Info").format(course_number=course.display_number_with_default)}</%block>
......@@ -85,20 +84,15 @@ from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
% if course_tools:
<h3 class="hd hd-3 handouts-header">${_("Course Tools")}</h3>
<div>
<a class="action-show-bookmarks" href="${reverse('openedx.course_bookmarks.home', args=[course.id])}">
<span class="icon fa fa-bookmark" aria-hidden="true"></span>
${_("Bookmarks")}
</a>
% if SHOW_REVIEWS_TOOL_FLAG.is_enabled(course.id) and show_reviews_link:
<a href="${reverse('openedx.course_experience.course_reviews', args=[course.id])}">
<span class="icon fa fa-star" aria-hidden="true"></span>
${_("Reviews")}
% for course_tool in course_tools:
<a href="${course_tool.url(course.id)}">
<span class="icon ${course_tool.icon_classes()}" aria-hidden="true"></span>
${course_tool.title()}
</a>
% endfor
% endif
</div>
% if SelfPacedConfiguration.current().enable_course_home_improvements:
${HTML(dates_fragment.body_html())}
% endif
......
......@@ -165,7 +165,9 @@ from pipeline_mako import render_require_js_path_overrides
</html>
<%def name="login_query()">${
u"?next={0}".format(urlquote_plus(login_redirect_url)) if login_redirect_url else ""
u"?next={next}".format(
next=urlquote_plus(login_redirect_url if login_redirect_url else request.path)
) if (login_redirect_url or request) else ""
}</%def>
<!-- Performance beacon for onload times -->
......
......@@ -13,7 +13,7 @@ from django.utils.translation import ugettext as _
<%block name="navigation_global_links_authenticated">
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE') and not show_program_listing:
<li class="item nav-global-01">
<a href="${marketing_link('COURSES')}">${_('Explore courses')}</a>
<a class="btn" href="${marketing_link('COURSES')}">${_('Explore courses')}</a>
</li>
% endif
% if show_program_listing:
......@@ -28,12 +28,12 @@ from django.utils.translation import ugettext as _
</a>
</li>
% endif
%if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD','') and user.is_staff:
% if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD','') and user.is_staff:
<li class="item">
## Translators: This is short for "System administration".
<a href="${reverse('sysadmin')}">${_("Sysadmin")}</a>
<a class="btn" href="${reverse('sysadmin')}">${_("Sysadmin")}</a>
</li>
%endif
% endif
</%block>
</ol>
......
......@@ -13,15 +13,15 @@ from django.utils.translation import ugettext as _
<%block name="navigation_global_links">
% if static.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)):
<li class="item nav-global-01">
<a href="${marketing_link('HOW_IT_WORKS')}">${_("How it Works")}</a>
<a class="btn" href="${marketing_link('HOW_IT_WORKS')}">${_("How it Works")}</a>
</li>
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
<li class="item nav-global-02">
<a href="${marketing_link('COURSES')}">${_("Courses")}</a>
<a class="btn" href="${marketing_link('COURSES')}">${_("Courses")}</a>
</li>
% endif
<li class="item nav-global-03">
<a href="${marketing_link('SCHOOLS')}">${_("Schools")}</a>
<a class="btn" href="${marketing_link('SCHOOLS')}">${_("Schools")}</a>
</li>
% endif
</%block>
......@@ -35,11 +35,11 @@ from django.utils.translation import ugettext as _
%endif
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<li class="item nav-global-04">
<a class="btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
<a class="btn btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
</li>
% elif static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<li class="item nav-global-04">
<a class="btn-neutral btn-register" href="/register${login_query()}">${_("Register")}</a>
<a class="btn btn-neutral btn-register" href="/register${login_query()}">${_("Register")}</a>
</li>
% endif
% endif
......@@ -51,9 +51,9 @@ from django.utils.translation import ugettext as _
<li class="item nav-courseware-01">
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON'] and not combined_login_and_register:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="btn btn-login" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
<a class="btn btn-brand btn-login" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
% else:
<a class="btn btn-login" href="/login${login_query()}">${_("Sign in")}</a>
<a class="btn brn-brand btn-login" href="/login${login_query()}">${_("Sign in")}</a>
% endif
% endif
</li>
......
......@@ -290,19 +290,21 @@ class CourseWaffleFlag(WaffleFlag):
return None
return course_override_callback
def is_enabled(self, course_id=None):
def is_enabled(self, course_key=None):
"""
Returns whether or not the flag is enabled.
Arguments:
course_id (CourseKey): The course to check for override before
course_key (CourseKey): The course to check for override before
checking waffle.
"""
# validate arguments
assert issubclass(type(course_id), CourseKey), "The course_id '{}' must be a CourseKey.".format(str(course_id))
assert issubclass(type(course_key), CourseKey), "The course_id '{}' must be a CourseKey.".format(
str(course_key)
)
return self.waffle_namespace.is_flag_active(
self.flag_name,
check_before_waffle_callback=self._get_course_override_callback(course_id),
check_before_waffle_callback=self._get_course_override_callback(course_key),
flag_undefined_default=self.flag_undefined_default
)
......@@ -2,9 +2,11 @@
Platform plugins to support course bookmarks.
"""
from courseware.access import has_access
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from openedx.features.course_experience.course_tools import CourseTool
from student.models import CourseEnrollment
class CourseBookmarksTool(CourseTool):
......@@ -14,9 +16,11 @@ class CourseBookmarksTool(CourseTool):
@classmethod
def is_enabled(cls, request, course_key):
"""
Always show the bookmarks tool.
The bookmarks tool is only enabled for enrolled users or staff.
"""
if has_access(request.user, 'staff', course_key):
return True
return CourseEnrollment.is_enrolled(request.user, course_key)
@classmethod
def title(cls):
......
"""
Unit tests for the course bookmarks feature.
"""
import ddt
from django.test import RequestFactory
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import CourseUserType, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ..plugins import CourseBookmarksTool
@ddt.ddt
class TestCourseBookmarksTool(SharedModuleStoreTestCase):
"""
Test the course bookmarks tool.
"""
@classmethod
def setUpClass(cls):
"""
Set up a course to be used for testing.
"""
# setUpClassAndTestData() already calls setUpClass on SharedModuleStoreTestCase
# pylint: disable=super-method-not-called
with super(TestCourseBookmarksTool, cls).setUpClassAndTestData():
with cls.store.default_store(ModuleStoreEnum.Type.split):
cls.course = CourseFactory.create()
with cls.store.bulk_operations(cls.course.id):
# Create a basic course structure
chapter = ItemFactory.create(category='chapter', parent_location=cls.course.location)
section = ItemFactory.create(category='sequential', parent_location=chapter.location)
ItemFactory.create(category='vertical', parent_location=section.location)
@ddt.data(
[CourseUserType.ANONYMOUS, False],
[CourseUserType.ENROLLED, True],
[CourseUserType.UNENROLLED, False],
[CourseUserType.UNENROLLED_STAFF, True],
)
@ddt.unpack
def test_bookmarks_tool_is_enabled(self, user_type, should_be_enabled):
request = RequestFactory().request()
request.user = self.create_user_for_course(self.course, user_type)
self.assertEqual(CourseBookmarksTool.is_enabled(request, self.course.id), should_be_enabled)
......@@ -65,3 +65,11 @@ class CourseToolsPluginManager(PluginManager):
course_tools = cls.get_available_plugins().values()
course_tools.sort(key=lambda course_tool: course_tool.title())
return course_tools
@classmethod
def get_enabled_course_tools(cls, request, course_key):
"""
Returns the course tools applicable to the current user and course.
"""
course_tools = CourseToolsPluginManager.get_course_tools()
return filter(lambda tool: tool.is_enabled(request, course_key), course_tools)
......@@ -8,6 +8,7 @@ from django.utils.translation import ugettext as _
from course_tools import CourseTool
from courseware.courses import get_course_by_id
from student.models import CourseEnrollment
from views.course_reviews import CourseReviewsModuleFragmentView
from views.course_updates import CourseUpdatesFragmentView
......@@ -35,11 +36,14 @@ class CourseUpdatesTool(CourseTool):
@classmethod
def is_enabled(cls, request, course_key):
"""
Returns True if this tool is enabled for the specified course key.
Returns True if the user should be shown course updates for this course.
"""
if not UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key):
return False
if not CourseEnrollment.is_enrolled(request.user, course_key):
return False
course = get_course_by_id(course_key)
has_updates = CourseUpdatesFragmentView.has_updates(request, course)
return UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key) and has_updates
return CourseUpdatesFragmentView.has_updates(request, course)
@classmethod
def url(cls, course_key):
......@@ -72,8 +76,9 @@ class CourseReviewsTool(CourseTool):
"""
Returns True if this tool is enabled for the specified course key.
"""
reviews_configured = CourseReviewsModuleFragmentView.is_configured()
return SHOW_REVIEWS_TOOL_FLAG.is_enabled(course_key) and reviews_configured
if not SHOW_REVIEWS_TOOL_FLAG.is_enabled(course_key):
return False
return CourseReviewsModuleFragmentView.is_configured()
@classmethod
def url(cls, course_key):
......
......@@ -34,3 +34,7 @@ from django.utils.translation import ugettext as _
</div>
</div>
% endfor
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform('.localized-datetime');
</%static:require_module_async>
......@@ -15,7 +15,6 @@ from django_comment_client.permissions import has_permission
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
from openedx.core.djangolib.markup import HTML
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REVIEWS_TOOL_FLAG
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
%>
<%block name="content">
......@@ -64,32 +63,31 @@ from openedx.features.course_experience.course_tools import CourseToolsPluginMan
</div>
% endif
% if outline_fragment:
${HTML(outline_fragment.body_html())}
% endif
</main>
<aside class="course-sidebar layout-col layout-col-a">
<%
course_tools = CourseToolsPluginManager.get_course_tools()
%>
% if course_tools:
<div class="section section-tools">
<h3 class="hd-6">${_("Course Tools")}</h3>
<ul class="list-unstyled">
% for course_tool in course_tools:
% if course_tool.is_enabled(request, course_key):
<li>
<a href="${course_tool.url(course_key)}">
<span class="icon ${course_tool.icon_classes()}" aria-hidden="true"></span>
${course_tool.title()}
</a>
</li>
% endif
% endfor
</ul>
</div>
% endif
% if dates_fragment:
<div class="section section-dates">
${HTML(dates_fragment.body_html())}
</div>
% endif
% if handouts_html:
<div class="section section-handouts">
<h3 class="hd-6">${_("Course Handouts")}</h3>
......@@ -99,6 +97,8 @@ from openedx.features.course_experience.course_tools import CourseToolsPluginMan
</aside>
</div>
</div>
% if course_sock_fragment:
${HTML(course_sock_fragment.body_html())}
% endif
</div>
</%block>
......@@ -151,7 +151,6 @@ from openedx.core.djangolib.markup import HTML, Text
)}
% endif
</div>
</div>
% endif
</main>
......
"""
Test helpers for the course experience.
"""
import datetime
from course_modes.models import CourseMode
TEST_COURSE_PRICE = 50
def add_course_mode(course, upgrade_deadline_expired=False):
"""
Adds a course mode to the test course.
"""
upgrade_exp_date = datetime.datetime.now()
if upgrade_deadline_expired:
upgrade_exp_date = upgrade_exp_date - datetime.timedelta(days=21)
else:
upgrade_exp_date = upgrade_exp_date + datetime.timedelta(days=21)
CourseMode(
course_id=course.id,
mode_slug=CourseMode.VERIFIED,
mode_display_name="Verified Certificate",
min_price=TEST_COURSE_PRICE,
_expiration_datetime=upgrade_exp_date, # pylint: disable=protected-access
).save()
"""
Tests for the course home page.
"""
import ddt
from courseware.tests.factories import StaffFactory
from django.core.urlresolvers import reverse
from django.utils.http import urlquote_plus
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG
from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG, UNIFIED_COURSE_TAB_FLAG
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import CourseUserType, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
from .test_course_updates import create_course_update, remove_course_updates
from .helpers import add_course_mode
from .test_course_updates import create_course_update
TEST_PASSWORD = 'test'
TEST_CHAPTER_NAME = 'Test Chapter'
TEST_WELCOME_MESSAGE = '<h2>Welcome!</h2>'
TEST_UPDATE_MESSAGE = '<h2>Test Update!</h2>'
TEST_COURSE_UPDATES_TOOL = '/course/updates">'
......@@ -32,20 +38,26 @@ def course_home_url(course):
)
class TestCourseHomePage(SharedModuleStoreTestCase):
class CourseHomePageTestCase(SharedModuleStoreTestCase):
"""
Test the course home page.
Base class for testing the course home page.
"""
@classmethod
def setUpClass(cls):
"""Set up the simplest course possible."""
"""
Set up a course to be used for testing.
"""
# setUpClassAndTestData() already calls setUpClass on SharedModuleStoreTestCase
# pylint: disable=super-method-not-called
with super(TestCourseHomePage, cls).setUpClassAndTestData():
with super(CourseHomePageTestCase, cls).setUpClassAndTestData():
with cls.store.default_store(ModuleStoreEnum.Type.split):
cls.course = CourseFactory.create(org='edX', number='test', display_name='Test Course')
with cls.store.bulk_operations(cls.course.id):
chapter = ItemFactory.create(category='chapter', parent_location=cls.course.location)
chapter = ItemFactory.create(
category='chapter',
parent_location=cls.course.location,
display_name=TEST_CHAPTER_NAME,
)
section = ItemFactory.create(category='sequential', parent_location=chapter.location)
section2 = ItemFactory.create(category='sequential', parent_location=chapter.location)
ItemFactory.create(category='vertical', parent_location=section.location)
......@@ -54,9 +66,12 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
@classmethod
def setUpTestData(cls):
"""Set up and enroll our fake user in the course."""
cls.staff_user = StaffFactory(course_key=cls.course.id, password=TEST_PASSWORD)
cls.user = UserFactory(password=TEST_PASSWORD)
CourseEnrollment.enroll(cls.user, cls.course.id)
class TestCourseHomePage(CourseHomePageTestCase):
def setUp(self):
"""
Set up for the tests.
......@@ -109,3 +124,90 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
with check_mongo_calls(4):
url = course_home_url(self.course)
self.client.get(url)
@ddt.ddt
class TestCourseHomePageAccess(CourseHomePageTestCase):
"""
Test access to the course home page.
"""
def setUp(self):
super(TestCourseHomePageAccess, self).setUp()
# Make this a verified course so that an upgrade message might be shown
add_course_mode(self.course, upgrade_deadline_expired=False)
# Add a welcome message
create_course_update(self.course, self.staff_user, TEST_WELCOME_MESSAGE)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data(
CourseUserType.ANONYMOUS,
CourseUserType.ENROLLED,
CourseUserType.UNENROLLED,
CourseUserType.UNENROLLED_STAFF,
)
def test_home_page(self, user_type):
self.user = self.create_user_for_course(self.course, user_type)
# Render the course home page
url = course_home_url(self.course)
response = self.client.get(url)
# Verify that the course tools and dates are always shown
self.assertContains(response, 'Course Tools')
self.assertContains(response, 'Today is')
# Verify that the outline, start button, course sock, and welcome message
# are only shown to enrolled users.
is_enrolled = user_type is CourseUserType.ENROLLED
is_unenrolled_staff = user_type is CourseUserType.UNENROLLED_STAFF
expected_count = 1 if (is_enrolled or is_unenrolled_staff) else 0
self.assertContains(response, TEST_CHAPTER_NAME, count=expected_count)
self.assertContains(response, 'Start Course', count=expected_count)
self.assertContains(response, 'Learn About Verified Certificate', count=expected_count)
self.assertContains(response, TEST_WELCOME_MESSAGE, count=expected_count)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=False)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data(
CourseUserType.ANONYMOUS,
CourseUserType.ENROLLED,
CourseUserType.UNENROLLED,
CourseUserType.UNENROLLED_STAFF,
)
def test_home_page_not_unified(self, user_type):
"""
Verifies the course home tab when not unified.
"""
self.user = self.create_user_for_course(self.course, user_type)
# Render the course home page
url = course_home_url(self.course)
response = self.client.get(url)
# Verify that the course tools and dates are always shown
self.assertContains(response, 'Course Tools')
self.assertContains(response, 'Today is')
# Verify that welcome messages are never shown
self.assertNotContains(response, TEST_WELCOME_MESSAGE)
# Verify that the outline, start button, course sock, and welcome message
# are only shown to enrolled users.
is_enrolled = user_type is CourseUserType.ENROLLED
is_unenrolled_staff = user_type is CourseUserType.UNENROLLED_STAFF
expected_count = 1 if (is_enrolled or is_unenrolled_staff) else 0
self.assertContains(response, TEST_CHAPTER_NAME, count=expected_count)
self.assertContains(response, 'Start Course', count=expected_count)
self.assertContains(response, 'Learn About Verified Certificate', count=expected_count)
def test_sign_in_button(self):
"""
Verify that the sign in button will return to this page.
"""
url = course_home_url(self.course)
response = self.client.get(url)
self.assertContains(response, '/login?next={url}'.format(url=urlquote_plus(url)))
......@@ -5,7 +5,6 @@ import datetime
import ddt
import json
from markupsafe import escape
from unittest import skip
from django.core.urlresolvers import reverse
from pyquery import PyQuery as pq
......
......@@ -2,7 +2,6 @@
Tests for course verification sock
"""
import datetime
import ddt
from course_modes.models import CourseMode
......@@ -12,11 +11,11 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from .helpers import add_course_mode
from .test_course_home import course_home_url
TEST_PASSWORD = 'test'
TEST_VERIFICATION_SOCK_LOCATOR = '<div class="verification-sock"'
TEST_COURSE_PRICE = 50
@ddt.ddt
......@@ -34,10 +33,10 @@ class TestCourseSockView(SharedModuleStoreTestCase):
cls.verified_course_update_expired = CourseFactory.create()
cls.verified_course_already_enrolled = CourseFactory.create()
# Assign each verifiable course a upgrade deadline
cls._add_course_mode(cls.verified_course, upgrade_deadline_expired=False)
cls._add_course_mode(cls.verified_course_update_expired, upgrade_deadline_expired=True)
cls._add_course_mode(cls.verified_course_already_enrolled, upgrade_deadline_expired=False)
# Assign each verifiable course an upgrade deadline
add_course_mode(cls.verified_course, upgrade_deadline_expired=False)
add_course_mode(cls.verified_course_update_expired, upgrade_deadline_expired=True)
add_course_mode(cls.verified_course_already_enrolled, upgrade_deadline_expired=False)
def setUp(self):
super(TestCourseSockView, self).setUp()
......@@ -47,7 +46,9 @@ class TestCourseSockView(SharedModuleStoreTestCase):
CourseEnrollmentFactory.create(user=self.user, course_id=self.standard_course.id)
CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course.id)
CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course_update_expired.id)
CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course_already_enrolled.id, mode=CourseMode.VERIFIED)
CourseEnrollmentFactory.create(
user=self.user, course_id=self.verified_course_already_enrolled.id, mode=CourseMode.VERIFIED
)
# Log the user in
self.client.login(username=self.user.username, password=TEST_PASSWORD)
......@@ -101,22 +102,3 @@ class TestCourseSockView(SharedModuleStoreTestCase):
response.content,
msg='Student should not be able to see sock in a unverifiable course.',
)
@classmethod
def _add_course_mode(cls, course, upgrade_deadline_expired=False):
"""
Adds a course mode to the test course.
"""
upgrade_exp_date = datetime.datetime.now()
if upgrade_deadline_expired:
upgrade_exp_date = upgrade_exp_date - datetime.timedelta(days=21)
else:
upgrade_exp_date = upgrade_exp_date + datetime.timedelta(days=21)
CourseMode(
course_id=course.id,
mode_slug=CourseMode.VERIFIED,
mode_display_name="Verified Certificate",
min_price=TEST_COURSE_PRICE,
_expiration_datetime=upgrade_exp_date, # pylint: disable=protected-access
).save()
......@@ -2,19 +2,21 @@
Views for the course home page.
"""
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.decorators.csrf import ensure_csrf_cookie
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
from courseware.access import has_access
from courseware.courses import get_course_info_section, get_course_with_access
from lms.djangoapps.courseware.views.views import CourseTabView
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
from student.models import CourseEnrollment
from util.views import ensure_valid_course_key
from web_fragments.fragment import Fragment
from ..utils import get_course_outline_block_tree
from .course_dates import CourseDatesFragmentView
......@@ -22,12 +24,13 @@ from .course_outline import CourseOutlineFragmentView
from .course_sock import CourseSockFragmentView
from .welcome_message import WelcomeMessageFragmentView
EMPTY_HANDOUTS_HTML = u'<ol></ol>'
class CourseHomeView(CourseTabView):
"""
The home page for a course.
"""
@method_decorator(login_required)
@method_decorator(ensure_csrf_cookie)
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
@method_decorator(ensure_valid_course_key)
......@@ -83,34 +86,48 @@ class CourseHomeFragmentView(EdxFragmentView):
return (has_visited_course, resume_course_url)
def _get_course_handouts(self, request, course):
"""
Returns the handouts for the specified course.
"""
handouts = get_course_info_section(request, request.user, course, 'handouts')
if not handouts or handouts == EMPTY_HANDOUTS_HTML:
return None
return handouts
def render_to_fragment(self, request, course_id=None, **kwargs):
"""
Renders the course's home page as a fragment.
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
# Render the outline as a fragment
outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
# Get resume course information
has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id)
# Render the course dates as a fragment
dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
# Render the welcome message as a fragment
# Render the full content to enrolled users, as well as to course and global staff.
# Unenrolled users who are not course or global staff are given only a subset.
is_enrolled = CourseEnrollment.is_enrolled(request.user, course_key)
is_staff = has_access(request.user, 'staff', course_key)
if is_enrolled or is_staff:
outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
welcome_message_fragment = WelcomeMessageFragmentView().render_to_fragment(
request, course_id=course_id, **kwargs
)
# Render the course dates as a fragment
dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
# TODO: Use get_course_overview_with_access and blocks api
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
# Render the verification sock as a fragment
course_sock_fragment = CourseSockFragmentView().render_to_fragment(request, course=course, **kwargs)
has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id)
else:
outline_fragment = None
welcome_message_fragment = None
course_sock_fragment = None
has_visited_course = None
resume_course_url = None
# Get the handouts
handouts_html = get_course_info_section(request, request.user, course, 'handouts')
handouts_html = self._get_course_handouts(request, course)
# Get the course tools enabled for this user and course
course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
# Render the course home fragment
context = {
......@@ -122,6 +139,7 @@ class CourseHomeFragmentView(EdxFragmentView):
'handouts_html': handouts_html,
'has_visited_course': has_visited_course,
'resume_course_url': resume_course_url,
'course_tools': course_tools,
'dates_fragment': dates_fragment,
'welcome_message_fragment': welcome_message_fragment,
'course_sock_fragment': course_sock_fragment,
......
......@@ -32,7 +32,7 @@ class CourseSockFragmentView(EdxFragmentView):
has_verified_mode = CourseMode.has_verified_mode(available_modes)
# Establish whether the user is already enrolled
is_already_verified = CourseEnrollment.is_enrolled_as_verified(request.user.id, course_key)
is_already_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
# Establish whether the verification deadline has already passed
verification_deadline = VerifiedUpgradeDeadlineDate(course, request.user)
......
......@@ -42,6 +42,7 @@ django==1.8.18
django-waffle==0.12.0
djangorestframework-jwt==1.8.0
djangorestframework-oauth==1.1.0
enum34==1.1.6
edx-ccx-keys==0.2.1
edx-celeryutils==0.2.4
edx-drf-extensions==1.2.2
......
......@@ -136,11 +136,11 @@ site_status_msg = get_site_status_msg(course_id)
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON'] and not combined_login_and_register:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<div class="item nav-courseware-02">
<a class="btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
<a class="btn btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
</div>
% elif static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<div class="item nav-courseware-02">
<a class="btn-neutral btn-register" href="/register">${_("Register")}</a>
<a class="btn btn-neutral btn-register" href="/register">${_("Register")}</a>
</div>
% endif
% endif
......
......@@ -138,11 +138,11 @@ site_status_msg = get_site_status_msg(course_id)
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<li class="nav-global-04">
<a class="btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register Now")}</a>
<a class="btn btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register Now")}</a>
</li>
% else:
<li class="nav-global-04">
<a class="btn-neutral btn-register" href="/register">${_("Register Now")}</a>
<a class="btn btn-neutral btn-register" href="/register">${_("Register Now")}</a>
</li>
% endif
% endif
......@@ -152,9 +152,9 @@ site_status_msg = get_site_status_msg(course_id)
<li class="nav-courseware-01">
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="btn btn-login" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
<a class="btn btn-brand btn-login" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
% else:
<a class="btn-brand btn-login" href="/login${login_query()}">${_("Sign in")}</a>
<a class="btn btn-brand btn-login" href="/login${login_query()}">${_("Sign in")}</a>
% endif
% endif
</li>
......
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