feat(exam-office): course/user opt-outs

This commit is contained in:
Gregor Kleen 2019-09-13 12:57:30 +02:00
parent 517da054b1
commit 484fa1cc63
8 changed files with 154 additions and 0 deletions

View File

@ -1022,6 +1022,7 @@ MenuCourseMembers: Kursteilnehmer
MenuCourseAddMembers: Kursteilnehmer hinzufügen MenuCourseAddMembers: Kursteilnehmer hinzufügen
MenuCourseCommunication: Kursmitteilung MenuCourseCommunication: Kursmitteilung
MenuCourseApplications: Bewerbungen MenuCourseApplications: Bewerbungen
MenuCourseExamOffice: Prüfungsämter
MenuTermShow: Semester MenuTermShow: Semester
MenuSubmissionDelete: Abgabe löschen MenuSubmissionDelete: Abgabe löschen
MenuUsers: Benutzer MenuUsers: Benutzer
@ -1743,3 +1744,5 @@ TitleChangeUserDisplayEmail: Öffentliche E-Mail Adresse setzen
MailSubjectChangeUserDisplayEmail: Diese E-Mail Adresse in Uni2work veröffentlichen MailSubjectChangeUserDisplayEmail: Diese E-Mail Adresse in Uni2work veröffentlichen
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! 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 MailTitleChangeUserDisplayEmail displayName@Text: #{displayName} möchte diese E-Mail Adresse in Uni2work veröffentlichen
ExamOfficeOptOutsChanged: Zuständige Prüfungsämter erfolgreich angepasst

1
routes
View File

@ -115,6 +115,7 @@
/correctors CHiWisR GET /correctors CHiWisR GET
/communication CCommR GET POST /communication CCommR GET POST
/notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access! /notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access!
/exam-office CExamOfficeR GET POST !course-registered
/subs CCorrectionsR GET POST /subs CCorrectionsR GET POST
/subs/assigned CAssignR GET POST /subs/assigned CAssignR GET POST
/sheet SheetListR GET !course-registered !materials !corrector /sheet SheetListR GET !course-registered !materials !corrector

View File

@ -67,6 +67,7 @@ import qualified Control.Monad.Catch as C
import Handler.Utils.StudyFeatures import Handler.Utils.StudyFeatures
import Handler.Utils.SchoolLdap import Handler.Utils.SchoolLdap
import Handler.Utils.ExamOffice.Exam.Auth import Handler.Utils.ExamOffice.Exam.Auth
import Handler.Utils.ExamOffice.Course
import Handler.Utils.Profile import Handler.Utils.Profile
import Utils.Form import Utils.Form
import Utils.Sheet import Utils.Sheet
@ -1831,6 +1832,7 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb (CourseR tid ssh csh CUsersR) = return ("Anmeldungen", Just $ CourseR tid ssh csh CShowR) breadcrumb (CourseR tid ssh csh CUsersR) = return ("Anmeldungen", Just $ CourseR tid ssh csh CShowR)
breadcrumb (CourseR tid ssh csh CAddUserR) = return ("Kursteilnehmer hinzufügen", Just $ CourseR tid ssh csh CUsersR) breadcrumb (CourseR tid ssh csh CAddUserR) = return ("Kursteilnehmer hinzufügen", Just $ CourseR tid ssh csh CUsersR)
breadcrumb (CourseR tid ssh csh CInviteR) = return ("Einladung", Just $ CourseR tid ssh csh CShowR) breadcrumb (CourseR tid ssh csh CInviteR) = return ("Einladung", Just $ CourseR tid ssh csh CShowR)
breadcrumb (CourseR tid ssh csh CExamOfficeR) = return ("Prüfungsamter", Just $ CourseR tid ssh csh CShowR)
breadcrumb (CourseR tid ssh csh (CUserR cID)) = do breadcrumb (CourseR tid ssh csh (CUserR cID)) = do
uid <- decrypt cID uid <- decrypt cID
User{userDisplayName} <- runDB $ get404 uid User{userDisplayName} <- runDB $ get404 uid
@ -2454,6 +2456,20 @@ pageActions (CourseR tid ssh csh CShowR) =
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
} }
, MenuItem
{ menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuCourseExamOffice
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamOfficeR
, menuItemModal = True
, menuItemAccessCallback' = do
uid <- requireAuthId
runDB $ do
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
E.selectExists $ do
(_school, isForced) <- courseExamOfficeSchools (E.val uid) (E.val cid)
E.where_ $ E.not_ isForced
}
] ]
pageActions (CourseR tid ssh csh CCorrectionsR) = pageActions (CourseR tid ssh csh CCorrectionsR) =
[ MenuItem [ MenuItem

View File

@ -16,6 +16,7 @@ import Handler.Course.Show as Handler.Course
import Handler.Course.User as Handler.Course import Handler.Course.User as Handler.Course
import Handler.Course.Users as Handler.Course import Handler.Course.Users as Handler.Course
import Handler.Course.Application as Handler.Course import Handler.Course.Application as Handler.Course
import Handler.ExamOffice.Course as Handler.Course
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html

View File

@ -0,0 +1,75 @@
module Handler.ExamOffice.Course
( getCExamOfficeR, postCExamOfficeR
) where
import Import
import qualified Data.Set as Set
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
import Handler.Utils.ExamOffice.Course
import Handler.Utils
examOfficeOptOutForm :: UserId -> CourseId -> Maybe (Set SchoolId) -> Form (Set SchoolId)
-- ^ Deals with sets of _opt outs_
examOfficeOptOutForm uid cid (fromMaybe Set.empty -> template) = renderWForm FormStandard $ do
schools <- liftHandlerT . runDB . E.select $ courseExamOfficeSchools (E.val uid) (E.val cid)
res <- fmap sequence . forM schools $ \(Entity ssh School{..}, E.Value isForced)
-> fmap (ssh, ) <$> (bool wpopt wforcedJust isForced) checkBoxField (fslI schoolName) (Just $ ssh `Set.notMember` template)
return $ res <&> setOf (folded . filtered (not . view _2) . _1)
getCExamOfficeR, postCExamOfficeR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCExamOfficeR = postCExamOfficeR
postCExamOfficeR tid ssh csh = do
uid <- requireAuthId
isModal <- hasCustomHeader HeaderIsModal
(cid, optOuts, hasForced) <- runDB $ do
cid <- getKeyBy404 (TermSchoolCourseShort tid ssh csh)
optOuts <- selectList [ CourseUserExamOfficeOptOutCourse ==. cid, CourseUserExamOfficeOptOutUser ==. uid ] []
hasForced <- E.selectExists $ do
(_school, isForced) <- courseExamOfficeSchools (E.val uid) (E.val cid)
E.where_ isForced
return (cid, optOuts, hasForced)
((optOutRes, optOutView), optOutEnc)
<- runFormPost $ examOfficeOptOutForm uid cid (Just $ setOf (folded . _entityVal . _courseUserExamOfficeOptOutSchool) optOuts )
formResultModal optOutRes (CourseR tid ssh csh CExamOfficeR) $ \optOuts' -> do
lift . runDB $ do
deleteWhere [ CourseUserExamOfficeOptOutCourse ==. cid
, CourseUserExamOfficeOptOutUser ==. uid
, CourseUserExamOfficeOptOutSchool /<-. Set.toList optOuts'
]
forM_ optOuts' $ \ssh' ->
void $ insertUnique CourseUserExamOfficeOptOut
{ courseUserExamOfficeOptOutCourse = cid
, courseUserExamOfficeOptOutUser = uid
, courseUserExamOfficeOptOutSchool = ssh'
}
tell . pure =<< messageI Success MsgExamOfficeOptOutsChanged
let optOutView' = wrapForm optOutView def
{ formAction = Just . SomeRoute $ CourseR tid ssh csh CExamOfficeR
, formEncoding = optOutEnc
, formAttrs = [ asyncSubmitAttr | isModal ]
}
siteLayoutMsg MsgMenuCourseExamOffice $ do
setTitleI MsgMenuCourseExamOffice
let explanation = $(i18nWidgetFile "course-exam-office-explanation")
[whamlet|
$newline never
<section>
^{explanation}
<section>
^{optOutView'}
|]

View File

@ -0,0 +1,32 @@
module Handler.Utils.ExamOffice.Course
( courseExamOfficeSchools
) where
import Import.NoFoundation
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
courseExamOfficeSchools :: E.SqlExpr (E.Value UserId)
-> E.SqlExpr (E.Value CourseId)
-> E.SqlQuery (E.SqlExpr (Entity School), E.SqlExpr (E.Value Bool)) -- ^ @Entity School@ and @forced@
courseExamOfficeSchools user _course = E.from $ \(school `E.InnerJoin` userFunction `E.InnerJoin` (examOfficeField `E.FullOuterJoin` examOfficeUser))
-> E.distinctOnOrderBy [E.asc $ userFunction E.^. UserFunctionSchool] $ do
E.on E.false
E.on $ ( examOfficeUser E.?. ExamOfficeUserUser E.==. E.just user
E.&&. examOfficeUser E.?. ExamOfficeUserOffice E.==. E.just (userFunction E.^. UserFunctionUser)
)
E.||. ( examOfficeField E.?. ExamOfficeFieldOffice E.==. E.just (userFunction E.^. UserFunctionUser)
E.&&. E.exists ( E.from $ \studyFeatures ->
E.where_ $ E.just (studyFeatures E.^. StudyFeaturesField) E.==. examOfficeField E.?. ExamOfficeFieldField
E.&&. studyFeatures E.^. StudyFeaturesUser E.==. user
)
)
E.on $ school E.^. SchoolId E.==. userFunction E.^. UserFunctionSchool
E.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
let forced = E.maybe E.true id $ examOfficeField E.?. ExamOfficeFieldForced
E.orderBy [E.desc forced]
return $ (school, forced)

View File

@ -191,6 +191,8 @@ makeLenses_ ''SchoolLdap
makeLenses_ ''UserFunction makeLenses_ ''UserFunction
makeLenses_ ''CourseUserExamOfficeOptOut
-- makeClassy_ ''Load -- makeClassy_ ''Load

View File

@ -0,0 +1,24 @@
$newline never
<p>
Hier können Sie der Meldung ihrer Prüfungsleistungen an Prüfungsämter #
bestimmter Institute (innerhalb von Uni2work) widersprechen.
<p>
Bedenken Sie, dass die Meldung der Prüfungsleistungen direkt in Uni2work den #
Verwaltungsaufwand (und die damit verbunden Dauer) für die ordnungsgemäße #
Anrechung Ihrer Leistungen drastisch reduziert.
<p>
Unter Umständen können Prüfungsämter ungeachtet der Angaben, die Sie hier #
machen, Einsicht in Ihre Leistungen erlangen.<br />
Dies geschieht nur in begründeten Einzelfällen (z.B. bei Studierenden im #
ERASMUS-Programm).
<p>
Nutzer, die unabhängig von diesen Einstellungen, Einsicht in Ihre #
Prüfungsleistungen haben (z.B. die Kursverwalter) können Ihre Note natürlich #
außerhalb von Uni2work an Prüfungsämter melden (auch solche, die hier nicht #
aufgeführt sind).
$if hasForced
<p>
Wenn Sie der Meldung an einzelne Prüfungsämter nicht widersprechen können, #
so hat das jeweilige Prüfungsamt angegeben, dass die Einsicht, entweder #
aufgrund einer Ihrer Studiengänge (z.B. aufgrund der Studienordnung) oder #
bei Ihnen spezifisch, zwingend erforderlich ist.