diff --git a/lms/djangoapps/discussion/rest_api/api.py b/lms/djangoapps/discussion/rest_api/api.py index edd5bd2da58c..2e200fbd3c25 100644 --- a/lms/djangoapps/discussion/rest_api/api.py +++ b/lms/djangoapps/discussion/rest_api/api.py @@ -44,7 +44,6 @@ DiscussionsConfiguration, DiscussionTopicLink, Provider, - PostingRestriction ) from openedx.core.djangoapps.discussions.utils import get_accessible_discussion_xblocks from openedx.core.djangoapps.django_comment_common import comment_client @@ -128,7 +127,7 @@ discussion_open_for_user, get_usernames_for_course, get_usernames_from_search_string, - set_attribute, send_response_notifications + set_attribute, send_response_notifications, is_posting_allowed ) @@ -324,25 +323,6 @@ def _format_datetime(dt): """ return dt.isoformat().replace('+00:00', 'Z') - def is_posting_allowed(posting_restrictions, blackout_schedules): - """ - Check if posting is allowed based on the given posting restrictions and blackout schedules. - - Args: - posting_restrictions (str): Values would be "disabled", "scheduled" or "enabled". - blackout_schedules (List[Dict[str, datetime]]): The list of blackout schedules - - Returns: - bool: True if posting is allowed, False otherwise. - """ - now = datetime.now(UTC) - if posting_restrictions == PostingRestriction.DISABLED: - return True - elif posting_restrictions == PostingRestriction.SCHEDULED: - return not any(schedule["start"] <= now <= schedule["end"] for schedule in blackout_schedules) - else: - return False - course = _get_course(course_key, request.user) user_roles = get_user_role_names(request.user, course_key) course_config = DiscussionsConfiguration.get(course_key) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_utils.py b/lms/djangoapps/discussion/rest_api/tests/test_utils.py index 400a35a4291b..655e78670208 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_utils.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta from unittest.mock import Mock +import ddt from django.conf import settings from httpretty import httpretty from pytz import UTC @@ -12,6 +13,7 @@ from common.djangoapps.student.roles import CourseStaffRole, CourseInstructorRole from lms.djangoapps.discussion.django_comment_client.tests.utils import ForumsEnableMixin from lms.djangoapps.discussion.rest_api.tests.utils import CommentsServiceMockMixin, ThreadMock +from openedx.core.djangoapps.discussions.models import PostingRestriction from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -24,7 +26,7 @@ get_moderator_users_list, get_archived_topics, remove_empty_sequentials, - send_response_notifications + send_response_notifications, is_posting_allowed ) from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED @@ -307,3 +309,80 @@ def test_comment_creators_own_response(self): _get_mfe_url(self.course.id, self.thread.id) ) self.assertEqual(args_comment.app_name, 'discussion') + + +@ddt.ddt +class TestBlackoutDates(ForumsEnableMixin, CommentsServiceMockMixin, ModuleStoreTestCase): + """ + Test for the is_posting_allowed function + """ + + def setUp(self): + super().setUp() + self.course = CourseFactory.create() + + def _get_date_ranges(self): + """ + Generate date ranges for testing purposes. + Returns: + list: List of date range tuples. + """ + now = datetime.now(UTC) + date_ranges = [ + (now - timedelta(days=14), now + timedelta(days=23)), + ] + return date_ranges + + def _set_discussion_blackouts(self, date_ranges): + """ + Set discussion blackouts for the given date ranges. + Args: + date_ranges (list): List of date range tuples. + """ + self.course.discussion_blackouts = [ + [start_date.isoformat(), end_date.isoformat()] for start_date, end_date in date_ranges + ] + + def _check_posting_allowed(self, posting_restriction): + """ + Check if posting is allowed for the given posting restriction. + Args: + posting_restriction (str): Posting restriction type. + Returns: + bool: True if posting is allowed, False otherwise. + """ + return is_posting_allowed( + posting_restriction, + self.course.get_discussion_blackout_datetimes() + ) + + @ddt.data( + (PostingRestriction.DISABLED, True), + (PostingRestriction.ENABLED, False), + (PostingRestriction.SCHEDULED, False), + ) + @ddt.unpack + def test_blackout_dates(self, restriction, state): + """ + Test is_posting_allowed function with the misc posting restriction + """ + date_ranges = self._get_date_ranges() + self._set_discussion_blackouts(date_ranges) + + posting_allowed = self._check_posting_allowed(restriction) + self.assertEqual(state, posting_allowed) + + def test_posting_scheduled_future(self): + """ + Test posting when the posting restriction is scheduled in the future. + Assertion: + Posting should be allowed. + """ + now = datetime.now(UTC) + date_ranges = [ + (now + timedelta(days=6), now + timedelta(days=23)), + ] + self._set_discussion_blackouts(date_ranges) + + posting_allowed = self._check_posting_allowed(PostingRestriction.SCHEDULED) + self.assertTrue(posting_allowed) diff --git a/lms/djangoapps/discussion/rest_api/utils.py b/lms/djangoapps/discussion/rest_api/utils.py index 23a6b79746c0..a774e579b45e 100644 --- a/lms/djangoapps/discussion/rest_api/utils.py +++ b/lms/djangoapps/discussion/rest_api/utils.py @@ -1,6 +1,8 @@ """ Utils for discussion API. """ +from datetime import datetime +from pytz import UTC from typing import List, Dict from django.conf import settings @@ -10,6 +12,7 @@ from common.djangoapps.student.roles import CourseStaffRole, CourseInstructorRole from lms.djangoapps.discussion.django_comment_client.utils import has_discussion_privileges +from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, PostingRestriction from openedx.core.djangoapps.django_comment_common.models import ( Role, FORUM_ROLE_ADMINISTRATOR, @@ -33,13 +36,18 @@ class AttributeDict(dict): def discussion_open_for_user(course, user): """ - Check if course discussion are open or not for user. + Check if the course discussion are open or not for user. Arguments: course: Course to check discussions for user: User to check for privileges in course """ - return course.forum_posts_allowed or has_discussion_privileges(user, course.id) + discussions_posting_restrictions = DiscussionsConfiguration.get(course.id).posting_restrictions + blackout_dates = course.get_discussion_blackout_datetimes() + return ( + is_posting_allowed(discussions_posting_restrictions, blackout_dates) or + has_discussion_privileges(user, course.id) + ) def set_attribute(threads, attribute, value): @@ -455,3 +463,23 @@ def send_new_comment_on_response_notification(self): self._response_and_thread_has_same_creator() ): self._send_notification([self.parent_response.user_id], "new_comment_on_response") + + +def is_posting_allowed(posting_restrictions: str, blackout_schedules: List): + """ + Check if posting is allowed based on the given posting restrictions and blackout schedules. + + Args: + posting_restrictions (str): Values would be "disabled", "scheduled" or "enabled". + blackout_schedules (List[Dict[str, datetime]]): The list of blackout schedules + + Returns: + bool: True if posting is allowed, False otherwise. + """ + now = datetime.now(UTC) + if posting_restrictions == PostingRestriction.DISABLED: + return True + elif posting_restrictions == PostingRestriction.SCHEDULED: + return not any(schedule["start"] <= now <= schedule["end"] for schedule in blackout_schedules) + else: + return False