Store Discussions ID Map on CourseDiscussionSettings model when course publish signal is fired.

This commit is contained in:
Douglas Hall
2018-04-24 19:42:21 -04:00
parent 085817f487
commit 695b036282
11 changed files with 128 additions and 19 deletions

View File

@@ -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),
),
]

View File

@@ -8,6 +8,7 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_noop
from jsonfield.fields import JSONField
from opaque_keys.edx.django.models import CourseKeyField
from six import text_type
@@ -180,6 +181,11 @@ class CourseDiscussionSettings(models.Model):
db_index=True,
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)
_divided_discussions = models.TextField(db_column='divided_discussions', null=True, blank=True) # JSON list

View File

@@ -139,11 +139,18 @@ def set_course_discussion_settings(course_key, **kwargs):
divided_discussions (list): List of discussion ids.
division_scheme (str): `CourseDiscussionSettings.NONE`, `CourseDiscussionSettings.COHORT`,
or `CourseDiscussionSettings.ENROLLMENT_TRACK`
discussions_id_map (dict): Dict containing discussion IDs as keys and the associated discussion
XBlock usage keys as values.
Returns:
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)
for field, field_type in fields.items():
if field in kwargs:

View File

@@ -4,3 +4,7 @@
def plugin_settings(settings):
"""Settings for the discussions plugin. """
settings.FEATURES['ALLOW_HIDING_DISCUSSION_TAB'] = False
settings.DISCUSSION_SETTINGS = {
'MAX_COMMENT_DEPTH': 2,
'COURSE_PUBLISH_TASK_DELAY': 30,
}

View File

@@ -3,13 +3,15 @@ Signal handlers related to discussions.
"""
import logging
from django.conf import settings
from django.dispatch import receiver
from opaque_keys.edx.keys import CourseKey
from django_comment_common import signals
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.theming.helpers import get_current_site
from xmodule.modulestore.django import SignalHandler
log = logging.getLogger(__name__)
@@ -18,6 +20,25 @@ log = logging.getLogger(__name__)
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)
def send_discussion_email_notification(sender, user, post, **kwargs):
current_site = get_current_site()

View File

@@ -12,12 +12,13 @@ from django.contrib.auth.models import User
from django.contrib.sites.models import Site
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.utils import date
from edx_ace.message import MessageType
from edx_ace.recipient import Recipient
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
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)
@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):
pass

View File

@@ -1,10 +1,12 @@
from django.test import TestCase
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
import openedx.core.djangoapps.request_cache as request_cache
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):
@@ -56,3 +58,39 @@ class SendMessageHandlerTestCase(TestCase):
signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
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)

View File

@@ -5,7 +5,6 @@ from datetime import datetime, timedelta
import json
import math
from crum import CurrentRequestUserMiddleware
import ddt
from django.contrib.sites.models import Site
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.ace_common.template_context import get_base_template_context
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 student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase

View File

@@ -406,8 +406,8 @@ class ViewsQueryCountTestCase(
return inner
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 4, 35),
(ModuleStoreEnum.Type.split, 3, 13, 35),
(ModuleStoreEnum.Type.mongo, 3, 3, 30),
(ModuleStoreEnum.Type.split, 3, 11, 30),
)
@ddt.unpack
@count_queries
@@ -415,8 +415,8 @@ class ViewsQueryCountTestCase(
self.create_thread_helper(mock_request)
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 3, 31),
(ModuleStoreEnum.Type.split, 3, 10, 31),
(ModuleStoreEnum.Type.mongo, 3, 2, 26),
(ModuleStoreEnum.Type.split, 3, 8, 26),
)
@ddt.unpack
@count_queries

View File

@@ -125,10 +125,10 @@ def get_accessible_discussion_xblocks(course, user, include_all=False): # pylin
@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
are accessible to the given user.
Return a list of all valid discussion xblocks in this course.
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)

View File

@@ -59,10 +59,6 @@ PLATFORM_TWITTER_ACCOUNT = "@YourPlatformTwitterAccount"
ENABLE_JASMINE = False
DISCUSSION_SETTINGS = {
'MAX_COMMENT_DEPTH': 2,
}
LMS_ROOT_URL = "http://localhost:8000"
LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL
LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"