Commit bf112f7e by Stephen Sanchez Committed by stephensanchez

Add new enrollment message to the dashboard

The body of the enrollment message template

Tokenize platform name in message.

Changing to a datetime enrollment approach

Adding sorting. A little refactoring.

Adding confguration model for time delta

Adding admin registration and basic form for new config model.

Fixing docstring typo

Updating default time delta to 0, adding test to show it disabled functionality.

Removing the form from configuration and tweaking the enrollment message html
parent 7f4cc1c7
......@@ -157,7 +157,7 @@ class ChooseModeView(View):
# it doesn't matter, but it will avoid hitting the database.
if requested_mode == 'honor':
CourseEnrollment.enroll(user, course_key, requested_mode)
return redirect('dashboard')
return redirect(reverse('dashboard'))
mode_info = allowed_modes[requested_mode]
......
'''
django admin pages for courseware model
'''
from config_models.admin import ConfigurationModelAdmin
from student.models import UserProfile, UserTestGroup, CourseEnrollmentAllowed
from student.models import UserProfile, UserTestGroup, CourseEnrollmentAllowed, DashboardConfiguration
from student.models import CourseEnrollment, Registration, PendingNameChange, CourseAccessRole, CourseAccessRoleAdmin
from ratelimitbackend import admin
......@@ -19,3 +20,5 @@ admin.site.register(Registration)
admin.site.register(PendingNameChange)
admin.site.register(CourseAccessRole, CourseAccessRoleAdmin)
admin.site.register(DashboardConfiguration, ConfigurationModelAdmin)
......@@ -6,6 +6,7 @@ from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.hashers import UNUSABLE_PASSWORD
class PasswordResetFormNoActive(PasswordResetForm):
def clean_email(self):
"""
......@@ -21,4 +22,4 @@ class PasswordResetFormNoActive(PasswordResetForm):
if any((user.password == UNUSABLE_PASSWORD)
for user in self.users_cache):
raise forms.ValidationError(self.error_messages['unusable'])
return email
return email
\ No newline at end of file
......@@ -32,6 +32,7 @@ from django.dispatch import receiver, Signal
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext_noop
from django_countries import CountryField
from config_models.models import ConfigurationModel
from track import contexts
from eventtracking import tracker
from importlib import import_module
......@@ -1388,3 +1389,20 @@ def enforce_single_login(sender, request, user, signal, **kwargs): # pylint:
else:
key = None
user.profile.set_login_session(key)
class DashboardConfiguration(ConfigurationModel):
"""Dashboard Configuration settings.
Includes configuration options for the dashboard, which impact behavior and rendering for the application.
"""
recent_enrollment_time_delta = models.PositiveIntegerField(
default=0,
help_text="The number of seconds in which a new enrollment is considered 'recent'. "
"Used to display notifications."
)
@property
def recent_enrollment_seconds(self):
return self.recent_enrollment_time_delta
"""
Tests for the recently enrolled messaging within the Dashboard.
"""
import datetime
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import Client
from opaque_keys.edx import locator
from pytz import UTC
import unittest
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.models import CourseEnrollment, DashboardConfiguration
from student.views import get_course_enrollment_pairs, _get_recently_enrolled_courses
class TestRecentEnrollments(ModuleStoreTestCase):
"""
Unit tests for getting the list of courses for a logged in user
"""
def setUp(self):
"""
Add a student
"""
super(TestRecentEnrollments, self).setUp()
self.student = UserFactory()
# Old Course
old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
course, enrollment = self._create_course_and_enrollment(old_course_location)
enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0)
enrollment.save()
# New Course
course_location = locator.CourseLocator('Org1', 'Course1', 'Run1')
self._create_course_and_enrollment(course_location)
def _create_course_and_enrollment(self, course_location):
""" Creates a course and associated enrollment. """
course = CourseFactory.create(
org=course_location.org,
number=course_location.course,
run=course_location.run
)
enrollment = CourseEnrollment.enroll(self.student, course.id)
return course, enrollment
def test_recently_enrolled_courses(self):
"""
Test if the function for filtering recent enrollments works appropriately.
"""
config = DashboardConfiguration(recent_enrollment_time_delta=60)
config.save()
# get courses through iterating all courses
courses_list = list(get_course_enrollment_pairs(self.student, None, []))
self.assertEqual(len(courses_list), 2)
recent_course_list = _get_recently_enrolled_courses(courses_list)
self.assertEqual(len(recent_course_list), 1)
def test_zero_second_delta(self):
"""
Tests that the recent enrollment list is empty if configured to zero seconds.
"""
config = DashboardConfiguration(recent_enrollment_time_delta=0)
config.save()
courses_list = list(get_course_enrollment_pairs(self.student, None, []))
self.assertEqual(len(courses_list), 2)
recent_course_list = _get_recently_enrolled_courses(courses_list)
self.assertEqual(len(recent_course_list), 0)
def test_enrollments_sorted_most_recent(self):
"""
Test that the list of newly created courses are properly sorted to show the most
recent enrollments first.
"""
config = DashboardConfiguration(recent_enrollment_time_delta=600)
config.save()
# Create a number of new enrollments and courses, and force their creation behind
# the first enrollment
course_location = locator.CourseLocator('Org2', 'Course2', 'Run2')
_, enrollment2 = self._create_course_and_enrollment(course_location)
enrollment2.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=5)
enrollment2.save()
course_location = locator.CourseLocator('Org3', 'Course3', 'Run3')
_, enrollment3 = self._create_course_and_enrollment(course_location)
enrollment3.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=10)
enrollment3.save()
course_location = locator.CourseLocator('Org4', 'Course4', 'Run4')
_, enrollment4 = self._create_course_and_enrollment(course_location)
enrollment4.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=15)
enrollment4.save()
course_location = locator.CourseLocator('Org5', 'Course5', 'Run5')
_, enrollment5 = self._create_course_and_enrollment(course_location)
enrollment5.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=20)
enrollment5.save()
courses_list = list(get_course_enrollment_pairs(self.student, None, []))
self.assertEqual(len(courses_list), 6)
recent_course_list = _get_recently_enrolled_courses(courses_list)
self.assertEqual(len(recent_course_list), 5)
self.assertEqual(recent_course_list[1][1], enrollment2)
self.assertEqual(recent_course_list[2][1], enrollment3)
self.assertEqual(recent_course_list[3][1], enrollment4)
self.assertEqual(recent_course_list[4][1], enrollment5)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_dashboard_rendering(self):
"""
Tests that the dashboard renders the recent enrollment messages appropriately.
"""
config = DashboardConfiguration(recent_enrollment_time_delta=600)
config.save()
self.client = Client()
self.client.login(username=self.student.username, password='test')
response = self.client.get(reverse("dashboard"))
self.assertContains(response, "You have successfully enrolled in")
......@@ -46,8 +46,8 @@ from student.models import (
Registration, UserProfile, PendingNameChange,
PendingEmailChange, CourseEnrollment, unique_id_for_user,
CourseEnrollmentAllowed, UserStanding, LoginFailures,
create_comments_service_user, PasswordHistory, UserSignupSource
)
create_comments_service_user, PasswordHistory, UserSignupSource,
DashboardConfiguration)
from student.forms import PasswordResetFormNoActive
from verify_student.models import SoftwareSecurePhotoVerification, MidcourseReverificationWindow
......@@ -471,6 +471,10 @@ def dashboard(request):
# enrollments, because it could have been a data push snafu.
course_enrollment_pairs = list(get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set))
# Check to see if the student has recently enrolled in a course. If so, display a notification message confirming
# the enrollment.
enrollment_message = _create_recent_enrollment_message(course_enrollment_pairs)
course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)
message = ""
......@@ -551,6 +555,7 @@ def dashboard(request):
current_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
context = {
'enrollment_message': enrollment_message,
'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
......@@ -586,6 +591,49 @@ def dashboard(request):
return render_to_response('dashboard.html', context)
def _create_recent_enrollment_message(course_enrollment_pairs):
"""Builds a recent course enrollment message
Constructs a new message template based on any recent course enrollments for the student.
Args:
course_enrollment_pairs (list): A list of tuples containing courses, and the associated enrollment information.
Returns:
A string representing the HTML message output from the message template.
"""
recent_course_enrollment_pairs = _get_recently_enrolled_courses(course_enrollment_pairs)
if recent_course_enrollment_pairs:
return render_to_string(
'enrollment/course_enrollment_message.html',
{'recent_course_enrollment_pairs': recent_course_enrollment_pairs,}
)
def _get_recently_enrolled_courses(course_enrollment_pairs):
"""Checks to see if the student has recently enrolled in courses.
Checks to see if any of the enrollments in the course_enrollment_pairs have been recently created and activated.
Args:
course_enrollment_pairs (list): A list of tuples containing courses, and the associated enrollment information.
Returns:
A list of tuples for the course and enrollment.
"""
seconds = DashboardConfiguration.current().recent_enrollment_time_delta
sorted_list = sorted(course_enrollment_pairs, key=lambda created: created[1].created, reverse=True)
time_delta = (datetime.datetime.now(UTC) - datetime.timedelta(seconds=seconds))
return [
(course, enrollment) for course, enrollment in sorted_list
# If the enrollment has no created date, we are explicitly excluding the course
# from the list of recent enrollments.
if enrollment.is_active and enrollment.created > time_delta
]
def try_change_enrollment(request):
"""
This method calls change_enrollment if the necessary POST
......
......@@ -192,6 +192,10 @@
</section>
%endif
%if enrollment_message:
${enrollment_message}
%endif
% if duplicate_provider:
<section class="dashboard-banner third-party-auth">
## Translators: this message is displayed when a user tries to link their account with a third-party authentication provider (for example, Google or LinkedIn) with a given edX account, but their third-party account is already associated with another edX account. provider_name is the name of the third-party authentication provider, and platform_name is the name of the edX deployment.
......
<%! from django.utils.translation import ugettext as _ %>
% for course, enrollment in recent_course_enrollment_pairs:
<section class="dashboard-banner">
<p class='activation-message'>${_("You have successfully enrolled in {enrolled_course}.").format(enrolled_course=course.display_name)}</p>
</section>
% endfor
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