Skip to content
Snippets Groups Projects
Unverified Commit 2c12792b authored by Dave St.Germain's avatar Dave St.Germain Committed by GitHub
Browse files

Merge pull request #23857 from edx/dcs/wikitab

Fixes hidden tabs showing up in courseware MFE.
parents 10e5b1a2 2def3f42
No related branches found
No related tags found
No related merge requests found
# Generated by Django 2.2.12 on 2020-04-30 15:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_overviews', '0021_courseoverviewtab_link'),
]
operations = [
migrations.AddField(
model_name='courseoverviewtab',
name='is_hidden',
field=models.BooleanField(default=False),
),
]
......@@ -59,7 +59,7 @@ class CourseOverview(TimeStampedModel):
app_label = 'course_overviews'
# IMPORTANT: Bump this whenever you modify this model and/or add a migration.
VERSION = 10
VERSION = 11 # this one goes to eleven
# Cache entry versioning.
version = IntegerField()
......@@ -262,6 +262,7 @@ class CourseOverview(TimeStampedModel):
course_staff_only=tab.course_staff_only,
url_slug=tab.get('url_slug'),
link=tab.get('link'),
is_hidden=tab.get('is_hidden', False),
course_overview=course_overview)
for tab in course.tabs
])
......@@ -864,6 +865,7 @@ class CourseOverviewTab(models.Model):
course_staff_only = models.BooleanField(default=False)
url_slug = models.TextField(null=True)
link = models.TextField(null=True)
is_hidden = models.BooleanField(default=False)
def __str__(self):
return self.tab_id
......
......@@ -2,16 +2,8 @@
Course API Serializers. Representing course catalog data
"""
from babel.numbers import get_currency_symbol
from django.urls import reverse
from rest_framework import serializers
from course_modes.models import CourseMode
from edxnotes.helpers import is_feature_enabled
from lms.djangoapps.courseware.tabs import get_course_tab_list
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
from openedx.core.lib.api.fields import AbsoluteURLField
......@@ -87,12 +79,12 @@ class CourseInfoSerializer(serializers.Serializer): # pylint: disable=abstract-
start_type = serializers.CharField()
pacing = serializers.CharField()
enrollment = serializers.DictField()
tabs = serializers.SerializerMethodField()
verified_mode = serializers.SerializerMethodField()
tabs = serializers.ListField()
verified_mode = serializers.DictField()
show_calculator = serializers.BooleanField()
is_staff = serializers.BooleanField()
can_load_courseware = serializers.DictField()
notes = serializers.SerializerMethodField()
notes = serializers.DictField()
def __init__(self, *args, **kwargs):
"""
......@@ -106,41 +98,3 @@ class CourseInfoSerializer(serializers.Serializer): # pylint: disable=abstract-
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
def get_tabs(self, course_overview):
"""
Return course tab metadata.
"""
tabs = []
for priority, tab in enumerate(get_course_tab_list(course_overview.effective_user, course_overview)):
tabs.append({
'title': tab.title or tab.get('name', ''),
'slug': tab.tab_id,
'priority': priority,
'type': tab.type,
'url': tab.link_func(course_overview, reverse),
})
return tabs
def get_verified_mode(self, course_overview):
"""
Return verified mode information, or None.
"""
mode = CourseMode.verified_mode_for_course(course_overview.id)
if mode:
return {
'price': mode.min_price,
'currency': mode.currency.upper(),
'currency_symbol': get_currency_symbol(mode.currency.upper()),
'sku': mode.sku,
'upgrade_url': verified_upgrade_deadline_link(course_overview.effective_user, course_overview),
}
def get_notes(self, course_overview):
"""
Return whether edxnotes is enabled and visible.
"""
return {
'enabled': is_feature_enabled(course_overview, course_overview.effective_user),
'visible': course_overview.edxnotes_visibility,
}
"""
Tests for courseware API
"""
from datetime import datetime
import unittest
from datetime import datetime
import ddt
import mock
from django.conf import settings
from lms.djangoapps.courseware.access_utils import (
ACCESS_DENIED,
ACCESS_GRANTED
)
from lms.djangoapps.courseware.access_utils import ACCESS_DENIED, ACCESS_GRANTED
from lms.djangoapps.courseware.tabs import ExternalLinkCourseTab
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE,
SharedModuleStoreTestCase
)
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, ToyCourseFactory
from student.tests.factories import UserFactory
from student.models import CourseEnrollment
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
......@@ -69,6 +62,9 @@ class CourseApiTestViews(BaseCoursewareTests):
def setUpClass(cls):
BaseCoursewareTests.setUpClass()
cls.course.tabs.append(ExternalLinkCourseTab.load('external_link', name='Zombo', link='http://zombo.com'))
cls.course.tabs.append(
ExternalLinkCourseTab.load('external_link', name='Hidden', link='http://hidden.com', is_hidden=True)
)
cls.store.update_item(cls.course, cls.user.id)
@ddt.data(
......@@ -96,11 +92,11 @@ class CourseApiTestViews(BaseCoursewareTests):
assert len(response.data['tabs']) == 5
found = False
for tab in response.data['tabs']:
if tab['type'] == 'external_link' and tab['url'] == 'http://zombo.com':
found = True
break
else:
assert found, 'external link not in course tabs'
if tab['type'] == 'external_link':
assert tab['url'] != 'http://hidden.com', "Hidden tab is not hidden"
if tab['url'] == 'http://zombo.com':
found = True
assert found, 'external link not in course tabs'
elif enable_anonymous and not logged_in:
# multiple checks use this handler
check_public_access.assert_called()
......
......@@ -4,26 +4,135 @@ Course API Views
import json
from babel.numbers import get_currency_symbol
from django.urls import reverse
from opaque_keys.edx.keys import CourseKey, UsageKey
from rest_framework.generics import RetrieveAPIView
from rest_framework.response import Response
from rest_framework.views import APIView
from course_modes.models import CourseMode
from edxnotes.helpers import is_feature_enabled
from lms.djangoapps.course_api.api import course_detail
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.courses import check_course_access
from lms.djangoapps.courseware.module_render import get_module_by_usage_id
from student.models import CourseEnrollment
from lms.djangoapps.courseware.tabs import get_course_tab_list
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.access import generate_course_expired_message
from openedx.features.discounts.utils import generate_offer_html
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC
from student.models import CourseEnrollment
from .serializers import CourseInfoSerializer
class CoursewareMeta:
"""
Encapsulates courseware and enrollment metadata.
"""
def __init__(self, course_key, request, username=''):
self.overview = course_detail(
request,
username or request.user.username,
course_key,
)
self.effective_user = self.overview.effective_user
self.course_key = course_key
def __getattr__(self, name):
return getattr(self.overview, name)
@property
def is_staff(self):
return has_access(self.effective_user, 'staff', self.overview).has_access
@property
def enrollment(self):
"""
Return enrollment information.
"""
if self.effective_user.is_anonymous:
mode = None
is_active = False
else:
mode, is_active = CourseEnrollment.enrollment_mode_for_user(
self.effective_user,
self.course_key
)
return {'mode': mode, 'is_active': is_active}
@property
def course_expired_message(self):
# TODO: TNL-7185 Legacy: Refactor to return the expiration date and format the message in the MFE
return generate_course_expired_message(self.effective_user, self.overview)
@property
def offer_html(self):
# TODO: TNL-7185 Legacy: Refactor to return the offer data and format the message in the MFE
return generate_offer_html(self.effective_user, self.overview)
@property
def content_type_gating_enabled(self):
return ContentTypeGatingConfig.enabled_for_enrollment(
user=self.effective_user,
course_key=self.course_key,
)
@property
def can_load_courseware(self):
return check_course_access(
self.overview,
self.effective_user,
'load',
check_if_enrolled=True,
check_survey_complete=False,
check_if_authenticated=True,
).to_json()
@property
def tabs(self):
"""
Return course tab metadata.
"""
tabs = []
for priority, tab in enumerate(get_course_tab_list(self.effective_user, self.overview)):
tabs.append({
'title': tab.title or tab.get('name', ''),
'slug': tab.tab_id,
'priority': priority,
'type': tab.type,
'url': tab.link_func(self.overview, reverse),
})
return tabs
@property
def verified_mode(self):
"""
Return verified mode information, or None.
"""
mode = CourseMode.verified_mode_for_course(self.course_key)
if mode:
return {
'price': mode.min_price,
'currency': mode.currency.upper(),
'currency_symbol': get_currency_symbol(mode.currency.upper()),
'sku': mode.sku,
'upgrade_url': verified_upgrade_deadline_link(self.effective_user, self.overview),
}
@property
def notes(self):
"""
Return whether edxnotes is enabled and visible.
"""
return {
'enabled': is_feature_enabled(self.overview, self.effective_user),
'visible': self.overview.edxnotes_visibility,
}
class CoursewareInformation(RetrieveAPIView):
"""
**Use Cases**
......@@ -71,6 +180,7 @@ class CoursewareInformation(RetrieveAPIView):
requested_fields (optional) comma separated list:
If set, then only those fields will be returned.
username (optional) username to masquerade as (if requesting user is staff)
**Returns**
......@@ -89,43 +199,16 @@ class CoursewareInformation(RetrieveAPIView):
Return the requested course object, if the user has appropriate
permissions.
"""
overview = course_detail(
self.request,
self.request.user.username,
if self.request.user.is_staff:
username = self.request.GET.get('username', '') or self.request.user.username
else:
username = self.request.user.username
overview = CoursewareMeta(
CourseKey.from_string(self.kwargs['course_key_string']),
self.request,
username=username,
)
if self.request.user.is_anonymous:
mode = None
is_active = False
else:
mode, is_active = CourseEnrollment.enrollment_mode_for_user(
overview.effective_user,
overview.id
)
overview.enrollment = {'mode': mode, 'is_active': is_active}
overview.is_staff = has_access(self.request.user, 'staff', overview).has_access
overview.can_load_courseware = check_course_access(
overview,
self.request.user,
'load',
check_if_enrolled=True,
check_survey_complete=False,
check_if_authenticated=True,
).to_json()
# TODO: TNL-7185 Legacy: Refactor to return the expiration date and format the message in the MFE
overview.course_expired_message = generate_course_expired_message(self.request.user, overview)
# TODO: TNL-7185 Legacy: Refactor to return the offer data and format the message in the MFE
overview.offer_html = generate_offer_html(self.request.user, overview)
course_key = CourseKey.from_string(self.kwargs['course_key_string'])
overview.content_type_gating_enabled = ContentTypeGatingConfig.enabled_for_enrollment(
user=self.request.user,
course_key=course_key,
)
return overview
def get_serializer_context(self):
......@@ -160,7 +243,7 @@ class SequenceMetadata(DeveloperErrorViewMixin, APIView):
another user specifies a username other than their own.
* 404 if the course is not available or cannot be seen.
"""
def get(self, request, usage_key_string, *args, **kwargs): # pylint: disable=unused-argument
def get(self, request, usage_key_string, *args, **kwargs):
"""
Return response to a GET request.
"""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment