Commit a227b14f by cahrens

Auto-enroll course staff to fix "View Live".

STUD-554

Code review feedback.
parent 25e98fcb
...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
Studio: Studio course authors (both instructors and staff) will be auto-enrolled
for their courses so that "View Live" works.
Common: Added ratelimiting to our authentication backend. Common: Added ratelimiting to our authentication backend.
Common: Add additional logging to cover login attempts and logouts. Common: Add additional logging to cover login attempts and logouts.
......
...@@ -49,7 +49,7 @@ import datetime ...@@ -49,7 +49,7 @@ import datetime
from pytz import UTC from pytz import UTC
from uuid import uuid4 from uuid import uuid4
from pymongo import MongoClient from pymongo import MongoClient
from .utils import get_enrollment_count
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['OPTIONS']['db'] = 'test_xcontent_%s' % uuid4().hex TEST_DATA_CONTENTSTORE['OPTIONS']['db'] = 'test_xcontent_%s' % uuid4().hex
...@@ -1041,12 +1041,18 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1041,12 +1041,18 @@ class ContentStoreTest(ModuleStoreTestCase):
data = parse_json(resp) data = parse_json(resp)
self.assertNotIn('ErrMsg', data) self.assertNotIn('ErrMsg', data)
self.assertEqual(data['id'], 'i4x://MITx/{0}/course/2013_Spring'.format(test_course_data['number'])) 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.assertEqual(1, get_enrollment_count(self.user, self._get_course_id(test_course_data)))
return test_course_data return test_course_data
def test_create_course_check_forum_seeding(self): def test_create_course_check_forum_seeding(self):
"""Test new course creation and verify forum seeding """ """Test new course creation and verify forum seeding """
test_course_data = self.assert_created_course(number_suffix=uuid4().hex) test_course_data = self.assert_created_course(number_suffix=uuid4().hex)
self.assertTrue(are_permissions_roles_seeded('MITx/{0}/2013_Spring'.format(test_course_data['number']))) self.assertTrue(are_permissions_roles_seeded(self._get_course_id(test_course_data)))
def _get_course_id(self, test_course_data):
"""Returns the course ID (org/number/run)."""
return "{org}/{number}/{run}".format(**test_course_data)
def test_create_course_duplicate_course(self): def test_create_course_duplicate_course(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
...@@ -1057,10 +1063,13 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1057,10 +1063,13 @@ class ContentStoreTest(ModuleStoreTestCase):
""" """
Checks that the course did not get created Checks that the course did not get created
""" """
course_id = self._get_course_id(self.course_data)
initially_enrolled_count = get_enrollment_count(self.user, course_id)
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['ErrMsg'], error_message) self.assertEqual(data['ErrMsg'], error_message)
self.assertEqual(initially_enrolled_count, get_enrollment_count(self.user, course_id))
def test_create_course_duplicate_number(self): def test_create_course_duplicate_number(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
......
"""
Tests for contentstore/views/user.py.
"""
import json import json
from .utils import CourseTestCase from .utils import CourseTestCase, get_enrollment_count
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from auth.authz import get_course_groupname_for_role from auth.authz import get_course_groupname_for_role
...@@ -90,6 +93,7 @@ class UsersTestCase(CourseTestCase): ...@@ -90,6 +93,7 @@ class UsersTestCase(CourseTestCase):
# no content: should not be in any roles # no content: should not be in any roles
self.assertNotIn(self.staff_groupname, groups) self.assertNotIn(self.staff_groupname, groups)
self.assertNotIn(self.inst_groupname, groups) self.assertNotIn(self.inst_groupname, groups)
self.assert_not_enrolled()
def test_detail_post_staff(self): def test_detail_post_staff(self):
resp = self.client.post( resp = self.client.post(
...@@ -104,6 +108,7 @@ class UsersTestCase(CourseTestCase): ...@@ -104,6 +108,7 @@ class UsersTestCase(CourseTestCase):
groups = [g.name for g in ext_user.groups.all()] groups = [g.name for g in ext_user.groups.all()]
self.assertIn(self.staff_groupname, groups) self.assertIn(self.staff_groupname, groups)
self.assertNotIn(self.inst_groupname, groups) self.assertNotIn(self.inst_groupname, groups)
self.assert_enrolled()
def test_detail_post_staff_other_inst(self): def test_detail_post_staff_other_inst(self):
inst_group, _ = Group.objects.get_or_create(name=self.inst_groupname) inst_group, _ = Group.objects.get_or_create(name=self.inst_groupname)
...@@ -122,6 +127,7 @@ class UsersTestCase(CourseTestCase): ...@@ -122,6 +127,7 @@ class UsersTestCase(CourseTestCase):
groups = [g.name for g in ext_user.groups.all()] groups = [g.name for g in ext_user.groups.all()]
self.assertIn(self.staff_groupname, groups) self.assertIn(self.staff_groupname, groups)
self.assertNotIn(self.inst_groupname, groups) self.assertNotIn(self.inst_groupname, groups)
self.assert_enrolled()
# check that other user is unchanged # check that other user is unchanged
user = User.objects.get(email=self.user.email) user = User.objects.get(email=self.user.email)
groups = [g.name for g in user.groups.all()] groups = [g.name for g in user.groups.all()]
...@@ -141,6 +147,7 @@ class UsersTestCase(CourseTestCase): ...@@ -141,6 +147,7 @@ class UsersTestCase(CourseTestCase):
groups = [g.name for g in ext_user.groups.all()] groups = [g.name for g in ext_user.groups.all()]
self.assertNotIn(self.staff_groupname, groups) self.assertNotIn(self.staff_groupname, groups)
self.assertIn(self.inst_groupname, groups) self.assertIn(self.inst_groupname, groups)
self.assert_enrolled()
def test_detail_post_missing_role(self): def test_detail_post_missing_role(self):
resp = self.client.post( resp = self.client.post(
...@@ -152,6 +159,7 @@ class UsersTestCase(CourseTestCase): ...@@ -152,6 +159,7 @@ class UsersTestCase(CourseTestCase):
self.assert4XX(resp.status_code) self.assert4XX(resp.status_code)
result = json.loads(resp.content) result = json.loads(resp.content)
self.assertIn("error", result) self.assertIn("error", result)
self.assert_not_enrolled()
def test_detail_post_bad_json(self): def test_detail_post_bad_json(self):
resp = self.client.post( resp = self.client.post(
...@@ -163,6 +171,7 @@ class UsersTestCase(CourseTestCase): ...@@ -163,6 +171,7 @@ class UsersTestCase(CourseTestCase):
self.assert4XX(resp.status_code) self.assert4XX(resp.status_code)
result = json.loads(resp.content) result = json.loads(resp.content)
self.assertIn("error", result) self.assertIn("error", result)
self.assert_not_enrolled()
def test_detail_post_no_json(self): def test_detail_post_no_json(self):
resp = self.client.post( resp = self.client.post(
...@@ -176,6 +185,7 @@ class UsersTestCase(CourseTestCase): ...@@ -176,6 +185,7 @@ class UsersTestCase(CourseTestCase):
groups = [g.name for g in ext_user.groups.all()] groups = [g.name for g in ext_user.groups.all()]
self.assertIn(self.staff_groupname, groups) self.assertIn(self.staff_groupname, groups)
self.assertNotIn(self.inst_groupname, groups) self.assertNotIn(self.inst_groupname, groups)
self.assert_enrolled()
def test_detail_delete_staff(self): def test_detail_delete_staff(self):
group, _ = Group.objects.get_or_create(name=self.staff_groupname) group, _ = Group.objects.get_or_create(name=self.staff_groupname)
...@@ -317,3 +327,57 @@ class UsersTestCase(CourseTestCase): ...@@ -317,3 +327,57 @@ class UsersTestCase(CourseTestCase):
ext_user = User.objects.get(email=self.ext_user.email) ext_user = User.objects.get(email=self.ext_user.email)
groups = [g.name for g in ext_user.groups.all()] groups = [g.name for g in ext_user.groups.all()]
self.assertIn(self.staff_groupname, groups) self.assertIn(self.staff_groupname, groups)
def test_user_not_initially_enrolled(self):
# Verify that ext_user is not enrolled in the new course before being added as a staff member.
self.assert_not_enrolled()
def test_remove_staff_does_not_enroll(self):
# Add user with staff permissions.
self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assert_enrolled()
# Remove user from staff on course. Will not un-enroll them from the course.
resp = self.client.delete(
self.detail_url,
HTTP_ACCEPT="application/json",
)
self.assert2XX(resp.status_code)
self.assert_enrolled()
def test_staff_to_instructor_still_enrolled(self):
# Add user with staff permission.
self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assert_enrolled()
# Now add with instructor permission. Verify still enrolled.
resp = self.client.post(
self.detail_url,
data=json.dumps({"role": "instructor"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assert2XX(resp.status_code)
self.assert_enrolled()
def assert_not_enrolled(self):
""" Asserts that self.ext_user is not enrolled in self.course. """
self.assertEqual(
0, get_enrollment_count(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.assertEqual(
1, get_enrollment_count(self.ext_user, self.course.location.course_id),
'User ext_user should have been enrolled in the course'
)
...@@ -5,6 +5,7 @@ Utilities for contentstore tests ...@@ -5,6 +5,7 @@ Utilities for contentstore tests
import json import json
from student.models import Registration from student.models import Registration
from student.models import CourseEnrollment
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test.client import Client from django.test.client import Client
...@@ -27,6 +28,15 @@ def registration(email): ...@@ -27,6 +28,15 @@ def registration(email):
return Registration.objects.get(user__email=email) return Registration.objects.get(user__email=email)
def get_enrollment_count(user, course_id):
"""
Gets the number of enrolled registrants for given course and user=self.user.
This should always be 0 or 1.
"""
return CourseEnrollment.objects.filter(user=user, course_id=course_id).count()
class CourseTestCase(ModuleStoreTestCase): class CourseTestCase(ModuleStoreTestCase):
def setUp(self): def setUp(self):
""" """
......
...@@ -44,6 +44,8 @@ from .component import ( ...@@ -44,6 +44,8 @@ from .component import (
from django_comment_common.utils import seed_permissions_roles from django_comment_common.utils import seed_permissions_roles
from student.models import CourseEnrollment
from xmodule.html_module import AboutDescriptor from xmodule.html_module import AboutDescriptor
__all__ = ['course_index', 'create_new_course', 'course_info', __all__ = ['course_index', 'create_new_course', 'course_info',
'course_info_updates', 'get_course_settings', 'course_info_updates', 'get_course_settings',
...@@ -162,6 +164,9 @@ def create_new_course(request): ...@@ -162,6 +164,9 @@ def create_new_course(request):
# seed the forums # seed the forums
seed_permissions_roles(new_course.location.course_id) seed_permissions_roles(new_course.location.course_id)
# auto-enroll the course creator in the course so that "View Live" will work.
CourseEnrollment.objects.get_or_create(user=request.user, course_id=new_course.location.course_id)
return JsonResponse({'id': new_course.location.url()}) return JsonResponse({'id': new_course.location.url()})
......
...@@ -23,6 +23,8 @@ from course_creators.views import ( ...@@ -23,6 +23,8 @@ from course_creators.views import (
from .access import has_access from .access import has_access
from student.models import CourseEnrollment
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -201,6 +203,8 @@ def course_team_user(request, org, course, name, email): ...@@ -201,6 +203,8 @@ def course_team_user(request, org, course, name, email):
return JsonResponse(msg, 400) return JsonResponse(msg, 400)
user.groups.add(groups["instructor"]) user.groups.add(groups["instructor"])
user.save() user.save()
# auto-enroll the course creator in the course so that "View Live" will work.
CourseEnrollment.objects.get_or_create(user=user, course_id=location.course_id)
elif role == "staff": elif role == "staff":
# if we're trying to downgrade a user from "instructor" to "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. # make sure we have at least one other instructor in the course team.
...@@ -214,6 +218,9 @@ def course_team_user(request, org, course, name, email): ...@@ -214,6 +218,9 @@ def course_team_user(request, org, course, name, email):
user.groups.remove(groups["instructor"]) user.groups.remove(groups["instructor"])
user.groups.add(groups["staff"]) user.groups.add(groups["staff"])
user.save() user.save()
# auto-enroll the course creator in the course so that "View Live" will work.
CourseEnrollment.objects.get_or_create(user=user, course_id=location.course_id)
return JsonResponse() return JsonResponse()
......
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