Merge branch 'feat/exam-sheets'
This commit is contained in:
commit
4061fb5c3a
@ -461,7 +461,7 @@ input[type="button"].btn-info:not(.btn-link):hover,
|
|||||||
overflow-y: auto
|
overflow-y: auto
|
||||||
|
|
||||||
.table--vertical
|
.table--vertical
|
||||||
th
|
th, .table__th
|
||||||
background-color: transparent
|
background-color: transparent
|
||||||
color: var(--color-font)
|
color: var(--color-font)
|
||||||
width: 170px
|
width: 170px
|
||||||
@ -469,7 +469,16 @@ input[type="button"].btn-info:not(.btn-link):hover,
|
|||||||
padding-right: 15px
|
padding-right: 15px
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
|
|
||||||
td
|
a
|
||||||
|
color: var(--color-lin)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: var(--color-link-hover)
|
||||||
|
|
||||||
|
&::before
|
||||||
|
display: none
|
||||||
|
|
||||||
|
td, .table__td
|
||||||
font-weight: 600
|
font-weight: 600
|
||||||
color: var(--color-font)
|
color: var(--color-font)
|
||||||
|
|
||||||
|
|||||||
@ -50,13 +50,6 @@ import Data.List (genericLength)
|
|||||||
import qualified Control.Retry as Retry
|
import qualified Control.Retry as Retry
|
||||||
|
|
||||||
|
|
||||||
instance (a ~ b, Monad m) => Monoid (Kleisli m a b) where
|
|
||||||
mempty = Kleisli return
|
|
||||||
|
|
||||||
instance (a ~ b, Monad m) => Semigroup (Kleisli m a b) where
|
|
||||||
Kleisli f <> Kleisli g = Kleisli $ f <=< g
|
|
||||||
|
|
||||||
|
|
||||||
data Normal k = Normal
|
data Normal k = Normal
|
||||||
{ dAvg :: k
|
{ dAvg :: k
|
||||||
, dRelDev :: Centi
|
, dRelDev :: Centi
|
||||||
|
|||||||
@ -720,6 +720,8 @@ RatingTime: Korrigiert
|
|||||||
RatingComment: Kommentar
|
RatingComment: Kommentar
|
||||||
SubmissionUsers: Studenten
|
SubmissionUsers: Studenten
|
||||||
Rating: Korrektur
|
Rating: Korrektur
|
||||||
|
IsRated: Korrigiert
|
||||||
|
SheetTypeIsExam: Anrechnung „als Prüfungsaufgabe“
|
||||||
RatingPoints: Punkte
|
RatingPoints: Punkte
|
||||||
RatingDone: Bewertung abgeschlossen
|
RatingDone: Bewertung abgeschlossen
|
||||||
RatingDoneTip: Das Korrekturergebnis ist nur dann für die Abgebenden sichtbar und kann gegen etwaige Prüfungs-Bonuspunkte verrechnet werden, wenn die Bewertung abgeschlossen ist.
|
RatingDoneTip: Das Korrekturergebnis ist nur dann für die Abgebenden sichtbar und kann gegen etwaige Prüfungs-Bonuspunkte verrechnet werden, wenn die Bewertung abgeschlossen ist.
|
||||||
@ -1126,9 +1128,10 @@ SheetGradingPassPoints': Bestehen nach Punkten
|
|||||||
SheetGradingPassBinary': Bestanden/Nicht bestanden
|
SheetGradingPassBinary': Bestanden/Nicht bestanden
|
||||||
SheetGradingPassAlways': Automatisch bestanden, sobald korrigiert
|
SheetGradingPassAlways': Automatisch bestanden, sobald korrigiert
|
||||||
|
|
||||||
SheetTypeBonus grading@SheetGrading: Bonus
|
SheetTypeBonus: Bonus
|
||||||
SheetTypeNormal grading@SheetGrading: Normal
|
SheetTypeNormal: Normal
|
||||||
SheetTypeInformational grading@SheetGrading: Ohne Anrechnung
|
SheetTypeInformational: Ohne Anrechnung
|
||||||
|
SheetTypeExamPartPoints: Als Prüfungsaufgabe
|
||||||
SheetTypeNotGraded: Keine Korrektur
|
SheetTypeNotGraded: Keine Korrektur
|
||||||
SheetTypeInfoNormalLecturer: Normale Blätter werden zur Berechnung eines etwaigen Prüfungsbonus herangezogen. Der Bonus kann sowohl anhand der zu bestehenden Blätter als auch der erreichbaren Maximalpunktzahl automatisch oder manuell berechnet werden.
|
SheetTypeInfoNormalLecturer: Normale Blätter werden zur Berechnung eines etwaigen Prüfungsbonus herangezogen. Der Bonus kann sowohl anhand der zu bestehenden Blätter als auch der erreichbaren Maximalpunktzahl automatisch oder manuell berechnet werden.
|
||||||
SheetTypeInfoNotGraded: Keine Korrektur bedeutet, dass es gar kein Feedback gibt.
|
SheetTypeInfoNotGraded: Keine Korrektur bedeutet, dass es gar kein Feedback gibt.
|
||||||
@ -1138,6 +1141,11 @@ SheetGradingBonusIncluded: Erzielte Bonuspunkte wurden hier bereits zu den errei
|
|||||||
SummaryTitle: Zusammenfassung über
|
SummaryTitle: Zusammenfassung über
|
||||||
SheetGradingSummaryTitle intgr@Integer: #{intgr} #{pluralDE intgr "Blatt" "Blätter"}
|
SheetGradingSummaryTitle intgr@Integer: #{intgr} #{pluralDE intgr "Blatt" "Blätter"}
|
||||||
SubmissionGradingSummaryTitle intgr@Integer: #{intgr} #{pluralDE intgr "Abgabe" "Abgaben"}
|
SubmissionGradingSummaryTitle intgr@Integer: #{intgr} #{pluralDE intgr "Abgabe" "Abgaben"}
|
||||||
|
SheetTypeExamPartPointsWeightNegative: Gewichtung darf nicht negativ sein
|
||||||
|
SheetTypeExamPartPointsWeight: Gewichtung
|
||||||
|
SheetTypeExamPartPointsExamPartOption examn@ExamName examPartNumber@ExamPartNumber: #{examn} - Teil #{view _ExamPartNumber examPartNumber}
|
||||||
|
SheetTypeInfoExamPartPoints: Bei diesem Übungsblatt erreichte Punkte werden direkt auf die Punktezahl eines Prüfungsteils angerechnet. Wenn die Anzahl von über Übungsblättern erreichbaren Punkten nicht der Maximalpunktzahl des Prüfungsteils entspricht, werden die Übungsblattpunkte anhand der angegebenen Gewichtung skaliert.
|
||||||
|
SheetTypeExamPartPointsExamPart: Prüfungsteil
|
||||||
|
|
||||||
SheetTypeBonus': Bonus
|
SheetTypeBonus': Bonus
|
||||||
SheetTypeNormal': Normal
|
SheetTypeNormal': Normal
|
||||||
@ -1998,6 +2006,10 @@ ExamPartMaxPoints: Maximalpunktzahl
|
|||||||
ExamPartWeight: Gewichtung
|
ExamPartWeight: Gewichtung
|
||||||
ExamPartWeightTip: Wird vor Anzeige oder automatischen Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen also auch bestehende Korrekturergebnisse an (derart geänderte Noten müssen erneut manuell übernommen werden)
|
ExamPartWeightTip: Wird vor Anzeige oder automatischen Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen also auch bestehende Korrekturergebnisse an (derart geänderte Noten müssen erneut manuell übernommen werden)
|
||||||
ExamPartResultPoints: Erreichte Punkte
|
ExamPartResultPoints: Erreichte Punkte
|
||||||
|
ExamPartSheets: Übungsblätter
|
||||||
|
|
||||||
|
ExamPartsFrom: Teile anzeigen ab
|
||||||
|
ExamPartsFromTip: Ab dem gegebenen Zeitpunkt wird die Liste von Prüfungsteilen/Aufgaben veröffentlicht, nicht jedoch die jeweilige Maximalpunktzahl. Ohne Zeitpunkt wird die Liste ab "Ergebnisse sichtbar ab" angezeigt.
|
||||||
|
|
||||||
ExamNameTaken exam@ExamName: Es existiert bereits eine Prüfung mit Namen #{exam}
|
ExamNameTaken exam@ExamName: Es existiert bereits eine Prüfung mit Namen #{exam}
|
||||||
ExamCreated exam@ExamName: #{exam} erfolgreich angelegt
|
ExamCreated exam@ExamName: #{exam} erfolgreich angelegt
|
||||||
@ -2035,6 +2047,7 @@ ExamRegistrationMustFollowSchoolSeparationFromStart dayCount@Int: Nach Regeln de
|
|||||||
ExamRegistrationMustFollowSchoolDuration dayCount@Int: Nach Regeln des Instituts #{pluralDE dayCount "muss" "müssen"} zwischen "Anmeldung ab" und "Anmeldung bis" mindestens #{dayCount} #{pluralDE dayCount "Tag" "Tage"} liegen.
|
ExamRegistrationMustFollowSchoolDuration dayCount@Int: Nach Regeln des Instituts #{pluralDE dayCount "muss" "müssen"} zwischen "Anmeldung ab" und "Anmeldung bis" mindestens #{dayCount} #{pluralDE dayCount "Tag" "Tage"} liegen.
|
||||||
ExamModeRequiredForRegistration: Nach Regeln des Institus muss die "Ausgestaltung der Prüfung" vollständig angegeben sein, bevor "Anmeldung ab" festgelegt werden kann.
|
ExamModeRequiredForRegistration: Nach Regeln des Institus muss die "Ausgestaltung der Prüfung" vollständig angegeben sein, bevor "Anmeldung ab" festgelegt werden kann.
|
||||||
ExamModeSchoolDiscouraged: Nach Regeln des Instituts wird von der angegebenen "Ausgestaltung der Prüfung" abgeraten
|
ExamModeSchoolDiscouraged: Nach Regeln des Instituts wird von der angegebenen "Ausgestaltung der Prüfung" abgeraten
|
||||||
|
ExamPartsFromMustBeBeforeFinished: "Teile anzeigen ab" muss vor "Ergebnisse sichtbar ab" liegen
|
||||||
|
|
||||||
ExamOccurrenceEndMustBeAfterStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss vor seinem Ende liegen
|
ExamOccurrenceEndMustBeAfterStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss vor seinem Ende liegen
|
||||||
ExamOccurrenceStartMustBeAfterExamStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss nach Beginn der Prüfung liegen
|
ExamOccurrenceStartMustBeAfterExamStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss nach Beginn der Prüfung liegen
|
||||||
@ -2043,6 +2056,7 @@ ExamOccurrenceDuplicate eoRoom@Text eoRange@Text: Raum #{eoRoom}, Termin #{eoRan
|
|||||||
ExamOccurrenceDuplicateName eoName@ExamOccurrenceName: Interne Terminbezeichnung #{eoName} kommt mehrfach vor
|
ExamOccurrenceDuplicateName eoName@ExamOccurrenceName: Interne Terminbezeichnung #{eoName} kommt mehrfach vor
|
||||||
ExamOccurrenceCannotBeDeletedDueToRegistrations eoName@ExamOccurrenceName: Termin #{eoName} kann nicht gelöscht werden, da noch Teilnehmer diesem Termin zugewiesen sind. Über die Liste von Prüfungsteilnehmern können Sie zunächst die entsprechenden Terminzuweisungen entfernen.
|
ExamOccurrenceCannotBeDeletedDueToRegistrations eoName@ExamOccurrenceName: Termin #{eoName} kann nicht gelöscht werden, da noch Teilnehmer diesem Termin zugewiesen sind. Über die Liste von Prüfungsteilnehmern können Sie zunächst die entsprechenden Terminzuweisungen entfernen.
|
||||||
ExamPartCannotBeDeletedDueToResults exampartnum@ExamPartNumber: Teil #{exampartnum} kann nicht gelöscht werden, da bereits Prüfungsergebnisse für diesen Teil eingetragen wurden.
|
ExamPartCannotBeDeletedDueToResults exampartnum@ExamPartNumber: Teil #{exampartnum} kann nicht gelöscht werden, da bereits Prüfungsergebnisse für diesen Teil eingetragen wurden.
|
||||||
|
ExamPartCannotBeDeletedDueToSheetReference exampartnum@ExamPartNumber sheetName@SheetName: Teil #{exampartnum} kann nicht gelöscht werden, da Übungsblatt #{sheetName} den Bewertungsmodus „als Prüfungsaufgabe“ trägt.
|
||||||
|
|
||||||
VersionHistory: Versionsgeschichte
|
VersionHistory: Versionsgeschichte
|
||||||
KnownBugs: Bekannte Bugs
|
KnownBugs: Bekannte Bugs
|
||||||
@ -2056,6 +2070,7 @@ ExamUserAssignOccurrence: Termin/Raum zuweisen
|
|||||||
ExamUserAcceptComputedResult: Berechnetes Prüfungsergebnis übernehmen
|
ExamUserAcceptComputedResult: Berechnetes Prüfungsergebnis übernehmen
|
||||||
ExamUserResetToComputedResult: Prüfungsergebnis zurücksetzen
|
ExamUserResetToComputedResult: Prüfungsergebnis zurücksetzen
|
||||||
ExamUserResetBonus: Auch Bonuspunkte zurücksetzen
|
ExamUserResetBonus: Auch Bonuspunkte zurücksetzen
|
||||||
|
ExamUserResetParts: Auch Teilergebnisse zurücksetzen
|
||||||
ExamUserSetPartResult: Teilergebnis setzen
|
ExamUserSetPartResult: Teilergebnis setzen
|
||||||
ExamUserSetBonus: Bonuspunkte setzen
|
ExamUserSetBonus: Bonuspunkte setzen
|
||||||
ExamUserSetResult: Prüfungsergebnis setzen
|
ExamUserSetResult: Prüfungsergebnis setzen
|
||||||
|
|||||||
@ -717,6 +717,8 @@ RatingTime: Marked
|
|||||||
RatingComment: Comment
|
RatingComment: Comment
|
||||||
SubmissionUsers: Submittors
|
SubmissionUsers: Submittors
|
||||||
Rating: Marking
|
Rating: Marking
|
||||||
|
IsRated: Marked
|
||||||
|
SheetTypeIsExam: Rating „as an exam part“
|
||||||
RatingPoints: Points
|
RatingPoints: Points
|
||||||
RatingDone: Rating finished
|
RatingDone: Rating finished
|
||||||
RatingDoneTip: The rating is only visible to the submittors and considered for any exam bonuses if it is finished.
|
RatingDoneTip: The rating is only visible to the submittors and considered for any exam bonuses if it is finished.
|
||||||
@ -1127,9 +1129,10 @@ SheetGradingPassPoints': Passing by points
|
|||||||
SheetGradingPassBinary': Pass/Fail
|
SheetGradingPassBinary': Pass/Fail
|
||||||
SheetGradingPassAlways': Automatically passed when corrected
|
SheetGradingPassAlways': Automatically passed when corrected
|
||||||
|
|
||||||
SheetTypeBonus grading: Bonus
|
SheetTypeBonus: Bonus
|
||||||
SheetTypeNormal grading: Normal
|
SheetTypeNormal: Normal
|
||||||
SheetTypeInformational grading: Informational
|
SheetTypeInformational: Informational
|
||||||
|
SheetTypeExamPartPoints: As an exam part
|
||||||
SheetTypeNotGraded: Not marked
|
SheetTypeNotGraded: Not marked
|
||||||
SheetTypeInfoNormalLecturer: Normal sheets are used to calculate exam bonuses. Bonuses may be calculated from the number of sheets that can be passed or the maximum number of points achievable either manually or automatically.
|
SheetTypeInfoNormalLecturer: Normal sheets are used to calculate exam bonuses. Bonuses may be calculated from the number of sheets that can be passed or the maximum number of points achievable either manually or automatically.
|
||||||
SheetTypeInfoNotGraded: "Not marked" means that there will be no feedback at all.
|
SheetTypeInfoNotGraded: "Not marked" means that there will be no feedback at all.
|
||||||
@ -1139,6 +1142,11 @@ SheetGradingBonusIncluded: Achieved bonus points are already counted among the a
|
|||||||
SummaryTitle: Summary of
|
SummaryTitle: Summary of
|
||||||
SheetGradingSummaryTitle intgr: #{intgr} #{pluralEN intgr "sheet" "sheets"}
|
SheetGradingSummaryTitle intgr: #{intgr} #{pluralEN intgr "sheet" "sheets"}
|
||||||
SubmissionGradingSummaryTitle intgr: #{intgr} #{pluralEN intgr "submission" "submissions"}
|
SubmissionGradingSummaryTitle intgr: #{intgr} #{pluralEN intgr "submission" "submissions"}
|
||||||
|
SheetTypeExamPartPointsWeightNegative: Weight may not be negative
|
||||||
|
SheetTypeExamPartPointsWeight: Weight
|
||||||
|
SheetTypeExamPartPointsExamPartOption examn examPartNumber: #{examn} - Part #{view _ExamPartNumber examPartNumber}
|
||||||
|
SheetTypeInfoExamPartPoints: Points achieved in this exercise sheet will be directly applied to the result of an exam part. If the number of points achievable via exercise sheets for an exam part does not match the maximum number of points of that exam part, the points achieved via exercise sheets will be scaled according to their weight.
|
||||||
|
SheetTypeExamPartPointsExamPart: Exam part
|
||||||
|
|
||||||
SheetTypeBonus': Bonus
|
SheetTypeBonus': Bonus
|
||||||
SheetTypeNormal': Normal
|
SheetTypeNormal': Normal
|
||||||
@ -1997,6 +2005,10 @@ ExamPartMaxPoints: Maximum points
|
|||||||
ExamPartWeight: Weight
|
ExamPartWeight: Weight
|
||||||
ExamPartWeightTip: Will be multiplied with the achieved number of points before they are shown to the participant or used in automatic grade computation. Thus this also affects existing exam results (changed exam achievements have to be accepted manually again)
|
ExamPartWeightTip: Will be multiplied with the achieved number of points before they are shown to the participant or used in automatic grade computation. Thus this also affects existing exam results (changed exam achievements have to be accepted manually again)
|
||||||
ExamPartResultPoints: Achieved points
|
ExamPartResultPoints: Achieved points
|
||||||
|
ExamPartSheets: Exercise sheets
|
||||||
|
|
||||||
|
ExamPartsFrom: Parts visible from
|
||||||
|
ExamPartsFromTip: At this time the list of exam parts/questions will be published, but without their respective maximum number of points. If left empty the list will be published with “Results visible from”
|
||||||
|
|
||||||
ExamNameTaken exam: There already is an exam named #{exam}
|
ExamNameTaken exam: There already is an exam named #{exam}
|
||||||
ExamCreated exam: Successfully created #{exam}
|
ExamCreated exam: Successfully created #{exam}
|
||||||
@ -2034,6 +2046,7 @@ ExamRegistrationMustFollowSchoolSeparationFromStart dayCount: As per school rule
|
|||||||
ExamRegistrationMustFollowSchoolDuration dayCount: As per school rules there #{pluralEN dayCount "needs" "need"} to be at least #{dayCount} #{pluralEN dayCount "day" "days"} between "Register from" and "Register to".
|
ExamRegistrationMustFollowSchoolDuration dayCount: As per school rules there #{pluralEN dayCount "needs" "need"} to be at least #{dayCount} #{pluralEN dayCount "day" "days"} between "Register from" and "Register to".
|
||||||
ExamModeRequiredForRegistration: As per school rules "Exam design" needs to be fully specified before "Register from" may be set.
|
ExamModeRequiredForRegistration: As per school rules "Exam design" needs to be fully specified before "Register from" may be set.
|
||||||
ExamModeSchoolDiscouraged: As per school rules the specified "Exam design" is discouraged
|
ExamModeSchoolDiscouraged: As per school rules the specified "Exam design" is discouraged
|
||||||
|
ExamPartsFromMustBeBeforeFinished: “Parts visible from” must be before “Results visible from”
|
||||||
|
|
||||||
ExamOccurrenceEndMustBeAfterStart eoName: End of the occurrence #{eoName} must be after it's start
|
ExamOccurrenceEndMustBeAfterStart eoName: End of the occurrence #{eoName} must be after it's start
|
||||||
ExamOccurrenceStartMustBeAfterExamStart eoName: Start of the occurrence #{eoName} must be after the exam start
|
ExamOccurrenceStartMustBeAfterExamStart eoName: Start of the occurrence #{eoName} must be after the exam start
|
||||||
@ -2042,6 +2055,7 @@ ExamOccurrenceDuplicate eoRoom eoRange: Combination of room #{eoRoom} and occurr
|
|||||||
ExamOccurrenceDuplicateName eoName: Internal name #{eoName} occurs multiple times
|
ExamOccurrenceDuplicateName eoName: Internal name #{eoName} occurs multiple times
|
||||||
ExamOccurrenceCannotBeDeletedDueToRegistrations eoName: Occurrence #{eoName} cannot be deleted because participants are registered for it. You can remove the offending registrations via the list of exam participants.
|
ExamOccurrenceCannotBeDeletedDueToRegistrations eoName: Occurrence #{eoName} cannot be deleted because participants are registered for it. You can remove the offending registrations via the list of exam participants.
|
||||||
ExamPartCannotBeDeletedDueToResults exampartnum: Part #{exampartnum} cannot be deleted because some exam part results were already entered for it.
|
ExamPartCannotBeDeletedDueToResults exampartnum: Part #{exampartnum} cannot be deleted because some exam part results were already entered for it.
|
||||||
|
ExamPartCannotBeDeletedDueToSheetReference exampartnum sheetName: Part #{exampartnum} cannot be deleted, since exercise sheet #{sheetName} is configured “as an exam part”.
|
||||||
|
|
||||||
VersionHistory: Version history
|
VersionHistory: Version history
|
||||||
KnownBugs: Known bugs
|
KnownBugs: Known bugs
|
||||||
@ -2055,6 +2069,7 @@ ExamUserAssignOccurrence: Assign occurrence/room
|
|||||||
ExamUserAcceptComputedResult: Accept computed result
|
ExamUserAcceptComputedResult: Accept computed result
|
||||||
ExamUserResetToComputedResult: Reset result
|
ExamUserResetToComputedResult: Reset result
|
||||||
ExamUserResetBonus: Also reset exam bonus
|
ExamUserResetBonus: Also reset exam bonus
|
||||||
|
ExamUserResetParts: Also reset exam part results
|
||||||
ExamUserSetPartResult: Set exam part result
|
ExamUserSetPartResult: Set exam part result
|
||||||
ExamUserSetBonus: Set exam bonus
|
ExamUserSetBonus: Set exam bonus
|
||||||
ExamUserSetResult: Set exam result
|
ExamUserSetResult: Set exam result
|
||||||
|
|||||||
@ -19,6 +19,7 @@ Exam
|
|||||||
description StoredMarkup Maybe
|
description StoredMarkup Maybe
|
||||||
examMode ExamMode
|
examMode ExamMode
|
||||||
staff Text Maybe
|
staff Text Maybe
|
||||||
|
partsFrom UTCTime Maybe
|
||||||
UniqueExam course name
|
UniqueExam course name
|
||||||
ExamPart
|
ExamPart
|
||||||
exam ExamId
|
exam ExamId
|
||||||
@ -28,6 +29,7 @@ ExamPart
|
|||||||
weight Rational
|
weight Rational
|
||||||
UniqueExamPartNumber exam number
|
UniqueExamPartNumber exam number
|
||||||
UniqueExamPartName exam name !force
|
UniqueExamPartName exam name !force
|
||||||
|
deriving Read Show Eq Ord Generic Typeable
|
||||||
ExamOccurrence
|
ExamOccurrence
|
||||||
exam ExamId
|
exam ExamId
|
||||||
name ExamOccurrenceName
|
name ExamOccurrenceName
|
||||||
|
|||||||
@ -2,7 +2,7 @@ Sheet -- exercise sheet for a given course
|
|||||||
course CourseId
|
course CourseId
|
||||||
name (CI Text)
|
name (CI Text)
|
||||||
description StoredMarkup Maybe
|
description StoredMarkup Maybe
|
||||||
type SheetType -- Does it count towards overall course grade?
|
type (SheetType SqlBackendKey) -- ExamPartId; Does it count towards overall course grade?
|
||||||
grouping SheetGroup -- May participants submit in groups of certain sizes?
|
grouping SheetGroup -- May participants submit in groups of certain sizes?
|
||||||
markingText StoredMarkup Maybe -- Instructons for correctors, included in marking templates
|
markingText StoredMarkup Maybe -- Instructons for correctors, included in marking templates
|
||||||
visibleFrom UTCTime Maybe -- Invisible to enrolled participants before
|
visibleFrom UTCTime Maybe -- Invisible to enrolled participants before
|
||||||
|
|||||||
15
src/Control/Arrow/Instances.hs
Normal file
15
src/Control/Arrow/Instances.hs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Control.Arrow.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Control.Arrow
|
||||||
|
|
||||||
|
|
||||||
|
instance (a ~ b, Monad m) => Monoid (Kleisli m a b) where
|
||||||
|
mempty = Kleisli return
|
||||||
|
|
||||||
|
instance (a ~ b, Monad m) => Semigroup (Kleisli m a b) where
|
||||||
|
Kleisli f <> Kleisli g = Kleisli $ f <=< g
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
module Database.Persist.Types.Instances
|
module Database.Persist.Types.Instances
|
||||||
@ -22,3 +23,6 @@ deriving instance Typeable PersistValue
|
|||||||
instance Hashable PersistValue
|
instance Hashable PersistValue
|
||||||
instance Binary PersistValue
|
instance Binary PersistValue
|
||||||
instance NFData PersistValue
|
instance NFData PersistValue
|
||||||
|
|
||||||
|
instance (NFData record, NFData (Key record)) => NFData (Entity record) where
|
||||||
|
rnf Entity{..} = rnf entityKey `seq` rnf entityVal
|
||||||
|
|||||||
@ -8,7 +8,7 @@ module Foundation.I18n
|
|||||||
, MsgLanguage(..)
|
, MsgLanguage(..)
|
||||||
, ShortSex(..)
|
, ShortSex(..)
|
||||||
, ShortWeekDay(..)
|
, ShortWeekDay(..)
|
||||||
, SheetTypeHeader(..)
|
, SheetType'(..), classifySheetType
|
||||||
, SheetArchiveFileTypeDirectory(..)
|
, SheetArchiveFileTypeDirectory(..)
|
||||||
, ShortStudyDegree(..)
|
, ShortStudyDegree(..)
|
||||||
, ShortStudyTerms(..)
|
, ShortStudyTerms(..)
|
||||||
@ -248,22 +248,27 @@ embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials"
|
|||||||
newtype ShortSex = ShortSex Sex
|
newtype ShortSex = ShortSex Sex
|
||||||
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)
|
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)
|
||||||
|
|
||||||
newtype SheetTypeHeader = SheetTypeHeader SheetType
|
data SheetType'
|
||||||
embedRenderMessageVariant ''UniWorX ''SheetTypeHeader ("SheetType" <>)
|
= NotGraded' | Normal' | Bonus' | Informational' | ExamPartPoints'
|
||||||
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
|
deriving (Universe, Finite)
|
||||||
|
|
||||||
|
classifySheetType :: SheetType a -> SheetType'
|
||||||
|
classifySheetType = \case
|
||||||
|
NotGraded -> NotGraded'
|
||||||
|
Normal{} -> Normal'
|
||||||
|
Bonus{} -> Bonus'
|
||||||
|
Informational{} -> Informational'
|
||||||
|
ExamPartPoints{} -> ExamPartPoints'
|
||||||
|
|
||||||
|
nullaryPathPiece ''SheetType' (camelToPathPiece . dropSuffix "'")
|
||||||
|
embedRenderMessage ''UniWorX ''SheetType' $ ("SheetType" <>) . fromMaybe (error "Expected SheetType' to have '") . stripSuffix "'"
|
||||||
|
|
||||||
newtype SheetArchiveFileTypeDirectory = SheetArchiveFileTypeDirectory SheetFileType
|
newtype SheetArchiveFileTypeDirectory = SheetArchiveFileTypeDirectory SheetFileType
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
deriving newtype (Enum, Bounded, Universe, Finite)
|
deriving newtype (Enum, Bounded, Universe, Finite)
|
||||||
embedRenderMessageVariant ''UniWorX ''SheetArchiveFileTypeDirectory $ ("SheetArchiveFileTypeDirectory" <>) . concat . drop 1 . splitCamel
|
embedRenderMessageVariant ''UniWorX ''SheetArchiveFileTypeDirectory $ ("SheetArchiveFileTypeDirectory" <>) . concat . drop 1 . splitCamel
|
||||||
|
|
||||||
instance RenderMessage UniWorX SheetType where
|
|
||||||
renderMessage foundation ls sheetType = case sheetType of
|
|
||||||
NotGraded -> mr $ SheetTypeHeader NotGraded
|
|
||||||
other -> mr (grading other) <> ", " <> mr (SheetTypeHeader other)
|
|
||||||
where
|
|
||||||
mr :: RenderMessage UniWorX msg => msg -> Text
|
|
||||||
mr = renderMessage foundation ls
|
|
||||||
|
|
||||||
instance RenderMessage UniWorX StudyDegree where
|
instance RenderMessage UniWorX StudyDegree where
|
||||||
renderMessage _found _ls StudyDegree{..} = fromMaybe (tshow studyDegreeKey) (studyDegreeName <|> studyDegreeShorthand)
|
renderMessage _found _ls StudyDegree{..} = fromMaybe (tshow studyDegreeKey) (studyDegreeName <|> studyDegreeShorthand)
|
||||||
|
|
||||||
|
|||||||
@ -85,7 +85,7 @@ type UserTableData = DBRow ( Entity User
|
|||||||
, ([Entity Tutorial], Map (CI Text) (Maybe (Entity Tutorial)))
|
, ([Entity Tutorial], Map (CI Text) (Maybe (Entity Tutorial)))
|
||||||
, [Entity Exam]
|
, [Entity Exam]
|
||||||
, Maybe (Entity SubmissionGroup)
|
, Maybe (Entity SubmissionGroup)
|
||||||
, Map SheetName (SheetType, Maybe Points)
|
, Map SheetName (SheetType SqlBackendKey, Maybe Points)
|
||||||
, UserTableStudyFeatures
|
, UserTableStudyFeatures
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ _userExams = _dbrOutput . _5
|
|||||||
_userSubmissionGroup :: Traversal' UserTableData (Entity SubmissionGroup)
|
_userSubmissionGroup :: Traversal' UserTableData (Entity SubmissionGroup)
|
||||||
_userSubmissionGroup = _dbrOutput . _6 . _Just
|
_userSubmissionGroup = _dbrOutput . _6 . _Just
|
||||||
|
|
||||||
_userSheets :: Lens' UserTableData (Map SheetName (SheetType, Maybe Points))
|
_userSheets :: Lens' UserTableData (Map SheetName (SheetType SqlBackendKey, Maybe Points))
|
||||||
_userSheets = _dbrOutput . _7
|
_userSheets = _dbrOutput . _7
|
||||||
|
|
||||||
_userStudyFeatures :: Lens' UserTableData UserTableStudyFeatures
|
_userStudyFeatures :: Lens' UserTableData UserTableStudyFeatures
|
||||||
@ -160,7 +160,7 @@ colUserSheets shns = cap (Sortable Nothing caption) $ foldMap userSheetCol shns
|
|||||||
]
|
]
|
||||||
|
|
||||||
userSheetCol :: SheetName -> Colonnade Sortable UserTableData (DBCell m c)
|
userSheetCol :: SheetName -> Colonnade Sortable UserTableData (DBCell m c)
|
||||||
userSheetCol shn = sortable (Just . SortingKey $ "sheet-" <> shn) (i18nCell shn) . views (_userSheets . at shn) $ \case
|
userSheetCol shn = sortable (Just . SortingKey $ "sheet-" <> shn) (i18nCell shn) $ \dat -> flip (views $ _userSheets . at shn) dat $ \case
|
||||||
Just (preview _grading -> Just Points{..}, Just points) -> i18nCell $ MsgAchievedOf points maxPoints
|
Just (preview _grading -> Just Points{..}, Just points) -> i18nCell $ MsgAchievedOf points maxPoints
|
||||||
Just (preview _grading -> Just grading', Just points) -> i18nCell . bool MsgNotPassed MsgPassed $ Just True == gradingPassed grading' points
|
Just (preview _grading -> Just grading', Just points) -> i18nCell . bool MsgNotPassed MsgPassed $ Just True == gradingPassed grading' points
|
||||||
_other -> mempty
|
_other -> mempty
|
||||||
@ -177,7 +177,7 @@ data UserTableCsv = UserTableCsv
|
|||||||
, csvUserNote :: Maybe StoredMarkup
|
, csvUserNote :: Maybe StoredMarkup
|
||||||
, csvUserTutorials :: ([TutorialName], Map (CI Text) (Maybe TutorialName))
|
, csvUserTutorials :: ([TutorialName], Map (CI Text) (Maybe TutorialName))
|
||||||
, csvUserExams :: [ExamName]
|
, csvUserExams :: [ExamName]
|
||||||
, csvUserSheets :: Map SheetName (SheetType, Maybe Points)
|
, csvUserSheets :: Map SheetName (SheetType (), Maybe Points)
|
||||||
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
makeLenses_ ''UserTableCsv
|
makeLenses_ ''UserTableCsv
|
||||||
|
|
||||||
@ -470,7 +470,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
|||||||
<*> (over (_2.traverse._Just) (tutorialName . entityVal) . over (_1.traverse) (tutorialName . entityVal) <$> view _userTutorials)
|
<*> (over (_2.traverse._Just) (tutorialName . entityVal) . over (_1.traverse) (tutorialName . entityVal) <$> view _userTutorials)
|
||||||
-- <*> (over (_2.traverse._Just) (examName . entityVal) . over (_1.traverse) (examName . entityVal) <$> view _userExams)
|
-- <*> (over (_2.traverse._Just) (examName . entityVal) . over (_1.traverse) (examName . entityVal) <$> view _userExams)
|
||||||
<*> (over traverse (examName . entityVal) <$> view _userExams)
|
<*> (over traverse (examName . entityVal) <$> view _userExams)
|
||||||
<*> view _userSheets
|
<*> views _userSheets (set (mapped . _1 . mapped) ())
|
||||||
, dbtCsvName = unpack csvName
|
, dbtCsvName = unpack csvName
|
||||||
, dbtCsvNoExportData = Nothing
|
, dbtCsvNoExportData = Nothing
|
||||||
, dbtCsvHeader = return . Vector.filter csvColumns' . userTableCsvHeader showSex tutorials sheets . fromMaybe def
|
, dbtCsvHeader = return . Vector.filter csvColumns' . userTableCsvHeader showSex tutorials sheets . fromMaybe def
|
||||||
|
|||||||
@ -48,6 +48,7 @@ postEEditR tid ssh csh examn = do
|
|||||||
, examDescription = efDescription
|
, examDescription = efDescription
|
||||||
, examExamMode = efExamMode
|
, examExamMode = efExamMode
|
||||||
, examStaff = efStaff
|
, examStaff = efStaff
|
||||||
|
, examPartsFrom = efPartsFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
when (is _Nothing insertRes) $ do
|
when (is _Nothing insertRes) $ do
|
||||||
|
|||||||
@ -26,6 +26,8 @@ import Text.Blaze.Html.Renderer.Text (renderHtml)
|
|||||||
|
|
||||||
import qualified Data.Text.Lazy as LT
|
import qualified Data.Text.Lazy as LT
|
||||||
|
|
||||||
|
import qualified Data.Conduit.Combinators as C
|
||||||
|
|
||||||
|
|
||||||
data ExamForm = ExamForm
|
data ExamForm = ExamForm
|
||||||
{ efName :: ExamName
|
{ efName :: ExamName
|
||||||
@ -37,6 +39,7 @@ data ExamForm = ExamForm
|
|||||||
, efRegisterTo :: Maybe UTCTime
|
, efRegisterTo :: Maybe UTCTime
|
||||||
, efDeregisterUntil :: Maybe UTCTime
|
, efDeregisterUntil :: Maybe UTCTime
|
||||||
, efPublishOccurrenceAssignments :: Maybe UTCTime
|
, efPublishOccurrenceAssignments :: Maybe UTCTime
|
||||||
|
, efPartsFrom :: Maybe UTCTime
|
||||||
, efFinished :: Maybe UTCTime
|
, efFinished :: Maybe UTCTime
|
||||||
, efOccurrences :: Set ExamOccurrenceForm
|
, efOccurrences :: Set ExamOccurrenceForm
|
||||||
, efPublicStatistics :: Bool
|
, efPublicStatistics :: Bool
|
||||||
@ -121,6 +124,7 @@ examForm (Entity _ Course{..}) template csrf = hoist liftHandler $ do
|
|||||||
<*> aopt utcTimeField (fslpI MsgExamRegisterTo (mr MsgDate)) (efRegisterTo <$> template)
|
<*> aopt utcTimeField (fslpI MsgExamRegisterTo (mr MsgDate)) (efRegisterTo <$> template)
|
||||||
<*> aopt utcTimeField (fslpI MsgExamDeregisterUntil (mr MsgDate)) (efDeregisterUntil <$> template)
|
<*> aopt utcTimeField (fslpI MsgExamDeregisterUntil (mr MsgDate)) (efDeregisterUntil <$> template)
|
||||||
<*> aopt utcTimeField (fslpI MsgExamPublishOccurrenceAssignments (mr MsgDate) & setTooltip MsgExamPublishOccurrenceAssignmentsTip) (efPublishOccurrenceAssignments <$> template)
|
<*> aopt utcTimeField (fslpI MsgExamPublishOccurrenceAssignments (mr MsgDate) & setTooltip MsgExamPublishOccurrenceAssignmentsTip) (efPublishOccurrenceAssignments <$> template)
|
||||||
|
<*> aopt utcTimeField (fslpI MsgExamPartsFrom (mr MsgDate) & setTooltip MsgExamPartsFromTip) (efPartsFrom <$> template)
|
||||||
<*> aopt utcTimeField (fslpI MsgExamFinished (mr MsgDate) & setTooltip (bool MsgExamFinishedTip MsgExamFinishedTipCloseOnFinished $ is _ExamCloseOnFinished' schoolExamCloseMode)) (efFinished <$> template)
|
<*> aopt utcTimeField (fslpI MsgExamFinished (mr MsgDate) & setTooltip (bool MsgExamFinishedTip MsgExamFinishedTipCloseOnFinished $ is _ExamCloseOnFinished' schoolExamCloseMode)) (efFinished <$> template)
|
||||||
<* aformSection MsgExamFormOccurrences
|
<* aformSection MsgExamFormOccurrences
|
||||||
<*> examOccurrenceForm (efOccurrences <$> template)
|
<*> examOccurrenceForm (efOccurrences <$> template)
|
||||||
@ -322,6 +326,7 @@ examFormTemplate (Entity eId Exam{..}) = do
|
|||||||
, efRegisterTo = examRegisterTo
|
, efRegisterTo = examRegisterTo
|
||||||
, efDeregisterUntil = examDeregisterUntil
|
, efDeregisterUntil = examDeregisterUntil
|
||||||
, efPublishOccurrenceAssignments = examPublishOccurrenceAssignments
|
, efPublishOccurrenceAssignments = examPublishOccurrenceAssignments
|
||||||
|
, efPartsFrom = examPartsFrom
|
||||||
, efStart = examStart
|
, efStart = examStart
|
||||||
, efEnd = examEnd
|
, efEnd = examEnd
|
||||||
, efFinished = examFinished
|
, efFinished = examFinished
|
||||||
@ -397,6 +402,7 @@ examTemplate cid = runMaybeT $ do
|
|||||||
, efRegisterTo = dateOffset <$> examRegisterTo oldExam
|
, efRegisterTo = dateOffset <$> examRegisterTo oldExam
|
||||||
, efDeregisterUntil = dateOffset <$> examDeregisterUntil oldExam
|
, efDeregisterUntil = dateOffset <$> examDeregisterUntil oldExam
|
||||||
, efPublishOccurrenceAssignments = dateOffset <$> examPublishOccurrenceAssignments oldExam
|
, efPublishOccurrenceAssignments = dateOffset <$> examPublishOccurrenceAssignments oldExam
|
||||||
|
, efPartsFrom = dateOffset <$> examPartsFrom oldExam
|
||||||
, efStart = dateOffset <$> examStart oldExam
|
, efStart = dateOffset <$> examStart oldExam
|
||||||
, efEnd = dateOffset <$> examEnd oldExam
|
, efEnd = dateOffset <$> examEnd oldExam
|
||||||
, efFinished = dateOffset <$> examFinished oldExam
|
, efFinished = dateOffset <$> examFinished oldExam
|
||||||
@ -425,9 +431,10 @@ validateExam cId oldExam = do
|
|||||||
guardValidation MsgExamDeregisterUntilMustBeAfterRegisterFrom $ NTop efDeregisterUntil >= NTop efRegisterFrom
|
guardValidation MsgExamDeregisterUntilMustBeAfterRegisterFrom $ NTop efDeregisterUntil >= NTop efRegisterFrom
|
||||||
guardValidation MsgExamStartMustBeAfterPublishOccurrenceAssignments $ Just False /= ((>=) <$> efStart <*> efPublishOccurrenceAssignments)
|
guardValidation MsgExamStartMustBeAfterPublishOccurrenceAssignments $ Just False /= ((>=) <$> efStart <*> efPublishOccurrenceAssignments)
|
||||||
guardValidation MsgExamEndMustBeAfterStart $ NTop efEnd >= NTop efStart
|
guardValidation MsgExamEndMustBeAfterStart $ NTop efEnd >= NTop efStart
|
||||||
guardValidation MsgExamFinishedMustBeAfterEnd $ Just False /= ((>=) <$> efFinished <*> efEnd)
|
guardValidation MsgExamFinishedMustBeAfterEnd $ Just False /= ((>=) <$> efFinished <*> efEnd)
|
||||||
guardValidation MsgExamFinishedMustBeAfterStart $ Just False /= ((>=) <$> efFinished <*> efStart)
|
guardValidation MsgExamFinishedMustBeAfterStart $ Just False /= ((>=) <$> efFinished <*> efStart)
|
||||||
|
guardValidation MsgExamPartsFromMustBeBeforeFinished $ NTop efFinished >= NTop efPartsFrom
|
||||||
|
|| is _Nothing efPartsFrom
|
||||||
|
|
||||||
forM_ efOccurrences $ \ExamOccurrenceForm{..} -> do
|
forM_ efOccurrences $ \ExamOccurrenceForm{..} -> do
|
||||||
guardValidation (MsgExamOccurrenceEndMustBeAfterStart eofName) $ NTop eofEnd >= NTop (Just eofStart)
|
guardValidation (MsgExamOccurrenceEndMustBeAfterStart eofName) $ NTop eofEnd >= NTop (Just eofStart)
|
||||||
@ -465,8 +472,11 @@ validateExam cId oldExam = do
|
|||||||
return ( examPart E.^. ExamPartId
|
return ( examPart E.^. ExamPartId
|
||||||
, examPart E.^. ExamPartNumber
|
, examPart E.^. ExamPartNumber
|
||||||
)
|
)
|
||||||
forM_ (join $ hoistMaybe oldPartsWithResults) $ \(E.Value epId, E.Value epNumber) ->
|
forM_ (join $ hoistMaybe oldPartsWithResults) $ \(E.Value epId, E.Value epNumber) -> do
|
||||||
guardValidationM (MsgExamPartCannotBeDeletedDueToResults epNumber) . anyM (otoList efExamParts) $ \ExamPartForm{..} -> (== Just epId) <$> traverse decrypt epfId
|
guardValidationM (MsgExamPartCannotBeDeletedDueToResults epNumber) . anyM (otoList efExamParts) $ \ExamPartForm{..} -> (== Just epId) <$> traverse decrypt epfId
|
||||||
|
runConduit $ transPipe lift (selectSource [] [])
|
||||||
|
.| C.filter (has $ _entityVal . _sheetType . _examPart . re _SqlKey . only epId)
|
||||||
|
.| C.mapM_ (\(Entity _ Sheet{..}) -> guardValidationM (MsgExamPartCannotBeDeletedDueToSheetReference epNumber sheetName) . anyM (otoList efExamParts) $ \ExamPartForm{..} -> (== Just epId) <$> traverse decrypt epfId)
|
||||||
|
|
||||||
|
|
||||||
mSchool <- liftHandler . runDB . E.selectMaybe . E.from $ \(course `E.InnerJoin` school) -> do
|
mSchool <- liftHandler . runDB . E.selectMaybe . E.from $ \(course `E.InnerJoin` school) -> do
|
||||||
|
|||||||
@ -50,6 +50,7 @@ postCExamNewR tid ssh csh = do
|
|||||||
, examDescription = efDescription
|
, examDescription = efDescription
|
||||||
, examExamMode = efExamMode
|
, examExamMode = efExamMode
|
||||||
, examStaff = efStaff
|
, examStaff = efStaff
|
||||||
|
, examPartsFrom = efPartsFrom
|
||||||
}
|
}
|
||||||
whenIsJust insertRes $ \examid -> do
|
whenIsJust insertRes $ \examid -> do
|
||||||
insertMany_
|
insertMany_
|
||||||
|
|||||||
@ -26,24 +26,34 @@ getEShowR tid ssh csh examn = do
|
|||||||
cTime <- liftIO getCurrentTime
|
cTime <- liftIO getCurrentTime
|
||||||
mUid <- maybeAuthId
|
mUid <- maybeAuthId
|
||||||
|
|
||||||
(Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools) <- runDB $ do
|
(Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools, (partsVisible, partsShown)) <- runDB $ do
|
||||||
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
|
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
|
||||||
school <- getJust examCourse >>= belongsToJust courseSchool
|
school <- getJust examCourse >>= belongsToJust courseSchool
|
||||||
|
|
||||||
|
lecturerInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
||||||
|
|
||||||
let examVisible = NTop (Just cTime) >= NTop examVisibleFrom
|
let examVisible = NTop (Just cTime) >= NTop examVisibleFrom
|
||||||
|
|
||||||
let gradingVisible = NTop (Just cTime) >= NTop examFinished
|
let gradingVisible = NTop (Just cTime) >= NTop examFinished
|
||||||
gradingShown <- or2M (return gradingVisible) . hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
gradingShown = gradingVisible || lecturerInfoShown
|
||||||
|
|
||||||
|
let partsVisible = gradingVisible
|
||||||
|
|| NTop (Just cTime) >= NTop examPartsFrom
|
||||||
|
partsShown = partsVisible || lecturerInfoShown
|
||||||
|
|
||||||
let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments || examOccurrenceRule == ExamRoomFifo
|
let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments || examOccurrenceRule == ExamRoomFifo
|
||||||
occurrenceAssignmentsShown <- or2M (return occurrenceAssignmentsVisible) . hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
occurrenceAssignmentsShown = occurrenceAssignmentsVisible || lecturerInfoShown
|
||||||
|
|
||||||
examParts <- sortOn (view $ _entityVal . _examPartNumber) <$> selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ]
|
sheets <- selectList [ SheetCourse ==. examCourse ] []
|
||||||
|
let examPartSheets epId = do
|
||||||
|
let sheets' = flip filter sheets $ \(Entity _ Sheet{..}) -> has (_examPart . re _SqlKey . only epId) sheetType
|
||||||
|
flip filterM sheets' $ \(Entity _ Sheet{..}) -> hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
||||||
|
examParts <- fmap (sortOn . view $ _1 . _entityVal . _examPartNumber) $ selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ] >>= traverse (\ep@(Entity epId _) -> (ep,,) <$> encrypt @ExamPartId @UUID epId <*> examPartSheets epId)
|
||||||
|
|
||||||
resultsRaw <- for mUid $ \uid ->
|
resultsRaw <- for mUid $ \uid ->
|
||||||
E.select . E.from $ \examPartResult -> do
|
E.select . E.from $ \examPartResult -> do
|
||||||
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
|
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
|
||||||
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map entityKey examParts)
|
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map (views _1 entityKey) examParts)
|
||||||
return examPartResult
|
return examPartResult
|
||||||
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
|
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
|
||||||
|
|
||||||
@ -83,8 +93,6 @@ getEShowR tid ssh csh examn = do
|
|||||||
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom)
|
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom)
|
||||||
= (Down $ registered' && not mayRegister, examOccurrenceStart, guardOn @Maybe showRoom examOccurrenceRoom)
|
= (Down $ registered' && not mayRegister, examOccurrenceStart, guardOn @Maybe showRoom examOccurrenceRoom)
|
||||||
|
|
||||||
lecturerInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
|
||||||
|
|
||||||
staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
|
staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
|
||||||
|
|
||||||
extraSchools <- E.select . E.from $ \(school' `E.InnerJoin` examOfficeSchool) -> do
|
extraSchools <- E.select . E.from $ \(school' `E.InnerJoin` examOfficeSchool) -> do
|
||||||
@ -92,7 +100,7 @@ getEShowR tid ssh csh examn = do
|
|||||||
E.where_ $ examOfficeSchool E.^. ExamOfficeSchoolExam E.==. E.val eId
|
E.where_ $ examOfficeSchool E.^. ExamOfficeSchoolExam E.==. E.val eId
|
||||||
return school'
|
return school'
|
||||||
|
|
||||||
return (exam, school, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools)
|
return (exam, school, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools, (partsVisible, partsShown))
|
||||||
|
|
||||||
let occurrenceNamesShown = lecturerInfoShown
|
let occurrenceNamesShown = lecturerInfoShown
|
||||||
partNumbersShown = lecturerInfoShown
|
partNumbersShown = lecturerInfoShown
|
||||||
@ -102,7 +110,7 @@ getEShowR tid ssh csh examn = do
|
|||||||
showRegisteredCount = lecturerInfoShown
|
showRegisteredCount = lecturerInfoShown
|
||||||
examFinishedMsg = if lecturerInfoShown then MsgExamFinished else MsgExamFinishedParticipant
|
examFinishedMsg = if lecturerInfoShown then MsgExamFinished else MsgExamFinishedParticipant
|
||||||
|
|
||||||
sumMaxPoints = sum [ fromRational examPartWeight * mPoints | Entity _ ExamPart{..} <- examParts, mPoints <- examPartMaxPoints ^.. _Just ]
|
sumMaxPoints = sum [ fromRational examPartWeight * mPoints | (Entity _ ExamPart{..}, _, _) <- examParts, mPoints <- examPartMaxPoints ^.. _Just ]
|
||||||
|
|
||||||
sumRegisteredCount = sumOf (folded . _3) occurrences
|
sumRegisteredCount = sumOf (folded . _3) occurrences
|
||||||
|
|
||||||
@ -175,8 +183,9 @@ getEShowR tid ssh csh examn = do
|
|||||||
|]
|
|]
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
showMaxPoints = any (has $ _entityVal . _examPartMaxPoints . _Just) examParts
|
showMaxPoints = gradingShown && any (has $ _1 . _entityVal . _examPartMaxPoints . _Just) examParts
|
||||||
showAchievedPoints = not $ null results
|
showAchievedPoints = gradingShown && not (null results)
|
||||||
|
showPartSheets = any (has $ _3 . folded) examParts
|
||||||
showOccurrenceRegisterColumn = occurrenceAssignmentsShown || (mayRegister && examOccurrenceRule == ExamRoomFifo)
|
showOccurrenceRegisterColumn = occurrenceAssignmentsShown || (mayRegister && examOccurrenceRule == ExamRoomFifo)
|
||||||
markUnregisteredOccurrences mOcc = occurrenceAssignmentsShown && hasRegistration && isn't _Just (registerWidget mOcc)
|
markUnregisteredOccurrences mOcc = occurrenceAssignmentsShown && hasRegistration && isn't _Just (registerWidget mOcc)
|
||||||
showOccurrenceMappingColumn = examOccurrenceRuleAutomatic examOccurrenceRule && occurrenceAssignmentsShown && is _Just examExamOccurrenceMapping
|
showOccurrenceMappingColumn = examOccurrenceRuleAutomatic examOccurrenceRule && occurrenceAssignmentsShown && is _Just examExamOccurrenceMapping
|
||||||
|
|||||||
@ -135,8 +135,8 @@ resultExamParts = _dbrOutput . _6 . itraversed
|
|||||||
resultExamPartResult :: ExamPartId -> Lens' ExamUserTableData (Maybe (Entity ExamPartResult))
|
resultExamPartResult :: ExamPartId -> Lens' ExamUserTableData (Maybe (Entity ExamPartResult))
|
||||||
resultExamPartResult epId = _dbrOutput . _6 . unsafeSingular (ix epId) . _2
|
resultExamPartResult epId = _dbrOutput . _6 . unsafeSingular (ix epId) . _2
|
||||||
|
|
||||||
resultExamPartResults :: IndexedTraversal' ExamPartId ExamUserTableData (Maybe (Entity ExamPartResult))
|
-- resultExamPartResults :: IndexedTraversal' ExamPartId ExamUserTableData (Maybe (Entity ExamPartResult))
|
||||||
resultExamPartResults = resultExamParts <. _2
|
-- resultExamPartResults = resultExamParts <. _2
|
||||||
|
|
||||||
resultCourseNote :: Traversal' ExamUserTableData (Entity CourseUserNote)
|
resultCourseNote :: Traversal' ExamUserTableData (Entity CourseUserNote)
|
||||||
resultCourseNote = _dbrOutput . _7 . _Just
|
resultCourseNote = _dbrOutput . _7 . _Just
|
||||||
@ -145,16 +145,24 @@ resultStudyFeatures :: Lens' ExamUserTableData UserTableStudyFeatures
|
|||||||
resultStudyFeatures = _dbrOutput . _8
|
resultStudyFeatures = _dbrOutput . _8
|
||||||
|
|
||||||
|
|
||||||
resultAutomaticExamBonus :: Exam -> Map UserId SheetTypeSummary -> Fold ExamUserTableData Points
|
resultAutomaticExamBonus :: Ord epId => Exam -> Map UserId (SheetTypeSummary epId) -> Fold ExamUserTableData Points
|
||||||
resultAutomaticExamBonus exam examBonus' = resultUser . _entityKey . folding (\uid -> join $ examResultBonus <$> examBonusRule exam <*> pure (examBonusPossible uid examBonus') <*> pure (examBonusAchieved uid examBonus'))
|
resultAutomaticExamBonus exam examBonus' = resultUser . _entityKey . folding (\uid -> join $ examResultBonus <$> examBonusRule exam <*> pure (examBonusPossible uid examBonus') <*> pure (examBonusAchieved uid examBonus'))
|
||||||
|
|
||||||
resultAutomaticExamResult :: Exam -> Map UserId SheetTypeSummary -> Fold ExamUserTableData ExamResultPassedGrade
|
resultAutomaticExamResult :: Exam -> Map UserId (SheetTypeSummary ExamPartId) -> Fold ExamUserTableData ExamResultPassedGrade
|
||||||
resultAutomaticExamResult exam@Exam{..} examBonus' = folding . runReader $ do
|
resultAutomaticExamResult exam@Exam{..} examBonus' = folding . runReader $ do
|
||||||
parts' <- asks $ sequence . toListOf (resultExamPartResults . to (^? _Just . _entityVal . _examPartResultResult))
|
parts' <- asks (itoListOf resultExamParts) >>= mapM (\(epId, (ep, mRes)) -> runMaybeT $ hoistMaybe (mRes ^? _Just . _entityVal . _examPartResultResult)
|
||||||
|
<|> MaybeT (preview $ resultAutomaticExamPartResult (Entity epId ep) examBonus')
|
||||||
|
)
|
||||||
bonus <- preview $ resultExamBonus . _entityVal . _examBonusBonus <> resultAutomaticExamBonus exam examBonus'
|
bonus <- preview $ resultExamBonus . _entityVal . _examBonusBonus <> resultAutomaticExamBonus exam examBonus'
|
||||||
let gradeRes = examGrade exam bonus =<< parts'
|
let gradeRes = examGrade exam bonus =<< sequence parts'
|
||||||
return $ fmap (bool Right (Left . view passingGrade) $ is _ExamGradingPass examGradingMode) <$> gradeRes
|
return $ fmap (bool Right (Left . view passingGrade) $ is _ExamGradingPass examGradingMode) <$> gradeRes
|
||||||
|
|
||||||
|
resultAutomaticExamPartResult :: Entity ExamPart -> Map UserId (SheetTypeSummary ExamPartId) -> Fold ExamUserTableData ExamResultPoints
|
||||||
|
resultAutomaticExamPartResult epEnt examBonus' = folding . runReader . runMaybeT $ do
|
||||||
|
uid <- view $ resultUser . _entityKey
|
||||||
|
summary <- hoistMaybe $ Map.lookup uid examBonus'
|
||||||
|
hoistMaybe $ sheetExamResult summary epEnt
|
||||||
|
|
||||||
|
|
||||||
csvExamPartHeader :: Prism' Csv.Name ExamPartNumber
|
csvExamPartHeader :: Prism' Csv.Name ExamPartNumber
|
||||||
csvExamPartHeader = prism' toHeader fromHeader
|
csvExamPartHeader = prism' toHeader fromHeader
|
||||||
@ -294,7 +302,8 @@ data ExamUserActionData = ExamUserDeregisterData
|
|||||||
| ExamUserSetResultData (Maybe ExamResultPassedGrade)
|
| ExamUserSetResultData (Maybe ExamResultPassedGrade)
|
||||||
| ExamUserAcceptComputedResultData
|
| ExamUserAcceptComputedResultData
|
||||||
| ExamUserResetToComputedResultData
|
| ExamUserResetToComputedResultData
|
||||||
{ examUserResetBonus :: Bool
|
{ examUserResetBonus
|
||||||
|
, examUserResetParts :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -479,8 +488,8 @@ postEUsersR tid ssh csh examn = do
|
|||||||
in propCell (getSum achievedPoints) (getSum sumSheetsPoints)
|
in propCell (getSum achievedPoints) (getSum sumSheetsPoints)
|
||||||
, guardOn doBonus $ sortable (Just "bonus") (i18nCell MsgExamBonusAchieved) . automaticCell $ resultExamBonus . _entityVal . _examBonusBonus . to Right <> resultAutomaticExamBonus' . to Left
|
, guardOn doBonus $ sortable (Just "bonus") (i18nCell MsgExamBonusAchieved) . automaticCell $ resultExamBonus . _entityVal . _examBonusBonus . to Right <> resultAutomaticExamBonus' . to Left
|
||||||
, pure $ mconcat
|
, pure $ mconcat
|
||||||
[ sortable (Just $ fromText [st|part-#{toPathPiece examPartNumber}|]) (i18nCell $ MsgExamPartNumbered examPartNumber) $ maybe mempty i18nCell . preview (resultExamPartResult epId . _Just . _entityVal . _examPartResultResult)
|
[ sortable (Just $ fromText [st|part-#{toPathPiece examPartNumber}|]) (i18nCell $ MsgExamPartNumbered examPartNumber) . automaticCell $ resultExamPartResult epId . _Just . _entityVal . _examPartResultResult . to Right <> resultAutomaticExamPartResult epEnt bonus . to Left
|
||||||
| Entity epId ExamPart{..} <- sortOn (examPartNumber . entityVal) examParts
|
| epEnt@(Entity epId ExamPart{..}) <- sortOn (examPartNumber . entityVal) examParts
|
||||||
]
|
]
|
||||||
, pure $ sortable (Just "exam-result") (i18nCell MsgExamResult) . automaticCell $ (resultExamResult . _entityVal . _examResultResult . to Right <> resultAutomaticExamResult' . to Left)
|
, pure $ sortable (Just "exam-result") (i18nCell MsgExamResult) . automaticCell $ (resultExamResult . _entityVal . _examResultResult . to Right <> resultAutomaticExamResult' . to Left)
|
||||||
, pure . sortable (Just "note") (i18nCell MsgCourseUserNote) $ \((,) <$> view (resultUser . _entityKey) <*> has resultCourseNote -> (uid, hasNote))
|
, pure . sortable (Just "note") (i18nCell MsgCourseUserNote) $ \((,) <$> view (resultUser . _entityKey) <*> has resultCourseNote -> (uid, hasNote))
|
||||||
@ -548,6 +557,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
, singletonMap ExamUserResetToComputedResult $
|
, singletonMap ExamUserResetToComputedResult $
|
||||||
ExamUserResetToComputedResultData
|
ExamUserResetToComputedResultData
|
||||||
<$> bool (pure True) (apopt checkBoxField (fslI MsgExamUserResetBonus) (Just True)) (is _Just examBonusRule)
|
<$> bool (pure True) (apopt checkBoxField (fslI MsgExamUserResetBonus) (Just True)) (is _Just examBonusRule)
|
||||||
|
<*> bool (pure True) (apopt checkBoxField (fslI MsgExamUserResetParts) (Just True)) (not $ all (null . examSummary) bonus)
|
||||||
, singletonMap ExamUserSetPartResult $
|
, singletonMap ExamUserSetPartResult $
|
||||||
ExamUserSetPartResultData
|
ExamUserSetPartResultData
|
||||||
<$> areq (selectField $ optionsPairs (map ((MsgExamPartNumbered &&& id) . examPartNumber . entityVal) examParts)) (fslI MsgExamPart) Nothing
|
<$> areq (selectField $ optionsPairs (map ((MsgExamPartNumbered &&& id) . examPartNumber . entityVal) examParts)) (fslI MsgExamPart) Nothing
|
||||||
@ -601,9 +611,11 @@ postEUsersR tid ssh csh examn = do
|
|||||||
<*> fmap (bool (const Nothing) Just showPoints) (preview $ resultUser . _entityKey . to (examBonusPossible ?? bonus) . _sumSheetsPoints . _Wrapped)
|
<*> fmap (bool (const Nothing) Just showPoints) (preview $ resultUser . _entityKey . to (examBonusPossible ?? bonus) . _sumSheetsPoints . _Wrapped)
|
||||||
<*> fmap (bool (const Nothing) Just showPasses) (preview $ resultUser . _entityKey . to (examBonusPossible ?? bonus) . _numSheetsPasses . _Wrapped . integral)
|
<*> fmap (bool (const Nothing) Just showPasses) (preview $ resultUser . _entityKey . to (examBonusPossible ?? bonus) . _numSheetsPasses . _Wrapped . integral)
|
||||||
<*> fmap (bool (const Nothing) Just doBonus ) (preview $ resultExamBonus . _entityVal . _examBonusBonus <> resultAutomaticExamBonus')
|
<*> fmap (bool (const Nothing) Just doBonus ) (preview $ resultExamBonus . _entityVal . _examBonusBonus <> resultAutomaticExamBonus')
|
||||||
<*> (Map.fromList . map (over _1 examPartNumber . over (_2 . _Just) (examPartResultResult . entityVal)) <$> asks (toListOf resultExamParts))
|
<*> encodePartResults
|
||||||
<*> preview (resultExamResult . _entityVal . _examResultResult <> resultAutomaticExamResult')
|
<*> preview (resultExamResult . _entityVal . _examResultResult <> resultAutomaticExamResult')
|
||||||
<*> preview (resultCourseNote . _entityVal . _courseUserNoteNote)
|
<*> preview (resultCourseNote . _entityVal . _courseUserNoteNote)
|
||||||
|
encodePartResults = fmap Map.fromList . forM examParts $ \epEnt@(Entity epId ExamPart{..}) -> (examPartNumber, ) <$>
|
||||||
|
preview (resultExamPartResult epId . _Just . _entityVal . _examPartResultResult <> resultAutomaticExamPartResult epEnt bonus)
|
||||||
dbtCsvDecode = Just DBTCsvDecode
|
dbtCsvDecode = Just DBTCsvDecode
|
||||||
{ dbtCsvRowKey = \csv -> do
|
{ dbtCsvRowKey = \csv -> do
|
||||||
uid <- lift $ view _2 <$> guessUser' csv
|
uid <- lift $ view _2 <$> guessUser' csv
|
||||||
@ -964,6 +976,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
hasBonus <- asks $ has resultExamBonus
|
hasBonus <- asks $ has resultExamBonus
|
||||||
autoResult <- preview $ resultAutomaticExamResult examVal bonus
|
autoResult <- preview $ resultAutomaticExamResult examVal bonus
|
||||||
autoBonus <- preview $ resultAutomaticExamBonus examVal bonus
|
autoBonus <- preview $ resultAutomaticExamBonus examVal bonus
|
||||||
|
autoParts <- asks (itoListOf resultExamParts) >>= mapM (\(epId, (ep, mRes)) -> fmap (guardOnM (isn't _Just mRes) . fmap (epId, )) . preview $ resultAutomaticExamPartResult (Entity epId ep) bonus)
|
||||||
lift $ if
|
lift $ if
|
||||||
| not hasResult
|
| not hasResult
|
||||||
, Just examResultResult <- autoResult
|
, Just examResultResult <- autoResult
|
||||||
@ -982,6 +995,15 @@ postEUsersR tid ssh csh examn = do
|
|||||||
| otherwise
|
| otherwise
|
||||||
-> return ()
|
-> return ()
|
||||||
|
|
||||||
|
iforM_ (Map.fromList $ catMaybes autoParts) $ \epId autoPartResult -> do
|
||||||
|
insert_ ExamPartResult
|
||||||
|
{ examPartResultExamPart = epId
|
||||||
|
, examPartResultUser = uid
|
||||||
|
, examPartResultResult = autoPartResult
|
||||||
|
, examPartResultLastChanged = now
|
||||||
|
}
|
||||||
|
audit $ TransactionExamPartResultEdit epId uid
|
||||||
|
|
||||||
insert_ ExamResult
|
insert_ ExamResult
|
||||||
{ examResultExam = eId
|
{ examResultExam = eId
|
||||||
, examResultUser = uid
|
, examResultUser = uid
|
||||||
@ -1003,6 +1025,12 @@ postEUsersR tid ssh csh examn = do
|
|||||||
whenIsJust bonusId' $ \bonusId -> do
|
whenIsJust bonusId' $ \bonusId -> do
|
||||||
delete bonusId
|
delete bonusId
|
||||||
audit $ TransactionExamBonusDeleted eId uid
|
audit $ TransactionExamBonusDeleted eId uid
|
||||||
|
when examUserResetParts $ do
|
||||||
|
forM_ (foldMap (Map.keysSet . unMergeMap . examSummary) $ Map.lookup uid bonus) $ \epId -> do
|
||||||
|
partResultId' <- getKeyBy $ UniqueExamPartResult epId uid
|
||||||
|
whenIsJust partResultId' $ \partResultId -> do
|
||||||
|
delete partResultId
|
||||||
|
audit $ TransactionExamPartResultDeleted epId uid
|
||||||
|
|
||||||
result <- getKeyBy $ UniqueExamResult eId uid
|
result <- getKeyBy $ UniqueExamResult eId uid
|
||||||
case result of
|
case result of
|
||||||
|
|||||||
@ -33,7 +33,7 @@ postSEditR tid ssh csh shn = do
|
|||||||
let template = Just $ SheetForm
|
let template = Just $ SheetForm
|
||||||
{ sfName = sheetName
|
{ sfName = sheetName
|
||||||
, sfDescription = sheetDescription
|
, sfDescription = sheetDescription
|
||||||
, sfType = sheetType
|
, sfType = review _SqlKey <$> sheetType
|
||||||
, sfGrouping = sheetGrouping
|
, sfGrouping = sheetGrouping
|
||||||
, sfVisibleFrom = sheetVisibleFrom
|
, sfVisibleFrom = sheetVisibleFrom
|
||||||
, sfActiveFrom = sheetActiveFrom
|
, sfActiveFrom = sheetActiveFrom
|
||||||
@ -74,7 +74,7 @@ handleSheetEdit tid ssh csh msId template dbAction = do
|
|||||||
{ sheetCourse = cid
|
{ sheetCourse = cid
|
||||||
, sheetName = sfName
|
, sheetName = sfName
|
||||||
, sheetDescription = sfDescription
|
, sheetDescription = sfDescription
|
||||||
, sheetType = sfType
|
, sheetType = view _SqlKey <$> sfType
|
||||||
, sheetGrouping = sfGrouping
|
, sheetGrouping = sfGrouping
|
||||||
, sheetMarkingText = sfMarkingText
|
, sheetMarkingText = sfMarkingText
|
||||||
, sheetVisibleFrom = sfVisibleFrom
|
, sheetVisibleFrom = sfVisibleFrom
|
||||||
|
|||||||
@ -37,7 +37,7 @@ data SheetForm = SheetForm
|
|||||||
, sfSolutionFrom :: Maybe UTCTime
|
, sfSolutionFrom :: Maybe UTCTime
|
||||||
, sfSubmissionMode :: SubmissionMode
|
, sfSubmissionMode :: SubmissionMode
|
||||||
, sfGrouping :: SheetGroup
|
, sfGrouping :: SheetGroup
|
||||||
, sfType :: SheetType
|
, sfType :: SheetType ExamPartId
|
||||||
, sfAutoDistribute :: Bool
|
, sfAutoDistribute :: Bool
|
||||||
, sfMarkingText :: Maybe StoredMarkup
|
, sfMarkingText :: Maybe StoredMarkup
|
||||||
, sfAnonymousCorrection :: Bool
|
, sfAnonymousCorrection :: Bool
|
||||||
@ -93,7 +93,7 @@ makeSheetForm cId msId template = identifyForm FIDsheet . validateForm validateS
|
|||||||
<* aformSection MsgSheetFormType
|
<* aformSection MsgSheetFormType
|
||||||
<*> submissionModeForm ((sfSubmissionMode <$> template) <|> pure (SubmissionMode False . Just $ UploadAny True defaultExtensionRestriction False))
|
<*> submissionModeForm ((sfSubmissionMode <$> template) <|> pure (SubmissionMode False . Just $ UploadAny True defaultExtensionRestriction False))
|
||||||
<*> sheetGroupAFormReq (fslI MsgSheetGroup) ((sfGrouping <$> template) <|> pure NoGroups)
|
<*> sheetGroupAFormReq (fslI MsgSheetGroup) ((sfGrouping <$> template) <|> pure NoGroups)
|
||||||
<*> sheetTypeAFormReq (fslI MsgSheetType) (sfType <$> template)
|
<*> sheetTypeAFormReq cId (fslI MsgSheetType) (sfType <$> template)
|
||||||
<*> apopt checkBoxField (fslI MsgAutoAssignCorrs) (sfAutoDistribute <$> template)
|
<*> apopt checkBoxField (fslI MsgAutoAssignCorrs) (sfAutoDistribute <$> template)
|
||||||
<*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
|
<*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
|
||||||
<*> apopt checkBoxField (fslI MsgSheetAnonymousCorrection & setTooltip MsgSheetAnonymousCorrectionTip) (sfAnonymousCorrection <$> template)
|
<*> apopt checkBoxField (fslI MsgSheetAnonymousCorrection & setTooltip MsgSheetAnonymousCorrectionTip) (sfAnonymousCorrection <$> template)
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import Handler.Utils
|
|||||||
import Handler.Utils.SheetType
|
import Handler.Utils.SheetType
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ getSheetListR tid ssh csh = do
|
|||||||
E.&&. submissionUser E.?. SubmissionUserUser E.==. E.val muid
|
E.&&. submissionUser E.?. SubmissionUserUser E.==. E.val muid
|
||||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
||||||
|
|
||||||
|
querySubmission :: E.SqlExpr (Entity Sheet) `E.LeftOuterJoin` (E.SqlExpr (Maybe (Entity Submission)) `E.InnerJoin` E.SqlExpr (Maybe (Entity SubmissionUser))) -> E.SqlExpr (Maybe (Entity Submission))
|
||||||
|
querySubmission (_sheet `E.LeftOuterJoin` (submission `E.InnerJoin` _submissionUser)) = submission
|
||||||
|
|
||||||
sheetFilter :: SheetName -> DB Bool
|
sheetFilter :: SheetName -> DB Bool
|
||||||
sheetFilter sheetName = hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
sheetFilter sheetName = hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
||||||
|
|
||||||
@ -64,8 +68,10 @@ getSheetListR tid ssh csh = do
|
|||||||
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> maybe mempty dateTimeCell sheetActiveFrom
|
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> maybe mempty dateTimeCell sheetActiveFrom
|
||||||
, sortable (Just "submission-until") (i18nCell MsgSheetActiveTo)
|
, sortable (Just "submission-until") (i18nCell MsgSheetActiveTo)
|
||||||
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> maybe mempty dateTimeCell sheetActiveTo
|
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> maybe mempty dateTimeCell sheetActiveTo
|
||||||
, sortable Nothing (i18nCell MsgSheetType)
|
, sortable Nothing (i18nCell MsgSheetType) $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> cell $ do
|
||||||
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> i18nCell sheetType
|
sheetTypeDesc <- liftHandler . runDB $ sheetTypeDescription sheetCourse sheetType
|
||||||
|
tr <- getTranslate
|
||||||
|
toWidget $ sheetTypeDesc tr
|
||||||
, sortable Nothing (i18nCell MsgSubmission)
|
, sortable Nothing (i18nCell MsgSubmission)
|
||||||
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub, _)} -> case mbSub of
|
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub, _)} -> case mbSub of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
@ -86,7 +92,6 @@ getSheetListR tid ssh csh = do
|
|||||||
mkRoute = liftHandler $ do
|
mkRoute = liftHandler $ do
|
||||||
cid' <- encrypt sid
|
cid' <- encrypt sid
|
||||||
return $ CSubmissionR tid ssh csh sheetName cid' CorrectionR
|
return $ CSubmissionR tid ssh csh sheetName cid' CorrectionR
|
||||||
mTuple mA mB = (,) <$> mA <*> mB -- Hamlet does not support enough haskell-syntax for this
|
|
||||||
acell = anchorCellM mkRoute $(widgetFile "widgets/rating/rating")
|
acell = anchorCellM mkRoute $(widgetFile "widgets/rating/rating")
|
||||||
tellStats = do
|
tellStats = do
|
||||||
r <- mkRoute
|
r <- mkRoute
|
||||||
@ -154,10 +159,17 @@ getSheetListR tid ssh csh = do
|
|||||||
, dbtFilter = mconcat
|
, dbtFilter = mconcat
|
||||||
[ singletonMap "may-access" . FilterProjected $ \(Any b) DBRow{..} ->
|
[ singletonMap "may-access" . FilterProjected $ \(Any b) DBRow{..} ->
|
||||||
let (Entity _ Sheet{..}, _, _, _) = dbrOutput :: (Entity Sheet, E.Value (Maybe UTCTime), Maybe (Entity Submission), (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool))
|
let (Entity _ Sheet{..}, _, _, _) = dbrOutput :: (Entity Sheet, E.Value (Maybe UTCTime), Maybe (Entity Submission), (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool))
|
||||||
in (==b) <$> sheetFilter sheetName :: DB Bool
|
in (== b) <$> sheetFilter sheetName :: DB Bool
|
||||||
|
, singletonMap "rated" . FilterColumn $ \(Any b) -> (E.==. E.val b) . E.isJust . (E.?. SubmissionRatingTime) . querySubmission
|
||||||
|
, singletonMap "is-exam" . FilterProjected $ \(Any b) DBRow{..} ->
|
||||||
|
let (Entity _ Sheet{..}, _, _, _) = dbrOutput :: (Entity Sheet, E.Value (Maybe UTCTime), Maybe (Entity Submission), (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool))
|
||||||
|
in return $ is _ExamPartPoints sheetType == b :: DB Bool
|
||||||
]
|
]
|
||||||
, dbtFilterUI = mempty
|
, dbtFilterUI = mconcat
|
||||||
, dbtStyle = def
|
[ flip (prismAForm $ singletonFilter "is-exam" . maybePrism _PathPiece) $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgSheetTypeIsExam)
|
||||||
|
, flip (prismAForm $ singletonFilter "rated" . maybePrism _PathPiece) $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgIsRated)
|
||||||
|
]
|
||||||
|
, dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
|
||||||
, dbtParams = def
|
, dbtParams = def
|
||||||
, dbtIdent = "sheets" :: Text
|
, dbtIdent = "sheets" :: Text
|
||||||
, dbtCsvEncode = noCsvEncode
|
, dbtCsvEncode = noCsvEncode
|
||||||
|
|||||||
@ -48,7 +48,7 @@ postSheetNewR tid ssh csh = do
|
|||||||
in Just $ SheetForm
|
in Just $ SheetForm
|
||||||
{ sfName = stepTextCounterCI sheetName
|
{ sfName = stepTextCounterCI sheetName
|
||||||
, sfDescription = sheetDescription
|
, sfDescription = sheetDescription
|
||||||
, sfType = sheetType
|
, sfType = review _SqlKey <$> sheetType
|
||||||
, sfGrouping = sheetGrouping
|
, sfGrouping = sheetGrouping
|
||||||
, sfVisibleFrom = addTime <$> sheetVisibleFrom
|
, sfVisibleFrom = addTime <$> sheetVisibleFrom
|
||||||
, sfActiveFrom = addTime <$> sheetActiveFrom
|
, sfActiveFrom = addTime <$> sheetActiveFrom
|
||||||
|
|||||||
@ -150,6 +150,8 @@ getSShowR tid ssh csh shn = do
|
|||||||
guardM . hasWriteAccessTo $ CSheetR tid ssh csh shn SEditR
|
guardM . hasWriteAccessTo $ CSheetR tid ssh csh shn SEditR
|
||||||
return $ notification NotificationBroad =<< messageI Warning MsgSheetSubmissionModeNoneWithoutNotGraded
|
return $ notification NotificationBroad =<< messageI Warning MsgSheetSubmissionModeNoneWithoutNotGraded
|
||||||
|
|
||||||
|
sTypeDesc <- runDB $ sheetTypeDescription (sheetCourse sheet) (sheetType sheet)
|
||||||
|
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI $ prependCourseTitle tid ssh csh $ SomeMessage shn
|
setTitleI $ prependCourseTitle tid ssh csh $ SomeMessage shn
|
||||||
let zipLink = CSheetR tid ssh csh shn SArchiveR
|
let zipLink = CSheetR tid ssh csh shn SArchiveR
|
||||||
@ -161,4 +163,5 @@ getSShowR tid ssh csh shn = do
|
|||||||
solutionFrom <- traverse (formatTime SelFormatDateTime) $ sheetSolutionFrom sheet
|
solutionFrom <- traverse (formatTime SelFormatDateTime) $ sheetSolutionFrom sheet
|
||||||
markingText <- runMaybeT $ assertM_ (Authorized ==) (evalAccessCorrector tid ssh csh) >> hoistMaybe (sheetMarkingText sheet)
|
markingText <- runMaybeT $ assertM_ (Authorized ==) (evalAccessCorrector tid ssh csh) >> hoistMaybe (sheetMarkingText sheet)
|
||||||
submissionTip <- messageI Info MsgSheetCorrectorSubmissionsTip
|
submissionTip <- messageI Info MsgSheetCorrectorSubmissionsTip
|
||||||
|
tr <- getTranslate
|
||||||
$(widgetFile "sheetShow")
|
$(widgetFile "sheetShow")
|
||||||
|
|||||||
@ -49,8 +49,11 @@ postCorrectionR tid ssh csh shn cid = do
|
|||||||
<*> hasWriteAccessTo (CSheetR tid ssh csh shn SSubsR)
|
<*> hasWriteAccessTo (CSheetR tid ssh csh shn SSubsR)
|
||||||
|
|
||||||
MsgRenderer mr <- getMsgRenderer
|
MsgRenderer mr <- getMsgRenderer
|
||||||
|
ur <- getUrlRenderParams
|
||||||
|
tr <- getTranslate
|
||||||
case results of
|
case results of
|
||||||
[(Entity cId Course{}, Entity shId Sheet{..}, Entity _ subm@Submission{..}, corrector, E.Value filesCorrected)] -> do
|
[(Entity cId Course{}, Entity shId Sheet{..}, Entity _ subm@Submission{..}, corrector, E.Value filesCorrected)] -> do
|
||||||
|
sheetTypeDesc <- runDB $ sheetTypeDescription cId sheetType
|
||||||
let ratingComment = submissionRatingComment >>= (\c -> c <$ guard (not $ null c)) . Text.strip
|
let ratingComment = submissionRatingComment >>= (\c -> c <$ guard (not $ null c)) . Text.strip
|
||||||
pointsForm = case sheetType of
|
pointsForm = case sheetType of
|
||||||
NotGraded
|
NotGraded
|
||||||
@ -59,10 +62,10 @@ postCorrectionR tid ssh csh shn cid = do
|
|||||||
-> Just <$> apopt (convertField (bool 0 1) (/= 0) checkBoxField) (fslI MsgPassed) submissionRatingPoints
|
-> Just <$> apopt (convertField (bool 0 1) (/= 0) checkBoxField) (fslI MsgPassed) submissionRatingPoints
|
||||||
(preview _grading -> Just PassAlways)
|
(preview _grading -> Just PassAlways)
|
||||||
-> Just <$> aforced (convertField (bool 0 1) (/= 0) checkBoxField) (fslI MsgPassed) 1
|
-> Just <$> aforced (convertField (bool 0 1) (/= 0) checkBoxField) (fslI MsgPassed) 1
|
||||||
_otherwise
|
_otherwise -> aSetTooltip (Just $ sheetTypeDesc tr ur) $
|
||||||
-> aopt (pointsFieldMax $ preview (_grading . _maxPoints) sheetType)
|
aopt (pointsFieldMax $ preview (_grading . _maxPoints) sheetType)
|
||||||
(fslpI MsgRatingPoints (mr MsgPointsPlaceholder) & setTooltip sheetType)
|
(fslpI MsgRatingPoints (mr MsgPointsPlaceholder))
|
||||||
(Just submissionRatingPoints)
|
(Just submissionRatingPoints)
|
||||||
correctorForm
|
correctorForm
|
||||||
| not isLecturer = wFormToAForm $ pure . Just <$> requireAuthId
|
| not isLecturer = wFormToAForm $ pure . Just <$> requireAuthId
|
||||||
| otherwise = wFormToAForm $ do
|
| otherwise = wFormToAForm $ do
|
||||||
@ -142,6 +145,7 @@ postCorrectionR tid ssh csh shn cid = do
|
|||||||
$if not (submissionRatingDone subm)
|
$if not (submissionRatingDone subm)
|
||||||
\ ^{isVisibleWidget False}
|
\ ^{isVisibleWidget False}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
siteLayout headingWgt $ do
|
siteLayout headingWgt $ do
|
||||||
setTitleI heading
|
setTitleI heading
|
||||||
urlArchive <- toTextUrl . CSubmissionR tid ssh csh shn cid $ SubArchiveR SubmissionCorrected
|
urlArchive <- toTextUrl . CSubmissionR tid ssh csh shn cid $ SubArchiveR SubmissionCorrected
|
||||||
@ -154,9 +158,13 @@ getCorrectionUserR tid ssh csh shn cid = do
|
|||||||
results <- runDB $ correctionData tid ssh csh shn sub
|
results <- runDB $ correctionData tid ssh csh shn sub
|
||||||
|
|
||||||
case results of
|
case results of
|
||||||
[(Entity _ Course{..}, Entity _ Sheet{..}, Entity _ Submission{..}, corrector@(Just _), E.Value filesCorrected)] ->
|
[(_, Entity _ Sheet{..}, Entity _ Submission{..}, corrector@(Just _), E.Value filesCorrected)] -> do
|
||||||
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment
|
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment
|
||||||
in defaultLayout $ do
|
let heading = MsgCorrectionHead tid ssh csh shn cid
|
||||||
urlArchive <- toTextUrl . CSubmissionR courseTerm courseSchool courseShorthand sheetName cid $ SubArchiveR SubmissionCorrected
|
urlArchive <- toTextUrl . CSubmissionR tid ssh csh shn cid $ SubArchiveR SubmissionCorrected
|
||||||
$(widgetFile "correction-user")
|
tr <- getTranslate
|
||||||
|
sheetTypeDesc <- liftHandler . runDB $ sheetTypeDescription sheetCourse sheetType
|
||||||
|
siteLayoutMsg heading $ do
|
||||||
|
setTitleI heading
|
||||||
|
$(widgetFile "correction-user")
|
||||||
_ -> notFound
|
_ -> notFound
|
||||||
|
|||||||
@ -548,12 +548,14 @@ submissionHelper tid ssh csh shn mcid = do
|
|||||||
E.where_ $ sFile1 E.^. SubmissionFileSubmission E.==. E.val subId
|
E.where_ $ sFile1 E.^. SubmissionFileSubmission E.==. E.val subId
|
||||||
E.where_ $ sFile2 E.?. SubmissionFileSubmission E.==. E.just (E.val subId)
|
E.where_ $ sFile2 E.?. SubmissionFileSubmission E.==. E.just (E.val subId)
|
||||||
|
|
||||||
|
sheetTypeDesc <- liftHandler . runDB $ sheetTypeDescription sheetCourse sheetType
|
||||||
|
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI $ MsgSubmissionEditHead tid ssh csh shn
|
setTitleI $ MsgSubmissionEditHead tid ssh csh shn
|
||||||
(urlArchive, urlOriginal) <- fmap ((,) <$> preview (_Just . _1) <*> preview (_Just . _2)) . for mcid $ \cID
|
(urlArchive, urlOriginal) <- fmap ((,) <$> preview (_Just . _1) <*> preview (_Just . _2)) . for mcid $ \cID
|
||||||
-> let mkUrl sft = toTextUrl . CSubmissionR tid ssh csh shn cID $ SubArchiveR sft
|
-> let mkUrl sft = toTextUrl . CSubmissionR tid ssh csh shn cID $ SubArchiveR sft
|
||||||
in liftHandler . runDB $ (,) <$> mkUrl SubmissionCorrected <*> mkUrl SubmissionOriginal
|
in liftHandler . runDB $ (,) <$> mkUrl SubmissionCorrected <*> mkUrl SubmissionOriginal
|
||||||
|
tr <- getTranslate
|
||||||
let correctionWdgt = guardOnM (showCorrection && maybe False submissionRatingDone msubmission) ((,) <$> msubmission <*> mcid) <&> \(Submission{..}, cid) ->
|
let correctionWdgt = guardOnM (showCorrection && maybe False submissionRatingDone msubmission) ((,) <$> msubmission <*> mcid) <&> \(Submission{..}, cid) ->
|
||||||
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment
|
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment
|
||||||
in $(widgetFile "correction-user")
|
in $(widgetFile "correction-user")
|
||||||
|
|||||||
@ -118,7 +118,7 @@ colSubmissionLink = sortable (Just "submission") (i18nCell MsgSubmission)
|
|||||||
shn = sheetName $ entityVal sheet
|
shn = sheetName $ entityVal sheet
|
||||||
in anchorCellC $cacheIdentHere (CSubmissionR tid ssh csh shn cid SubShowR) (toPathPiece cid)
|
in anchorCellC $cacheIdentHere (CSubmissionR tid ssh csh shn cid SubShowR) (toPathPiece cid)
|
||||||
|
|
||||||
colSelect :: forall act h. (Semigroup act, Monoid act, Headedness h) => Colonnade h CorrectionTableData (DBCell _ (FormResult (act, DBFormResult CryptoFileNameSubmission Bool CorrectionTableData), SheetTypeSummary))
|
colSelect :: forall act h epId. (Semigroup act, Monoid act, Headedness h, Ord epId) => Colonnade h CorrectionTableData (DBCell _ (FormResult (act, DBFormResult CryptoFileNameSubmission Bool CorrectionTableData), SheetTypeSummary epId))
|
||||||
colSelect = dbSelect (_1 . applying _2) id $ \DBRow{ dbrOutput=(_, _, _, _, _, _, cid, _) } -> return cid
|
colSelect = dbSelect (_1 . applying _2) id $ \DBRow{ dbrOutput=(_, _, _, _, _, _, cid, _) } -> return cid
|
||||||
|
|
||||||
colSubmittors :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
colSubmittors :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
@ -150,7 +150,7 @@ colSGroups = sortable (Just "submittors-group") (i18nCell MsgSubmissionGroup) $
|
|||||||
| otherwise
|
| otherwise
|
||||||
-> mempty
|
-> mempty
|
||||||
|
|
||||||
colRating :: forall m a. IsDBTable m (a, SheetTypeSummary) => Colonnade Sortable CorrectionTableData (DBCell m (a, SheetTypeSummary))
|
colRating :: forall m a. IsDBTable m (a, SheetTypeSummary SqlBackendKey) => Colonnade Sortable CorrectionTableData (DBCell m (a, SheetTypeSummary SqlBackendKey))
|
||||||
colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(Entity subId sub@Submission{..}, Entity _ Sheet{..}, course, _, _, _, _, _) } ->
|
colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(Entity subId sub@Submission{..}, Entity _ Sheet{..}, course, _, _, _, _, _) } ->
|
||||||
let csh = course ^. _2
|
let csh = course ^. _2
|
||||||
tid = course ^. _3
|
tid = course ^. _3
|
||||||
@ -160,14 +160,13 @@ colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(E
|
|||||||
mkRoute = do
|
mkRoute = do
|
||||||
cid <- encrypt subId
|
cid <- encrypt subId
|
||||||
return $ CSubmissionR tid ssh csh sheetName cid CorrectionR
|
return $ CSubmissionR tid ssh csh sheetName cid CorrectionR
|
||||||
mTuple mA mB = (,) <$> mA <*> mB -- Hamlet does not support enough haskell-syntax for this
|
|
||||||
in mconcat
|
in mconcat
|
||||||
[ anchorCellCM $cacheIdentHere mkRoute $(widgetFile "widgets/rating/rating")
|
[ anchorCellCM $cacheIdentHere mkRoute $(widgetFile "widgets/rating/rating")
|
||||||
, writerCell $ do
|
, writerCell $ do
|
||||||
let
|
let
|
||||||
summary :: SheetTypeSummary
|
summary :: SheetTypeSummary SqlBackendKey
|
||||||
summary = sheetTypeSum sheetType $ submissionRatingPoints <* guard (submissionRatingDone sub)
|
summary = sheetTypeSum sheetType $ submissionRatingPoints <* guard (submissionRatingDone sub)
|
||||||
scribe (_2 :: Lens' (a, SheetTypeSummary) SheetTypeSummary) summary
|
scribe (_2 :: Lens' (a, SheetTypeSummary SqlBackendKey) (SheetTypeSummary SqlBackendKey)) summary
|
||||||
]
|
]
|
||||||
|
|
||||||
colAssigned :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
colAssigned :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
@ -198,7 +197,10 @@ colPointsField = sortable (Just "rating") (i18nCell MsgColumnRatingPoints) $ for
|
|||||||
)
|
)
|
||||||
|
|
||||||
colMaxPointsField :: _ => Colonnade Sortable CorrectionTableData (DBCell m (FormResult (DBFormResult SubmissionId (a, Maybe Points, b) CorrectionTableData)))
|
colMaxPointsField :: _ => Colonnade Sortable CorrectionTableData (DBCell m (FormResult (DBFormResult SubmissionId (a, Maybe Points, b) CorrectionTableData)))
|
||||||
colMaxPointsField = sortable (Just "sheet-type") (i18nCell MsgSheetType) $ i18nCell . (\DBRow{ dbrOutput=(_, Entity _ Sheet{sheetType}, _, _, _, _, _, _) } -> sheetType)
|
colMaxPointsField = sortable (Just "sheet-type") (i18nCell MsgSheetType) $ \DBRow{ dbrOutput=(_, Entity _ Sheet{sheetCourse, sheetType}, _, _, _, _, _, _) } -> cell $ do
|
||||||
|
sheetTypeDesc <- liftHandler . runDB $ sheetTypeDescription sheetCourse sheetType
|
||||||
|
tr <- getTranslate
|
||||||
|
toWidget $ sheetTypeDesc tr
|
||||||
|
|
||||||
colCommentField :: Colonnade Sortable CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, b, Maybe Text) CorrectionTableData)))
|
colCommentField :: Colonnade Sortable CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, b, Maybe Text) CorrectionTableData)))
|
||||||
colCommentField = sortable (Just "comment") (i18nCell MsgRatingComment) $ (cellAttrs <>~ [("style","width:60%")]) <$> formCell id
|
colCommentField = sortable (Just "comment") (i18nCell MsgRatingComment) $ (cellAttrs <>~ [("style","width:60%")]) <$> formCell id
|
||||||
@ -434,7 +436,7 @@ correctionsR whereClause displayColumns dbtFilterUI psValidator actions = do
|
|||||||
setTitleI MsgCourseCorrectionsTitle
|
setTitleI MsgCourseCorrectionsTitle
|
||||||
$(widgetFile "corrections")
|
$(widgetFile "corrections")
|
||||||
|
|
||||||
correctionsR' :: _ -> _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler (Widget, SheetTypeSummary)
|
correctionsR' :: _ -> _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler (Widget, SheetTypeSummary SqlBackendKey)
|
||||||
correctionsR' whereClause displayColumns dbtFilterUI psValidator actions = do
|
correctionsR' whereClause displayColumns dbtFilterUI psValidator actions = do
|
||||||
currentRoute <- fromMaybe (error "correctionsR called from 404-handler") <$> getCurrentRoute -- This should never be called from a 404 handler
|
currentRoute <- fromMaybe (error "correctionsR called from 404-handler") <$> getCurrentRoute -- This should never be called from a 404 handler
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import qualified Data.Char as Char
|
|||||||
import qualified Data.RFC5051 as RFC5051
|
import qualified Data.RFC5051 as RFC5051
|
||||||
|
|
||||||
import Handler.Utils.I18n
|
import Handler.Utils.I18n
|
||||||
|
import Handler.Utils.Sheet
|
||||||
|
|
||||||
|
|
||||||
fetchExamAux :: ( SqlBackendCanRead backend
|
fetchExamAux :: ( SqlBackendCanRead backend
|
||||||
@ -90,7 +91,7 @@ fetchCourseIdExam :: MonadHandler m => TermId -> SchoolId -> CourseShorthand ->
|
|||||||
fetchCourseIdExam tid ssh cid examn = over _1 E.unValue <$> fetchExamAux (\tutorial course -> (course E.^. CourseId, tutorial)) tid ssh cid examn
|
fetchCourseIdExam tid ssh cid examn = over _1 E.unValue <$> fetchExamAux (\tutorial course -> (course E.^. CourseId, tutorial)) tid ssh cid examn
|
||||||
|
|
||||||
|
|
||||||
examBonus :: MonadHandler m => Entity Exam -> ReaderT SqlBackend m (Map UserId SheetTypeSummary)
|
examBonus :: (MonadHandler m, MonadThrow m) => Entity Exam -> ReaderT SqlBackend m (Map UserId (SheetTypeSummary ExamPartId))
|
||||||
examBonus (Entity eId Exam{..}) = runConduit $
|
examBonus (Entity eId Exam{..}) = runConduit $
|
||||||
let
|
let
|
||||||
rawData = E.selectSource . E.from $ \(((examRegistration `E.LeftOuterJoin` examOccurrence) `E.InnerJoin` sheet) `E.LeftOuterJoin` submission) -> E.distinctOnOrderBy [ E.asc $ examRegistration E.^. ExamRegistrationUser, E.asc $ sheet E.^. SheetId ] $ do
|
rawData = E.selectSource . E.from $ \(((examRegistration `E.LeftOuterJoin` examOccurrence) `E.InnerJoin` sheet) `E.LeftOuterJoin` submission) -> E.distinctOnOrderBy [ E.asc $ examRegistration E.^. ExamRegistrationUser, E.asc $ sheet E.^. SheetId ] $ do
|
||||||
@ -112,19 +113,20 @@ examBonus (Entity eId Exam{..}) = runConduit $
|
|||||||
]
|
]
|
||||||
( E.else_ . E.not_ . E.isNothing $ sheet E.^. SheetVisibleFrom
|
( E.else_ . E.not_ . E.isNothing $ sheet E.^. SheetVisibleFrom
|
||||||
)
|
)
|
||||||
return (examRegistration E.^. ExamRegistrationUser, sheet E.^. SheetType, submission)
|
return (examRegistration E.^. ExamRegistrationUser, sheet E.^. SheetType, submission, sheet E.^. SheetCourse)
|
||||||
accum = C.fold ?? Map.empty $ \acc (E.Value uid, E.Value sheetType, fmap entityVal -> sub) ->
|
accum = C.foldM ?? Map.empty $ \acc (E.Value uid, E.Value sheetType, fmap entityVal -> sub, E.Value cId) -> do
|
||||||
flip (Map.insertWith mappend uid) acc . sheetTypeSum sheetType $ assertM submissionRatingDone sub >>= submissionRatingPoints
|
sheetType' <- fmap entityKey <$> resolveSheetType cId sheetType
|
||||||
|
return . flip (Map.insertWith mappend uid) acc . sheetTypeSum sheetType' $ assertM submissionRatingDone sub >>= submissionRatingPoints
|
||||||
in rawData .| accum
|
in rawData .| accum
|
||||||
|
|
||||||
examBonusPossible, examBonusAchieved :: UserId -> Map UserId SheetTypeSummary -> SheetGradeSummary
|
examBonusPossible, examBonusAchieved :: Ord epId => UserId -> Map UserId (SheetTypeSummary epId) -> SheetGradeSummary
|
||||||
examBonusPossible uid bonusMap = normalSummary $ Map.findWithDefault mempty uid bonusMap
|
examBonusPossible uid bonusMap = normalSummary $ Map.findWithDefault mempty uid bonusMap
|
||||||
examBonusAchieved uid bonusMap = mappend <$> normalSummary <*> bonusSummary $ Map.findWithDefault mempty uid bonusMap
|
examBonusAchieved uid bonusMap = mappend <$> normalSummary <*> bonusSummary $ Map.findWithDefault mempty uid bonusMap
|
||||||
|
|
||||||
getRelevantSheetsUpTo :: CourseId
|
getRelevantSheetsUpTo :: CourseId
|
||||||
-> UserId
|
-> UserId
|
||||||
-> Maybe UTCTime
|
-> Maybe UTCTime
|
||||||
-> DB (Map SheetId (SheetType, Maybe Points))
|
-> DB (Map SheetId (SheetType SqlBackendKey, Maybe Points))
|
||||||
getRelevantSheetsUpTo cid uid mCutoff
|
getRelevantSheetsUpTo cid uid mCutoff
|
||||||
= fmap postprocess . E.select . E.from $ \(sheet `E.LeftOuterJoin` submission) -> E.distinctOnOrderBy [ E.asc $ sheet E.^. SheetId ] $ do
|
= fmap postprocess . E.select . E.from $ \(sheet `E.LeftOuterJoin` submission) -> E.distinctOnOrderBy [ E.asc $ sheet E.^. SheetId ] $ do
|
||||||
E.on $ submission E.?. SubmissionSheet E.==. E.just (sheet E.^. SheetId)
|
E.on $ submission E.?. SubmissionSheet E.==. E.just (sheet E.^. SheetId)
|
||||||
@ -138,8 +140,8 @@ getRelevantSheetsUpTo cid uid mCutoff
|
|||||||
Nothing -> E.where_ . E.not_ . E.isNothing $ sheet E.^. SheetVisibleFrom
|
Nothing -> E.where_ . E.not_ . E.isNothing $ sheet E.^. SheetVisibleFrom
|
||||||
return (sheet E.^. SheetId, sheet E.^. SheetType, submission)
|
return (sheet E.^. SheetId, sheet E.^. SheetType, submission)
|
||||||
where
|
where
|
||||||
postprocess :: [(E.Value SheetId, E.Value SheetType, Maybe (Entity Submission))]
|
postprocess :: [(E.Value SheetId, E.Value (SheetType SqlBackendKey), Maybe (Entity Submission))]
|
||||||
-> Map SheetId (SheetType, Maybe Points)
|
-> Map SheetId (SheetType SqlBackendKey, Maybe Points)
|
||||||
postprocess = Map.fromList . map postprocess'
|
postprocess = Map.fromList . map postprocess'
|
||||||
where postprocess' (E.Value sId, E.Value sType, fmap entityVal -> sub)
|
where postprocess' (E.Value sId, E.Value sType, fmap entityVal -> sub)
|
||||||
= (sId, ) . (sType, ) $ assertM submissionRatingDone sub >>= submissionRatingPoints
|
= (sId, ) . (sType, ) $ assertM submissionRatingDone sub >>= submissionRatingPoints
|
||||||
@ -155,36 +157,27 @@ examResultBonus bonusRule bonusPossible bonusAchieved = case bonusRule of
|
|||||||
ExamBonusManual{}
|
ExamBonusManual{}
|
||||||
-> Nothing
|
-> Nothing
|
||||||
ExamBonusPoints{..}
|
ExamBonusPoints{..}
|
||||||
-> Just . roundToPoints bonusRound $ toRational bonusMaxPoints * bonusProp
|
-> Just . roundToPoints' bonusRound $ toRational bonusMaxPoints * bonusProp bonusMaxPoints
|
||||||
where
|
where
|
||||||
bonusProp :: Rational
|
bonusProp :: Points -> Rational
|
||||||
bonusProp
|
bonusProp mPoints
|
||||||
| possible <= 0 = 1
|
| possible <= 0 = 1
|
||||||
| otherwise = achieved / possible
|
| otherwise = achieved / possible
|
||||||
where
|
where
|
||||||
achieved = toRational (getSum $ achievedPoints bonusAchieved) + scalePasses (getSum $ achievedPasses bonusAchieved)
|
achieved = toRational (getSum $ achievedPoints bonusAchieved - achievedPassPoints bonusAchieved) + scalePasses (getSum $ achievedPasses bonusAchieved)
|
||||||
possible = toRational (getSum $ sumSheetsPoints bonusPossible) + scalePasses (getSum $ numSheetsPasses bonusPossible)
|
possible = toRational (getSum $ sumSheetsPoints bonusPossible - sumSheetsPassPoints bonusPossible) + scalePasses (getSum $ numSheetsPasses bonusPossible)
|
||||||
|
|
||||||
scalePasses :: Integer -> Rational
|
scalePasses :: Integer -> Rational
|
||||||
-- ^ Rescale passes so count of all sheets with pass is worth as many points as sum of all sheets with points
|
-- ^ Rescale passes so count of all sheets with pass is worth as many points as sum of all sheets with points
|
||||||
scalePasses passes
|
scalePasses passes
|
||||||
|
| pointsPossible <= 0 = toRational mPoints / fromInteger passesPossible
|
||||||
| passesPossible <= 0 = 0
|
| passesPossible <= 0 = 0
|
||||||
| otherwise = fromInteger passes / fromInteger passesPossible * toRational pointsPossible
|
| otherwise = fromInteger passes / fromInteger passesPossible * toRational pointsPossible
|
||||||
where
|
where
|
||||||
passesPossible = getSum $ numSheetsPasses bonusPossible
|
passesPossible = getSum $ numSheetsPasses bonusPossible
|
||||||
pointsPossible = getSum $ sumSheetsPoints bonusPossible
|
pointsPossible = getSum $ sumSheetsPoints bonusPossible - sumSheetsPassPoints bonusPossible
|
||||||
|
|
||||||
roundToPoints :: forall a. HasResolution a => Fixed a -> Rational -> Fixed a
|
roundToPoints' mult = (* mult) . roundToPoints . (/ toRational mult)
|
||||||
-- ^ 'round-to-nearest' whole multiple
|
|
||||||
roundToPoints (MkFixed mult'@(fromInteger -> mult)) ((* toRational (resolution (Proxy @a))) -> raw)
|
|
||||||
= MkFixed . (* mult') $
|
|
||||||
let (whole, frac) = raw `divMod'` mult
|
|
||||||
in if | abs frac < abs (mult / 2)
|
|
||||||
-> whole
|
|
||||||
| raw >= 0
|
|
||||||
-> succ whole
|
|
||||||
| otherwise
|
|
||||||
-> pred whole
|
|
||||||
|
|
||||||
examGrade :: ( MonoFoldable mono
|
examGrade :: ( MonoFoldable mono
|
||||||
, Element mono ~ ExamResultPoints
|
, Element mono ~ ExamResultPoints
|
||||||
@ -223,9 +216,10 @@ examGrade Exam{..} mBonus (otoList -> results)
|
|||||||
lowerBounds = zip [Grade40, Grade37 ..] examGradingKey'
|
lowerBounds = zip [Grade40, Grade37 ..] examGradingKey'
|
||||||
|
|
||||||
examBonusGrade :: ( MonoFoldable sheets
|
examBonusGrade :: ( MonoFoldable sheets
|
||||||
, Element sheets ~ (SheetType, Maybe Points)
|
, Element sheets ~ (SheetType epId, Maybe Points)
|
||||||
, MonoFoldable results
|
, MonoFoldable results
|
||||||
, Element results ~ ExamResultPoints
|
, Element results ~ ExamResultPoints
|
||||||
|
, Ord epId
|
||||||
)
|
)
|
||||||
=> Exam
|
=> Exam
|
||||||
-> Either Points sheets -- ^ `Points` retrieved from relevant `ExamBonus`, iff it exists
|
-> Either Points sheets -- ^ `Points` retrieved from relevant `ExamBonus`, iff it exists
|
||||||
@ -241,8 +235,6 @@ examBonusGrade exam@Exam{..} bonusInp = (mBonus, ) . examGrade exam mBonus
|
|||||||
bonusAchieved = (<>) <$> fmap normalSummary sheetSummary <*> fmap bonusSummary sheetSummary
|
bonusAchieved = (<>) <$> fmap normalSummary sheetSummary <*> fmap bonusSummary sheetSummary
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data ExamAutoOccurrenceConfig = ExamAutoOccurrenceConfig
|
data ExamAutoOccurrenceConfig = ExamAutoOccurrenceConfig
|
||||||
{ eaocMinimizeRooms :: Bool
|
{ eaocMinimizeRooms :: Bool
|
||||||
, eaocFinenessCost :: Rational -- ^ Cost factor incentivising shorter common prefixes on breaks between rooms
|
, eaocFinenessCost :: Rational -- ^ Cost factor incentivising shorter common prefixes on breaks between rooms
|
||||||
|
|||||||
@ -1168,16 +1168,6 @@ nullaryPathPiece ''SheetGrading' (camelToPathPiece . dropSuffix "'")
|
|||||||
embedRenderMessage ''UniWorX ''SheetGrading' ("SheetGrading" <>)
|
embedRenderMessage ''UniWorX ''SheetGrading' ("SheetGrading" <>)
|
||||||
|
|
||||||
|
|
||||||
data SheetType' = Normal' | Bonus' | Informational' | NotGraded'
|
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
|
||||||
|
|
||||||
instance Universe SheetType'
|
|
||||||
instance Finite SheetType'
|
|
||||||
|
|
||||||
nullaryPathPiece ''SheetType' (camelToPathPiece . dropSuffix "'")
|
|
||||||
embedRenderMessage ''UniWorX ''SheetType' ("SheetType" <>)
|
|
||||||
|
|
||||||
|
|
||||||
data SheetGroup' = Arbitrary' | RegisteredGroups' | NoGroups'
|
data SheetGroup' = Arbitrary' | RegisteredGroups' | NoGroups'
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
||||||
|
|
||||||
@ -1207,30 +1197,47 @@ sheetGradingAFormReq fs template = multiActionA selOptions fs (classify' <$> tem
|
|||||||
passPointsReq = apreq pointsField (fslI MsgSheetGradingPassingPoints) (template >>= preview _passingPoints)
|
passPointsReq = apreq pointsField (fslI MsgSheetGradingPassingPoints) (template >>= preview _passingPoints)
|
||||||
|
|
||||||
|
|
||||||
sheetTypeAFormReq :: FieldSettings UniWorX -> Maybe SheetType -> AForm Handler SheetType
|
sheetTypeAFormReq :: CourseId -> FieldSettings UniWorX -> Maybe (SheetType ExamPartId) -> AForm Handler (SheetType ExamPartId)
|
||||||
sheetTypeAFormReq fs template = explainedMultiActionA acts opts fs (classify' <$> template)
|
sheetTypeAFormReq cId fs template = wFormToAForm $ do
|
||||||
where
|
examParts'' <- liftHandler . runDB . E.select . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examPart) -> do
|
||||||
acts = Map.fromList
|
E.on $ exam E.^. ExamId E.==. examPart E.^. ExamPartExam
|
||||||
[ ( Normal', Normal <$> gradingReq )
|
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||||
, ( Bonus' , Bonus <$> gradingReq )
|
E.where_ $ exam E.^. ExamCourse E.==. E.val cId
|
||||||
, ( Informational', Informational <$> gradingReq )
|
return (exam, course, examPart)
|
||||||
, ( NotGraded', pure NotGraded )
|
|
||||||
|
editableExams <- fmap Map.keysSet . flip mapFilterM (foldMap (\(Entity eId exam, Entity _ course, _) -> Map.singleton eId (exam, course)) examParts'') $ \(Exam{..}, Course{..}) ->
|
||||||
|
hasWriteAccessTo $ CExamR courseTerm courseSchool courseShorthand examName EEditR
|
||||||
|
|
||||||
|
let
|
||||||
|
examParts' = flip foldMap examParts'' $ \(eEnt@(Entity eId _), _, epEnt) -> guardOn @[] (eId `Set.member` editableExams) (eEnt, epEnt)
|
||||||
|
examParts = flip sortOn examParts' $ \(Entity _ Exam{..}, Entity _ ExamPart{..}) -> (examName, examPartNumber)
|
||||||
|
|
||||||
|
doExamPartPoints = fmap classifySheetType template == Just ExamPartPoints'
|
||||||
|
|| not (null examParts)
|
||||||
|
|
||||||
|
acts = Map.fromList $ catMaybes
|
||||||
|
[ pure ( Normal', Normal <$> gradingReq )
|
||||||
|
, pure ( Bonus' , Bonus <$> gradingReq )
|
||||||
|
, pure ( Informational', Informational <$> gradingReq )
|
||||||
|
, pure ( NotGraded', pure NotGraded )
|
||||||
|
, guardOn doExamPartPoints ( ExamPartPoints', ExamPartPoints <$> examPartReq <*> weightReq <*> gradingReq )
|
||||||
]
|
]
|
||||||
gradingReq = sheetGradingAFormReq (fslI MsgSheetGrading
|
gradingReq = sheetGradingAFormReq (fslI MsgSheetGrading
|
||||||
& setTooltip MsgSheetGradingInfo) (template >>= preview _grading)
|
& setTooltip MsgSheetGradingInfo) $ template >>= preview _grading
|
||||||
|
weightReq = apreq (checkBool (>= 0) MsgSheetTypeExamPartPointsWeightNegative rationalField) (fslI MsgSheetTypeExamPartPointsWeight) $ preview (_Just . _weight) template
|
||||||
|
examPartReq = apreq examPartField (fslI MsgSheetTypeExamPartPointsExamPart) $ preview (_Just . _examPart) template >>= assertM' (\epId -> any (\(_, Entity epId' _) -> epId == epId') examParts)
|
||||||
|
examPartField = selectField' Nothing . fmap (fmap $ \(_, Entity epId _) -> epId) $ optionsCryptoIdF examParts
|
||||||
|
(\(_, Entity epId _) -> return epId)
|
||||||
|
(\(Entity _ Exam{..}, Entity _ ExamPart{..}) -> return $ MsgSheetTypeExamPartPointsExamPartOption examName examPartNumber)
|
||||||
|
|
||||||
opts = explainOptionList optionsFinite $ \case
|
opts = explainOptionList optionsFinite $ \case
|
||||||
Normal' -> return $ i18n MsgSheetTypeInfoNormalLecturer
|
Normal' -> return $ i18n MsgSheetTypeInfoNormalLecturer
|
||||||
Bonus' -> return $ i18n MsgSheetTypeInfoBonus
|
Bonus' -> return $ i18n MsgSheetTypeInfoBonus
|
||||||
Informational' -> return $ i18n MsgSheetTypeInfoInformational
|
Informational' -> return $ i18n MsgSheetTypeInfoInformational
|
||||||
NotGraded' -> return $ i18n MsgSheetTypeInfoNotGraded
|
NotGraded' -> return $ i18n MsgSheetTypeInfoNotGraded
|
||||||
|
ExamPartPoints' -> return $ i18n MsgSheetTypeInfoExamPartPoints
|
||||||
classify' :: SheetType -> SheetType'
|
|
||||||
classify' = \case
|
aFormToWForm . explainedMultiActionA acts opts fs $ classifySheetType <$> template
|
||||||
Bonus {} -> Bonus'
|
|
||||||
Normal {} -> Normal'
|
|
||||||
Informational {} -> Informational'
|
|
||||||
NotGraded -> NotGraded'
|
|
||||||
|
|
||||||
sheetGroupAFormReq :: FieldSettings UniWorX -> Maybe SheetGroup -> AForm Handler SheetGroup
|
sheetGroupAFormReq :: FieldSettings UniWorX -> Maybe SheetGroup -> AForm Handler SheetGroup
|
||||||
sheetGroupAFormReq fs template = explainedMultiActionA acts opts fs (classify' <$> template)
|
sheetGroupAFormReq fs template = explainedMultiActionA acts opts fs (classify' <$> template)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import Import
|
|||||||
|
|
||||||
import Handler.Utils.Files
|
import Handler.Utils.Files
|
||||||
import Handler.Utils.DateTime (getDateTimeFormatter)
|
import Handler.Utils.DateTime (getDateTimeFormatter)
|
||||||
|
import Handler.Utils.Sheet (resolveSheetTypeRating)
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import qualified Data.Char as Char
|
import qualified Data.Char as Char
|
||||||
|
|
||||||
|
|
||||||
validateRating :: SheetType -> Rating' -> [RatingValidityException]
|
validateRating :: SheetType a -> Rating' -> [RatingValidityException]
|
||||||
validateRating ratingSheetType Rating'{ ratingPoints=Just rp }
|
validateRating ratingSheetType Rating'{ ratingPoints=Just rp }
|
||||||
| rp < 0
|
| rp < 0
|
||||||
= [RatingNegative]
|
= [RatingNegative]
|
||||||
@ -59,7 +60,8 @@ getRating submissionId = runMaybeT $ do
|
|||||||
E.where_ $ submission E.^. SubmissionId E.==. E.val submissionId
|
E.where_ $ submission E.^. SubmissionId E.==. E.val submissionId
|
||||||
|
|
||||||
-- Yes, we can only pass a tuple through 'E.select'
|
-- Yes, we can only pass a tuple through 'E.select'
|
||||||
return ( course E.^. CourseTerm
|
return ( course E.^. CourseId
|
||||||
|
, course E.^. CourseTerm
|
||||||
, school E.^. SchoolName
|
, school E.^. SchoolName
|
||||||
, course E.^. CourseName
|
, course E.^. CourseName
|
||||||
, sheet E.^. SheetName
|
, sheet E.^. SheetName
|
||||||
@ -68,14 +70,16 @@ getRating submissionId = runMaybeT $ do
|
|||||||
, submission
|
, submission
|
||||||
)
|
)
|
||||||
|
|
||||||
[ ( unTermKey . E.unValue -> ratingCourseTerm
|
[ ( E.unValue -> cId
|
||||||
|
, unTermKey . E.unValue -> ratingCourseTerm
|
||||||
, E.unValue -> ratingCourseSchool
|
, E.unValue -> ratingCourseSchool
|
||||||
, E.unValue -> ratingCourseName
|
, E.unValue -> ratingCourseName
|
||||||
, E.unValue -> ratingSheetName
|
, E.unValue -> ratingSheetName
|
||||||
, E.unValue -> ratingCorrectorName
|
, E.unValue -> ratingCorrectorName
|
||||||
, E.unValue -> ratingSheetType
|
, E.unValue -> ratingSheetType'
|
||||||
, E.Entity _ sub@Submission{..}
|
, E.Entity _ sub@Submission{..}
|
||||||
) ] <- lift query
|
) ] <- lift query
|
||||||
|
ratingSheetType <- lift $ resolveSheetTypeRating cId ratingSheetType'
|
||||||
|
|
||||||
let ratingPoints = submissionRatingPoints
|
let ratingPoints = submissionRatingPoints
|
||||||
ratingComment = submissionRatingComment
|
ratingComment = submissionRatingComment
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import Import
|
|||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
import qualified Database.Esqueleto.Internal.Internal as E
|
import qualified Database.Esqueleto.Internal.Internal as E
|
||||||
|
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
|
import Text.Hamlet
|
||||||
|
|
||||||
|
|
||||||
-- | Map sheet file types to their visibily dates of a given sheet, for convenience
|
-- | Map sheet file types to their visibily dates of a given sheet, for convenience
|
||||||
sheetFileTypeDates :: Sheet -> SheetFileType -> Maybe UTCTime
|
sheetFileTypeDates :: Sheet -> SheetFileType -> Maybe UTCTime
|
||||||
@ -51,3 +55,96 @@ fetchSheetIdCourseId :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Ye
|
|||||||
fetchSheetIdCourseId tid ssh cid shn = bimap E.unValue E.unValue <$> fetchSheetAux (\sheet course -> (sheet E.^. SheetId, course E.^. CourseId)) tid ssh cid shn
|
fetchSheetIdCourseId tid ssh cid shn = bimap E.unValue E.unValue <$> fetchSheetAux (\sheet course -> (sheet E.^. SheetId, course E.^. CourseId)) tid ssh cid shn
|
||||||
|
|
||||||
|
|
||||||
|
data ResolveSheetTypeException
|
||||||
|
= ResolveSheetTypeExamPartUnavailable SqlBackendKey
|
||||||
|
| ResolveSheetTypeForeignExam
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Exception)
|
||||||
|
|
||||||
|
resolveSheetType :: ( MonadThrow m
|
||||||
|
, MonadIO m
|
||||||
|
)
|
||||||
|
=> CourseId
|
||||||
|
-> SheetType SqlBackendKey
|
||||||
|
-> ReaderT SqlBackend m (SheetType (Entity ExamPart))
|
||||||
|
resolveSheetType cId = traverse $ \epId'@(review _SqlKey -> epId) -> do
|
||||||
|
ep@(Entity _ ExamPart{..}) <- maybe (throwM $ ResolveSheetTypeExamPartUnavailable epId') return =<< getEntity epId
|
||||||
|
Exam{..} <- getJust examPartExam
|
||||||
|
if | examCourse /= cId -> throwM ResolveSheetTypeForeignExam
|
||||||
|
| otherwise -> return ep
|
||||||
|
|
||||||
|
resolveSheetTypeRating :: ( MonadThrow m
|
||||||
|
, MonadIO m
|
||||||
|
)
|
||||||
|
=> CourseId
|
||||||
|
-> SheetType SqlBackendKey
|
||||||
|
-> ReaderT SqlBackend m (SheetType RatingExamPartReference)
|
||||||
|
resolveSheetTypeRating cId dbST = do
|
||||||
|
eST <- resolveSheetType cId dbST
|
||||||
|
case matching _ExamPartPoints eST of
|
||||||
|
Left t -> return t
|
||||||
|
Right (Entity _ ExamPart{..}, weight, grading) -> do
|
||||||
|
Exam{..} <- getJust examPartExam
|
||||||
|
return ExamPartPoints
|
||||||
|
{ examPart = RatingExamPartReference examName examPartNumber
|
||||||
|
, ..
|
||||||
|
}
|
||||||
|
|
||||||
|
sheetTypeDescription :: forall m.
|
||||||
|
( MonadThrow m
|
||||||
|
, MonadHandler m, HandlerSite m ~ UniWorX
|
||||||
|
)
|
||||||
|
=> CourseId
|
||||||
|
-> SheetType SqlBackendKey
|
||||||
|
-> ReaderT SqlBackend m (HtmlUrlI18n (SomeMessage UniWorX) (Route UniWorX))
|
||||||
|
sheetTypeDescription cId dbST = do
|
||||||
|
sType' <- resolveSheetType cId dbST
|
||||||
|
sType <- for sType' $ \(Entity _epId ExamPart{..}) -> do
|
||||||
|
Exam{..} <- getJust examPartExam
|
||||||
|
Course{..} <- getJust examCourse
|
||||||
|
cTime <- liftIO getCurrentTime
|
||||||
|
lecturerInfo <- hasReadAccessTo $ CExamR courseTerm courseSchool courseShorthand examName EEditR
|
||||||
|
let partVisible = gradingVisible
|
||||||
|
|| NTop (Just cTime) >= NTop examPartsFrom
|
||||||
|
|| lecturerInfo
|
||||||
|
gradingVisible = NTop (Just cTime) >= NTop examPartsFrom
|
||||||
|
|| lecturerInfo
|
||||||
|
return (examName, examPartName, examPartNumber, partVisible, gradingVisible, CExamR courseTerm courseSchool courseShorthand examName EShowR)
|
||||||
|
return $(ihamletFile "templates/widgets/sheetType.hamlet")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sheetExamResult :: SheetTypeSummary ExamPartId -> Entity ExamPart -> Maybe ExamResultPoints
|
||||||
|
sheetExamResult SheetTypeSummary{ examSummary = MergeMap examSummary'' } (Entity epId ExamPart{..}) = Map.lookup epId examSummary'' <&> \examSummary' ->
|
||||||
|
let
|
||||||
|
sumOfWeights = getSum $ foldMap (views _1 Sum) examSummary'
|
||||||
|
weightRescale = recip sumOfWeights
|
||||||
|
|
||||||
|
toExamPoints :: (Rational, SheetGradeSummary) -> Maybe Rational
|
||||||
|
toExamPoints (weight, summary)
|
||||||
|
| sumOfWeights <= 0 = Nothing
|
||||||
|
| otherwise = Just . (* weight) $ case examPartMaxPoints of
|
||||||
|
Just maxPoints -> toRational maxPoints * bonusProp
|
||||||
|
Nothing -> bonusProp * possible
|
||||||
|
where
|
||||||
|
bonusProp :: Rational
|
||||||
|
bonusProp | possible <= 0 = 1
|
||||||
|
| otherwise = achieved / possible
|
||||||
|
|
||||||
|
achieved = toRational (getSum $ achievedPoints summary - achievedPassPoints summary) + scalePasses (getSum $ achievedPasses summary)
|
||||||
|
possible = toRational (getSum $ sumSheetsPoints summary - sumSheetsPassPoints summary) + scalePasses (getSum $ numSheetsPasses summary)
|
||||||
|
|
||||||
|
scalePasses :: Integer -> Rational
|
||||||
|
scalePasses passes
|
||||||
|
| pointsPossible <= 0
|
||||||
|
, Just maxPoints <- examPartMaxPoints = fromInteger passes * toRational maxPoints / fromInteger passesPossible
|
||||||
|
| pointsPossible <= 0 = 0
|
||||||
|
| passesPossible <= 0 = 0
|
||||||
|
| otherwise = fromInteger passes / (fromInteger passesPossible * passesWeights) * (toRational pointsPossible * pointsWeights)
|
||||||
|
where
|
||||||
|
passesPossible = getSum $ numSheetsPasses summary
|
||||||
|
pointsPossible = getSum $ sumSheetsPoints summary - sumSheetsPassPoints summary
|
||||||
|
|
||||||
|
pointsWeights = getSum $ foldMap (\(sWeight, gradeSummary) -> guardMonoid (sumSheetsPoints gradeSummary - sumSheetsPassPoints gradeSummary > 0) $ Sum sWeight) examSummary'
|
||||||
|
passesWeights = getSum $ foldMap (\(sWeight, gradeSummary) -> guardMonoid (numSheetsPasses gradeSummary > 0) $ Sum sWeight) examSummary'
|
||||||
|
in ExamAttended . roundToPoints . (* examPartWeight) . (* weightRescale) . getSum . fold $ foldMapM (fmap Sum . toExamPoints) examSummary'
|
||||||
|
|||||||
@ -5,7 +5,7 @@ module Handler.Utils.SheetType
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
addBonusToPoints :: SheetTypeSummary -> SheetTypeSummary
|
addBonusToPoints :: SheetTypeSummary a -> SheetTypeSummary a
|
||||||
addBonusToPoints sts =
|
addBonusToPoints sts =
|
||||||
sts & _normalSummary . _achievedPasses %~ (min passmax . (passbonus +))
|
sts & _normalSummary . _achievedPasses %~ (min passmax . (passbonus +))
|
||||||
& _normalSummary . _achievedPoints %~ (min ptsmax . (ptsbonus +))
|
& _normalSummary . _achievedPoints %~ (min ptsmax . (ptsbonus +))
|
||||||
@ -15,7 +15,7 @@ addBonusToPoints sts =
|
|||||||
ptsmax = sts ^. _normalSummary . _sumMarkedPoints
|
ptsmax = sts ^. _normalSummary . _sumMarkedPoints
|
||||||
ptsbonus = sts ^. _bonusSummary . _achievedPoints
|
ptsbonus = sts ^. _bonusSummary . _achievedPoints
|
||||||
|
|
||||||
gradeSummaryWidget :: RenderMessage UniWorX msg => (Integer -> msg) -> SheetTypeSummary -> Widget
|
gradeSummaryWidget :: RenderMessage UniWorX msg => (Integer -> msg) -> SheetTypeSummary a -> Widget
|
||||||
gradeSummaryWidget title sts =
|
gradeSummaryWidget title sts =
|
||||||
let SheetTypeSummary{..} = addBonusToPoints sts
|
let SheetTypeSummary{..} = addBonusToPoints sts
|
||||||
sumSummaries = normalSummary <> bonusSummary <> informationalSummary & _numSheets %~ (<> numNotGraded)
|
sumSummaries = normalSummary <> bonusSummary <> informationalSummary & _numSheets %~ (<> numNotGraded)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ module Handler.Utils.Table.Pagination
|
|||||||
, dbTableWidget, dbTableWidget'
|
, dbTableWidget, dbTableWidget'
|
||||||
, dbTableDB, dbTableDB'
|
, dbTableDB, dbTableDB'
|
||||||
, widgetColonnade, formColonnade, dbColonnade
|
, widgetColonnade, formColonnade, dbColonnade
|
||||||
, cell, textCell, stringCell, i18nCell
|
, cell, wgtCell, textCell, stringCell, i18nCell
|
||||||
, anchorCell, anchorCell', anchorCellM, anchorCellM'
|
, anchorCell, anchorCell', anchorCellM, anchorCellM'
|
||||||
, linkEitherCell, linkEitherCellM, linkEitherCellM'
|
, linkEitherCell, linkEitherCellM, linkEitherCellM'
|
||||||
, maybeAnchorCellM, maybeAnchorCellM', maybeLinkEitherCellM'
|
, maybeAnchorCellM, maybeAnchorCellM', maybeLinkEitherCellM'
|
||||||
@ -1508,6 +1508,9 @@ pagesizeField psLim = selectField $ do
|
|||||||
cell :: IsDBTable m a => Widget -> DBCell m a
|
cell :: IsDBTable m a => Widget -> DBCell m a
|
||||||
cell wgt = dbCell # ([], return wgt)
|
cell wgt = dbCell # ([], return wgt)
|
||||||
|
|
||||||
|
wgtCell :: (IsDBTable m a, ToWidget UniWorX wgt) => wgt -> DBCell m a
|
||||||
|
wgtCell = cell . toWidget
|
||||||
|
|
||||||
textCell, stringCell :: (MonoFoldable msg, Element msg ~ Char, IsDBTable m a) => msg -> DBCell m a
|
textCell, stringCell :: (MonoFoldable msg, Element msg ~ Char, IsDBTable m a) => msg -> DBCell m a
|
||||||
textCell = cell . toWidget . (pack :: String -> Text) . otoList
|
textCell = cell . toWidget . (pack :: String -> Text) . otoList
|
||||||
stringCell = textCell
|
stringCell = textCell
|
||||||
|
|||||||
@ -187,6 +187,7 @@ import Text.Shakespeare.Text.Instances as Import ()
|
|||||||
import Ldap.Client.Instances as Import ()
|
import Ldap.Client.Instances as Import ()
|
||||||
import Network.URI.Instances as Import ()
|
import Network.URI.Instances as Import ()
|
||||||
import Data.MultiSet.Instances as Import ()
|
import Data.MultiSet.Instances as Import ()
|
||||||
|
import Control.Arrow.Instances as Import ()
|
||||||
|
|
||||||
import Crypto.Hash as Import (Digest, SHA3_256, SHA3_512)
|
import Crypto.Hash as Import (Digest, SHA3_256, SHA3_512)
|
||||||
import Crypto.Random as Import (ChaChaDRG, Seed)
|
import Crypto.Random as Import (ChaChaDRG, Seed)
|
||||||
@ -213,6 +214,8 @@ import Data.Kind as Import (Type, Constraint)
|
|||||||
|
|
||||||
import Data.Scientific as Import (Scientific, formatScientific)
|
import Data.Scientific as Import (Scientific, formatScientific)
|
||||||
|
|
||||||
|
import Data.MultiSet as Import (MultiSet)
|
||||||
|
|
||||||
|
|
||||||
import Control.Monad.Trans.RWS (RWST)
|
import Control.Monad.Trans.RWS (RWST)
|
||||||
|
|
||||||
|
|||||||
@ -12,14 +12,16 @@ import Jobs.Handler.SendNotification.Utils
|
|||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
|
||||||
dispatchNotificationSubmissionRated :: SubmissionId -> UserId -> Handler ()
|
dispatchNotificationSubmissionRated :: SubmissionId -> UserId -> Handler ()
|
||||||
dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipient $ do
|
dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipient $ do
|
||||||
(Course{..}, Sheet{..}, Submission{..}, corrector) <- liftHandler . runDB $ do
|
(Course{..}, Sheet{..}, Submission{..}, corrector, sheetTypeDesc) <- liftHandler . runDB $ do
|
||||||
submission@Submission{submissionRatingBy} <- getJust nSubmission
|
submission@Submission{submissionRatingBy} <- getJust nSubmission
|
||||||
sheet <- belongsToJust submissionSheet submission
|
sheet <- belongsToJust submissionSheet submission
|
||||||
course <- belongsToJust sheetCourse sheet
|
course <- belongsToJust sheetCourse sheet
|
||||||
corrector <- traverse getJust submissionRatingBy
|
corrector <- traverse getJust submissionRatingBy
|
||||||
return (course, sheet, submission, corrector)
|
sheetTypeDesc <- sheetTypeDescription (sheetCourse sheet) (sheetType sheet)
|
||||||
|
return (course, sheet, submission, corrector, sheetTypeDesc)
|
||||||
|
|
||||||
whenIsJust corrector $ \corrector' ->
|
whenIsJust corrector $ \corrector' ->
|
||||||
addMailHeader "Reply-To" . renderAddress $ userAddressFrom corrector'
|
addMailHeader "Reply-To" . renderAddress $ userAddressFrom corrector'
|
||||||
@ -29,7 +31,6 @@ dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipien
|
|||||||
csid <- encrypt nSubmission
|
csid <- encrypt nSubmission
|
||||||
MsgRenderer mr <- getMailMsgRenderer
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
let sheetTypeDesc = mr sheetType
|
|
||||||
submissionRatingTime' <- traverse (formatTimeMail SelFormatDateTime) submissionRatingTime
|
submissionRatingTime' <- traverse (formatTimeMail SelFormatDateTime) submissionRatingTime
|
||||||
let tid = courseTerm
|
let tid = courseTerm
|
||||||
ssh = courseSchool
|
ssh = courseSchool
|
||||||
@ -38,4 +39,4 @@ dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipien
|
|||||||
|
|
||||||
editNotifications <- mkEditNotifications jRecipient
|
editNotifications <- mkEditNotifications jRecipient
|
||||||
|
|
||||||
addHtmlMarkdownAlternatives ($(ihamletFile "templates/mail/submissionRated.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
addHtmlMarkdownAlternatives ($(ihamletFile "templates/mail/submissionRated.hamlet") :: HtmlUrlI18n (SomeMessage UniWorX) (Route UniWorX))
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import qualified Data.HashSet as HashSet
|
|||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
|
|
||||||
ihamletSomeMessage :: HtmlUrlI18n UniWorXMessage (Route UniWorX) -> HtmlUrlI18n (SomeMessage UniWorX) (Route UniWorX)
|
ihamletSomeMessage :: HtmlUrlI18n UniWorXMessage route -> HtmlUrlI18n (SomeMessage UniWorX) route
|
||||||
ihamletSomeMessage f trans = f $ trans . SomeMessage
|
ihamletSomeMessage f trans = f $ trans . SomeMessage
|
||||||
|
|
||||||
mkEditNotifications :: (MonadHandler m, HandlerSite m ~ UniWorX) => UserId -> m (HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
mkEditNotifications :: (MonadHandler m, HandlerSite m ~ UniWorX) => UserId -> m (HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import Text.Blaze (ToMarkup(..))
|
|||||||
|
|
||||||
import Database.Persist.Sql (BackendKey(..))
|
import Database.Persist.Sql (BackendKey(..))
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
|
||||||
type SqlBackendKey = BackendKey SqlBackend
|
type SqlBackendKey = BackendKey SqlBackend
|
||||||
|
|
||||||
@ -38,6 +40,9 @@ share [mkPersist sqlSettings, mkDeleteCascade sqlSettings, mkMigrate "migrateUni
|
|||||||
submissionRatingDone :: Submission -> Bool
|
submissionRatingDone :: Submission -> Bool
|
||||||
submissionRatingDone Submission{..} = isJust submissionRatingTime
|
submissionRatingDone Submission{..} = isJust submissionRatingTime
|
||||||
|
|
||||||
|
sqlSubmissionRatingDone :: E.SqlExpr (Entity Submission) -> E.SqlExpr (E.Value Bool)
|
||||||
|
sqlSubmissionRatingDone submission = E.not_ . E.isNothing $ submission E.^. SubmissionRatingTime
|
||||||
|
|
||||||
|
|
||||||
deriving newtype instance ToJSONKey UserId
|
deriving newtype instance ToJSONKey UserId
|
||||||
deriving newtype instance FromJSONKey UserId
|
deriving newtype instance FromJSONKey UserId
|
||||||
@ -46,6 +51,8 @@ deriving newtype instance FromJSONKey ExamOccurrenceId
|
|||||||
|
|
||||||
deriving instance Show (Unique ExamPart)
|
deriving instance Show (Unique ExamPart)
|
||||||
|
|
||||||
|
deriving anyclass instance NFData ExamPart
|
||||||
|
|
||||||
-- ToMarkup and ToMessage instances for displaying selected database primary keys
|
-- ToMarkup and ToMessage instances for displaying selected database primary keys
|
||||||
|
|
||||||
instance ToMarkup (Key School) where
|
instance ToMarkup (Key School) where
|
||||||
|
|||||||
@ -96,6 +96,7 @@ data ManualMigration
|
|||||||
| Migration20200916ExamMode
|
| Migration20200916ExamMode
|
||||||
| Migration20201106StoredMarkup
|
| Migration20201106StoredMarkup
|
||||||
| Migration20201119RoomTypes
|
| Migration20201119RoomTypes
|
||||||
|
| Migration20210115ExamPartsFrom
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
@ -959,6 +960,14 @@ customMigrations = mapF $ \case
|
|||||||
migrateCourse _ = return ()
|
migrateCourse _ = return ()
|
||||||
in runConduit $ getCourses .| C.mapM_ migrateCourse
|
in runConduit $ getCourses .| C.mapM_ migrateCourse
|
||||||
|
|
||||||
|
Migration20210115ExamPartsFrom -> do
|
||||||
|
whenM (tableExists "exam") $ do
|
||||||
|
[executeQQ|ALTER TABLE "exam" ADD COLUMN "parts_from" timestamp with time zone|]
|
||||||
|
let getExam = [queryQQ|SELECT "id", "finished" FROM "exam"|]
|
||||||
|
migrateExam [ fromPersistValue -> Right (eId :: ExamId), fromPersistValue -> Right (finished :: Maybe UTCTime) ] = [executeQQ|UPDATE "exam" SET "parts_from" = #{finished} WHERE "id" = #{eId}|]
|
||||||
|
migrateExam _ = return ()
|
||||||
|
in runConduit $ getExam .| C.mapM_ migrateExam
|
||||||
|
|
||||||
|
|
||||||
tableExists :: MonadIO m => Text -> ReaderT SqlBackend m Bool
|
tableExists :: MonadIO m => Text -> ReaderT SqlBackend m Bool
|
||||||
tableExists table = do
|
tableExists table = do
|
||||||
|
|||||||
@ -28,7 +28,7 @@ data SheetType
|
|||||||
| NotGraded
|
| NotGraded
|
||||||
deriving (Show, Read, Eq)
|
deriving (Show, Read, Eq)
|
||||||
|
|
||||||
sheetType :: SheetType -> Current.SheetType
|
sheetType :: SheetType -> Current.SheetType a
|
||||||
sheetType Bonus {..} = Current.Bonus Current.Points {..}
|
sheetType Bonus {..} = Current.Bonus Current.Points {..}
|
||||||
sheetType Normal {..} = Current.Normal Current.Points {..}
|
sheetType Normal {..} = Current.Normal Current.Points {..}
|
||||||
sheetType Pass {..} = Current.Normal Current.PassPoints {..}
|
sheetType Pass {..} = Current.Normal Current.PassPoints {..}
|
||||||
|
|||||||
@ -6,6 +6,9 @@ import CryptoID
|
|||||||
-- import Data.Text (Text)
|
-- import Data.Text (Text)
|
||||||
import Data.Text.Encoding.Error (UnicodeException(..))
|
import Data.Text.Encoding.Error (UnicodeException(..))
|
||||||
|
|
||||||
|
import Data.Aeson.TH
|
||||||
|
import Utils.PathPiece
|
||||||
|
|
||||||
|
|
||||||
data Rating = Rating
|
data Rating = Rating
|
||||||
{ ratingCourseTerm :: TermIdentifier
|
{ ratingCourseTerm :: TermIdentifier
|
||||||
@ -13,11 +16,17 @@ data Rating = Rating
|
|||||||
, ratingCourseName :: CourseName
|
, ratingCourseName :: CourseName
|
||||||
, ratingSheetName :: SheetName
|
, ratingSheetName :: SheetName
|
||||||
, ratingCorrectorName :: Maybe Text
|
, ratingCorrectorName :: Maybe Text
|
||||||
, ratingSheetType :: SheetType
|
, ratingSheetType :: SheetType RatingExamPartReference
|
||||||
, ratingValues :: Rating'
|
, ratingValues :: Rating'
|
||||||
} deriving (Read, Show, Eq, Generic, Typeable)
|
} deriving (Read, Show, Eq, Generic, Typeable)
|
||||||
deriving anyclass (NFData)
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
|
data RatingExamPartReference = RatingExamPartReference
|
||||||
|
{ ratingExamName :: ExamName
|
||||||
|
, ratingExamPartNumber :: ExamPartNumber
|
||||||
|
} deriving (Read, Show, Eq, Ord, Generic, Typeable)
|
||||||
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
data Rating' = Rating'
|
data Rating' = Rating'
|
||||||
{ ratingPoints :: Maybe Points
|
{ ratingPoints :: Maybe Points
|
||||||
, ratingComment :: Maybe Text
|
, ratingComment :: Maybe Text
|
||||||
@ -26,6 +35,11 @@ data Rating' = Rating'
|
|||||||
} deriving (Read, Show, Eq, Generic, Typeable)
|
} deriving (Read, Show, Eq, Generic, Typeable)
|
||||||
deriving anyclass (NFData)
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
|
deriveJSON defaultOptions
|
||||||
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
|
} ''RatingExamPartReference
|
||||||
|
|
||||||
|
|
||||||
data RatingValidityException
|
data RatingValidityException
|
||||||
= RatingNegative -- ^ Rating points must be non-negative
|
= RatingNegative -- ^ Rating points must be non-negative
|
||||||
| RatingExceedsMax -- ^ Rating point must not exceed maximum points
|
| RatingExceedsMax -- ^ Rating point must not exceed maximum points
|
||||||
|
|||||||
@ -387,6 +387,7 @@ hasExamGradingGrades _ = True
|
|||||||
|
|
||||||
newtype ExamPartNumber = ExamPartNumber { examPartNumberFragments :: [Either (CI Text) Natural] }
|
newtype ExamPartNumber = ExamPartNumber { examPartNumberFragments :: [Either (CI Text) Natural] }
|
||||||
deriving (Eq, Ord, Generic, Typeable)
|
deriving (Eq, Ord, Generic, Typeable)
|
||||||
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
_ExamPartNumber :: Iso' ExamPartNumber (CI Text)
|
_ExamPartNumber :: Iso' ExamPartNumber (CI Text)
|
||||||
_ExamPartNumber = iso pToText pFromText
|
_ExamPartNumber = iso pToText pFromText
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Module: Model.Types.Sheet
|
Module: Model.Types.Sheet
|
||||||
Description: Types for modeling sheets
|
Description: Types for modeling sheets
|
||||||
@ -15,6 +17,7 @@ import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
|||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.MultiSet as MultiSet
|
||||||
|
|
||||||
import Text.Blaze (Markup)
|
import Text.Blaze (Markup)
|
||||||
|
|
||||||
@ -26,6 +29,7 @@ import qualified Data.Aeson as Aeson
|
|||||||
import qualified Data.Aeson.Types as Aeson
|
import qualified Data.Aeson.Types as Aeson
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data SheetGrading
|
data SheetGrading
|
||||||
= Points { maxPoints :: Points }
|
= Points { maxPoints :: Points }
|
||||||
| PassPoints { maxPoints, passingPoints :: Points }
|
| PassPoints { maxPoints, passingPoints :: Points }
|
||||||
@ -78,7 +82,7 @@ data SheetGradeSummary = SheetGradeSummary
|
|||||||
, achievedPasses :: Count -- Achieved passes (within marked sheets)
|
, achievedPasses :: Count -- Achieved passes (within marked sheets)
|
||||||
, achievedPoints :: Sum Points -- Achieved points (within marked sheets)
|
, achievedPoints :: Sum Points -- Achieved points (within marked sheets)
|
||||||
, achievedPassPoints :: Sum Points -- Achieved points within marked sheets where passing is by points
|
, achievedPassPoints :: Sum Points -- Achieved points within marked sheets where passing is by points
|
||||||
} deriving (Generic, Read, Show, Eq)
|
} deriving (Generic, Read, Show, Eq, Ord)
|
||||||
|
|
||||||
instance Monoid SheetGradeSummary where
|
instance Monoid SheetGradeSummary where
|
||||||
mempty = memptydefault
|
mempty = memptydefault
|
||||||
@ -113,12 +117,17 @@ sheetGradeSum gr (Just p) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data SheetType
|
data SheetType exampartid
|
||||||
= NotGraded
|
= NotGraded
|
||||||
| Normal { grading :: SheetGrading }
|
| Normal { grading :: SheetGrading }
|
||||||
| Bonus { grading :: SheetGrading }
|
| Bonus { grading :: SheetGrading }
|
||||||
| Informational { grading :: SheetGrading }
|
| Informational { grading :: SheetGrading }
|
||||||
deriving (Eq, Ord, Read, Show, Generic)
|
| ExamPartPoints
|
||||||
|
{ examPart :: exampartid
|
||||||
|
, weight :: Rational
|
||||||
|
, grading :: SheetGrading
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable, Generic)
|
||||||
deriving anyclass (NFData)
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
@ -131,27 +140,29 @@ derivePersistFieldJSON ''SheetType
|
|||||||
makeLenses_ ''SheetType
|
makeLenses_ ''SheetType
|
||||||
makePrisms ''SheetType
|
makePrisms ''SheetType
|
||||||
|
|
||||||
data SheetTypeSummary = SheetTypeSummary
|
data SheetTypeSummary exampartid = SheetTypeSummary
|
||||||
{ normalSummary
|
{ normalSummary
|
||||||
, bonusSummary
|
, bonusSummary
|
||||||
, informationalSummary :: SheetGradeSummary
|
, informationalSummary :: SheetGradeSummary
|
||||||
|
, examSummary :: MergeMap exampartid (MultiSet (Rational, SheetGradeSummary))
|
||||||
, numNotGraded :: Count
|
, numNotGraded :: Count
|
||||||
} deriving (Generic, Read, Show, Eq)
|
} deriving (Generic, Show, Eq)
|
||||||
|
|
||||||
instance Monoid SheetTypeSummary where
|
instance Ord epid => Monoid (SheetTypeSummary epid) where
|
||||||
mempty = memptydefault
|
mempty = memptydefault
|
||||||
mappend = mappenddefault
|
mappend = mappenddefault
|
||||||
|
|
||||||
instance Semigroup SheetTypeSummary where
|
instance Ord epid => Semigroup (SheetTypeSummary epid) where
|
||||||
(<>) = mappend -- TODO: remove for GHC > 8.4.x
|
(<>) = mappend -- TODO: remove for GHC > 8.4.x
|
||||||
|
|
||||||
makeLenses_ ''SheetTypeSummary
|
makeLenses_ ''SheetTypeSummary
|
||||||
|
|
||||||
sheetTypeSum :: SheetType -> Maybe Points -> SheetTypeSummary
|
sheetTypeSum :: forall epid. Ord epid => SheetType epid -> Maybe Points -> SheetTypeSummary epid
|
||||||
sheetTypeSum Bonus{..} mps = mempty { bonusSummary = sheetGradeSum grading mps }
|
sheetTypeSum Bonus{..} mps = mempty { bonusSummary = sheetGradeSum grading mps }
|
||||||
sheetTypeSum Normal{..} mps = mempty { normalSummary = sheetGradeSum grading mps }
|
sheetTypeSum Normal{..} mps = mempty { normalSummary = sheetGradeSum grading mps }
|
||||||
sheetTypeSum Informational{..} mps = mempty { informationalSummary = sheetGradeSum grading mps }
|
sheetTypeSum Informational{..} mps = mempty { informationalSummary = sheetGradeSum grading mps }
|
||||||
sheetTypeSum NotGraded _ = mempty { numNotGraded = Sum 1 }
|
sheetTypeSum NotGraded _ = mempty { numNotGraded = Sum 1 }
|
||||||
|
sheetTypeSum ExamPartPoints{..} mps = (mempty @(SheetTypeSummary epid)) { examSummary = MergeMap . Map.singleton examPart $ MultiSet.singleton (weight, sheetGradeSum grading mps) }
|
||||||
|
|
||||||
data SheetGroup
|
data SheetGroup
|
||||||
= Arbitrary { maxParticipants :: Natural }
|
= Arbitrary { maxParticipants :: Natural }
|
||||||
@ -360,7 +371,7 @@ showCompactCorrectorLoad Load{..} CorrectorNormal | byProportion == 0 = tutoria
|
|||||||
Just True -> "(T)"
|
Just True -> "(T)"
|
||||||
Just False -> "T"
|
Just False -> "T"
|
||||||
|
|
||||||
instance Csv.ToField (SheetType, Maybe Points) where
|
instance Csv.ToField (SheetType epid, Maybe Points) where
|
||||||
toField (_, Nothing) = mempty
|
toField (_, Nothing) = mempty
|
||||||
toField (sType, Just res)
|
toField (sType, Just res)
|
||||||
| Just passed <- flip gradingPassed res =<< preview _grading sType
|
| Just passed <- flip gradingPassed res =<< preview _grading sType
|
||||||
|
|||||||
30
src/Utils.hs
30
src/Utils.hs
@ -10,6 +10,8 @@ import qualified Data.Foldable as Fold
|
|||||||
import Data.Foldable as Utils (foldlM, foldrM)
|
import Data.Foldable as Utils (foldlM, foldrM)
|
||||||
import Data.Monoid (First, Sum(..))
|
import Data.Monoid (First, Sum(..))
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
|
import Control.Arrow (Kleisli(..))
|
||||||
|
import Control.Arrow.Instances ()
|
||||||
|
|
||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
@ -36,7 +38,7 @@ import Utils.NTop as Utils
|
|||||||
import Utils.HttpConditional as Utils
|
import Utils.HttpConditional as Utils
|
||||||
import Utils.Persist as Utils
|
import Utils.Persist as Utils
|
||||||
|
|
||||||
import Text.Blaze (Markup, ToMarkup)
|
import Text.Blaze (Markup, ToMarkup(..))
|
||||||
|
|
||||||
import Data.Char (isDigit, isSpace, isAscii)
|
import Data.Char (isDigit, isSpace, isAscii)
|
||||||
import Data.Text (dropWhileEnd, takeWhileEnd, justifyRight)
|
import Data.Text (dropWhileEnd, takeWhileEnd, justifyRight)
|
||||||
@ -132,6 +134,10 @@ import Yesod.Core.Types.Instances.Catch ()
|
|||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
import Control.Monad.Reader.Class (MonadReader(local))
|
import Control.Monad.Reader.Class (MonadReader(local))
|
||||||
|
|
||||||
|
import Text.Hamlet (Translate)
|
||||||
|
|
||||||
|
import Data.Ratio ((%))
|
||||||
|
|
||||||
{-# ANN module ("HLint: ignore Use asum" :: String) #-}
|
{-# ANN module ("HLint: ignore Use asum" :: String) #-}
|
||||||
|
|
||||||
|
|
||||||
@ -148,6 +154,9 @@ getMsgRenderer = do
|
|||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
return $ MsgRenderer (mr . SomeMessage :: forall msg. RenderMessage site msg => msg -> Text)
|
return $ MsgRenderer (mr . SomeMessage :: forall msg. RenderMessage site msg => msg -> Text)
|
||||||
|
|
||||||
|
getTranslate :: forall m site msg. (MonadHandler m, HandlerSite m ~ site, RenderMessage site msg) => m (Translate msg)
|
||||||
|
getTranslate = (toMarkup .) <$> getMessageRender
|
||||||
|
|
||||||
|
|
||||||
guardAuthResult :: MonadHandler m => AuthResult -> m ()
|
guardAuthResult :: MonadHandler m => AuthResult -> m ()
|
||||||
guardAuthResult AuthenticationRequired = notAuthenticated
|
guardAuthResult AuthenticationRequired = notAuthenticated
|
||||||
@ -346,6 +355,14 @@ rationalToFixed2 = rationalToFixed
|
|||||||
realToFixed :: forall a n. (Real n, HasResolution a) => n -> Fixed a
|
realToFixed :: forall a n. (Real n, HasResolution a) => n -> Fixed a
|
||||||
realToFixed = rationalToFixed . toRational
|
realToFixed = rationalToFixed . toRational
|
||||||
|
|
||||||
|
roundToPoints :: forall a. HasResolution a => Rational -> Fixed a
|
||||||
|
roundToPoints ((* toRational (resolution $ Proxy @a)) -> raw) = MkFixed $
|
||||||
|
let (whole, frac) = properFraction raw
|
||||||
|
in if | abs frac < abs (1 % 2)
|
||||||
|
-> whole
|
||||||
|
| otherwise
|
||||||
|
-> succ whole
|
||||||
|
|
||||||
----------
|
----------
|
||||||
-- Bool --
|
-- Bool --
|
||||||
----------
|
----------
|
||||||
@ -427,6 +444,9 @@ guardMonoid :: Monoid m => Bool -> m -> m
|
|||||||
guardMonoid False _ = mempty
|
guardMonoid False _ = mempty
|
||||||
guardMonoid True x = x
|
guardMonoid True x = x
|
||||||
|
|
||||||
|
assertMonoid :: Monoid m => (m -> Bool) -> m -> m
|
||||||
|
assertMonoid f x = guardMonoid (f x) x
|
||||||
|
|
||||||
------------
|
------------
|
||||||
-- Tuples --
|
-- Tuples --
|
||||||
------------
|
------------
|
||||||
@ -442,7 +462,8 @@ trd3 (_,_,z) = z
|
|||||||
-- $(projN n m) :: (t1,..,tn) -> tm (for m<=n)
|
-- $(projN n m) :: (t1,..,tn) -> tm (for m<=n)
|
||||||
-- snd3 = $(projNI 3 2)
|
-- snd3 = $(projNI 3 2)
|
||||||
|
|
||||||
|
mTuple :: Applicative f => f a -> f b -> f (a, b)
|
||||||
|
mTuple = liftA2 (,)
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
-- Lists --
|
-- Lists --
|
||||||
@ -574,6 +595,9 @@ partitionKeysEither = over _2 (Map.mapKeysMonotonic . view $ singular _Right) .
|
|||||||
mapFromSetM :: Applicative m => (k -> m v) -> Set k -> m (Map k v)
|
mapFromSetM :: Applicative m => (k -> m v) -> Set k -> m (Map k v)
|
||||||
mapFromSetM = (sequenceA .) . Map.fromSet
|
mapFromSetM = (sequenceA .) . Map.fromSet
|
||||||
|
|
||||||
|
mapFilterM :: (Monad m, Ord k) => (v -> m Bool) -> Map k v -> m (Map k v)
|
||||||
|
mapFilterM f m = ($ m) . runKleisli $ foldMap (Kleisli . Map.alterF (runMaybeT . assertMM (lift . f) . hoistMaybe)) (Map.keys m)
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
-- Functions --
|
-- Functions --
|
||||||
---------------
|
---------------
|
||||||
@ -893,6 +917,7 @@ diffTimeout timeoutLength timeoutRes act = fromMaybe timeoutRes <$> timeout time
|
|||||||
= let (MkFixed micro :: Micro) = realToFrac timeoutLength
|
= let (MkFixed micro :: Micro) = realToFrac timeoutLength
|
||||||
in fromInteger micro
|
in fromInteger micro
|
||||||
|
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
-- Foldable --
|
-- Foldable --
|
||||||
--------------
|
--------------
|
||||||
@ -1468,7 +1493,6 @@ instance (Ord k, FromJSON v, FromJSONKey k, Semigroup v) => FromJSON (MergeMap k
|
|||||||
|
|
||||||
parseJSONElemAtIndex :: (Value -> Aeson.Parser a) -> Int -> Vector Value -> Aeson.Parser a
|
parseJSONElemAtIndex :: (Value -> Aeson.Parser a) -> Int -> Vector Value -> Aeson.Parser a
|
||||||
parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) Aeson.<?> Aeson.Index idx
|
parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) Aeson.<?> Aeson.Index idx
|
||||||
|
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
-- FilePath --
|
-- FilePath --
|
||||||
|
|||||||
@ -161,6 +161,8 @@ makeLenses_ ''SubmissionGroup
|
|||||||
|
|
||||||
makeLenses_ ''SheetGrading
|
makeLenses_ ''SheetGrading
|
||||||
|
|
||||||
|
makeLenses_ ''Sheet
|
||||||
|
|
||||||
makePrisms ''SheetGroup
|
makePrisms ''SheetGroup
|
||||||
|
|
||||||
makePrisms ''AuthResult
|
makePrisms ''AuthResult
|
||||||
|
|||||||
@ -6,7 +6,6 @@ module Utils.Workflow.Lint
|
|||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import Data.MultiSet (MultiSet)
|
|
||||||
import qualified Data.MultiSet as MultiSet
|
import qualified Data.MultiSet as MultiSet
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
|
|||||||
@ -17,11 +17,11 @@
|
|||||||
$case grading
|
$case grading
|
||||||
$of Points{..}
|
$of Points{..}
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
<th .table__th>_{sheetType}
|
<th .table__th>^{sheetTypeDesc tr}
|
||||||
<td .table__td>_{MsgAchievedOf points maxPoints}
|
<td .table__td>_{MsgAchievedOf points maxPoints}
|
||||||
$of PassPoints{..}
|
$of PassPoints{..}
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
<th .table__th>_{sheetType}
|
<th .table__th>^{sheetTypeDesc tr}
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}
|
_{MsgPassed}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<td .table__td>_{MsgPassAchievedOf points passingPoints maxPoints}
|
<td .table__td>_{MsgPassAchievedOf points passingPoints maxPoints}
|
||||||
$of PassBinary
|
$of PassBinary
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
<th .table__th>_{sheetType}
|
<th .table__th>^{sheetTypeDesc tr}
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}
|
_{MsgPassed}
|
||||||
|
|||||||
@ -260,11 +260,11 @@ $if not (null occurrences)
|
|||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
|
||||||
$if gradingShown && not (null examParts)
|
$if (gradingShown || partsShown) && not (null examParts)
|
||||||
<section>
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
_{MsgExamParts}
|
_{MsgExamParts}
|
||||||
$if gradingShown && not gradingVisible
|
$if (gradingShown && not gradingVisible) || (partsShown && not partsVisible)
|
||||||
\ ^{isVisible False}
|
\ ^{isVisible False}
|
||||||
<table .table .table--striped .table--hover >
|
<table .table .table--striped .table--hover >
|
||||||
<thead>
|
<thead>
|
||||||
@ -273,13 +273,15 @@ $if gradingShown && not (null examParts)
|
|||||||
<th .table__th>
|
<th .table__th>
|
||||||
_{MsgExamPartNumber} ^{isVisible False}
|
_{MsgExamPartNumber} ^{isVisible False}
|
||||||
<th .table__th>_{MsgExamPartName}
|
<th .table__th>_{MsgExamPartName}
|
||||||
|
$if showPartSheets
|
||||||
|
<th .table__th>_{MsgExamPartSheets}
|
||||||
$if showMaxPoints
|
$if showMaxPoints
|
||||||
<th .table__th>_{MsgExamPartMaxPoints}
|
<th .table__th>_{MsgExamPartMaxPoints}
|
||||||
$if showAchievedPoints
|
$if showAchievedPoints
|
||||||
<th .table__th>_{MsgExamPartResultPoints}
|
<th .table__th>_{MsgExamPartResultPoints}
|
||||||
<tbody>
|
<tbody>
|
||||||
$forall Entity partId ExamPart{examPartNumber, examPartName, examPartWeight, examPartMaxPoints} <- examParts
|
$forall (Entity partId ExamPart{examPartNumber, examPartName, examPartWeight, examPartMaxPoints}, cID, partSheets) <- examParts
|
||||||
<tr .table__row>
|
<tr .table__row ##{toPathPiece cID}>
|
||||||
$if partNumbersShown
|
$if partNumbersShown
|
||||||
<td .table__td>#{examPartNumber}
|
<td .table__td>#{examPartNumber}
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
@ -287,6 +289,14 @@ $if gradingShown && not (null examParts)
|
|||||||
#{pName}
|
#{pName}
|
||||||
$nothing
|
$nothing
|
||||||
_{MsgExamPartNumbered examPartNumber}
|
_{MsgExamPartNumbered examPartNumber}
|
||||||
|
$if showPartSheets
|
||||||
|
<td .table__td>
|
||||||
|
$if not (null partSheets)
|
||||||
|
<ul .list--inline .list--comma-separated>
|
||||||
|
$forall Entity _ Sheet{sheetName} <- partSheets
|
||||||
|
<li>
|
||||||
|
<a href=@{CSheetR tid ssh csh sheetName SShowR}>
|
||||||
|
#{sheetName}
|
||||||
$if showMaxPoints
|
$if showMaxPoints
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
$maybe mPoints <- examPartMaxPoints
|
$maybe mPoints <- examPartMaxPoints
|
||||||
|
|||||||
2
templates/i18n/changelog/exam-sheets.de-de-formal.hamlet
Normal file
2
templates/i18n/changelog/exam-sheets.de-de-formal.hamlet
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
$newline never
|
||||||
|
Übungsblätter können nun „als Prüfungsaufgabe“ bewertet werden.
|
||||||
2
templates/i18n/changelog/exam-sheets.en-eu.hamlet
Normal file
2
templates/i18n/changelog/exam-sheets.en-eu.hamlet
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
$newline never
|
||||||
|
Exercise sheets can now be rated “as an exam part”.
|
||||||
@ -16,52 +16,52 @@ $newline never
|
|||||||
}
|
}
|
||||||
<body>
|
<body>
|
||||||
<h1>
|
<h1>
|
||||||
_{MsgMailSubmissionRatedIntro (CI.original courseName) termDesc}
|
_{SomeMessage (MsgMailSubmissionRatedIntro (CI.original courseName) termDesc)}
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
_{MsgSubmission}
|
_{SomeMessage MsgSubmission}
|
||||||
<dd>
|
<dd>
|
||||||
<a href=@{CSubmissionR tid ssh csh shn csid SubShowR}>
|
<a href=@{CSubmissionR tid ssh csh shn csid SubShowR}>
|
||||||
#{csid}
|
#{csid}
|
||||||
$maybe User{userDisplayName, userSurname, userEmail} <- corrector
|
$maybe User{userDisplayName, userSurname, userEmail} <- corrector
|
||||||
<dt>
|
<dt>
|
||||||
_{MsgRatingBy}
|
_{SomeMessage MsgRatingBy}
|
||||||
<dd>
|
<dd>
|
||||||
#{nameEmailHtml userEmail userDisplayName userSurname}
|
#{nameEmailHtml userEmail userDisplayName userSurname}
|
||||||
$maybe time <- submissionRatingTime'
|
$maybe time <- submissionRatingTime'
|
||||||
<dt>
|
<dt>
|
||||||
_{MsgRatingTime}
|
_{SomeMessage MsgRatingTime}
|
||||||
<dd>
|
<dd>
|
||||||
#{time}
|
#{time}
|
||||||
<dt> #{sheetTypeDesc}
|
<dt> ^{sheetTypeDesc}
|
||||||
$maybe points <- submissionRatingPoints
|
$maybe points <- submissionRatingPoints
|
||||||
$maybe grading <- preview _grading sheetType
|
$maybe grading <- preview _grading sheetType
|
||||||
$case grading
|
$case grading
|
||||||
$of Points{..}
|
$of Points{..}
|
||||||
<dd>
|
<dd>
|
||||||
_{MsgAchievedOf points maxPoints}
|
_{SomeMessage (MsgAchievedOf points maxPoints)}
|
||||||
$of PassPoints{..}
|
$of PassPoints{..}
|
||||||
<dd>
|
<dd>
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}
|
_{SomeMessage MsgPassed}
|
||||||
$else
|
$else
|
||||||
_{MsgNotPassed}
|
_{SomeMessage MsgNotPassed}
|
||||||
<dt>
|
<dt>
|
||||||
_{MsgAchievedPassPoints}
|
_{SomeMessage MsgAchievedPassPoints}
|
||||||
<dd>
|
<dd>
|
||||||
_{MsgPassAchievedOf points passingPoints maxPoints}
|
_{SomeMessage (MsgPassAchievedOf points passingPoints maxPoints)}
|
||||||
$of PassBinary
|
$of PassBinary
|
||||||
<dd>
|
<dd>
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}
|
_{SomeMessage MsgPassed}
|
||||||
$else
|
$else
|
||||||
_{MsgNotPassed}
|
_{SomeMessage MsgNotPassed}
|
||||||
$of PassAlways
|
$of PassAlways
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
$maybe comment <- submissionRatingComment
|
$maybe comment <- submissionRatingComment
|
||||||
<dt>
|
<dt>
|
||||||
_{MsgRatingComment}
|
_{SomeMessage MsgRatingComment}
|
||||||
<dd .comment>
|
<dd .comment>
|
||||||
#{comment}
|
#{comment}
|
||||||
^{editNotifications}
|
^{ihamletSomeMessage editNotifications}
|
||||||
|
|||||||
@ -66,8 +66,10 @@ $maybe descr <- sheetDescription sheet
|
|||||||
$nothing
|
$nothing
|
||||||
^{generateForm}
|
^{generateForm}
|
||||||
$of _
|
$of _
|
||||||
<dt .deflist__dt>_{MsgSheetType}
|
<dt .deflist__dt>
|
||||||
<dd .deflist__dd>_{sheetType sheet}
|
_{MsgSheetType}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
^{sTypeDesc tr}
|
||||||
|
|
||||||
$maybe marktxt <- markingText
|
$maybe marktxt <- markingText
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@ -8,20 +8,17 @@ $if submissionRatingDone sub
|
|||||||
$case grading
|
$case grading
|
||||||
$of Points{..}
|
$of Points{..}
|
||||||
_{MsgAchievedOf points maxPoints}
|
_{MsgAchievedOf points maxPoints}
|
||||||
, #
|
|
||||||
$of PassPoints{maxPoints}
|
$of PassPoints{maxPoints}
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}, _{MsgAchievedOf points maxPoints}
|
_{MsgPassed}, _{MsgAchievedOf points maxPoints}
|
||||||
$else
|
$else
|
||||||
_{MsgNotPassed}, _{MsgAchievedOf points maxPoints}
|
_{MsgNotPassed}, _{MsgAchievedOf points maxPoints}
|
||||||
, #
|
|
||||||
$of PassBinary
|
$of PassBinary
|
||||||
$if fromMaybe False (gradingPassed grading points)
|
$if fromMaybe False (gradingPassed grading points)
|
||||||
_{MsgPassed}
|
_{MsgPassed}
|
||||||
$else
|
$else
|
||||||
_{MsgNotPassed}
|
_{MsgNotPassed}
|
||||||
, #
|
|
||||||
$of PassAlways
|
$of PassAlways
|
||||||
_{SheetTypeHeader sheetType}
|
#{hasTickmark True}
|
||||||
$nothing
|
$nothing
|
||||||
#{hasTickmark True}
|
#{hasTickmark True}
|
||||||
|
|||||||
13
templates/widgets/sheetType.hamlet
Normal file
13
templates/widgets/sheetType.hamlet
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
$newline never
|
||||||
|
$maybe g <- preview _grading sType
|
||||||
|
_{SomeMessage g}, #
|
||||||
|
_{SomeMessage (classifySheetType sType)}
|
||||||
|
$maybe ((eName, epName, epNumber, partVisible, gradingVisible, eRoute), weight, _) <- preview _ExamPartPoints sType
|
||||||
|
\ (
|
||||||
|
<a href=@{eRoute}>
|
||||||
|
#{eName}
|
||||||
|
$if partVisible
|
||||||
|
\ - _{fromMaybe (SomeMessage (MsgExamPartNumbered epNumber)) (fmap SomeMessage epName)}
|
||||||
|
$if gradingVisible
|
||||||
|
, _{SomeMessage MsgSheetTypeExamPartPointsWeight} _{SomeMessage (rationalToFixed3 weight)}
|
||||||
|
)
|
||||||
@ -3,7 +3,7 @@ module Database.Fill
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import "uniworx" Import hiding (Option(..), currentYear)
|
import "uniworx" Import hiding (Option(..), currentYear)
|
||||||
import Handler.Utils.Form (SheetGrading'(..), SheetType'(..), SheetGroup'(..))
|
import Handler.Utils.Form (SheetGrading'(..), SheetGroup'(..))
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
@ -708,6 +708,7 @@ fillDb = do
|
|||||||
, examStart = Just $ termTime True Summer 3 True Tuesday (toTimeOfDay 10 0 0)
|
, examStart = Just $ termTime True Summer 3 True Tuesday (toTimeOfDay 10 0 0)
|
||||||
, examEnd = Just $ termTime True Summer 3 True Tuesday (toTimeOfDay 12 0 0)
|
, examEnd = Just $ termTime True Summer 3 True Tuesday (toTimeOfDay 12 0 0)
|
||||||
, examFinished = Just $ termTime True Summer 3 True Wednesday (toTimeOfDay 22 0 0)
|
, examFinished = Just $ termTime True Summer 3 True Wednesday (toTimeOfDay 22 0 0)
|
||||||
|
, examPartsFrom = Just $ termTime True Summer (-4) True Monday toMidnight
|
||||||
, examClosed = Nothing
|
, examClosed = Nothing
|
||||||
, examPublicStatistics = True
|
, examPublicStatistics = True
|
||||||
, examGradingMode = ExamGradingGrades
|
, examGradingMode = ExamGradingGrades
|
||||||
@ -888,11 +889,7 @@ fillDb = do
|
|||||||
| otherwise
|
| otherwise
|
||||||
= mr sheetType'
|
= mr sheetType'
|
||||||
where
|
where
|
||||||
sheetType' = case sheetType of
|
sheetType' = classifySheetType sheetType
|
||||||
NotGraded -> NotGraded'
|
|
||||||
Normal{} -> Normal'
|
|
||||||
Bonus{} -> Bonus'
|
|
||||||
Informational{} -> Informational'
|
|
||||||
|
|
||||||
prog = 14 * (shNr % genericLength sheetCombinations)
|
prog = 14 * (shNr % genericLength sheetCombinations)
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import TestImport
|
|||||||
import ModelSpec ()
|
import ModelSpec ()
|
||||||
import Model.Rating
|
import Model.Rating
|
||||||
|
|
||||||
|
instance Arbitrary RatingExamPartReference where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary Rating' where
|
instance Arbitrary Rating' where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|||||||
@ -91,7 +91,7 @@ instance Arbitrary SheetGrading where
|
|||||||
]
|
]
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary SheetType where
|
instance Arbitrary epId => Arbitrary (SheetType epId) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
@ -103,9 +103,8 @@ instance Arbitrary SheetGroup where
|
|||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary SheetTypeSummary where
|
instance (Arbitrary epId, Ord epId) => Arbitrary (SheetTypeSummary epId) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = foldMap (uncurry sheetTypeSum) <$> listOf arbitrary
|
||||||
shrink = genericShrink
|
|
||||||
|
|
||||||
instance Arbitrary SheetFileType where
|
instance Arbitrary SheetFileType where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
@ -342,10 +341,10 @@ spec = do
|
|||||||
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
lawsCheckHspec (Proxy @SheetGradeSummary)
|
lawsCheckHspec (Proxy @SheetGradeSummary)
|
||||||
[ eqLaws, showReadLaws, commutativeMonoidLaws, commutativeSemigroupLaws ]
|
[ eqLaws, showReadLaws, commutativeMonoidLaws, commutativeSemigroupLaws ]
|
||||||
lawsCheckHspec (Proxy @SheetType)
|
lawsCheckHspec (Proxy @(SheetType SqlBackendKey))
|
||||||
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
lawsCheckHspec (Proxy @SheetTypeSummary)
|
lawsCheckHspec (Proxy @(SheetTypeSummary SqlBackendKey))
|
||||||
[ eqLaws, showReadLaws, commutativeMonoidLaws ]
|
[ eqLaws, showLaws, commutativeMonoidLaws ]
|
||||||
lawsCheckHspec (Proxy @SheetGroup)
|
lawsCheckHspec (Proxy @SheetGroup)
|
||||||
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
lawsCheckHspec (Proxy @SheetFileType)
|
lawsCheckHspec (Proxy @SheetFileType)
|
||||||
|
|||||||
Reference in New Issue
Block a user