AA-36: Link to toggle calendar sync

This commit is contained in:
Dillon Dumesnil
2020-02-24 12:06:13 -05:00
parent 0c2dfd136e
commit a59155e83c
16 changed files with 318 additions and 32 deletions

View File

@@ -3,6 +3,9 @@
from .models import UserCalendarSyncConfig
SUBSCRIBE = 'subscribe'
UNSUBSCRIBE = 'unsubscribe'
def subscribe_user_to_calendar(user, course_key):
"""

View File

@@ -0,0 +1,76 @@
"""
Platform plugins to support Calendar Sync toggle.
"""
from django.urls import reverse
from django.utils.translation import ugettext as _
from openedx.features.calendar_sync.api import SUBSCRIBE, UNSUBSCRIBE
from openedx.features.calendar_sync.models import UserCalendarSyncConfig
from openedx.features.course_experience import CALENDAR_SYNC_FLAG, RELATIVE_DATES_FLAG
from openedx.features.course_experience.course_tools import CourseTool, HttpMethod
from student.models import CourseEnrollment
class CalendarSyncToggleTool(CourseTool):
"""
The Calendar Sync toggle tool.
"""
http_method = HttpMethod.POST
link_title = _('Calendar Sync')
toggle_data = {'toggle_data': ''}
@classmethod
def analytics_id(cls):
"""
Returns an id to uniquely identify this tool in analytics events.
"""
return 'edx.calendar-sync'
@classmethod
def is_enabled(cls, request, course_key):
"""
The Calendar Sync toggle tool is limited to user enabled through a waffle flag.
Staff always has access.
"""
if not (CALENDAR_SYNC_FLAG.is_enabled(course_key) and RELATIVE_DATES_FLAG.is_enabled(course_key)):
return False
if CourseEnrollment.is_enrolled(request.user, course_key):
if UserCalendarSyncConfig.is_enabled_for_course(request.user, course_key):
cls.link_title = _('Unsubscribe from calendar updates')
cls.toggle_data['toggle_data'] = UNSUBSCRIBE
else:
cls.link_title = _('Subscribe to calendar updates')
cls.toggle_data['toggle_data'] = SUBSCRIBE
return True
return False
@classmethod
def title(cls): # pylint: disable=arguments-differ
"""
Returns the title of this tool.
"""
return cls.link_title
@classmethod
def icon_classes(cls): # pylint: disable=arguments-differ
"""
Returns the icon classes needed to represent this tool.
"""
return 'fa fa-calendar'
@classmethod
def url(cls, course_key):
"""
Returns the URL for this tool for the specified course key.
"""
return reverse('openedx.calendar_sync', args=[course_key])
@classmethod
def data(cls):
"""
Additional data to send with a form submission
"""
return cls.toggle_data

View File

@@ -0,0 +1,44 @@
"""
Unit tests for the calendar sync plugins.
"""
import crum
import ddt
from django.test import RequestFactory
from xmodule.modulestore.tests.django_utils import CourseUserType, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.features.calendar_sync.plugins import CalendarSyncToggleTool
from openedx.features.course_experience import CALENDAR_SYNC_FLAG, RELATIVE_DATES_FLAG
@ddt.ddt
class TestCalendarSyncToggleTool(SharedModuleStoreTestCase):
"""
Test the calendar sync toggle tool.
"""
@classmethod
def setUpClass(cls):
""" Set up any course data """
super(TestCalendarSyncToggleTool, cls).setUpClass()
cls.course = CourseFactory.create()
cls.course_key = cls.course.id
@ddt.data(
[CourseUserType.ANONYMOUS, False],
[CourseUserType.ENROLLED, True],
[CourseUserType.UNENROLLED, False],
[CourseUserType.UNENROLLED_STAFF, False],
)
@ddt.unpack
@override_waffle_flag(CALENDAR_SYNC_FLAG, active=True)
@RELATIVE_DATES_FLAG.override(active=True)
def test_calendar_sync_toggle_tool_is_enabled(self, user_type, should_be_enabled):
request = RequestFactory().request()
request.user = self.create_user_for_course(self.course, user_type)
self.addCleanup(crum.set_current_request, None)
crum.set_current_request(request)
self.assertEqual(CalendarSyncToggleTool.is_enabled(request, self.course.id), should_be_enabled)

View File

@@ -0,0 +1,51 @@
"""
Tests for Calendar Sync views.
"""
import ddt
from django.test import TestCase
from django.urls import reverse
from openedx.features.calendar_sync.api import SUBSCRIBE, UNSUBSCRIBE
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
TEST_PASSWORD = 'test'
@ddt.ddt
class TestCalendarSyncView(SharedModuleStoreTestCase, TestCase):
"""Tests for the calendar sync view."""
@classmethod
def setUpClass(cls):
""" Set up any course data """
super(TestCalendarSyncView, cls).setUpClass()
cls.course = CourseFactory.create()
def setUp(self):
super(TestCalendarSyncView, self).setUp()
self.user = self.create_user_for_course(self.course)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
self.calendar_sync_url = reverse('openedx.calendar_sync', args=[self.course.id])
@ddt.data(
# Redirect on successful subscribe
[{'tool_data': "{{'toggle_data': '{}'}}".format(SUBSCRIBE)}, 302, ''],
# Redirect on successful unsubscribe
[{'tool_data': "{{'toggle_data': '{}'}}".format(UNSUBSCRIBE)}, 302, ''],
# 422 on unknown toggle_data
[{'tool_data': "{{'toggle_data': '{}'}}".format('gibberish')}, 422,
'Toggle data was not provided or had unknown value.'],
# 422 on no toggle_data
[{'tool_data': "{{'random_data': '{}'}}".format('gibberish')}, 422,
'Toggle data was not provided or had unknown value.'],
# 422 on no tool_data
[{'nonsense': "{{'random_data': '{}'}}".format('gibberish')}, 422, 'Tool data was not provided.'],
)
@ddt.unpack
def test_course_dates_fragment(self, data, expected_status_code, contained_text):
response = self.client.post(self.calendar_sync_url, data)
self.assertEqual(response.status_code, expected_status_code)
self.assertIn(contained_text, str(response.content))

View File

@@ -0,0 +1,16 @@
"""
Defines URLs for Calendar Sync.
"""
from django.conf.urls import url
from .views.calendar_sync import CalendarSyncView
urlpatterns = [
url(
r'^calendar_sync$',
CalendarSyncView.as_view(),
name='openedx.calendar_sync',
),
]

View File

@@ -0,0 +1,54 @@
"""
Views to toggle Calendar Sync settings for a user on a course
"""
import json
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import View
from opaque_keys.edx.keys import CourseKey
from rest_framework import status
from openedx.features.calendar_sync.api import (
SUBSCRIBE, UNSUBSCRIBE, subscribe_user_to_calendar, unsubscribe_user_to_calendar
)
from util.views import ensure_valid_course_key
class CalendarSyncView(View):
"""
View for Calendar Sync
"""
@method_decorator(login_required)
@method_decorator(ensure_csrf_cookie)
@method_decorator(ensure_valid_course_key)
def post(self, request, course_id):
"""
Updates the request user's calendar sync subscription status
Arguments:
request: HTTP request
course_id (str): string of a course key
"""
course_key = CourseKey.from_string(course_id)
tool_data = request.POST.get('tool_data')
if not tool_data:
return HttpResponse('Tool data was not provided.', status=status.HTTP_422_UNPROCESSABLE_ENTITY)
json_acceptable_string = tool_data.replace("'", "\"")
data = json.loads(json_acceptable_string)
toggle_data = data.get('toggle_data')
if toggle_data == SUBSCRIBE:
subscribe_user_to_calendar(request.user, course_key)
elif toggle_data == UNSUBSCRIBE:
unsubscribe_user_to_calendar(request.user, course_key)
else:
return HttpResponse('Toggle data was not provided or had unknown value.',
status=status.HTTP_422_UNPROCESSABLE_ENTITY)
return redirect(reverse('openedx.course_experience.course_home', args=[course_id]))