Merge branch 'feat/exercises' into feat/pagination

This commit is contained in:
Gregor Kleen 2018-06-27 15:05:00 +02:00
commit 3c8f13b4dc
11 changed files with 207 additions and 13 deletions

View File

@ -1,4 +1,4 @@
SummerTerm year@Integer: Sommersemester #{tshow year} SummerTerm year@Integer: Sommersemester #{tshow year}
WinterTerm year@Integer: Wintersemester #{tshow year}/#{tshow $ succ year} WinterTerm year@Integer: Wintersemester #{tshow year}/#{tshow $ succ year}
PSLimitNonPositive: “pagesize” muss größer als null sein PSLimitNonPositive: “pagesize” muss größer als null sein
Page n@Int64: #{tshow n} Page n@Int64: #{tshow n}
@ -9,12 +9,13 @@ Term: Semester
TermPlaceholder: W/S + vierstellige Jahreszahl TermPlaceholder: W/S + vierstellige Jahreszahl
TermEditHeading: Semester editieren/anlegen TermEditHeading: Semester editieren/anlegen
LectureStart: Beginn Vorlesungen
Course: Kurs Course: Kurs
CourseNewOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich erstellt. CourseNewOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich erstellt.
CourseEditOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich geändert. CourseEditOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich geändert.
CourseNewDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester. CourseNewDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester.
CourseEditDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester. CourseEditDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester.
FFSheetName: Name
TermCourseListHeading tid@TermIdentifier: Kursübersicht #{termToText tid} TermCourseListHeading tid@TermIdentifier: Kursübersicht #{termToText tid}
TermCourseListTitle tid@TermIdentifier: Kurse #{termToText tid} TermCourseListTitle tid@TermIdentifier: Kurse #{termToText tid}
CourseEditHeading: Kurs editieren/anlegen CourseEditHeading: Kurs editieren/anlegen
@ -51,6 +52,7 @@ DeprecatedRoute: Diese Ansicht ist obsolet und könnte in Zukunft entfallen.
UnfreeMaterials: Die Materialien für diese Veranstaltung sind nicht allgemein freigegeben. UnfreeMaterials: Die Materialien für diese Veranstaltung sind nicht allgemein freigegeben.
UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung
Submission: Abgabenummer
SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt.
SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt.
SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe editieren/anlegen SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe editieren/anlegen
@ -59,6 +61,10 @@ SubmissionArchive: Zip-Archiv der Abgabedatei(en)
SubmissionFile: Datei zur Abgabe SubmissionFile: Datei zur Abgabe
SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem bÜbungsblatt. SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem bÜbungsblatt.
CorrectionsTitle: Zugewiesene Korrekturen
CourseCorrectionsTitle: Korrekturen für diesen Kurs
Corrector: Korrektor
EMail: E-Mail EMail: E-Mail
EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. 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. NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet.
@ -86,3 +92,5 @@ SheetSolution: Lösung
SheetMarking: Korrekturhinweise SheetMarking: Korrekturhinweise
MultiFileUploadInfo: (Mehrere Dateien mit Shift oder Strg auswählen) MultiFileUploadInfo: (Mehrere Dateien mit Shift oder Strg auswählen)
NrColumn: Nr

2
routes
View File

@ -49,6 +49,7 @@
/course/#TermId/#Text CourseR !lecturer: /course/#TermId/#Text CourseR !lecturer:
/show CShowR GET POST !free /show CShowR GET POST !free
/edit CEditR GET POST /edit CEditR GET POST
/corrections CourseCorrectionsR GET
/ex SheetListR GET !registered !materials /ex SheetListR GET !registered !materials
!/ex/new SheetNewR GET POST !/ex/new SheetNewR GET POST
/ex/#Text SheetR: /ex/#Text SheetR:
@ -60,6 +61,7 @@
!/sub/own SubmissionOwnR GET !free !/sub/own SubmissionOwnR GET !free
!/sub/#CryptoFileNameSubmission SubmissionR GET POST !owner !corrector !/sub/#CryptoFileNameSubmission SubmissionR GET POST !owner !corrector
/corrections CorrectionsR GET !free
-- TODO below -- TODO below
!/#{ZIPArchiveName SubmissionId} SubmissionDownloadArchiveR GET !deprecated !/#{ZIPArchiveName SubmissionId} SubmissionDownloadArchiveR GET !deprecated

View File

@ -48,6 +48,7 @@ import Handler.Term
import Handler.Course import Handler.Course
import Handler.Sheet import Handler.Sheet
import Handler.Submission import Handler.Submission
import Handler.Corrections
import Handler.CryptoIDDispatch import Handler.CryptoIDDispatch

View File

@ -40,6 +40,8 @@ import Data.ByteArray (convert)
import Crypto.Hash (Digest, SHAKE256) import Crypto.Hash (Digest, SHAKE256)
import Crypto.Hash.Conduit (sinkHash) import Crypto.Hash.Conduit (sinkHash)
import qualified Data.CryptoID (CryptoID) -- for DisplayAble instance only
import qualified Data.ByteString.Base64.URL as Base64 (encode) import qualified Data.ByteString.Base64.URL as Base64 (encode)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -73,13 +75,19 @@ import Handler.Utils.DateTime
import Control.Lens import Control.Lens
import Utils.Lens import Utils.Lens
-- -- TODO: Move me to appropriate Place
-- -- TODO: Move the following to the appropriate place, if DisplayAble is kept
instance DisplayAble TermId where instance DisplayAble TermId where
display = termToText . unTermKey display = termToText . unTermKey
instance DisplayAble UTCTime where instance DisplayAble UTCTime where
display = pack . formatTimeGerDT2 -- default Time Format to be used: 00.00.00 00:00 display = pack . formatTimeGerDT2 -- default Time Format to be used: 00.00.00 00:00
instance (PathPiece b) => DisplayAble (Data.CryptoID.CryptoID a b) where
display = toPathPiece -- requires import of Data.CryptoID here
-- -- MOVE ABOVE
-- infixl 9 :$: -- infixl 9 :$:
-- pattern a :$: b = a b -- pattern a :$: b = a b
@ -693,6 +701,8 @@ pageHeading (CourseR tid csh CShowR)
= Just $ do = Just $ do
Entity _ Course{..} <- handlerToWidget . runDB . getBy404 $ CourseTermShort tid csh Entity _ Course{..} <- handlerToWidget . runDB . getBy404 $ CourseTermShort tid csh
toWidget courseName toWidget courseName
pageHeading CorrectionsR
= Just $ i18nHeading MsgCorrectionsTitle
-- TODO: add headings for more single course- and single term-pages -- TODO: add headings for more single course- and single term-pages
pageHeading _ pageHeading _
= Nothing = Nothing

164
src/Handler/Corrections.hs Normal file
View File

@ -0,0 +1,164 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
module Handler.Corrections where
import Import
-- import System.FilePath (takeFileName)
import Handler.Utils
-- import Handler.Utils.Zip
-- import qualified Data.Set as Set
-- import qualified Data.Map as Map
-- import Data.Time
-- import qualified Data.Text as T
-- import Data.Function ((&))
--
import Colonnade hiding (fromMaybe, singleton)
-- import Yesod.Colonnade
--
-- import qualified Data.UUID.Cryptographic as UUID
-- import qualified Data.Conduit.List as C
import qualified Database.Esqueleto as E
-- import qualified Database.Esqueleto.Internal.Sql as E
import Control.Lens
-- import Control.Monad.Writer (MonadWriter(..), execWriterT)
-- import Network.Mime
type CorrectionsWhere = forall query expr backend . (E.Esqueleto query expr backend) =>
(expr (Entity Course), expr (Entity Sheet), expr (Entity Submission))
-> expr (E.Value Bool)
ratedBy :: Key User -> CorrectionsWhere
ratedBy uid (_course,_sheet,submission) = submission E.^. SubmissionRatingBy E.==. E.just (E.val uid)
courseIs :: Key Course -> CorrectionsWhere
courseIs cid (course,_sheet,_submission) = course E.^. CourseId E.==. E.val cid
type CorrectionTableData = DBRow (Entity Submission, Entity Sheet, (E.Value Text, E.Value Text, E.Value (Key Term), E.Value (Key School)))
colTerm :: Colonnade _ CorrectionTableData _
colTerm = widgetColonnade $ sortable (Just "term") (i18nCell MsgTerm)
$ \DBRow{ dbrOutput=(_, _, course) } ->
-- cell [whamlet| _{untermKey $ course ^. _3}|] -- lange, internationale Semester
textCell $ termToText $ unTermKey $ E.unValue $ course ^. _3 -- kurze Semsterkürzel
colCourse :: Colonnade _ CorrectionTableData _
colCourse = widgetColonnade $ sortable (Just "course") (i18nCell MsgCourse)
$ \DBRow{ dbrOutput=(_, _, course) } -> cell $
let tid = E.unValue $ course ^. _3
csh = E.unValue $ course ^. _2
in [whamlet|<a href=@{CourseR tid csh CShowR}>#{display csh}|]
colSheet :: Colonnade _ CorrectionTableData _
colSheet = widgetColonnade $ sortable (Just "sheet") (i18nCell MsgSheet)
$ \DBRow{ dbrOutput=(_, sheet, course) } -> cell $
let tid = E.unValue $ course ^. _3
csh = E.unValue $ course ^. _2
shn = sheetName $ entityVal sheet
in [whamlet|<a href=@{CSheetR tid csh shn SShowR}>#{display shn}|]
-- textCell $ sheetName $ entityVal sheet
colCorrector :: Colonnade _ CorrectionTableData _
colCorrector = widgetColonnade $ sortable (Just "corrector") (i18nCell MsgCorrector)
$ \DBRow{ dbrOutput=(submission, _, _) } ->
textCell $ display $ submissionRatingBy $ entityVal submission
colSubmissionLink :: Colonnade _ CorrectionTableData _
colSubmissionLink = widgetColonnade $ sortable Nothing (i18nCell MsgSubmission)
$ \DBRow{ dbrOutput=(submission, sheet, course) } -> cell $ do
let tid = E.unValue $ course ^. _3
csh = E.unValue $ course ^. _2
shn = sheetName $ entityVal sheet
cid <- encrypt (entityKey submission :: SubmissionId)
[whamlet|<a href=@{CSheetR tid csh shn (SubmissionR cid)}>#{display cid}|]
makeCorrectionsTable whereClause colChoices = do
let tableData :: E.InnerJoin (E.InnerJoin (E.SqlExpr (Entity Course))
(E.SqlExpr (Entity Sheet )))
(E.SqlExpr (Entity Submission))
-> E.SqlQuery _
tableData (course `E.InnerJoin` sheet `E.InnerJoin` submission) = do
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.where_ $ whereClause (course,sheet,submission)
let crse = ( course E.^. CourseName :: E.SqlExpr (E.Value Text)
, course E.^. CourseShorthand
, course E.^. CourseTerm
, course E.^. CourseSchool :: E.SqlExpr (E.Value SchoolId)
)
return (submission, sheet, crse)
dbTable def $ DBTable
{ dbtSQLQuery = tableData
, dbtColonnade = colChoices
, dbtSorting = [ ( "term"
, SortColumn $ \(course `E.InnerJoin` _ `E.InnerJoin` _ ) -> course E.^. CourseTerm
)
, ( "course"
, SortColumn $ \(course `E.InnerJoin` _ `E.InnerJoin` _ ) -> course E.^. CourseShorthand
)
-- TODO
]
, dbtFilter = mempty {- [ ( "term"
, FilterColumn $ \(course `E.InnerJoin` _ `E.InnerJoin` _ ) tids -> if
| Set.null tids -> E.val True :: E.SqlExpr (E.Value Bool)
| otherwise -> course E.^. CourseTerm `E.in_` E.valList (Set.toList tids)
)
] -}
, dbtAttrs = tableDefault
, dbtIdent = "corrections" :: Text
}
getCorrectionsR :: Handler Html
getCorrectionsR = do
uid <- requireAuthId
let whereClause = ratedBy uid
displayColumns = mconcat
[ dbRow
, colTerm
, colCourse
, colSheet
, colSubmissionLink
] -- Continue here
table <- makeCorrectionsTable whereClause displayColumns
defaultLayout $ do
setTitleI MsgCourseCorrectionsTitle
$(widgetFile "corrections")
getCourseCorrectionsR :: TermId -> Text -> Handler Html
getCourseCorrectionsR tid csh = do
cid <- runDB $ getBy404 $ CourseTermShort tid csh
let whereClause = courseIs $ entityKey cid
displayColumns = mconcat
[ dbRow
, colSheet
, colCorrector
, colSubmissionLink
] -- Continue here
table <- makeCorrectionsTable whereClause displayColumns
defaultLayout $ do
setTitleI MsgCorrectionsTitle
$(widgetFile "corrections")

View File

@ -52,7 +52,7 @@ getTermShowR = do
$else $else
#{termToText termName} #{termToText termName}
|] |]
, sortable (Just "lecture-start") "Beginn Vorlesungen" $ \(Entity _ Term{..},_) -> , sortable (Just "lecture-start") (i18nCell MsgLectureStart) $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureStart stringCell $ formatTimeGerWD termLectureStart
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) -> , sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureEnd stringCell $ formatTimeGerWD termLectureEnd

View File

@ -35,9 +35,6 @@ numberColonnade = headed "Nr" (fromString.show)
pairColonnade :: (Functor h) => Colonnade h a c -> Colonnade h b c -> Colonnade h (a,b) c pairColonnade :: (Functor h) => Colonnade h a c -> Colonnade h b c -> Colonnade h (a,b) c
pairColonnade a b = mconcat [ lmap fst a, lmap snd b] pairColonnade a b = mconcat [ lmap fst a, lmap snd b]
i18nCell :: RenderMessage site a => a -> Cell site
i18nCell msg = cell [whamlet|_{msg}|]
-- Table Modification -- Table Modification
encodeHeadedWidgetTableNumbered :: Attribute -> Colonnade Headed a (WidgetT site IO ()) -> [a] -> WidgetT site IO () encodeHeadedWidgetTableNumbered :: Attribute -> Colonnade Headed a (WidgetT site IO ()) -> [a] -> WidgetT site IO ()
@ -94,3 +91,5 @@ headedRowSelector toExternal fromExternal attrs colonnade tdata = do
return ( catMaybes <$> collectResult selectionResults return ( catMaybes <$> collectResult selectionResults
, encodeCellTable attrs (pairColonnade selColonnade colonnade) (zip [0..] tdata) , encodeCellTable attrs (pairColonnade selColonnade colonnade) (zip [0..] tdata)
) )

View File

@ -1,6 +1,7 @@
{-# LANGUAGE NoImplicitPrelude {-# LANGUAGE NoImplicitPrelude
, ExistentialQuantification , ExistentialQuantification
, RecordWildCards , RecordWildCards
, NamedFieldPuns
, OverloadedStrings , OverloadedStrings
, TemplateHaskell , TemplateHaskell
, QuasiQuotes , QuasiQuotes
@ -18,14 +19,14 @@
module Handler.Utils.Table.Pagination module Handler.Utils.Table.Pagination
( SortColumn(..), SortDirection(..) ( SortColumn(..), SortDirection(..)
, FilterColumn(..), IsFilterColumn , FilterColumn(..), IsFilterColumn
, DBRow(..), DBOutput , DBRow(..), dbRow, DBOutput
, DBTable(..), IsDBTable(..) , DBTable(..), IsDBTable(..)
, PaginationSettings(..) , PaginationSettings(..)
, PSValidator(..) , PSValidator(..)
, Sortable(..), sortable , Sortable(..), sortable
, dbTable , dbTable
, widgetColonnade, formColonnade , widgetColonnade, formColonnade
, textCell, stringCell, anchorCell , textCell, stringCell, i18nCell, anchorCell
, formCell, DBFormResult, getDBFormResult , formCell, DBFormResult, getDBFormResult
) where ) where
@ -55,6 +56,7 @@ import qualified Data.Map as Map
import Data.Profunctor (lmap) import Data.Profunctor (lmap)
import Colonnade hiding (bool, fromMaybe, singleton) import Colonnade hiding (bool, fromMaybe, singleton)
import qualified Colonnade (singleton)
import Colonnade.Encode import Colonnade.Encode
import Text.Hamlet (hamletFile) import Text.Hamlet (hamletFile)
@ -112,6 +114,9 @@ data DBRow r = DBRow
, dbrOutput :: r , dbrOutput :: r
} }
dbRow :: (Headedness h, IsDBTable m a) => Colonnade h (DBRow r) (DBCell m a)
dbRow = Colonnade.singleton (headednessPure $ textCell MsgNrColumn) $ \DBRow{ dbrIndex } -> textCell $ tshow dbrIndex
class DBOutput r r' where class DBOutput r r' where
dbProj :: r -> r' dbProj :: r -> r'
@ -331,8 +336,9 @@ formColonnade :: (Headedness h, Monoid a)
-> Colonnade h r (DBCell (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) (FormResult a)) -> Colonnade h r (DBCell (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) (FormResult a))
formColonnade = id formColonnade = id
textCell, stringCell :: (RenderMessage UniWorX msg, IsDBTable m a) => msg -> DBCell m a textCell, stringCell, i18nCell :: (RenderMessage UniWorX msg, IsDBTable m a) => msg -> DBCell m a
stringCell = textCell stringCell = textCell
i18nCell = textCell
textCell msg = cell [whamlet|_{msg}|] textCell msg = cell [whamlet|_{msg}|]
anchorCell :: IsDBTable m a anchorCell :: IsDBTable m a

View File

@ -16,7 +16,7 @@ import Language.Haskell.TH
-- Tuples -- -- Tuples --
------------ ------------
-- Alternatively uses lenses: "^. _3" projects the 3rd component of an n-tuple for any n >=3, requires import Control.Lens
projNI :: Int -> Int -> ExpQ -- generic projection gives I-th element of N-tuple, i.e. snd3 = $(projNI 3 2) --ghci -fth projNI :: Int -> Int -> ExpQ -- generic projection gives I-th element of N-tuple, i.e. snd3 = $(projNI 3 2) --ghci -fth
-- $(projN n m) :: (t1,..,tn) -> tm (for m<=n) -- $(projN n m) :: (t1,..,tn) -> tm (for m<=n)
projNI n i = lamE [pat] rhs projNI n i = lamE [pat] rhs

View File

@ -0,0 +1,2 @@
<div .container>
^{table}

View File

@ -4,7 +4,8 @@ $newline never
<thead> <thead>
<tr .table__row.table__row--head> <tr .table__row.table__row--head>
$forall widget <- wHeaders' $forall widget <- wHeaders'
^{widget} $# cell/header.hamlet $# cell/header.hamlet
^{widget}
$nothing $nothing
<tbody> <tbody>
$if null wRows $if null wRows
@ -15,4 +16,5 @@ $newline never
$forall row <- wRows $forall row <- wRows
<tr .table__row> <tr .table__row>
$forall widget <- row $forall widget <- row
^{widget} $# cell/body.hamlet $# cell/body.hamlet
^{widget}