Commit 3f43fb64 by chrisndodge

Merge pull request #1967 from edx/feature/cdodge/cap-num-enrollments

Add ability to cap number of enrollments in a course
parents 818ad159 ad7348ec
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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('<a href="#" class="register">', 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)
......@@ -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
......
......@@ -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}),
......
......@@ -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():
......
......@@ -166,6 +166,10 @@
cost=registration_price)}
</a>
<div id="register_error"></div>
% elif is_course_full:
<span class="register disabled">
${_("Course is full")}
</span>
%else:
<a href="#" class="register">
${_("Register for {course.display_number_with_default}").format(course=course) | h}
......
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