feat: move new forum content creation notifications to use course wide notification event
This commit is contained in:
@@ -3,12 +3,10 @@ Discussion notifications sender util.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from lms.djangoapps.discussion.django_comment_client.permissions import get_team
|
||||
from openedx_events.learning.data import UserNotificationData
|
||||
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED
|
||||
from openedx_events.learning.data import UserNotificationData, CourseNotificationData
|
||||
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED, COURSE_NOTIFICATION_REQUESTED
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from openedx.core.djangoapps.course_groups.models import CourseCohortsSettings, CourseUserGroup
|
||||
from openedx.core.djangoapps.course_groups.models import CourseCohortsSettings
|
||||
from openedx.core.djangoapps.discussions.utils import get_divided_discussions
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -19,7 +17,6 @@ from openedx.core.djangoapps.django_comment_common.models import (
|
||||
FORUM_ROLE_COMMUNITY_TA,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
CourseDiscussionSettings,
|
||||
Role
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +59,29 @@ class DiscussionNotificationSender:
|
||||
)
|
||||
USER_NOTIFICATION_REQUESTED.send_event(notification_data=notification_data)
|
||||
|
||||
def _send_course_wide_notification(self, notification_type, audience_filters=None, extra_context=None):
|
||||
"""
|
||||
Send notification to all users in the course
|
||||
"""
|
||||
if not extra_context:
|
||||
extra_context = {}
|
||||
|
||||
notification_data = CourseNotificationData(
|
||||
course_key=self.course.id,
|
||||
content_context={
|
||||
"replier_name": self.creator.username,
|
||||
"post_title": self.thread.title,
|
||||
"course_name": self.course.display_name,
|
||||
"sender_id": self.creator.id,
|
||||
**extra_context,
|
||||
},
|
||||
notification_type=notification_type,
|
||||
content_url=f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/{str(self.course.id)}/posts/{self.thread.id}",
|
||||
app_name="discussion",
|
||||
audience_filters=audience_filters,
|
||||
)
|
||||
COURSE_NOTIFICATION_REQUESTED.send_event(course_notification_data=notification_data)
|
||||
|
||||
def _get_parent_response(self):
|
||||
"""
|
||||
Get parent response object
|
||||
@@ -175,7 +195,7 @@ class DiscussionNotificationSender:
|
||||
|
||||
def _create_cohort_course_audience(self):
|
||||
"""
|
||||
Creates audience based on user cohort and role
|
||||
Creates audience filter based on user cohort and role
|
||||
"""
|
||||
course_key_str = str(self.course.id)
|
||||
discussion_cohorted = is_discussion_cohorted(course_key_str)
|
||||
@@ -193,43 +213,21 @@ class DiscussionNotificationSender:
|
||||
group_id = int(group_id)
|
||||
|
||||
# Course wide topics
|
||||
topic_id = self.thread.attributes['commentable_id']
|
||||
all_topics = divided_inline_discussions + divided_course_wide_discussions
|
||||
topic_id = self.thread.attributes['commentable_id']
|
||||
topic_divided = topic_id in all_topics or discussion_settings.always_divide_inline_discussions
|
||||
|
||||
# Team object from topic id
|
||||
team = get_team(topic_id)
|
||||
|
||||
user_ids = []
|
||||
if team:
|
||||
user_ids = team.users.all().values_list('id', flat=True)
|
||||
elif discussion_cohorted and topic_divided and group_id is not None:
|
||||
users_in_cohort = CourseUserGroup.objects.filter(
|
||||
course_id=course_key_str, id=group_id
|
||||
).values_list('users__id', flat=True)
|
||||
user_ids.extend(users_in_cohort)
|
||||
|
||||
privileged_roles = [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]
|
||||
privileged_users = Role.objects.filter(
|
||||
name__in=privileged_roles,
|
||||
course_id=course_key_str
|
||||
).values_list('users__id', flat=True)
|
||||
user_ids.extend(privileged_users)
|
||||
|
||||
staff_users = CourseStaffRole(self.course.id).users_with_role().values_list('id', flat=True)
|
||||
user_ids.extend(staff_users)
|
||||
|
||||
admin_users = CourseInstructorRole(self.course.id).users_with_role().values_list('id', flat=True)
|
||||
user_ids.extend(admin_users)
|
||||
else:
|
||||
user_ids = CourseEnrollment.objects.filter(
|
||||
course__id=course_key_str, is_active=True
|
||||
).values_list('user__id', flat=True)
|
||||
|
||||
unique_user_ids = list(set(user_ids))
|
||||
if self.creator.id in unique_user_ids:
|
||||
unique_user_ids.remove(self.creator.id)
|
||||
return unique_user_ids
|
||||
return {
|
||||
'teams': [team.team_id],
|
||||
}
|
||||
if discussion_cohorted and topic_divided and group_id is not None:
|
||||
return {
|
||||
'cohorts': [group_id],
|
||||
}
|
||||
return {}
|
||||
|
||||
def send_new_thread_created_notification(self):
|
||||
"""
|
||||
@@ -244,12 +242,22 @@ class DiscussionNotificationSender:
|
||||
if notification_type not in ['new_discussion_post', 'new_question_post']:
|
||||
raise ValueError(f'Invalid notification type {notification_type}')
|
||||
|
||||
user_ids = self._create_cohort_course_audience()
|
||||
audience_filters = self._create_cohort_course_audience()
|
||||
|
||||
if audience_filters:
|
||||
# If the audience is cohorted/teamed, we add the course and forum roles to the audience.
|
||||
# Include course staff and instructors for course wide discussion notifications.
|
||||
audience_filters['course_roles'] = ['staff', 'instructor']
|
||||
|
||||
# Include privileged forum roles for course wide discussion notifications.
|
||||
audience_filters['discussion_roles'] = [
|
||||
FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA
|
||||
]
|
||||
context = {
|
||||
'username': self.creator.username,
|
||||
'post_title': self.thread.title
|
||||
}
|
||||
self._send_notification(user_ids, notification_type, context)
|
||||
self._send_course_wide_notification(notification_type, audience_filters, context)
|
||||
|
||||
|
||||
def is_discussion_cohorted(course_key_str):
|
||||
|
||||
@@ -8,7 +8,7 @@ import ddt
|
||||
import httpretty
|
||||
from django.conf import settings
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED
|
||||
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED, COURSE_NOTIFICATION_REQUESTED
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.tests.factories import StaffFactory, UserFactory
|
||||
@@ -158,14 +158,6 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
self.register_get_thread_response(thread)
|
||||
return thread
|
||||
|
||||
def assert_users_id_list(self, user_ids_1, user_ids_2):
|
||||
"""
|
||||
Assert whether the user ids in two lists are same
|
||||
"""
|
||||
assert len(user_ids_1) == len(user_ids_2)
|
||||
for user_id in user_ids_1:
|
||||
assert user_id in user_ids_2
|
||||
|
||||
def test_basic(self):
|
||||
"""
|
||||
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
|
||||
@@ -176,15 +168,6 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
|
||||
"""
|
||||
|
||||
def test_no_notification_if_course_has_no_enrollments(self):
|
||||
"""
|
||||
Tests no notification is send if course has no enrollments
|
||||
"""
|
||||
handler = mock.Mock()
|
||||
USER_NOTIFICATION_REQUESTED.connect(handler)
|
||||
send_thread_created_notification(self.thread['id'], str(self.course.id), self.author.id)
|
||||
self.assertEqual(handler.call_count, 0)
|
||||
|
||||
@ddt.data(
|
||||
('new_question_post',),
|
||||
('new_discussion_post',),
|
||||
@@ -192,7 +175,7 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
@ddt.unpack
|
||||
def test_notification_is_send_to_all_enrollments(self, notification_type):
|
||||
"""
|
||||
Tests notification is send to all users if course is not cohorted
|
||||
Tests notification is sent to all users if course is not cohorted
|
||||
"""
|
||||
self._assign_enrollments()
|
||||
thread_type = (
|
||||
@@ -202,12 +185,13 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
)
|
||||
thread = self._create_thread(thread_type=thread_type)
|
||||
handler = mock.Mock()
|
||||
USER_NOTIFICATION_REQUESTED.connect(handler)
|
||||
COURSE_NOTIFICATION_REQUESTED.connect(handler)
|
||||
send_thread_created_notification(thread['id'], str(self.course.id), self.author.id)
|
||||
self.assertEqual(handler.call_count, 1)
|
||||
assert notification_type == handler.call_args[1]['notification_data'].notification_type
|
||||
user_ids_list = [user.id for user in self.notification_to_all_users]
|
||||
self.assert_users_id_list(user_ids_list, handler.call_args[1]['notification_data'].user_ids)
|
||||
course_notification_data = handler.call_args[1]['course_notification_data']
|
||||
assert notification_type == course_notification_data.notification_type
|
||||
notification_audience_filters = {}
|
||||
assert notification_audience_filters == course_notification_data.audience_filters
|
||||
|
||||
@ddt.data(
|
||||
('cohort_1', 'new_question_post'),
|
||||
@@ -218,7 +202,7 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
@ddt.unpack
|
||||
def test_notification_is_send_to_cohort_ids(self, cohort_text, notification_type):
|
||||
"""
|
||||
Tests if notification is send only to privileged users and cohort members if the
|
||||
Tests if notification is sent only to privileged users and cohort members if the
|
||||
course is cohorted
|
||||
"""
|
||||
self._assign_enrollments()
|
||||
@@ -238,12 +222,17 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
cohort_id = cohort.id
|
||||
thread = self._create_thread(group_id=cohort_id, thread_type=thread_type)
|
||||
handler = mock.Mock()
|
||||
USER_NOTIFICATION_REQUESTED.connect(handler)
|
||||
COURSE_NOTIFICATION_REQUESTED.connect(handler)
|
||||
send_thread_created_notification(thread['id'], str(self.course.id), self.author.id)
|
||||
assert notification_type == handler.call_args[1]['notification_data'].notification_type
|
||||
course_notification_data = handler.call_args[1]['course_notification_data']
|
||||
assert notification_type == course_notification_data.notification_type
|
||||
notification_audience_filters = {
|
||||
'cohorts': [cohort_id],
|
||||
'course_roles': ['staff', 'instructor'],
|
||||
'discussion_roles': ['Administrator', 'Moderator', 'Community TA'],
|
||||
}
|
||||
assert notification_audience_filters == handler.call_args[1]['course_notification_data'].audience_filters
|
||||
self.assertEqual(handler.call_count, 1)
|
||||
user_ids_list = [user.id for user in audience]
|
||||
self.assert_users_id_list(user_ids_list, handler.call_args[1]['notification_data'].user_ids)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
||||
@@ -4,9 +4,14 @@ Audience based filters for notifications
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseStaffRole, CourseInstructorRole
|
||||
from lms.djangoapps.discussion.django_comment_client.utils import get_users_with_roles
|
||||
from lms.djangoapps.teams.models import CourseTeam
|
||||
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
|
||||
from openedx.core.djangoapps.django_comment_common.models import (
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
@@ -33,11 +38,10 @@ class NotificationAudienceFilterBase:
|
||||
pass
|
||||
|
||||
|
||||
class RoleAudienceFilter(NotificationAudienceFilterBase):
|
||||
class ForumRoleAudienceFilter(NotificationAudienceFilterBase):
|
||||
"""
|
||||
Filter class for roles
|
||||
"""
|
||||
# TODO: Add course roles to this. We currently support only forum roles
|
||||
allowed_filters = [
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
@@ -55,6 +59,36 @@ class RoleAudienceFilter(NotificationAudienceFilterBase):
|
||||
return [user.id for user in get_users_with_roles(roles, self.course_key)]
|
||||
|
||||
|
||||
class CourseRoleAudienceFilter(NotificationAudienceFilterBase):
|
||||
"""
|
||||
Filter class for course roles
|
||||
"""
|
||||
allowed_filters = ['staff', 'instructor']
|
||||
|
||||
def filter(self, course_roles):
|
||||
"""
|
||||
Filter users based on their course roles
|
||||
"""
|
||||
if not self.is_valid_filter(course_roles):
|
||||
raise ValueError(f'Invalid roles {course_roles} passed to CourseRoleAudienceFilter')
|
||||
|
||||
user_ids = []
|
||||
|
||||
course_key = self.course_key
|
||||
if not isinstance(course_key, CourseKey):
|
||||
course_key = CourseKey.from_string(course_key)
|
||||
|
||||
if 'staff' in course_roles:
|
||||
staff_users = CourseStaffRole(course_key).users_with_role().values_list('id', flat=True)
|
||||
user_ids.extend(staff_users)
|
||||
|
||||
if 'instructor' in course_roles:
|
||||
instructor_users = CourseInstructorRole(course_key).users_with_role().values_list('id', flat=True)
|
||||
user_ids.extend(instructor_users)
|
||||
|
||||
return user_ids
|
||||
|
||||
|
||||
class EnrollmentAudienceFilter(NotificationAudienceFilterBase):
|
||||
"""
|
||||
Filter class for enrollment modes
|
||||
@@ -71,3 +105,39 @@ class EnrollmentAudienceFilter(NotificationAudienceFilterBase):
|
||||
course_id=self.course_key,
|
||||
mode__in=enrollment_modes,
|
||||
).values_list('user_id', flat=True)
|
||||
|
||||
|
||||
class TeamAudienceFilter(NotificationAudienceFilterBase):
|
||||
"""
|
||||
Filter class for team roles
|
||||
"""
|
||||
|
||||
def filter(self, team_ids):
|
||||
"""
|
||||
Filter users based on team id
|
||||
"""
|
||||
teams = CourseTeam.objects.filter(team_id__in=team_ids, course_id=self.course_key)
|
||||
|
||||
if not teams: # invalid team ids passed
|
||||
raise ValueError(f'Invalid Team ids {team_ids} passed to TeamAudienceFilter for course {self.course_key}')
|
||||
|
||||
user_ids = []
|
||||
for team in teams:
|
||||
user_ids.extend(team.users.all().values_list('id', flat=True))
|
||||
|
||||
return user_ids
|
||||
|
||||
|
||||
class CohortAudienceFilter(NotificationAudienceFilterBase):
|
||||
"""
|
||||
Filter class for cohort roles
|
||||
"""
|
||||
|
||||
def filter(self, group_ids):
|
||||
"""
|
||||
Filter users based on their cohort ids
|
||||
"""
|
||||
users_in_cohort = CourseUserGroup.objects.filter(
|
||||
course_id=self.course_key, id__in=group_ids
|
||||
).values_list('users__id', flat=True)
|
||||
return users_in_cohort
|
||||
|
||||
@@ -14,17 +14,24 @@ from openedx_events.learning.signals import (
|
||||
)
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from openedx.core.djangoapps.notifications.audience_filters import RoleAudienceFilter, EnrollmentAudienceFilter
|
||||
from openedx.core.djangoapps.notifications.audience_filters import (
|
||||
ForumRoleAudienceFilter,
|
||||
EnrollmentAudienceFilter,
|
||||
TeamAudienceFilter,
|
||||
CohortAudienceFilter,
|
||||
CourseRoleAudienceFilter,
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
|
||||
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AUDIENCE_FILTER_TYPES = ['role', 'enrollment']
|
||||
|
||||
AUDIENCE_FILTER_CLASSES = {
|
||||
'role': RoleAudienceFilter,
|
||||
'enrollment': EnrollmentAudienceFilter,
|
||||
'discussion_roles': ForumRoleAudienceFilter,
|
||||
'course_roles': CourseRoleAudienceFilter,
|
||||
'enrollments': EnrollmentAudienceFilter,
|
||||
'teams': TeamAudienceFilter,
|
||||
'cohorts': CohortAudienceFilter,
|
||||
}
|
||||
|
||||
|
||||
@@ -76,11 +83,15 @@ def calculate_course_wide_notification_audience(course_key, audience_filters):
|
||||
Calculate the audience for a course-wide notification based on the audience filters
|
||||
"""
|
||||
if not audience_filters:
|
||||
return CourseEnrollment.objects.filter(course_id=course_key, is_active=True).values_list('user_id', flat=True)
|
||||
active_enrollments = CourseEnrollment.objects.filter(
|
||||
course_id=course_key,
|
||||
is_active=True
|
||||
).values_list('user_id', flat=True)
|
||||
return list(active_enrollments)
|
||||
|
||||
audience_user_ids = []
|
||||
for filter_type, filter_values in audience_filters.items():
|
||||
if filter_type in AUDIENCE_FILTER_TYPES:
|
||||
if filter_type in AUDIENCE_FILTER_CLASSES.keys(): # lint-amnesty, pylint: disable=consider-iterating-dictionary
|
||||
filter_class = AUDIENCE_FILTER_CLASSES.get(filter_type)
|
||||
if filter_class:
|
||||
filter_instance = filter_class(course_key)
|
||||
@@ -93,20 +104,24 @@ def calculate_course_wide_notification_audience(course_key, audience_filters):
|
||||
|
||||
|
||||
@receiver(COURSE_NOTIFICATION_REQUESTED)
|
||||
def generate_course_notifications(signal, sender, notification_data, metadata, **kwargs):
|
||||
def generate_course_notifications(signal, sender, course_notification_data, metadata, **kwargs):
|
||||
"""
|
||||
Watches for COURSE_NOTIFICATION_REQUESTED signal and calls send_notifications task
|
||||
"""
|
||||
from openedx.core.djangoapps.notifications.tasks import send_notifications
|
||||
notification_data = notification_data.__dict__
|
||||
notification_data['course_key'] = str(notification_data['course_key'])
|
||||
|
||||
audience_filters = notification_data.pop('audience_filters')
|
||||
user_ids = calculate_course_wide_notification_audience(
|
||||
notification_data['course_key'],
|
||||
audience_filters,
|
||||
)
|
||||
notification_data['user_ids'] = user_ids
|
||||
notification_data['context'] = notification_data.pop('content_context')
|
||||
from openedx.core.djangoapps.notifications.tasks import send_notifications
|
||||
course_notification_data = course_notification_data.__dict__
|
||||
|
||||
notification_data = {
|
||||
'course_key': str(course_notification_data['course_key']),
|
||||
'user_ids': calculate_course_wide_notification_audience(
|
||||
str(course_notification_data['course_key']),
|
||||
course_notification_data['audience_filters'],
|
||||
),
|
||||
'context': course_notification_data.get('content_context'),
|
||||
'app_name': course_notification_data.get('app_name'),
|
||||
'notification_type': course_notification_data.get('notification_type'),
|
||||
'content_url': course_notification_data.get('content_url'),
|
||||
}
|
||||
|
||||
send_notifications.delay(**notification_data)
|
||||
|
||||
@@ -9,9 +9,11 @@ from django.utils.timezone import now
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseInstructorRole
|
||||
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from common.djangoapps.student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
|
||||
from openedx.core.djangoapps.django_comment_common.models import (
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_COMMUNITY_TA,
|
||||
@@ -22,7 +24,10 @@ from openedx.core.djangoapps.django_comment_common.models import (
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.audience_filters import (
|
||||
EnrollmentAudienceFilter,
|
||||
RoleAudienceFilter,
|
||||
ForumRoleAudienceFilter,
|
||||
CourseRoleAudienceFilter,
|
||||
CohortAudienceFilter,
|
||||
TeamAudienceFilter,
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.filters import NotificationFilter
|
||||
from openedx.core.djangoapps.notifications.handlers import calculate_course_wide_notification_audience
|
||||
@@ -224,9 +229,9 @@ class TestEnrollmentAudienceFilter(ModuleStoreTestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestRoleAudienceFilter(ModuleStoreTestCase):
|
||||
class TestForumRoleAudienceFilter(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the RoleAudienceFilter.
|
||||
Tests for the ForumRoleAudienceFilter.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
@@ -263,17 +268,134 @@ class TestRoleAudienceFilter(ModuleStoreTestCase):
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_valid_role_filter(self, role_names, expected_count):
|
||||
role_filter = RoleAudienceFilter(self.course.id)
|
||||
role_filter = ForumRoleAudienceFilter(self.course.id)
|
||||
filtered_users = role_filter.filter(role_names)
|
||||
self.assertEqual(len(filtered_users), expected_count)
|
||||
|
||||
def test_invalid_role_filter(self):
|
||||
role_filter = RoleAudienceFilter(self.course.id)
|
||||
role_filter = ForumRoleAudienceFilter(self.course.id)
|
||||
role_names = ["INVALID_MODE"]
|
||||
with self.assertRaises(ValueError):
|
||||
role_filter.filter(role_names)
|
||||
|
||||
|
||||
# TODO: Cleanup this test class
|
||||
@ddt.ddt
|
||||
class TestCourseRoleAudienceFilter(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the CourseRoleAudienceFilter.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
self.course = CourseFactory()
|
||||
self.students = [UserFactory() for _ in range(10)]
|
||||
|
||||
# Assign 5 users with course staff role
|
||||
for student in self.students[:5]:
|
||||
CourseStaffRole(self.course.id).add_users(student)
|
||||
|
||||
# Assign 5 users with course instructor role
|
||||
for student in self.students[5:10]:
|
||||
CourseInstructorRole(self.course.id).add_users(student)
|
||||
|
||||
@ddt.data(
|
||||
(["instructor"], 5),
|
||||
(["staff"], 5),
|
||||
(["instructor", "staff"], 10),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_valid_role_filter(self, role_names, expected_count):
|
||||
course_role_filter = CourseRoleAudienceFilter(self.course.id)
|
||||
filtered_users = course_role_filter.filter(role_names)
|
||||
self.assertEqual(len(filtered_users), expected_count)
|
||||
|
||||
def test_invalid_role_filter(self):
|
||||
course_role_filter = CourseRoleAudienceFilter(self.course.id)
|
||||
role_names = ["INVALID_MODE"]
|
||||
with self.assertRaises(ValueError):
|
||||
course_role_filter.filter(role_names)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCohortAudienceFilter(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the CohortAudienceFilter.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
self.course = CourseFactory()
|
||||
|
||||
self.cohort1_users = [UserFactory() for _ in range(3)]
|
||||
self.cohort2_users = [UserFactory() for _ in range(2)]
|
||||
|
||||
users = self.cohort1_users + self.cohort2_users
|
||||
|
||||
for user in users:
|
||||
CourseEnrollment.enroll(user, self.course.id)
|
||||
|
||||
self.cohort1 = CohortFactory(course_id=self.course.id, users=self.cohort1_users)
|
||||
self.cohort2 = CohortFactory(course_id=self.course.id, users=self.cohort2_users)
|
||||
|
||||
@ddt.data(
|
||||
([1], 3),
|
||||
([2], 2),
|
||||
([1, 2], 5),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_valid_cohort_filter(self, cohort_ids, expected_count):
|
||||
cohort_filter = CohortAudienceFilter(self.course.id)
|
||||
filtered_users = cohort_filter.filter(cohort_ids)
|
||||
self.assertEqual(len(filtered_users), expected_count)
|
||||
|
||||
def test_invalid_cohort_filter(self):
|
||||
cohort_filter = CohortAudienceFilter(self.course.id)
|
||||
cohort_ids = ["INVALID_MODE"]
|
||||
with self.assertRaises(ValueError):
|
||||
cohort_filter.filter(cohort_ids)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestTeamAudienceFilter(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the TeamAudienceFilter.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
self.course = CourseFactory()
|
||||
self.teams = [CourseTeamFactory(course_id=self.course.id, team_id=f"team-{i}") for i in range(2)]
|
||||
|
||||
self.team1_users = [UserFactory() for _ in range(3)]
|
||||
self.team2_users = [UserFactory() for _ in range(2)]
|
||||
|
||||
users = self.team1_users + self.team2_users
|
||||
|
||||
for user in users:
|
||||
CourseEnrollment.enroll(user, self.course.id)
|
||||
|
||||
for user in self.team1_users:
|
||||
CourseTeamMembershipFactory.create(team=self.teams[0], user=user)
|
||||
|
||||
for user in self.team2_users:
|
||||
CourseTeamMembershipFactory.create(team=self.teams[1], user=user)
|
||||
|
||||
@ddt.data(
|
||||
(["team-0"], 3),
|
||||
(["team-1"], 2),
|
||||
(["team-0", "team-1"], 5),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_valid_team_filter(self, team_ids, expected_count):
|
||||
team_filter = TeamAudienceFilter(self.course.id)
|
||||
filtered_users = team_filter.filter(team_ids)
|
||||
self.assertEqual(len(filtered_users), expected_count)
|
||||
|
||||
def test_invalid_team_filter(self):
|
||||
team_filter = TeamAudienceFilter(self.course.id)
|
||||
team_ids = ["INVALID_MODE"]
|
||||
with self.assertRaises(ValueError):
|
||||
team_filter.filter(team_ids)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestAudienceFilter(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -310,16 +432,16 @@ class TestAudienceFilter(ModuleStoreTestCase):
|
||||
|
||||
@ddt.data(
|
||||
({
|
||||
"enrollment": ["verified"],
|
||||
"role": ["Moderator"],
|
||||
"enrollments": ["verified"],
|
||||
"discussion_roles": ["Moderator"],
|
||||
}, 15),
|
||||
({
|
||||
"enrollment": ["audit", "verified"],
|
||||
"role": ["Administrator", "Student"],
|
||||
"enrollments": ["audit", "verified"],
|
||||
"discussion_roles": ["Administrator", "Student"],
|
||||
}, 30),
|
||||
({
|
||||
"enrollment": ["audit", "honor", "verified"],
|
||||
"role": ["Administrator", "Moderator", "Student", "Community TA"],
|
||||
"enrollments": ["audit", "honor", "verified"],
|
||||
"discussion_roles": ["Administrator", "Moderator", "Student", "Community TA"],
|
||||
}, 30),
|
||||
)
|
||||
@ddt.unpack
|
||||
|
||||
Reference in New Issue
Block a user