DEPR-48 Remove CourseTalk integration & Course Reviews functionality

This commit is contained in:
sarina
2020-11-10 18:20:47 -05:00
parent 8654feb7e1
commit b8bd4f6310
21 changed files with 8 additions and 407 deletions

View File

@@ -478,11 +478,6 @@ def course_info(request, course_id):
# Shared code with the new Course Home (DONE)
dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id)
# This local import is due to the circularity of lms and openedx references.
# This may be resolved by using stevedore to allow web fragments to be used
# as plugins, and to avoid the direct import.
from openedx.features.course_experience.views.course_reviews import CourseReviewsModuleFragmentView
# Shared code with the new Course Home (DONE)
# Get the course tools enabled for this user and course
course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
@@ -947,14 +942,6 @@ def course_about(request, course_id):
allow_anonymous = check_public_access(course, [COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE])
# This local import is due to the circularity of lms and openedx references.
# This may be resolved by using stevedore to allow web fragments to be used
# as plugins, and to avoid the direct import.
from openedx.features.course_experience.views.course_reviews import CourseReviewsModuleFragmentView
# Embed the course reviews tool
reviews_fragment_view = CourseReviewsModuleFragmentView().render_to_fragment(request, course=course)
context = {
'course': course,
'course_details': course_details,
@@ -979,7 +966,6 @@ def course_about(request, course_id):
'disable_courseware_header': True,
'pre_requisite_courses': pre_requisite_courses,
'course_image_urls': overview.image_urls,
'reviews_fragment_view': reviews_fragment_view,
'sidebar_html_enabled': sidebar_html_enabled,
'allow_anonymous': allow_anonymous,
}

View File

@@ -702,15 +702,7 @@ FEATURES = {
# e.g. COURSE_BLOCKS_API_EXTRA_FIELDS = [ ('course', 'other_course_settings'), ("problem", "weight") ]
COURSE_BLOCKS_API_EXTRA_FIELDS = []
# Settings for the course reviews tool template and identification key, set either to None to disable course reviews
COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME = 'coursetalk-reviews-fragment.html'
COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY = 'edx'
# CDN links to CourseTalk scripts to load read and write widgets
COURSE_TALK_READ_ONLY_SOURCE = '//d3q6qq2zt8nhwv.cloudfront.net/s/js/widgets/coursetalk-read-reviews.js'
COURSE_TALK_WRITE_ONLY_SOURCE = '//d3q6qq2zt8nhwv.cloudfront.net/s/js/widgets/coursetalk-write-reviews.js'
# Ignore static asset files on import which match this pattern
ASSET_IGNORE_REGEX = r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)"
# Used for A/B testing

View File

@@ -241,13 +241,6 @@ from six import string_types
</%block>
</div>
<%block name="course_about_reviews_tool">
## Course reviews tool
% if reviews_fragment_view:
${HTML(reviews_fragment_view.body_html())}
% endif
</%block>
## For now, ocw links are the only thing that goes in additional resources
% if get_course_about_section(request, course, "ocw_links"):
<div class="additional-resources">

View File

@@ -56,14 +56,7 @@ The course experience consists of a number of views:
The course updates page shows the user all of the course team's updates
in a scrolling list. The updates page is also provided as a course tool.
7. **Course Reviews**
This page shows the user reviews of the course from an external provider.
The default provider is `CourseTalk`_. The reviews page is also provided
as a course tool.
A number of the features in the course experience are controlled via Waffle
flags. For documentation, see `Waffle flag definitions`_.
.. _CourseTalk: https://www.coursetalk.com/
.. _Waffle flag definitions: https://github.com/edx/edx-platform/blob/master/openedx/features/course_experience/__init__.py

View File

@@ -32,19 +32,6 @@ DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_cour
# Waffle flag to let learners access a course before its start date.
COURSE_PRE_START_ACCESS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_start_access', __name__)
# Waffle flag to enable a review page link from the unified home page.
# .. toggle_name: course_experience.show_reviews_tool
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Used with our integration with CourseTalk to display reviews for a course.
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2017-06-19
# .. toggle_target_removal_date: None
# .. toggle_warnings: We are no longer integrating with CourseTalk, so this probably should be deprecated and the code
# for reviews should be removed. This temporary feature toggle should have a target removal date.
# .. toggle_tickets: DEPR-48
SHOW_REVIEWS_TOOL_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_reviews_tool', __name__)
# Waffle flag to enable the setting of course goals.
# .. toggle_name: course_experience.enable_course_goals
# .. toggle_implementation: CourseWaffleFlag

View File

@@ -11,9 +11,8 @@ from django.utils.translation import ugettext as _
from lms.djangoapps.courseware.courses import get_course_by_id
from common.djangoapps.student.models import CourseEnrollment
from . import DISABLE_UNIFIED_COURSE_TAB_FLAG, SHOW_REVIEWS_TOOL_FLAG
from . import DISABLE_UNIFIED_COURSE_TAB_FLAG
from .course_tools import CourseTool
from .views.course_reviews import CourseReviewsModuleFragmentView
from .views.course_updates import CourseUpdatesFragmentView
@@ -60,45 +59,3 @@ class CourseUpdatesTool(CourseTool):
Returns the URL for this tool for the specified course key.
"""
return reverse('openedx.course_experience.course_updates', args=[course_key])
class CourseReviewsTool(CourseTool):
"""
The course reviews tool.
"""
@classmethod
def analytics_id(cls):
"""
Returns an id to uniquely identify this tool in analytics events.
"""
return 'edx.reviews'
@classmethod
def title(cls):
"""
Returns the title of this tool.
"""
return _('Reviews')
@classmethod
def icon_classes(cls):
"""
Returns icon classes needed to represent this tool.
"""
return 'fa fa-star'
@classmethod
def is_enabled(cls, request, course_key):
"""
Returns True if this tool is enabled for the specified course key.
"""
if not SHOW_REVIEWS_TOOL_FLAG.is_enabled(course_key):
return False
return CourseReviewsModuleFragmentView.is_configured()
@classmethod
def url(cls, course_key):
"""
Returns the URL for this tool for the specified course key.
"""
return reverse('openedx.course_experience.course_reviews', args=[course_key])

View File

@@ -72,12 +72,6 @@
Bookmarks
</a>
</li>
<li>
<a class="course-tool-link" data-analytics-id="edx.reviews" href="/courses/course-v1:W3Cx+HTML5.0x+1T2017/course/reviews">
<span class="icon fa fa-star" aria-hidden="true"></span>
Reviews
</a>
</li>
<li>
<a class="course-tool-link" data-analytics-id="edx.updates" href="/courses/course-v1:W3Cx+HTML5.0x+1T2017/course/updates">
<span class="icon fa fa-newspaper-o" aria-hidden="true"></span>

View File

@@ -1,42 +0,0 @@
/**
Enable users to switch between viewing and writing CourseTalk reviews.
*/
export class CourseTalkReviews { // eslint-disable-line import/prefer-default-export
constructor(options) {
const $courseTalkToggleReadWriteReviews = $(options.toggleButton);
const toReadBtnText = 'View Reviews';
const toWriteBtnText = 'Write a Review';
// Initialize page to the read reviews view
self.currentSrc = options.readSrc;
$.getScript(options.readSrc, () => { // eslint-disable-line func-names
$('iframe').load(() => {
$(options.loadIcon).hide();
});
});
$courseTalkToggleReadWriteReviews.text(toWriteBtnText);
$courseTalkToggleReadWriteReviews.on('click', () => {
const switchToReadView = self.currentSrc === options.writeSrc;
// Cache js file for future button clicks
$.ajaxSetup({ cache: true });
// Show the loading icon
$(options.loadIcon).show();
// Update toggle button text
const newBtnText = switchToReadView ? toWriteBtnText : toReadBtnText;
$courseTalkToggleReadWriteReviews.text(newBtnText);
// Toggle the new coursetalk script object
self.currentSrc = switchToReadView ? options.readSrc : options.writeSrc;
$.getScript(self.currentSrc, () => { // eslint-disable-line func-names
$('iframe').load(() => {
$(options.loadIcon).hide();
});
});
});
}
}

View File

@@ -14,7 +14,7 @@ from django.urls import reverse
from lms.djangoapps.discussion.django_comment_client.permissions import has_permission
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
from openedx.core.djangolib.markup import Text, HTML
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, SHOW_REVIEWS_TOOL_FLAG
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG
from openedx.features.course_experience.course_tools import HttpMethod
%>

View File

@@ -1,40 +0,0 @@
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML
from openedx.features.course_experience import course_home_page_title
%>
<div class="course-reviews page-content-container" tabindex="-1">
<header class="page-header has-secondary">
## Breadcrumb navigation
<div class="page-header-main">
<nav aria-label="${_('Reviews')}" class="sr-is-focusable" tabindex="-1">
<div class="has-breadcrumbs">
<div class="breadcrumbs">
<span class="nav-item">
<a href="${course_url}">${course_home_page_title(course)}</a>
</span>
<span class="icon fa fa-angle-right" aria-hidden="true"></span>
<span class="nav-item">${_('Reviews')}</span>
</div>
</div>
</nav>
% if is_enrolled:
<div class="btn btn-secondary toggle-read-write-reviews"></div>
% endif
</div>
</header>
<div class="course-reviews-tool">
% if course_reviews_fragment:
${HTML(course_reviews_fragment.body_html())}
% endif
</div>
</div>

View File

@@ -1,28 +0,0 @@
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='../../static_content.html'/>
<%!
from django.conf import settings
from openedx.core.djangolib.js_utils import js_escaped_string
from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG
%>
% if SHOW_REVIEWS_TOOL_FLAG.is_enabled(course.id):
<div class="coursetalk-read-reviews">
<span class="fa fa-spinner fa-spin" aria-hidden="true"></span>
## Coursetalk Widget
<div id="ct-custom-read-review-widget" data-provider="${platform_key}" data-course="${course.id}"></div>
</div>
% endif
<%static:webpack entry="CourseTalkReviews">
new CourseTalkReviews({
toggleButton: '.toggle-read-write-reviews',
loadIcon: '.coursetalk-read-reviews .fa.fa-spinner',
readSrc: "${settings.COURSE_TALK_READ_ONLY_SOURCE | n, js_escaped_string}",
writeSrc: "${settings.COURSE_TALK_WRITE_ONLY_SOURCE | n, js_escaped_string}"
});
</%static:webpack>

View File

@@ -51,7 +51,6 @@ from openedx.features.course_duration_limits.models import CourseDurationLimitCo
from openedx.features.course_experience import (
COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
DISABLE_UNIFIED_COURSE_TAB_FLAG,
SHOW_REVIEWS_TOOL_FLAG,
SHOW_UPGRADE_MSG_ON_COURSE_HOME
)
from openedx.features.course_experience.tests import BaseCourseUpdatesTestCase
@@ -249,7 +248,6 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
# Add a welcome message
self.create_course_update(TEST_WELCOME_MESSAGE)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data(
[False, COURSE_VISIBILITY_PRIVATE, CourseUserType.ANONYMOUS, True, False],
[False, COURSE_VISIBILITY_PUBLIC_OUTLINE, CourseUserType.ANONYMOUS, True, False],
@@ -297,15 +295,15 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
private_url = course_home_url(self.private_course)
private_response = self.client.get(private_url)
# Verify that the course tools and dates are always shown
self.assertContains(response, TEST_COURSE_TOOLS)
is_anonymous = user_type is CourseUserType.ANONYMOUS
is_enrolled = user_type is CourseUserType.ENROLLED
is_enrolled_or_staff = is_enrolled or user_type in (
CourseUserType.UNENROLLED_STAFF, CourseUserType.GLOBAL_STAFF
)
# Verify that the course tools and dates are shown for enrolled users & staff
self.assertContains(response, TEST_COURSE_TOOLS, count=(1 if is_enrolled_or_staff else 0))
self.assertContains(response, 'Learn About Verified Certificate', count=(1 if is_enrolled else 0))
# Verify that start button, course sock, and welcome message
@@ -332,7 +330,6 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
'You must be enrolled in the course to see course content.')
@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data(
[CourseUserType.ANONYMOUS, 'To see course content'],
[CourseUserType.ENROLLED, None],
@@ -350,19 +347,17 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
url = course_home_url(self.course)
response = self.client.get(url)
# Verify that the course tools and dates are always shown
self.assertContains(response, TEST_COURSE_TOOLS)
# Verify that welcome messages are never shown
self.assertNotContains(response, TEST_WELCOME_MESSAGE)
# Verify that the outline, start button, course sock, and welcome message
# are only shown to enrolled users.
# Verify that the outline, start button, course sock, course tools, and welcome message
# are only shown to enrolled users or unenrolled staff.
is_enrolled = user_type is CourseUserType.ENROLLED
is_unenrolled_staff = user_type is CourseUserType.UNENROLLED_STAFF
expected_count = 1 if (is_enrolled or is_unenrolled_staff) else 0
self.assertContains(response, TEST_CHAPTER_NAME, count=expected_count)
self.assertContains(response, 'Start Course', count=expected_count)
self.assertContains(response, TEST_COURSE_TOOLS, count=expected_count)
self.assertContains(response, 'Learn About Verified Certificate', count=(1 if is_enrolled else 0))
# Verify that the expected message is shown to the user

View File

@@ -8,7 +8,6 @@ from django.conf.urls import url
from .views.course_dates import CourseDatesFragmentMobileView
from .views.course_home import CourseHomeFragmentView, CourseHomeView
from .views.course_outline import CourseOutlineFragmentView
from .views.course_reviews import CourseReviewsView
from .views.course_sock import CourseSockFragmentView
from .views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView
from .views.latest_update import LatestUpdateFragmentView
@@ -28,11 +27,6 @@ urlpatterns = [
CourseUpdatesView.as_view(),
name='openedx.course_experience.course_updates',
),
url(
r'^reviews$',
CourseReviewsView.as_view(),
name='openedx.course_experience.course_reviews',
),
url(
r'^home_fragment$',
CourseHomeFragmentView.as_view(),

View File

@@ -1,119 +0,0 @@
"""
Fragment for rendering the course reviews panel
"""
import six
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.courseware.views.views import CourseTabView
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.features.course_experience import default_course_url_name
from common.djangoapps.student.models import CourseEnrollment
class CourseReviewsView(CourseTabView):
"""
The course reviews page.
"""
@method_decorator(login_required)
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
def get(self, request, course_id, **kwargs):
"""
Displays the reviews page for the specified course.
"""
return super(CourseReviewsView, self).get(request, course_id, 'courseware', **kwargs)
def render_to_fragment(self, request, course=None, tab=None, **kwargs):
course_id = six.text_type(course.id)
reviews_fragment_view = CourseReviewsFragmentView()
return reviews_fragment_view.render_to_fragment(request, course_id=course_id, **kwargs)
class CourseReviewsFragmentView(EdxFragmentView):
"""
A fragment to display course reviews.
"""
def render_to_fragment(self, request, course_id=None, **kwargs):
"""
Fragment to render the course reviews fragment.
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)
course_url_name = default_course_url_name(course.id)
course_url = reverse(course_url_name, kwargs={'course_id': six.text_type(course.id)})
is_enrolled = CourseEnrollment.is_enrolled(request.user, course.id)
# Create the fragment
course_reviews_fragment = CourseReviewsModuleFragmentView().render_to_fragment(
request,
course=course,
**kwargs
)
context = {
'course': course,
'course_url': course_url,
'course_reviews_fragment': course_reviews_fragment,
'is_enrolled': is_enrolled,
}
html = render_to_string('course_experience/course-reviews-fragment.html', context)
return Fragment(html)
class CourseReviewsModuleFragmentView(EdxFragmentView):
"""
A fragment to display the course reviews module as specified by
the configured template.
"""
def render_to_fragment(self, request, course=None, **kwargs):
"""
Renders the configured template as a module.
There are two relevant configuration settings:
COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME points to the template that
will be rendered and returned.
COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY references the platform that
hosts the course. Generally, this is the domain name of the platform,
for example, 'edx.org' would have a platform key of 'edx'.
"""
# Grab the fragment type and provider from the configuration file
course_reviews_fragment_provider_template = \
settings.COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME
course_platform_key = \
settings.COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY
if not self.is_configured():
return None
context = {
'course': course,
'platform_key': course_platform_key
}
# Create the fragment from the given template
provider_reviews_template = 'course_experience/course_reviews_modules/%s' \
% course_reviews_fragment_provider_template
html = render_to_string(provider_reviews_template, context)
return Fragment(html)
@classmethod
def is_configured(self):
return settings.COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME \
and settings.COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY

View File

@@ -1,7 +0,0 @@
CourseTalk
----------
This directory contains a Django application that integrates `CourseTalk`_
as a course review provider for Open edX.
.. _CourseTalk: https://www.coursetalk.com/

View File

@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CourseTalkWidgetConfiguration',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('platform_key', models.CharField(help_text="This key needs to associate CourseTalk reviews with your platform. Better to use domain name Ex: for 'http://edx.org' platform_key will be 'edx'", max_length=50)),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
options={
'ordering': ('-change_date',),
'abstract': False,
},
),
]

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('coursetalk', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='coursetalkwidgetconfiguration',
name='platform_key',
field=models.CharField(help_text='The platform key associates CourseTalk widgets with your platform. Generally, it is the domain name for your platform. For example, if your platform is http://edx.org, the platform key is "edx".', max_length=50),
),
]

View File

@@ -40,7 +40,6 @@ setup(
"calendar_sync_toggle = openedx.features.calendar_sync.plugins:CalendarSyncToggleTool",
"course_bookmarks = openedx.features.course_bookmarks.plugins:CourseBookmarksTool",
"course_updates = openedx.features.course_experience.plugins:CourseUpdatesTool",
"course_reviews = openedx.features.course_experience.plugins:CourseReviewsTool",
"verified_upgrade = lms.djangoapps.courseware.course_tools:VerifiedUpgradeTool",
"financial_assistance = lms.djangoapps.courseware.course_tools:FinancialAssistanceTool",
],

View File

@@ -110,7 +110,6 @@ module.exports = Merge.smart({
CourseHome: './openedx/features/course_experience/static/course_experience/js/CourseHome.js',
CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
CourseTalkReviews: './openedx/features/course_experience/static/course_experience/js/CourseTalkReviews.js',
Currency: './openedx/features/course_experience/static/course_experience/js/currency.js',
Enrollment: './openedx/features/course_experience/static/course_experience/js/Enrollment.js',
LatestUpdate: './openedx/features/course_experience/static/course_experience/js/LatestUpdate.js',
@@ -424,7 +423,6 @@ module.exports = Merge.smart({
$: 'jQuery',
backbone: 'Backbone',
canvas: 'canvas',
coursetalk: 'CourseTalk',
gettext: 'gettext',
jquery: 'jQuery',
logger: 'Logger',