Merge remote-tracking branch 'origin/master' into 126-ubungsbetrieb-statistik-seiten-pro-kurs
This commit is contained in:
commit
4b58f42ab6
3
messages/button/de.msg
Normal file
3
messages/button/de.msg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
AmbiguousButtons: Mehrere Submit-Buttons aktiv
|
||||||
|
WrongButtonValue: Submit-Button hat falschen Wert
|
||||||
|
MultipleButtonValues: Submit-Button hat mehrere Werte
|
||||||
@ -1 +1,2 @@
|
|||||||
DummyIdent: Nutzer-Kennung
|
DummyIdent: Nutzer-Kennung
|
||||||
|
DummyNoFormData: Keine Formulardaten empfangen
|
||||||
@ -93,6 +93,7 @@ SheetEditOk tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetNa
|
|||||||
SheetNameDup tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: Es gibt bereits ein Übungsblatt #{sheetName} in diesem Kurs #{display tid}-#{display ssh}-#{csh}.
|
SheetNameDup tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: Es gibt bereits ein Übungsblatt #{sheetName} in diesem Kurs #{display tid}-#{display ssh}-#{csh}.
|
||||||
SheetDelHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{sheetName} wirklich aus Kurs #{display tid}-#{display ssh}-#{csh} herauslöschen? Alle assoziierten Abgaben und Korrekturen gehen ebenfalls verloren!
|
SheetDelHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{sheetName} wirklich aus Kurs #{display tid}-#{display ssh}-#{csh} herauslöschen? Alle assoziierten Abgaben und Korrekturen gehen ebenfalls verloren!
|
||||||
SheetDelOk tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{display tid}-#{display ssh}-#{csh}: #{sheetName} gelöscht.
|
SheetDelOk tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{display tid}-#{display ssh}-#{csh}: #{sheetName} gelöscht.
|
||||||
|
SheetDelHasSubmissions objs@Int: Inkl. #{tshow objs} #{pluralDE objs "Abgabe" "Abgaben"}!
|
||||||
|
|
||||||
SheetDeleteQuestion: Wollen Sie das unten aufgeführte Übungsblatt und alle zugehörigen Abgaben wirklich löschen?
|
SheetDeleteQuestion: Wollen Sie das unten aufgeführte Übungsblatt und alle zugehörigen Abgaben wirklich löschen?
|
||||||
SheetDeleted: Übungsblatt gelöscht
|
SheetDeleted: Übungsblatt gelöscht
|
||||||
@ -194,6 +195,7 @@ AddCorrector: Zusätzlicher Korrektor
|
|||||||
CorrectorExists email@UserEmail: #{email} ist bereits als Korrektor eingetragen
|
CorrectorExists email@UserEmail: #{email} ist bereits als Korrektor eingetragen
|
||||||
SheetCorrectorsTitle tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: Korrektoren für #{display tid}-#{display ssh}-#{csh} #{sheetName}
|
SheetCorrectorsTitle tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: Korrektoren für #{display tid}-#{display ssh}-#{csh} #{sheetName}
|
||||||
CountTutProp: Tutorien zählen gegen Proportion
|
CountTutProp: Tutorien zählen gegen Proportion
|
||||||
|
AutoAssignCorrs: Korrekturen am Ende der Abgabefrist automatisch zuteilen
|
||||||
Corrector: Korrektor
|
Corrector: Korrektor
|
||||||
Correctors: Korrektoren
|
Correctors: Korrektoren
|
||||||
CorState: Status
|
CorState: Status
|
||||||
@ -244,6 +246,7 @@ CorrUploadField: Korrekturen
|
|||||||
CorrUpload: Korrekturen hochladen
|
CorrUpload: Korrekturen hochladen
|
||||||
CorrSetCorrector: Korrektor zuweisen
|
CorrSetCorrector: Korrektor zuweisen
|
||||||
CorrAutoSetCorrector: Korrekturen verteilen
|
CorrAutoSetCorrector: Korrekturen verteilen
|
||||||
|
CorrDelete: Abgaben löschen
|
||||||
NatField name@Text: #{name} muss eine natürliche Zahl sein!
|
NatField name@Text: #{name} muss eine natürliche Zahl sein!
|
||||||
JSONFieldDecodeFailure aesonFailure@String: Konnte JSON nicht parsen: #{aesonFailure}
|
JSONFieldDecodeFailure aesonFailure@String: Konnte JSON nicht parsen: #{aesonFailure}
|
||||||
SecretJSONFieldDecryptFailure: Konnte versteckte vertrauliche Daten nicht entschlüsseln
|
SecretJSONFieldDecryptFailure: Konnte versteckte vertrauliche Daten nicht entschlüsseln
|
||||||
@ -387,11 +390,17 @@ MailSubmissionRatedIntro courseName@Text termDesc@Text: Ihre Abgabe im Kurs #{co
|
|||||||
MailSubjectSheetActive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} wurde herausgegeben
|
MailSubjectSheetActive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} wurde herausgegeben
|
||||||
MailSheetActiveIntro courseName@Text termDesc@Text sheetName@SheetName: Sie können nun #{sheetName} im Kurs #{courseName} (#{termDesc}) herunterladen.
|
MailSheetActiveIntro courseName@Text termDesc@Text sheetName@SheetName: Sie können nun #{sheetName} im Kurs #{courseName} (#{termDesc}) herunterladen.
|
||||||
|
|
||||||
|
MailSubjectSubmissionsUnassigned csh@CourseShorthand sheetName@SheetName: Abgaben zu #{sheetName} in #{csh} konnten nicht verteilt werden
|
||||||
|
MailSubmissionsUnassignedIntro n@Int courseName@Text termDesc@Text sheetName@SheetName: #{tshow n} Abgaben zu #{sheetName} im Kurs #{courseName} (#{termDesc}) konnten nicht automatisiert verteilt werden.
|
||||||
|
|
||||||
MailSubjectSheetSoonInactive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} kann nur noch kurze Zeit abgegeben werden
|
MailSubjectSheetSoonInactive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} kann nur noch kurze Zeit abgegeben werden
|
||||||
MailSheetSoonInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze.
|
MailSheetSoonInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze.
|
||||||
MailSubjectSheetInactive csh@CourseShorthand sheetName@SheetName: Abgabfrist für #{sheetName} in #{csh} abgelaufen
|
MailSubjectSheetInactive csh@CourseShorthand sheetName@SheetName: Abgabfrist für #{sheetName} in #{csh} abgelaufen
|
||||||
MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Die Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) beendet.
|
MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Die Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) beendet.
|
||||||
MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{display n} Abgaben wurden Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt.
|
|
||||||
|
MailSubjectCorrectionsAssigned csh@CourseShorthand sheetName@SheetName: Ihnen wurden Korrekturen zu #{sheetName} in #{csh} zugeteilt
|
||||||
|
MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{display n} #{pluralDE n "Abgabe wurde" "Abgaben wurden"} Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt.
|
||||||
|
|
||||||
MailEditNotifications: Benachrichtigungen ein-/ausschalten
|
MailEditNotifications: Benachrichtigungen ein-/ausschalten
|
||||||
MailSubjectSupport: Supportanfrage
|
MailSubjectSupport: Supportanfrage
|
||||||
|
|
||||||
@ -415,8 +424,8 @@ SheetTypeInfoNotGraded: Blätter ohne Wertung werden nirgends angerechnet, die B
|
|||||||
SheetTypeInfoBonus: Bonus Blätter zählen normal, erhöhen aber nicht die maximal erreichbare Punktzahl bzw. Anzahl zu bestehender Blätter.
|
SheetTypeInfoBonus: Bonus Blätter zählen normal, erhöhen aber nicht die maximal erreichbare Punktzahl bzw. Anzahl zu bestehender Blätter.
|
||||||
SheetGradingBonusIncluded: Erzielte Bonuspunkte wurden hier bereits zu den erreichten normalen Punkten hinzugezählt.
|
SheetGradingBonusIncluded: Erzielte Bonuspunkte wurden hier bereits zu den erreichten normalen Punkten hinzugezählt.
|
||||||
SummaryTitle: Zusammenfassung über alle
|
SummaryTitle: Zusammenfassung über alle
|
||||||
SheetGradingSummaryTitle count@Integer: #{display count} #{pluralDE count "Blatt" "Blätter"}
|
SheetGradingSummaryTitle intgr@Integer: #{display intgr} #{pluralDE intgr "Blatt" "Blätter"}
|
||||||
SubmissionGradingSummaryTitle count@Integer: #{display count} #{pluralDE count "Abgabe" "Abgaben"}
|
SubmissionGradingSummaryTitle intgr@Integer: #{display intgr} #{pluralDE intgr "Abgabe" "Abgaben"}
|
||||||
|
|
||||||
SheetTypeBonus': Bonus
|
SheetTypeBonus': Bonus
|
||||||
SheetTypeNormal': Normal
|
SheetTypeNormal': Normal
|
||||||
@ -439,6 +448,7 @@ NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen
|
|||||||
NotificationTriggerSheetSoonInactive: Ich kann ein Übungsblatt bald nicht mehr abgeben
|
NotificationTriggerSheetSoonInactive: Ich kann ein Übungsblatt bald nicht mehr abgeben
|
||||||
NotificationTriggerSheetInactive: Abgabefrist eines meiner Übungsblätter ist abgelaufen
|
NotificationTriggerSheetInactive: Abgabefrist eines meiner Übungsblätter ist abgelaufen
|
||||||
NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugeteilt
|
NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugeteilt
|
||||||
|
NotificationTriggerCorrectionsNotDistributed: Abgaben eines meiner Übungsblätter konnten keinem Korrektur zugeteilt werden
|
||||||
|
|
||||||
CorrCreate: Abgaben erstellen
|
CorrCreate: Abgaben erstellen
|
||||||
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
|
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
|
||||||
|
|||||||
@ -12,6 +12,7 @@ Sheet
|
|||||||
solutionFrom UTCTime Maybe
|
solutionFrom UTCTime Maybe
|
||||||
uploadMode UploadMode
|
uploadMode UploadMode
|
||||||
submissionMode SheetSubmissionMode default='UserSubmissions'
|
submissionMode SheetSubmissionMode default='UserSubmissions'
|
||||||
|
autoDistribute Bool default=false
|
||||||
CourseSheet course name
|
CourseSheet course name
|
||||||
SheetEdit
|
SheetEdit
|
||||||
user UserId
|
user UserId
|
||||||
|
|||||||
@ -13,10 +13,12 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
|
|
||||||
|
|
||||||
data DummyMessage = MsgDummyIdent
|
data DummyMessage = MsgDummyIdent
|
||||||
|
| MsgDummyNoFormData
|
||||||
|
|
||||||
|
|
||||||
dummyForm :: ( RenderMessage site FormMessage
|
dummyForm :: ( RenderMessage site FormMessage
|
||||||
, RenderMessage site DummyMessage
|
, RenderMessage site DummyMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, YesodPersist site
|
, YesodPersist site
|
||||||
, SqlBackendCanRead (YesodPersistBackend site)
|
, SqlBackendCanRead (YesodPersistBackend site)
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
@ -33,6 +35,7 @@ dummyLogin :: ( YesodAuth site
|
|||||||
, SqlBackendCanRead (YesodPersistBackend site)
|
, SqlBackendCanRead (YesodPersistBackend site)
|
||||||
, RenderMessage site FormMessage
|
, RenderMessage site FormMessage
|
||||||
, RenderMessage site DummyMessage
|
, RenderMessage site DummyMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
, Show (ButtonCssClass site)
|
, Show (ButtonCssClass site)
|
||||||
) => AuthPlugin site
|
) => AuthPlugin site
|
||||||
@ -46,7 +49,9 @@ dummyLogin = AuthPlugin{..}
|
|||||||
FormFailure errs -> do
|
FormFailure errs -> do
|
||||||
lift . forM_ errs $ addMessage Error . toHtml
|
lift . forM_ errs $ addMessage Error . toHtml
|
||||||
redirect LoginR
|
redirect LoginR
|
||||||
FormMissing -> redirect LoginR
|
FormMissing -> do
|
||||||
|
lift $ addMessageI Warning MsgDummyNoFormData
|
||||||
|
redirect LoginR
|
||||||
FormSuccess ident ->
|
FormSuccess ident ->
|
||||||
lift . setCredsRedirect $ Creds "dummy" (CI.original ident) []
|
lift . setCredsRedirect $ Creds "dummy" (CI.original ident) []
|
||||||
apDispatch _ _ = notFound
|
apDispatch _ _ = notFound
|
||||||
|
|||||||
@ -53,6 +53,7 @@ userPrincipalName = Ldap.Attr "userPrincipalName"
|
|||||||
|
|
||||||
campusForm :: ( RenderMessage site FormMessage
|
campusForm :: ( RenderMessage site FormMessage
|
||||||
, RenderMessage site CampusMessage
|
, RenderMessage site CampusMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
, Show (ButtonCssClass site)
|
, Show (ButtonCssClass site)
|
||||||
) => AForm (HandlerT site IO) CampusLogin
|
) => AForm (HandlerT site IO) CampusLogin
|
||||||
@ -65,6 +66,7 @@ campusLogin :: forall site.
|
|||||||
( YesodAuth site
|
( YesodAuth site
|
||||||
, RenderMessage site FormMessage
|
, RenderMessage site FormMessage
|
||||||
, RenderMessage site CampusMessage
|
, RenderMessage site CampusMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
, Show (ButtonCssClass site)
|
, Show (ButtonCssClass site)
|
||||||
) => LdapConf -> LdapPool -> AuthPlugin site
|
) => LdapConf -> LdapPool -> AuthPlugin site
|
||||||
|
|||||||
@ -27,6 +27,7 @@ data PWHashMessage = MsgPWHashIdent
|
|||||||
|
|
||||||
hashForm :: ( RenderMessage site FormMessage
|
hashForm :: ( RenderMessage site FormMessage
|
||||||
, RenderMessage site PWHashMessage
|
, RenderMessage site PWHashMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
, Show (ButtonCssClass site)
|
, Show (ButtonCssClass site)
|
||||||
) => AForm (HandlerT site IO) HashLogin
|
) => AForm (HandlerT site IO) HashLogin
|
||||||
@ -41,6 +42,7 @@ hashLogin :: ( YesodAuth site
|
|||||||
, SqlBackendCanRead (YesodPersistBackend site)
|
, SqlBackendCanRead (YesodPersistBackend site)
|
||||||
, RenderMessage site FormMessage
|
, RenderMessage site FormMessage
|
||||||
, RenderMessage site PWHashMessage
|
, RenderMessage site PWHashMessage
|
||||||
|
, RenderMessage site ButtonMessage
|
||||||
, Button site SubmitButton
|
, Button site SubmitButton
|
||||||
, Show (ButtonCssClass site)
|
, Show (ButtonCssClass site)
|
||||||
) => PWHashAlgorithm -> AuthPlugin site
|
) => PWHashAlgorithm -> AuthPlugin site
|
||||||
|
|||||||
@ -161,6 +161,7 @@ mkMessage "UniWorX" "messages/uniworx" "de"
|
|||||||
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
|
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
|
||||||
mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de"
|
mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de"
|
||||||
mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de"
|
mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de"
|
||||||
|
mkMessageVariant "UniWorX" "Button" "messages/button" "de"
|
||||||
|
|
||||||
-- This instance is required to use forms. You can modify renderMessage to
|
-- This instance is required to use forms. You can modify renderMessage to
|
||||||
-- achieve customized and internationalized form validation messages.
|
-- achieve customized and internationalized form validation messages.
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Handler.Utils
|
|||||||
import Handler.Utils.Submission
|
import Handler.Utils.Submission
|
||||||
import Handler.Utils.Table.Cells
|
import Handler.Utils.Table.Cells
|
||||||
import Handler.Utils.SheetType
|
import Handler.Utils.SheetType
|
||||||
|
import Handler.Utils.Delete
|
||||||
-- import Handler.Utils.Zip
|
-- import Handler.Utils.Zip
|
||||||
|
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
@ -39,8 +40,6 @@ import qualified Database.Esqueleto as E
|
|||||||
|
|
||||||
-- import Network.Mime
|
-- import Network.Mime
|
||||||
|
|
||||||
import Web.PathPieces
|
|
||||||
|
|
||||||
import Text.Hamlet (ihamletFile)
|
import Text.Hamlet (ihamletFile)
|
||||||
|
|
||||||
import Database.Persist.Sql (updateWhereCount)
|
import Database.Persist.Sql (updateWhereCount)
|
||||||
@ -286,24 +285,29 @@ makeCorrectionsTable whereClause dbtColonnade psValidator dbtProj' dbtParams = d
|
|||||||
data ActionCorrections = CorrDownload
|
data ActionCorrections = CorrDownload
|
||||||
| CorrSetCorrector
|
| CorrSetCorrector
|
||||||
| CorrAutoSetCorrector
|
| CorrAutoSetCorrector
|
||||||
|
| CorrDelete
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
||||||
instance PathPiece ActionCorrections where
|
|
||||||
fromPathPiece = readFromPathPiece
|
|
||||||
toPathPiece = showToPathPiece
|
|
||||||
|
|
||||||
instance RenderMessage UniWorX ActionCorrections where
|
instance Universe ActionCorrections
|
||||||
renderMessage m ls CorrDownload = renderMessage m ls MsgCorrDownload
|
instance Finite ActionCorrections
|
||||||
renderMessage m ls CorrSetCorrector = renderMessage m ls MsgCorrSetCorrector
|
|
||||||
renderMessage m ls CorrAutoSetCorrector = renderMessage m ls MsgCorrAutoSetCorrector
|
nullaryPathPiece ''ActionCorrections $ camelToPathPiece' 1
|
||||||
|
embedRenderMessage ''UniWorX ''ActionCorrections id
|
||||||
|
|
||||||
data ActionCorrectionsData = CorrDownloadData
|
data ActionCorrectionsData = CorrDownloadData
|
||||||
| CorrSetCorrectorData (Maybe UserId)
|
| CorrSetCorrectorData (Maybe UserId)
|
||||||
| CorrAutoSetCorrectorData SheetId
|
| CorrAutoSetCorrectorData SheetId
|
||||||
|
| CorrDeleteData
|
||||||
|
|
||||||
correctionsR :: _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerT UniWorX IO) ActionCorrectionsData) -> Handler TypedContent
|
correctionsR :: _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerT UniWorX IO) ActionCorrectionsData) -> Handler TypedContent
|
||||||
correctionsR whereClause (formColonnade -> displayColumns) psValidator actions = do
|
correctionsR whereClause (formColonnade -> displayColumns) psValidator actions = do
|
||||||
Just currentRoute <- getCurrentRoute -- This should never be called from a 404 handler
|
Just currentRoute <- getCurrentRoute -- This should never be called from a 404 handler
|
||||||
|
|
||||||
|
postDeleteR $ \drRecords -> (submissionDeleteRoute drRecords)
|
||||||
|
{ drAbort = SomeRoute currentRoute
|
||||||
|
, drSuccess = SomeRoute currentRoute
|
||||||
|
}
|
||||||
|
|
||||||
((actionRes', table), statistics) <- runDB $ do
|
((actionRes', table), statistics) <- runDB $ do
|
||||||
-- Query for Table
|
-- Query for Table
|
||||||
tableRes <- makeCorrectionsTable whereClause displayColumns psValidator return def
|
tableRes <- makeCorrectionsTable whereClause displayColumns psValidator return def
|
||||||
@ -395,6 +399,12 @@ correctionsR whereClause (formColonnade -> displayColumns) psValidator actions =
|
|||||||
unassigned' <- forM (Set.toList stillUnassigned) $ \sid -> encrypt sid :: DB CryptoFileNameSubmission
|
unassigned' <- forM (Set.toList stillUnassigned) $ \sid -> encrypt sid :: DB CryptoFileNameSubmission
|
||||||
addMessage Warning =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsNotAssignedAuto.hamlet") mr)
|
addMessage Warning =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsNotAssignedAuto.hamlet") mr)
|
||||||
redirect currentRoute
|
redirect currentRoute
|
||||||
|
FormSuccess (CorrDeleteData, subs) -> do
|
||||||
|
subs' <- Set.fromList <$> forM (Set.toList subs) decrypt -- Set is not traversable
|
||||||
|
getDeleteR (submissionDeleteRoute subs')
|
||||||
|
{ drAbort = SomeRoute currentRoute
|
||||||
|
, drSuccess = SomeRoute currentRoute
|
||||||
|
}
|
||||||
|
|
||||||
fmap toTypedContent . defaultLayout $ do
|
fmap toTypedContent . defaultLayout $ do
|
||||||
setTitleI MsgCourseCorrectionsTitle
|
setTitleI MsgCourseCorrectionsTitle
|
||||||
@ -415,10 +425,13 @@ correctionsR whereClause (formColonnade -> displayColumns) psValidator actions =
|
|||||||
|
|
||||||
type ActionCorrections' = (ActionCorrections, AForm (HandlerT UniWorX IO) ActionCorrectionsData)
|
type ActionCorrections' = (ActionCorrections, AForm (HandlerT UniWorX IO) ActionCorrectionsData)
|
||||||
|
|
||||||
downloadAction :: ActionCorrections'
|
downloadAction, deleteAction :: ActionCorrections'
|
||||||
downloadAction = ( CorrDownload
|
downloadAction = ( CorrDownload
|
||||||
, pure CorrDownloadData
|
, pure CorrDownloadData
|
||||||
)
|
)
|
||||||
|
deleteAction = ( CorrDelete
|
||||||
|
, pure CorrDeleteData
|
||||||
|
)
|
||||||
|
|
||||||
assignAction :: Either CourseId SheetId -> ActionCorrections'
|
assignAction :: Either CourseId SheetId -> ActionCorrections'
|
||||||
assignAction selId = ( CorrSetCorrector
|
assignAction selId = ( CorrSetCorrector
|
||||||
@ -490,6 +503,7 @@ postCCorrectionsR tid ssh csh = do
|
|||||||
correctionsR whereClause colonnade psValidator $ Map.fromList
|
correctionsR whereClause colonnade psValidator $ Map.fromList
|
||||||
[ downloadAction
|
[ downloadAction
|
||||||
, assignAction (Left cid)
|
, assignAction (Left cid)
|
||||||
|
, deleteAction
|
||||||
]
|
]
|
||||||
|
|
||||||
getSSubsR, postSSubsR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler TypedContent
|
getSSubsR, postSSubsR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler TypedContent
|
||||||
@ -513,6 +527,7 @@ postSSubsR tid ssh csh shn = do
|
|||||||
[ downloadAction
|
[ downloadAction
|
||||||
, assignAction (Right shid)
|
, assignAction (Right shid)
|
||||||
, autoAssignAction shid
|
, autoAssignAction shid
|
||||||
|
, deleteAction
|
||||||
]
|
]
|
||||||
|
|
||||||
correctionData :: TermId -> SchoolId -> CourseShorthand -> SheetName -> _ -- CryptoFileNameSubmission -> _
|
correctionData :: TermId -> SchoolId -> CourseShorthand -> SheetName -> _ -- CryptoFileNameSubmission -> _
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Utils.Lens
|
|||||||
-- import Utils.DB
|
-- import Utils.DB
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Table.Cells
|
import Handler.Utils.Table.Cells
|
||||||
|
import Handler.Utils.Course
|
||||||
import Handler.Utils.Delete
|
import Handler.Utils.Delete
|
||||||
|
|
||||||
-- import Data.Time
|
-- import Data.Time
|
||||||
@ -391,28 +392,10 @@ getCDeleteR, postCDeleteR :: TermId -> SchoolId -> CourseShorthand -> Handler Ht
|
|||||||
getCDeleteR = postCDeleteR
|
getCDeleteR = postCDeleteR
|
||||||
postCDeleteR tid ssh csh = do
|
postCDeleteR tid ssh csh = do
|
||||||
Entity cId _ <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh
|
Entity cId _ <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
deleteR DeleteRoute
|
deleteR $ (courseDeleteRoute $ Set.singleton cId)
|
||||||
{ drRecords = Set.singleton cId
|
{ drAbort = SomeRoute $ CourseR tid ssh csh CShowR
|
||||||
, drRenderRecord = \(Entity _ Course{courseName, courseTerm, courseSchool}) -> do
|
, drSuccess = SomeRoute $ TermSchoolCourseListR tid ssh
|
||||||
School{schoolName} <- getJust courseSchool
|
|
||||||
return [whamlet|
|
|
||||||
#{courseName} (_{SomeMessage $ ShortTermIdentifier (unTermKey courseTerm)}, #{schoolName})
|
|
||||||
|]
|
|
||||||
, drRecordConfirmString = \(Entity _ Course{courseShorthand, courseTerm, courseSchool}) ->
|
|
||||||
return [st|#{unSchoolKey courseSchool}/#{termToText (unTermKey courseTerm)}/#{courseShorthand}|]
|
|
||||||
, drCaption = SomeMessage MsgCourseDeleteQuestion
|
|
||||||
, drSuccessMessage = SomeMessage MsgCourseDeleted
|
|
||||||
, drAbort = SomeRoute $ CourseR tid ssh csh CShowR
|
|
||||||
, drSuccess = SomeRoute CourseListR
|
|
||||||
}
|
}
|
||||||
{- TODO
|
|
||||||
| False -- DELETE -- TODO: This no longer works that way!!! See new way in Handler.Term.termEditHandler
|
|
||||||
, Just cid <- cfCourseId res -> do
|
|
||||||
runDB $ deleteCascade cid -- TODO Sicherheitsabfrage einbauen!
|
|
||||||
let cti = toPathPiece $ cfTerm res
|
|
||||||
addMessage Info [shamlet| Kurs #{cti}/#{cfShort res} wurde gelöscht!|]
|
|
||||||
redirect $ TermCourseListR $ cfTerm res
|
|
||||||
-}
|
|
||||||
|
|
||||||
|
|
||||||
-- | Course Creation and Editing
|
-- | Course Creation and Editing
|
||||||
|
|||||||
@ -106,9 +106,9 @@ postProfileDataR = do
|
|||||||
defaultLayout
|
defaultLayout
|
||||||
$(widgetFile "deletedUser")
|
$(widgetFile "deletedUser")
|
||||||
|
|
||||||
(FormSuccess BtnAbort ) -> do
|
-- (FormSuccess BtnAbort ) -> do
|
||||||
addMessageI Info MsgAborted
|
-- addMessageI Info MsgAborted
|
||||||
redirect ProfileDataR
|
-- redirect ProfileDataR
|
||||||
_other -> getProfileDataR
|
_other -> getProfileDataR
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -498,6 +498,7 @@ handleSheetEdit tid ssh csh msId template dbAction = do
|
|||||||
, sheetSolutionFrom = sfSolutionFrom
|
, sheetSolutionFrom = sfSolutionFrom
|
||||||
, sheetUploadMode = sfUploadMode
|
, sheetUploadMode = sfUploadMode
|
||||||
, sheetSubmissionMode = sfSubmissionMode
|
, sheetSubmissionMode = sfSubmissionMode
|
||||||
|
, sheetAutoDistribute = False
|
||||||
}
|
}
|
||||||
mbsid <- dbAction newSheet
|
mbsid <- dbAction newSheet
|
||||||
case mbsid of
|
case mbsid of
|
||||||
@ -533,20 +534,8 @@ getSDelR, postSDelR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Han
|
|||||||
getSDelR = postSDelR
|
getSDelR = postSDelR
|
||||||
postSDelR tid ssh csh shn = do
|
postSDelR tid ssh csh shn = do
|
||||||
sid <- runDB $ fetchSheetId tid ssh csh shn
|
sid <- runDB $ fetchSheetId tid ssh csh shn
|
||||||
deleteR DeleteRoute
|
deleteR $ (sheetDeleteRoute $ Set.singleton sid)
|
||||||
{ drRecords = Set.singleton sid
|
{ drAbort = SomeRoute $ CSheetR tid ssh csh shn SShowR
|
||||||
, drRenderRecord = \(Entity _ Sheet{sheetName, sheetCourse}) -> do
|
|
||||||
Course{courseTerm, courseSchool, courseName} <- getJust sheetCourse
|
|
||||||
School{schoolName} <- getJust courseSchool
|
|
||||||
return [whamlet|
|
|
||||||
#{sheetName} (_{ShortTermIdentifier (unTermKey courseTerm)}, #{schoolName}, #{courseName})
|
|
||||||
|]
|
|
||||||
, drRecordConfirmString = \(Entity _ Sheet{sheetName, sheetCourse}) -> do
|
|
||||||
Course{courseTerm, courseSchool, courseShorthand} <- getJust sheetCourse
|
|
||||||
return [st|#{termToText (unTermKey courseTerm)}/#{unSchoolKey courseSchool}/#{courseShorthand}/#{sheetName}|]
|
|
||||||
, drCaption = SomeMessage MsgSheetDeleteQuestion
|
|
||||||
, drSuccessMessage = SomeMessage MsgSheetDeleted
|
|
||||||
, drAbort = SomeRoute $ CSheetR tid ssh csh shn SShowR
|
|
||||||
, drSuccess = SomeRoute $ CourseR tid ssh csh SheetListR
|
, drSuccess = SomeRoute $ CourseR tid ssh csh SheetListR
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,7 +597,7 @@ defaultLoads shid = do
|
|||||||
toMap = foldMap $ \(E.Value uid, E.Value load, E.Value state) -> Map.singleton uid (state, load)
|
toMap = foldMap $ \(E.Value uid, E.Value load, E.Value state) -> Map.singleton uid (state, load)
|
||||||
|
|
||||||
|
|
||||||
correctorForm :: SheetId -> MForm Handler (FormResult (Set SheetCorrector), [FieldView UniWorX])
|
correctorForm :: SheetId -> MForm Handler (FormResult (Bool {- ^ autoDistribute -} , Set SheetCorrector), [FieldView UniWorX])
|
||||||
correctorForm shid = do
|
correctorForm shid = do
|
||||||
cListIdent <- newFormIdent
|
cListIdent <- newFormIdent
|
||||||
let
|
let
|
||||||
@ -621,7 +610,7 @@ correctorForm shid = do
|
|||||||
let
|
let
|
||||||
currentLoads :: DB Loads
|
currentLoads :: DB Loads
|
||||||
currentLoads = foldMap (\(Entity _ SheetCorrector{..}) -> Map.singleton sheetCorrectorUser (sheetCorrectorState, sheetCorrectorLoad)) <$> selectList [ SheetCorrectorSheet ==. shid ] []
|
currentLoads = foldMap (\(Entity _ SheetCorrector{..}) -> Map.singleton sheetCorrectorUser (sheetCorrectorState, sheetCorrectorLoad)) <$> selectList [ SheetCorrectorSheet ==. shid ] []
|
||||||
(defaultLoads', currentLoads') <- lift . runDB $ (,) <$> defaultLoads shid <*> currentLoads
|
(autoDistribute, defaultLoads', currentLoads') <- lift . runDB $ (,,) <$> (sheetAutoDistribute <$> getJust shid) <*> defaultLoads shid <*> currentLoads
|
||||||
loads' <- fmap (Map.fromList [(uid, (CorrectorNormal, mempty)) | uid <- formCIDs] `Map.union`) $ if
|
loads' <- fmap (Map.fromList [(uid, (CorrectorNormal, mempty)) | uid <- formCIDs] `Map.union`) $ if
|
||||||
| Map.null currentLoads'
|
| Map.null currentLoads'
|
||||||
, null formCIDs -> defaultLoads' <$ when (not $ Map.null defaultLoads') (addMessageI Warning MsgCorrectorsDefaulted)
|
, null formCIDs -> defaultLoads' <$ when (not $ Map.null defaultLoads') (addMessageI Warning MsgCorrectorsDefaulted)
|
||||||
@ -633,6 +622,7 @@ correctorForm shid = do
|
|||||||
didDelete = any (flip Set.member deletions) formCIDs
|
didDelete = any (flip Set.member deletions) formCIDs
|
||||||
|
|
||||||
(countTutRes, countTutView) <- mreq checkBoxField (fsm MsgCountTutProp) . Just $ any (\(_, Load{..}) -> fromMaybe False byTutorial) $ Map.elems loads'
|
(countTutRes, countTutView) <- mreq checkBoxField (fsm MsgCountTutProp) . Just $ any (\(_, Load{..}) -> fromMaybe False byTutorial) $ Map.elems loads'
|
||||||
|
(autoDistributeRes, autoDistributeView) <- mreq checkBoxField (fsm MsgAutoAssignCorrs) (Just autoDistribute)
|
||||||
let
|
let
|
||||||
tutorField :: Field Handler [UserEmail]
|
tutorField :: Field Handler [UserEmail]
|
||||||
tutorField = convertField (map CI.mk) (map CI.original) $ multiEmailField
|
tutorField = convertField (map CI.mk) (map CI.original) $ multiEmailField
|
||||||
@ -726,23 +716,25 @@ correctorForm shid = do
|
|||||||
cID <- encrypt uid :: WidgetT UniWorX IO CryptoUUIDUser
|
cID <- encrypt uid :: WidgetT UniWorX IO CryptoUUIDUser
|
||||||
toWidget [hamlet|<input name="#{toPathPiece cID}-del" type=hidden value=yes>|]
|
toWidget [hamlet|<input name="#{toPathPiece cID}-del" type=hidden value=yes>|]
|
||||||
|
|
||||||
return (corrResults, [ countTutView
|
return ( (,) <$> autoDistributeRes <*> corrResults
|
||||||
, FieldView
|
, [ autoDistributeView
|
||||||
{ fvLabel = text $ mr MsgCorrectors
|
, countTutView
|
||||||
, fvTooltip = Nothing
|
, FieldView
|
||||||
, fvId = ""
|
{ fvLabel = text $ mr MsgCorrectors
|
||||||
, fvInput = Yesod.encodeCellTable tableDefault corrColonnade corrData >> mapM_ idField corrData >> mapM_ delField deletions'
|
, fvTooltip = Nothing
|
||||||
, fvErrors = Nothing
|
, fvId = ""
|
||||||
, fvRequired = True
|
, fvInput = Yesod.encodeCellTable tableDefault corrColonnade corrData >> mapM_ idField corrData >> mapM_ delField deletions'
|
||||||
}
|
, fvErrors = Nothing
|
||||||
, addTutView
|
, fvRequired = True
|
||||||
{ fvInput = [whamlet|
|
}
|
||||||
<div>
|
, addTutView
|
||||||
^{fvInput addTutView}
|
{ fvInput = [whamlet|
|
||||||
<button type=submit formnovalidate data-formnorequired>Hinzufügen
|
<div>
|
||||||
|]
|
^{fvInput addTutView}
|
||||||
}
|
<button type=submit formnovalidate data-formnorequired>Hinzufügen
|
||||||
])
|
|]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
-- Eingabebox für Korrektor hinzufügen
|
-- Eingabebox für Korrektor hinzufügen
|
||||||
-- Eingabe für Korrekt ausgefüllt: FormMissing zurückschicken um dann Feld hinzuzufügen
|
-- Eingabe für Korrekt ausgefüllt: FormMissing zurückschicken um dann Feld hinzuzufügen
|
||||||
@ -756,7 +748,8 @@ getSCorrR tid ssh csh shn = do
|
|||||||
|
|
||||||
case res of
|
case res of
|
||||||
FormFailure errs -> mapM_ (addMessage Error . toHtml) errs
|
FormFailure errs -> mapM_ (addMessage Error . toHtml) errs
|
||||||
FormSuccess res' -> runDB $ do
|
FormSuccess (autoDistribute, res') -> runDB $ do
|
||||||
|
update shid [ SheetAutoDistribute =. autoDistribute ]
|
||||||
deleteWhere [SheetCorrectorSheet ==. shid]
|
deleteWhere [SheetCorrectorSheet ==. shid]
|
||||||
insertMany_ $ Set.toList res'
|
insertMany_ $ Set.toList res'
|
||||||
addMessageI Success MsgCorrectorsUpdated
|
addMessageI Success MsgCorrectorsUpdated
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import Network.Mime
|
|||||||
import Data.Monoid (Any(..))
|
import Data.Monoid (Any(..))
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
-- import qualified Data.Maybe
|
-- import qualified Data.Maybe
|
||||||
import qualified Data.Text as Text
|
|
||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
|
|
||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
@ -397,30 +396,7 @@ getSubDelR, postSubDelR :: TermId -> SchoolId -> CourseShorthand -> SheetName ->
|
|||||||
getSubDelR = postSubDelR
|
getSubDelR = postSubDelR
|
||||||
postSubDelR tid ssh csh shn cID = do
|
postSubDelR tid ssh csh shn cID = do
|
||||||
subId <- runDB $ submissionMatchesSheet tid ssh csh shn cID
|
subId <- runDB $ submissionMatchesSheet tid ssh csh shn cID
|
||||||
deleteR DeleteRoute
|
deleteR $ (submissionDeleteRoute $ Set.singleton subId)
|
||||||
{ drRecords = Set.singleton subId
|
{ drAbort = SomeRoute $ CSubmissionR tid ssh csh shn cID SubShowR
|
||||||
, drRenderRecord = \(Entity subId' Submission{submissionSheet}) -> do
|
|
||||||
Sheet{sheetName, sheetCourse} <- getJust submissionSheet
|
|
||||||
Course{courseName, courseSchool, courseTerm} <- getJust sheetCourse
|
|
||||||
School{schoolName} <- getJust courseSchool
|
|
||||||
subUsers <- selectList [SubmissionUserSubmission ==. subId'] []
|
|
||||||
subNames <- fmap (sortOn snd) . forM subUsers $ \(Entity _ SubmissionUser{submissionUserUser}) -> (userDisplayName &&& userSurname) <$> getJust submissionUserUser
|
|
||||||
return [whamlet|
|
|
||||||
$newline never
|
|
||||||
<ul .list--comma-separated .list--inline .list--iconless>
|
|
||||||
$forall (dName, sName) <- subNames
|
|
||||||
<li>^{nameWidget dName sName}
|
|
||||||
(_{ShortTermIdentifier (unTermKey courseTerm)}, #{schoolName}, #{courseName}, #{sheetName})
|
|
||||||
|]
|
|
||||||
, drRecordConfirmString = \(Entity subId' Submission{submissionSheet}) -> do
|
|
||||||
Sheet{sheetName, sheetCourse} <- getJust submissionSheet
|
|
||||||
Course{courseShorthand, courseSchool, courseTerm} <- getJust sheetCourse
|
|
||||||
subUsers <- selectList [SubmissionUserSubmission ==. subId'] []
|
|
||||||
subNames <- fmap sort . forM subUsers $ \(Entity _ SubmissionUser{submissionUserUser}) -> userSurname <$> getJust submissionUserUser
|
|
||||||
let subNames' = Text.intercalate ", " subNames
|
|
||||||
return [st|#{termToText (unTermKey courseTerm)}/#{unSchoolKey courseSchool}/#{courseShorthand}/#{sheetName}/#{subNames'}|]
|
|
||||||
, drCaption = SomeMessage $ MsgSubmissionsDeleteQuestion 1
|
|
||||||
, drSuccessMessage = SomeMessage $ MsgSubmissionsDeleted 1
|
|
||||||
, drAbort = SomeRoute $ CSubmissionR tid ssh csh shn cID SubShowR
|
|
||||||
, drSuccess = SomeRoute $ CSheetR tid ssh csh shn SShowR
|
, drSuccess = SomeRoute $ CSheetR tid ssh csh shn SShowR
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/Handler/Utils/Course.hs
Normal file
27
src/Handler/Utils/Course.hs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module Handler.Utils.Course where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
import Handler.Utils.Delete
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
|
||||||
|
courseDeleteRoute :: Set CourseId -> DeleteRoute Course
|
||||||
|
courseDeleteRoute drRecords = DeleteRoute
|
||||||
|
{ drRecords
|
||||||
|
, drGetInfo = \(course `E.InnerJoin` school) -> do
|
||||||
|
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
||||||
|
E.orderBy [E.asc $ course E.^. CourseName]
|
||||||
|
return (course E.^. CourseName, school E.^. SchoolShorthand, school E.^. SchoolName, course E.^. CourseTerm)
|
||||||
|
, drUnjoin = \(course `E.InnerJoin` _) -> course
|
||||||
|
, drRenderRecord = \(E.Value cName, _, E.Value sName, E.Value tid') ->
|
||||||
|
return [whamlet|
|
||||||
|
#{cName} (_{ShortTermIdentifier (unTermKey tid')}, #{sName})
|
||||||
|
|]
|
||||||
|
, drRecordConfirmString = \(E.Value cName, E.Value ssh', _, E.Value tid') ->
|
||||||
|
return [st|#{termToText (unTermKey tid')}/#{ssh'}/#{cName}|]
|
||||||
|
, drCaption = SomeMessage MsgCourseDeleteQuestion
|
||||||
|
, drSuccessMessage = SomeMessage MsgCourseDeleted
|
||||||
|
, drAbort = error "drAbort undefined"
|
||||||
|
, drSuccess = error "drSuccess undefined"
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
module Handler.Utils.Delete
|
module Handler.Utils.Delete
|
||||||
( DeleteRoute(..)
|
( DeleteRoute(..)
|
||||||
, deleteR
|
, deleteR
|
||||||
|
, postDeleteR, getDeleteR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
@ -13,63 +14,89 @@ import qualified Data.Set as Set
|
|||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
import Control.Monad.Trans.Random
|
|
||||||
import System.Random (mkStdGen)
|
|
||||||
import System.Random.Shuffle (shuffleM)
|
|
||||||
import qualified Crypto.Hash as Crypto (hash)
|
|
||||||
import Crypto.Hash (Digest, SHAKE128)
|
|
||||||
|
|
||||||
import qualified Data.ByteArray as ByteArray
|
|
||||||
|
|
||||||
import Data.Char (isAlphaNum)
|
import Data.Char (isAlphaNum)
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Internal.Sql as E (SqlSelect)
|
||||||
|
import qualified Database.Esqueleto.Internal.Language as E (From)
|
||||||
|
|
||||||
data DeleteRoute = forall record. (DeleteCascade record SqlBackend, Hashable (Key record)) => DeleteRoute
|
|
||||||
|
data DeleteRoute record = forall tables infoExpr info. (E.SqlSelect infoExpr info, E.From E.SqlQuery E.SqlExpr SqlBackend tables) => DeleteRoute
|
||||||
{ drRecords :: Set (Key record)
|
{ drRecords :: Set (Key record)
|
||||||
, drRenderRecord :: Entity record -> ReaderT SqlBackend (HandlerT UniWorX IO) Widget
|
, drUnjoin :: tables -> E.SqlExpr (Entity record)
|
||||||
, drRecordConfirmString :: Entity record -> ReaderT SqlBackend (HandlerT UniWorX IO) Text
|
, drGetInfo :: tables -> E.SqlQuery infoExpr
|
||||||
|
, drRenderRecord :: info -> ReaderT SqlBackend (HandlerT UniWorX IO) Widget
|
||||||
|
, drRecordConfirmString :: info -> ReaderT SqlBackend (HandlerT UniWorX IO) Text
|
||||||
, drCaption
|
, drCaption
|
||||||
, drSuccessMessage :: SomeMessage UniWorX
|
, drSuccessMessage :: SomeMessage UniWorX
|
||||||
, drAbort
|
, drAbort
|
||||||
, drSuccess :: SomeRoute UniWorX
|
, drSuccess :: SomeRoute UniWorX
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirmForm :: ( MonadHandler m, HandlerSite m ~ UniWorX )
|
||||||
|
=> Text -- ^ Confirmation string
|
||||||
|
-> AForm m Bool
|
||||||
|
confirmForm confirmString = flip traverseAForm aform $ \(inpConfirmStr, BtnDelete) -> if
|
||||||
|
| ((==) `on` map (CI.mk . filter isAlphaNum) . Text.words) confirmString inpConfirmStr
|
||||||
|
-> return $ pure True
|
||||||
|
| otherwise
|
||||||
|
-> formFailure [MsgDeleteConfirmationWrong]
|
||||||
|
where
|
||||||
|
aform = (,)
|
||||||
|
<$> areq confirmField (fslI MsgDeleteConfirmation) Nothing
|
||||||
|
<*> disambiguateButtons (combinedButtonFieldF "")
|
||||||
|
confirmField
|
||||||
|
| multiple = convertField unTextarea Textarea textareaField
|
||||||
|
| otherwise = textField
|
||||||
|
multiple = length (filter (not . Text.null . Text.strip) $ Text.lines confirmString) > 1
|
||||||
|
|
||||||
deleteR :: DeleteRoute -> Handler Html
|
confirmForm' :: PersistEntity record => Set (Key record) -> Text -> Form Bool
|
||||||
deleteR DeleteRoute{..} = do
|
confirmForm' drRecords confirmString = addDeleteTargets . identForm FIDDelete . renderAForm FormStandard $ confirmForm confirmString
|
||||||
targets <- runDB . mconcatForM drRecords $ \rKey -> do
|
where
|
||||||
ent <- Entity rKey <$> get404 rKey
|
addDeleteTargets :: Form a -> Form a
|
||||||
recordWdgt <- drRenderRecord ent
|
addDeleteTargets form csrf = do
|
||||||
recordConfirmString <- drRecordConfirmString ent
|
(_, fvTargets) <- mreq secretJsonField ("" & addName (toPathPiece PostDeleteTarget)) (Just drRecords)
|
||||||
return $ pure (recordWdgt, recordConfirmString)
|
over _2 (mappend $ fvInput fvTargets) <$> form csrf
|
||||||
|
|
||||||
cIDKey <- hash . (ByteArray.convert :: Digest (SHAKE128 64) -> ByteString) . Crypto.hash <$> getsYesod appCryptoIDKey
|
|
||||||
|
|
||||||
let sTargets = evalRand (shuffleM targets) . mkStdGen . hashWithSalt cIDKey $ Set.toList drRecords
|
postDeleteR :: ( DeleteCascade record SqlBackend )
|
||||||
confirmString = Text.unlines $ map (Text.strip . view _2) sTargets
|
=> (Set (Key record) -> DeleteRoute record) -- ^ Construct `DeleteRoute` based on incoming record keys
|
||||||
confirmField
|
-> Handler ()
|
||||||
| Set.size drRecords <= 1 = textField
|
-- | Perform deletion
|
||||||
| otherwise = convertField unTextarea Textarea textareaField
|
postDeleteR mkRoute = do
|
||||||
|
drResult <- fmap (fmap mkRoute) . runInputPost . iopt secretJsonField $ toPathPiece PostDeleteTarget
|
||||||
((deleteFormRes, deleteFormWdgt), deleteFormEnctype) <- runFormPost . identForm FIDDelete . renderAForm FormStandard $ (,)
|
|
||||||
<$> areq confirmField (fslI MsgDeleteConfirmation) Nothing
|
|
||||||
<*> combinedButtonFieldF ""
|
|
||||||
|
|
||||||
formResult deleteFormRes $ \case
|
void . for drResult $ \DeleteRoute{..} -> do
|
||||||
(_, catMaybes -> [BtnAbort]) ->
|
confirmString <- fmap Text.unlines . runDB $ mapM drRecordConfirmString <=< E.select . E.from $ \t -> drGetInfo t <* E.where_ (drUnjoin t E.^. persistIdField `E.in_` E.valList (Set.toList drRecords))
|
||||||
redirect drAbort
|
|
||||||
(inpConfirmStr, catMaybes -> [BtnDelete])
|
((confirmRes, _), _) <- runFormPost $ confirmForm' drRecords confirmString
|
||||||
| ((==) `on` map (CI.mk . filter isAlphaNum) . Text.words) confirmString inpConfirmStr
|
|
||||||
-> do
|
formResult confirmRes $ \case
|
||||||
runDB $ do
|
True -> do
|
||||||
forM_ drRecords deleteCascade
|
runDB $ do
|
||||||
addMessageI Success drSuccessMessage
|
forM_ drRecords deleteCascade
|
||||||
redirect drSuccess
|
addMessageI Success drSuccessMessage
|
||||||
| otherwise
|
redirect drSuccess
|
||||||
-> addMessageI Error MsgDeleteConfirmationWrong
|
False ->
|
||||||
_other -> return ()
|
redirect drAbort
|
||||||
|
|
||||||
|
|
||||||
|
getDeleteR :: (DeleteCascade record SqlBackend) => DeleteRoute record -> Handler a
|
||||||
|
getDeleteR DeleteRoute{..} = do
|
||||||
|
targets <- runDB $ mapM (\i -> (,) <$> drRenderRecord i <*> drRecordConfirmString i) <=< E.select . E.from $ \t -> drGetInfo t <* E.where_ (drUnjoin t E.^. persistIdField `E.in_` E.valList (Set.toList drRecords))
|
||||||
|
|
||||||
|
let confirmString = Text.unlines $ view _2 <$> targets
|
||||||
|
|
||||||
|
(deleteFormWdgt, deleteFormEnctype) <- generateFormPost $ confirmForm' drRecords confirmString
|
||||||
|
|
||||||
Just targetRoute <- getCurrentRoute
|
Just targetRoute <- getCurrentRoute
|
||||||
|
|
||||||
defaultLayout
|
sendResponse =<<
|
||||||
$(widgetFile "widgets/delete-confirmation")
|
defaultLayout $(widgetFile "widgets/delete-confirmation")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deleteR :: (DeleteCascade record SqlBackend) => DeleteRoute record -> Handler Html
|
||||||
|
deleteR dr = do
|
||||||
|
postDeleteR $ \drRecords -> dr {drRecords}
|
||||||
|
getDeleteR dr
|
||||||
|
|||||||
@ -23,8 +23,6 @@ import qualified Data.Text as T
|
|||||||
import Yesod.Form.Functions (parseHelper)
|
import Yesod.Form.Functions (parseHelper)
|
||||||
import Yesod.Form.Bootstrap3
|
import Yesod.Form.Bootstrap3
|
||||||
|
|
||||||
import Web.PathPieces (showToPathPiece, readFromPathPiece)
|
|
||||||
|
|
||||||
import Handler.Utils.Zip
|
import Handler.Utils.Zip
|
||||||
import qualified Data.Conduit.List as C
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
@ -53,29 +51,26 @@ import Data.Aeson.Text (encodeToLazyText)
|
|||||||
-- Buttons (new version ) --
|
-- Buttons (new version ) --
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
data BtnDelete = BtnDelete | BtnAbort
|
data BtnDelete = BtnDelete
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
||||||
|
|
||||||
instance Universe BtnDelete
|
instance Universe BtnDelete
|
||||||
instance Finite BtnDelete
|
instance Finite BtnDelete
|
||||||
|
|
||||||
instance PathPiece BtnDelete where -- for displaying the button only, not really for paths
|
nullaryPathPiece ''BtnDelete $ camelToPathPiece' 1
|
||||||
toPathPiece = showToPathPiece
|
|
||||||
fromPathPiece = readFromPathPiece
|
|
||||||
|
|
||||||
instance Button UniWorX BtnDelete where
|
instance Button UniWorX BtnDelete where
|
||||||
label BtnDelete = [whamlet|_{MsgBtnDelete}|]
|
label BtnDelete = [whamlet|_{MsgBtnDelete}|]
|
||||||
label BtnAbort = [whamlet|_{MsgBtnAbort}|]
|
|
||||||
|
|
||||||
cssClass BtnDelete = BCDanger
|
cssClass BtnDelete = BCDanger
|
||||||
cssClass BtnAbort = BCDefault
|
|
||||||
|
|
||||||
data RegisterButton = BtnRegister | BtnDeregister
|
data RegisterButton = BtnRegister | BtnDeregister
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
||||||
|
|
||||||
instance PathPiece RegisterButton where
|
instance Universe RegisterButton
|
||||||
toPathPiece = showToPathPiece
|
instance Finite RegisterButton
|
||||||
fromPathPiece = readFromPathPiece
|
|
||||||
|
nullaryPathPiece ''RegisterButton $ camelToPathPiece' 1
|
||||||
|
|
||||||
instance Button UniWorX RegisterButton where
|
instance Button UniWorX RegisterButton where
|
||||||
label BtnRegister = [whamlet|_{MsgBtnRegister}|]
|
label BtnRegister = [whamlet|_{MsgBtnRegister}|]
|
||||||
@ -87,9 +82,10 @@ instance Button UniWorX RegisterButton where
|
|||||||
data AdminHijackUserButton = BtnHijack
|
data AdminHijackUserButton = BtnHijack
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
||||||
|
|
||||||
instance PathPiece AdminHijackUserButton where
|
instance Universe AdminHijackUserButton
|
||||||
toPathPiece = showToPathPiece
|
instance Finite AdminHijackUserButton
|
||||||
fromPathPiece = readFromPathPiece
|
|
||||||
|
nullaryPathPiece ''AdminHijackUserButton $ camelToPathPiece' 1
|
||||||
|
|
||||||
instance Button UniWorX AdminHijackUserButton where
|
instance Button UniWorX AdminHijackUserButton where
|
||||||
label BtnHijack = [whamlet|_{MsgBtnHijack}|]
|
label BtnHijack = [whamlet|_{MsgBtnHijack}|]
|
||||||
@ -109,7 +105,10 @@ instance Button UniWorX BtnSubmitDelete where
|
|||||||
cssClass BtnSubmit' = BCPrimary
|
cssClass BtnSubmit' = BCPrimary
|
||||||
cssClass BtnDelete' = BCDanger
|
cssClass BtnDelete' = BCDanger
|
||||||
|
|
||||||
nullaryPathPiece ''BtnSubmitDelete (camelToPathPiece' 1 . dropSuffix "'")
|
btnValidate _ BtnSubmit' = True
|
||||||
|
btnValidate _ BtnDelete' = False
|
||||||
|
|
||||||
|
nullaryPathPiece ''BtnSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'"
|
||||||
|
|
||||||
|
|
||||||
-- -- Looks like a button, but is just a link (e.g. for create course, etc.)
|
-- -- Looks like a button, but is just a link (e.g. for create course, etc.)
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
module Handler.Utils.Sheet where
|
module Handler.Utils.Sheet where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
import Handler.Utils.Delete
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
import qualified Database.Esqueleto.Internal.Sql as E
|
import qualified Database.Esqueleto.Internal.Sql as E
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fetchSheetAux :: ( BaseBackend backend ~ SqlBackend
|
fetchSheetAux :: ( BaseBackend backend ~ SqlBackend
|
||||||
, E.SqlSelect b a
|
, E.SqlSelect b a
|
||||||
, Typeable a, MonadHandler m, IsPersistBackend backend
|
, Typeable a, MonadHandler m, IsPersistBackend backend
|
||||||
@ -41,3 +40,31 @@ fetchSheetId tid ssh cid shn = E.unValue <$> fetchSheetAux (E.^. SheetId) tid ss
|
|||||||
|
|
||||||
fetchSheetIdCourseId :: TermId -> SchoolId -> CourseShorthand -> SheetName -> YesodDB UniWorX (Key Sheet, Key Course)
|
fetchSheetIdCourseId :: TermId -> SchoolId -> CourseShorthand -> SheetName -> YesodDB UniWorX (Key Sheet, Key Course)
|
||||||
fetchSheetIdCourseId tid ssh cid shn = bimap E.unValue E.unValue <$> fetchSheetAux ((,) <$> (E.^. SheetId) <*> (E.^. SheetCourse)) tid ssh cid shn
|
fetchSheetIdCourseId tid ssh cid shn = bimap E.unValue E.unValue <$> fetchSheetAux ((,) <$> (E.^. SheetId) <*> (E.^. SheetCourse)) tid ssh cid shn
|
||||||
|
|
||||||
|
|
||||||
|
sheetDeleteRoute :: Set SheetId -> DeleteRoute Sheet
|
||||||
|
sheetDeleteRoute drRecords = DeleteRoute
|
||||||
|
{ drRecords
|
||||||
|
, drGetInfo = \(sheet `E.InnerJoin` course `E.InnerJoin` school) -> do
|
||||||
|
E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool
|
||||||
|
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
|
||||||
|
let submissions = E.sub_select . E.from $ \submission -> do
|
||||||
|
E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId
|
||||||
|
return E.countRows
|
||||||
|
E.orderBy [E.asc $ sheet E.^. SheetName]
|
||||||
|
return (submissions, sheet E.^. SheetName, course E.^. CourseShorthand, course E.^. CourseName, school E.^. SchoolShorthand, school E.^. SchoolName, course E.^. CourseTerm)
|
||||||
|
, drUnjoin = \(sheet `E.InnerJoin` _ `E.InnerJoin` _) -> sheet
|
||||||
|
, drRenderRecord = \(E.Value submissions, E.Value shn', _, E.Value cName, _, E.Value sName, E.Value tid') ->
|
||||||
|
return [whamlet|
|
||||||
|
$newline never
|
||||||
|
#{shn'} (_{SomeMessage $ ShortTermIdentifier (unTermKey tid')}, #{sName}, #{cName})
|
||||||
|
$if submissions /= 0
|
||||||
|
<i>_{SomeMessage $ MsgSheetDelHasSubmissions submissions}
|
||||||
|
|]
|
||||||
|
, drRecordConfirmString = \(E.Value submissions, E.Value shn', E.Value csh', _, E.Value ssh', _, E.Value tid') ->
|
||||||
|
return $ [st|#{termToText (unTermKey tid')}/#{ssh'}/#{csh'}/#{shn'}|] <> bool mempty [st| + #{tshow submissions} Subs|] (submissions /= 0)
|
||||||
|
, drCaption = SomeMessage MsgSheetDeleteQuestion
|
||||||
|
, drSuccessMessage = SomeMessage MsgSheetDeleted
|
||||||
|
, drAbort = error "drAbort undefined"
|
||||||
|
, drSuccess = error "drSuccess undefined"
|
||||||
|
}
|
||||||
|
|||||||
@ -8,10 +8,11 @@ module Handler.Utils.Submission
|
|||||||
, msgSubmissionErrors -- wrap around sinkSubmission/sinkMultiSubmission, but outside of runDB!
|
, msgSubmissionErrors -- wrap around sinkSubmission/sinkMultiSubmission, but outside of runDB!
|
||||||
, sinkSubmission, sinkMultiSubmission
|
, sinkSubmission, sinkMultiSubmission
|
||||||
, submissionMatchesSheet
|
, submissionMatchesSheet
|
||||||
|
, submissionDeleteRoute
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import hiding (joinPath)
|
import Import hiding (joinPath)
|
||||||
import Jobs
|
import Jobs.Queue
|
||||||
import Prelude (lcm)
|
import Prelude (lcm)
|
||||||
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))
|
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))
|
||||||
|
|
||||||
@ -37,11 +38,10 @@ import Data.Ratio
|
|||||||
import Data.Monoid (Monoid, Any(..), Sum(..))
|
import Data.Monoid (Monoid, Any(..), Sum(..))
|
||||||
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
||||||
|
|
||||||
import Handler.Utils.Rating hiding (extractRatings)
|
import Handler.Utils
|
||||||
import qualified Handler.Utils.Rating as Rating (extractRatings)
|
import qualified Handler.Utils.Rating as Rating (extractRatings)
|
||||||
import Handler.Utils.Zip
|
|
||||||
import Handler.Utils.Sheet
|
|
||||||
import Handler.Utils.Submission.TH
|
import Handler.Utils.Submission.TH
|
||||||
|
import Handler.Utils.Delete
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
@ -601,3 +601,40 @@ submissionMatchesSheet tid ssh csh shn cid = do
|
|||||||
Submission{..} <- get404 sid
|
Submission{..} <- get404 sid
|
||||||
when (shid /= submissionSheet) $ invalidArgsI [MsgSubmissionWrongSheet]
|
when (shid /= submissionSheet) $ invalidArgsI [MsgSubmissionWrongSheet]
|
||||||
return sid
|
return sid
|
||||||
|
|
||||||
|
|
||||||
|
submissionDeleteRoute :: Set SubmissionId -> DeleteRoute Submission
|
||||||
|
submissionDeleteRoute drRecords = DeleteRoute
|
||||||
|
{ drRecords
|
||||||
|
, drUnjoin = \(submission `E.InnerJoin` _ `E.InnerJoin` _ `E.InnerJoin` _) -> submission
|
||||||
|
, drGetInfo = \(submission `E.InnerJoin` sheet `E.InnerJoin` course `E.InnerJoin` school) -> do
|
||||||
|
E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool
|
||||||
|
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
|
||||||
|
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
||||||
|
let lastEdit = E.sub_select . E.from $ \submissionEdit -> do
|
||||||
|
E.where_ $ submissionEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId
|
||||||
|
E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime]
|
||||||
|
E.limit 1
|
||||||
|
return $ submissionEdit E.^. SubmissionEditTime
|
||||||
|
E.orderBy [E.desc lastEdit]
|
||||||
|
return (submission E.^. SubmissionId, sheet E.^. SheetName, course E.^. CourseShorthand, course E.^. CourseName, school E.^. SchoolShorthand, school E.^. SchoolName, course E.^. CourseTerm)
|
||||||
|
, drRenderRecord = \(E.Value subId', E.Value shn', _, E.Value cName, _, E.Value sName, E.Value tid') -> do
|
||||||
|
subUsers <- selectList [SubmissionUserSubmission ==. subId'] []
|
||||||
|
subNames <- fmap (sortOn snd) . forM subUsers $ \(Entity _ SubmissionUser{submissionUserUser}) -> (userDisplayName &&& userSurname) <$> getJust submissionUserUser
|
||||||
|
return [whamlet|
|
||||||
|
$newline never
|
||||||
|
<ul .list--comma-separated .list--inline .list--iconless>
|
||||||
|
$forall (dName, sName) <- subNames
|
||||||
|
<li>^{nameWidget dName sName}
|
||||||
|
(_{ShortTermIdentifier (unTermKey tid')}, #{sName}, #{cName}, #{shn'})
|
||||||
|
|]
|
||||||
|
, drRecordConfirmString = \(E.Value subId', E.Value shn', E.Value csh', _, E.Value ssh', _, E.Value tid') -> do
|
||||||
|
subUsers <- selectList [SubmissionUserSubmission ==. subId'] []
|
||||||
|
subNames <- fmap sort . forM subUsers $ \(Entity _ SubmissionUser{submissionUserUser}) -> userSurname <$> getJust submissionUserUser
|
||||||
|
let subNames' = Text.intercalate ", " subNames
|
||||||
|
return [st|#{termToText (unTermKey tid')}/#{ssh'}/#{csh'}/#{shn'}/#{subNames'}|]
|
||||||
|
, drCaption = SomeMessage $ MsgSubmissionsDeleteQuestion 1
|
||||||
|
, drSuccessMessage = SomeMessage $ MsgSubmissionsDeleted 1
|
||||||
|
, drAbort = error "drAbort undefined"
|
||||||
|
, drSuccess = error "drSuccess undefined"
|
||||||
|
}
|
||||||
|
|||||||
@ -471,7 +471,10 @@ dbParamsFormWrap DBParamsForm{..} tableForm frag = do
|
|||||||
return . (res,) $ do
|
return . (res,) $ do
|
||||||
btnId <- newIdent
|
btnId <- newIdent
|
||||||
act <- traverse toTextUrl dbParamsFormAction
|
act <- traverse toTextUrl dbParamsFormAction
|
||||||
let submitField = buttonField BtnSubmit
|
let submitField :: Field Handler SubmitButton
|
||||||
|
submitField = buttonField BtnSubmit
|
||||||
|
submitView :: Widget
|
||||||
|
submitView = fieldView submitField btnId "" mempty (Right BtnSubmit) False
|
||||||
enctype' = bool id (mappend $ fieldEnctype submitField) dbParamsFormAddSubmit enctype
|
enctype' = bool id (mappend $ fieldEnctype submitField) dbParamsFormAddSubmit enctype
|
||||||
$(widgetFile "table/form-wrap")
|
$(widgetFile "table/form-wrap")
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import Jobs.Handler.SendTestEmail
|
|||||||
import Jobs.Handler.QueueNotification
|
import Jobs.Handler.QueueNotification
|
||||||
import Jobs.Handler.HelpRequest
|
import Jobs.Handler.HelpRequest
|
||||||
import Jobs.Handler.SetLogSettings
|
import Jobs.Handler.SetLogSettings
|
||||||
|
import Jobs.Handler.DistributeCorrections
|
||||||
|
|
||||||
|
|
||||||
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
||||||
|
|||||||
@ -71,6 +71,15 @@ determineCrontab = execWriterT $ do
|
|||||||
, cronRateLimit = appNotificationRateLimit
|
, cronRateLimit = appNotificationRateLimit
|
||||||
, cronNotAfter = Left appNotificationExpiration
|
, cronNotAfter = Left appNotificationExpiration
|
||||||
}
|
}
|
||||||
|
when sheetAutoDistribute $
|
||||||
|
tell $ HashMap.singleton
|
||||||
|
(JobCtlQueue $ JobDistributeCorrections nSheet)
|
||||||
|
Cron
|
||||||
|
{ cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo
|
||||||
|
, cronRepeat = CronRepeatNever
|
||||||
|
, cronRateLimit = 3600 -- Irrelevant due to `cronRepeat`
|
||||||
|
, cronNotAfter = Left nominalDay
|
||||||
|
}
|
||||||
|
|
||||||
sheetSubmissions <- lift $ collateSubmissions <$>
|
sheetSubmissions <- lift $ collateSubmissions <$>
|
||||||
selectList [SubmissionRatingBy !=. Nothing, SubmissionSheet ==. nSheet] []
|
selectList [SubmissionRatingBy !=. Nothing, SubmissionSheet ==. nSheet] []
|
||||||
|
|||||||
21
src/Jobs/Handler/DistributeCorrections.hs
Normal file
21
src/Jobs/Handler/DistributeCorrections.hs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module Jobs.Handler.DistributeCorrections
|
||||||
|
( dispatchJobDistributeCorrections
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Jobs.Queue
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Reader (mapReaderT)
|
||||||
|
|
||||||
|
import Handler.Utils.Submission
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
|
||||||
|
dispatchJobDistributeCorrections :: SheetId
|
||||||
|
-> Handler ()
|
||||||
|
dispatchJobDistributeCorrections jSheet = runDBJobs $ do
|
||||||
|
(_, unassigned) <- mapReaderT lift $ assignSubmissions jSheet Nothing
|
||||||
|
unless (Set.null unassigned) $
|
||||||
|
queueDBJob . JobQueueNotification $ NotificationCorrectionsNotDistributed jSheet
|
||||||
@ -22,26 +22,37 @@ dispatchJobQueueNotification jNotification = runDBJobs . setSerializable $ do
|
|||||||
|
|
||||||
|
|
||||||
determineNotificationCandidates :: Notification -> DB [Entity User]
|
determineNotificationCandidates :: Notification -> DB [Entity User]
|
||||||
determineNotificationCandidates NotificationSubmissionRated{..} = E.select . E.from $ \(user `E.InnerJoin` submissionUser) -> do
|
determineNotificationCandidates NotificationSubmissionRated{..}
|
||||||
E.on $ user E.^. UserId E.==. submissionUser E.^. SubmissionUserUser
|
= E.select . E.from $ \(user `E.InnerJoin` submissionUser) -> do
|
||||||
E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. E.val nSubmission
|
E.on $ user E.^. UserId E.==. submissionUser E.^. SubmissionUserUser
|
||||||
return user
|
E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. E.val nSubmission
|
||||||
determineNotificationCandidates NotificationSheetActive{..} = E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do
|
return user
|
||||||
E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse
|
determineNotificationCandidates NotificationSheetActive{..}
|
||||||
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
|
= E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do
|
||||||
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse
|
||||||
return user
|
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
|
||||||
determineNotificationCandidates NotificationSheetSoonInactive{..} = E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do
|
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
||||||
E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse
|
return user
|
||||||
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
|
determineNotificationCandidates NotificationSheetSoonInactive{..}
|
||||||
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
= E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do
|
||||||
return user
|
E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse
|
||||||
determineNotificationCandidates NotificationSheetInactive{..} = E.select . E.from $ \(user `E.InnerJoin` lecturer `E.InnerJoin` sheet) -> do
|
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
|
||||||
E.on $ lecturer E.^. LecturerCourse E.==. sheet E.^. SheetCourse
|
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
||||||
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
|
return user
|
||||||
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
determineNotificationCandidates NotificationSheetInactive{..}
|
||||||
return user
|
= E.select . E.from $ \(user `E.InnerJoin` lecturer `E.InnerJoin` sheet) -> do
|
||||||
determineNotificationCandidates NotificationCorrectionsAssigned{..} = selectList [UserId ==. nUser] []
|
E.on $ lecturer E.^. LecturerCourse E.==. sheet E.^. SheetCourse
|
||||||
|
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
|
||||||
|
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
||||||
|
return user
|
||||||
|
determineNotificationCandidates NotificationCorrectionsAssigned{..}
|
||||||
|
= selectList [UserId ==. nUser] []
|
||||||
|
determineNotificationCandidates NotificationCorrectionsNotDistributed{nSheet}
|
||||||
|
= E.select . E.from $ \(user `E.InnerJoin` lecturer `E.InnerJoin` sheet) -> do
|
||||||
|
E.on $ lecturer E.^. LecturerCourse E.==. sheet E.^. SheetCourse
|
||||||
|
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
|
||||||
|
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
|
||||||
|
return user
|
||||||
|
|
||||||
classifyNotification :: Notification -> DB NotificationTrigger
|
classifyNotification :: Notification -> DB NotificationTrigger
|
||||||
classifyNotification NotificationSubmissionRated{..} = do
|
classifyNotification NotificationSubmissionRated{..} = do
|
||||||
@ -53,5 +64,6 @@ classifyNotification NotificationSheetActive{} = return NTSheetActive
|
|||||||
classifyNotification NotificationSheetSoonInactive{} = return NTSheetSoonInactive
|
classifyNotification NotificationSheetSoonInactive{} = return NTSheetSoonInactive
|
||||||
classifyNotification NotificationSheetInactive{} = return NTSheetInactive
|
classifyNotification NotificationSheetInactive{} = return NTSheetInactive
|
||||||
classifyNotification NotificationCorrectionsAssigned{} = return NTCorrectionsAssigned
|
classifyNotification NotificationCorrectionsAssigned{} = return NTCorrectionsAssigned
|
||||||
|
classifyNotification NotificationCorrectionsNotDistributed{} = return NTCorrectionsNotDistributed
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import Jobs.Handler.SendNotification.SubmissionRated
|
|||||||
import Jobs.Handler.SendNotification.SheetActive
|
import Jobs.Handler.SendNotification.SheetActive
|
||||||
import Jobs.Handler.SendNotification.SheetInactive
|
import Jobs.Handler.SendNotification.SheetInactive
|
||||||
import Jobs.Handler.SendNotification.CorrectionsAssigned
|
import Jobs.Handler.SendNotification.CorrectionsAssigned
|
||||||
|
import Jobs.Handler.SendNotification.CorrectionsNotDistributed
|
||||||
|
|
||||||
|
|
||||||
dispatchJobSendNotification :: UserId -> Notification -> Handler ()
|
dispatchJobSendNotification :: UserId -> Notification -> Handler ()
|
||||||
|
|||||||
@ -22,7 +22,7 @@ dispatchNotificationCorrectionsAssigned nUser nSheet jRecipient = do
|
|||||||
]
|
]
|
||||||
return (course, sheet, nbrSubs)
|
return (course, sheet, nbrSubs)
|
||||||
when (nbrSubs > 0) . userMailT jRecipient $ do
|
when (nbrSubs > 0) . userMailT jRecipient $ do
|
||||||
setSubjectI $ MsgMailSubjectSheetActive courseShorthand sheetName
|
setSubjectI $ MsgMailSubjectCorrectionsAssigned courseShorthand sheetName
|
||||||
|
|
||||||
MsgRenderer mr <- getMailMsgRenderer
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
module Jobs.Handler.SendNotification.CorrectionsNotDistributed
|
||||||
|
( dispatchNotificationCorrectionsNotDistributed
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Handler.Utils.Mail
|
||||||
|
|
||||||
|
import Text.Hamlet
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
dispatchNotificationCorrectionsNotDistributed :: SheetId -> UserId -> Handler ()
|
||||||
|
dispatchNotificationCorrectionsNotDistributed nSheet jRecipient = do
|
||||||
|
(Course{..}, Sheet{..}, nbrSubs) <- liftHandlerT . runDB $ do
|
||||||
|
sheet <- getJust nSheet
|
||||||
|
course <- belongsToJust sheetCourse sheet
|
||||||
|
nbrSubs <- count [ SubmissionSheet ==. nSheet
|
||||||
|
, SubmissionRatingBy ==. Nothing
|
||||||
|
]
|
||||||
|
return (course, sheet, nbrSubs)
|
||||||
|
when (nbrSubs > 0) . userMailT jRecipient $ do
|
||||||
|
setSubjectI $ MsgMailSubjectSubmissionsUnassigned courseShorthand sheetName
|
||||||
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
|
tid = courseTerm
|
||||||
|
ssh = courseSchool
|
||||||
|
csh = courseShorthand
|
||||||
|
shn = sheetName
|
||||||
|
|
||||||
|
addAlternatives $
|
||||||
|
providePreferredAlternative ($(ihamletFile "templates/mail/correctionsUndistributed.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
@ -3,6 +3,7 @@ module Jobs.Queue
|
|||||||
, queueJob, queueJob'
|
, queueJob, queueJob'
|
||||||
, YesodJobDB
|
, YesodJobDB
|
||||||
, runDBJobs, queueDBJob
|
, runDBJobs, queueDBJob
|
||||||
|
, module Jobs.Types
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|||||||
@ -19,12 +19,14 @@ data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notifica
|
|||||||
, jRequestTime :: UTCTime
|
, jRequestTime :: UTCTime
|
||||||
, jHelpRequest :: Text, jReferer :: Maybe Text }
|
, jHelpRequest :: Text, jReferer :: Maybe Text }
|
||||||
| JobSetLogSettings { jInstance :: InstanceId, jLogSettings :: LogSettings }
|
| JobSetLogSettings { jInstance :: InstanceId, jLogSettings :: LogSettings }
|
||||||
|
| JobDistributeCorrections { jSheet :: SheetId }
|
||||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
||||||
| NotificationSheetActive { nSheet :: SheetId }
|
| NotificationSheetActive { nSheet :: SheetId }
|
||||||
| NotificationSheetSoonInactive { nSheet :: SheetId }
|
| NotificationSheetSoonInactive { nSheet :: SheetId }
|
||||||
| NotificationSheetInactive { nSheet :: SheetId }
|
| NotificationSheetInactive { nSheet :: SheetId }
|
||||||
| NotificationCorrectionsAssigned { nUser :: UserId, nSheet :: SheetId }
|
| NotificationCorrectionsAssigned { nUser :: UserId, nSheet :: SheetId }
|
||||||
|
| NotificationCorrectionsNotDistributed { nSheet :: SheetId }
|
||||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
|
|
||||||
instance Hashable Job
|
instance Hashable Job
|
||||||
|
|||||||
@ -574,6 +574,7 @@ data NotificationTrigger = NTSubmissionRatedGraded
|
|||||||
| NTSheetSoonInactive
|
| NTSheetSoonInactive
|
||||||
| NTSheetInactive
|
| NTSheetInactive
|
||||||
| NTCorrectionsAssigned
|
| NTCorrectionsAssigned
|
||||||
|
| NTCorrectionsNotDistributed
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
|
|
||||||
instance Universe NotificationTrigger
|
instance Universe NotificationTrigger
|
||||||
@ -604,6 +605,7 @@ instance Default NotificationSettings where
|
|||||||
NTSheetSoonInactive -> False
|
NTSheetSoonInactive -> False
|
||||||
NTSheetInactive -> True
|
NTSheetInactive -> True
|
||||||
NTCorrectionsAssigned -> True
|
NTCorrectionsAssigned -> True
|
||||||
|
NTCorrectionsNotDistributed -> True
|
||||||
|
|
||||||
instance ToJSON NotificationSettings where
|
instance ToJSON NotificationSettings where
|
||||||
toJSON v = toJSON . HashMap.fromList $ map (id &&& notificationAllowed v) universeF
|
toJSON v = toJSON . HashMap.fromList $ map (id &&& notificationAllowed v) universeF
|
||||||
|
|||||||
14
src/Utils.hs
14
src/Utils.hs
@ -542,6 +542,20 @@ lookupGlobalGetParam ident = (>>= fromPathPiece) <$> lookupGetParam (toPathPiece
|
|||||||
hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool
|
hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool
|
||||||
hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident)
|
hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident)
|
||||||
|
|
||||||
|
|
||||||
|
data GlobalPostParam = PostDeleteTarget
|
||||||
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||||
|
|
||||||
|
instance Universe GlobalPostParam
|
||||||
|
instance Finite GlobalPostParam
|
||||||
|
nullaryPathPiece ''GlobalPostParam (camelToPathPiece' 1)
|
||||||
|
|
||||||
|
lookupGlobalPostParam :: (MonadHandler m, PathPiece result) => GlobalPostParam -> m (Maybe result)
|
||||||
|
lookupGlobalPostParam ident = (>>= fromPathPiece) <$> lookupPostParam (toPathPiece ident)
|
||||||
|
|
||||||
|
hasGlobalPostParam :: MonadHandler m => GlobalPostParam -> m Bool
|
||||||
|
hasGlobalPostParam ident = isJust <$> lookupPostParam (toPathPiece ident)
|
||||||
|
|
||||||
---------------------------------
|
---------------------------------
|
||||||
-- Custom HTTP Request-Headers --
|
-- Custom HTTP Request-Headers --
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-deprecations #-}
|
||||||
|
|
||||||
module Utils.Form where
|
module Utils.Form where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod hiding (addMessage)
|
import ClassyPrelude.Yesod hiding (addMessage, cons, Proxy(..))
|
||||||
import Settings
|
import Settings
|
||||||
|
|
||||||
import qualified Text.Blaze.Internal as Blaze (null)
|
import qualified Text.Blaze.Internal as Blaze (null)
|
||||||
@ -19,13 +21,16 @@ import Control.Monad.Trans.Maybe (MaybeT(..))
|
|||||||
|
|
||||||
import Data.List ((!!))
|
import Data.List ((!!))
|
||||||
|
|
||||||
import Control.Lens ((&))
|
import Control.Lens
|
||||||
|
|
||||||
import Web.PathPieces
|
import Web.PathPieces
|
||||||
|
|
||||||
import Data.UUID
|
import Data.UUID
|
||||||
|
|
||||||
import Utils.Message
|
import Utils.Message
|
||||||
|
import Utils.PathPiece
|
||||||
|
|
||||||
|
import Data.Proxy
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
-- Form Renderer --
|
-- Form Renderer --
|
||||||
@ -36,7 +41,7 @@ data FormLayout = FormStandard | FormDBTableFilter | FormDBTablePagesize
|
|||||||
|
|
||||||
renderAForm :: Monad m => FormLayout -> FormRender m a
|
renderAForm :: Monad m => FormLayout -> FormRender m a
|
||||||
renderAForm formLayout aform fragment = do
|
renderAForm formLayout aform fragment = do
|
||||||
(res, ($ []) -> views) <- aFormToForm aform
|
(res, ($ []) -> fieldViews) <- aFormToForm aform
|
||||||
let widget = $(widgetFile "widgets/form")
|
let widget = $(widgetFile "widgets/form")
|
||||||
return (res, widget)
|
return (res, widget)
|
||||||
|
|
||||||
@ -204,38 +209,60 @@ class (Enum a, Bounded a, Ord a, PathPiece a) => Button site a where
|
|||||||
label :: a -> WidgetT site IO ()
|
label :: a -> WidgetT site IO ()
|
||||||
label = toWidget . toPathPiece
|
label = toWidget . toPathPiece
|
||||||
|
|
||||||
|
btnValidate :: forall p. p site -> a -> Bool
|
||||||
|
btnValidate _ _ = True
|
||||||
|
|
||||||
cssClass :: a -> ButtonCssClass site
|
cssClass :: a -> ButtonCssClass site
|
||||||
|
|
||||||
|
data ButtonMessage = MsgAmbiguousButtons
|
||||||
|
| MsgWrongButtonValue
|
||||||
|
| MsgMultipleButtonValues
|
||||||
|
|
||||||
data SubmitButton = BtnSubmit
|
data SubmitButton = BtnSubmit
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show)
|
||||||
|
|
||||||
instance PathPiece SubmitButton where
|
instance Universe SubmitButton
|
||||||
toPathPiece = showToPathPiece
|
instance Finite SubmitButton
|
||||||
fromPathPiece = readFromPathPiece
|
|
||||||
|
|
||||||
buttonField :: forall site a. (Button site a, Show (ButtonCssClass site)) => a -> Field (HandlerT site IO) a -- already validates that the correct button press was received (result only neccessary for combinedButtonField)
|
nullaryPathPiece ''SubmitButton $ camelToPathPiece' 1
|
||||||
buttonField btn = Field {fieldParse, fieldView, fieldEnctype}
|
|
||||||
|
buttonField :: forall a m.
|
||||||
|
( Button (HandlerSite m) a
|
||||||
|
, Show (ButtonCssClass (HandlerSite m))
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
, Monad m
|
||||||
|
) => a -> Field m a
|
||||||
|
-- | Already validates that the correct button press was received (result only neccessary for combinedButtonField)
|
||||||
|
buttonField btn = Field{..}
|
||||||
where
|
where
|
||||||
fieldEnctype = UrlEncoded
|
fieldEnctype = UrlEncoded
|
||||||
|
|
||||||
|
fieldView :: FieldViewFunc m a
|
||||||
fieldView fid name attrs _val _ = let
|
fieldView fid name attrs _val _ = let
|
||||||
cssClass' :: ButtonCssClass site
|
cssClass' :: ButtonCssClass (HandlerSite m)
|
||||||
cssClass' = cssClass btn
|
cssClass' = cssClass btn
|
||||||
|
validate = btnValidate (Proxy @(HandlerSite m)) btn
|
||||||
in [whamlet|
|
in [whamlet|
|
||||||
<button .btn .#{bcc2txt cssClass'} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid}>^{label btn}
|
$newline never
|
||||||
|
<button .btn .#{bcc2txt cssClass'} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid} :not validate:formnovalidate>^{label btn}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
fieldParse [] _ = return $ Right Nothing
|
fieldParse [] [] = return $ Right Nothing
|
||||||
fieldParse [str] _
|
fieldParse [str] []
|
||||||
| str == toPathPiece btn = return $ Right $ Just btn
|
| str == toPathPiece btn = return . Right $ Just btn
|
||||||
| otherwise = return $ Left "Wrong button value"
|
| otherwise = return . Left $ SomeMessage MsgWrongButtonValue
|
||||||
fieldParse _ _ = return $ Left "Multiple button values"
|
fieldParse _ _ = return . Left $ SomeMessage MsgMultipleButtonValues
|
||||||
|
|
||||||
combinedButtonField :: (Button site a, Show (ButtonCssClass site)) => [a] -> FieldSettings site -> AForm (HandlerT site IO) [Maybe a]
|
combinedButtonField :: forall a m.
|
||||||
|
( Button (HandlerSite m) a
|
||||||
|
, Show (ButtonCssClass (HandlerSite m))
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
, MonadHandler m
|
||||||
|
) => [a] -> FieldSettings (HandlerSite m) -> AForm m [Maybe a]
|
||||||
combinedButtonField bs FieldSettings{..} = formToAForm $ do
|
combinedButtonField bs FieldSettings{..} = formToAForm $ do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
fvId <- maybe newIdent return fsId
|
fvId <- maybe newFormIdent return fsId
|
||||||
name <- maybe newIdent return fsName
|
name <- maybe newFormIdent return fsName
|
||||||
(ress, fvs) <- fmap unzip . for bs $ \b -> mopt (buttonField b) ("" { fsId = Just $ fvId <> "__" <> toPathPiece b
|
(ress, fvs) <- fmap unzip . for bs $ \b -> mopt (buttonField b) ("" { fsId = Just $ fvId <> "__" <> toPathPiece b
|
||||||
, fsName = Just $ name <> "__" <> toPathPiece b
|
, fsName = Just $ name <> "__" <> toPathPiece b
|
||||||
}) Nothing
|
}) Nothing
|
||||||
@ -250,14 +277,46 @@ combinedButtonField bs FieldSettings{..} = formToAForm $ do
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
combinedButtonFieldF :: forall site a. (Button site a, Show (ButtonCssClass site), Finite a) => FieldSettings site -> AForm (HandlerT site IO) [Maybe a]
|
combinedButtonFieldF :: forall m a.
|
||||||
|
( Button (HandlerSite m) a
|
||||||
|
, Show (ButtonCssClass (HandlerSite m))
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
, Finite a
|
||||||
|
, MonadHandler m
|
||||||
|
) => FieldSettings (HandlerSite m) -> AForm m [Maybe a]
|
||||||
combinedButtonFieldF = combinedButtonField (universeF :: [a])
|
combinedButtonFieldF = combinedButtonField (universeF :: [a])
|
||||||
|
|
||||||
submitButton :: (Button site SubmitButton, Show (ButtonCssClass site)) => AForm (HandlerT site IO) ()
|
disambiguateButtons :: forall m a.
|
||||||
submitButton = void $ combinedButtonField [BtnSubmit] ""
|
( MonadHandler m
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
) => AForm m [Maybe a] -> AForm m a
|
||||||
|
disambiguateButtons = traverseAForm $ \case
|
||||||
|
(catMaybes -> [bRes]) -> return $ FormSuccess bRes
|
||||||
|
(catMaybes -> [] ) -> return FormMissing
|
||||||
|
_other -> formFailure [MsgAmbiguousButtons]
|
||||||
|
|
||||||
autosubmitButton :: (Button site SubmitButton, Show (ButtonCssClass site)) => AForm (HandlerT site IO) ()
|
combinedButtonField_ :: forall a m.
|
||||||
autosubmitButton = void $ aopt (buttonField BtnSubmit) ("" & addAutosubmit) Nothing
|
( Button (HandlerSite m) a
|
||||||
|
, Show (ButtonCssClass (HandlerSite m))
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
, MonadHandler m
|
||||||
|
) => [a] -> FieldSettings (HandlerSite m) -> AForm m ()
|
||||||
|
combinedButtonField_ bs fs = void . disambiguateButtons $ combinedButtonField bs fs
|
||||||
|
|
||||||
|
combinedButtonFieldF_ :: forall m a p.
|
||||||
|
( Button (HandlerSite m) a
|
||||||
|
, Show (ButtonCssClass (HandlerSite m))
|
||||||
|
, RenderMessage (HandlerSite m) ButtonMessage
|
||||||
|
, MonadHandler m
|
||||||
|
, Finite a
|
||||||
|
) => p a -> FieldSettings (HandlerSite m) -> AForm m ()
|
||||||
|
combinedButtonFieldF_ _ fs = void . disambiguateButtons $ combinedButtonFieldF @m @a fs
|
||||||
|
|
||||||
|
submitButton :: (Button (HandlerSite m) SubmitButton, Show (ButtonCssClass (HandlerSite m)), MonadHandler m, RenderMessage (HandlerSite m) ButtonMessage) => AForm m ()
|
||||||
|
submitButton = combinedButtonFieldF_ (Proxy @SubmitButton) ""
|
||||||
|
|
||||||
|
autosubmitButton :: (Button (HandlerSite m) SubmitButton, Show (ButtonCssClass (HandlerSite m)), MonadHandler m, RenderMessage (HandlerSite m) ButtonMessage) => AForm m ()
|
||||||
|
autosubmitButton = combinedButtonFieldF_ (Proxy @SubmitButton) $ "" & addAutosubmit
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
-- Custom Fields --
|
-- Custom Fields --
|
||||||
@ -331,6 +390,27 @@ optionsFinite = do
|
|||||||
-- Form evaluation --
|
-- Form evaluation --
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
traverseAForm :: forall m a b. Monad m => (a -> m (FormResult b)) -> (AForm m a -> AForm m b)
|
||||||
|
traverseAForm adj (AForm f) = AForm $ \mr env ints -> do
|
||||||
|
ret@(res, _, _, _) <- f mr env ints
|
||||||
|
case res of
|
||||||
|
FormFailure errs
|
||||||
|
-> return $ ret & _1 .~ FormFailure errs
|
||||||
|
FormMissing
|
||||||
|
-> return $ ret & _1 .~ FormMissing
|
||||||
|
FormSuccess a -> do
|
||||||
|
a' <- adj a
|
||||||
|
return $ ret & _1 .~ a'
|
||||||
|
|
||||||
|
formFailure :: forall msg m a.
|
||||||
|
( MonadHandler m
|
||||||
|
, RenderMessage (HandlerSite m) msg
|
||||||
|
) => [msg] -> m (FormResult a)
|
||||||
|
formFailure errs' = do
|
||||||
|
mr <- getMessageRender
|
||||||
|
return . FormFailure $ map mr errs'
|
||||||
|
|
||||||
|
|
||||||
formResult :: MonadHandler m => FormResult a -> (a -> m ()) -> m ()
|
formResult :: MonadHandler m => FormResult a -> (a -> m ()) -> m ()
|
||||||
formResult res f = void . formResultMaybe res $ \x -> Nothing <$ f x
|
formResult res f = void . formResultMaybe res $ \x -> Nothing <$ f x
|
||||||
|
|
||||||
|
|||||||
@ -439,6 +439,18 @@ input[type="button"].btn-info:hover,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list--inline {
|
.list--inline {
|
||||||
|
ul {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.list--inline {
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
|
|||||||
17
templates/mail/correctionsUndistributed.hamlet
Normal file
17
templates/mail/correctionsUndistributed.hamlet
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
$newline never
|
||||||
|
\<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
_{MsgMailSubmissionsUnassignedIntro nbrSubs (CI.original courseName) termDesc sheetName}
|
||||||
|
<p>
|
||||||
|
<a href=@{CSheetR tid ssh csh shn SSubsR}>
|
||||||
|
#{sheetName}
|
||||||
@ -2,4 +2,4 @@ $newline never
|
|||||||
<form method=#{decodeUtf8 (renderStdMethod dbParamsFormMethod)} action=#{fromMaybe "" act} *{dbParamsFormAttrs} enctype=#{enctype'}>
|
<form method=#{decodeUtf8 (renderStdMethod dbParamsFormMethod)} action=#{fromMaybe "" act} *{dbParamsFormAttrs} enctype=#{enctype'}>
|
||||||
^{fWidget}
|
^{fWidget}
|
||||||
$if dbParamsFormAddSubmit
|
$if dbParamsFormAddSubmit
|
||||||
^{fieldView submitField btnId "" mempty (Right BtnSubmit) False}
|
^{submitView}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<p>_{drCaption}
|
<p>_{drCaption}
|
||||||
<ul>
|
<ul>
|
||||||
$forall (wdgt, _) <- sTargets
|
$forall (wdgt, _) <- targets
|
||||||
<li>
|
<li>
|
||||||
^{wdgt}
|
^{wdgt}
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,10 @@ $newline never
|
|||||||
#{fragment}
|
#{fragment}
|
||||||
$case formLayout
|
$case formLayout
|
||||||
$of FormDBTablePagesize
|
$of FormDBTablePagesize
|
||||||
$forall view <- views
|
$forall view <- fieldViews
|
||||||
^{fvInput view}
|
^{fvInput view}
|
||||||
$of _
|
$of _
|
||||||
$forall view <- views
|
$forall view <- fieldViews
|
||||||
$# TODO: add class 'form-group--submit' if this is the submit-button view
|
$# TODO: add class 'form-group--submit' if this is the submit-button view
|
||||||
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
||||||
$if not (Blaze.null $ fvLabel view)
|
$if not (Blaze.null $ fvLabel view)
|
||||||
|
|||||||
@ -77,7 +77,7 @@ document.addEventListener('setup', function(e) {
|
|||||||
var forms = e.detail.scope.querySelectorAll('form');
|
var forms = e.detail.scope.querySelectorAll('form');
|
||||||
Array.from(forms).forEach(function(form) {
|
Array.from(forms).forEach(function(form) {
|
||||||
// auto reactiveButton submit-buttons with required fields
|
// auto reactiveButton submit-buttons with required fields
|
||||||
var submitBtns = Array.from(form.querySelectorAll('[type=submit]'));
|
var submitBtns = Array.from(form.querySelectorAll('[type=submit]:not([formnovalidate])'));
|
||||||
submitBtns.forEach(function(submitBtn) {
|
submitBtns.forEach(function(submitBtn) {
|
||||||
window.utils.reactiveButton(form, submitBtn, validateForm);
|
window.utils.reactiveButton(form, submitBtn, validateForm);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -237,11 +237,11 @@ fillDb = do
|
|||||||
void . insert $ DegreeCourse ffp sdMst sdInf
|
void . insert $ DegreeCourse ffp sdMst sdInf
|
||||||
void . insert $ Lecturer jost ffp
|
void . insert $ Lecturer jost ffp
|
||||||
void . insert $ Lecturer gkleen ffp
|
void . insert $ Lecturer gkleen ffp
|
||||||
adhoc <- insert $ Sheet ffp "AdHoc-Gruppen" Nothing NotGraded (Arbitrary 3) Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions
|
adhoc <- insert $ Sheet ffp "AdHoc-Gruppen" Nothing NotGraded (Arbitrary 3) Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions False
|
||||||
insert_ $ SheetEdit gkleen now adhoc
|
insert_ $ SheetEdit gkleen now adhoc
|
||||||
feste <- insert $ Sheet ffp "Feste Gruppen" Nothing NotGraded RegisteredGroups Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions
|
feste <- insert $ Sheet ffp "Feste Gruppen" Nothing NotGraded RegisteredGroups Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions False
|
||||||
insert_ $ SheetEdit gkleen now feste
|
insert_ $ SheetEdit gkleen now feste
|
||||||
keine <- insert $ Sheet ffp "Keine Gruppen" Nothing NotGraded NoGroups Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions
|
keine <- insert $ Sheet ffp "Keine Gruppen" Nothing NotGraded NoGroups Nothing Nothing now now Nothing Nothing (Upload True) UserSubmissions False
|
||||||
insert_ $ SheetEdit gkleen now keine
|
insert_ $ SheetEdit gkleen now keine
|
||||||
-- EIP
|
-- EIP
|
||||||
eip <- insert' Course
|
eip <- insert' Course
|
||||||
@ -330,6 +330,7 @@ fillDb = do
|
|||||||
, sheetUploadMode = Upload True
|
, sheetUploadMode = Upload True
|
||||||
, sheetHintFrom = Nothing
|
, sheetHintFrom = Nothing
|
||||||
, sheetSolutionFrom = Nothing
|
, sheetSolutionFrom = Nothing
|
||||||
|
, sheetAutoDistribute = True
|
||||||
}
|
}
|
||||||
void . insert $ SheetEdit jost now sh1
|
void . insert $ SheetEdit jost now sh1
|
||||||
forM_ [fhamann, maxMuster, tinaTester] $ \u -> do
|
forM_ [fhamann, maxMuster, tinaTester] $ \u -> do
|
||||||
|
|||||||
Reference in New Issue
Block a user