Skip to content

Commit

Permalink
Academics Hotfix - Section Admin Dropdown, Section and Course Query O…
Browse files Browse the repository at this point in the history
…ptimization (#510)

* Fix Academics Editors

* Implement Adding Instructors Frontend

* Create CatalogSection Model

* Change Term Services to return Term instead of TermDetails

* Update Frontend to Reflect Changes, Fix Section Dropdown

* Add Multiple Instructors in Table

* Ensure Unknown Appears if No Instructors Set

* Fix Dropdown in Section Admin Page

* Add Joined Load to Section Query, Return Course Model from Get All

* Change IDs for Active Terms

* Remove double joined load

* Add Joined Load to Room Entity

* Modify Room Joined Load to work with lecture rooms
  • Loading branch information
ajaygandecha authored Jul 2, 2024
1 parent 7d959ab commit 0399a78
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 38 deletions.
6 changes: 3 additions & 3 deletions backend/api/academics/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
}


@api.get("", response_model=list[CourseDetails], tags=["Academics"])
def get_courses(course_service: CourseService = Depends()) -> list[CourseDetails]:
@api.get("", tags=["Academics"])
def get_courses(course_service: CourseService = Depends()) -> list[Course]:
"""
Get all courses
Returns:
list[CourseDetails]: All `Course`s in the `Course` database table
list[Course]: All `Course`s in the `Course` database table
"""
return course_service.all()

Expand Down
4 changes: 2 additions & 2 deletions backend/services/academics/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(
self._session = session
self._permission_svc = permission_svc

def all(self) -> list[CourseDetails]:
def all(self) -> list[Course]:
"""Retrieves all courses from the table
Returns:
Expand All @@ -45,7 +45,7 @@ def all(self) -> list[CourseDetails]:
entities = self._session.scalars(query).all()

# Convert entries to a model and return
return [entity.to_details_model() for entity in entities]
return [entity.to_model() for entity in entities]

def get_by_id(self, id: str) -> CourseDetails:
"""Gets the course from the table for an id.
Expand Down
13 changes: 10 additions & 3 deletions backend/services/academics/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from fastapi import Depends
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload
from pydantic import BaseModel

from ...database import db_session
Expand Down Expand Up @@ -62,8 +62,15 @@ def get_by_term(self, term_id: str) -> list[CatalogSection]:
select(SectionEntity)
.where(SectionEntity.term_id == term_id)
.order_by(SectionEntity.course_id, SectionEntity.number)
.options(
joinedload(SectionEntity.members).joinedload(SectionMemberEntity.user),
joinedload(SectionEntity.lecture_rooms).joinedload(
SectionRoomEntity.room
),
joinedload(SectionEntity.course),
)
)
entities = self._session.scalars(query).all()
entities = self._session.scalars(query).unique().all()

# Return the model
return [entity.to_catalog_model() for entity in entities]
Expand Down Expand Up @@ -276,7 +283,7 @@ def update_enrollment_totals(self, subject: User):
# Currently active terms.
# This is hard-coded based on the availability and representation
# of course enrollment data from UNC's course database.
AVAILABLE_TERMS = {"2024+Summer+II": "SuII24", "2024+Fall": "F24"}
AVAILABLE_TERMS = {"2024+Summer+II": "24SSII", "2024+Fall": "24F"}

# Store updates to make.
updates: dict[tuple[str, str], SectionEnrollmentData] = {}
Expand Down
4 changes: 2 additions & 2 deletions backend/test/services/academics/course_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)
from backend.services.permission import PermissionService
from ....services.academics import CourseService
from ....models.academics import CourseDetails
from ....models.academics import Course, CourseDetails

# Imported fixtures provide dependencies injected for the tests as parameters.
from .fixtures import permission_svc, course_svc
Expand All @@ -29,7 +29,7 @@ def test_all(course_svc: CourseService):
courses = course_svc.all()

assert len(courses) == len(course_data.courses)
assert isinstance(courses[0], CourseDetails)
assert isinstance(courses[0], Course)


def test_get_by_id(course_svc: CourseService):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 16px;
padding-bottom: 16px;
}

.row {
Expand All @@ -29,3 +30,17 @@
#edit-button {
margin-right: 8px;
}

.right-header-container {
display: flex;
flex-direction: row;
margin-left: auto;
gap: 12px;
align-items: center;
}

.term-selector {
width: 260px;
margin-bottom: -1.25em;
}

Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
<!-- HTML Structure of Admin Terms List -->
<div class="content" *ngIf="displayTerm.value && sections(); else noTerm">
<div class="content" *ngIf="displayTerm() && sections(); else noTerm">
<table mat-table [dataSource]="sections()">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>
<div class="header">
Sections
<mat-form-field appearance="outline" style="margin-left: auto">
<mat-label>Select Term</mat-label>
<mat-select [formControl]="displayTerm">
<mat-option *ngFor="let term of terms$ | async" [value]="term">{{
term.name
}}</mat-option>
</mat-select>
</mat-form-field>

<button mat-stroked-button (click)="createSection()">Create</button>
<div class="right-header-container">
<mat-form-field class="term-selector" appearance="outline">
<mat-label>Select Term</mat-label>
<mat-select
[(ngModel)]="displayTermId"
(selectionChange)="resetSections()">
@for(term of terms; track term.id) {
<mat-option [value]="term.id">{{ term.name }}</mat-option>
}
</mat-select>
</mat-form-field>
<button mat-stroked-button (click)="createSection()">Create</button>
</div>
</div>
</th>
<td mat-cell *matCellDef="let element">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ export class AdminSectionComponent {
public sections: WritableSignal<CatalogSection[]> = signal([]);

/** Store list of Terms */
public terms: RxTermList = new RxTermList();
public terms$: Observable<Term[]> = this.terms.value$;
public terms: Term[];

/** Store the currently selected term from the form */
public displayTerm: FormControl<Term> = new FormControl();
// NOTE: Separating these fields into an ID and a selected term was required
// for Angular to correctly show the correct term in the initial drop down.
public displayTermId: string | null;
public displayTerm: WritableSignal<Term | undefined>;

public displayedColumns: string[] = ['name'];

Expand All @@ -64,17 +66,13 @@ export class AdminSectionComponent {
currentTerm: Term | undefined;
};

this.terms.set(data.terms);
this.terms = data.terms;

if (data.currentTerm) {
this.displayTerm.setValue(data.currentTerm);

this.academicsService
.getSectionsByTerm(data.currentTerm!)
.subscribe((sections) => {
this.sections.set(sections);
});
}
// Set initial display term
this.displayTermId = data.currentTerm?.id ?? null;
this.displayTerm = signal(this.selectedTerm());
// Initialize the sections list
this.resetSections();
}

/** Event handler to open the Section Editor to create a new term */
Expand Down Expand Up @@ -104,15 +102,29 @@ export class AdminSectionComponent {
);
confirmDelete.onAction().subscribe(() => {
this.academicsService.deleteSection(section).subscribe(() => {
let termToUpdate = this.displayTerm.value;
this.sections.update((sections) =>
sections.filter((s) => s.id !== section.id)
);
this.terms.updateTerm(termToUpdate);
this.snackBar.open('This Section has been deleted.', '', {
duration: 2000
});
});
});
}

selectedTerm() {
return this.terms.find((term) => term.id == this.displayTermId);
}

/** Resets the section data based on the selected term. */
resetSections() {
this.displayTerm.set(this.selectedTerm());
if (this.displayTerm()) {
this.academicsService
.getSectionsByTerm(this.displayTerm()!)
.subscribe((sections) => {
this.sections.set(sections);
});
}
}
}
3 changes: 2 additions & 1 deletion frontend/src/app/academics/academics.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AcademicsRoutingModule } from './academics-routing.module';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { ReactiveFormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AcademicsHomeComponent } from './academics-home/academics-home.component';
import { AcademicsAdminComponent } from './academics-admin/academics-admin.component';
import { MatTabsModule } from '@angular/material/tabs';
Expand Down Expand Up @@ -52,6 +52,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
MatTableModule,
MatIconModule,
MatFormFieldModule,
FormsModule,
MatSelectModule,
ReactiveFormsModule,
MatTabsModule,
Expand Down

0 comments on commit 0399a78

Please sign in to comment.