feat: adds ability to disable posting in discussions indefinitely (#32171)
* feat: adds ability to disable posting in discussions indefinitely * test: fixed ffailing test cases * test: added new model field in test cases * refactor: removes unnecessary migrations * refactor: removed previous migrations and adds new field in discussions api * refactor: added docstring and changed method name --------- Co-authored-by: ayesha waris <73840786+ayeshoali@users.noreply.github.com>
This commit is contained in:
@@ -40,7 +40,12 @@ from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
|
||||
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE, ENABLE_LEARNERS_TAB_IN_DISCUSSIONS_MFE
|
||||
from lms.djangoapps.discussion.toggles_utils import reported_content_email_notification_enabled
|
||||
from lms.djangoapps.discussion.views import is_privileged_user
|
||||
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, DiscussionTopicLink, Provider
|
||||
from openedx.core.djangoapps.discussions.models import (
|
||||
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
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client.comment import Comment
|
||||
@@ -319,14 +324,38 @@ def get_course(request, course_key):
|
||||
"""
|
||||
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)
|
||||
EDIT_REASON_CODES = getattr(settings, "DISCUSSION_MODERATION_EDIT_REASON_CODES", {})
|
||||
CLOSE_REASON_CODES = getattr(settings, "DISCUSSION_MODERATION_CLOSE_REASON_CODES", {})
|
||||
is_posting_enabled = is_posting_allowed(
|
||||
course_config.posting_restrictions,
|
||||
course.get_discussion_blackout_datetimes()
|
||||
)
|
||||
|
||||
return {
|
||||
"id": str(course_key),
|
||||
"is_posting_enabled": is_posting_enabled,
|
||||
"blackouts": [
|
||||
{
|
||||
"start": _format_datetime(blackout["start"]),
|
||||
|
||||
@@ -190,6 +190,7 @@ class GetCourseTest(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase)
|
||||
def test_basic(self):
|
||||
assert get_course(self.request, self.course.id) == {
|
||||
'id': str(self.course.id),
|
||||
'is_posting_enabled': True,
|
||||
'blackouts': [],
|
||||
'thread_list_url': 'http://testserver/api/discussion/v1/threads/?course_id=course-v1%3Ax%2By%2Bz',
|
||||
'following_thread_list_url':
|
||||
|
||||
@@ -517,6 +517,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
200,
|
||||
{
|
||||
"id": str(self.course.id),
|
||||
"is_posting_enabled": True,
|
||||
"blackouts": [],
|
||||
"thread_list_url": "http://testserver/api/discussion/v1/threads/?course_id=course-v1%3Ax%2By%2Bz",
|
||||
"following_thread_list_url": (
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-18 09:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('discussions', '0015_discussiontopiclink_context'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='discussionsconfiguration',
|
||||
name='posting_restrictions',
|
||||
field=models.CharField(choices=[('enabled', 'Enabled'), ('disabled', 'Disabled'), ('scheduled', 'Scheduled')], default='scheduled', help_text='The Posting availabilty in discussions whether it will be enabled, scheduled or indefinitely disabled.', max_length=15),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicaldiscussionsconfiguration',
|
||||
name='posting_restrictions',
|
||||
field=models.CharField(choices=[('enabled', 'Enabled'), ('disabled', 'Disabled'), ('scheduled', 'Scheduled')], default='scheduled', help_text='The Posting availabilty in discussions whether it will be enabled, scheduled or indefinitely disabled.', max_length=15),
|
||||
),
|
||||
]
|
||||
@@ -46,6 +46,15 @@ class Provider:
|
||||
OPEN_EDX = 'openedx'
|
||||
|
||||
|
||||
class PostingRestriction(models.TextChoices):
|
||||
"""
|
||||
Discussions Restrictions choices
|
||||
"""
|
||||
ENABLED = 'enabled'
|
||||
DISABLED = 'disabled'
|
||||
SCHEDULED = 'scheduled'
|
||||
|
||||
|
||||
DEFAULT_CONFIG_ENABLED = True
|
||||
|
||||
|
||||
@@ -413,6 +422,14 @@ class DiscussionsConfiguration(TimeStampedModel):
|
||||
default=True,
|
||||
help_text=_("If disabled, the discussions in the associated learning context/course will be disabled.")
|
||||
)
|
||||
posting_restrictions = models.CharField(
|
||||
max_length=15,
|
||||
default=PostingRestriction.SCHEDULED,
|
||||
choices=PostingRestriction.choices,
|
||||
help_text=_(
|
||||
"The Posting availabilty in discussions whether it will be enabled, scheduled or indefinitely disabled."
|
||||
)
|
||||
)
|
||||
lti_configuration = models.ForeignKey(
|
||||
LtiConfiguration,
|
||||
on_delete=models.SET_NULL,
|
||||
|
||||
@@ -176,6 +176,7 @@ class DiscussionsConfigurationSerializer(serializers.ModelSerializer):
|
||||
'enable_in_context',
|
||||
'enable_graded_units',
|
||||
'unit_level_visibility',
|
||||
'posting_restrictions',
|
||||
]
|
||||
fields = [
|
||||
'enabled',
|
||||
|
||||
@@ -16,6 +16,8 @@ from ..config.waffle import ENABLE_NEW_STRUCTURE_DISCUSSIONS
|
||||
from ..models import DEFAULT_CONFIG_ENABLED, Provider, get_default_provider_type
|
||||
from ..models import DiscussionsConfiguration
|
||||
from ..models import ProviderFilter
|
||||
from ..models import PostingRestriction
|
||||
|
||||
|
||||
SUPPORTED_PROVIDERS = [
|
||||
'legacy',
|
||||
@@ -141,6 +143,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
self.configuration_with_defaults.save()
|
||||
self.configuration_with_values = DiscussionsConfiguration(
|
||||
context_key=self.course_key_with_values,
|
||||
posting_restrictions=PostingRestriction.ENABLED,
|
||||
enabled=False,
|
||||
provider_type=Provider.LEGACY,
|
||||
plugin_configuration={
|
||||
@@ -164,6 +167,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.objects.get(context_key=self.course_key_with_defaults)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.SCHEDULED
|
||||
assert configuration.enabled # by default
|
||||
assert configuration.lti_configuration is None
|
||||
assert len(configuration.plugin_configuration.keys()) == 0
|
||||
@@ -175,6 +179,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.objects.get(context_key=self.course_key_with_values)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.ENABLED
|
||||
assert not configuration.enabled
|
||||
assert configuration.lti_configuration is None
|
||||
actual_url = configuration.plugin_configuration.get('url')
|
||||
@@ -187,6 +192,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
Assert we can update an existing record
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.objects.get(context_key=self.course_key_with_defaults)
|
||||
configuration.posting_restrictions = PostingRestriction.SCHEDULED
|
||||
configuration.enabled = False
|
||||
configuration.plugin_configuration = {
|
||||
'url': 'http://localhost',
|
||||
@@ -195,6 +201,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
configuration.save()
|
||||
configuration = DiscussionsConfiguration.objects.get(context_key=self.course_key_with_defaults)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.SCHEDULED
|
||||
assert not configuration.enabled
|
||||
assert configuration.lti_configuration is None
|
||||
assert configuration.plugin_configuration['url'] == 'http://localhost'
|
||||
@@ -233,6 +240,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
with override_waffle_flag(ENABLE_NEW_STRUCTURE_DISCUSSIONS, active=new_structure_enabled):
|
||||
configuration = DiscussionsConfiguration.get(self.course_key_without_config)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.SCHEDULED
|
||||
assert configuration.enabled == DEFAULT_CONFIG_ENABLED
|
||||
assert configuration.provider_type == default_provider_type
|
||||
assert not configuration.lti_configuration
|
||||
@@ -244,6 +252,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.get(self.course_key_with_defaults)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.SCHEDULED
|
||||
assert configuration.enabled
|
||||
assert not configuration.lti_configuration
|
||||
assert not configuration.plugin_configuration
|
||||
@@ -255,6 +264,7 @@ class DiscussionsConfigurationModelTest(TestCase):
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.get(self.course_key_with_values)
|
||||
assert configuration is not None
|
||||
assert configuration.posting_restrictions == PostingRestriction.ENABLED
|
||||
assert not configuration.enabled
|
||||
assert not configuration.lti_configuration
|
||||
assert configuration.plugin_configuration
|
||||
|
||||
Reference in New Issue
Block a user