Store Discussions ID Map on CourseDiscussionSettings model when course publish signal is fired.
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-23 21:09
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import jsonfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_comment_common', '0005_coursediscussionsettings'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='coursediscussionsettings',
|
||||||
|
name='discussions_id_map',
|
||||||
|
field=jsonfield.fields.JSONField(blank=True, help_text=b'Key/value store mapping discussion IDs to discussion XBlock usage keys.', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,6 +8,7 @@ from django.db import models
|
|||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_noop
|
from django.utils.translation import ugettext_noop
|
||||||
|
from jsonfield.fields import JSONField
|
||||||
from opaque_keys.edx.django.models import CourseKeyField
|
from opaque_keys.edx.django.models import CourseKeyField
|
||||||
from six import text_type
|
from six import text_type
|
||||||
|
|
||||||
@@ -180,6 +181,11 @@ class CourseDiscussionSettings(models.Model):
|
|||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="Which course are these settings associated with?",
|
help_text="Which course are these settings associated with?",
|
||||||
)
|
)
|
||||||
|
discussions_id_map = JSONField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Key/value store mapping discussion IDs to discussion XBlock usage keys.",
|
||||||
|
)
|
||||||
always_divide_inline_discussions = models.BooleanField(default=False)
|
always_divide_inline_discussions = models.BooleanField(default=False)
|
||||||
_divided_discussions = models.TextField(db_column='divided_discussions', null=True, blank=True) # JSON list
|
_divided_discussions = models.TextField(db_column='divided_discussions', null=True, blank=True) # JSON list
|
||||||
|
|
||||||
|
|||||||
@@ -139,11 +139,18 @@ def set_course_discussion_settings(course_key, **kwargs):
|
|||||||
divided_discussions (list): List of discussion ids.
|
divided_discussions (list): List of discussion ids.
|
||||||
division_scheme (str): `CourseDiscussionSettings.NONE`, `CourseDiscussionSettings.COHORT`,
|
division_scheme (str): `CourseDiscussionSettings.NONE`, `CourseDiscussionSettings.COHORT`,
|
||||||
or `CourseDiscussionSettings.ENROLLMENT_TRACK`
|
or `CourseDiscussionSettings.ENROLLMENT_TRACK`
|
||||||
|
discussions_id_map (dict): Dict containing discussion IDs as keys and the associated discussion
|
||||||
|
XBlock usage keys as values.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A CourseDiscussionSettings object.
|
A CourseDiscussionSettings object.
|
||||||
"""
|
"""
|
||||||
fields = {'division_scheme': basestring, 'always_divide_inline_discussions': bool, 'divided_discussions': list}
|
fields = {
|
||||||
|
'division_scheme': basestring,
|
||||||
|
'always_divide_inline_discussions': bool,
|
||||||
|
'divided_discussions': list,
|
||||||
|
'discussions_id_map': dict,
|
||||||
|
}
|
||||||
course_discussion_settings = get_course_discussion_settings(course_key)
|
course_discussion_settings = get_course_discussion_settings(course_key)
|
||||||
for field, field_type in fields.items():
|
for field, field_type in fields.items():
|
||||||
if field in kwargs:
|
if field in kwargs:
|
||||||
|
|||||||
@@ -4,3 +4,7 @@
|
|||||||
def plugin_settings(settings):
|
def plugin_settings(settings):
|
||||||
"""Settings for the discussions plugin. """
|
"""Settings for the discussions plugin. """
|
||||||
settings.FEATURES['ALLOW_HIDING_DISCUSSION_TAB'] = False
|
settings.FEATURES['ALLOW_HIDING_DISCUSSION_TAB'] = False
|
||||||
|
settings.DISCUSSION_SETTINGS = {
|
||||||
|
'MAX_COMMENT_DEPTH': 2,
|
||||||
|
'COURSE_PUBLISH_TASK_DELAY': 30,
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ Signal handlers related to discussions.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from opaque_keys.edx.keys import CourseKey
|
|
||||||
|
|
||||||
from django_comment_common import signals
|
from django_comment_common import signals
|
||||||
from lms.djangoapps.discussion import tasks
|
from lms.djangoapps.discussion import tasks
|
||||||
|
from opaque_keys.edx.locator import LibraryLocator
|
||||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||||
from openedx.core.djangoapps.theming.helpers import get_current_site
|
from openedx.core.djangoapps.theming.helpers import get_current_site
|
||||||
|
from xmodule.modulestore.django import SignalHandler
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -18,6 +20,25 @@ log = logging.getLogger(__name__)
|
|||||||
ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY = 'enable_forum_notifications'
|
ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY = 'enable_forum_notifications'
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(SignalHandler.course_published)
|
||||||
|
def update_discussions_on_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
|
||||||
|
"""
|
||||||
|
Catches the signal that a course has been published in the module
|
||||||
|
store and creates/updates the corresponding cache entry.
|
||||||
|
Ignores publish signals from content libraries.
|
||||||
|
"""
|
||||||
|
if isinstance(course_key, LibraryLocator):
|
||||||
|
return
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'course_id': unicode(course_key),
|
||||||
|
}
|
||||||
|
tasks.update_discussions_map.apply_async(
|
||||||
|
args=[context],
|
||||||
|
countdown=settings.DISCUSSION_SETTINGS['COURSE_PUBLISH_TASK_DELAY'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.comment_created)
|
@receiver(signals.comment_created)
|
||||||
def send_discussion_email_notification(sender, user, post, **kwargs):
|
def send_discussion_email_notification(sender, user, post, **kwargs):
|
||||||
current_site = get_current_site()
|
current_site = get_current_site()
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ from django.contrib.auth.models import User
|
|||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
from celery_utils.logged_task import LoggedTask
|
from celery_utils.logged_task import LoggedTask
|
||||||
|
from django_comment_common.utils import set_course_discussion_settings
|
||||||
from edx_ace import ace
|
from edx_ace import ace
|
||||||
from edx_ace.utils import date
|
from edx_ace.utils import date
|
||||||
from edx_ace.message import MessageType
|
from edx_ace.message import MessageType
|
||||||
from edx_ace.recipient import Recipient
|
from edx_ace.recipient import Recipient
|
||||||
from opaque_keys.edx.keys import CourseKey
|
from opaque_keys.edx.keys import CourseKey
|
||||||
from lms.djangoapps.django_comment_client.utils import permalink
|
from lms.djangoapps.django_comment_client.utils import permalink, get_accessible_discussion_xblocks_by_course_id
|
||||||
import lms.lib.comment_client as cc
|
import lms.lib.comment_client as cc
|
||||||
|
|
||||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||||
@@ -32,6 +33,24 @@ DEFAULT_LANGUAGE = 'en'
|
|||||||
ROUTING_KEY = getattr(settings, 'ACE_ROUTING_KEY', None)
|
ROUTING_KEY = getattr(settings, 'ACE_ROUTING_KEY', None)
|
||||||
|
|
||||||
|
|
||||||
|
@task(base=LoggedTask)
|
||||||
|
def update_discussions_map(context):
|
||||||
|
"""
|
||||||
|
Updates the mapping between discussion_id to discussion block usage key
|
||||||
|
for all discussion blocks in the given course.
|
||||||
|
|
||||||
|
context is a dict that contains:
|
||||||
|
course_id (string): identifier of the course
|
||||||
|
"""
|
||||||
|
course_key = CourseKey.from_string(context['course_id'])
|
||||||
|
discussion_blocks = get_accessible_discussion_xblocks_by_course_id(course_key, include_all=True)
|
||||||
|
discussions_id_map = {
|
||||||
|
discussion_block.discussion_id: unicode(discussion_block.location)
|
||||||
|
for discussion_block in discussion_blocks
|
||||||
|
}
|
||||||
|
set_course_discussion_settings(course_key, discussions_id_map=discussions_id_map)
|
||||||
|
|
||||||
|
|
||||||
class ResponseNotification(MessageType):
|
class ResponseNotification(MessageType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from django_comment_common import signals
|
from django_comment_common import signals, models
|
||||||
from lms.djangoapps.discussion.signals.handlers import ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY
|
from lms.djangoapps.discussion.signals.handlers import ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY
|
||||||
|
import openedx.core.djangoapps.request_cache as request_cache
|
||||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory, SiteConfigurationFactory
|
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory, SiteConfigurationFactory
|
||||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||||
|
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
class SendMessageHandlerTestCase(TestCase):
|
class SendMessageHandlerTestCase(TestCase):
|
||||||
@@ -56,3 +58,39 @@ class SendMessageHandlerTestCase(TestCase):
|
|||||||
signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
|
signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
|
||||||
|
|
||||||
self.assertFalse(mock_send_message.called)
|
self.assertFalse(mock_send_message.called)
|
||||||
|
|
||||||
|
|
||||||
|
class CoursePublishHandlerTestCase(ModuleStoreTestCase):
|
||||||
|
"""
|
||||||
|
Tests for discussion updates on course publish.
|
||||||
|
"""
|
||||||
|
ENABLED_SIGNALS = ['course_published']
|
||||||
|
|
||||||
|
def test_discussion_id_map_updates_on_publish(self):
|
||||||
|
course_key_args = dict(org='org', course='number', run='run')
|
||||||
|
course_key = self.store.make_course_key(**course_key_args)
|
||||||
|
|
||||||
|
with self.assertRaises(models.CourseDiscussionSettings.DoesNotExist):
|
||||||
|
models.CourseDiscussionSettings.objects.get(course_id=course_key)
|
||||||
|
|
||||||
|
# create course
|
||||||
|
course = CourseFactory(emit_signals=True, **course_key_args)
|
||||||
|
self.assertEqual(course.id, course_key)
|
||||||
|
self._assert_discussion_id_map(course_key, {})
|
||||||
|
|
||||||
|
# create discussion block
|
||||||
|
request_cache.clear_cache(name=None)
|
||||||
|
discussion_id = 'discussion1'
|
||||||
|
discussion_block = ItemFactory.create(
|
||||||
|
parent_location=course.location,
|
||||||
|
category="discussion",
|
||||||
|
discussion_id=discussion_id,
|
||||||
|
)
|
||||||
|
self._assert_discussion_id_map(course_key, {discussion_id: str(discussion_block.location)})
|
||||||
|
|
||||||
|
def _assert_discussion_id_map(self, course_key, expected_map):
|
||||||
|
"""
|
||||||
|
Verifies the discussion ID map for the given course matches the expected value.
|
||||||
|
"""
|
||||||
|
discussion_settings = models.CourseDiscussionSettings.objects.get(course_id=course_key)
|
||||||
|
self.assertDictEqual(discussion_settings.discussions_id_map, expected_map)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from datetime import datetime, timedelta
|
|||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from crum import CurrentRequestUserMiddleware
|
|
||||||
import ddt
|
import ddt
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
import mock
|
import mock
|
||||||
@@ -22,8 +21,6 @@ from lms.djangoapps.discussion.tasks import _should_send_message, _track_notific
|
|||||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||||
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
||||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
|
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
|
||||||
from openedx.core.djangoapps.theming.middleware import CurrentSiteThemeMiddleware
|
|
||||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
|
||||||
from openedx.core.lib.celery.task_utils import emulate_http_request
|
from openedx.core.lib.celery.task_utils import emulate_http_request
|
||||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||||
|
|||||||
@@ -406,8 +406,8 @@ class ViewsQueryCountTestCase(
|
|||||||
return inner
|
return inner
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
(ModuleStoreEnum.Type.mongo, 3, 4, 35),
|
(ModuleStoreEnum.Type.mongo, 3, 3, 30),
|
||||||
(ModuleStoreEnum.Type.split, 3, 13, 35),
|
(ModuleStoreEnum.Type.split, 3, 11, 30),
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
@count_queries
|
@count_queries
|
||||||
@@ -415,8 +415,8 @@ class ViewsQueryCountTestCase(
|
|||||||
self.create_thread_helper(mock_request)
|
self.create_thread_helper(mock_request)
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
(ModuleStoreEnum.Type.mongo, 3, 3, 31),
|
(ModuleStoreEnum.Type.mongo, 3, 2, 26),
|
||||||
(ModuleStoreEnum.Type.split, 3, 10, 31),
|
(ModuleStoreEnum.Type.split, 3, 8, 26),
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
@count_queries
|
@count_queries
|
||||||
|
|||||||
@@ -125,10 +125,10 @@ def get_accessible_discussion_xblocks(course, user, include_all=False): # pylin
|
|||||||
|
|
||||||
|
|
||||||
@request_cached
|
@request_cached
|
||||||
def get_accessible_discussion_xblocks_by_course_id(course_id, user, include_all=False): # pylint: disable=invalid-name
|
def get_accessible_discussion_xblocks_by_course_id(course_id, user=None, include_all=False): # pylint: disable=invalid-name
|
||||||
"""
|
"""
|
||||||
Return a list of all valid discussion xblocks in this course that
|
Return a list of all valid discussion xblocks in this course.
|
||||||
are accessible to the given user.
|
Checks for the given user's access if include_all is False.
|
||||||
"""
|
"""
|
||||||
all_xblocks = modulestore().get_items(course_id, qualifiers={'category': 'discussion'}, include_orphans=False)
|
all_xblocks = modulestore().get_items(course_id, qualifiers={'category': 'discussion'}, include_orphans=False)
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,6 @@ PLATFORM_TWITTER_ACCOUNT = "@YourPlatformTwitterAccount"
|
|||||||
|
|
||||||
ENABLE_JASMINE = False
|
ENABLE_JASMINE = False
|
||||||
|
|
||||||
DISCUSSION_SETTINGS = {
|
|
||||||
'MAX_COMMENT_DEPTH': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
LMS_ROOT_URL = "http://localhost:8000"
|
LMS_ROOT_URL = "http://localhost:8000"
|
||||||
LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL
|
LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL
|
||||||
LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"
|
LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"
|
||||||
|
|||||||
Reference in New Issue
Block a user