diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e7d106b2a..611239484 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: check-toml - id: debug-statements - repo: https://github.com/scop/pre-commit-shfmt - rev: v3.10.0-1 + rev: v3.10.0-2 hooks: - id: shfmt - repo: https://github.com/adrienverge/yamllint.git @@ -51,7 +51,7 @@ repos: - --exclude-files - "_test.js$" - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.7.4" + rev: "v0.8.1" hooks: - id: ruff-format - id: ruff diff --git a/RELEASE.rst b/RELEASE.rst index 6734405a8..adb0c50e4 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,12 @@ Release Notes ============= +Version 0.164.3 +--------------- + +- feat: add emeritus api list view (#3329) +- [pre-commit.ci] pre-commit autoupdate (#3326) + Version 0.164.2 (Released December 02, 2024) --------------- diff --git a/authentication/middleware.py b/authentication/middleware.py index f0d517f32..bc58a756d 100644 --- a/authentication/middleware.py +++ b/authentication/middleware.py @@ -30,7 +30,7 @@ def process_exception(self, request, exception): url = self.get_redirect_uri(request, exception) if url: # noqa: RET503 - url += ("?" in url and "&" or "?") + "message={}&backend={}".format( # noqa: UP032 + url += (("?" in url and "&") or "?") + "message={}&backend={}".format( # noqa: UP032 quote(message), backend_name ) return redirect(url) diff --git a/b2b_ecommerce/api_test.py b/b2b_ecommerce/api_test.py index 373317c3b..e6b099798 100644 --- a/b2b_ecommerce/api_test.py +++ b/b2b_ecommerce/api_test.py @@ -31,7 +31,7 @@ @pytest.fixture(autouse=True) -def cybersource_settings(settings): # noqa: PT004 +def cybersource_settings(settings): """ Set cybersource settings """ diff --git a/b2b_ecommerce/views_test.py b/b2b_ecommerce/views_test.py index 80122326a..fab0bf4ef 100644 --- a/b2b_ecommerce/views_test.py +++ b/b2b_ecommerce/views_test.py @@ -33,7 +33,7 @@ @pytest.fixture(autouse=True) -def ecommerce_settings(settings): # noqa: PT004 +def ecommerce_settings(settings): """ Set cybersource settings """ diff --git a/conftest.py b/conftest.py index 06ac190c0..e91030dd6 100644 --- a/conftest.py +++ b/conftest.py @@ -65,7 +65,7 @@ def pytest_configure(config): @pytest.fixture(scope="session", autouse=True) -def clean_up_files(): # noqa: PT004 +def clean_up_files(): """ Fixture that removes the media root folder after the suite has finished running, effectively deleting any files that were created by factories over the course of the test suite. @@ -76,7 +76,7 @@ def clean_up_files(): # noqa: PT004 @pytest.fixture(scope="session") -def django_db_setup(django_db_setup, django_db_blocker): # noqa: ARG001, PT004 +def django_db_setup(django_db_setup, django_db_blocker): # noqa: ARG001 """ Creates all the index pages during the tests setup as index pages are required by the factories. """ diff --git a/courses/urls.py b/courses/urls.py index 83f18942d..a0aae1d17 100644 --- a/courses/urls.py +++ b/courses/urls.py @@ -4,6 +4,7 @@ from rest_framework import routers from courses.views import v1 +from courses.views.v1 import EmeritusCourseListView router = routers.SimpleRouter() router.register(r"programs", v1.ProgramViewSet, basename="programs_api") @@ -29,4 +30,9 @@ re_path( r"^api/enrollments/", v1.UserEnrollmentsView.as_view(), name="user-enrollments" ), + path( + "api/emeritus_courses/", + EmeritusCourseListView.as_view(), + name="emeritus_courses", + ), ] diff --git a/courses/views/v1/__init__.py b/courses/views/v1/__init__.py index 0ee5dd328..951d197c2 100644 --- a/courses/views/v1/__init__.py +++ b/courses/views/v1/__init__.py @@ -4,7 +4,7 @@ from mitol.digitalcredentials.mixins import DigitalCredentialsRequestViewSetMixin from rest_framework import status, viewsets from rest_framework.authentication import SessionAuthentication -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -27,6 +27,7 @@ ProgramEnrollmentSerializer, ProgramSerializer, ) +from courses.sync_external_courses.emeritus_api import fetch_emeritus_courses from ecommerce.models import Product @@ -202,3 +203,24 @@ def get_queryset(self): Returns parent topics with course count > 0. """ return CourseTopic.parent_topics_with_courses() + + +class EmeritusCourseListView(APIView): + """ + ReadOnly View to list Emeritus courses. + """ + + permission_classes = [IsAdminUser] + + def get(self, request, *args, **kwargs): # noqa: ARG002 + """ + Get Emeritus courses list from the Emeritus API and return it. + """ + try: + data = fetch_emeritus_courses() + return Response(data, status=status.HTTP_200_OK) + except Exception as e: # noqa: BLE001 + return Response( + {"error": "Some error occurred.", "details": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) diff --git a/courses/views_test.py b/courses/views_test.py index 2c735eac6..b252fbc01 100644 --- a/courses/views_test.py +++ b/courses/views_test.py @@ -2,8 +2,10 @@ Tests for course views """ +import json import operator as op from datetime import timedelta +from pathlib import Path import pytest from django.contrib.auth.models import AnonymousUser @@ -611,3 +613,33 @@ def test_course_topics_api(client, django_assert_num_queries): assert len(resp_json) == 1 assert resp_json[0]["name"] == parent_topic.name assert resp_json[0]["course_count"] == 4 + + +@pytest.mark.parametrize("expected_status_code", [200, 500]) +def test_emeritus_course_list_view(admin_drf_client, mocker, expected_status_code): + """ + Test that the Emeritus API List calls fetch_emeritus_courses and returns its mocked response. + """ + if expected_status_code == 200: + with Path( + "courses/sync_external_courses/test_data/batch_test.json" + ).open() as test_data_file: + mocked_response = json.load(test_data_file)["rows"] + + patched_fetch_emeritus_courses = mocker.patch( + "courses.views.v1.fetch_emeritus_courses", return_value=mocked_response + ) + else: + patched_fetch_emeritus_courses = mocker.patch( + "courses.views.v1.fetch_emeritus_courses", + side_effect=Exception("Some error occurred."), + ) + mocked_response = { + "error": "Some error occurred.", + "details": "Some error occurred.", + } + + response = admin_drf_client.get(reverse("emeritus_courses")) + assert response.json() == mocked_response + assert response.status_code == expected_status_code + patched_fetch_emeritus_courses.assert_called_once() diff --git a/ecommerce/api_test.py b/ecommerce/api_test.py index 31eaf3f67..f1c10cf84 100644 --- a/ecommerce/api_test.py +++ b/ecommerce/api_test.py @@ -117,7 +117,7 @@ @pytest.fixture(autouse=True) -def cybersource_settings(settings): # noqa: PT004 +def cybersource_settings(settings): """ Set cybersource settings """ diff --git a/ecommerce/views_test.py b/ecommerce/views_test.py index 6f3be8076..c38380223 100644 --- a/ecommerce/views_test.py +++ b/ecommerce/views_test.py @@ -92,7 +92,7 @@ def render_json(serializer): @pytest.fixture(autouse=True) -def ecommerce_settings(settings): # noqa: PT004 +def ecommerce_settings(settings): """ Set cybersource settings """ diff --git a/fixtures/autouse.py b/fixtures/autouse.py index 48859038f..819e8c8ba 100644 --- a/fixtures/autouse.py +++ b/fixtures/autouse.py @@ -4,6 +4,6 @@ @pytest.fixture(autouse=True) -def disable_hubspot_api(settings): # noqa: PT004 +def disable_hubspot_api(settings): """Disable Hubspot API by default for tests""" settings.MITOL_HUBSPOT_API_PRIVATE_TOKEN = None diff --git a/fixtures/common.py b/fixtures/common.py index 0dc6a3095..b74334dfc 100644 --- a/fixtures/common.py +++ b/fixtures/common.py @@ -107,7 +107,7 @@ def valid_address_dict(): @pytest.fixture -def nplusone_fail(settings): # noqa: PT004 +def nplusone_fail(settings): """Configures the nplusone app to raise errors""" settings.NPLUSONE_RAISE = True diff --git a/hubspot_xpro/api.py b/hubspot_xpro/api.py index ac8344ee9..1311f7c19 100644 --- a/hubspot_xpro/api.py +++ b/hubspot_xpro/api.py @@ -421,7 +421,7 @@ def get_hubspot_id_for_object( return hubspot_obj.id elif raise_error: raise ValueError( - "Hubspot id could not be found for %s for id %d" + "Hubspot id could not be found for %s for id %d" # noqa: UP031 % (content_type.name, obj.id) ) diff --git a/mail/api_test.py b/mail/api_test.py index 46632e197..696fba0e3 100644 --- a/mail/api_test.py +++ b/mail/api_test.py @@ -24,7 +24,7 @@ @pytest.fixture -def email_settings(settings): # noqa: PT004 +def email_settings(settings): """Default settings for email tests""" settings.MAILGUN_RECIPIENT_OVERRIDE = None diff --git a/mitxpro/settings.py b/mitxpro/settings.py index db66536d4..f506830a1 100644 --- a/mitxpro/settings.py +++ b/mitxpro/settings.py @@ -26,7 +26,7 @@ from mitxpro.celery_utils import OffsettingSchedule from mitxpro.sentry import init_sentry -VERSION = "0.164.2" +VERSION = "0.164.3" env.reset() diff --git a/sheets/conftest.py b/sheets/conftest.py index f681f7176..6ea01c306 100644 --- a/sheets/conftest.py +++ b/sheets/conftest.py @@ -4,7 +4,7 @@ @pytest.fixture(autouse=True) -def sheets_settings(settings): # noqa: PT004 +def sheets_settings(settings): """Default settings for sheets tests""" settings.FEATURES["COUPON_SHEETS"] = True settings.SHEETS_REQ_EMAIL_COL = 7 diff --git a/sheets/coupon_request_api_test.py b/sheets/coupon_request_api_test.py index 59970b40b..b1d790d15 100644 --- a/sheets/coupon_request_api_test.py +++ b/sheets/coupon_request_api_test.py @@ -19,7 +19,7 @@ @pytest.fixture -def courseware_objects(): # noqa: PT004 +def courseware_objects(): """Database objects that CSV data depends on""" run = CourseRunFactory.create(courseware_id="course-v1:edX+DemoX+Demo_Course") ProductVersionFactory.create(product__content_object=run)