From d223b4fb1206b794ecaaf273b555476e40c9ebc1 Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Wed, 13 Sep 2023 03:06:39 -0700 Subject: [PATCH 01/11] Added destroy method to section view and added tests --- .../scheduler/tests/models/test_section.py | 68 +++++++++++++++++++ csm_web/scheduler/views/section.py | 22 ++++++ 2 files changed, 90 insertions(+) create mode 100644 csm_web/scheduler/tests/models/test_section.py diff --git a/csm_web/scheduler/tests/models/test_section.py b/csm_web/scheduler/tests/models/test_section.py new file mode 100644 index 00000000..a081c163 --- /dev/null +++ b/csm_web/scheduler/tests/models/test_section.py @@ -0,0 +1,68 @@ +import pytest +from django.urls import reverse +from scheduler.factories import ( + CourseFactory, + MentorFactory, + SectionFactory, + StudentFactory, + UserFactory, +) +from scheduler.models import Section + + +@pytest.fixture +def setup_test_scheduler(): + """ + Create a course, coordinator, mentor, and a student for testing. + """ + # Setup course + course = CourseFactory.create() + # Setup coordinator for course + coordinator_user = UserFactory.create() + + return ( + course, + coordinator_user, + ) + + +@pytest.mark.django_db +def test_section_delete(client, setup_scheduler): + """ + Test that a section can be deleted. + """ + ( + section_one, + coordinator_user, + ) = setup_scheduler + # Login as coordinator + client.force_login(coordinator_user) + # Delete section + response = client.delete(reverse("section-detail", kwargs={"pk": section_one.id})) + # Check that section was deleted + assert response.status_code == 204 + assert Section.objects.count() == 1 + + +def create_students(course, section, quantity): + """ + Creates a given number of students for a given section. + """ + student_users = UserFactory.create_batch(quantity) + students = [] + for student_user in student_users: + student = StudentFactory.create( + user=student_user, course=course, section=section + ) + students.append(student) + return students + + +def create_section(course): + """ + Creates a section for a given course. + """ + mentor_user = UserFactory.create() + mentor = MentorFactory.create(user=mentor_user, course=course) + section = SectionFactory.create(mentor=mentor) + return section diff --git a/csm_web/scheduler/views/section.py b/csm_web/scheduler/views/section.py index 1a063140..04e867bb 100644 --- a/csm_web/scheduler/views/section.py +++ b/csm_web/scheduler/views/section.py @@ -119,6 +119,28 @@ def create(self, request): serializer = self.serializer_class(section) return Response(serializer.data, status=status.HTTP_201_CREATED) + def destroy(self, request, pk=None): + """ + Handle request to delete section through the UI; + deletes mentor and spacetimes along with it + """ + section = get_object_or_error(self.get_queryset(), pk=pk) + if not section.mentor.course.coordinator_set.filter( + user=self.request.user + ).count(): + raise PermissionDenied("Only coordinators can delete section") + # Delete all students in the section + for student in section.students.all(): + student.delete() + # Delete all spacetimes in the section + for spacetime in section.spacetimes.all(): + spacetime.delete() + # Delete the mentor + section.mentor.delete() + + section.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + def partial_update(self, request, pk=None): """Update section metadata (capacity and description)""" section = get_object_or_error(self.get_queryset(), pk=pk) From 86b0537148078fa57ccef24614f475c63a74056e Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Wed, 13 Sep 2023 03:21:51 -0700 Subject: [PATCH 02/11] Added destroy method to section view and added tests --- csm_web/scheduler/tests/models/test_section.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/csm_web/scheduler/tests/models/test_section.py b/csm_web/scheduler/tests/models/test_section.py index a081c163..83ae78e8 100644 --- a/csm_web/scheduler/tests/models/test_section.py +++ b/csm_web/scheduler/tests/models/test_section.py @@ -1,6 +1,7 @@ import pytest from django.urls import reverse from scheduler.factories import ( + CoordinatorFactory, CourseFactory, MentorFactory, SectionFactory, @@ -10,15 +11,23 @@ from scheduler.models import Section -@pytest.fixture -def setup_test_scheduler(): +@pytest.fixture(name="setup") +def setup_test(): """ Create a course, coordinator, mentor, and a student for testing. """ # Setup course course = CourseFactory.create() + # Setup sections + section_one = create_section(course) + section_two = create_section(course) + # Setup students + create_students(course, section_one, 3) + create_students(course, section_two, 3) # Setup coordinator for course coordinator_user = UserFactory.create() + # Create coordinator for course + CoordinatorFactory.create(user=coordinator_user, course=course) return ( course, @@ -27,14 +36,14 @@ def setup_test_scheduler(): @pytest.mark.django_db -def test_section_delete(client, setup_scheduler): +def test_section_delete(client, setup): """ Test that a section can be deleted. """ ( section_one, coordinator_user, - ) = setup_scheduler + ) = setup # Login as coordinator client.force_login(coordinator_user) # Delete section From 533c94205d3c7063931427a439d257ef16232b1d Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Wed, 13 Sep 2023 04:00:08 -0700 Subject: [PATCH 03/11] Fixed incorrect export --- csm_web/scheduler/tests/models/test_section.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csm_web/scheduler/tests/models/test_section.py b/csm_web/scheduler/tests/models/test_section.py index 83ae78e8..83d1885c 100644 --- a/csm_web/scheduler/tests/models/test_section.py +++ b/csm_web/scheduler/tests/models/test_section.py @@ -30,7 +30,7 @@ def setup_test(): CoordinatorFactory.create(user=coordinator_user, course=course) return ( - course, + section_one, coordinator_user, ) From 1cecffbe5ba9a8e8f454ae53a8a340da4d39e803 Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Fri, 15 Sep 2023 16:11:00 -0700 Subject: [PATCH 04/11] Better logging for section delete method --- csm_web/scheduler/views/section.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/csm_web/scheduler/views/section.py b/csm_web/scheduler/views/section.py index 04e867bb..f4cc414c 100644 --- a/csm_web/scheduler/views/section.py +++ b/csm_web/scheduler/views/section.py @@ -125,10 +125,19 @@ def destroy(self, request, pk=None): deletes mentor and spacetimes along with it """ section = get_object_or_error(self.get_queryset(), pk=pk) - if not section.mentor.course.coordinator_set.filter( - user=self.request.user - ).count(): - raise PermissionDenied("Only coordinators can delete section") + course = section.mentor.course + is_coordinator = course.coordinator_set.filter(user=request.user).exists() + if not is_coordinator: + logger.error( + ( + " Could not delete spacetime, user %s" + " does not have proper permissions" + ), + log_str(request.user), + ) + raise PermissionDenied( + "You must be a coordinator to delete this spacetime!" + ) # Delete all students in the section for student in section.students.all(): student.delete() From e2e56b3f9869710a305c45a7fd3484a253589360 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 18 Sep 2023 05:03:07 -0700 Subject: [PATCH 05/11] Added delete button added delete button --- .../components/section/MentorSectionInfo.tsx | 33 +++++++++++++++ .../src/css/coordinator-add-student.scss | 41 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index efbaa8a2..ead212e9 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -8,6 +8,8 @@ import { InfoCard, SectionSpacetime } from "./Section"; import SpacetimeEditModal from "./SpacetimeEditModal"; import StudentDropper from "./StudentDropper"; import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; +import { fetchWithMethod } from "../../utils/api"; +import Modal from "../Modal"; // Images import XIcon from "../../../static/frontend/img/x.svg"; @@ -15,6 +17,7 @@ import PencilIcon from "../../../static/frontend/img/pencil.svg"; // Styles import "../../css/coordinator-add-student.scss"; +import { NavLink } from "react-router-dom"; enum ModalStates { NONE = "NONE", @@ -51,6 +54,13 @@ export default function MentorSectionInfo({ const closeModal = () => setShowModal(ModalStates.NONE); + // const [deletionStage, setDeletionStage] = useState(0); + + // function handleDelete() { + // fetchWithMethod(`/sections/${sectionId}`, "DELETE") + // setDeletionStage(0); + // } + const closeAddModal = () => { setIsAddingStudent(false); }; @@ -232,6 +242,29 @@ export default function MentorSectionInfo({ + {/* {deletionStage === 1 && ( + setDeletionStage(0)}> +
+

Are you sure you want to delete this resource?

+

This action is irreversible!

+ +
+
+ )} */} +
+
+ fetchWithMethod(`/sections/${sectionId}`, "DELETE")} + className="coordinator-delete-link" + > + Remove This Section + + +
+
); } diff --git a/csm_web/frontend/src/css/coordinator-add-student.scss b/csm_web/frontend/src/css/coordinator-add-student.scss index bdfe1078..e08d4d81 100644 --- a/csm_web/frontend/src/css/coordinator-add-student.scss +++ b/csm_web/frontend/src/css/coordinator-add-student.scss @@ -308,3 +308,44 @@ $modal-effective-height: calc($modal-height - $modal-padding-y); .coordinator-email-response-item-right > * { flex: 1; } + +.coordinator-delete-position { + display: flex; + justify-content: flex-end; +} + +.coordinator-delete-button { + width: 250px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + + // background-color: #fc4a14; + background-color: #ff7272; + cursor: pointer; + border-radius: 6px; + border: none; + outline: none; + transition: background-color 0.4s; + box-shadow: 0px 8px 24px rgba(149, 157, 165, 0.5); +} + +.coordinator-delete-link { + display: flex; + text-decoration: none; + color: white; + justify-content: space-between; + align-items: center; + // background: none; + + // border: none; + // padding: 0; + // font: inherit; + // cursor: pointer; + // outline: inherit; + + // :hover { + // background-color: #f15120; + // } +} From e53cc8b1d8ccb6f1857e17d8ac08766aa7a30704 Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Tue, 19 Sep 2023 01:34:56 -0700 Subject: [PATCH 06/11] Coordinators must remove all students manually for a delete section request to go through --- csm_web/scheduler/views/section.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/csm_web/scheduler/views/section.py b/csm_web/scheduler/views/section.py index f4cc414c..f02045ef 100644 --- a/csm_web/scheduler/views/section.py +++ b/csm_web/scheduler/views/section.py @@ -126,6 +126,15 @@ def destroy(self, request, pk=None): """ section = get_object_or_error(self.get_queryset(), pk=pk) course = section.mentor.course + # If the course has students, we cannot delete the section + if section.students.count() > 0: + logger.error( + ( + "
Could not delete section %s, it has" + " students. Remove all students manually first." + ), + log_str(section), + ) is_coordinator = course.coordinator_set.filter(user=request.user).exists() if not is_coordinator: logger.error( @@ -138,9 +147,6 @@ def destroy(self, request, pk=None): raise PermissionDenied( "You must be a coordinator to delete this spacetime!" ) - # Delete all students in the section - for student in section.students.all(): - student.delete() # Delete all spacetimes in the section for spacetime in section.spacetimes.all(): spacetime.delete() From 92fd246d1c361145b8483a5111a481fb73ef1132 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Tue, 19 Sep 2023 02:09:11 -0700 Subject: [PATCH 07/11] added warning and invalidating queries --- .../components/section/MentorSectionInfo.tsx | 113 +++++++++++++----- .../src/css/coordinator-add-student.scss | 5 +- .../frontend/src/utils/queries/sections.tsx | 40 +++++++ 3 files changed, 125 insertions(+), 33 deletions(-) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index ead212e9..27f1e3aa 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; -import { useSectionStudents } from "../../utils/queries/sections"; -import { Mentor, Spacetime, Student } from "../../utils/types"; +import { Navigate, Route, Routes } from "react-router-dom"; +import { useSectionStudents, useDropSectionMutation } from "../../utils/queries/sections"; +import { Mentor, Spacetime, Student, Course } from "../../utils/types"; import LoadingSpinner from "../LoadingSpinner"; import { CoordinatorAddStudentModal } from "./CoordinatorAddStudentModal"; import MetaEditModal from "./MetaEditModal"; @@ -8,9 +9,22 @@ import { InfoCard, SectionSpacetime } from "./Section"; import SpacetimeEditModal from "./SpacetimeEditModal"; import StudentDropper from "./StudentDropper"; import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; -import { fetchWithMethod } from "../../utils/api"; +import { fetchWithMethod, HTTP_METHODS } from "../../utils/api"; + +import { useProfiles, useUserInfo } from "../../utils/queries/base"; +import { useCourses } from "../../utils/queries/courses"; + import Modal from "../Modal"; +import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; +import { + handleError, + handlePermissionsError, + handleRetry, + PermissionError, + ServerError +} from "./../../utils/queries/helpers"; + // Images import XIcon from "../../../static/frontend/img/x.svg"; import PencilIcon from "../../../static/frontend/img/pencil.svg"; @@ -47,24 +61,31 @@ export default function MentorSectionInfo({ }: MentorSectionInfoProps) { const { data: students, isSuccess: studentsLoaded, isError: studentsLoadError } = useSectionStudents(sectionId); + const { data: courses, isSuccess: coursesLoaded, isError: coursesLoadError } = useCourses(); + const [showModal, setShowModal] = useState(ModalStates.NONE); const [focusedSpacetimeID, setFocusedSpacetimeID] = useState(-1); const [isAddingStudent, setIsAddingStudent] = useState(false); const [deleteType, setDeleteType] = useState(false); - const closeModal = () => setShowModal(ModalStates.NONE); - - // const [deletionStage, setDeletionStage] = useState(0); + let courseIds = []; - // function handleDelete() { - // fetchWithMethod(`/sections/${sectionId}`, "DELETE") - // setDeletionStage(0); - // } + const closeModal = () => setShowModal(ModalStates.NONE); const closeAddModal = () => { setIsAddingStudent(false); }; + if (coursesLoaded) { + const coursesById: Map = new Map(); + for (const courseObj of courses) { + coursesById.set(courseObj.id, courseObj); + } + + let courseIds = Array.from(coursesById.keys()); + // console.log(courseIds) + } + return (

{`${ @@ -242,29 +263,59 @@ export default function MentorSectionInfo({ - {/* {deletionStage === 1 && ( - setDeletionStage(0)}> -
-

Are you sure you want to delete this resource?

-

This action is irreversible!

- + + ); + case DropSectionStage.CONFIRM: + return ( + setStage(DropSectionStage.INITIAL)}> +
+
Are you sure you want to drop?
+

You are not guaranteed an available spot in another section!

+
- )} */} -
-
- fetchWithMethod(`/sections/${sectionId}`, "DELETE")} - className="coordinator-delete-link" - > - Remove This Section - - -
-
- - ); + ); + case DropSectionStage.DROPPED: + return ; + } } diff --git a/csm_web/frontend/src/css/coordinator-add-student.scss b/csm_web/frontend/src/css/coordinator-add-student.scss index e08d4d81..c1c97c64 100644 --- a/csm_web/frontend/src/css/coordinator-add-student.scss +++ b/csm_web/frontend/src/css/coordinator-add-student.scss @@ -324,11 +324,12 @@ $modal-effective-height: calc($modal-height - $modal-padding-y); // background-color: #fc4a14; background-color: #ff7272; cursor: pointer; - border-radius: 6px; + border-radius: 10px; border: none; outline: none; transition: background-color 0.4s; - box-shadow: 0px 8px 24px rgba(149, 157, 165, 0.5); + //box-shadow: 0px 8px 24px rgba(149, 157, 165, 0.5); + box-shadow: 0px 4px 4px rgba(198, 198, 198, 0.25); } .coordinator-delete-link { diff --git a/csm_web/frontend/src/utils/queries/sections.tsx b/csm_web/frontend/src/utils/queries/sections.tsx index a4bbcf8c..1e18bcc2 100644 --- a/csm_web/frontend/src/utils/queries/sections.tsx +++ b/csm_web/frontend/src/utils/queries/sections.tsx @@ -6,6 +6,7 @@ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResul import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; import { Attendance, RawAttendance, Section, Spacetime, Student } from "../types"; import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; +import { useProfiles } from "./base"; /* ===== Queries ===== */ @@ -501,3 +502,42 @@ export const useSectionUpdateMutation = ( handleError(mutationResult); return mutationResult; }; + +/** + * Hook to drop the current section + * + * Invalidates the current user profile query. + */ +export const useDropSectionMutation = ( + sectionId: number, + courseIds: Array +): UseMutationResult => { + const queryClient = useQueryClient(); + const mutationResult = useMutation( + async () => { + if (isNaN(sectionId) || isNaN(sectionId)) { + throw new PermissionError("Invalid section id"); + } + const response = await fetchWithMethod(`sections/${sectionId}`, HTTP_METHODS.DELETE); + if (response.ok) { + return; + } else { + handlePermissionsError(response.status); + throw new ServerError(`Failed to drop section ${sectionId}`); + } + }, + { + onSuccess: () => { + queryClient.invalidateQueries(["sections", sectionId]); + for (const courseId of courseIds) { + // console.log(courseId) + queryClient.invalidateQueries(["courses", courseId]); + } + }, + retry: handleRetry + } + ); + + handleError(mutationResult); + return mutationResult; +}; From 4da7c301fc5e8f9fad3d0498c8802da190814961 Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Wed, 20 Sep 2023 12:16:01 -0700 Subject: [PATCH 08/11] Fixed eslint error for courseIds variable in MentorSectionInfo.tsx --- csm_web/frontend/src/components/section/MentorSectionInfo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 27f1e3aa..d470fbf8 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -68,7 +68,7 @@ export default function MentorSectionInfo({ const [isAddingStudent, setIsAddingStudent] = useState(false); const [deleteType, setDeleteType] = useState(false); - let courseIds = []; + let courseIds: number[] = []; const closeModal = () => setShowModal(ModalStates.NONE); @@ -82,7 +82,7 @@ export default function MentorSectionInfo({ coursesById.set(courseObj.id, courseObj); } - let courseIds = Array.from(coursesById.keys()); + courseIds = Array.from(coursesById.keys()); // console.log(courseIds) } From 6997dc7e21e510f16902440c1a0c7ea76d3044dc Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 25 Sep 2023 20:30:09 -0700 Subject: [PATCH 09/11] update drop to delete --- .../components/section/MentorSectionInfo.tsx | 17 +++++++---------- csm_web/frontend/src/utils/queries/sections.tsx | 10 ++-------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 27f1e3aa..99fa9190 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -68,8 +68,6 @@ export default function MentorSectionInfo({ const [isAddingStudent, setIsAddingStudent] = useState(false); const [deleteType, setDeleteType] = useState(false); - let courseIds = []; - const closeModal = () => setShowModal(ModalStates.NONE); const closeAddModal = () => { @@ -263,14 +261,13 @@ export default function MentorSectionInfo({
- + ); } interface DropSectionProps { sectionId: number; - courseIds: Array; } enum DropSectionStage { @@ -279,8 +276,8 @@ enum DropSectionStage { DROPPED = "DROPPED" } -function DropSection({ sectionId, courseIds }: DropSectionProps) { - const sectionDropMutation = useDropSectionMutation(sectionId, courseIds); +function DropSection({ sectionId }: DropSectionProps) { + const sectionDropMutation = useDropSectionMutation(sectionId); const [stage, setStage] = useState(DropSectionStage.INITIAL); const performDrop = () => { @@ -296,10 +293,10 @@ function DropSection({ sectionId, courseIds }: DropSectionProps) { case DropSectionStage.INITIAL: return ( -
Drop Section
+
Delete Section
); @@ -307,8 +304,8 @@ function DropSection({ sectionId, courseIds }: DropSectionProps) { return ( setStage(DropSectionStage.INITIAL)}>
-
Are you sure you want to drop?
-

You are not guaranteed an available spot in another section!

+
Are you sure you want to delete?
+ diff --git a/csm_web/frontend/src/utils/queries/sections.tsx b/csm_web/frontend/src/utils/queries/sections.tsx index 1e18bcc2..7795ba92 100644 --- a/csm_web/frontend/src/utils/queries/sections.tsx +++ b/csm_web/frontend/src/utils/queries/sections.tsx @@ -508,10 +508,7 @@ export const useSectionUpdateMutation = ( * * Invalidates the current user profile query. */ -export const useDropSectionMutation = ( - sectionId: number, - courseIds: Array -): UseMutationResult => { +export const useDropSectionMutation = (sectionId: number): UseMutationResult => { const queryClient = useQueryClient(); const mutationResult = useMutation( async () => { @@ -529,10 +526,7 @@ export const useDropSectionMutation = ( { onSuccess: () => { queryClient.invalidateQueries(["sections", sectionId]); - for (const courseId of courseIds) { - // console.log(courseId) - queryClient.invalidateQueries(["courses", courseId]); - } + queryClient.invalidateQueries(["courses"]); }, retry: handleRetry } From 410e74095a72aca0f20206b2d013aaa4d36a0815 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 2 Oct 2023 19:35:20 -0700 Subject: [PATCH 10/11] Deleted comments in css --- .../src/components/section/MentorSectionInfo.tsx | 1 - .../frontend/src/css/coordinator-add-student.scss | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 09fb3371..2f66d70c 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -273,7 +273,6 @@ function DropSection({ sectionId }: DropSectionProps) { const performDrop = () => { sectionDropMutation.mutate(undefined, { onSuccess: () => { - // console.log(sectionId) setStage(DropSectionStage.DROPPED); } }); diff --git a/csm_web/frontend/src/css/coordinator-add-student.scss b/csm_web/frontend/src/css/coordinator-add-student.scss index c1c97c64..3ea680c1 100644 --- a/csm_web/frontend/src/css/coordinator-add-student.scss +++ b/csm_web/frontend/src/css/coordinator-add-student.scss @@ -321,7 +321,6 @@ $modal-effective-height: calc($modal-height - $modal-padding-y); justify-content: center; align-items: center; - // background-color: #fc4a14; background-color: #ff7272; cursor: pointer; border-radius: 10px; @@ -338,15 +337,4 @@ $modal-effective-height: calc($modal-height - $modal-padding-y); color: white; justify-content: space-between; align-items: center; - // background: none; - - // border: none; - // padding: 0; - // font: inherit; - // cursor: pointer; - // outline: inherit; - - // :hover { - // background-color: #f15120; - // } } From c8b199843169efb6225ee4018630ff5707c2cba6 Mon Sep 17 00:00:00 2001 From: Kartavya Sharma Date: Mon, 9 Oct 2023 19:37:08 -0700 Subject: [PATCH 11/11] Mooved coordinator check before student check, made delete section process atomic --- csm_web/scheduler/views/section.py | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/csm_web/scheduler/views/section.py b/csm_web/scheduler/views/section.py index f02045ef..a1bbef32 100644 --- a/csm_web/scheduler/views/section.py +++ b/csm_web/scheduler/views/section.py @@ -124,17 +124,8 @@ def destroy(self, request, pk=None): Handle request to delete section through the UI; deletes mentor and spacetimes along with it """ - section = get_object_or_error(self.get_queryset(), pk=pk) + section = get_object_or_error(Course.objects.all(), pk=pk) course = section.mentor.course - # If the course has students, we cannot delete the section - if section.students.count() > 0: - logger.error( - ( - "
Could not delete section %s, it has" - " students. Remove all students manually first." - ), - log_str(section), - ) is_coordinator = course.coordinator_set.filter(user=request.user).exists() if not is_coordinator: logger.error( @@ -147,13 +138,24 @@ def destroy(self, request, pk=None): raise PermissionDenied( "You must be a coordinator to delete this spacetime!" ) + # If the course has students, we cannot delete the section + if section.students.count() > 0: + logger.error( + ( + "
Could not delete section %s, it has" + " students. Remove all students manually first." + ), + log_str(section), + ) + return PermissionDenied("Cannot delete section with students") # Delete all spacetimes in the section - for spacetime in section.spacetimes.all(): - spacetime.delete() - # Delete the mentor - section.mentor.delete() + with transaction.atomic(): + for spacetime in section.spacetimes.all(): + spacetime.delete() + # Delete the mentor + section.mentor.delete() - section.delete() + section.delete() return Response(status=status.HTTP_204_NO_CONTENT) def partial_update(self, request, pk=None):