From 30b941eeecef93d76c77d3030382f38661885c21 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 17 May 2018 15:39:50 +0200 Subject: [PATCH 01/21] Mockup of SheetCorrectorsR --- messages/de.msg | 3 + routes | 15 +++-- src/Foundation.hs | 7 ++ src/Handler/Sheet.hs | 153 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 159 insertions(+), 19 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index d296157b4..2a20f9bf6 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -39,3 +39,6 @@ SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet. +AddCorrector: Zusätzlicher Korrektor +CorrectorExists user@Text: #{user} ist bereits als Korrektor eingetragen +SheetCorrectorsTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: Korrektoren für #{termToText tid}-#{courseShortHand} #{sheetName} \ No newline at end of file diff --git a/routes b/routes index c04ca7ada..8dc1ec910 100644 --- a/routes +++ b/routes @@ -21,13 +21,14 @@ /edit CourseEditR GET POST !lecturer /ex SheetR !registered: - / SheetListR GET - /#Text/show SheetShowR GET !time - /#Text/#SheetFileType/#FilePath SheetFileR GET !time - /new SheetNewR GET POST !lecturer - /#Text/edit SheetEditR GET POST !lecturer - /#Text/delete SheetDelR GET POST !lecturer - !/#Text/submission/#SubmissionMode SubmissionR GET POST !time + / SheetListR GET + /#Text/show SheetShowR GET !time + /#Text/#SheetFileType/#FilePath SheetFileR GET !time + /new SheetNewR GET POST !lecturer + /#Text/edit SheetEditR GET POST !lecturer + /#Text/delete SheetDelR GET POST !lecturer + /#Text/correctors SheetCorrectorsR GET POST !lecturer + !/#Text/submission/#SubmissionMode SubmissionR GET POST !time !/#{ZIPArchiveName SubmissionId} SubmissionDownloadArchiveR GET diff --git a/src/Foundation.hs b/src/Foundation.hs index 659b00513..c2861c77f 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -331,6 +331,7 @@ isAuthorizedDB (CourseR t c (SheetR SheetNewR)) _ = courseLecturerAcce isAuthorizedDB (CourseR t c (SheetR (SheetEditR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) isAuthorizedDB (CourseR t c (SheetR (SheetDelR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) isAuthorizedDB (CourseR t c (SheetR (SubmissionR s m))) _ = return Authorized -- TODO -- submissionAccess $ Right cID +isAuthorizedDB (CourseR t c (SheetR (SheetCorrectorsR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) isAuthorizedDB (CourseEditIDR cID) _ = do courseId <- decrypt cID courseLecturerAccess courseId @@ -453,6 +454,12 @@ pageActions (CSheetR tid csh (SheetShowR shn)) = , menuItemRoute = CSheetR tid csh (SubmissionR shn newSubmission) , menuItemAccessCallback' = return True } + , PageActionPrime $ MenuItem + { menuItemLabel = "Korrektoren" + , menuItemIcon = Nothing + , menuItemRoute = CSheetR tid csh (SheetCorrectorsR shn) + , menuItemAccessCallback' = return True + } ] pageActions TermShowR = [ PageActionPrime $ MenuItem diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 8f3d895cb..44e3734cc 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -8,6 +8,8 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE RankNTypes #-} module Handler.Sheet where @@ -31,11 +33,20 @@ import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Internal.Sql as E import Control.Monad.Writer (MonadWriter(..), execWriterT) +import Control.Monad.Trans.RWS.Lazy (RWST) + +import qualified Data.List as List import Network.Mime import qualified Data.Set as Set +import Data.Map (Map, (!), (!?)) +import qualified Data.Map as Map + +import Control.Lens +import Utils.Lens + instance Eq (Unique Sheet) where (CourseSheet cid1 name1) == (CourseSheet cid2 name2) = @@ -62,7 +73,6 @@ data SheetForm = SheetForm , sfSolutionFrom :: Maybe UTCTime , sfSolutionF :: Maybe FileInfo -- Keine SheetId im Formular! - , sfCorrectors :: [(UserId,Load)] } @@ -90,8 +100,7 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do <*> fileAFormOpt (fsb "Hinweis") <*> aopt utcTimeField (fsb "Lösung ab") (sfSolutionFrom <$> template) <*> fileAFormOpt (fsb "Lösung") - <*> formToAForm (correctorForm msId (maybe [] sfCorrectors template)) - -- <* submitButton + <* submitButton return $ case result of FormSuccess sheetResult | errorMsgs <- validateSheet sheetResult @@ -120,14 +129,6 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do -- TODO: continue validation here!!! ] ] -correctorForm :: Maybe SheetId -> [(UserId,Load)] -> MForm Handler (FormResult [(UserId,Load)], [FieldView UniWorX]) -correctorForm _msid templates = do undefined - -- Datenbank UserId -> UserName - -- Eingabelist für Colonnade - -- enthält die benötigten Felder - -- FormResult konstruieren - -- Eingabebox für Korrektor hinzufügen - -- Eingabe für Korrekt ausgefüllt: FormMissing zurückschicken um dann Feld hinzuzufügen -- List Sheets @@ -298,7 +299,6 @@ getSheetEditR tid csh shn = do , sfHintF = Nothing -- TODO , sfSolutionFrom = sheetSolutionFrom , sfSolutionF = Nothing -- TODO - , sfCorrectors = [] -- TODO read correctors from list } let action newSheet = do replaceRes <- myReplaceUnique sid $ newSheet @@ -407,3 +407,132 @@ insertSheetFile' sid ftype fs = do finsert (Right file) = lift $ do fid <- insert file void . insert $ SheetFile sid fid ftype -- cannot fail due to uniqueness, since we generated a fresh FileId in the previous step + + +data CorrectorForm = CorrectorForm + { cfUserId :: UserId + , cfUserName :: Text + , cfResult :: FormResult Load + , cfViewByTut, cfViewCountTut, cfViewProp :: FieldView UniWorX + } + +type Loads = Map UserId Load + +defaultLoads :: SheetId -> DB Loads +-- ^ Generate `Loads` in such a way that minimal editing is required +-- +-- For every user, that ever was a corrector for this course, return their last `Load`. +-- "Last `Load`" is taken to mean their `Load` on the `Sheet` with the most recent creation time (first edit). +defaultLoads shid = fmap toMap . E.select . E.from $ \(sheet `E.InnerJoin` sheetCorrector) -> E.distinctOnOrderBy [E.asc (sheetCorrector E.^. SheetCorrectorUser)] $ do + E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet + + let creationTime = E.sub_select . E.from $ \sheetEdit -> do + E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId + return . E.min_ $ sheetEdit E.^. SheetEditTime + + E.orderBy [E.desc creationTime] + + return (sheetCorrector E.^. SheetCorrectorUser, sheetCorrector E.^. SheetCorrectorLoad) + where + toMap :: [(E.Value UserId, E.Value Load)] -> Loads + toMap = foldMap $ \(E.Value uid, E.Value load) -> Map.singleton uid load + + +correctorForm :: SheetId -> MForm Handler (FormResult Loads, [FieldView UniWorX]) +correctorForm shid = do + cListIdent <- newFormIdent + formCIDs <- lift $ mapM decrypt =<< (catMaybes . map fromPathPiece <$> lookupPostParams cListIdent :: Handler [CryptoUUIDUser]) + loads'' <- lift . runDB $ defaultLoads shid + let loads' = loads'' `Map.union` Map.fromList [(uid, mempty) | uid <- formCIDs] + + (addTutRes, addTutView) <- mopt emailField (fsm MsgAddCorrector) (Just Nothing) + + loads <- case addTutRes of + FormSuccess (Just email) -> do + mUid <- fmap (fmap entityKey) . lift . runDB $ getBy (UniqueEmail email) + case mUid of + Nothing -> loads' <$ addMessageI "error" (MsgEMailUnknown email) + Just uid + | not (Map.member uid loads') -> return $ Map.insert uid mempty loads' + | otherwise -> loads' <$ addMessageI "warning" (MsgCorrectorExists email) + FormFailure errs -> loads' <$ mapM_ (addMessage "error" . toHtml) errs + _ -> return loads' + + names <- fmap (Map.fromList . map (\(E.Value a, E.Value b) -> (a, b))) . lift . runDB . E.select . E.from $ \user -> do + E.where_ $ user E.^. UserId `E.in_` E.valList (Map.keys loads) + return $ (user E.^. UserId, user E.^. UserDisplayName) + + let + constructFields :: (UserId, Text, Load) -> MForm Handler CorrectorForm + constructFields (uid, uname, Load{..}) = do + cID@CryptoID{..} <- encrypt uid :: MForm Handler CryptoUUIDUser + let + fs name = "" + { fsName = Just $ tshow ciphertext <> "-" <> name + } + rationalField = convertField toRational fromRational doubleField + + (byTutRes, cfViewByTut) <- mreq checkBoxField (fs "bytut") (Just $ isJust byTutorial) + (countTutRes, cfViewCountTut) <- mreq checkBoxField (fs "counttut") byTutorial + (propRes, cfViewProp) <- mreq rationalField (fs "prop") (Just byProportion) + let + cfResult :: FormResult Load + cfResult = Load <$> tutRes' <*> propRes + tutRes' + | FormSuccess True <- byTutRes = Just <$> countTutRes + | otherwise = Nothing <$ byTutRes + cfUserId = uid + cfUserName = uname + return CorrectorForm{..} + + corrData <- sequence . catMaybes . (flip map) (Map.keys loads) $ \uid -> fmap constructFields $ (,,) <$> pure uid <*> names !? uid <*> loads !? uid + + let + corrColonnade = mconcat + [ headed "Korrektor" $ \CorrectorForm{..} -> textCell cfUserName + , headed "Nach Tutorium" $ \CorrectorForm{..} -> cell $ fvInput cfViewByTut + , headed "Tutorium zählt gegen Proportion" $ \CorrectorForm{..} -> cell $ fvInput cfViewCountTut -- TODO: set this once for entire sheet? + , headed "Proportion" $ \CorrectorForm{..} -> cell $ fvInput cfViewProp + ] + corrResults + | FormSuccess _ <- addTutRes = FormMissing + | otherwise = fmap Map.fromList $ sequenceA [ (,) <$> pure cfUserId <*> cfResult + | CorrectorForm{..} <- corrData + ] + idField CorrectorForm{..} = do + cID <- encrypt cfUserId :: WidgetT UniWorX IO CryptoUUIDUser + toWidget [hamlet||] + + return (corrResults, [ FieldView + { fvLabel = mempty + , fvTooltip = Nothing + , fvId = "" + , fvInput = encodeCellTable tableDefault corrColonnade corrData >> mapM_ idField corrData + , fvErrors = Nothing + , fvRequired = True + } + , addTutView + { fvInput = fvInput addTutView >> toWidget [hamlet|