Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mode and is_active to CourseEnrollment, shift enrollment logic to model #651

Merged
merged 1 commit into from
Aug 14, 2013
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
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ LMS: Enable beta instructor dashboard. The beta dashboard is a rearchitecture
of the existing instructor dashboard and is available by clicking a link at
the top right of the existing dashboard.

Common: CourseEnrollment has new fields `is_active` and `mode`. The mode will be
used to differentiate different kinds of enrollments (currently, all enrollments
are honor certificate enrollments). The `is_active` flag will be used to
deactivate enrollments without deleting them, so that we know what course you
*were* enrolled in. Because of the latter change, enrollment and unenrollment
logic has been consolidated into the model -- you should use new class methods
to `enroll()`, `unenroll()`, and to check `is_enrolled()`, instead of creating
CourseEnrollment objects or querying them directly.

Studio: Email will be sent to admin address when a user requests course creator
privileges for Studio (edge only).

Expand Down
8 changes: 4 additions & 4 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from pytz import UTC
from uuid import uuid4
from pymongo import MongoClient
from student.views import is_enrolled_in_course
from student.models import CourseEnrollment

TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['OPTIONS']['db'] = 'test_xcontent_%s' % uuid4().hex
Expand Down Expand Up @@ -1168,7 +1168,7 @@ def assert_created_course(self, number_suffix=None):
self.assertNotIn('ErrMsg', data)
self.assertEqual(data['id'], 'i4x://MITx/{0}/course/2013_Spring'.format(test_course_data['number']))
# Verify that the creator is now registered in the course.
self.assertTrue(is_enrolled_in_course(self.user, self._get_course_id(test_course_data)))
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self._get_course_id(test_course_data)))
return test_course_data

def test_create_course_check_forum_seeding(self):
Expand All @@ -1190,14 +1190,14 @@ def assert_course_creation_failed(self, error_message):
Checks that the course did not get created
"""
course_id = self._get_course_id(self.course_data)
initially_enrolled = is_enrolled_in_course(self.user, course_id)
initially_enrolled = CourseEnrollment.is_enrolled(self.user, course_id)
resp = self.client.post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['ErrMsg'], error_message)
# One test case involves trying to create the same course twice. Hence for that course,
# the user will be enrolled. In the other cases, initially_enrolled will be False.
self.assertEqual(initially_enrolled, is_enrolled_in_course(self.user, course_id))
self.assertEqual(initially_enrolled, CourseEnrollment.is_enrolled(self.user, course_id))

def test_create_course_duplicate_number(self):
"""Test new course creation - error path"""
Expand Down
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.contrib.auth.models import User, Group
from django.core.urlresolvers import reverse
from auth.authz import get_course_groupname_for_role
from student.views import is_enrolled_in_course
from student.models import CourseEnrollment


class UsersTestCase(CourseTestCase):
Expand Down Expand Up @@ -372,13 +372,13 @@ def test_staff_to_instructor_still_enrolled(self):
def assert_not_enrolled(self):
""" Asserts that self.ext_user is not enrolled in self.course. """
self.assertFalse(
is_enrolled_in_course(self.ext_user, self.course.location.course_id),
CourseEnrollment.is_enrolled(self.ext_user, self.course.location.course_id),
'Did not expect ext_user to be enrolled in course'
)

def assert_enrolled(self):
""" Asserts that self.ext_user is enrolled in self.course. """
self.assertTrue(
is_enrolled_in_course(self.ext_user, self.course.location.course_id),
CourseEnrollment.is_enrolled(self.ext_user, self.course.location.course_id),
'User ext_user should have been enrolled in the course'
)
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

from django_comment_common.utils import seed_permissions_roles

from student.views import enroll_in_course
from student.models import CourseEnrollment

from xmodule.html_module import AboutDescriptor
__all__ = ['course_index', 'create_new_course', 'course_info',
Expand Down Expand Up @@ -165,7 +165,7 @@ def create_new_course(request):
seed_permissions_roles(new_course.location.course_id)

# auto-enroll the course creator in the course so that "View Live" will work.
enroll_in_course(request.user, new_course.location.course_id)
CourseEnrollment.enroll(request.user, new_course.location.course_id)

return JsonResponse({'id': new_course.location.url()})

Expand Down
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from .access import has_access

from student.views import enroll_in_course
from student.models import CourseEnrollment


@login_required
Expand Down Expand Up @@ -208,7 +208,7 @@ def course_team_user(request, org, course, name, email):
user.groups.add(groups["instructor"])
user.save()
# auto-enroll the course creator in the course so that "View Live" will work.
enroll_in_course(user, location.course_id)
CourseEnrollment.enroll(user, location.course_id)
elif role == "staff":
# if we're trying to downgrade a user from "instructor" to "staff",
# make sure we have at least one other instructor in the course team.
Expand All @@ -223,7 +223,7 @@ def course_team_user(request, org, course, name, email):
user.groups.add(groups["staff"])
user.save()
# auto-enroll the course creator in the course so that "View Live" will work.
enroll_in_course(user, location.course_id)
CourseEnrollment.enroll(user, location.course_id)

return JsonResponse()

Expand Down
14 changes: 14 additions & 0 deletions common/djangoapps/django_comment_common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@

@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
# The code below would remove all forum Roles from a user when they unenroll
# from a course. Concerns were raised that it should apply only to students,
# or that even the history of student roles is important for research
# purposes. Since this was new functionality being added in this release,
# I'm just going to comment it out for now and let the forums team deal with
# implementing the right behavior.
#
# # We've unenrolled the student, so remove all roles for this course
# if not instance.is_active:
# course_roles = list(Role.objects.filter(course_id=instance.course_id))
# instance.user.roles.remove(*course_roles)
# return

# We've enrolled the student, so make sure they have a default role
if instance.user.is_staff:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want two consecutive ifs rather than nested ones? It will likely never matter, but semantically, do you want to make one branch if you're a staff and another if you're a student. The edge case is a staff who unregisters for a course but should still have staff access. I guess the question is do you only want to check the active flag for non-staff, more as a policy question?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why a staff member should continue to have Moderator privileges for a course that they are no longer enrolled in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was more concerned about the corollary where this essentially requires all moderators to be enrolled. Does every PM and every TA have to be enrolled explicitly to have rights in the forums? How about the instructor? I don't know the answer to that but if the answer is no, then we'll inadvertently remove all course roles for staff by virtue of their not being enrolled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I had forgotten just how divorced forum user privileges are from everything else in our system. @jimabramson: What's the desired behavior here? Remove student roles only?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is important for researchers studying course histories to know what roles a student had in the forum, during the course, eg community TA or other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, how about this? I'll comment this check out, I'll comment out the tests that depend on it, and the forum team can implement whatever the right thing is at a later date. This will make the situation no worse than before the merge (since it didn't take any action back then on unenrollment either).

role = Role.objects.get_or_create(course_id=instance.course_id, name="Moderator")[0]
else:
Expand Down
58 changes: 58 additions & 0 deletions common/djangoapps/django_comment_common/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from django.test import TestCase

from django_comment_common.models import Role
from student.models import CourseEnrollment, User

class RoleAssignmentTest(TestCase):
"""
Basic checks to make sure our Roles get assigned and unassigned as students
are enrolled and unenrolled from a course.
"""

def setUp(self):
self.staff_user = User.objects.create_user(
"patty",
"patty@fake.edx.org",
)
self.staff_user.is_staff = True

self.student_user = User.objects.create_user(
"hacky",
"hacky@fake.edx.org"
)
self.course_id = "edX/Fake101/2012"
CourseEnrollment.enroll(self.staff_user, self.course_id)
CourseEnrollment.enroll(self.student_user, self.course_id)

def test_enrollment_auto_role_creation(self):
moderator_role = Role.objects.get(
course_id=self.course_id,
name="Moderator"
)
student_role = Role.objects.get(
course_id=self.course_id,
name="Student"
)
self.assertIn(moderator_role, self.staff_user.roles.all())

self.assertIn(student_role, self.student_user.roles.all())
self.assertNotIn(moderator_role, self.student_user.roles.all())

# The following was written on the assumption that unenrolling from a course
# should remove all forum Roles for that student for that course. This is
# not necessarily the case -- please see comments at the top of
# django_comment_client.models.assign_default_role(). Leaving it for the
# forums team to sort out.
#
# def test_unenrollment_auto_role_removal(self):
# another_student = User.objects.create_user("sol", "sol@fake.edx.org")
# CourseEnrollment.enroll(another_student, self.course_id)
#
# CourseEnrollment.unenroll(self.student_user, self.course_id)
# # Make sure we didn't delete the actual Role
# student_role = Role.objects.get(
# course_id=self.course_id,
# name="Student"
# )
# self.assertNotIn(student_role, self.student_user.roles.all())
# self.assertIn(student_role, another_student.roles.all())
10 changes: 5 additions & 5 deletions common/djangoapps/external_auth/tests/test_shib.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,12 @@ def test_enrollment_limit_by_domain(self):
# If course is not limited or student has correct shib extauth then enrollment should be allowed
if course is open_enroll_course or student is shib_student:
self.assertEqual(response.status_code, 200)
self.assertEqual(CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 1)
self.assertTrue(CourseEnrollment.is_enrolled(student, course.id))
# Clean up
CourseEnrollment.objects.filter(user=student, course_id=course.id).delete()
CourseEnrollment.unenroll(student, course.id)
else:
self.assertEqual(response.status_code, 400)
self.assertEqual(CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 0)
self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))

@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), True)
def test_shib_login_enrollment(self):
Expand All @@ -462,7 +462,7 @@ def test_shib_login_enrollment(self):

# use django test client for sessions and url processing
# no enrollment before trying
self.assertEqual(CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 0)
self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))
self.client.logout()
request_kwargs = {'path': '/shib-login/',
'data': {'enrollment_action': 'enroll', 'course_id': course.id},
Expand All @@ -474,4 +474,4 @@ def test_shib_login_enrollment(self):
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], 'http://testserver/')
# now there is enrollment
self.assertEqual(CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 1)
self.assertTrue(CourseEnrollment.is_enrolled(student, course.id))
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def create(n, course_id):
for i in range(n):
(user, user_profile, _) = _do_create_account(get_random_post_override())
if course_id is not None:
CourseEnrollment.objects.create(user=user, course_id=course_id)
CourseEnrollment.enroll(user, course_id)


class Command(BaseCommand):
Expand Down
Loading