Commit 90fe71db by wajeeha-khalid

MA-1248 - CourseEnrollmentAPI: added discussion URL

parent 06129ada
"""
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
......
# -*- 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
"""
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(
[
{
......
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