feat(external-exams): create new exams

This commit is contained in:
Gregor Kleen 2019-12-02 11:50:13 +01:00 committed by Gregor Kleen
parent fa3521d6db
commit 94bb3911cb
12 changed files with 275 additions and 12 deletions

View File

@ -401,9 +401,9 @@ UnauthorizedToken404: Authorisierungs-Tokens können nicht auf Fehlerseiten ausg
UnauthorizedSiteAdmin: Sie sind kein System-weiter Administrator.
UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen.
UnauthorizedAdminEscalation: Sie sind nicht Administrator für alle Institute, für die dieser Nutzer Administrator oder Veranstalter ist.
UnauthorizedExamOffice: Sie sind nicht Teil eines Prüfungsamts.
UnauthorizedExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer, für die Sie Teil eines assoziierten Prüfungsamts sind.
UnauthorizedExternalExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer, für die Sie Teil eines assoziierten Prüfungsamts sind.
UnauthorizedExamOffice: Sie sind nicht mit Prüfungsverwaltung beauftragt.
UnauthorizedExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer, für die Sie mit der Prüfungsverwaltung beauftragt sind.
UnauthorizedExternalExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer, für die Sie mit der Prüfungsverwaltung beauftragt sind.
UnauthorizedSchoolLecturer: Sie sind nicht als Veranstalter für dieses Institut eingetragen.
UnauthorizedLecturer: Sie sind nicht als Veranstalter für diese Veranstaltung eingetragen.
UnauthorizedAllocationLecturer: Sie sind nicht als Veranstalter für eine Veranstaltung dieser Zentralanmeldung eingetragen.
@ -1000,7 +1000,7 @@ NotificationTriggerKindCorrector: Für Korrektoren
NotificationTriggerKindLecturer: Für Dozenten
NotificationTriggerKindCourseLecturer: Für Kursverwalter
NotificationTriggerKindAdmin: Für Administratoren
NotificationTriggerKindExamOffice: Für das Prüfungsamt
NotificationTriggerKindExamOffice: Für Prüfungsverwalter
NotificationTriggerKindEvaluation: Für Vorlesungsumfragen
NotificationTriggerKindAllocationStaff: Für Zentralanmeldungen (Dozenten)
NotificationTriggerKindAllocationParticipant: Für Zentralanmeldungen
@ -1127,7 +1127,7 @@ MenuCourseMembers: Kursteilnehmer
MenuCourseAddMembers: Kursteilnehmer hinzufügen
MenuCourseCommunication: Kursmitteilung (E-Mail)
MenuCourseApplications: Bewerbungen
MenuCourseExamOffice: Prüfungsämter
MenuCourseExamOffice: Prüfungsbeauftragte
MenuTermShow: Semester
MenuSubmissionDelete: Abgabe löschen
MenuUsers: Benutzer
@ -1267,7 +1267,7 @@ AuthPredsActive: Aktive Authorisierungsprädikate
AuthPredsActiveChanged: Authorisierungseinstellungen für aktuelle Sitzung gespeichert
AuthTagFree: Seite ist universell zugänglich
AuthTagAdmin: Nutzer ist Administrator
AuthTagExamOffice: Nutzer ist Teil eines Prüfungsamts
AuthTagExamOffice: Nutzer ist mit Prüfungsverwaltung beauftragt
AuthTagToken: Nutzer präsentiert Authorisierungs-Token
AuthTagNoEscalation: Nutzer-Rechte werden nicht auf fremde Institute ausgeweitet
AuthTagDeprecated: Seite ist nicht überholt
@ -1522,9 +1522,9 @@ ExamFinishedOffice: Noten bekannt gegeben
ExamFinishedParticipant: Bewertung vorrausichtlich abgeschlossen
ExamFinishedTip: Zeitpunkt zu dem Prüfungergebnisse den Teilnehmern gemeldet werden
ExamClosed: Noten gemeldet
ExamClosedTip: Prüfungsämter, die im System Noten einsehen, werden zu diesem Zeitpunkt benachrichtigt und danach bei Änderungen informiert
ExamClosedTip: Prüfungsbeauftraget, die im System Noten einsehen, werden zu diesem Zeitpunkt benachrichtigt und danach bei Änderungen informiert
ExamShowGrades: Klausur ist benotet
ExamShowGradesTip: Sollen genaue Noten angezeigt werden, oder sollen Teilnehmer und Prüfungsämter nur informiert werden, ob die Klausur bestanden wurde?
ExamShowGradesTip: Sollen genaue Noten angezeigt werden, oder sollen Teilnehmer und Prüfungsbeauftragte nur informiert werden, ob die Klausur bestanden wurde?
ExamPublicStatistics: Statistik veröffentlichen
ExamPublicStatisticsTip: Soll die automatisch berechnete statistische Auswertung auch den Teilnehmern angezeigt werden, sobald diese ihre Noten einsehen können?
ExamAutomaticGrading: Automatische Notenberechnung
@ -1938,7 +1938,7 @@ SchoolExists ssh@SchoolId: Institut „#{ssh}“ existiert bereits
SchoolAdmin: Admin
SchoolLecturer: Dozent
SchoolEvaluation: Kursumfragenverwaltung
SchoolExamOffice: Prüfungsamt
SchoolExamOffice: Prüfungsverwaltung
ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden.
@ -2016,10 +2016,10 @@ MailSubjectChangeUserDisplayEmail: Diese E-Mail Adresse in Uni2work veröffentli
MailIntroChangeUserDisplayEmail displayEmail@UserEmail: Der oben genannte Benutzer möchte „#{displayEmail}“ als öffentliche Adresse, assoziiert mit sich selbst, angeben. Wenn Sie diese Aktion nicht selbst ausgelöst haben, ignorieren Sie diese Mitteilung bitte!
MailTitleChangeUserDisplayEmail displayName@Text: #{displayName} möchte diese E-Mail Adresse in Uni2work veröffentlichen
ExamOfficeOptOutsChanged: Zuständige Prüfungsämter erfolgreich angepasst
ExamOfficeOptOutsChanged: Zuständige Prüfungsbeauftragte erfolgreich angepasst
BtnCloseExam: Klausur abschließen
ExamCloseTip: Wenn eine Klausur abgeschlossen wird, werden Prüfungsämter, die im System Noten einsehen, benachrichtigt und danach bei Änderungen informiert.
ExamCloseTip: Wenn eine Klausur abgeschlossen wird, werden Prüfungsbeauftragte, die im System Noten einsehen, benachrichtigt und danach bei Änderungen informiert.
ExamCloseReminder: Bitte schließen Sie die Klausur frühstmöglich, sobald die Prüfungsleistungen sich voraussichtlich nicht mehr ändern werden. Z.B. direkt nach der Klausureinsicht.
ExamDidClose: Klausur erfolgreich abgeschlossen
@ -2220,3 +2220,26 @@ Deficit: Defizit
MetricNoSamples: Keine Messwerte
MetricName: Name
MetricValue: Wert
ExternalExamSemester: Semester
ExternalExamSchool: Institut
ExternalExamCourseName: Veranstaltung
ExternalExamCourseNameTip: Muss nur innerhalb von Semester und Institut eindeutig sein.
ExternalExamCourseNamePlaceholder: Analysis I, Programmierung und Modellierung, ...
ExternalExamExamName: Prüfung
ExternalExamExamNameTip: Muss innerhalb der Veranstaltung eindeutig sein.
ExternalExamExamNamePlaceholder: Klausur, Nachklausur, Projektabnahme, ...
ExternalExamDefaultTime: Voreingestellter Zeitpunkt
ExternalExamDefaultTimePlaceholder: Zeitpunkt
ExternalExamDefaultTimeTip: Der Zeitpunkt zu dem die Prüfung abgelegt wurde, muss pro Teilnehmer festgelegt werden. Der hier angegebene Zeitpunkt wird als Standardwert für Teilnehmer verwendet, bei denen später nicht ein abweichender Zeitpunkt angegeben wird.
ExternalExamShowGrades: Klausur ist benotet
ExternalExamShowGradesTip: Sollen genaue Noten angezeigt werden, oder sollen Teilnehmer und Prüfungsbeauftragte nur informiert werden, ob die Klausur bestanden wurde?
ExternalExamExamOfficeSchools: Zusätzliche Institute
ExternalExamExamOfficeSchoolsTip: Prüfungsbeauftragte von Instituten, die Sie hier angeben, erhalten im System (zusätzlich zum angegebenen primären Institut) volle Einsicht in sämtliche für diese Prüfung hinterlegten Leistungen, unabhängig von den Studiendaten der Teilnehmer.
ExternalExamStaff: Assoziierte Personen
ExternalExamStaffTip: Assoziierte Personen werden den Prüfungsbeauftragten und Teilnehmern angezeigt und dürfen Leistungen für die Prüfung hinterlegen.
ExternalExamStaffAlreadyAdded: Person wurde bereits der Prüfung hinzugefügt
ExternalExamUserMustBeStaff: Sie selbst müssen stets assoziierte Person sein, für die externen Prüfungen, die Sie anlegen
ExternalExamCourseExists: Der angegebene Kurs existiert im System. Prüfungen sollten daher direkt beim Kurs (statt extern) hinterlegt werden.
ExternalExamExists coursen@CourseName examn@ExamName: Prüfung „#{examn}“ für Kurs „#{coursen}“ existiert bereits.
ExternalExamCreated coursen@CourseName examn@ExamName: Prüfung „#{examn}“ für Kurs „#{coursen}“ erfolgreich angelegt.

View File

@ -401,6 +401,7 @@ UnauthorizedSchoolAdmin: You are no administrator for this department.
UnauthorizedAdminEscalation: You aren't an administrator for all departments for which this user is an administrator.
UnauthorizedExamOffice: You are not part of an exam office.
UnauthorizedExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam.
UnauthorizedExternalExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam.
UnauthorizedSchoolLecturer: You are no lecturer for this department.
UnauthorizedLecturer: You are no administrator for this course.
UnauthorizedAllocationLecturer: You are no administrator for any of the courses of this central allocation.
@ -449,6 +450,7 @@ UnauthorizedTutorialRegisterGroup: You are already registered for a tutorial wit
UnauthorizedLDAP: Specified user does not log in with their campus account.
UnauthorizedPWHash: Specified user does not log in with an Uni2work-account.
UnauthorizedExternalExamListNotEmpty: List of external exams is not empty
UnauthorizedExternalExamLecturer: You are not an associated person for this external exam
UnauthorizedPasswordResetToken: This authorisation-token may no longer be used to change passwords
@ -2217,3 +2219,26 @@ Deficit: Deficit
MetricNoSamples: No samples
MetricName: Name
MetricValue: Value
ExternalExamSemester: Semester
ExternalExamSchool: Department
ExternalExamCourseName: Course
ExternalExamCourseNameTip: Needs only be unique among within semester and department.
ExternalExamCourseNamePlaceholder: Analysis I, Programming and Modelling, ...
ExternalExamExamName: Exam title
ExternalExamExamNameTip: Needs only be unique within the course.
ExternalExamExamNamePlaceholder: Exam, Exam resit, Project discussion, ...
ExternalExamDefaultTime: Default time
ExternalExamDefaultTimePlaceholder: Time
ExternalExamDefaultTimeTip: The time of the exam needs to be specified for each participant. The time entered here is used as a default value for participants for whom no different time is later specified.
ExternalExamShowGrades: Exam is graded
ExternalExamShowGradesTip: Should participants and relevant exam offices be show exact grades or only whether the exam was passed or failed?
ExternalExamExamOfficeSchools: Additional departments
ExternalExamExamOfficeSchoolsTip: Exam offices of departments you specify here will also have full access to all results for this exam disregarding the individual participants' features of study.
ExternalExamStaff: Associated persons
ExternalExamStaffTip: The list of ssociated persons is shown to exam offices and participants. Additionally associated persons may upload results for the exam.
ExternalExamStaffAlreadyAdded: Person is already associated with the exam.
ExternalExamUserMustBeStaff: You yourself must always be an associated person for exams you create.
ExternalExamCourseExists: This course already exists with uni2work. Exams for courses that exist within uni2work should be associated with the course directly instead of being created as an external exam.
ExternalExamExists coursen@CourseName examn@ExamName: Exam “#{examn}” already exists for course “#{coursen}”.
ExternalExamCreated coursen@CourseName examn@ExamName: Succesfully created exam “#{examn}” for course “#{coursen}”.

View File

@ -4,6 +4,8 @@ module Handler.ExternalExam.Edit
import Import
import Handler.ExternalExam.Form ()
getEEEditR, postEEEditR :: TermId -> SchoolId -> CourseName -> ExamName -> Handler Html
getEEEditR = postEEEditR

View File

@ -0,0 +1,115 @@
module Handler.ExternalExam.Form
( ExternalExamForm(..)
, externalExamForm
) where
import Import
import Handler.Utils
import Handler.ExternalExam.StaffInvite ()
import qualified Data.Set as Set
import Data.Map ((!))
import qualified Control.Monad.State.Class as State
data ExternalExamForm = ExternalExamForm
{ eefTerm :: TermId
, eefSchool :: SchoolId
, eefCourseName :: CI Text
, eefExamName :: CI Text
, eefDefaultTime :: Maybe UTCTime
, eefShowGrades :: Bool
, eefOfficeSchools :: Set SchoolId
, eefStaff :: Set (Either UserEmail UserId)
}
makeLenses_ ''ExternalExamForm
externalExamForm :: Maybe ExternalExamForm -> Form ExternalExamForm
externalExamForm template = validateForm validateExternalExam $ \html -> do
uid <- requireAuthId
cRoute <- fromMaybe (error "tutorialForm called from 404-Handler") <$> getCurrentRoute
MsgRenderer mr <- getMsgRenderer
let termsField = case template of
Just template' -> termsSetField [eefTerm template']
_other -> termsAllowedField
(lecturerSchools, adminSchools, oldSchool) <- liftHandler . runDB $ do
lecturerSchools <- map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid, UserFunctionFunction <-. [SchoolLecturer]] []
protoAdminSchools <- map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid, UserFunctionFunction <-. [SchoolAdmin]] []
adminSchools <- filterM (hasWriteAccessTo . flip SchoolR SchoolEditR) protoAdminSchools
let oldSchool = eefSchool <$> template
return (lecturerSchools, adminSchools, oldSchool)
let userSchools = nub . maybe id (:) oldSchool $ lecturerSchools ++ adminSchools
flip (renderAForm FormStandard) html $ ExternalExamForm
<$> areq termsField (fslI MsgExternalExamSemester) (eefTerm <$> template)
<*> areq (schoolFieldFor userSchools) (fslI MsgExternalExamSchool) (eefSchool <$> template)
<*> areq (textField & cfStrip & cfCI) (fslI MsgExternalExamCourseName & setTooltip MsgExternalExamCourseNameTip & addPlaceholder (mr MsgExternalExamCourseNamePlaceholder)) (eefCourseName <$> template)
<*> areq (textField & cfStrip & cfCI) (fslI MsgExternalExamExamName & setTooltip MsgExternalExamExamNameTip & addPlaceholder (mr MsgExternalExamExamNamePlaceholder)) (eefExamName <$> template)
<*> aopt utcTimeField (fslI MsgExternalExamDefaultTime & setTooltip MsgExternalExamDefaultTimeTip & addPlaceholder (mr MsgExternalExamDefaultTimePlaceholder)) (eefDefaultTime <$> template)
<*> apopt checkBoxField (fslI MsgExternalExamShowGrades & setTooltip MsgExternalExamShowGradesTip) (eefShowGrades <$> template)
<*> (Set.fromList <$> officeSchoolForm cRoute (Set.toList . eefOfficeSchools <$> template))
<*> (Set.fromList <$> staffForm cRoute ((Set.toList . eefStaff <$> template) <|> pure (pure $ Right uid)))
where
officeSchoolForm cRoute = massInputAccumA miAdd miCell miButtonAction miLayout miIdent fSettings fRequired
where
miAdd mkUnique submitView csrf = do
(schoolRes, addView) <- mpopt schoolField ("" & addName (mkUnique "school")) Nothing
let schoolRes' = schoolRes <&> \newDat oldDat -> FormSuccess (guardOn (newDat `notElem` oldDat) newDat)
return (schoolRes', $(widgetFile "external-exam/schoolMassInput/add"))
miCell ssh = do
School{..} <- liftHandler . runDB $ getJust ssh
$(widgetFile "external-exam/schoolMassInput/cell")
miButtonAction :: forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)
miButtonAction = Just . SomeRoute . (cRoute :#:)
miLayout :: MassInputLayout ListLength SchoolId ()
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "external-exam/schoolMassInput/layout")
miIdent :: Text
miIdent = "external-exams-school"
fSettings = fslI MsgExternalExamExamOfficeSchools & setTooltip (UniWorXMessages [SomeMessage MsgExternalExamExamOfficeSchoolsTip, SomeMessage MsgMassInputTip])
fRequired = False
staffForm cRoute = massInputAccumA miAdd miCell miButtonAction miLayout miIdent fSettings fRequired
where
miAdd mkUnique submitView csrf = do
MsgRenderer mr <- getMsgRenderer
(usersRes, addView) <- mpreq (multiUserField False Nothing) ("" & addName (mkUnique "email")) Nothing
let
usersRes' = usersRes <&> \newDat oldDat -> if
| existing <- newDat `Set.intersection` Set.fromList oldDat
, not $ Set.null existing
-> FormFailure [mr MsgExternalExamStaffAlreadyAdded]
| otherwise
-> FormSuccess $ Set.toList newDat
return (usersRes', $(widgetFile "external-exam/staffMassInput/add"))
miCell (Left email) = do
invWarnMsg <- messageI Warning MsgEmailInvitationWarning
$(widgetFile "external-exam/staffMassInput/cellInvitation")
miCell (Right userId) = do
User{..} <- liftHandler . runDB $ getJust userId
$(widgetFile "external-exam/staffMassInput/cellKnown")
miButtonAction :: forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)
miButtonAction = Just . SomeRoute . (cRoute :#:)
miLayout :: MassInputLayout ListLength (Either UserEmail UserId) ()
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "external-exam/staffMassInput/layout")
miIdent :: Text
miIdent = "external-exams-staff"
fSettings = fslI MsgExternalExamStaff & setTooltip (UniWorXMessages [SomeMessage MsgExternalExamStaffTip, SomeMessage MsgMassInputTip])
fRequired = True
validateExternalExam :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => FormValidator ExternalExamForm m ()
validateExternalExam = do
State.modify $ \eeForm -> eeForm & over _eefOfficeSchools (Set.delete $ eeForm ^. _eefSchool)
ExternalExamForm{..} <- State.get
isAdmin <- hasWriteAccessTo $ SchoolR eefSchool SchoolEditR
unless isAdmin $ do
uid <- requireAuthId
guardValidation MsgExternalExamUserMustBeStaff $ Right uid `Set.member` eefStaff
courseExists <- liftHandler . runDB . existsBy $ TermSchoolCourseName eefTerm eefSchool eefCourseName
guardValidation MsgExternalExamCourseExists $ not courseExists

View File

@ -4,7 +4,59 @@ module Handler.ExternalExam.New
import Import
import Jobs.Queue
import Handler.Utils
import Handler.Utils.Invitations
import Handler.ExternalExam.StaffInvite
import Handler.ExternalExam.Form
import qualified Data.Set as Set
getEExamNewR, postEExamNewR :: Handler Html
getEExamNewR = postEExamNewR
postEExamNewR = error "Not implemented"
postEExamNewR = do
((newExamResult, newExamWidget'), newExamEnctype) <- runFormPost $ externalExamForm Nothing
formResult newExamResult $ \ExternalExamForm{..} -> do
insertRes <- runDBJobs $ do
insertRes <- insertUnique ExternalExam
{ externalExamTerm = eefTerm
, externalExamSchool = eefSchool
, externalExamCourseName = eefCourseName
, externalExamExamName = eefExamName
, externalExamDefaultTime = eefDefaultTime
, externalExamShowGrades = eefShowGrades
}
whenIsJust insertRes $ \eeId -> do
insertMany_
[ ExternalExamOfficeSchool{..}
| externalExamOfficeSchoolSchool <- Set.toList eefOfficeSchools
, externalExamOfficeSchoolSchool /= eefSchool
, let externalExamOfficeSchoolExam = eeId
]
let (invites, adds) = partitionEithers $ Set.toList eefStaff
insertMany_ [ ExternalExamStaff{..}
| externalExamStaffUser <- adds
, let externalExamStaffExam = eeId
]
sinkInvitationsF externalExamStaffInvitationConfig $ map (, eeId, (InvDBDataExternalExamStaff, InvTokenDataExternalExamStaff)) invites
return insertRes
case insertRes of
Nothing -> addMessageI Error $ MsgExternalExamExists eefCourseName eefExamName
Just _ -> do
addMessageI Success $ MsgExternalExamCreated eefCourseName eefExamName
redirect $ EExamR eefTerm eefSchool eefCourseName eefExamName EEShowR
let heading = MsgMenuExternalExamNew
siteLayoutMsg heading $ do
setTitleI heading
wrapForm newExamWidget' def
{ formAction = Just $ SomeRoute EExamNewR
, formEncoding = newExamEnctype
}

View File

@ -0,0 +1,6 @@
$newline never
<td>
#{csrf}
^{fvInput addView}
<td>
^{fvInput submitView}

View File

@ -0,0 +1,3 @@
$newline never
<td>
#{schoolName}

View File

@ -0,0 +1,11 @@
$newline never
<table>
<tbody>
$forall coord <- review liveCoords lLength
<tr .massinput__cell>
^{cellWdgts ! coord}
<td>
^{fvInput (delButtons ! coord)}
<tfoot>
<tr .massinput__cell.massinput__cell--add>
^{addWdgts ! (0, 0)}

View File

@ -0,0 +1,6 @@
$newline never
<td colspan=2>
#{csrf}
^{fvInput addView}
<td>
^{fvInput submitView}

View File

@ -0,0 +1,6 @@
$newline never
<td>
<span style="font-family: monospace">
#{email}
<td>
^{messageTooltip invWarnMsg}

View File

@ -0,0 +1,3 @@
$newline never
<td colspan=2>
^{nameEmailWidget userEmail userDisplayName userSurname}

View File

@ -0,0 +1,11 @@
$newline never
<table>
<tbody>
$forall coord <- review liveCoords lLength
<tr .massinput__cell>
^{cellWdgts ! coord}
<td>
^{fvInput (delButtons ! coord)}
<tfoot>
<tr .massinput__cell.massinput__cell--add>
^{addWdgts ! (0, 0)}