Commit 90fe71db by wajeeha-khalid

MA-1248 - CourseEnrollmentAPI: added discussion URL

parent 06129ada
""" """
Useful django models for implementing XBlock infrastructure in django. Useful django models for implementing XBlock infrastructure in django.
""" """
import warnings import warnings
from django.db import models from django.db import models
......
...@@ -13,8 +13,8 @@ from rest_framework.exceptions import PermissionDenied ...@@ -13,8 +13,8 @@ from rest_framework.exceptions import PermissionDenied
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.locator import CourseKey from opaque_keys.edx.locator import CourseKey
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from discussion_api.forms import CommentActionsForm, ThreadActionsForm from discussion_api.forms import CommentActionsForm, ThreadActionsForm
from discussion_api.pagination import get_paginated_data from discussion_api.pagination import get_paginated_data
from discussion_api.permissions import ( from discussion_api.permissions import (
...@@ -55,7 +55,7 @@ def _get_course_or_404(course_key, user): ...@@ -55,7 +55,7 @@ def _get_course_or_404(course_key, user):
disabled for the course. disabled for the course.
""" """
course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True) course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True)
if not any([tab.type == 'discussion' for tab in course.tabs]): if not any([tab.type == 'discussion' and tab.is_enabled(course, user) for tab in course.tabs]):
raise Http404 raise Http404
return course return course
......
...@@ -81,11 +81,11 @@ def _discussion_disabled_course_for(user): ...@@ -81,11 +81,11 @@ def _discussion_disabled_course_for(user):
@ddt.ddt @ddt.ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase): class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase):
"""Test for get_course""" """Test for get_course"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(GetCourseTest, cls).setUpClass() super(GetCourseTest, cls).setUpClass()
cls.course = CourseFactory.create(org="x", course="y", run="z") cls.course = CourseFactory.create(org="x", course="y", run="z")
...@@ -154,9 +154,9 @@ class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase): ...@@ -154,9 +154,9 @@ class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase):
@mock.patch.dict("django.conf.settings.FEATURES", {"DISABLE_START_DATES": False}) @mock.patch.dict("django.conf.settings.FEATURES", {"DISABLE_START_DATES": False})
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
"""Test for get_course_topics""" """Test for get_course_topics"""
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self): def setUp(self):
super(GetCourseTopicsTest, self).setUp() super(GetCourseTopicsTest, self).setUp()
...@@ -480,11 +480,11 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -480,11 +480,11 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
@ddt.ddt @ddt.ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase):
"""Test for get_thread_list""" """Test for get_thread_list"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(GetThreadListTest, cls).setUpClass() super(GetThreadListTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -909,15 +909,16 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleSto ...@@ -909,15 +909,16 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleSto
@ddt.ddt @ddt.ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
"""Test for get_comment_list""" """Test for get_comment_list"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(GetCommentListTest, cls).setUpClass() super(GetCommentListTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self): def setUp(self):
super(GetCommentListTest, self).setUp() super(GetCommentListTest, self).setUp()
httpretty.reset() httpretty.reset()
...@@ -1333,6 +1334,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): ...@@ -1333,6 +1334,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
@ddt.ddt @ddt.ddt
@disable_signal(api, 'thread_created') @disable_signal(api, 'thread_created')
@disable_signal(api, 'thread_voted') @disable_signal(api, 'thread_voted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CreateThreadTest( class CreateThreadTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -1341,7 +1343,6 @@ class CreateThreadTest( ...@@ -1341,7 +1343,6 @@ class CreateThreadTest(
): ):
"""Tests for create_thread""" """Tests for create_thread"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(CreateThreadTest, cls).setUpClass() super(CreateThreadTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -1585,6 +1586,7 @@ class CreateThreadTest( ...@@ -1585,6 +1586,7 @@ class CreateThreadTest(
@ddt.ddt @ddt.ddt
@disable_signal(api, 'comment_created') @disable_signal(api, 'comment_created')
@disable_signal(api, 'comment_voted') @disable_signal(api, 'comment_voted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CreateCommentTest( class CreateCommentTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -1593,7 +1595,6 @@ class CreateCommentTest( ...@@ -1593,7 +1595,6 @@ class CreateCommentTest(
): ):
"""Tests for create_comment""" """Tests for create_comment"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(CreateCommentTest, cls).setUpClass() super(CreateCommentTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -1859,6 +1860,7 @@ class CreateCommentTest( ...@@ -1859,6 +1860,7 @@ class CreateCommentTest(
@ddt.ddt @ddt.ddt
@disable_signal(api, 'thread_edited') @disable_signal(api, 'thread_edited')
@disable_signal(api, 'thread_voted') @disable_signal(api, 'thread_voted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class UpdateThreadTest( class UpdateThreadTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -1867,7 +1869,6 @@ class UpdateThreadTest( ...@@ -1867,7 +1869,6 @@ class UpdateThreadTest(
): ):
"""Tests for update_thread""" """Tests for update_thread"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(UpdateThreadTest, cls).setUpClass() super(UpdateThreadTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -2247,6 +2248,7 @@ class UpdateThreadTest( ...@@ -2247,6 +2248,7 @@ class UpdateThreadTest(
@ddt.ddt @ddt.ddt
@disable_signal(api, 'comment_edited') @disable_signal(api, 'comment_edited')
@disable_signal(api, 'comment_voted') @disable_signal(api, 'comment_voted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class UpdateCommentTest( class UpdateCommentTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -2256,7 +2258,6 @@ class UpdateCommentTest( ...@@ -2256,7 +2258,6 @@ class UpdateCommentTest(
"""Tests for update_comment""" """Tests for update_comment"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(UpdateCommentTest, cls).setUpClass() super(UpdateCommentTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -2304,7 +2305,6 @@ class UpdateCommentTest( ...@@ -2304,7 +2305,6 @@ class UpdateCommentTest(
self.register_get_comment_response(cs_comment_data) self.register_get_comment_response(cs_comment_data)
self.register_put_comment_response(cs_comment_data) self.register_put_comment_response(cs_comment_data)
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_empty(self): def test_empty(self):
"""Check that an empty update does not make any modifying requests.""" """Check that an empty update does not make any modifying requests."""
self.register_comment() self.register_comment()
...@@ -2632,6 +2632,7 @@ class UpdateCommentTest( ...@@ -2632,6 +2632,7 @@ class UpdateCommentTest(
@ddt.ddt @ddt.ddt
@disable_signal(api, 'thread_deleted') @disable_signal(api, 'thread_deleted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class DeleteThreadTest( class DeleteThreadTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -2640,7 +2641,6 @@ class DeleteThreadTest( ...@@ -2640,7 +2641,6 @@ class DeleteThreadTest(
): ):
"""Tests for delete_thread""" """Tests for delete_thread"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(DeleteThreadTest, cls).setUpClass() super(DeleteThreadTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -2771,6 +2771,7 @@ class DeleteThreadTest( ...@@ -2771,6 +2771,7 @@ class DeleteThreadTest(
@ddt.ddt @ddt.ddt
@disable_signal(api, 'comment_deleted') @disable_signal(api, 'comment_deleted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class DeleteCommentTest( class DeleteCommentTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -2779,7 +2780,6 @@ class DeleteCommentTest( ...@@ -2779,7 +2780,6 @@ class DeleteCommentTest(
): ):
"""Tests for delete_comment""" """Tests for delete_comment"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(DeleteCommentTest, cls).setUpClass() super(DeleteCommentTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
...@@ -2928,6 +2928,7 @@ class DeleteCommentTest( ...@@ -2928,6 +2928,7 @@ class DeleteCommentTest(
@ddt.ddt @ddt.ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class RetrieveThreadTest( class RetrieveThreadTest(
CommentsServiceMockMixin, CommentsServiceMockMixin,
UrlResetMixin, UrlResetMixin,
...@@ -2935,7 +2936,6 @@ class RetrieveThreadTest( ...@@ -2935,7 +2936,6 @@ class RetrieveThreadTest(
): ):
"""Tests for get_thread""" """Tests for get_thread"""
@classmethod @classmethod
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUpClass(cls): def setUpClass(cls):
super(RetrieveThreadTest, cls).setUpClass() super(RetrieveThreadTest, cls).setUpClass()
cls.course = CourseFactory.create() cls.course = CourseFactory.create()
......
...@@ -70,6 +70,7 @@ class DiscussionAPIViewTestMixin(CommentsServiceMockMixin, UrlResetMixin): ...@@ -70,6 +70,7 @@ class DiscussionAPIViewTestMixin(CommentsServiceMockMixin, UrlResetMixin):
) )
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CourseView""" """Tests for CourseView"""
def setUp(self): def setUp(self):
...@@ -103,6 +104,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -103,6 +104,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
) )
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CourseTopicsView""" """Tests for CourseTopicsView"""
def setUp(self): def setUp(self):
...@@ -139,6 +141,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -139,6 +141,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@ddt.ddt @ddt.ddt
@httpretty.activate @httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet list""" """Tests for ThreadViewSet list"""
def setUp(self): def setUp(self):
...@@ -388,6 +391,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -388,6 +391,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@disable_signal(api, 'thread_created') @disable_signal(api, 'thread_created')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet create""" """Tests for ThreadViewSet create"""
def setUp(self): def setUp(self):
...@@ -480,6 +484,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -480,6 +484,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@disable_signal(api, 'thread_edited') @disable_signal(api, 'thread_edited')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet partial_update""" """Tests for ThreadViewSet partial_update"""
def setUp(self): def setUp(self):
...@@ -580,6 +585,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest ...@@ -580,6 +585,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
@httpretty.activate @httpretty.activate
@disable_signal(api, 'thread_deleted') @disable_signal(api, 'thread_deleted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet delete""" """Tests for ThreadViewSet delete"""
def setUp(self): def setUp(self):
...@@ -613,6 +619,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -613,6 +619,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CommentViewSet list""" """Tests for CommentViewSet list"""
def setUp(self): def setUp(self):
...@@ -744,6 +751,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -744,6 +751,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@disable_signal(api, 'comment_deleted') @disable_signal(api, 'comment_deleted')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet delete""" """Tests for ThreadViewSet delete"""
...@@ -785,6 +793,7 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -785,6 +793,7 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@disable_signal(api, 'comment_created') @disable_signal(api, 'comment_created')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CommentViewSet create""" """Tests for CommentViewSet create"""
def setUp(self): def setUp(self):
...@@ -869,6 +878,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -869,6 +878,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@disable_signal(api, 'comment_edited') @disable_signal(api, 'comment_edited')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CommentViewSet partial_update""" """Tests for CommentViewSet partial_update"""
def setUp(self): def setUp(self):
...@@ -954,6 +964,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes ...@@ -954,6 +964,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
@httpretty.activate @httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet Retrieve""" """Tests for ThreadViewSet Retrieve"""
def setUp(self): def setUp(self):
......
...@@ -27,7 +27,6 @@ from openedx.core.djangoapps.course_groups.cohorts import ( ...@@ -27,7 +27,6 @@ from openedx.core.djangoapps.course_groups.cohorts import (
from courseware.tabs import EnrolledTab from courseware.tabs import EnrolledTab
from courseware.access import has_access from courseware.access import has_access
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx
from django_comment_common.utils import ThreadContext from django_comment_common.utils import ThreadContext
from django_comment_client.permissions import has_permission, get_team from django_comment_client.permissions import has_permission, get_team
...@@ -66,11 +65,7 @@ class DiscussionTab(EnrolledTab): ...@@ -66,11 +65,7 @@ class DiscussionTab(EnrolledTab):
def is_enabled(cls, course, user=None): def is_enabled(cls, course, user=None):
if not super(DiscussionTab, cls).is_enabled(course, user): if not super(DiscussionTab, cls).is_enabled(course, user):
return False return False
return utils.is_discussion_enabled(course.id)
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
if get_current_ccx(course.id):
return False
return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')
def _attr_safe_json(obj): def _attr_safe_json(obj):
......
...@@ -2,6 +2,7 @@ from collections import defaultdict ...@@ -2,6 +2,7 @@ from collections import defaultdict
from datetime import datetime from datetime import datetime
import json import json
import logging import logging
from django.conf import settings
import pytz import pytz
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -13,6 +14,7 @@ import pystache_custom as pystache ...@@ -13,6 +14,7 @@ import pystache_custom as pystache
from opaque_keys.edx.locations import i4xEncoder from opaque_keys.edx.locations import i4xEncoder
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx
from django_comment_common.models import Role, FORUM_ROLE_STUDENT from django_comment_common.models import Role, FORUM_ROLE_STUDENT
from django_comment_client.permissions import check_permissions_by_view, has_permission, get_team from django_comment_client.permissions import check_permissions_by_view, has_permission, get_team
...@@ -716,3 +718,13 @@ def is_commentable_cohorted(course_key, commentable_id): ...@@ -716,3 +718,13 @@ def is_commentable_cohorted(course_key, commentable_id):
log.debug(u"is_commentable_cohorted(%s, %s) = {%s}", course_key, commentable_id, ans) log.debug(u"is_commentable_cohorted(%s, %s) = {%s}", course_key, commentable_id, ans)
return ans return ans
def is_discussion_enabled(course_id):
"""
Return True if Discussion is enabled for a course; else False
"""
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
if get_current_ccx(course_id):
return False
return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')
...@@ -34,10 +34,16 @@ class CourseOverviewField(serializers.RelatedField): ...@@ -34,10 +34,16 @@ class CourseOverviewField(serializers.RelatedField):
kwargs={'course_id': course_id}, kwargs={'course_id': course_id},
request=request request=request
) )
discussion_url = reverse(
'discussion_course',
kwargs={'course_id': course_id},
request=request
) if course_overview.is_discussion_tab_enabled() else None
else: else:
video_outline_url = None video_outline_url = None
course_updates_url = None course_updates_url = None
course_handouts_url = None course_handouts_url = None
discussion_url = None
if course_overview.advertised_start is not None: if course_overview.advertised_start is not None:
start_type = "string" start_type = "string"
...@@ -68,6 +74,7 @@ class CourseOverviewField(serializers.RelatedField): ...@@ -68,6 +74,7 @@ class CourseOverviewField(serializers.RelatedField):
"video_outline": video_outline_url, "video_outline": video_outline_url,
"course_updates": course_updates_url, "course_updates": course_updates_url,
"course_handouts": course_handouts_url, "course_handouts": course_handouts_url,
"discussion_url": discussion_url,
"subscription_id": course_overview.clean_id(padding_char='_'), "subscription_id": course_overview.clean_id(padding_char='_'),
"courseware_access": has_access(request.user, 'load_mobile', course_overview).to_json() if request else None "courseware_access": has_access(request.user, 'load_mobile', course_overview).to_json() if request else None
} }
......
...@@ -28,6 +28,7 @@ from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory ...@@ -28,6 +28,7 @@ from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from .. import errors from .. import errors
from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin
from .serializers import CourseEnrollmentSerializer from .serializers import CourseEnrollmentSerializer
from util.testing import UrlResetMixin
class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin): class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
...@@ -60,7 +61,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin): ...@@ -60,7 +61,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
@ddt.ddt @ddt.ddt
class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin): class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin):
""" """
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/ Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
""" """
...@@ -71,7 +72,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin): ...@@ -71,7 +72,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7) LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
ADVERTISED_START = "Spring 2016" ADVERTISED_START = "Spring 2016"
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self, *args, **kwargs):
super(TestUserEnrollmentApi, self).setUp()
def verify_success(self, response): def verify_success(self, response):
"""
Verifies user course enrollment response for success
"""
super(TestUserEnrollmentApi, self).verify_success(response) super(TestUserEnrollmentApi, self).verify_success(response)
courses = response.data courses = response.data
self.assertEqual(len(courses), 1) self.assertEqual(len(courses), 1)
...@@ -205,6 +213,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin): ...@@ -205,6 +213,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
course_data = response.data[0]['course'] course_data = response.data[0]['course']
self.assertEquals(course_data['social_urls']['facebook'], self.course.facebook_url) self.assertEquals(course_data['social_urls']['facebook'], self.course.facebook_url)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_discussion_url(self):
self.login_and_enroll()
response = self.api_response()
response_discussion_url = response.data[0]['course']['discussion_url'] # pylint: disable=E1101
self.assertIn('/api/discussion/v1/courses/{}'.format(self.course.id), response_discussion_url)
class CourseStatusAPITestCase(MobileAPITestCase): class CourseStatusAPITestCase(MobileAPITestCase):
""" """
......
...@@ -239,6 +239,8 @@ class UserCourseEnrollmentsList(generics.ListAPIView): ...@@ -239,6 +239,8 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
the course. the course.
* video_outline: The URI to get the list of all videos that the user * video_outline: The URI to get the list of all videos that the user
can access in the course. can access in the course.
* discussion_url: The URI to access data for course discussions if
it is enabled, otherwise null.
* created: The date the course was created. * created: The date the course was created.
* is_active: Whether the course is currently active. Possible values * is_active: Whether the course is currently active. Possible values
......
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CourseOverviewTab'
db.create_table('course_overviews_courseoverviewtab', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('tab_id', self.gf('django.db.models.fields.CharField')(max_length=50)),
('course_overview', self.gf('django.db.models.fields.related.ForeignKey')(related_name='tabs', to=orm['course_overviews.CourseOverview'])),
))
db.send_create_signal('course_overviews', ['CourseOverviewTab'])
def backwards(self, orm):
# Deleting model 'CourseOverviewTab'
db.delete_table('course_overviews_courseoverviewtab')
models = {
'course_overviews.courseoverview': {
'Meta': {'object_name': 'CourseOverview'},
'_location': ('xmodule_django.models.UsageKeyField', [], {'max_length': '255'}),
'_pre_requisite_courses_json': ('django.db.models.fields.TextField', [], {}),
'advertised_start': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'cert_html_view_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cert_name_long': ('django.db.models.fields.TextField', [], {}),
'cert_name_short': ('django.db.models.fields.TextField', [], {}),
'certificates_display_behavior': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'certificates_show_before_end': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'course_image_url': ('django.db.models.fields.TextField', [], {}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'days_early_for_beta': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
'display_name': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'display_number_with_default': ('django.db.models.fields.TextField', [], {}),
'display_org_with_default': ('django.db.models.fields.TextField', [], {}),
'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'end_of_course_survey_url': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'enrollment_domain': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'enrollment_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'enrollment_start': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'facebook_url': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'has_any_active_web_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'primary_key': 'True', 'db_index': 'True'}),
'invitation_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'lowest_passing_grade': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '5', 'decimal_places': '2'}),
'max_student_enrollments_allowed': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'mobile_available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'social_sharing_url': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'start': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'version': ('django.db.models.fields.IntegerField', [], {}),
'visible_to_staff_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'course_overviews.courseoverviewtab': {
'Meta': {'object_name': 'CourseOverviewTab'},
'course_overview': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tabs'", 'to': "orm['course_overviews.CourseOverview']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'tab_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}
}
complete_apps = ['course_overviews']
\ No newline at end of file
""" """
Declaration of CourseOverview model Declaration of CourseOverview model
""" """
import json import json
from django.db import models
from django.db.models.fields import BooleanField, DateTimeField, DecimalField, TextField, FloatField, IntegerField from django.db.models.fields import BooleanField, DateTimeField, DecimalField, TextField, FloatField, IntegerField
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.utils.translation import ugettext from django.utils.translation import ugettext
from lms.djangoapps import django_comment_client
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from util.date_utils import strftime_localized from util.date_utils import strftime_localized
from xmodule import course_metadata_utils from xmodule import course_metadata_utils
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
...@@ -30,7 +32,7 @@ class CourseOverview(TimeStampedModel): ...@@ -30,7 +32,7 @@ class CourseOverview(TimeStampedModel):
""" """
# IMPORTANT: Bump this whenever you modify this model and/or add a migration. # IMPORTANT: Bump this whenever you modify this model and/or add a migration.
VERSION = 1 VERSION = 2
# Cache entry versioning. # Cache entry versioning.
version = IntegerField() version = IntegerField()
...@@ -176,6 +178,10 @@ class CourseOverview(TimeStampedModel): ...@@ -176,6 +178,10 @@ class CourseOverview(TimeStampedModel):
course_overview = cls._create_from_course(course) course_overview = cls._create_from_course(course)
try: try:
course_overview.save() course_overview.save()
CourseOverviewTab.objects.bulk_create([
CourseOverviewTab(tab_id=tab.tab_id, course_overview=course_overview)
for tab in course.tabs
])
except IntegrityError: except IntegrityError:
# There is a rare race condition that will occur if # There is a rare race condition that will occur if
# CourseOverview.get_from_id is called while a # CourseOverview.get_from_id is called while a
...@@ -358,3 +364,22 @@ class CourseOverview(TimeStampedModel): ...@@ -358,3 +364,22 @@ class CourseOverview(TimeStampedModel):
CourseKey.from_string(course_overview['id']) CourseKey.from_string(course_overview['id'])
for course_overview in CourseOverview.objects.values('id') for course_overview in CourseOverview.objects.values('id')
] ]
def is_discussion_tab_enabled(self):
"""
Returns True if course has discussion tab and is enabled
"""
tabs = self.tabs.all() # pylint: disable=E1101
# creates circular import; hence explicitly referenced is_discussion_enabled
for tab in tabs:
if tab.tab_id == "discussion" and django_comment_client.utils.is_discussion_enabled(self.id):
return True
return False
class CourseOverviewTab(models.Model):
"""
Model for storing and caching tabs information of a course.
"""
tab_id = models.CharField(max_length=50)
course_overview = models.ForeignKey(CourseOverview, db_index=True, related_name="tabs")
...@@ -34,6 +34,8 @@ class CourseOverviewTestCase(ModuleStoreTestCase): ...@@ -34,6 +34,8 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
NEXT_WEEK = TODAY + datetime.timedelta(days=7) NEXT_WEEK = TODAY + datetime.timedelta(days=7)
NEXT_MONTH = TODAY + datetime.timedelta(days=30) NEXT_MONTH = TODAY + datetime.timedelta(days=30)
COURSE_OVERVIEW_TABS = {'courseware', 'info', 'textbooks', 'discussion', 'wiki', 'progress'}
def check_course_overview_against_course(self, course): def check_course_overview_against_course(self, course):
""" """
Compares a CourseOverview object against its corresponding Compares a CourseOverview object against its corresponding
...@@ -164,6 +166,12 @@ class CourseOverviewTestCase(ModuleStoreTestCase): ...@@ -164,6 +166,12 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
self.assertEqual(course_value, cache_miss_value) self.assertEqual(course_value, cache_miss_value)
self.assertEqual(cache_miss_value, cache_hit_value) self.assertEqual(cache_miss_value, cache_hit_value)
# test tabs for both cached miss and cached hit courses
for course_overview in [course_overview_cache_miss, course_overview_cache_hit]:
course_overview_tabs = course_overview.tabs.all()
course_resp_tabs = {tab.tab_id for tab in course_overview_tabs}
self.assertEqual(self.COURSE_OVERVIEW_TABS, course_resp_tabs)
@ddt.data(*itertools.product( @ddt.data(*itertools.product(
[ [
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment