From ad7348ec11b623bf75b86239f6dec813b2dc123b Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 16 Dec 2013 15:19:59 -0500 Subject: [PATCH] Allow author to cap enrollments in course add a test case for enrollment caps pep8 fix --- CHANGELOG.rst | 2 + common/djangoapps/student/models.py | 22 ++++++++ common/djangoapps/student/views.py | 6 +++ common/lib/xmodule/xmodule/course_module.py | 5 +- lms/djangoapps/courseware/tests/test_about.py | 51 +++++++++++++++++++ lms/djangoapps/courseware/views.py | 6 ++- .../instructor/views/instructor_dashboard.py | 2 +- lms/djangoapps/instructor/views/legacy.py | 2 +- lms/templates/courseware/course_about.html | 4 ++ 9 files changed, 96 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc52c5156e..3631612f9b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -193,6 +193,8 @@ it pauses on the end time. Blades: Disallow users to enter video url's in http. +Studio/LMS: Ability to cap the max number of active enrollments in a course + LMS: Improve the acessibility of the forum follow post buttons. Blades: Latex problems are now enabled via use_latex_compiler diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 62d9dc1aa8..ee9ec4ec88 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -424,6 +424,28 @@ class CourseEnrollment(models.Model): return enrollment + @classmethod + def num_enrolled_in(cls, course_id): + """ + Returns the count of active enrollments in a course. + + 'course_id' is the course_id to return enrollments + """ + enrollment_number = CourseEnrollment.objects.filter(course_id=course_id, is_active=1).count() + + return enrollment_number + + @classmethod + def is_course_full(cls, course): + """ + Returns a boolean value regarding whether a course has already reached it's max enrollment + capacity + """ + is_course_full = False + if course.max_student_enrollments_allowed is not None: + is_course_full = cls.num_enrolled_in(course.location.course_id) >= course.max_student_enrollments_allowed + return is_course_full + def update_enrollment(self, mode=None, is_active=None): """ Updates an enrollment for a user in a class. This includes options diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 147c587746..d82cb8cd63 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -561,6 +561,12 @@ def change_enrollment(request): if not has_access(user, course, 'enroll'): return HttpResponseBadRequest(_("Enrollment is closed")) + # see if we have already filled up all allowed enrollments + is_course_full = CourseEnrollment.is_course_full(course) + + if is_course_full: + return HttpResponseBadRequest(_("Course is full")) + # If this course is available in multiple modes, redirect them to a page # where they can choose which mode they want. available_modes = CourseMode.modes_for_course(course_id) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index a7519902c9..d378c2ad24 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -13,7 +13,7 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.graders import grader_from_conf import json -from xblock.fields import Scope, List, String, Dict, Boolean +from xblock.fields import Scope, List, String, Dict, Boolean, Integer from .fields import Date from xmodule.modulestore.locator import CourseLocator from django.utils.timezone import UTC @@ -384,6 +384,9 @@ class CourseFields(object): display_coursenumber = String(help="An optional display string for the course number that will get rendered in the LMS", scope=Scope.settings) + max_student_enrollments_allowed = Integer(help="Limit the number of students allowed to enroll in this course.", + scope=Scope.settings) + class CourseDescriptor(CourseFields, SequenceDescriptor): module_class = SequenceModule diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 9e6354dd3f..3154b39002 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -51,3 +51,54 @@ class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn(self.xml_data, resp.content) + + +@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) +class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): + """ + This test case will check the About page when a course has a capped enrollment + """ + def setUp(self): + """ + Set up the tests + """ + self.course = CourseFactory.create(metadata={"max_student_enrollments_allowed": 1}) + + self.about = ItemFactory.create( + category="about", parent_location=self.course.location, + data="OOGIE BLOOGIE", display_name="overview" + ) + # The following XML course is closed; we're testing that + # an about page still appears when the course is already closed + self.xml_course_id = 'edX/detached_pages/2014' + self.xml_data = "about page 463139" + + def test_enrollment_cap(self): + """ + This test will make sure that enrollment caps are enforced + """ + self.setup_user() + url = reverse('about_course', args=[self.course.id]) + resp = self.client.get(url) + self.assertEqual(resp.status_code, 200) + self.assertIn('', resp.content) + + self.enroll(self.course, verify=True) + + # create a new account since the first account is already registered for the course + self.email = 'foo_second@test.com' + self.password = 'bar' + self.username = 'test_second' + self.create_account(self.username, + self.email, self.password) + self.activate_user(self.email) + self.login(self.email, self.password) + + # Get the about page again and make sure that the page says that the course is full + resp = self.client.get(url) + self.assertEqual(resp.status_code, 200) + self.assertIn("Course is full", resp.content) + + # Try to enroll as well + result = self.enroll(self.course) + self.assertFalse(result) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index d87a3830e2..a47ebac017 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -562,6 +562,9 @@ def course_about(request, course_id): reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format( reg_url=reverse('register_user'), course_id=course.id) + # see if we have already filled up all allowed enrollments + is_course_full = CourseEnrollment.is_course_full(course) + return render_to_response('courseware/course_about.html', {'course': course, 'registered': registered, @@ -569,7 +572,8 @@ def course_about(request, course_id): 'registration_price': registration_price, 'in_cart': in_cart, 'reg_then_add_to_cart_link': reg_then_add_to_cart_link, - 'show_courseware_link': show_courseware_link}) + 'show_courseware_link': show_courseware_link, + 'is_course_full': is_course_full}) @ensure_csrf_cookie diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 2000c83900..c22a774fdf 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -115,7 +115,7 @@ def _section_course_info(course_id, access): 'course_num': course_num, 'course_name': course_name, 'course_display_name': course.display_name, - 'enrollment_count': CourseEnrollment.objects.filter(course_id=course_id, is_active=1).count(), + 'enrollment_count': CourseEnrollment.num_enrolled_in(course_id), 'has_started': course.has_started(), 'has_ended': course.has_ended(), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index c671b28922..6e8e39a374 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -110,7 +110,7 @@ def instructor_dashboard(request, course_id): else: idash_mode = request.session.get('idash_mode', 'Grades') - enrollment_number = CourseEnrollment.objects.filter(course_id=course_id, is_active=1).count() + enrollment_number = CourseEnrollment.num_enrolled_in(course_id) # assemble some course statistics for output to instructor def get_course_stats_table(): diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index 3437e57483..8eecd634ad 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -166,6 +166,10 @@ cost=registration_price)}
+ % elif is_course_full: + + ${_("Course is full")} + %else: ${_("Register for {course.display_number_with_default}").format(course=course) | h}