feat: add email support to the enrollment post and get methods (#33006)
This commit is contained in:
committed by
GitHub
parent
02dd36cdc1
commit
cddfc02fbc
@@ -15,9 +15,10 @@ class CourseEnrollmentsApiListForm(Form):
|
||||
"""
|
||||
A form that validates the query string parameters for the CourseEnrollmentsApiListView.
|
||||
"""
|
||||
MAX_USERNAME_COUNT = 100
|
||||
MAX_INPUT_COUNT = 100
|
||||
username = CharField(required=False)
|
||||
course_id = CharField(required=False)
|
||||
email = CharField(required=False)
|
||||
|
||||
def clean_course_id(self):
|
||||
"""
|
||||
@@ -38,14 +39,31 @@ class CourseEnrollmentsApiListForm(Form):
|
||||
usernames_csv_string = self.cleaned_data.get('username')
|
||||
if usernames_csv_string:
|
||||
usernames = usernames_csv_string.split(',')
|
||||
if len(usernames) > self.MAX_USERNAME_COUNT:
|
||||
if len(usernames) > self.MAX_INPUT_COUNT:
|
||||
raise ValidationError(
|
||||
"Too many usernames in a single request - {}. A maximum of {} is allowed".format(
|
||||
len(usernames),
|
||||
self.MAX_USERNAME_COUNT,
|
||||
self.MAX_INPUT_COUNT,
|
||||
)
|
||||
)
|
||||
for username in usernames:
|
||||
validate_username(username)
|
||||
return usernames
|
||||
return usernames_csv_string
|
||||
|
||||
def clean_email(self):
|
||||
"""
|
||||
Validate a string of comma-separated emails and return a list of emails.
|
||||
"""
|
||||
emails_csv_string = self.cleaned_data.get('email')
|
||||
if emails_csv_string:
|
||||
emails = emails_csv_string.split(',')
|
||||
if len(emails) > self.MAX_INPUT_COUNT:
|
||||
raise ValidationError(
|
||||
"Too many emails in a single request - {}. A maximum of {} is allowed".format(
|
||||
len(emails),
|
||||
self.MAX_INPUT_COUNT,
|
||||
)
|
||||
)
|
||||
return emails
|
||||
return emails_csv_string
|
||||
|
||||
@@ -114,6 +114,63 @@
|
||||
]
|
||||
|
||||
],
|
||||
[
|
||||
{
|
||||
"email": "student1@example.com"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "course-v1:e+d+X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student1",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"email": "student1@example.com,student2@example.com"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "course-v1:e+d+X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student1",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "course-v1:e+d+X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "course-v1:x+y+Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"email": "student2@example.com",
|
||||
"course_id": "course-v1:e+d+X"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "course-v1:e+d+X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
[
|
||||
|
||||
@@ -237,6 +237,112 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
|
||||
assert is_active
|
||||
assert course_mode == enrollment_mode
|
||||
|
||||
def test_enroll_with_email_staff(self):
|
||||
# Create enrollments with email are allowed if you are staff.
|
||||
|
||||
self.client.logout()
|
||||
AdminFactory.create(username='global_staff', email='global_staff@example.com', password=self.PASSWORD)
|
||||
self.client.login(username="global_staff", password=self.PASSWORD)
|
||||
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'email': self.user.email
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == status.HTTP_200_OK
|
||||
|
||||
@patch('openedx.core.djangoapps.enrollments.views.EnrollmentListView.has_api_key_permissions')
|
||||
def test_enroll_with_email_server(self, has_api_key_permissions_mock):
|
||||
# Create enrollments with email are allowed if it is a server-to-server request.
|
||||
|
||||
has_api_key_permissions_mock.return_value = True
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'email': self.user.email
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == status.HTTP_200_OK
|
||||
|
||||
def test_enroll_with_email_without_staff(self):
|
||||
# If you are not staff or server request you can't create enrollments with email.
|
||||
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'email': self.other_user.email
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_enroll_with_user_and_email(self):
|
||||
# Creating enrollments the user has priority over the email.
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'user': self.user.username,
|
||||
'email': self.other_user.email
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
self.assertContains(resp, self.user.username, status_code=status.HTTP_200_OK)
|
||||
|
||||
def test_enroll_with_user_without_permissions_and_email(self):
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'user': self.other_user.username,
|
||||
'email': self.user.email
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_enroll_with_user_as_self_user(self):
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
},
|
||||
'user': self.user.username
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
self.assertContains(resp, self.user.username, status_code=status.HTTP_200_OK)
|
||||
|
||||
def test_enroll_without_user(self):
|
||||
# To check if it takes the request.user.
|
||||
resp = self.client.post(
|
||||
reverse('courseenrollments'),
|
||||
{
|
||||
'course_details': {
|
||||
'course_id': str(self.course.id)
|
||||
}
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
self.assertContains(resp, self.user.username, status_code=status.HTTP_200_OK)
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect that users are automatically enrolled as the default
|
||||
|
||||
@@ -676,7 +676,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
|
||||
"""
|
||||
# Get the User, Course ID, and Mode from the request.
|
||||
|
||||
username = request.data.get('user', request.user.username)
|
||||
username = request.data.get('user')
|
||||
course_id = request.data.get('course_details', {}).get('course_id')
|
||||
|
||||
if not course_id:
|
||||
@@ -700,14 +700,32 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
|
||||
has_api_key_permissions = self.has_api_key_permissions(request)
|
||||
|
||||
# Check that the user specified is either the same user, or this is a server-to-server request.
|
||||
if not username:
|
||||
username = request.user.username
|
||||
if username != request.user.username and not has_api_key_permissions \
|
||||
if username and username != request.user.username and not has_api_key_permissions \
|
||||
and not GlobalStaff().has_user(request.user):
|
||||
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
|
||||
# other users, do not let them deduce the existence of an enrollment.
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# A provided user has priority over a provided email.
|
||||
# Fallback on request user if neither is provided.
|
||||
if not username:
|
||||
email = request.data.get('email')
|
||||
if email:
|
||||
# Only server-to-server or staff users can use the email for the request.
|
||||
if not has_api_key_permissions and not GlobalStaff().has_user(request.user):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
try:
|
||||
username = User.objects.get(email=email).username
|
||||
except ObjectDoesNotExist:
|
||||
return Response(
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
data={
|
||||
'message': f'The user with the email address {email} does not exist.'
|
||||
}
|
||||
)
|
||||
else:
|
||||
username = request.user.username
|
||||
|
||||
if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions \
|
||||
and not GlobalStaff().has_user(request.user):
|
||||
return Response(
|
||||
@@ -911,6 +929,8 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView):
|
||||
|
||||
GET /api/enrollment/v1/enrollments?course_id={course_id}&username={username}
|
||||
|
||||
GET /api/enrollment/v1/enrollments?email={email},{email}
|
||||
|
||||
**Query Parameters for GET**
|
||||
|
||||
* course_id: Filters the result to course enrollments for the course corresponding to the
|
||||
@@ -919,6 +939,9 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView):
|
||||
* username: List of comma-separated usernames. Filters the result to the course enrollments
|
||||
of the given users. Optional.
|
||||
|
||||
* email: List of comma-separated emails. Filters the result to the course enrollments
|
||||
of the given users. Optional.
|
||||
|
||||
* page_size: Number of results to return per page. Optional.
|
||||
|
||||
* page: Page number to retrieve. Optional.
|
||||
@@ -981,9 +1004,12 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView):
|
||||
queryset = CourseEnrollment.objects.all()
|
||||
course_id = form.cleaned_data.get('course_id')
|
||||
usernames = form.cleaned_data.get('username')
|
||||
emails = form.cleaned_data.get('email')
|
||||
|
||||
if course_id:
|
||||
queryset = queryset.filter(course_id=course_id)
|
||||
if usernames:
|
||||
queryset = queryset.filter(user__username__in=usernames)
|
||||
if emails:
|
||||
queryset = queryset.filter(user__email__in=emails)
|
||||
return queryset
|
||||
|
||||
Reference in New Issue
Block a user