Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions common/djangoapps/student/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,28 @@ def get_or_create_enrollment(cls, user, course_id):

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
Expand Down
6 changes: 6 additions & 0 deletions common/djangoapps/student/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion common/lib/xmodule/xmodule/course_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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
Expand Down Expand Up @@ -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

Expand Down
51 changes: 51 additions & 0 deletions lms/djangoapps/courseware/tests/test_about.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,54 @@ def test_anonymous_user_xml(self):
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)
6 changes: 5 additions & 1 deletion lms/djangoapps/courseware/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,14 +562,18 @@ 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,
'course_target': course_target,
'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
Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/instructor/views/instructor_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/instructor/views/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
4 changes: 4 additions & 0 deletions lms/templates/courseware/course_about.html
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ <h1>
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}
Expand Down