-
- {% include "loading.html" %}
-
-
-{% endblock %}
diff --git a/analytics_dashboard/courses/tests/test_views/test_learners.py b/analytics_dashboard/courses/tests/test_views/test_learners.py
deleted file mode 100644
index 6137affaa..000000000
--- a/analytics_dashboard/courses/tests/test_views/test_learners.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import json
-import logging
-
-import unittest.mock as mock
-import httpretty
-from ddt import data, ddt
-from django.conf import settings
-from django.test import TestCase
-from django.test.utils import override_settings
-from edx_toggles.toggles.testutils import override_waffle_flag
-from requests.exceptions import ConnectionError as RequestsConnectionError
-from requests.exceptions import Timeout
-from testfixtures import LogCapture
-from waffle.testutils import override_switch
-
-from analytics_dashboard.courses.tests.test_views import ViewTestMixin
-from analytics_dashboard.courses.tests.utils import assert_dict_contains_subset, CourseSamples
-from analytics_dashboard.courses.waffle import (
- DISPLAY_LEARNER_ANALYTICS,
-)
-
-
-@httpretty.activate
-@override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=True)
-@ddt
-class LearnersViewTests(ViewTestMixin, TestCase):
- TABLE_ERROR_TEXT = 'We are unable to load this table.'
- viewname = 'courses:learners:learners'
-
- def _register_uris(self, learners_status, learners_payload, course_metadata_status, course_metadata_payload):
- httpretty.register_uri(
- httpretty.GET,
- f'{settings.DATA_API_URL}/learners/',
- body=json.dumps(learners_payload),
- status=learners_status
- )
- httpretty.register_uri(
- httpretty.GET,
- '{data_api_url}/course_learner_metadata/{course_id}/'.format(
- data_api_url=settings.DATA_API_URL,
- course_id=CourseSamples.DEMO_COURSE_ID,
- ),
- body=json.dumps(course_metadata_payload),
- status=course_metadata_status
- )
- self.addCleanup(httpretty.reset)
-
- def _get(self):
- return self.client.get(self.path(course_id=CourseSamples.DEMO_COURSE_ID))
-
- def _assert_context(self, response, expected_context_subset):
- default_expected_context_subset = {
- 'learner_list_url': '/api/learner_analytics/v0/learners/',
- 'course_learner_metadata_url': '/api/learner_analytics/v0/course_learner_metadata/{course_id}/'.format(
- course_id=CourseSamples.DEMO_COURSE_ID
- ),
- }
- assert_dict_contains_subset(response.context, dict(list(expected_context_subset.items())))
- assert_dict_contains_subset(
- response.context['js_data']['course'],
- dict(list(default_expected_context_subset.items())),
- )
-
- def get_mock_data(self, *args, **kwargs):
- pass
-
- def test_success(self):
- learners_payload = {'arbitrary_learners_key': ['arbitrary_value_1', 'arbitrary_value_2']}
- course_metadata_payload = {'arbitrary_metadata_value': {'arbitrary_value_1': 'arbitrary_value_2'}}
- self._register_uris(200, learners_payload, 200, course_metadata_payload)
- response = self._get()
- self._assert_context(response, {
- 'learner_list_json': learners_payload,
- 'course_learner_metadata_json': course_metadata_payload
- })
- self.assertNotContains(response, self.TABLE_ERROR_TEXT)
- return response
-
- @override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=False)
- def test_redirect_if_disabled(self):
- learners_payload = {'arbitrary_learners_key': ['arbitrary_value_1', 'arbitrary_value_2']}
- course_metadata_payload = {'arbitrary_metadata_value': {'arbitrary_value_1': 'arbitrary_value_2'}}
- self._register_uris(200, learners_payload, 200, course_metadata_payload)
- response = self._get()
- self.assertRedirects(response, '/courses', target_status_code=301)
-
- @override_switch('enable_learner_download', active=False)
- def test_disable_learner_download_button(self):
- response = self.test_success()
- self.assertNotIn('learner_list_download_url', response.context['js_data']['course'])
-
- @override_switch('enable_learner_download', active=True)
- @override_settings(LEARNER_API_LIST_DOWNLOAD_FIELDS=None)
- def test_enable_learner_download_button(self):
- response = self.test_success()
- self.assertEqual(response.context['js_data']['course']['learner_list_download_url'],
- '/api/learner_analytics/v0/learners.csv')
-
- @override_switch('enable_learner_download', active=True)
- @override_settings(LEARNER_API_LIST_DOWNLOAD_FIELDS='username,email')
- def test_enable_learner_download_button_with_fields(self):
- response = self.test_success()
- self.assertEqual(response.context['js_data']['course']['learner_list_download_url'],
- '/api/learner_analytics/v0/learners.csv?fields=username%2Cemail')
-
- @data(Timeout, RequestsConnectionError, ValueError)
- def test_data_api_error(self, RequestExceptionClass):
- learners_payload = {'should_not': 'return this value'}
- course_metadata_payload = learners_payload
- self._register_uris(200, learners_payload, 200, course_metadata_payload)
-
- # False positive https://github.com/PyCQA/pylint/issues/289
- # pylint: disable=bad-continuation
- with mock.patch(
- 'analytics_dashboard.learner_analytics_api.v0.clients.LearnerApiResource.get',
- mock.Mock(side_effect=RequestExceptionClass),
- ):
- with LogCapture(level=logging.ERROR) as lc:
- response = self._get()
- self._assert_context(response, {
- 'learner_list_json': 'Failed to reach the Learner List endpoint',
- 'course_learner_metadata_json': 'Failed to reach the Course Learner Metadata endpoint'
- })
- lc.check(
- (
- 'analytics_dashboard.courses.views.learners',
- 'ERROR',
- 'Failed to reach the Learner List endpoint',
- ),
- (
- 'analytics_dashboard.courses.views.learners',
- 'ERROR',
- 'Failed to reach the Course Learner Metadata endpoint',
- ),
- )
diff --git a/analytics_dashboard/courses/urls.py b/analytics_dashboard/courses/urls.py
index b45deb565..220d0179a 100644
--- a/analytics_dashboard/courses/urls.py
+++ b/analytics_dashboard/courses/urls.py
@@ -10,7 +10,6 @@
csv,
engagement,
enrollment,
- learners,
performance,
)
@@ -120,10 +119,6 @@
url(r'problem_responses/', csv.PerformanceProblemResponseCSV.as_view(), name='performance_problem_responses')
], 'csv')
-LEARNER_URLS = ([
- url(r'^$', learners.LearnersView.as_view(), name='learners'),
-], 'learners')
-
COURSE_URLS = [
# Course homepage. This should be the entry point for other applications linking to the course.
url(r'^$', views.CourseHome.as_view(), name='home'),
@@ -131,7 +126,6 @@
url(r'^engagement/', include(ENGAGEMENT_URLS)),
url(r'^performance/', include(PERFORMANCE_URLS)),
url(r'^csv/', include(CSV_URLS)),
- url(r'^learners/', include(LEARNER_URLS)),
]
app_name = 'courses'
diff --git a/analytics_dashboard/courses/views/__init__.py b/analytics_dashboard/courses/views/__init__.py
index 9457f68ca..ad5136bd1 100644
--- a/analytics_dashboard/courses/views/__init__.py
+++ b/analytics_dashboard/courses/views/__init__.py
@@ -35,9 +35,7 @@
from analytics_dashboard.courses.presenters.performance import CourseReportDownloadPresenter
from analytics_dashboard.courses.serializers import LazyEncoder
from analytics_dashboard.courses.utils import get_page_name, is_feature_enabled
-from analytics_dashboard.courses.waffle import (
- DISPLAY_LEARNER_ANALYTICS, age_available,
-)
+from analytics_dashboard.courses.waffle import age_available
from analytics_dashboard.help.views import ContextSensitiveHelpMixin
logger = logging.getLogger(__name__)
@@ -337,18 +335,6 @@ def get_primary_nav_items(self, request):
'lens': 'performance',
'report': 'graded',
'depth': ''
- },
- {
- 'name': 'learners',
- 'text': ugettext_noop('Learners'),
- 'view': 'courses:learners:learners',
- 'icon': 'fa-users',
- 'flag': 'display_learner_analytics',
- 'fragment': '#?ignore_segments=inactive',
- 'scope': 'course',
- 'lens': 'learners',
- 'report': 'roster',
- 'depth': ''
}
]
@@ -692,44 +678,6 @@ def get_table_items(self):
'items': subitems
})
- if DISPLAY_LEARNER_ANALYTICS.is_enabled():
- items.append({
- 'name': _('Learners'),
- 'icon': 'fa-users',
- 'heading': _('What are individual learners doing?'),
- 'items': [
- {
- 'title': ugettext_noop("Who is engaged? Who isn't?"),
- 'view': 'courses:learners:learners',
- 'breadcrumbs': [_('All Learners')],
- 'fragment': '#?ignore_segments=inactive',
- 'scope': 'course',
- 'lens': 'learners',
- 'report': 'roster',
- 'depth': ''
- },
- # TODO: this is commented out until we complete the deep linking work, AN-6671
- # {
- # 'title': _('Who has been active recently?'),
- # 'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
- # # TODO: what would the breadcrumbs be?
- # 'breadcrumbs': [_('Learners')]
- # },
- # {
- # 'title': _('Who is most engaged in the discussions?'),
- # 'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
- # # TODO: what would the breadcrumbs be?
- # 'breadcrumbs': [_('Learners')]
- # },
- # {
- # 'title': _("Who hasn't watched videos recently?"),
- # 'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
- # # TODO: what would the breadcrumbs be?
- # 'breadcrumbs': [_('Learners')]
- # }
- ]
- })
-
translate_dict_values(items, ('name',))
for item in items:
translate_dict_values(item['items'], ('title',))
@@ -745,22 +693,6 @@ def get_context_data(self, **kwargs):
context['page_data'] = self.get_page_data(context)
- # Some orgs do not wish to allow access to learner analytics.
- # See https://openedx.atlassian.net/browse/DENG-536
- course_org = CourseKey.from_string(self.course_id).org
- if course_org in settings.BLOCK_LEARNER_ANALYTICS_ORG_LIST:
- user = self.request.user.get_username()
- logger.info(
- 'Removing learner analytics from the %s course home page user %s',
- self.course_id, user
- )
- context['primary_nav_items'] = [
- item for item in context['primary_nav_items'] if item['name'] != 'learners'
- ]
- context['table_items'] = [
- item for item in context['table_items'] if item['name'] != _('Learners')
- ]
-
overview_data = []
if self.course_api_enabled:
if switch_is_active('display_course_name_in_nav'):
diff --git a/analytics_dashboard/courses/views/learners.py b/analytics_dashboard/courses/views/learners.py
deleted file mode 100644
index 239823e21..000000000
--- a/analytics_dashboard/courses/views/learners.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import logging
-
-from urllib.parse import urlencode
-from django.conf import settings
-from django.shortcuts import redirect
-from django.urls import reverse
-from django.utils.translation import ugettext_lazy as _
-from requests.exceptions import ConnectionError as RequestsConnectionError
-from requests.exceptions import Timeout
-from waffle import switch_is_active
-
-from analytics_dashboard.courses.views import CourseTemplateWithNavView, AnalyticsV0Mixin
-from analytics_dashboard.courses.waffle import DISPLAY_LEARNER_ANALYTICS
-from analytics_dashboard.learner_analytics_api.v0.clients import LearnerAPIClient
-
-logger = logging.getLogger(__name__)
-
-
-class LearnersView(AnalyticsV0Mixin, CourseTemplateWithNavView):
- template_name = 'courses/learners.html'
- active_primary_nav_item = 'learners'
- page_title = _('Learners')
- page_name = {
- 'scope': 'course',
- 'lens': 'learners',
- 'report': 'roster',
- 'depth': ''
- }
-
- def dispatch(self, request, *args, **kwargs):
- if DISPLAY_LEARNER_ANALYTICS.is_enabled():
- return super().dispatch(request, *args, **kwargs)
- return redirect('/courses')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['js_data']['course'].update({
- 'learner_list_url': reverse('learner_analytics_api:v0:LearnerList'),
- 'course_learner_metadata_url': reverse(
- 'learner_analytics_api:v0:CourseMetadata',
- args=(self.course_id,)
- ),
- 'learner_engagement_timeline_url': reverse(
- 'learner_analytics_api:v0:EngagementTimeline',
- # Unfortunately, we need to pass a username to the `reverse`
- # function. This will get dynamically interpolated with the
- # actual users' usernames on the client side.
- kwargs={'username': 'temporary_username'}
- ),
- })
-
- # Try to prefetch API responses. If anything fails, the front-end will
- # retry the requests and gracefully fail.
- client = LearnerAPIClient()
- for data_name, request_function, error_message in [
- (
- 'learner_list_json',
- lambda: client.learners.get(course_id=self.course_id).json(),
- 'Failed to reach the Learner List endpoint',
- ),
- (
- 'course_learner_metadata_json',
- lambda: client.course_learner_metadata(self.course_id).get().json(),
- 'Failed to reach the Course Learner Metadata endpoint',
- )
- ]:
- try:
- context[data_name] = request_function()
- except (Timeout, RequestsConnectionError, ValueError):
- # ValueError may be thrown by the call to .json()
- logger.exception(error_message)
- context[data_name] = error_message
- context['js_data']['course'].update({
- data_name: context[data_name]
- })
-
- # Only show learner download button(s) if switch is enabled
- if switch_is_active('enable_learner_download'):
- list_download_url = reverse('learner_analytics_api:v0:LearnerListCSV')
-
- # Append the 'fields' parameter if configured
- list_fields = getattr(settings, 'LEARNER_API_LIST_DOWNLOAD_FIELDS', None)
- if list_fields is not None:
- list_download_url = '{}?{}'.format(list_download_url,
- urlencode(dict(fields=list_fields)))
- context['js_data']['course'].update({
- 'learner_list_download_url': list_download_url,
- })
-
- context['page_data'] = self.get_page_data(context)
- return context
diff --git a/analytics_dashboard/courses/waffle.py b/analytics_dashboard/courses/waffle.py
index b68d03ac7..1564818f6 100644
--- a/analytics_dashboard/courses/waffle.py
+++ b/analytics_dashboard/courses/waffle.py
@@ -4,22 +4,7 @@
"""
-from edx_toggles.toggles import NonNamespacedWaffleFlag, SettingToggle
-
-# .. toggle_name: display_learner_analytics
-# .. toggle_implementation: WaffleFlag
-# .. toggle_default: False
-# .. toggle_description: Displays the Learner Analytics tab and links to Learner Analytics. Learner Analytics helps
-# identify which learners are active and engaged and which aren't. It also provides an overview of their daily
-# activity and their enrollment in a course. This was a rollout flag and we recommend you enable this feature.
-# .. toggle_warning: Requires the `ModuleEngagementWorkflowTask` to be run to populate the charts.
-# .. toggle_use_cases: temporary
-# .. toggle_creation_date: 2016-04-15
-# .. toggle_target_removal_date: 2016-07-15
-# .. toggle_tickets: https://github.com/edx/edx-analytics-dashboard/pull/440,
-# https://github.com/edx/edx-analytics-dashboard/pull/522
-DISPLAY_LEARNER_ANALYTICS = NonNamespacedWaffleFlag(
- 'display_learner_analytics', __name__)
+from edx_toggles.toggles import SettingToggle
# .. toggle_name: ENROLLMENT_AGE_AVAILABLE
# .. toggle_implementation: SettingToggle
diff --git a/analytics_dashboard/learner_analytics_api/__init__.py b/analytics_dashboard/learner_analytics_api/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/analytics_dashboard/learner_analytics_api/urls.py b/analytics_dashboard/learner_analytics_api/urls.py
deleted file mode 100644
index 7eea709a1..000000000
--- a/analytics_dashboard/learner_analytics_api/urls.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.conf.urls import include, url
-
-app_name = 'learner_analytics_api'
-urlpatterns = [
- url(r'^v0/', include('learner_analytics_api.v0.urls'))
-]
diff --git a/analytics_dashboard/learner_analytics_api/v0/__init__.py b/analytics_dashboard/learner_analytics_api/v0/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/analytics_dashboard/learner_analytics_api/v0/clients.py b/analytics_dashboard/learner_analytics_api/v0/clients.py
deleted file mode 100644
index 6faf21e8a..000000000
--- a/analytics_dashboard/learner_analytics_api/v0/clients.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import requests
-from django.conf import settings
-from slumber import API, Resource, exceptions, serialize
-
-
-class TokenAuth(requests.auth.AuthBase):
- """A requests auth class for DRF-style token-based authentication"""
- def __init__(self, token):
- self.token = token
-
- def __call__(self, r):
- r.headers['Authorization'] = f'Token {self.token}'
- return r
-
-
-class TextSerializer(serialize.BaseSerializer):
- """
- Slumber API Serializer for text data, e.g. CSV.
- """
- key = 'text'
- content_types = ('text/csv', 'text/plain', )
-
- def unchanged(self, data):
- """Leaves the request/response data unchanged."""
- return data
-
- # Define the abstract methods from BaseSerializer
- dumps = loads = unchanged
-
-
-class LearnerApiResource(Resource):
- """
- Overrides slumber's default behavior of hiding the requests library's
- response object. This allows us to return responses directly from the
- Learner Analytics API to the browser.
- """
- def _request(self, *args, **kwargs):
- # Doesn't hide 400s and 500s, however timeouts will still
- # raise a requests.exceptions.ConnectTimeout.
- try:
- response = super()._request(*args, **kwargs)
- except exceptions.SlumberHttpBaseException as e:
- response = e.response
- return response
-
- def _process_response(self, response):
- response.serialized_content = self._try_to_serialize_response(response)
- return response
-
-
-class LearnerAPIClient(API):
- resource_class = LearnerApiResource
-
- def __init__(self, timeout=5, serializer_type='json'):
- session = requests.session()
- session.timeout = timeout
-
- serializers = serialize.Serializer(
- default=serializer_type,
- serializers=[
- serialize.JsonSerializer(),
- serialize.YamlSerializer(),
- TextSerializer(),
- ]
- )
- super().__init__(
- settings.DATA_API_URL,
- session=session,
- auth=TokenAuth(settings.DATA_API_AUTH_TOKEN),
- serializer=serializers,
- )
diff --git a/analytics_dashboard/learner_analytics_api/v0/permissions.py b/analytics_dashboard/learner_analytics_api/v0/permissions.py
deleted file mode 100644
index 2ead0767d..000000000
--- a/analytics_dashboard/learner_analytics_api/v0/permissions.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import logging
-
-from rest_framework.permissions import BasePermission
-
-from analytics_dashboard.courses.exceptions import PermissionsRetrievalFailedError
-from analytics_dashboard.courses.permissions import user_can_view_course
-
-logger = logging.getLogger(__name__)
-
-
-def user_can_view_learners_in_course(user, course_id):
- """
- Returns whether or not a user can access a particular course within the
- learner API.
- """
- try:
- user_has_permission = user_can_view_course(user, course_id)
- except PermissionsRetrievalFailedError:
- logger.exception(
- "Unable to retrieve course permissions for username=%s in v0",
- user.username,
- )
- user_has_permission = False
- return user_has_permission
-
-
-class HasCourseAccessPermission(BasePermission):
- """
- Enforces that the requesting user has course access permissions. Requires
- that view classes have a course_id property for the requested course_id.
- """
- def has_permission(self, request, view):
- return user_can_view_learners_in_course(request.user, view.course_id)
diff --git a/analytics_dashboard/learner_analytics_api/v0/renderers.py b/analytics_dashboard/learner_analytics_api/v0/renderers.py
deleted file mode 100644
index 13192a41c..000000000
--- a/analytics_dashboard/learner_analytics_api/v0/renderers.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
-Custom Django REST Framework rendererers used by the learner analytics API views.
-"""
-
-from rest_framework.renderers import BaseRenderer
-
-
-class TextRenderer(BaseRenderer):
- """Renders the REST response data without modification."""
-
- def render(self, data, *_args, **_kwargs):
- """Return the data unchanged."""
- return data
diff --git a/analytics_dashboard/learner_analytics_api/v0/tests/__init__.py b/analytics_dashboard/learner_analytics_api/v0/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/analytics_dashboard/learner_analytics_api/v0/tests/test_views.py b/analytics_dashboard/learner_analytics_api/v0/tests/test_views.py
deleted file mode 100644
index 60f17e992..000000000
--- a/analytics_dashboard/learner_analytics_api/v0/tests/test_views.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import json
-
-import unittest.mock as mock
-import ddt
-import httpretty
-from django.conf import settings
-from django.test import TestCase
-from edx_toggles.toggles.testutils import override_waffle_flag
-from requests.exceptions import ConnectTimeout
-
-from analytics_dashboard.core.tests.test_views import UserTestCaseMixin
-from analytics_dashboard.courses.tests.test_views import PermissionsTestMixin
-from analytics_dashboard.courses.waffle import (
- DISPLAY_LEARNER_ANALYTICS,
-)
-
-
-@ddt.ddt
-class LearnerAPITestMixin(UserTestCaseMixin, PermissionsTestMixin):
- """
- Provides test cases and helper methods for learner analytics api test
- classes.
-
- Subclasses must override the following properties:
-
- - endpoint (str): the part of the url following
- '/api/learner_analytics/v0' which identifies the resource, e.g.
- '/learners/'
- - required_query_params (dict): a dict of querystring parameter keys
- and values which are required to make a valid request against the
- endpoint
- - no_permissions_status_code (int): the status code expected to be
- returned when the user is authenticated but does not have permission
- to access the endpoint
- """
- endpoint = ''
- required_query_params = {}
- no_permissions_status_code = None
- content_type = 'application/json'
-
- @property
- def remote_endpoint(self):
- '''By default, the remote API endpoint matches the local API endpoint.'''
- return self.endpoint
-
- def assert_response_equals(self, response, expected_status_code, expected_body=None):
- self.assertEqual(response.status_code, expected_status_code)
- if expected_body is not None:
- self.assertEqual(json.loads(response.content.decode()), expected_body)
-
- def test_not_authenticated(self):
- response = self.client.get('/api/learner_analytics/v0' + self.endpoint, self.required_query_params)
- self.assert_response_equals(response, 403, {'detail': 'Authentication credentials were not provided.'})
-
- def test_no_course_permissions(self):
- self.login()
- self.grant_permission(self.user, [])
- response = self.client.get('/api/learner_analytics/v0' + self.endpoint, self.required_query_params)
- self.assert_response_equals(response, self.no_permissions_status_code)
-
- @mock.patch('learner_analytics_api.v0.clients.LearnerApiResource._request', mock.Mock(side_effect=ConnectTimeout))
- def test_timeout(self):
- self.login()
- self.grant_permission(self.user, 'edX/DemoX/Demo_Course')
- response = self.client.get('/api/learner_analytics/v0' + self.endpoint, self.required_query_params)
- self.assertEqual(response.status_code, 504)
-
- @ddt.data((200, {'test': 'value'}), (400, {'a': 'b', 'c': 'd'}), (500, {}))
- @ddt.unpack
- @httpretty.activate
- def test_authenticated_and_authorized(self, status_code, body):
- self.login()
- course_id = 'edX/DemoX/Demo_Course'
- self.grant_permission(self.user, course_id)
- httpretty.register_uri(
- httpretty.GET, settings.DATA_API_URL + self.remote_endpoint, body=json.dumps(body), status=status_code,
- content_type=self.content_type,
- )
- response = self.client.get('/api/learner_analytics/v0' + self.endpoint, self.required_query_params)
- self.assert_response_equals(response, status_code, body)
-
-
-@override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=True)
-class LearnerDetailViewTestCase(LearnerAPITestMixin, TestCase):
- endpoint = '/learners/username/'
- required_query_params = {'course_id': 'edX/DemoX/Demo_Course'}
- no_permissions_status_code = 404
-
- def test_no_course_id_provided(self):
- self.login()
- self.grant_permission(self.user, 'edX/DemoX/Demo_Course')
- response = self.client.get('/api/learner_analytics/v0/learners/username/')
- self.assert_response_equals(response, 404, {
- 'developer_message': 'Learner username not found.', 'error_code': 'no_learner_for_course'
- })
-
-
-@override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=True)
-class LearnerListViewTestCase(LearnerAPITestMixin, TestCase):
- endpoint = '/learners/'
- required_query_params = {'course_id': 'edX/DemoX/Demo_Course'}
- no_permissions_status_code = 403
-
- def test_no_course_id_provided(self):
- self.login()
- self.grant_permission(self.user, 'edX/DemoX/Demo_Course')
- response = self.client.get('/api/learner_analytics/v0/learners/')
- self.assert_response_equals(response, 403, {'detail': 'You do not have permission to perform this action.'})
-
-
-@ddt.ddt
-class LearnerListCSVTestCase(LearnerListViewTestCase):
- endpoint = '/learners.csv'
- remote_endpoint = '/learners/'
- content_type = 'text/csv'
- course_id = 'edX/DemoX/Demo_Course'
-
- @httpretty.activate
- def test_headers(self):
- self.login()
- self.grant_permission(self.user, self.course_id)
-
- # Ensure the extra headers from the remote endpoint get passed through to the response.
- content_disposition = 'attachment; filename=learners.csv'
- httpretty.register_uri(
- httpretty.GET, settings.DATA_API_URL + self.remote_endpoint, body='body',
- status=200, content_type=self.content_type, adding_headers={
- 'Content-Disposition': content_disposition,
- },
- )
- response = self.client.get('/api/learner_analytics/v0' + self.endpoint, self.required_query_params)
- self.assertEqual(response['Content-Disposition'], content_disposition)
-
-
-@override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=True)
-class EngagementTimelinesViewTestCase(LearnerAPITestMixin, TestCase):
- endpoint = '/engagement_timelines/username/'
- required_query_params = {'course_id': 'edX/DemoX/Demo_Course'}
- no_permissions_status_code = 404
-
- def test_no_course_id_provided(self):
- self.login()
- self.grant_permission(self.user, 'edX/DemoX/Demo_Course')
- response = self.client.get('/api/learner_analytics/v0/engagement_timelines/username/')
- self.assert_response_equals(response, 404, {
- 'developer_message': 'Learner username engagement timeline not found.',
- 'error_code': 'no_learner_engagement_timeline'
- })
-
-
-@override_waffle_flag(DISPLAY_LEARNER_ANALYTICS, active=True)
-class CourseLearnerMetadataViewTestCase(LearnerAPITestMixin, TestCase):
- endpoint = '/course_learner_metadata/edX/DemoX/Demo_Course/'
- no_permissions_status_code = 403
diff --git a/analytics_dashboard/learner_analytics_api/v0/urls.py b/analytics_dashboard/learner_analytics_api/v0/urls.py
deleted file mode 100644
index 890e1af72..000000000
--- a/analytics_dashboard/learner_analytics_api/v0/urls.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.conf import settings
-from django.conf.urls import url
-
-from . import views
-
-USERNAME_PATTERN = r'(?P