Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion lms/djangoapps/instructor/tests/views/test_api_v2.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
"""
Tests for Instructor API v2 endpoints.
"""
import json
from textwrap import dedent
from unittest.mock import MagicMock, patch
from unittest.mock import patch as patch_filter
from uuid import uuid4

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient

from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, InstructorFactory, UserFactory
from lms.djangoapps.courseware.models import StudentModule
from lms.djangoapps.instructor_task.models import InstructorTask
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory

Check failure on line 18 in lms/djangoapps/instructor/tests/views/test_api_v2.py

View workflow job for this annotation

GitHub Actions / Quality Others (ubuntu-24.04, 3.12, 20)

ruff (I001)

lms/djangoapps/instructor/tests/views/test_api_v2.py:4:1: I001 Import block is un-sorted or un-formatted help: Organize imports


class LearnerViewTestCase(ModuleStoreTestCase):
Expand Down Expand Up @@ -692,3 +692,37 @@
format='json',
)
assert response.status_code == status.HTTP_400_BAD_REQUEST


class CourseMetadataViewTestCase(ModuleStoreTestCase):
"""
Tests for GET /api/instructor/v2/courses/{course_id} with InstructorDashboardTabsRequested filter.
"""
def setUp(self):
super().setUp()
self.client = APIClient()
self.course = CourseFactory.create()
self.instructor = InstructorFactory.create(course_key=self.course.id)
self.client.force_authenticate(user=self.instructor)

def test_tabs_filter_adds_custom_tab(self):
"""Test that a filter can add a custom tab to the tabs list."""
custom_tab = {
"tab_id": "custom",
"title": "Custom Tab",
"url": "/custom/123",
"sort_order": 999,
}
# Patch the filter to add a custom tab
with patch_filter("openedx_filters.learning.filters.InstructorDashboardTabsRequested.run_filter") as mock_filter:
def filter_side_effect(tabs, user, course_key):
return tabs + [custom_tab]
mock_filter.side_effect = filter_side_effect

url = reverse("instructor_api_v2:course_metadata", kwargs={"course_id": str(self.course.id)})
response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK
data = response.json()
tab_ids = [tab["tab_id"] for tab in data["tabs"]]
required_tabs = {"course_info", "custom"}
assert required_tabs.issubset(set(tab_ids)), f"Missing required tabs: {required_tabs - set(tab_ids)}"
17 changes: 15 additions & 2 deletions lms/djangoapps/instructor/views/serializers_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.utils.html import escape
from django.utils.translation import gettext as _
from edx_when.api import is_enabled_for_course
from openedx_filters.learning.filters import InstructorDashboardTabsRequested
from rest_framework import serializers

from common.djangoapps.course_modes.models import CourseMode
Expand Down Expand Up @@ -305,6 +306,19 @@ def get_tabs(self, data):
'sort_order': 110,
})

try:
# .. filter_implemented_name: InstructorDashboardTabsRequested
# .. filter_type: org.openedx.learning.instructor.dashboard.tabs.requested.v1
filtered_tabs = InstructorDashboardTabsRequested.run_filter(
tabs=tabs,
user=request.user,
course_key=course_key
)
return filtered_tabs if filtered_tabs is not None else tabs
except InstructorDashboardTabsRequested.PreventTabsGeneration as exc:
# Plugin provided custom tabs or prevented tab generation
custom_tabs = getattr(exc, 'tabs', [])

# We provide the tabs in a specific order based on how it was
# historically presented in the frontend. The frontend can use
# this info or choose to ignore the ordering.
Expand All @@ -322,8 +336,7 @@ def get_tabs(self, data):
'special_exams',
]
order_index = {tab: i for i, tab in enumerate(tabs_order)}
tabs = sorted(tabs, key=lambda x: order_index.get(x['tab_id'], float("inf")))
return tabs
return sorted(custom_tabs, key=lambda x: order_index.get(x['tab_id'], float("inf")))

def get_course_id(self, data):
"""Get course ID as string."""
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.3.0
# via
# -r requirements/edx/kernel.in
# edx-enterprise
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.3.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.3.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.3.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
Loading