diff --git a/lms/models/__init__.py b/lms/models/__init__.py index a7083904de..f9864dded8 100644 --- a/lms/models/__init__.py +++ b/lms/models/__init__.py @@ -39,6 +39,7 @@ LMSCourseMembership, ) from lms.models.lms_segment import LMSSegment, LMSSegmentMembership +from lms.models.lms_term import LMSTerm from lms.models.lms_user import LMSUser, LMSUserApplicationInstance from lms.models.lti_params import CLAIM_PREFIX, LTIParams from lms.models.lti_registration import LTIRegistration diff --git a/lms/models/lms_course.py b/lms/models/lms_course.py index fd6e319bcf..5d00b88de8 100644 --- a/lms/models/lms_course.py +++ b/lms/models/lms_course.py @@ -18,7 +18,7 @@ from lms.models._mixins import CreatedUpdatedMixin if TYPE_CHECKING: - from lms.models import LMSUser, LTIRole + from lms.models import LMSTerm, LMSUser, LTIRole class LMSCourse(CreatedUpdatedMixin, Base): @@ -58,6 +58,11 @@ class LMSCourse(CreatedUpdatedMixin, Base): ends_at: Mapped[datetime | None] = mapped_column() """The end date of the course. Only for when we get this information directly from the LMS""" + lms_term_id: Mapped[int | None] = mapped_column( + sa.ForeignKey("lms_term.id", ondelete="cascade"), index=True + ) + lms_term: Mapped["LMSTerm"] = relationship() + class LMSCourseApplicationInstance(CreatedUpdatedMixin, Base): """Record of on which installs (application instances) we have seen one course.""" diff --git a/lms/models/lti_params.py b/lms/models/lti_params.py index 5a4d27f0c1..b15398bb74 100644 --- a/lms/models/lti_params.py +++ b/lms/models/lti_params.py @@ -1,4 +1,7 @@ import logging +from datetime import datetime + +from dateutil import parser CLAIM_PREFIX = "https://purl.imsglobal.org/spec/lti/claim" @@ -26,6 +29,12 @@ def __init__(self, v11: dict, v13: dict | None = None): def v11(self): return self + def get_datetime(self, key: str) -> datetime | None: + try: + return parser.isoparse(self.get(key)) + except (TypeError, ValueError): + return None + @classmethod def from_request(cls, request): """Create an LTIParams from the request.""" diff --git a/lms/services/__init__.py b/lms/services/__init__.py index 41813936ec..0dd1c75ed1 100644 --- a/lms/services/__init__.py +++ b/lms/services/__init__.py @@ -28,6 +28,7 @@ LTILaunchVerificationError, LTIOAuthError, ) +from lms.services.lms_term import LMSTermService from lms.services.lti_grading import LTIGradingService from lms.services.lti_names_roles import LTINamesRolesService from lms.services.lti_registration import LTIRegistrationService @@ -165,6 +166,9 @@ def includeme(config): # noqa: PLR0915 config.register_service_factory( "lms.services.auto_grading.factory", iface=AutoGradingService ) + config.register_service_factory( + "lms.services.lms_term.factory", iface=LMSTermService + ) # Plugins are not the same as top level services but we want to register them as pyramid services too # Importing them here to: diff --git a/lms/services/course.py b/lms/services/course.py index 129a8135fb..a7fcbda4ad 100644 --- a/lms/services/course.py +++ b/lms/services/course.py @@ -1,9 +1,6 @@ import json from copy import deepcopy -from datetime import datetime -from typing import Mapping -from dateutil import parser from sqlalchemy import Select, select, union from lms.db import full_text_match @@ -28,14 +25,22 @@ ) from lms.product.family import Family from lms.services.grouping import GroupingService +from lms.services.lms_term import LMSTermService from lms.services.upsert import bulk_upsert class CourseService: - def __init__(self, db, application_instance, grouping_service: GroupingService): + def __init__( + self, + db, + application_instance, + grouping_service: GroupingService, + lms_term_service: LMSTermService, + ): self._db = db self._application_instance = application_instance self._grouping_service = grouping_service + self._lms_term_service = lms_term_service def any_with_setting(self, group, key, value=True) -> bool: """ @@ -314,7 +319,10 @@ def _upsert_lms_course(self, course: Course, lti_params: LTIParams) -> LMSCourse "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice", {} ).get("context_memberships_url") - course_starts_at, course_ends_at = self._get_course_dates(lti_params) + course_starts_at = lti_params.get_datetime("custom_curse_starts") + course_ends_at = lti_params.get_datetime("custom_course_ends") + + lms_term = self._lms_term_service.get_term(lti_params) lms_api_course_id = self._get_api_id_from_launch(lti_params) lms_course = bulk_upsert( @@ -330,6 +338,7 @@ def _upsert_lms_course(self, course: Course, lti_params: LTIParams) -> LMSCourse "starts_at": course_starts_at, "ends_at": course_ends_at, "lms_api_course_id": lms_api_course_id, + "lms_term_id": lms_term.id if lms_term else None, } ], index_elements=["h_authority_provided_id"], @@ -340,6 +349,7 @@ def _upsert_lms_course(self, course: Course, lti_params: LTIParams) -> LMSCourse "starts_at", "ends_at", "lms_api_course_id", + "lms_term_id", ], ).one() bulk_upsert( @@ -431,22 +441,6 @@ def _get_copied_from_course(self, lti_params) -> Course | None: return None - def _get_course_dates( - self, lti_params: Mapping - ) -> tuple[datetime | None, datetime | None]: - """Get the dates for the current curse, None if not available.""" - try: - course_starts_at = parser.isoparse(lti_params.get("custom_course_starts")) - except (TypeError, ValueError): - course_starts_at = None - - try: - course_ends_at = parser.isoparse(lti_params.get("custom_course_ends")) - except (TypeError, ValueError): - course_ends_at = None - - return course_starts_at, course_ends_at - def _get_api_id_from_launch(self, lti_params: LTIParams) -> str | None: """Get the API ID from the launch params. @@ -464,4 +458,5 @@ def course_service_factory(_context, request): request.lti_user.application_instance if request.lti_user else None ), grouping_service=request.find_service(name="grouping"), + lms_term_service=request.find_service(LMSTermService), )