From 36a3b04ad845749e790cd183f0ec266533cd1d4a Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Mon, 9 Dec 2024 17:38:34 +0100 Subject: [PATCH] chore(exam): add examiner to occurrence options we intend to use the exam examiner for adding occurrences through tutorials --- fixtest.sh | 6 ------ .../courses/exam/exam/de-de-formal.msg | 5 ++++- .../categories/courses/exam/exam/en-eu.msg | 8 ++++++-- messages/uniworx/misc/de-de-formal.msg | 3 ++- messages/uniworx/misc/en-eu.msg | 3 ++- models/exams.model | 17 +++++++++-------- src/Handler/Exam/Edit.hs | 4 +++- src/Handler/Exam/Form.hs | 15 ++++++++++++++- src/Handler/Exam/New.hs | 1 + src/Handler/Exam/Show.hs | 9 +++++++-- src/Handler/Utils/Communication.hs | 2 +- src/Handler/Utils/Form.hs | 2 +- templates/exam-show.hamlet | 17 ++++++++++++++++- .../widgets/massinput/examRooms/form.hamlet | 1 + .../widgets/massinput/examRooms/layout.hamlet | 3 +++ 15 files changed, 70 insertions(+), 26 deletions(-) delete mode 100755 fixtest.sh diff --git a/fixtest.sh b/fixtest.sh deleted file mode 100755 index d59f51144..000000000 --- a/fixtest.sh +++ /dev/null @@ -1,6 +0,0 @@ -if [[ ! -d .stack-work-test ]]; then - mv -vT .stack-work .stack-work-test - [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work -else - echo "Directory .stack-work-test exists already." -fi diff --git a/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg b/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg index bd87559c7..f0799fcf7 100644 --- a/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg +++ b/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg @@ -64,7 +64,7 @@ ExamAutomaticGradingTip: Sollen die Gesamtleistungen der Teilnehmer:innen automa ExamBonus: Bonuspunkte-System ExamGradingMode: Bewertungsmodus ExamGradingModeTip: In welcher Form werden Prüfungsleistungen für diese Prüfung eingetragen? -ExamStaff: Prüfer:innen/Verantwortliche Hochschullehrer:innen +ExamStaff: Prüfer:innen ExamStaffTip: Geben Sie bitte in jedem Fall einen Namen an, der Prüfer:in/Veranstalter:in/Hochschullehrer:in eindeutig identifiziert! Sollte der Name des Prüfers/der Prüferin allein womöglich nicht eindeutig sein, so geben Sie bitte eindeutig identifizierende Zusatzinfos, wie beispielsweise den Lehrstuhl bzw. die LFE o.Ä., an. ExamExamOfficeSchools: Zusätzliche Bereiche ExamExamOfficeSchoolsTip: Prüfungsbeauftragte von Bereichen, die Sie hier angeben, erhalten im System (zusätzlich zum primären Bereich der zugehörigen Kursart) volle Einsicht in sämtliche für diese Prüfung hinterlegten Leistungen, unabhängig von den Studiendaten der Teilnehmer:innen. @@ -123,6 +123,8 @@ ExamOccurrenceStartMustBeAfterExamStart eoName@ExamOccurrenceName: Beginn des Te ExamOccurrenceEndMustBeBeforeExamEnd eoName@ExamOccurrenceName: Ende des Termins #{eoName} liegt nach dem Ende der Prüfung ExamOccurrenceDuplicate eoRoom@Text eoRange@Text: Raum #{eoRoom}, Termin #{eoRange} kommt mehrfach mit der selben Beschreibung vor ExamOccurrenceDuplicateName eoName@ExamOccurrenceName: Interne Terminbezeichnung #{eoName} kommt mehrfach vor +ExamOccurrenceExaminerIsUnset !ident-ok: — +ExamOccurrenceExaminerIsHidden: Prüfer wird nur Teilnehmer:innen angezeigt ExamOccurrenceRoomIsUnset !ident-ok: — ExamOccurrenceRoomIsHidden: Raum wird nur Teilnehmer:innen angezeigt ExamOccurrenceCannotBeDeletedDueToRegistrations eoName@ExamOccurrenceName: Termin #{eoName} kann nicht gelöscht werden, da noch Teilnehmer:innen diesem Termin zugewiesen sind. Über die Liste von Prüfungsteilnehmern können Sie zunächst die entsprechenden Terminzuweisungen entfernen. @@ -264,6 +266,7 @@ ExamAutoOccurrenceExceptionRoomTooSmall: Automatische Verteilung gescheitert. Ei ExamBonusInfoPoints: Zur Berechnung von Bonuspunkten werden nur jene Blätter herangezogen, deren Aktivitätszeitraum vor Start des jeweiligen Termin/Prüfung begonnen hat ExamUserCsvSheetName tid@TermId ssh@SchoolId csh@CourseShorthand examn@ExamName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn} Teilnehmer +ExamRoomExaminerTip: Nur bereits eingetragene Korrektor:innen sind hier erlaubt ExamRoomCapacityTip: Maximale Anzahl an Prüfungsteilnehmern für diesen Termin/Raum; leer lassen für unbeschränkte Teilnehmeranzahl ExamRoomMappingRandom: Verteilung ExamFinishHeading: Prüfungsergebnisse sichtbar schalten diff --git a/messages/uniworx/categories/courses/exam/exam/en-eu.msg b/messages/uniworx/categories/courses/exam/exam/en-eu.msg index 32ffda98e..acea135a1 100644 --- a/messages/uniworx/categories/courses/exam/exam/en-eu.msg +++ b/messages/uniworx/categories/courses/exam/exam/en-eu.msg @@ -64,7 +64,7 @@ ExamAutomaticGradingTip: Should the exam achievement be automatically computed f ExamBonus: Bonus point system ExamGradingMode: Grading mode ExamGradingModeTip: In which format should grades for this exam be entered? -ExamStaff: Examiner/Responsible university teacher +ExamStaff: Examiner ExamStaffTip: Please always specify a name that uniquely identifies the examiner/organiser/repsonsible university teacher! If there is a possibility that the name alone is ambiguous please also specify some additional information e.g. the professorial chair or the educational and research unit. ExamExamOfficeSchools: Additional departments ExamExamOfficeSchoolsTip: Exam offices of departments you specify here will also have full access to all results for this exam disregarding the individual participants' features of study. @@ -123,8 +123,10 @@ ExamOccurrenceStartMustBeAfterExamStart eoName: Start of the occurrence #{eoName ExamOccurrenceEndMustBeBeforeExamEnd eoName: End of the occurrence #{eoName} must be before the exam end ExamOccurrenceDuplicate eoRoom eoRange: Combination of room #{eoRoom} and occurrence #{eoRange} occurs multiple times ExamOccurrenceDuplicateName eoName: Internal name #{eoName} occurs multiple times +ExamOccurrenceExaminerIsUnset !ident-ok: — +ExamOccurrenceExaminerIsHidden: Examiner only displayed to participants registered for this occurrence ExamOccurrenceRoomIsUnset: — -ExamOccurrenceRoomIsHidden: Room is only displayed to participants registered for this occurrence/room +ExamOccurrenceRoomIsHidden: Room only displayed to participants registered for this occurrence ExamOccurrenceCannotBeDeletedDueToRegistrations eoName: Occurrence #{eoName} cannot be deleted because participants are registered for it. You can remove the offending registrations via the list of exam participants. ExamRegistrationMustFollowSchoolSeparationFromStart dayCount: As per school rules there #{pluralEN dayCount "needs" "need"} to be at least #{dayCount} #{pluralEN dayCount "day" "days"} between "Register from" and "Start". ExamRegistrationMustFollowSchoolDuration dayCount: As per school rules there #{pluralEN dayCount "needs" "need"} to be at least #{dayCount} #{pluralEN dayCount "day" "days"} between "Register from" and "Register to". @@ -262,6 +264,8 @@ ExamAutoOccurrenceExceptionNoUsers: No participants can be distributed with the ExamAutoOccurrenceExceptionRoomTooSmall: Automatic distribution failed. A different distribution procedure might succeed. Alternatively, minimizing rooms or removing small rooms might help. ExamBonusInfoPoints: When calculating an exam bonus only those sheets will be considered, for which the submission period started before the start of the relevant occurrence/room ExamUserCsvSheetName tid ssh csh examn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn} Participants + +ExamRoomExaminerTip: Only correctors allowed here, add beforehand ExamRoomCapacityTip: Maximum number of participants for this occurrence/room; leave empty for unlimited capacity ExamRoomMappingRandom: Distribution ExamFinishHeading: Make results visible diff --git a/messages/uniworx/misc/de-de-formal.msg b/messages/uniworx/misc/de-de-formal.msg index 960a75fe2..1d66c6c5c 100644 --- a/messages/uniworx/misc/de-de-formal.msg +++ b/messages/uniworx/misc/de-de-formal.msg @@ -34,4 +34,5 @@ NullDeletes: Zum Löschen NULL eingeben. SortPriority: Sortierungspriorität NoProblem: Keine Probleme gefunden Unknown: ist unbekannt -Ambiguous: ist uneindeutig \ No newline at end of file +UnknownOrNotAllowed: ist unbekannt oder hier nicht erlaubt +Ambiguous: ist uneindeutig diff --git a/messages/uniworx/misc/en-eu.msg b/messages/uniworx/misc/en-eu.msg index 14d4838c6..ea3957c50 100644 --- a/messages/uniworx/misc/en-eu.msg +++ b/messages/uniworx/misc/en-eu.msg @@ -34,4 +34,5 @@ NullDeletes: Enter NULL to delete. SortPriority: Sort order priority NoProblem: No Probleme found Unknown: is unknown -Ambiguous: is ambiguous \ No newline at end of file +UnknownOrNotAllowed: is unknown or not allowed here +Ambiguous: is ambiguous diff --git a/models/exams.model b/models/exams.model index e7dae4212..89a812ad0 100644 --- a/models/exams.model +++ b/models/exams.model @@ -37,14 +37,15 @@ ExamPart UniqueExamPartName exam name !force deriving Read Show Eq Ord Generic ExamOccurrence - exam ExamId - name ExamOccurrenceName - room RoomReference Maybe - roomHidden Bool default=false - capacity Word64 Maybe - start UTCTime - end UTCTime Maybe - description StoredMarkup Maybe + exam ExamId + name ExamOccurrenceName + examiner UserId Maybe + room RoomReference Maybe + roomHidden Bool default=false + capacity Word64 Maybe + start UTCTime + end UTCTime Maybe + description StoredMarkup Maybe UniqueExamOccurrence exam name deriving Generic ExamRegistration diff --git a/src/Handler/Exam/Edit.hs b/src/Handler/Exam/Edit.hs index 725236bd8..5178809ed 100644 --- a/src/Handler/Exam/Edit.hs +++ b/src/Handler/Exam/Edit.hs @@ -80,6 +80,7 @@ postEEditR tid ssh csh examn = do ExamOccurrence { examOccurrenceExam = eId , examOccurrenceName = eofName + , examOccurrenceExaminer = eofExaminer , examOccurrenceRoom = eofRoom , examOccurrenceRoomHidden = eofRoomHidden , examOccurrenceCapacity = eofCapacity @@ -95,6 +96,7 @@ postEEditR tid ssh csh examn = do lift $ replace eofId' ExamOccurrence { examOccurrenceExam = eId , examOccurrenceName = eofName + , examOccurrenceExaminer = eofExaminer , examOccurrenceRoom = eofRoom , examOccurrenceRoomHidden = eofRoomHidden , examOccurrenceCapacity = eofCapacity @@ -118,7 +120,7 @@ postEEditR tid ssh csh examn = do when brokenRefs $ throwM ExamEditWouldBreakSheetTypeReference - + deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ] forM_ (Set.toList efExamParts) $ \case ExamPartForm{ epfId = Nothing, .. } -> insert_ diff --git a/src/Handler/Exam/Form.hs b/src/Handler/Exam/Form.hs index 4d5d5e958..62ba159c0 100644 --- a/src/Handler/Exam/Form.hs +++ b/src/Handler/Exam/Form.hs @@ -62,6 +62,7 @@ data ExamForm = ExamForm data ExamOccurrenceForm = ExamOccurrenceForm { eofId :: Maybe CryptoUUIDExamOccurrence , eofName :: ExamOccurrenceName + , eofExaminer :: Maybe UserId , eofRoom :: Maybe RoomReference , eofRoomHidden :: Bool , eofCapacity :: Maybe Word64 @@ -74,6 +75,7 @@ instance Ord ExamOccurrenceForm where compare = mconcat [ comparing eofName , comparing eofStart + , comparing eofExaminer , comparing eofRoom , comparing eofEnd , comparing eofCapacity @@ -164,7 +166,7 @@ examForm (Entity _ Course{..}) template csrf = hoist liftHandler $ do (fslI MsgExamAuthorshipStatementContent & setTooltip MsgExamAuthorshipStatementContentForcedTip) contentField ttipReq | not schoolSheetExamAuthorshipStatementAllowOther - = fmap (fmap authorshipStatementDefinitionContent) . traverse forcedContentField $ entityVal <$> mSchoolAuthorshipStatement + = fmap (fmap authorshipStatementDefinitionContent) (traverse (forcedContentField . entityVal) mSchoolAuthorshipStatement) | otherwise = Just <$> reqContentField ttipReq in case schoolSheetExamAuthorshipStatementMode of @@ -257,9 +259,18 @@ examOccurrenceForm prev = wFormToAForm $ do fmap (fmap Set.fromList) . massInputAccumEditW miAdd' miCell' miButtonAction' miLayout' miIdent' (fslI MsgExamOccurrences) False $ Set.toList <$> prev where + examinerField = knownUserField True $ Just $ E.from $ \usr -> do + E.where_ $ + (E.exists . E.from $ \exCorr -> E.where_ $ exCorr E.^. ExamCorrectorUser E.==. usr E.^. UserId + ) E.||. + (E.exists . E.from $ \exOccr -> E.where_ $ exOccr E.^. ExamOccurrenceExaminer E.==. E.just (usr E.^. UserId) + ) + pure usr + examOccurrenceForm' nudge mPrev csrf = do (eofIdRes, eofIdView) <- mopt hiddenField ("" & addName (nudge "id")) (Just $ eofId =<< mPrev) (eofNameRes, eofNameView) <- mpreq (textField & cfStrip & cfCI) (fslI MsgExamRoomName & addName (nudge "name")) (eofName <$> mPrev) + (eofExaminerRes, eofExaminerView) <- mopt examinerField (fslI MsgExamStaff & addName (nudge "examiner")) (eofExaminer <$> mPrev) -- TODO: restrict suggestions! (eofRoomRes', eofRoomView) <- ($ mempty) . renderAForm FormVertical $ (,) <$> roomReferenceFormOpt (fslI MsgExamRoomRoom & addName (nudge "room")) (eofRoom <$> mPrev) <*> apopt checkBoxField (fslI MsgExamRoomRoomHidden & setTooltip MsgExamRoomRoomHiddenTip & addName (nudge "room-hidden")) (eofRoomHidden <$> mPrev) @@ -273,6 +284,7 @@ examOccurrenceForm prev = wFormToAForm $ do return ( ExamOccurrenceForm <$> eofIdRes <*> eofNameRes + <*> eofExaminerRes <*> eofRoomRes <*> eofRoomHiddenRes <*> eofCapacityRes @@ -372,6 +384,7 @@ examFormTemplate (Entity eId Exam{..}) = do return ExamOccurrenceForm { eofId , eofName = examOccurrenceName + , eofExaminer = examOccurrenceExaminer , eofRoom = examOccurrenceRoom , eofRoomHidden = examOccurrenceRoomHidden , eofCapacity = examOccurrenceCapacity diff --git a/src/Handler/Exam/New.hs b/src/Handler/Exam/New.hs index 886dfa0b9..9ba6cd12e 100644 --- a/src/Handler/Exam/New.hs +++ b/src/Handler/Exam/New.hs @@ -75,6 +75,7 @@ postCExamNewR tid ssh csh = do | ExamOccurrenceForm{..} <- Set.toList efOccurrences , let examOccurrenceExam = examid examOccurrenceName = eofName + examOccurrenceExaminer = eofExaminer examOccurrenceRoom = eofRoom examOccurrenceRoomHidden = eofRoomHidden examOccurrenceCapacity = eofCapacity diff --git a/src/Handler/Exam/Show.hs b/src/Handler/Exam/Show.hs index 090c6f1ed..b5e55db73 100644 --- a/src/Handler/Exam/Show.hs +++ b/src/Handler/Exam/Show.hs @@ -48,7 +48,7 @@ getEShowR tid ssh csh examn = do let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments || examOccurrenceRule == ExamRoomFifo occurrenceAssignmentsShown = occurrenceAssignmentsVisible || lecturerInfoShown - sheets <- selectList [ SheetCourse ==. examCourse ] [] + sheets <- selectList [ SheetCourse ==. examCourse ] [] let examPartSheets epId = do let sheets' = flip filter sheets $ \(Entity _ Sheet{..}) -> has (_examPart . re _SqlKey . only epId) sheetType flip filterM sheets' $ \(Entity _ Sheet{..}) -> hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR @@ -142,6 +142,11 @@ getEShowR tid ssh csh examn = do guard $ all (\(Entity _ occ, _, _, _) -> examOccurrenceRoom occ == examOccurrenceRoom primeOcc) occurrences guard $ andOf (folded . _4) occurrences examOccurrenceRoom primeOcc + examExaminer = do + (Entity _ primeOcc, _, _, _) <- occurrences ^? _head + guard $ all (\(Entity _ occ, _, _, _) -> examOccurrenceExaminer occ == examOccurrenceExaminer primeOcc) occurrences + guard $ andOf (folded . _4) occurrences + examOccurrenceExaminer primeOcc registerWidget mOcc | isRegistered <- is _Just $ join registered , examOccurrenceRule /= ExamRoomFifo || (isRegistered && not (orOf (folded . _2) occurrences)) @@ -204,7 +209,7 @@ getEShowR tid ssh csh examn = do guard $ evalExamModeDNF schoolExamDiscouragedModes examExamMode guardM . lift . hasWriteAccessTo $ CExamR tid ssh csh examn EEditR return $ notification NotificationBroad =<< messageI Warning MsgExamModeSchoolDiscouraged - + siteLayoutMsg heading $ do setTitleI heading let diff --git a/src/Handler/Utils/Communication.hs b/src/Handler/Utils/Communication.hs index 4990c21f2..0bc8e3f38 100644 --- a/src/Handler/Utils/Communication.hs +++ b/src/Handler/Utils/Communication.hs @@ -185,7 +185,7 @@ commR CommunicationRoute{..} = do recipientAForm = postProcess <$> massInputA MassInput{..} (fslI MsgCommRecipients & setTooltip MsgCommRecipientsTip) True (Just chosenRecipients') where miAdd pos@(BoundedPosition RecipientCustom, 0) dim@1 liveliness nudge submitView = guardOn (miAllowAdd pos dim liveliness) $ \csrf -> do - (addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgUtilEMail (mr MsgUtilEMail) & setTooltip MsgUtilMultiEmailFieldTip & addName (nudge "email")) Nothing + (addRes, addView) <- mpreq (multiUserField False Nothing) (fslpI MsgUtilEMail (mr MsgUtilEMail) & setTooltip MsgUtilMultiEmailFieldTip & addName (nudge "email")) Nothing let addRes' = addRes <&> \nEmails ((Map.elems &&& maybe 0 (succ . snd . fst) . Map.lookupMax) . Map.filterWithKey (\(BoundedPosition c, _) _ -> c == RecipientCustom) -> (oEmails, kStart)) -> FormSuccess . Map.fromList . zip (map (BoundedPosition RecipientCustom, ) [kStart..]) . Set.toList $ nEmails `Set.difference` Set.fromList oEmails return (addRes', $(widgetFile "widgets/communication/recipientAdd")) diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index b9ee7a7d4..0f3e65c0b 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -2006,7 +2006,7 @@ knownUserField onlySuggested suggestions = Field{..} let errMsg m = SomeMessage $ SomeMessages [SomeMessage MsgAvsPersonNo, text2message "/", SomeMessage MsgCompanyPersonalNumber, text2message t, m] case dbRes of [uid] -> return $ Right $ Just $ E.unValue uid - [] -> return $ Left $ errMsg $ SomeMessage MsgUnknown + [] -> return $ Left $ errMsg $ SomeMessage $ bool MsgUnknown MsgUnknownOrNotAllowed onlySuggested _ -> return $ Left $ errMsg $ SomeMessage MsgAmbiguous fieldParse _ _ = return $ Right Nothing diff --git a/templates/exam-show.hamlet b/templates/exam-show.hamlet index 73177a5e2..e82982821 100644 --- a/templates/exam-show.hamlet +++ b/templates/exam-show.hamlet @@ -84,6 +84,9 @@ $maybe desc <- examDescription #{c} ^{notificationPersonalIdentification} + $maybe examinerId <- examExaminer +
_{MsgExamStaff} +
^{userIdWidget examinerId} $maybe room <- examRoom
_{MsgExamRoom}
^{roomReferenceShortWidget room} @@ -194,6 +197,8 @@ $if not (null occurrences) _{MsgExamRoomName} \ ^{isVisible False} + $if is _Nothing examExaminer + _{MsgExamStaff} $if is _Nothing examRoom _{MsgExamRoom} $if not examTimes @@ -234,11 +239,21 @@ $if not (null occurrences) _{MsgExamRoomDescription} $forall (occurrence, registered, rCount, showRoom) <- occurrences - $with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence + $with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceExaminer, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence $with registerWdgt <- registerWidget (Just occurrence) $if occurrenceNamesShown #{examOccurrenceName} + $if is _Nothing examExaminer + $if showRoom + + $maybe examinerId <- examOccurrenceExaminer + ^{userIdWidget examinerId} + $nothing + _{MsgExamOccurrenceExaminerIsUnset} + $else + + _{MsgExamOccurrenceExaminerIsHidden} $if is _Nothing examRoom $if showRoom diff --git a/templates/widgets/massinput/examRooms/form.hamlet b/templates/widgets/massinput/examRooms/form.hamlet index 784a572d6..2d92479fa 100644 --- a/templates/widgets/massinput/examRooms/form.hamlet +++ b/templates/widgets/massinput/examRooms/form.hamlet @@ -5,6 +5,7 @@ $# $# SPDX-License-Identifier: AGPL-3.0-or-later #{csrf}^{fvInput eofIdView}^{fvWidget eofNameView} +^{fvWidget eofExaminerView} ^{eofRoomView} ^{fvWidget eofCapacityView} ^{fvWidget eofStartView} diff --git a/templates/widgets/massinput/examRooms/layout.hamlet b/templates/widgets/massinput/examRooms/layout.hamlet index 1c1121c4b..83f365c87 100644 --- a/templates/widgets/massinput/examRooms/layout.hamlet +++ b/templates/widgets/massinput/examRooms/layout.hamlet @@ -10,6 +10,8 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later _{MsgExamRoomName} # + + _{MsgExamStaff} _{MsgExamRoom} @@ -22,6 +24,7 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later + _{MsgExamRoomExaminerTip} _{MsgExamRoomCapacityTip}