Skip to content
Snippets Groups Projects
Commit a5d8cbb8 authored by Gregory Martin's avatar Gregory Martin Committed by Sanford Student
Browse files

Add "last completed block" to dropdown

parent 335bf4b0
No related branches found
No related tags found
No related merge requests found
......@@ -8,10 +8,12 @@ import time
import six
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import NoReverseMatch, reverse
from django.dispatch import Signal
from django.utils.http import cookie_date
from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed
from student.models import CourseEnrollment
CREATE_LOGON_COOKIE = Signal(providing_args=['user', 'response'])
......@@ -58,6 +60,8 @@ def set_logged_in_cookies(request, response, user):
"username": "test-user",
"header_urls": {
"account_settings": "https://example.com/account/settings",
"resume_block":
"https://example.com//courses/org.0/course_0/Run_0/jump_to/i4x://org.0/course_0/vertical/vertical_4"
"learner_profile": "https://example.com/u/test-user",
"logout": "https://example.com/logout"
}
......@@ -165,6 +169,12 @@ def get_user_info_cookie_data(request):
except NoReverseMatch:
pass
# Add 'resume course' last completed block
try:
header_urls['resume_block'] = retrieve_last_sitewide_block_completed(user)
except User.DoesNotExist:
pass
# Convert relative URL paths to absolute URIs
for url_name, url_path in six.iteritems(header_urls):
header_urls[url_name] = request.build_absolute_uri(url_path)
......
......@@ -6,6 +6,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import RequestFactory
from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed
from student.cookies import get_user_info_cookie_data
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
......@@ -26,6 +27,7 @@ class CookieTests(SharedModuleStoreTestCase):
def _get_expected_header_urls(self, request):
expected_header_urls = {
'logout': reverse('logout'),
'resume_block': retrieve_last_sitewide_block_completed(self.user.username)
}
# Studio (CMS) does not have the URLs below
......
......@@ -39,7 +39,7 @@ from util.milestones_helpers import (get_course_milestones,
from util.testing import UrlResetMixin
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
PASSWORD = 'test'
......@@ -704,7 +704,11 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
course_key = course.id
block_keys = [
course_key.make_usage_key('video', unicode(number))
ItemFactory.create(
category='video',
parent_location=course.location,
display_name='Video {0}'.format(unicode(number))
).location
for number in xrange(5)
]
......@@ -773,7 +777,11 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
# Submit completed course blocks in even-numbered courses.
if isEven(i):
block_keys = [
course_key.make_usage_key('video', unicode(number))
ItemFactory.create(
category='video',
parent_location=course.location,
display_name='Video {0}'.format(unicode(number))
).location
for number in xrange(5)
]
last_completed_block_string = str(block_keys[-1])
......
......@@ -12,11 +12,14 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user
from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed
%>
<%
profile_image_url = get_profile_image_urls_for_user(self.real_user)['medium']
username = self.real_user.username
resume_block = retrieve_last_sitewide_block_completed(username)
%>
......@@ -32,7 +35,11 @@ from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_
<span class="fa fa-caret-down" aria-hidden="true"></span>
</div>
<div class="dropdown-user-menu hidden" aria-label=${_("More Options")} role="menu" id="user-menu" tabindex="-1">
% if resume_block:
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${resume_block}" role="menuitem">${_("Resume your last course")}</a></div>
% endif
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('dashboard')}" role="menuitem">${_("Dashboard")}</a></div>
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('learner_profile', kwargs={'username': username})}" role="menuitem">${_("Profile")}</a></div>
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('account_settings')}" role="menuitem">${_("Account")}</a></div>
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('logout')}" role="menuitem">${_("Sign Out")}</a></div>
</div>
......
""" Unit tests for custom UserProfile properties. """
import ddt
from __future__ import absolute_import, division, print_function, unicode_literals
import ddt
from django.test import TestCase
from django.test.utils import override_settings
from mock import patch
from completion import models
from completion.test_utils import CompletionWaffleTestMixin
from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed
from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ..utils import validate_social_link, format_social_link
from ..utils import format_social_link, validate_social_link
@ddt.ddt
......@@ -51,3 +62,77 @@ class UserAccountSettingsTest(TestCase):
self.assertEqual(is_valid_expected, self.validate_social_link(platform_name, link_input))
self.assertEqual(formatted_link_expected, format_social_link(platform_name, link_input))
@ddt.ddt
class CompletionUtilsTestCase(SharedModuleStoreTestCase, CompletionWaffleTestMixin, TestCase):
"""
Test completion utility functions
"""
def setUp(self):
"""
Creates a test course that can be used for non-destructive tests
"""
super(CompletionUtilsTestCase, self).setUp()
self.override_waffle_switch(True)
self.engaged_user = UserFactory.create()
self.cruft_user = UserFactory.create()
self.course = self.create_test_course()
self.submit_faux_completions()
def create_test_course(self):
"""
Create, populate test course.
"""
course = CourseFactory.create()
with self.store.bulk_operations(course.id):
self.chapter = ItemFactory.create(category='chapter', parent_location=course.location)
self.sequential = ItemFactory.create(category='sequential', parent_location=self.chapter.location)
self.vertical1 = ItemFactory.create(category='vertical', parent_location=self.sequential.location)
self.vertical2 = ItemFactory.create(category='vertical', parent_location=self.sequential.location)
course.children = [self.chapter]
self.chapter.children = [self.sequential]
self.sequential.children = [self.vertical1, self.vertical2]
if hasattr(self, 'user_one'):
CourseEnrollment.enroll(self.engaged_user, course.id)
if hasattr(self, 'user_two'):
CourseEnrollment.enroll(self.cruft_user, course.id)
return course
def submit_faux_completions(self):
"""
Submit completions (only for user_one)g
"""
for block in self.course.children[0].children[0].children:
models.BlockCompletion.objects.submit_completion(
user=self.engaged_user,
course_key=self.course.id,
block_key=block.location,
completion=1.0
)
@override_settings(LMS_ROOT_URL='test_url:9999')
@patch('completion.waffle.get_current_site')
@ddt.data(True, False)
def test_retrieve_last_sitewide_block_completed(self, use_username, get_patched_current_site): # pylint: disable=unused-argument
"""
Test that the method returns a URL for the "last completed" block
when sending a user object
"""
block_url = retrieve_last_sitewide_block_completed(
self.engaged_user.username if use_username else self.engaged_user
)
empty_block_url = retrieve_last_sitewide_block_completed(
self.cruft_user.username if use_username else self.cruft_user
)
self.assertEqual(
block_url,
u'test_url:9999/courses/{org}/{course}/{run}/jump_to/i4x://{org}/{course}/vertical/{vertical_id}'.format(
org=self.course.location.course_key.org,
course=self.course.location.course_key.course,
run=self.course.location.course_key.run,
vertical_id=self.vertical2.location.block_id,
)
)
self.assertEqual(empty_block_url, None)
......@@ -5,7 +5,15 @@ import re
from urlparse import urlparse
from django.conf import settings
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from six import text_type
from completion import waffle as completion_waffle
from completion.models import BlockCompletion
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.core.djangoapps.theming.helpers import get_config_value_from_site_or_settings, get_current_site
from xmodule.modulestore.django import modulestore
def validate_social_link(platform_name, new_social_link):
......@@ -89,3 +97,77 @@ def _is_valid_social_username(value):
in the username.
"""
return '/' not in value
def retrieve_last_sitewide_block_completed(username):
"""
Completion utility
From a string 'username' or object User retrieve
the last course block marked as 'completed' and construct a URL
:param username: str(username) or obj(User)
:return: block_lms_url
"""
if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING):
return
if not isinstance(username, User):
userobj = User.objects.get(username=username)
else:
userobj = username
latest_completions_by_course = BlockCompletion.latest_blocks_completed_all_courses(userobj)
known_site_configs = [
other_site_config.get_value('course_org_filter') for other_site_config in SiteConfiguration.objects.all()
if other_site_config.get_value('course_org_filter')
]
current_site_configuration = get_config_value_from_site_or_settings(
name='course_org_filter',
site=get_current_site()
)
# courses.edx.org has no 'course_org_filter'
# however the courses within DO, but those entries are not found in
# known_site_configs, which are White Label sites
# This is necessary because the WL sites and courses.edx.org
# have the same AWS RDS mySQL instance
candidate_course = None
candidate_block_key = None
latest_date = None
# Go through dict, find latest
for course, [modified_date, block_key] in latest_completions_by_course.items():
if not current_site_configuration:
# This is a edx.org
if course.org in known_site_configs:
continue
if not latest_date or modified_date > latest_date:
candidate_course = course
candidate_block_key = block_key
latest_date = modified_date
else:
# This is a White Label site, and we should find candidates from the same site
if course.org not in current_site_configuration:
# Not the same White Label, or a edx.org course
continue
if not latest_date or modified_date > latest_date:
candidate_course = course
candidate_block_key = block_key
latest_date = modified_date
if not candidate_course:
return
lms_root = SiteConfiguration.get_value_for_org(candidate_course.org, "LMS_ROOT_URL", settings.LMS_ROOT_URL)
item = modulestore().get_item(candidate_block_key, depth=1)
if not lms_root:
return
return u"{lms_root}/courses/{course_key}/jump_to/{location}".format(
lms_root=lms_root,
course_key=text_type(item.location.course_key),
location=text_type(item.location),
)
......@@ -10,6 +10,7 @@ from completion.models import BlockCompletion
from completion.test_utils import CompletionWaffleTestMixin
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.test import override_settings
from mock import Mock, patch
from six import text_type
......@@ -402,6 +403,7 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
),
active=True
)
@override_settings(LMS_BASE='test_url:9999')
@patch('completion.waffle.get_current_site')
def test_resume_course_with_completion_api(self, get_patched_current_site):
"""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment