From f8d0b021edcf254c161ff98e1183e9e4bfab0df9 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 26 Jun 2019 19:34:56 +0200 Subject: [PATCH] feat(forms): Introduce more convenient form validation --- ChangeLog.md | 4 +++ messages/uniworx/de.msg | 4 ++- src/Handler/Exam.hs | 16 ++++++++++-- src/Utils/Form.hs | 56 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 25616306d..2cfe46396 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,7 @@ + * Version 26.06.2019 + + Rudimentäre Unterstützung für Klausurbetrieb + * Version 07.06.2019 Abgaben können bestimmte Dateinamen und Endungen erzwingen diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index ef3910846..66a159c18 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -1148,4 +1148,6 @@ ExamRegisteredSuccess exam@ExamName: Erfolgreich zur Klausur #{exam} angemeldet ExamDeregisteredSuccess exam@ExamName: Erfolgreich von der Klausur #{exam} abgemeldet ExamRegistered: Angemeldet ExamNotRegistered: Nicht angemeldet -ExamRegistration: Anmeldung \ No newline at end of file +ExamRegistration: Anmeldung + +ExamEndMustBeAfterStart: Beginn der Klausur muss vor ihrem Ende liegen \ No newline at end of file diff --git a/src/Handler/Exam.hs b/src/Handler/Exam.hs index 5a7817339..d63ed3de9 100644 --- a/src/Handler/Exam.hs +++ b/src/Handler/Exam.hs @@ -25,6 +25,8 @@ import Text.Blaze.Html.Renderer.String (renderHtml) import qualified Data.CaseInsensitive as CI +import qualified Control.Monad.State.Class as State + getCExamListR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCExamListR tid ssh csh = do @@ -438,6 +440,16 @@ examTemplate cid = runMaybeT $ do , efCorrectors = Set.empty } + +validateExam :: (MonadHandler m, HandlerSite m ~ UniWorX) => FormValidator ExamForm m () +validateExam = do + ExamForm{..} <- State.get + + guardValidation MsgExamEndMustBeAfterStart $ NTop efEnd >= NTop (Just efStart) + + -- TODO + + getCExamNewR, postCExamNewR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCExamNewR = postCExamNewR postCExamNewR tid ssh csh = do @@ -446,7 +458,7 @@ postCExamNewR tid ssh csh = do template <- examTemplate cid return (cid, template) - ((newExamResult, newExamWidget), newExamEnctype) <- runFormPost $ examForm template + ((newExamResult, newExamWidget), newExamEnctype) <- runFormPost . validateForm validateExam $ examForm template formResult newExamResult $ \ExamForm{..} -> do insertRes <- runDBJobs $ do @@ -525,7 +537,7 @@ postEEditR tid ssh csh examn = do return (cid, eId, template) - ((editExamResult, editExamWidget), editExamEnctype) <- runFormPost . examForm $ Just template + ((editExamResult, editExamWidget), editExamEnctype) <- runFormPost . validateForm validateExam . examForm $ Just template formResult editExamResult $ \ExamForm{..} -> do insertRes <- runDBJobs $ do diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs index 3947e214d..86d757dec 100644 --- a/src/Utils/Form.hs +++ b/src/Utils/Form.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -fno-warn-deprecations #-} module Utils.Form where @@ -23,8 +24,11 @@ import qualified Data.Set as Set import Control.Monad.Trans.Maybe (MaybeT(..)) import Control.Monad.Reader.Class (MonadReader(..)) import Control.Monad.Writer.Class (MonadWriter(..)) -import Control.Monad.Trans.RWS (mapRWST) +import Control.Monad.State.Class (MonadState(..)) +import Control.Monad.Trans.RWS (RWST, execRWST, mapRWST) import Control.Monad.Trans.Except (ExceptT, runExceptT) +import Control.Monad.Fix (MonadFix) +import Control.Monad.Morph (MFunctor(..)) import Data.List ((!!)) @@ -779,6 +783,56 @@ prismAForm p outer form = review p <$> form inner where inner = outer >>= preview p +newtype FormValidator r m a = FormValidator { unFormValidator :: RWST () [SomeMessage (HandlerSite m)] r m a } + +deriving newtype instance Functor m => Functor (FormValidator r m) +deriving newtype instance Monad m => Applicative (FormValidator r m) +deriving newtype instance Monad m => Monad (FormValidator r m) +deriving newtype instance Monad m => MonadState r (FormValidator r m) +deriving newtype instance MonadFix m => MonadFix (FormValidator r m) +instance MonadTrans (FormValidator r) where + lift = FormValidator . lift + +validateForm :: MonadHandler m + => FormValidator a m () + -> (Markup -> MForm m (FormResult a, xml)) + -> (Markup -> MForm m (FormResult a, xml)) +validateForm valF form csrf = do + (res, xml) <- form csrf + res' <- for res $ lift . execRWST (unFormValidator valF) () + (, xml) <$> case res' of + FormSuccess (x, [] ) -> return $ FormSuccess x + FormSuccess (_, msgs) -> formFailure msgs + FormMissing -> return FormMissing + FormFailure errs -> return $ FormFailure errs + +validateFormDB :: ( MonadHandler m + , YesodPersist (HandlerSite m) + ) + => FormValidator a (YesodDB (HandlerSite m)) () + -> (Markup -> MForm m (FormResult a, xml)) + -> (Markup -> MForm m (FormResult a, xml)) +validateFormDB (FormValidator valF) = validateForm . FormValidator $ hoist (liftHandlerT . runDB) valF + +tellValidationError :: ( MonadHandler m + , RenderMessage (HandlerSite m) msg + ) + => msg -> FormValidator r m () +tellValidationError = FormValidator . tell . pure . SomeMessage + +guardValidation :: ( MonadHandler m + , RenderMessage (HandlerSite m) msg + ) + => msg -> Bool -> FormValidator r m () +guardValidation _ False = return () +guardValidation msg True = tellValidationError msg + +guardValidationM :: ( MonadHandler m + , RenderMessage (HandlerSite m) msg + ) + => msg -> m Bool -> FormValidator r m () +guardValidationM = (. lift) . (=<<) . guardValidation + ----------------------- -- Form Manipulation -- -----------------------