From c7db817100b93728ead46d711587867ff57978af Mon Sep 17 00:00:00 2001 From: Kris Jordan Date: Fri, 9 Aug 2024 11:59:05 -0400 Subject: [PATCH] Add API to create missing course sites (#573) --- backend/api/academics/hiring.py | 12 ++++++ backend/entities/academics/section_entity.py | 9 +++++ backend/entities/user_entity.py | 9 +++++ backend/services/academics/hiring.py | 37 +++++++++++++++++++ .../services/academics/hiring/hiring_test.py | 13 +++++++ 5 files changed, 80 insertions(+) diff --git a/backend/api/academics/hiring.py b/backend/api/academics/hiring.py index 4c3f4e314..8568e9e4b 100644 --- a/backend/api/academics/hiring.py +++ b/backend/api/academics/hiring.py @@ -113,6 +113,18 @@ def update_hiring_level( return hiring_service.update_hiring_level(subject, level) +@api.post("/create_sites", tags=["Hiring"]) +def create_missing_course_sites_for_term( + term_id: str, + subject: User = Depends(registered_user), + hiring_service: HiringService = Depends(), +) -> bool: + """ + Creates missing course sites for the term + """ + return hiring_service.create_missing_course_sites_for_term(subject, term_id) + + @api.get("/{course_site_id}", tags=["Hiring"]) def get_status( course_site_id: int, diff --git a/backend/entities/academics/section_entity.py b/backend/entities/academics/section_entity.py index 7fd3ffc68..469d33516 100644 --- a/backend/entities/academics/section_entity.py +++ b/backend/entities/academics/section_entity.py @@ -82,6 +82,15 @@ class SectionEntity(EntityBase): primaryjoin="and_(SectionEntity.id==SectionRoomEntity.section_id, SectionRoomEntity.assignment_type=='OFFICE_HOURS')", ) + def get_title(self) -> str: + """ + Returns the title of the section, either the override title or the course title + + Returns: + str: Title of the section + """ + return self.override_title if self.override_title else self.course.title + # Members of the course members: Mapped[list["SectionMemberEntity"]] = relationship( back_populates="section", cascade="delete" diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index f59ff8539..daeb4ae5d 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -89,6 +89,15 @@ class UserEntity(EntityBase): back_populates="user" ) + def full_name(self) -> str: + """ + Returns the full name of the user. + + Returns: + str: The full name of the user. + """ + return f"{self.first_name} {self.last_name}" + @classmethod def from_model(cls, model: User) -> Self: """ diff --git a/backend/services/academics/hiring.py b/backend/services/academics/hiring.py index 51be09327..f04e4e42c 100644 --- a/backend/services/academics/hiring.py +++ b/backend/services/academics/hiring.py @@ -144,6 +144,43 @@ def update_status( # Reload the data and return the hiring status. return self.get_status(subject, course_site_id) + def create_missing_course_sites_for_term(self, subject: User, term_id: str) -> bool: + """ + Creates missing course sites for a given term. + """ + self._permission.enforce( + subject, "hiring.create_missing_course_sites_for_term", f"course_sites/*" + ) + + # Get a list of all sections that are not associated with course sites + section_query = select(SectionEntity).where( + SectionEntity.course_site_id.is_(None) + ) + joint: dict[tuple[str, str], list[SectionEntity]] = {} + for section in self._session.scalars(section_query).all(): + instructors = [ + section_member.user.full_name() + for section_member in section.members + if section_member.member_role == RosterRole.INSTRUCTOR + ] + key = (f"{section.course_id}", str(instructors)) + if key not in joint: + joint[key] = [] + joint[key].append(section) + + # Create a course site for each group of sections + for key, sections in joint.items(): + course_site = CourseSiteEntity( + term_id=term_id, + title=sections[0].get_title(), + ) + for section in sections: + section.course_site = course_site + self._session.add(course_site) + + self._session.commit() + return True + def _load_course_site(self, course_site_id: int) -> CourseSiteEntity: """ Loads a course site given a subject and course site ID. diff --git a/backend/test/services/academics/hiring/hiring_test.py b/backend/test/services/academics/hiring/hiring_test.py index cce8e09da..cf76a9e84 100644 --- a/backend/test/services/academics/hiring/hiring_test.py +++ b/backend/test/services/academics/hiring/hiring_test.py @@ -18,9 +18,11 @@ ) from .....services.academics import HiringService from .....services.application import ApplicationService +from .....services.academics.course_site import CourseSiteService # Injected Service Fixtures from .fixtures import hiring_svc +from ..course_site_test import course_site_svc # Import the setup_teardown fixture explicitly to load entities in database from ...core_data import setup_insert_data_fixture as insert_order_0 @@ -281,3 +283,14 @@ def test_update_hiring_level_not_found(hiring_svc: HiringService): with pytest.raises(ResourceNotFoundException): hiring_svc.update_hiring_level(user_data.root, hiring_data.new_level) pytest.fail() + + +def test_create_missing_course_sites_for_term( + hiring_svc: HiringService, course_site_svc: CourseSiteService +): + user = user_data.root + term = term_data.current_term + overview_pre = hiring_svc.get_hiring_admin_overview(user, term.id) + hiring_svc.create_missing_course_sites_for_term(user, term.id) + overview_post = hiring_svc.get_hiring_admin_overview(user, term.id) + assert len(overview_post.sites) > len(overview_pre.sites)