Replace SheetCorrectors

This commit is contained in:
Gregor Kleen 2018-05-28 16:41:47 +02:00
parent 30b941eeec
commit 9260024676
7 changed files with 76 additions and 20 deletions

View File

@ -41,4 +41,12 @@ NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs
AddCorrector: Zusätzlicher Korrektor AddCorrector: Zusätzlicher Korrektor
CorrectorExists user@Text: #{user} ist bereits als Korrektor eingetragen CorrectorExists user@Text: #{user} ist bereits als Korrektor eingetragen
SheetCorrectorsTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: Korrektoren für #{termToText tid}-#{courseShortHand} #{sheetName} SheetCorrectorsTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: Korrektoren für #{termToText tid}-#{courseShortHand} #{sheetName}
CountTutProp: Tutorien zählen gegen Proportion
Corrector: Korrektor
Correctors: Korrektoren
CorByTut: Nach Tutorium
CorProportion: Anteil
DeleteRow: Zeile entfernen
ProportionNegative: Anteile dürfen nicht negativ sein
CorrectorsUpdated: Korrektoren erfolgreich aktualisiert

1
models
View File

@ -118,6 +118,7 @@ SheetCorrector
sheet SheetId sheet SheetId
load Load load Load
UniqueSheetCorrector user sheet UniqueSheetCorrector user sheet
deriving Show Eq Ord
SheetFile SheetFile
sheet SheetId sheet SheetId
file FileId file FileId

View File

@ -81,6 +81,7 @@ dependencies:
- exceptions - exceptions
- lens - lens
- MonadRandom - MonadRandom
- email-validate
# The library contains all of our application code. The executable # The library contains all of our application code. The executable
# defined below is just a thin wrapper. # defined below is just a thin wrapper.

View File

@ -414,6 +414,7 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb (CourseR tid csh (SheetR (SheetShowR shn))) = return (shn, Just $ CourseR tid csh $ SheetR SheetListR) breadcrumb (CourseR tid csh (SheetR (SheetShowR shn))) = return (shn, Just $ CourseR tid csh $ SheetR SheetListR)
breadcrumb (CourseR tid csh (SheetR (SheetEditR shn))) = return ("Edit", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) breadcrumb (CourseR tid csh (SheetR (SheetEditR shn))) = return ("Edit", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
breadcrumb (CourseR tid csh (SheetR (SheetDelR shn))) = return ("DELETE", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) breadcrumb (CourseR tid csh (SheetR (SheetDelR shn))) = return ("DELETE", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
breadcrumb (CourseR tid csh (SheetR (SheetCorrectorsR shn))) = return ("Korrektoren", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
breadcrumb (CourseR tid csh (SheetR (SubmissionR shn _))) = return ("Abgabe", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) breadcrumb (CourseR tid csh (SheetR (SubmissionR shn _))) = return ("Abgabe", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
breadcrumb SubmissionListR = return ("Abgaben", Just HomeR) breadcrumb SubmissionListR = return ("Abgaben", Just HomeR)

View File

@ -25,6 +25,7 @@ import qualified Data.Text as T
-- --
import Colonnade hiding (fromMaybe, singleton) import Colonnade hiding (fromMaybe, singleton)
import Yesod.Colonnade import Yesod.Colonnade
import Text.Blaze (text)
-- --
import qualified Data.UUID.Cryptographic as UUID import qualified Data.UUID.Cryptographic as UUID
import qualified Data.Conduit.List as C import qualified Data.Conduit.List as C
@ -33,12 +34,15 @@ import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Internal.Sql as E import qualified Database.Esqueleto.Internal.Sql as E
import Control.Monad.Writer (MonadWriter(..), execWriterT) import Control.Monad.Writer (MonadWriter(..), execWriterT)
import Control.Monad.Trans.RWS.Lazy (RWST) import Control.Monad.Trans.RWS.Lazy (RWST, local)
import qualified Text.Email.Validate as Email
import qualified Data.List as List import qualified Data.List as List
import Network.Mime import Network.Mime
import Data.Set (Set)
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.Map (Map, (!), (!?)) import Data.Map (Map, (!), (!?))
@ -413,7 +417,7 @@ data CorrectorForm = CorrectorForm
{ cfUserId :: UserId { cfUserId :: UserId
, cfUserName :: Text , cfUserName :: Text
, cfResult :: FormResult Load , cfResult :: FormResult Load
, cfViewByTut, cfViewCountTut, cfViewProp :: FieldView UniWorX , cfViewByTut, cfViewProp, cfViewDel :: FieldView UniWorX
} }
type Loads = Map UserId Load type Loads = Map UserId Load
@ -438,17 +442,46 @@ defaultLoads shid = fmap toMap . E.select . E.from $ \(sheet `E.InnerJoin` sheet
toMap = foldMap $ \(E.Value uid, E.Value load) -> Map.singleton uid load toMap = foldMap $ \(E.Value uid, E.Value load) -> Map.singleton uid load
correctorForm :: SheetId -> MForm Handler (FormResult Loads, [FieldView UniWorX]) correctorForm :: SheetId -> MForm Handler (FormResult (Set SheetCorrector), [FieldView UniWorX])
correctorForm shid = do correctorForm shid = do
cListIdent <- newFormIdent cListIdent <- newFormIdent
formCIDs <- lift $ mapM decrypt =<< (catMaybes . map fromPathPiece <$> lookupPostParams cListIdent :: Handler [CryptoUUIDUser]) let
guardNonDeleted :: CryptoUUIDUser -> Handler (Maybe CryptoUUIDUser)
guardNonDeleted cID@CryptoID{..} = do
deleted <- lookupPostParam $ tshow ciphertext <> "-" <> "del"
case deleted of
Just _ -> return Nothing
Nothing -> return $ Just cID
formCIDs <- lift $ (mapM decrypt <=< fmap catMaybes . mapM (maybe (return Nothing) guardNonDeleted)) =<< (map fromPathPiece <$> lookupPostParams cListIdent :: Handler [Maybe CryptoUUIDUser])
loads'' <- lift . runDB $ defaultLoads shid loads'' <- lift . runDB $ defaultLoads shid
let loads' = loads'' `Map.union` Map.fromList [(uid, mempty) | uid <- formCIDs] let loads' = loads'' `Map.union` Map.fromList [(uid, mempty) | uid <- formCIDs]
(addTutRes, addTutView) <- mopt emailField (fsm MsgAddCorrector) (Just Nothing) (countTutRes, countTutView) <- mreq checkBoxField (fsm MsgCountTutProp) Nothing
let
tutorField :: Field Handler [Text]
tutorField = multiEmailField
{ fieldView = \theId name attrs val isReq -> asWidgetT $ do
listIdent <- newIdent
userId <- handlerToWidget requireAuthId
previousCorrectors <- handlerToWidget . runDB . E.select . E.from $ \(user `E.InnerJoin` sheetCorrector `E.InnerJoin` sheet `E.InnerJoin` course `E.InnerJoin` lecturer) -> E.distinctOnOrderBy [E.asc $ user E.^. UserEmail ] $ do
E.on $ lecturer E.^. LecturerCourse E.==. course E.^. CourseId
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet
E.on $ sheetCorrector E.^. SheetCorrectorUser E.==. user E.^. UserId
E.where_ $ lecturer E.^. LecturerUser E.==. E.val userId
return $ user E.^. UserEmail
toWidget [hamlet|
$newline never
<input id=#{theId} name=#{name} list=#{listIdent} *{attrs} type=email multiple :isReq:required value="">
<datalist id=#{listIdent}>
$forall E.Value prev <- previousCorrectors
<option value=#{prev}>
|]
}
(addTutRes, addTutView) <- mopt tutorField (fsm MsgAddCorrector) (Just Nothing)
loads <- case addTutRes of loads <- case addTutRes of
FormSuccess (Just email) -> do FormSuccess (Just emails) -> fmap Map.unions . forM emails $ \email -> do
mUid <- fmap (fmap entityKey) . lift . runDB $ getBy (UniqueEmail email) mUid <- fmap (fmap entityKey) . lift . runDB $ getBy (UniqueEmail email)
case mUid of case mUid of
Nothing -> loads' <$ addMessageI "error" (MsgEMailUnknown email) Nothing -> loads' <$ addMessageI "error" (MsgEMailUnknown email)
@ -473,8 +506,8 @@ correctorForm shid = do
rationalField = convertField toRational fromRational doubleField rationalField = convertField toRational fromRational doubleField
(byTutRes, cfViewByTut) <- mreq checkBoxField (fs "bytut") (Just $ isJust byTutorial) (byTutRes, cfViewByTut) <- mreq checkBoxField (fs "bytut") (Just $ isJust byTutorial)
(countTutRes, cfViewCountTut) <- mreq checkBoxField (fs "counttut") byTutorial (propRes, cfViewProp) <- mreq (checkBool (>= 0) MsgProportionNegative $ rationalField) (fs "prop") (Just byProportion)
(propRes, cfViewProp) <- mreq rationalField (fs "prop") (Just byProportion) (_, cfViewDel) <- mreq checkBoxField (fs "del") (Just False)
let let
cfResult :: FormResult Load cfResult :: FormResult Load
cfResult = Load <$> tutRes' <*> propRes cfResult = Load <$> tutRes' <*> propRes
@ -487,24 +520,28 @@ correctorForm shid = do
corrData <- sequence . catMaybes . (flip map) (Map.keys loads) $ \uid -> fmap constructFields $ (,,) <$> pure uid <*> names !? uid <*> loads !? uid corrData <- sequence . catMaybes . (flip map) (Map.keys loads) $ \uid -> fmap constructFields $ (,,) <$> pure uid <*> names !? uid <*> loads !? uid
mr <- getMessageRender
let let
corrColonnade = mconcat corrColonnade = mconcat
[ headed "Korrektor" $ \CorrectorForm{..} -> textCell cfUserName [ headed (textCell $ mr MsgCorrector) $ \CorrectorForm{..} -> textCell cfUserName
, headed "Nach Tutorium" $ \CorrectorForm{..} -> cell $ fvInput cfViewByTut , headed (textCell $ mr MsgCorByTut) $ \CorrectorForm{..} -> cell $ fvInput cfViewByTut
, headed "Tutorium zählt gegen Proportion" $ \CorrectorForm{..} -> cell $ fvInput cfViewCountTut -- TODO: set this once for entire sheet? , headed (textCell $ mr MsgCorProportion) $ \CorrectorForm{..} -> cell $ fvInput cfViewProp
, headed "Proportion" $ \CorrectorForm{..} -> cell $ fvInput cfViewProp , headed (textCell $ mr MsgDeleteRow) $ \CorrectorForm{..} -> cell $ fvInput cfViewDel
] ]
corrResults corrResults
| FormSuccess _ <- addTutRes = FormMissing | FormSuccess (Just es) <- addTutRes
| otherwise = fmap Map.fromList $ sequenceA [ (,) <$> pure cfUserId <*> cfResult , not $ null es = FormMissing
| otherwise = fmap Set.fromList $ sequenceA [ SheetCorrector <$> pure cfUserId <*> pure shid <*> cfResult
| CorrectorForm{..} <- corrData | CorrectorForm{..} <- corrData
] ]
idField CorrectorForm{..} = do idField CorrectorForm{..} = do
cID <- encrypt cfUserId :: WidgetT UniWorX IO CryptoUUIDUser cID <- encrypt cfUserId :: WidgetT UniWorX IO CryptoUUIDUser
toWidget [hamlet|<input name=#{cListIdent} type=hidden value=#{toPathPiece cID}>|] toWidget [hamlet|<input name=#{cListIdent} type=hidden value=#{toPathPiece cID}>|]
return (corrResults, [ FieldView return (corrResults, [ countTutView
{ fvLabel = mempty , FieldView
{ fvLabel = text $ mr MsgCorrectors
, fvTooltip = Nothing , fvTooltip = Nothing
, fvId = "" , fvId = ""
, fvInput = encodeCellTable tableDefault corrColonnade corrData >> mapM_ idField corrData , fvInput = encodeCellTable tableDefault corrColonnade corrData >> mapM_ idField corrData
@ -527,7 +564,15 @@ postSheetCorrectorsR = getSheetCorrectorsR
getSheetCorrectorsR tid@(unTermKey -> tident) csh shn = do getSheetCorrectorsR tid@(unTermKey -> tident) csh shn = do
Entity shid Sheet{..} <- runDB $ fetchSheet tid csh shn Entity shid Sheet{..} <- runDB $ fetchSheet tid csh shn
((res,formWidget), formEnctype) <- runFormPost . renderAForm FormStandard $ formToAForm (correctorForm shid) <* submitButton ((res,formWidget), formEnctype) <- runFormPost . identForm FIDcorrectors . renderAForm FormStandard $ formToAForm (correctorForm shid) <* submitButton
case res of
FormFailure errs -> mapM_ (addMessage "error" . toHtml) errs
FormSuccess res -> runDB $ do
deleteWhere [SheetCorrectorSheet ==. shid]
insertMany_ $ Set.toList res
addMessageI "success" MsgCorrectorsUpdated
FormMissing -> return ()
let let
formTitle = MsgSheetCorrectorsTitle tident csh shn formTitle = MsgSheetCorrectorsTitle tident csh shn

View File

@ -48,7 +48,7 @@ import Control.Monad.Writer.Class
-- Unique Form Identifiers to avoid accidents -- -- Unique Form Identifiers to avoid accidents --
------------------------------------------------ ------------------------------------------------
data FormIdentifier = FIDcourse | FIDsheet | FIDsubmission data FormIdentifier = FIDcourse | FIDsheet | FIDsubmission | FIDcorrectors
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show)

View File

@ -82,7 +82,7 @@ data Load -- = ByTutorial { countsToLoad :: Bool } | ByProportion { load :: Rati
= Load { byTutorial :: Maybe Bool -- ^ Just all from Tutorial, True if counting towards overall workload = Load { byTutorial :: Maybe Bool -- ^ Just all from Tutorial, True if counting towards overall workload
, byProportion :: Rational -- ^ workload proportion of all submission not assigned to tutorial leaders , byProportion :: Rational -- ^ workload proportion of all submission not assigned to tutorial leaders
} }
deriving (Show, Read, Eq) deriving (Show, Read, Eq, Ord)
derivePersistField "Load" derivePersistField "Load"
instance Semigroup Load where instance Semigroup Load where