Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6aa5d2c

Browse files
committed
Merge pull request openedx#1562 from edx/hotfix-2013-10-31
Hotfix 2013 10 31
2 parents 2428b3c + c89c85c commit 6aa5d2c

File tree

25 files changed

+662
-131
lines changed

25 files changed

+662
-131
lines changed

common/djangoapps/course_modes/models.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.utils.translation import ugettext as _
1010
from django.db.models import Q
1111

12-
Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency'])
12+
Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency', 'expiration_date'])
1313

1414

1515
class CourseMode(models.Model):
@@ -39,7 +39,7 @@ class CourseMode(models.Model):
3939
# turn this mode off after the given expiration date
4040
expiration_date = models.DateField(default=None, null=True, blank=True)
4141

42-
DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd')
42+
DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd', None)
4343
DEFAULT_MODE_SLUG = 'honor'
4444

4545
class Meta:
@@ -57,8 +57,14 @@ def modes_for_course(cls, course_id):
5757
found_course_modes = cls.objects.filter(Q(course_id=course_id) &
5858
(Q(expiration_date__isnull=True) |
5959
Q(expiration_date__gte=now)))
60-
modes = ([Mode(mode.mode_slug, mode.mode_display_name, mode.min_price, mode.suggested_prices, mode.currency)
61-
for mode in found_course_modes])
60+
modes = ([Mode(
61+
mode.mode_slug,
62+
mode.mode_display_name,
63+
mode.min_price,
64+
mode.suggested_prices,
65+
mode.currency,
66+
mode.expiration_date
67+
) for mode in found_course_modes])
6268
if not modes:
6369
modes = [cls.DEFAULT_MODE]
6470
return modes

common/djangoapps/course_modes/tests/test_models.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_nodes_for_course_single(self):
4949

5050
self.create_mode('verified', 'Verified Certificate')
5151
modes = CourseMode.modes_for_course(self.course_id)
52-
mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
52+
mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None)
5353
self.assertEqual([mode], modes)
5454

5555
modes_dict = CourseMode.modes_for_course_dict(self.course_id)
@@ -61,8 +61,8 @@ def test_modes_for_course_multiple(self):
6161
"""
6262
Finding the modes when there's multiple modes
6363
"""
64-
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd')
65-
mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
64+
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None)
65+
mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None)
6666
set_modes = [mode1, mode2]
6767
for mode in set_modes:
6868
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices)
@@ -81,9 +81,9 @@ def test_min_course_price_for_currency(self):
8181
self.assertEqual(0, CourseMode.min_course_price_for_currency(self.course_id, 'usd'))
8282

8383
# create some modes
84-
mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd')
85-
mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd')
86-
mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny')
84+
mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd', None)
85+
mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd', None)
86+
mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny', None)
8787
set_modes = [mode1, mode2, mode3]
8888
for mode in set_modes:
8989
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
@@ -98,14 +98,15 @@ def test_modes_for_course_expired(self):
9898
modes = CourseMode.modes_for_course(self.course_id)
9999
self.assertEqual([CourseMode.DEFAULT_MODE], modes)
100100

101-
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd')
101+
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None)
102102
self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices)
103103
modes = CourseMode.modes_for_course(self.course_id)
104104
self.assertEqual([mode1], modes)
105105

106-
expired_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=1)
106+
expiration_date = datetime.now(pytz.UTC) + timedelta(days=1)
107+
expired_mode.expiration_date = expiration_date
107108
expired_mode.save()
108-
expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
109+
expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', expiration_date.date())
109110
modes = CourseMode.modes_for_course(self.course_id)
110111
self.assertEqual([expired_mode_value, mode1], modes)
111112

common/djangoapps/course_modes/views.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,19 @@ class ChooseModeView(View):
3434
@method_decorator(login_required)
3535
def get(self, request, course_id, error=None):
3636
""" Displays the course mode choice page """
37-
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
37+
38+
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(request.user, course_id)
39+
upgrade = request.GET.get('upgrade', False)
40+
41+
# verified users do not need to register or upgrade
42+
if enrollment_mode == 'verified':
3843
return redirect(reverse('dashboard'))
39-
modes = CourseMode.modes_for_course_dict(course_id)
4044

45+
# registered users who are not trying to upgrade do not need to re-register
46+
if enrollment_mode is not None and upgrade is False:
47+
return redirect(reverse('dashboard'))
48+
49+
modes = CourseMode.modes_for_course_dict(course_id)
4150
donation_for_course = request.session.get("donation_for_course", {})
4251
chosen_price = donation_for_course.get(course_id, None)
4352

@@ -50,6 +59,7 @@ def get(self, request, course_id, error=None):
5059
"course_num": course.display_number_with_default,
5160
"chosen_price": chosen_price,
5261
"error": error,
62+
"upgrade": upgrade,
5363
}
5464
if "verified" in modes:
5565
context["suggested_prices"] = [decimal.Decimal(x) for x in modes["verified"].suggested_prices.split(",")]
@@ -70,6 +80,8 @@ def post(self, request, course_id):
7080
error_msg = _("Enrollment is closed")
7181
return self.get(request, course_id, error=error_msg)
7282

83+
upgrade = request.GET.get('upgrade', False)
84+
7385
requested_mode = self.get_requested_mode(request.POST.get("mode"))
7486
if requested_mode == "verified" and request.POST.get("honor-code"):
7587
requested_mode = "honor"
@@ -106,13 +118,12 @@ def post(self, request, course_id):
106118
if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
107119
return redirect(
108120
reverse('verify_student_verified',
109-
kwargs={'course_id': course_id})
121+
kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade)
110122
)
111123

112124
return redirect(
113125
reverse('verify_student_show_requirements',
114-
kwargs={'course_id': course_id}),
115-
)
126+
kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade))
116127

117128
def get_requested_mode(self, user_choice):
118129
"""
@@ -121,6 +132,7 @@ def get_requested_mode(self, user_choice):
121132
"""
122133
choices = {
123134
"Select Audit": "audit",
124-
"Select Certificate": "verified"
135+
"Select Certificate": "verified",
136+
"Upgrade Your Registration": "verified"
125137
}
126138
return choices.get(user_choice)

common/djangoapps/student/tests/factories.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
CourseEnrollmentAllowed, CourseEnrollment,
33
PendingEmailChange, UserStanding,
44
)
5+
from course_modes.models import CourseMode
56
from django.contrib.auth.models import Group
67
from datetime import datetime
78
from factory import DjangoModelFactory, SubFactory, PostGenerationMethodCall, post_generation, Sequence
@@ -36,6 +37,16 @@ class UserProfileFactory(DjangoModelFactory):
3637
goals = u'World domination'
3738

3839

40+
class CourseModeFactory(DjangoModelFactory):
41+
FACTORY_FOR = CourseMode
42+
43+
course_id = None
44+
mode_display_name = u'Honor Code',
45+
mode_slug = 'honor'
46+
min_price = 0
47+
suggested_prices = ''
48+
currency = 'usd'
49+
3950
class RegistrationFactory(DjangoModelFactory):
4051
FACTORY_FOR = Registration
4152

common/djangoapps/student/tests/tests.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import json
99
import re
1010
import unittest
11+
from datetime import datetime, timedelta
12+
import pytz
1113

1214
from django.conf import settings
1315
from django.test import TestCase
@@ -28,8 +30,8 @@
2830

2931
from student.models import unique_id_for_user, CourseEnrollment
3032
from student.views import (process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper,
31-
change_enrollment)
32-
from student.tests.factories import UserFactory
33+
change_enrollment, complete_course_mode_info)
34+
from student.tests.factories import UserFactory, CourseModeFactory
3335
from student.tests.test_email import mock_render_to_string
3436

3537
import shoppingcart
@@ -216,6 +218,45 @@ def test_cert_info(self):
216218
})
217219

218220

221+
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
222+
class DashboardTest(TestCase):
223+
"""
224+
Tests for dashboard utility functions
225+
"""
226+
# arbitrary constant
227+
COURSE_SLUG = "100"
228+
COURSE_NAME = "test_course"
229+
COURSE_ORG = "EDX"
230+
231+
def setUp(self):
232+
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
233+
self.assertIsNotNone(self.course)
234+
self.user = UserFactory.create(username="jack", email="[email protected]")
235+
CourseModeFactory.create(
236+
course_id=self.course.id,
237+
mode_slug='honor',
238+
mode_display_name='Honor Code',
239+
)
240+
241+
def test_course_mode_info(self):
242+
verified_mode = CourseModeFactory.create(
243+
course_id=self.course.id,
244+
mode_slug='verified',
245+
mode_display_name='Verified',
246+
expiration_date=(datetime.now(pytz.UTC) + timedelta(days=1)).date()
247+
)
248+
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
249+
course_mode_info = complete_course_mode_info(self.course.id, enrollment)
250+
self.assertTrue(course_mode_info['show_upsell'])
251+
self.assertEquals(course_mode_info['days_for_upsell'], 1)
252+
253+
verified_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=-1)
254+
verified_mode.save()
255+
course_mode_info = complete_course_mode_info(self.course.id, enrollment)
256+
self.assertFalse(course_mode_info['show_upsell'])
257+
self.assertIsNone(course_mode_info['days_for_upsell'])
258+
259+
219260
class EnrollInCourseTest(TestCase):
220261
"""Tests enrolling and unenrolling in courses."""
221262

common/djangoapps/student/views.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,29 @@ def register_user(request, extra_context=None):
267267
return render_to_response('register.html', context)
268268

269269

270+
def complete_course_mode_info(course_id, enrollment):
271+
"""
272+
We would like to compute some more information from the given course modes
273+
and the user's current enrollment
274+
275+
Returns the given information:
276+
- whether to show the course upsell information
277+
- numbers of days until they can't upsell anymore
278+
"""
279+
modes = CourseMode.modes_for_course_dict(course_id)
280+
mode_info = {'show_upsell': False, 'days_for_upsell': None}
281+
# we want to know if the user is already verified and if verified is an
282+
# option
283+
if 'verified' in modes and enrollment.mode != 'verified':
284+
mode_info['show_upsell'] = True
285+
# if there is an expiration date, find out how long from now it is
286+
if modes['verified'].expiration_date:
287+
today = datetime.datetime.now(UTC).date()
288+
mode_info['days_for_upsell'] = (modes['verified'].expiration_date - today).days
289+
290+
return mode_info
291+
292+
270293
@login_required
271294
@ensure_csrf_cookie
272295
def dashboard(request):
@@ -300,6 +323,7 @@ def dashboard(request):
300323
show_courseware_links_for = frozenset(course.id for course, _enrollment in courses
301324
if has_access(request.user, course, 'load'))
302325

326+
course_modes = {course.id: complete_course_mode_info(course.id, enrollment) for course, enrollment in courses}
303327
cert_statuses = {course.id: cert_info(request.user, course) for course, _enrollment in courses}
304328

305329
# only show email settings for Mongo course and when bulk email is turned on
@@ -324,6 +348,7 @@ def dashboard(request):
324348
'staff_access': staff_access,
325349
'errored_courses': errored_courses,
326350
'show_courseware_links_for': show_courseware_links_for,
351+
'all_course_modes': course_modes,
327352
'cert_statuses': cert_statuses,
328353
'show_email_settings_for': show_email_settings_for,
329354
}

common/lib/xmodule/xmodule/conditional_module.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import json
66
import logging
7+
from lazy import lazy
78
from lxml import etree
89
from pkg_resources import resource_string
910

@@ -97,10 +98,12 @@ def _get_condition(self):
9798
return xml_value, attr_name
9899
raise Exception('Error in conditional module: unknown condition "%s"' % xml_attr)
99100

100-
def is_condition_satisfied(self):
101-
self.required_modules = [self.system.get_module(descriptor) for
102-
descriptor in self.descriptor.get_required_module_descriptors()]
101+
@lazy
102+
def required_modules(self):
103+
return [self.system.get_module(descriptor) for
104+
descriptor in self.descriptor.get_required_module_descriptors()]
103105

106+
def is_condition_satisfied(self):
104107
xml_value, attr_name = self._get_condition()
105108

106109
if xml_value and self.required_modules:

common/lib/xmodule/xmodule/lti_module.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def get_html(self):
181181
]
182182

183183
# Obtains client_key and client_secret credentials from current course:
184-
course_id = self.runtime.course_id
184+
course_id = self.course_id
185185
course_location = CourseDescriptor.id_to_location(course_id)
186186
course = self.descriptor.runtime.modulestore.get_item(course_location)
187187
client_key = client_secret = ''

common/lib/xmodule/xmodule/peer_grading_module.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ def peer_grading(self, _data=None):
519519
error_text = ""
520520
problem_list = []
521521
try:
522-
problem_list_json = self.peer_gs.get_problem_list(self.system.course_id, self.system.anonymous_student_id)
522+
problem_list_json = self.peer_gs.get_problem_list(self.course_id, self.system.anonymous_student_id)
523523
problem_list_dict = problem_list_json
524524
success = problem_list_dict['success']
525525
if 'error' in problem_list_dict:
@@ -569,7 +569,7 @@ def peer_grading(self, _data=None):
569569

570570
ajax_url = self.ajax_url
571571
html = self.system.render_template('peer_grading/peer_grading.html', {
572-
'course_id': self.system.course_id,
572+
'course_id': self.course_id,
573573
'ajax_url': ajax_url,
574574
'success': success,
575575
'problem_list': good_problem_list,
@@ -603,7 +603,7 @@ def peer_grading_problem(self, data=None):
603603
html = self.system.render_template('peer_grading/peer_grading_problem.html', {
604604
'view_html': '',
605605
'problem_location': problem_location,
606-
'course_id': self.system.course_id,
606+
'course_id': self.course_id,
607607
'ajax_url': ajax_url,
608608
# Checked above
609609
'staff_access': False,

common/lib/xmodule/xmodule/x_module.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class XModuleMixin(XBlockMixin):
133133
default=None
134134
)
135135

136+
@property
137+
def course_id(self):
138+
return self.runtime.course_id
139+
136140
@property
137141
def id(self):
138142
return self.location.url()
@@ -743,6 +747,7 @@ def _xmodule(self):
743747
self.xmodule_runtime.xmodule_instance = descriptor._xmodule # pylint: disable=protected-access
744748
return self.xmodule_runtime.xmodule_instance
745749

750+
course_id = module_attr('course_id')
746751
displayable_items = module_attr('displayable_items')
747752
get_display_items = module_attr('get_display_items')
748753
get_icon_class = module_attr('get_icon_class')

0 commit comments

Comments
 (0)