Allow author to cap enrollments in course
add a test case for enrollment caps pep8 fix
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user