MA-1248 - CourseEnrollmentAPI: added discussion URL
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
Useful django models for implementing XBlock infrastructure in django.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from django.db import models
|
||||
|
||||
@@ -13,8 +13,8 @@ from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.locator import CourseKey
|
||||
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
from discussion_api.forms import CommentActionsForm, ThreadActionsForm
|
||||
from discussion_api.pagination import get_paginated_data
|
||||
from discussion_api.permissions import (
|
||||
@@ -55,7 +55,7 @@ def _get_course_or_404(course_key, user):
|
||||
disabled for the course.
|
||||
"""
|
||||
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
|
||||
return course
|
||||
|
||||
|
||||
@@ -81,11 +81,11 @@ def _discussion_disabled_course_for(user):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase):
|
||||
"""Test for get_course"""
|
||||
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(GetCourseTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create(org="x", course="y", run="z")
|
||||
@@ -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", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test for get_course_topics"""
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super(GetCourseTopicsTest, self).setUp()
|
||||
@@ -480,11 +480,11 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase):
|
||||
"""Test for get_thread_list"""
|
||||
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(GetThreadListTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -909,15 +909,16 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleSto
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
|
||||
"""Test for get_comment_list"""
|
||||
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(GetCommentListTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super(GetCommentListTest, self).setUp()
|
||||
httpretty.reset()
|
||||
@@ -1333,6 +1334,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'thread_created')
|
||||
@disable_signal(api, 'thread_voted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CreateThreadTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -1341,7 +1343,6 @@ class CreateThreadTest(
|
||||
):
|
||||
"""Tests for create_thread"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(CreateThreadTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -1585,6 +1586,7 @@ class CreateThreadTest(
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'comment_created')
|
||||
@disable_signal(api, 'comment_voted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CreateCommentTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -1593,7 +1595,6 @@ class CreateCommentTest(
|
||||
):
|
||||
"""Tests for create_comment"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(CreateCommentTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -1859,6 +1860,7 @@ class CreateCommentTest(
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'thread_edited')
|
||||
@disable_signal(api, 'thread_voted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class UpdateThreadTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -1867,7 +1869,6 @@ class UpdateThreadTest(
|
||||
):
|
||||
"""Tests for update_thread"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(UpdateThreadTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -2247,6 +2248,7 @@ class UpdateThreadTest(
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'comment_edited')
|
||||
@disable_signal(api, 'comment_voted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class UpdateCommentTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -2256,7 +2258,6 @@ class UpdateCommentTest(
|
||||
"""Tests for update_comment"""
|
||||
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(UpdateCommentTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -2304,7 +2305,6 @@ class UpdateCommentTest(
|
||||
self.register_get_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):
|
||||
"""Check that an empty update does not make any modifying requests."""
|
||||
self.register_comment()
|
||||
@@ -2632,6 +2632,7 @@ class UpdateCommentTest(
|
||||
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'thread_deleted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class DeleteThreadTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -2640,7 +2641,6 @@ class DeleteThreadTest(
|
||||
):
|
||||
"""Tests for delete_thread"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(DeleteThreadTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -2771,6 +2771,7 @@ class DeleteThreadTest(
|
||||
|
||||
@ddt.ddt
|
||||
@disable_signal(api, 'comment_deleted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class DeleteCommentTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -2779,7 +2780,6 @@ class DeleteCommentTest(
|
||||
):
|
||||
"""Tests for delete_comment"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(DeleteCommentTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
@@ -2928,6 +2928,7 @@ class DeleteCommentTest(
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class RetrieveThreadTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
@@ -2935,7 +2936,6 @@ class RetrieveThreadTest(
|
||||
):
|
||||
"""Tests for get_thread"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(RetrieveThreadTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@@ -70,6 +70,7 @@ class DiscussionAPIViewTestMixin(CommentsServiceMockMixin, UrlResetMixin):
|
||||
)
|
||||
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for CourseView"""
|
||||
def setUp(self):
|
||||
@@ -103,6 +104,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for CourseTopicsView"""
|
||||
def setUp(self):
|
||||
@@ -139,6 +141,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
@ddt.ddt
|
||||
@httpretty.activate
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet list"""
|
||||
def setUp(self):
|
||||
@@ -388,6 +391,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@disable_signal(api, 'thread_created')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet create"""
|
||||
def setUp(self):
|
||||
@@ -480,6 +484,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@disable_signal(api, 'thread_edited')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet partial_update"""
|
||||
def setUp(self):
|
||||
@@ -580,6 +585,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
|
||||
|
||||
@httpretty.activate
|
||||
@disable_signal(api, 'thread_deleted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet delete"""
|
||||
def setUp(self):
|
||||
@@ -613,6 +619,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for CommentViewSet list"""
|
||||
def setUp(self):
|
||||
@@ -744,6 +751,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@disable_signal(api, 'comment_deleted')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet delete"""
|
||||
|
||||
@@ -785,6 +793,7 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@disable_signal(api, 'comment_created')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for CommentViewSet create"""
|
||||
def setUp(self):
|
||||
@@ -869,6 +878,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@disable_signal(api, 'comment_edited')
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for CommentViewSet partial_update"""
|
||||
def setUp(self):
|
||||
@@ -954,6 +964,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
|
||||
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet Retrieve"""
|
||||
def setUp(self):
|
||||
|
||||
@@ -27,7 +27,6 @@ from openedx.core.djangoapps.course_groups.cohorts import (
|
||||
from courseware.tabs import EnrolledTab
|
||||
from courseware.access import has_access
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from ccx.overrides import get_current_ccx
|
||||
|
||||
from django_comment_common.utils import ThreadContext
|
||||
from django_comment_client.permissions import has_permission, get_team
|
||||
@@ -66,11 +65,7 @@ class DiscussionTab(EnrolledTab):
|
||||
def is_enabled(cls, course, user=None):
|
||||
if not super(DiscussionTab, cls).is_enabled(course, user):
|
||||
return False
|
||||
|
||||
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
|
||||
if get_current_ccx(course.id):
|
||||
return False
|
||||
return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')
|
||||
return utils.is_discussion_enabled(course.id)
|
||||
|
||||
|
||||
def _attr_safe_json(obj):
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections import defaultdict
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
import pytz
|
||||
from django.contrib.auth.models import User
|
||||
@@ -13,6 +14,7 @@ import pystache_custom as pystache
|
||||
from opaque_keys.edx.locations import i4xEncoder
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
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_client.permissions import check_permissions_by_view, has_permission, get_team
|
||||
@@ -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)
|
||||
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):
|
||||
kwargs={'course_id': course_id},
|
||||
request=request
|
||||
)
|
||||
discussion_url = reverse(
|
||||
'discussion_course',
|
||||
kwargs={'course_id': course_id},
|
||||
request=request
|
||||
) if course_overview.is_discussion_tab_enabled() else None
|
||||
else:
|
||||
video_outline_url = None
|
||||
course_updates_url = None
|
||||
course_handouts_url = None
|
||||
discussion_url = None
|
||||
|
||||
if course_overview.advertised_start is not None:
|
||||
start_type = "string"
|
||||
@@ -68,6 +74,7 @@ class CourseOverviewField(serializers.RelatedField):
|
||||
"video_outline": video_outline_url,
|
||||
"course_updates": course_updates_url,
|
||||
"course_handouts": course_handouts_url,
|
||||
"discussion_url": discussion_url,
|
||||
"subscription_id": course_overview.clean_id(padding_char='_'),
|
||||
"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
|
||||
from .. import errors
|
||||
from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin
|
||||
from .serializers import CourseEnrollmentSerializer
|
||||
from util.testing import UrlResetMixin
|
||||
|
||||
|
||||
class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
|
||||
@@ -60,7 +61,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
|
||||
class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin):
|
||||
"""
|
||||
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
|
||||
"""
|
||||
@@ -71,7 +72,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
|
||||
LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
|
||||
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):
|
||||
"""
|
||||
Verifies user course enrollment response for success
|
||||
"""
|
||||
super(TestUserEnrollmentApi, self).verify_success(response)
|
||||
courses = response.data
|
||||
self.assertEqual(len(courses), 1)
|
||||
@@ -205,6 +213,14 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
|
||||
course_data = response.data[0]['course']
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -239,6 +239,8 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
|
||||
the course.
|
||||
* video_outline: The URI to get the list of all videos that the user
|
||||
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.
|
||||
* is_active: Whether the course is currently active. Possible values
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- 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']
|
||||
@@ -1,15 +1,17 @@
|
||||
"""
|
||||
Declaration of CourseOverview model
|
||||
"""
|
||||
|
||||
import json
|
||||
from django.db import models
|
||||
|
||||
from django.db.models.fields import BooleanField, DateTimeField, DecimalField, TextField, FloatField, IntegerField
|
||||
from django.db.utils import IntegrityError
|
||||
from django.utils.translation import ugettext
|
||||
from lms.djangoapps import django_comment_client
|
||||
from model_utils.models import TimeStampedModel
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from util.date_utils import strftime_localized
|
||||
from xmodule import course_metadata_utils
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
@@ -30,7 +32,7 @@ class CourseOverview(TimeStampedModel):
|
||||
"""
|
||||
|
||||
# IMPORTANT: Bump this whenever you modify this model and/or add a migration.
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
|
||||
# Cache entry versioning.
|
||||
version = IntegerField()
|
||||
@@ -176,6 +178,10 @@ class CourseOverview(TimeStampedModel):
|
||||
course_overview = cls._create_from_course(course)
|
||||
try:
|
||||
course_overview.save()
|
||||
CourseOverviewTab.objects.bulk_create([
|
||||
CourseOverviewTab(tab_id=tab.tab_id, course_overview=course_overview)
|
||||
for tab in course.tabs
|
||||
])
|
||||
except IntegrityError:
|
||||
# There is a rare race condition that will occur if
|
||||
# CourseOverview.get_from_id is called while a
|
||||
@@ -358,3 +364,22 @@ class CourseOverview(TimeStampedModel):
|
||||
CourseKey.from_string(course_overview['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):
|
||||
NEXT_WEEK = TODAY + datetime.timedelta(days=7)
|
||||
NEXT_MONTH = TODAY + datetime.timedelta(days=30)
|
||||
|
||||
COURSE_OVERVIEW_TABS = {'courseware', 'info', 'textbooks', 'discussion', 'wiki', 'progress'}
|
||||
|
||||
def check_course_overview_against_course(self, course):
|
||||
"""
|
||||
Compares a CourseOverview object against its corresponding
|
||||
@@ -164,6 +166,12 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
|
||||
self.assertEqual(course_value, cache_miss_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(
|
||||
[
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user