chore(tutorial): towards #2347 exam occurrence form added (WIP)

This commit is contained in:
Steffen Jost 2025-01-08 15:28:10 +01:00
parent 8b52f00fb0
commit d37d39301c
6 changed files with 60 additions and 42 deletions

View File

@ -36,3 +36,4 @@ NoProblem: Keine Probleme gefunden
Unknown: ist unbekannt Unknown: ist unbekannt
UnknownOrNotAllowed: ist unbekannt oder hier nicht erlaubt UnknownOrNotAllowed: ist unbekannt oder hier nicht erlaubt
Ambiguous: ist uneindeutig Ambiguous: ist uneindeutig
Action: Aktion

View File

@ -36,3 +36,4 @@ NoProblem: No Probleme found
Unknown: is unknown Unknown: is unknown
UnknownOrNotAllowed: is unknown or not allowed here UnknownOrNotAllowed: is unknown or not allowed here
Ambiguous: is ambiguous Ambiguous: is ambiguous
Action: Action

View File

@ -7,6 +7,7 @@ module Handler.Exam.Form
, ExamOccurrenceForm(..) , ExamOccurrenceForm(..)
, ExamPartForm(..) , ExamPartForm(..)
, examForm , examForm
, examOccurrenceMultiForm, examOccurrenceForm
, examFormTemplate, examTemplate , examFormTemplate, examTemplate
, validateExam , validateExam
) where ) where
@ -137,7 +138,7 @@ examForm (Entity _ Course{..}) template csrf = hoist liftHandler $ do
<*> aopt utcTimeField (fslpI MsgExamPartsFrom (mr MsgDate) & setTooltip MsgExamPartsFromTip) (efPartsFrom <$> template) <*> aopt utcTimeField (fslpI MsgExamPartsFrom (mr MsgDate) & setTooltip MsgExamPartsFromTip) (efPartsFrom <$> template)
<*> aopt utcTimeField (fslpI MsgExamFinished (mr MsgDate) & setTooltip (bool MsgExamFinishedTip MsgExamFinishedTipCloseOnFinished $ is _ExamCloseOnFinished' schoolExamCloseMode)) (efFinished <$> template) <*> aopt utcTimeField (fslpI MsgExamFinished (mr MsgDate) & setTooltip (bool MsgExamFinishedTip MsgExamFinishedTipCloseOnFinished $ is _ExamCloseOnFinished' schoolExamCloseMode)) (efFinished <$> template)
<* aformSection MsgExamFormOccurrences <* aformSection MsgExamFormOccurrences
<*> examOccurrenceForm (efOccurrences <$> template) <*> examOccurrenceMultiForm (efOccurrences <$> template)
<* aformSection MsgExamFormAutomaticFunctions <* aformSection MsgExamFormAutomaticFunctions
<*> apopt checkBoxField (fslI MsgExamPublicStatistics & setTooltip MsgExamPublicStatisticsTip) (efPublicStatistics <$> template <|> Just True) <*> apopt checkBoxField (fslI MsgExamPublicStatistics & setTooltip MsgExamPublicStatisticsTip) (efPublicStatistics <$> template <|> Just True)
<*> optionalActionA (examGradingRuleForm $ efGradingRule =<< template) (fslI MsgExamAutomaticGrading & setTooltip MsgExamAutomaticGradingTip) (is _Just . efGradingRule <$> template) <*> optionalActionA (examGradingRuleForm $ efGradingRule =<< template) (fslI MsgExamAutomaticGrading & setTooltip MsgExamAutomaticGradingTip) (is _Just . efGradingRule <$> template)
@ -250,24 +251,9 @@ examCorrectorsForm mPrev = wFormToAForm $ do
fmap Set.fromList <$> massInputAccumW miAdd' miCell' miButtonAction' miLayout' ("correctors" :: Text) (fslI MsgExamCorrectors & setTooltip MsgExamCorrectorsTip) False (Set.toList <$> mPrev) fmap Set.fromList <$> massInputAccumW miAdd' miCell' miButtonAction' miLayout' ("correctors" :: Text) (fslI MsgExamCorrectors & setTooltip MsgExamCorrectorsTip) False (Set.toList <$> mPrev)
examOccurrenceForm :: Maybe (Set ExamOccurrenceForm) -> AForm Handler (Set ExamOccurrenceForm)
examOccurrenceForm prev = wFormToAForm $ do
currentRoute <- fromMaybe (error "examOccurrenceForm called from 404-handler") <$> getCurrentRoute
let
miButtonAction' :: forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)
miButtonAction' frag = Just . SomeRoute $ currentRoute :#: frag
fmap (fmap Set.fromList) . massInputAccumEditW miAdd' miCell' miButtonAction' miLayout' miIdent' (fslI MsgExamOccurrences) False $ Set.toList <$> prev examOccurrenceForm :: (Text -> Text) -> Maybe ExamOccurrenceForm -> Form ExamOccurrenceForm
where examOccurrenceForm nudge mPrev csrf = do
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) (eofIdRes, eofIdView) <- mopt hiddenField ("" & addName (nudge "id")) (Just $ eofId =<< mPrev)
(eofNameRes, eofNameView) <- mpreq (textField & cfStrip & cfCI) (fslI MsgExamRoomName & addName (nudge "name")) (eofName <$> 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! (eofExaminerRes, eofExaminerView) <- mopt examinerField (fslI MsgExamStaff & addName (nudge "examiner")) (eofExaminer <$> mPrev) -- TODO: restrict suggestions!
@ -280,7 +266,6 @@ examOccurrenceForm prev = wFormToAForm $ do
(eofStartRes, eofStartView) <- mpreq utcTimeField (fslI MsgExamRoomStart & addName (nudge "start")) (eofStart <$> mPrev) (eofStartRes, eofStartView) <- mpreq utcTimeField (fslI MsgExamRoomStart & addName (nudge "start")) (eofStart <$> mPrev)
(eofEndRes, eofEndView) <- mopt utcTimeField (fslI MsgExamRoomEnd & addName (nudge "end")) (eofEnd <$> mPrev) (eofEndRes, eofEndView) <- mopt utcTimeField (fslI MsgExamRoomEnd & addName (nudge "end")) (eofEnd <$> mPrev)
(eofDescRes, eofDescView) <- mopt htmlField (fslI MsgExamRoomDescription & addName (nudge "description")) (eofDescription <$> mPrev) (eofDescRes, eofDescView) <- mopt htmlField (fslI MsgExamRoomDescription & addName (nudge "description")) (eofDescription <$> mPrev)
return ( ExamOccurrenceForm return ( ExamOccurrenceForm
<$> eofIdRes <$> eofIdRes
<*> eofNameRes <*> eofNameRes
@ -293,16 +278,33 @@ examOccurrenceForm prev = wFormToAForm $ do
<*> eofDescRes <*> eofDescRes
, $(widgetFile "widgets/massinput/examRooms/form") , $(widgetFile "widgets/massinput/examRooms/form")
) )
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
examOccurrenceMultiForm :: Maybe (Set ExamOccurrenceForm) -> AForm Handler (Set ExamOccurrenceForm)
examOccurrenceMultiForm prev = wFormToAForm $ do
currentRoute <- fromMaybe (error "examOccurrenceMultiForm called from 404-handler") <$> getCurrentRoute
let
miButtonAction' :: forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)
miButtonAction' frag = Just . SomeRoute $ currentRoute :#: frag
fmap (fmap Set.fromList) . massInputAccumEditW miAdd' miCell' miButtonAction' miLayout' miIdent' (fslI MsgExamOccurrences) False $ Set.toList <$> prev
where
miAdd' nudge submitView csrf = do miAdd' nudge submitView csrf = do
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
(res, formWidget) <- examOccurrenceForm' nudge Nothing csrf (res, formWidget) <- examOccurrenceForm nudge Nothing csrf
let let
addRes = res <&> \newDat (Set.fromList -> oldDat) -> if addRes = res <&> \newDat (Set.fromList -> oldDat) -> if
| newDat `Set.member` oldDat -> FormFailure [mr MsgExamRoomAlreadyExists] | newDat `Set.member` oldDat -> FormFailure [mr MsgExamRoomAlreadyExists]
| otherwise -> FormSuccess $ pure newDat | otherwise -> FormSuccess $ pure newDat
return (addRes, $(widgetFile "widgets/massinput/examRooms/add")) return (addRes, $(widgetFile "widgets/massinput/examRooms/add"))
miCell' nudge dat = examOccurrenceForm' nudge (Just dat) miCell' nudge dat = examOccurrenceForm nudge (Just dat)
miLayout' lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/examRooms/layout") miLayout' lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/examRooms/layout")
miIdent' :: Text miIdent' :: Text
miIdent' = "exam-occurrences" miIdent' = "exam-occurrences"

View File

@ -16,6 +16,7 @@ import Handler.Utils
import Handler.Utils.Course import Handler.Utils.Course
import Handler.Utils.Course.Cache import Handler.Utils.Course.Cache
import Handler.Utils.Tutorial import Handler.Utils.Tutorial
import Handler.Exam.Form (examOccurrenceMultiForm, ExamOccurrenceForm(..))
import Database.Persist.Sql (deleteWhereCount) import Database.Persist.Sql (deleteWhereCount)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -44,22 +45,28 @@ embedRenderMessage ''UniWorX ''GenTutAction id
data GenTutActionData data GenTutActionData
= GenTutActNoneData -- Dummy action to hide form in a more natural way = GenTutActNoneData -- Dummy action to hide form in a more natural way
| GenTutActOccAddData | GenTutActOccAddData
{ -- gtaoaExam :: ExamId,
gtaoaOcc :: Set ExamOccurrenceForm
}
| GenTutActOccEditData | GenTutActOccEditData
deriving (Eq, Ord, Read, Show, Generic) deriving (Eq, Ord, Show, Generic)
genTutActionMap ::(_ -> Text) -> Map GenTutAction (AForm Handler GenTutActionData) genTutActionMap ::(_ -> Text) -> Map GenTutAction (AForm Handler GenTutActionData)
genTutActionMap _mr = Map.fromList genTutActionMap _mr = Map.fromList
[ (GenTutActNone [ (GenTutActNone
, pure GenTutActNoneData ) , pure GenTutActNoneData )
, (GenTutActOccAdd , (GenTutActOccAdd, GenTutActOccAddData
, pure GenTutActOccAddData ) <$> examOccurrenceMultiForm Nothing -- TODO
)
, (GenTutActOccEdit , (GenTutActOccEdit
, pure GenTutActOccEditData) -- TODO , pure GenTutActOccEditData) -- TODO
] ]
makeGenTutActionForm :: (_ -> Text) -> Form GenTutActionData makeGenTutActionForm :: (_ -> Text) -> Form GenTutActionData
makeGenTutActionForm mr html = flip (renderAForm FormStandard) html $ multiActionA (genTutActionMap mr) (fslI MsgGenTutActions) (Just GenTutActNone) makeGenTutActionForm mr html = flip (renderAForm FormStandard) html $ multiActionA (genTutActionMap mr) (fslI MsgAction) (Just GenTutActNone)
-- TODO: Idee: MultiAction für jedes Exam, um so die einzelnen Occurrences zu markieren!
-- Default muss auch entsprechend generiert werden, wenn keine Occurrences für den Tag existieren
-- Im Form sollten die neuen markiert werden als ungespeichert! Generell wünschenswert für MassInput!
data TutorialUserAction data TutorialUserAction
= TutorialUserAssignExam = TutorialUserAssignExam
@ -166,7 +173,7 @@ postTUsersR tid ssh csh tutn = do
letters <- runDB $ makeCourseCertificates tut Nothing $ toList selectedUsers letters <- runDB $ makeCourseCertificates tut Nothing $ toList selectedUsers
let mbAletter = anyone letters let mbAletter = anyone letters
case mbAletter of case mbAletter of
Nothing -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing -- TODO: better error message Nothing -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing -- cannot really happen
Just aletter -> do Just aletter -> do
apcIdent <- letterApcIdent aletter encRcvr now apcIdent <- letterApcIdent aletter encRcvr now
let fName = letterFileName aletter let fName = letterFileName aletter
@ -220,8 +227,8 @@ postTUsersR tid ssh csh tutn = do
_other -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing _other -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing
case tcontent of case tcontent of
Just act -> act -- abort and return produced content Just act -> act -- execute action and return produced content
Nothing -> do Nothing -> do -- no table action, continue normally
tutors <- runDB $ E.select $ do tutors <- runDB $ E.select $ do
(tutor :& user) <- E.from $ E.table @Tutor `E.innerJoin` E.table @User (tutor :& user) <- E.from $ E.table @Tutor `E.innerJoin` E.table @User
`E.on` (\(tutor :& user) -> tutor E.^. TutorUser E.==. user E.^. UserId) `E.on` (\(tutor :& user) -> tutor E.^. TutorUser E.==. user E.^. UserId)
@ -230,7 +237,7 @@ postTUsersR tid ssh csh tutn = do
let mr :: (() -> Text) = const "TODO: message renderer for general tutorial action form" -- getMessageRender let mr :: (() -> Text) = const "TODO: message renderer for general tutorial action form" -- getMessageRender
genTutActWgt <- do genTutActWgt <- do
((_gtaRes, gtaWgt), gtaEnctype) <- runFormPost . identifyForm FIDGeneralTutorialAction $ makeGenTutActionForm mr ((gtaRes, gtaWgt), gtaEnctype) <- runFormPost . identifyForm FIDGeneralTutorialAction $ makeGenTutActionForm mr
let gtaAnchor = "general-tutorial-action-form" :: Text let gtaAnchor = "general-tutorial-action-form" :: Text
gtaRoute = croute :#: gtaAnchor gtaRoute = croute :#: gtaAnchor
gtaForm = wrapForm gtaWgt FormSettings gtaForm = wrapForm gtaWgt FormSettings
@ -241,6 +248,11 @@ postTUsersR tid ssh csh tutn = do
, formSubmit = FormSubmit , formSubmit = FormSubmit
, formAnchor = Just gtaAnchor , formAnchor = Just gtaAnchor
} }
formResult gtaRes $ \case
GenTutActNoneData -> return ()
GenTutActOccAddData{} -> error "not yet implemended" -- TODO
GenTutActOccEditData{} -> error "not yet implemended" -- TODO
----------------------------------------------- -----------------------------------------------
-- !!!!!TODO: evaluate form result !!!!!!!!! -- -- !!!!!TODO: evaluate form result !!!!!!!!! --

View File

@ -323,12 +323,12 @@ massInput MassInput{ miIdent = toPathPiece -> miIdent, ..} FieldSettings{..} fvR
runTwice act = do runTwice act = do
r <- ask r <- ask
s <- State.get s <- State.get
res1 <- fmap (view _1) . lift $ evalRWST (act Nothing) r s res1 <- fmap (view _1) . lift $ evalRWST (act Nothing) r s
local (_1 .~ Nothing) . act $ Just res1 local (_1 .~ Nothing) . act $ Just res1
replaceWithFirst :: forall k x y. Ord k => Maybe (Map k (x, y)) -> Map k (x, y) -> Map k (x, y) replaceWithFirst :: forall k x y. Ord k => Maybe (Map k (x, y)) -> Map k (x, y) -> Map k (x, y)
replaceWithFirst Nothing xs = xs replaceWithFirst Nothing xs = xs
replaceWithFirst (Just f) s = Map.unionWith (\(f1, _f2) (_s1, s2) -> (f1, s2)) f s replaceWithFirst (Just f) s = Map.unionWith (\(f1, _f2) (_s1, s2) -> (f1, s2)) f s
(shape, liveliness, delShapeUpdate, addResults, addResults', delResults, shapeChanged) <- runTwice $ \mPrev -> do (shape, liveliness, delShapeUpdate, addResults, addResults', delResults, shapeChanged) <- runTwice $ \mPrev -> do
let sentLiveliness' = maybe sentLiveliness (view _2) mPrev let sentLiveliness' = maybe sentLiveliness (view _2) mPrev
@ -567,7 +567,7 @@ massInputAccum miAdd' miCell' miButtonAction miLayout miIdent fSettings fRequire
doAdd f prevData = Map.fromList . zip [startKey..] <$> f prevElems doAdd f prevData = Map.fromList . zip [startKey..] <$> f prevElems
where where
prevElems = Map.elems prevData prevElems = Map.elems prevData
startKey = maybe 0 succ $ fst <$> Map.lookupMax prevData startKey = maybe 0 (succ . fst) $ Map.lookupMax prevData
miCell :: ListPosition -> cellData -> Maybe () -> (Text -> Text) miCell :: ListPosition -> cellData -> Maybe () -> (Text -> Text)
-> (Markup -> MForm handler (FormResult (), Widget)) -> (Markup -> MForm handler (FormResult (), Widget))
@ -643,7 +643,7 @@ massInputAccumEdit miAdd' miCell' miButtonAction miLayout miIdent fSettings fReq
doAdd f prevData = Map.fromList . zip [startKey..] <$> f prevElems doAdd f prevData = Map.fromList . zip [startKey..] <$> f prevElems
where where
prevElems = Map.elems prevData prevElems = Map.elems prevData
startKey = maybe 0 succ $ fst <$> Map.lookupMax prevData startKey = maybe 0 (succ . fst) $ Map.lookupMax prevData
miCell :: ListPosition -> cellData -> Maybe cellData -> (Text -> Text) miCell :: ListPosition -> cellData -> Maybe cellData -> (Text -> Text)
-> (Markup -> MForm handler (FormResult cellData, Widget)) -> (Markup -> MForm handler (FormResult cellData, Widget))

View File

@ -14,7 +14,7 @@ module Utils.Set
, mapIntersectNotOne , mapIntersectNotOne
, set2NonEmpty , set2NonEmpty
, maybeInsert , maybeInsert
) where ) where
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Set as Set import qualified Data.Set as Set
@ -27,7 +27,7 @@ import Control.Lens
-- | cardinal number of an intersection of a set and a list of sets -- | cardinal number of an intersection of a set and a list of sets
setIntersectNotOne :: Ord a => Set a -> [Set a] -> Int setIntersectNotOne :: Ord a => Set a -> [Set a] -> Int
setIntersectNotOne _ [] = 0 setIntersectNotOne _ [] = 0
setIntersectNotOne k r = Set.size $ Set.intersection k others where others = Set.unions r setIntersectNotOne k r = Set.size $ Set.intersection k others where others = Set.unions r
@ -37,14 +37,14 @@ setIntersectNotOne k r = Set.size $ Set.intersection k others where others = Set
-- | extracts from a map a list of values (sets) without one specific entry (a) -- | extracts from a map a list of values (sets) without one specific entry (a)
getAllElemsWithoutOne :: (Ord a) => Map a (Set b) -> a -> [Set b] getAllElemsWithoutOne :: (Ord a) => Map a (Set b) -> a -> [Set b]
getAllElemsWithoutOne m cid = Map.elems $ Map.delete cid m getAllElemsWithoutOne m cid = Map.elems $ Map.delete cid m
-- | transforms values (sets) of a map to integers. The number gives information about how many entreis are not only in this one -- | transforms values (sets) of a map to integers. The number gives information about how many entreis are not only in this one
mapIntersectNotOne :: forall a b. (Ord a, Ord b) => Map a (Set b) -> Map a Int mapIntersectNotOne :: forall a b. (Ord a, Ord b) => Map a (Set b) -> Map a Int
mapIntersectNotOne m = Map.mapWithKey f m where mapIntersectNotOne m = Map.mapWithKey f m where
f :: a -> Set b -> Int f :: a -> Set b -> Int
f k _ = setIntersectNotOne (Map.findWithDefault Set.empty k m) (getAllElemsWithoutOne m k) f k _ = setIntersectNotOne (Map.findWithDefault Set.empty k m) (getAllElemsWithoutOne m k)
-------------------------- --------------------------
-- Functions from Utils -- -- Functions from Utils --
-------------------------- --------------------------
@ -56,6 +56,7 @@ setIntersections (h:t) = foldl' Set.intersection h t
setMapMaybe :: Ord b => (a -> Maybe b) -> Set a -> Set b setMapMaybe :: Ord b => (a -> Maybe b) -> Set a -> Set b
setMapMaybe f = Set.fromList . mapMaybe f . Set.toList setMapMaybe f = Set.fromList . mapMaybe f . Set.toList
-- setMapMaybe f = Set.foldr (maybeInsert . f) Set.empty -- actually half as efficient as previous line using -O2
-- | like `setMapMaybe`, but only when f is strictly increasing -- | like `setMapMaybe`, but only when f is strictly increasing
setMapMaybeMonotonic :: (a -> Maybe b) -> Set a -> Set b setMapMaybeMonotonic :: (a -> Maybe b) -> Set a -> Set b
@ -75,7 +76,7 @@ setProduct (Set.toAscList -> as) (Set.toAscList -> bs) = Set.fromDistinctAscList
-- setPartitionEithers :: (Ord a, Ord b) => Set (Either a b) -> (Set a, Set b) -- setPartitionEithers :: (Ord a, Ord b) => Set (Either a b) -> (Set a, Set b)
-- setPartitionEithers = (,) <$> setMapMaybe (preview _Left) <*> setMapMaybe (preview _Right) -- setPartitionEithers = (,) <$> setMapMaybe (preview _Left) <*> setMapMaybe (preview _Right)
-- --
setPartitionEithers :: Set (Either a b) -> (Set a, Set b) setPartitionEithers :: Set (Either a b) -> (Set a, Set b)
setPartitionEithers = (,) <$> setMapMaybeMonotonic (preview _Left) <*> setMapMaybeMonotonic (preview _Right) setPartitionEithers = (,) <$> setMapMaybeMonotonic (preview _Left) <*> setMapMaybeMonotonic (preview _Right)
@ -88,5 +89,6 @@ set2NonEmpty _ (Set.toList -> h:t) = h NonEmpty.:| t
set2NonEmpty d _ = d NonEmpty.:| [] set2NonEmpty d _ = d NonEmpty.:| []
maybeInsert :: Ord a => Maybe a -> Set a -> Set a maybeInsert :: Ord a => Maybe a -> Set a -> Set a
-- maybeInsert = flip (ap maybe (flip Set.insert))
maybeInsert Nothing = id maybeInsert Nothing = id
maybeInsert (Just k) = Set.insert k maybeInsert (Just k) = Set.insert k