feat(sheets): submission groups & rework sheet form
This commit is contained in:
parent
6d00410682
commit
57f1ce9265
@ -98,6 +98,9 @@ export class InteractiveFieldset {
|
||||
|
||||
if (this._isCheckbox()) {
|
||||
matches = this.conditionalInput.checked === true;
|
||||
} else if (this._isRadio()) {
|
||||
const radios = Array.from(this.conditionalInput.querySelectorAll('input[type=radio]'));
|
||||
matches = radios.some(radio => radio.checked && radio.value === this.conditionalValue);
|
||||
} else {
|
||||
matches = this.conditionalInput.value === this.conditionalValue;
|
||||
}
|
||||
@ -112,4 +115,8 @@ export class InteractiveFieldset {
|
||||
_isCheckbox() {
|
||||
return this.conditionalInput.getAttribute('type') === 'checkbox';
|
||||
}
|
||||
|
||||
_isRadio() {
|
||||
return !!this.conditionalInput.querySelector('input[type=radio]');
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,13 +60,21 @@
|
||||
grid-column: 1
|
||||
|
||||
.form-group--has-error
|
||||
background-color: rgba(255, 0, 0, 0.1)
|
||||
background-color: rgba(140, 7, 7, 0.05)
|
||||
|
||||
.form-group-label
|
||||
border-left: 2px solid var(--color-error)
|
||||
align-self: stretch
|
||||
padding-left: 7px
|
||||
|
||||
input, textarea
|
||||
border-color: var(--color-error) !important
|
||||
|
||||
.form-error
|
||||
display: block
|
||||
font-weight: 600
|
||||
color: var(--color-error)
|
||||
margin: 7px 0
|
||||
|
||||
.form-error
|
||||
display: none
|
||||
|
||||
@ -331,10 +331,12 @@ SheetFormType: Wertung & Abgabe
|
||||
SheetFormTimes: Zeiten
|
||||
SheetFormFiles: Dateien
|
||||
|
||||
SheetErrVisibility: "Beginn Abgabezeitraum" muss nach "Sichbar für Teilnehmer ab" liegen
|
||||
SheetErrDeadlineEarly: "Ende Abgabezeitraum" muss nach "Beginn Abzeitraum" liegen
|
||||
SheetErrVisibility: "Aktiv ab/Beginn Abgabezeitraum" muss nach "Sichbar für Teilnehmer ab" liegen
|
||||
SheetErrDeadlineEarly: "Aktiv bis/Ende Abgabezeitraum" muss nach "Aktiv ab/Beginn Abzeitraum" liegen
|
||||
SheetErrHintEarly: Hinweise dürfen erst nach Beginn des Abgabezeitraums herausgegeben werden
|
||||
SheetErrSolutionEarly: Lösungen dürfen erst nach Ende der Abgabezeitraums herausgegeben werden
|
||||
SheetErrVisibleWithoutActive: Wird "Sichtbar für Teilnehmer ab" angegeben, muss auch "Aktiv ab/Beginn Abgabezeitraum" angegeben werden
|
||||
SheetWarnNoActiveTo: "Aktiv bis/Ende Abgabezeitraum" sollte stets angegeben werden
|
||||
SheetNoCurrent: Es gibt momentan kein aktives Übungsblatt.
|
||||
SheetNoOldUnassigned: Alle Abgaben inaktiver Blätter sind bereits einen Korrektor zugeteilt.
|
||||
SheetsUnassignable name@Text: Momentan keine Abgaben zuteilbar für #{name}
|
||||
@ -474,6 +476,8 @@ UnauthorizedLDAP: Angegebener Nutzer meldet sich nicht mit Campus-Kennung an.
|
||||
UnauthorizedPWHash: Angegebener Nutzer meldet sich nicht mit Uni2work-Kennung an.
|
||||
UnauthorizedExternalExamListNotEmpty: Liste von externen Prüfungen ist nicht leer
|
||||
UnauthorizedExternalExamLecturer: Sie sind nicht als Prüfer für diese externe Prüfung eingetragen
|
||||
UnauthorizedSubmissionSubmissionGroup: Sie sind nicht Mitglied in einer der registrierten Abgabegruppen, die an dieser Abgabe beteiligt sind
|
||||
UnauthorizedSheetSubmissionGroup: Sie sind nicht Mitglied in einer registrierten Abgabegruppe
|
||||
|
||||
UnauthorizedPasswordResetToken: Dieses Authorisierungs-Token kann nicht mehr zum Passwort ändern benutzt werden
|
||||
|
||||
@ -793,6 +797,8 @@ CorrectorSubmissions: Abgabe extern mit Pseudonym
|
||||
UserSubmissions: Direkte Abgabe in Uni2work
|
||||
BothSubmissions: Abgabe direkt in Uni2work & extern mit Pseudonym
|
||||
|
||||
BothSubmissionsTip: Abgabe kann, nach Wahl des Teilnehmers, entweder direkt in Uni2work oder extern mit Pseudonym erfolgen
|
||||
|
||||
SheetCorrectorSubmissionsTip: Abgabe erfolgt über ein Uni2work-externes Verfahren (zumeist in Papierform durch Einwurf) unter Angabe eines persönlichen Pseudonyms. Korrektoren können mithilfe des Pseudonyms später Korrekturergebnisse in Uni2work eintragen, damit Sie sie einsehen können.
|
||||
|
||||
SubmissionNoUploadExpected: Es ist keine Abgabe von Dateien vorgesehen.
|
||||
@ -973,6 +979,7 @@ SheetTypeBonus grading@SheetGrading: Bonus
|
||||
SheetTypeNormal grading@SheetGrading: Normal
|
||||
SheetTypeInformational grading@SheetGrading: Ohne Anrechnung
|
||||
SheetTypeNotGraded: Keine Korrektur
|
||||
SheetTypeInfoNormalLecturer: Normale Blätter werden zur Berechnung eines etwaigen Klausurbonus 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.
|
||||
SheetTypeInfoBonus: Bonus Blätter zählen normal, erhöhen aber nicht die maximal erreichbare Punktzahl bzw. Anzahl zu bestehender Blätter.
|
||||
SheetTypeInfoInformational: Blätter ohne Anrechnung werden nirgends angerechnet, die Bewertung durch den Korrektor dient lediglich zur Information der Teilnehmer.
|
||||
@ -1376,6 +1383,7 @@ AuthTagIsPWHash: Nutzer meldet sich mit Uni2work-Kennung an
|
||||
AuthTagAuthentication: Nutzer ist angemeldet, falls erforderlich
|
||||
AuthTagRead: Zugriff ist nur lesend
|
||||
AuthTagWrite: Zugriff ist i.A. schreibend
|
||||
AuthTagSubmissionGroup: Nutzer ist Mitglied in registrierter Abgabegruppe
|
||||
|
||||
DeleteCopyStringIfSure n@Int: Wenn Sie sich sicher sind, dass Sie #{pluralDE n "das obige Objekt" "obige Objekte"} unwiderbringlich löschen möchten, schreiben Sie bitte zunächst den angezeigten Text ab.
|
||||
DeletePressButtonIfSure n@Int: Wenn Sie sich sicher sind, dass Sie #{pluralDE n "das obige Objekt" "obige Objekte"} unwiderbringlich löschen möchten, bestätigen Sie dies bitte durch Drücken des untigen Knopfes.
|
||||
@ -1772,6 +1780,13 @@ CourseUserExamsDeregistered count@Int64: Teilnehmer von #{show count} #{pluralDE
|
||||
CourseUserNoExamsDeregistered: Teilnehmer ist zu keiner der gewählten Prüfungen angemeldet
|
||||
CourseUserExamsResultSet count@Int64: Ergebnis zu #{show count} #{pluralDE count "Prüfung" "Prüfungen"} erfolgreich angepasst
|
||||
CourseUserExamResultDoesNotMatchMode examn@ExamName: Gewähtes Ergebnis passt nicht zu Bewertungsmodus von Prüfung „#{examn}“.
|
||||
CourseUserSetSubmissionGroup: Feste Abgabegruppe setzen/entfernen
|
||||
CourseUsersSubmissionGroupSetNew count@Int64: #{show count} Benutzer der festen Abgabegruppe zugeordnet
|
||||
CourseUsersSubmissionGroupUnset count@Int64: #{show count} Benutzer aus ihren jeweiligen festen Abgabegruppen entfernt
|
||||
|
||||
SubmissionGroup: Feste Abgabegruppe
|
||||
NoSubmissionGroup: Keine feste Abgabegruppe
|
||||
SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer aus den jeweiligen Abgabegruppen ersatzlos zu entfernen
|
||||
|
||||
ExamUserSynchronised: Synchronisiert
|
||||
ExamUserSyncOfficeName: Name
|
||||
@ -1854,6 +1869,7 @@ CsvColumnUserRegistration: Zeitpunkt der Anmeldung zum Kurs (ISO 8601)
|
||||
CsvColumnUserNote: Notizen zum Teilnehmer
|
||||
CsvColumnUserTutorial: Tutorien zu denen der Teilnehmer angemeldet ist, als Semikolon (;) separierte Liste. Für Registrierungs-Gruppen unter den Tutorien gibt es jeweils eine weitere Spalte. Die Registrierungs-Gruppen-Spalten enthalten jeweils maximal ein Tutorium pro Teilnehmer. Sind alle Tutorien in Registrierungs-Gruppen, so gibt es keine Spalte "tutorial".
|
||||
CsvColumnUserExam: Prüfungen zu denen der Teilnehmer angemeldet ist, als Semikolon (;) separierte Liste.
|
||||
CsvColumnUserSubmissionGroup: Registrierte Abgabegruppe
|
||||
|
||||
CsvColumnExamOfficeExamUserOccurrenceStart: Prüfungstermin (ISO 8601)
|
||||
|
||||
@ -2487,4 +2503,6 @@ BearerTokenOverrideStart: Startzeitpunkt
|
||||
BearerTokenOverrideStartTip: Wird kein Startzeitpunkt angegeben, wird bei Verwendung des Tokens nur der Ablaufzeitpunkt überprüft.
|
||||
|
||||
FaqTitle: Häufig gestellte Fragen
|
||||
AdditionalFaqs: Weitere häufig gestellte Fragen
|
||||
AdditionalFaqs: Weitere häufig gestellte Fragen
|
||||
|
||||
MultiActionUnknownAction: In einem von einem Eingabefeld abhängigen Formular wurde ein Wert gewählt, für den kein Formular verfügbar ist
|
||||
@ -334,6 +334,8 @@ SheetErrVisibility: "Submission period start" must be after "Visible from"
|
||||
SheetErrDeadlineEarly: "Submission period end" must be after "Submission period start"
|
||||
SheetErrHintEarly: "Hint from" must be after "Submission period start"
|
||||
SheetErrSolutionEarly: "Solution from" must be after "Submission period end"
|
||||
SheetErrVisibleWithoutActive: If “Visible from (for participants)” is specified “Active from/Submission period start” must also be specified
|
||||
SheetWarnNoActiveTo: “Active to/Submission period end” should always be specified
|
||||
SheetNoCurrent: There is no currently active exercise sheet
|
||||
SheetNoOldUnassigned: All submissions for inactive sheets are already assigned to correctors.
|
||||
SheetsUnassignable name: Submission for #{name} may not currently be assigned to correctors.
|
||||
@ -472,6 +474,8 @@ 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
|
||||
UnauthorizedSubmissionSubmissionGroup: You are not member in any of the submission groups for this submission
|
||||
UnauthorizedSheetSubmissionGroup: You are not member in any submission group
|
||||
|
||||
UnauthorizedPasswordResetToken: This authorisation-token may no longer be used to change passwords
|
||||
|
||||
@ -790,6 +794,8 @@ CorrectorSubmissions: External submission via pseudonym
|
||||
UserSubmissions: Direct submission in Uni2work
|
||||
BothSubmissions: Submission either directly in Uni2work or externally via pseudonym
|
||||
|
||||
BothSubmissionsTip: Participants may choose to submit either directly in Uni2work or externally via a pseudonym
|
||||
|
||||
SheetCorrectorSubmissionsTip: Submissions are expected to be handed in through some Uni2work-external procedure (usually on paper) marked with your personal pseudonym. Correctors can, using the pseudonym, register the marking in Uni2work for you to review.
|
||||
|
||||
SubmissionNoUploadExpected: No upload of files expected.
|
||||
@ -973,6 +979,7 @@ SheetTypeBonus grading: Bonus
|
||||
SheetTypeNormal grading: Normal
|
||||
SheetTypeInformational grading: Informational
|
||||
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.
|
||||
SheetTypeInfoNotGraded: "Not marked" means that there will be no feedback at all.
|
||||
SheetTypeInfoBonus: Sheets marked "bonus" count normally but do not increase either the maximum number of points or the count of sheets that can be passed.
|
||||
SheetTypeInfoInformational: Sheets marked "informational" do not counted anywhere. They are marked only as feedback for participants.
|
||||
@ -1375,6 +1382,7 @@ AuthTagIsPWHash: User logs in using their Uni2work-internal account
|
||||
AuthTagAuthentication: User is authenticated
|
||||
AuthTagRead: Access is read only
|
||||
AuthTagWrite: Access might write
|
||||
AuthTagSubmissionGroup: User is part of a submission group
|
||||
|
||||
DeleteCopyStringIfSure n: If you are sure that you want to permanently delete the #{pluralEN n "object" "objects"} listed below, please copy the shown text.
|
||||
DeletePressButtonIfSure n: If you are sure that you want to permanently delete the #{pluralEN n "object" "objects"} listed below, please confirm the action by pressing the button.
|
||||
@ -1771,6 +1779,13 @@ CourseUserExamsDeregistered count: Successfully deregistered participant from #{
|
||||
CourseUserNoExamsDeregistered: Participant is not registered for any of the selected exams
|
||||
CourseUserExamsResultSet count: Successfully adjusted the participant's result for #{show count} #{pluralEN count "exam" "exams"}
|
||||
CourseUserExamResultDoesNotMatchMode examn: The chosen result does not match the grading mode for exam “#{examn}”
|
||||
CourseUserSetSubmissionGroup: Set/Unset registered submission group
|
||||
CourseUsersSubmissionGroupSetNew count: Successfully added #{show count} #{pluralEN count "user" "users"} to submission group
|
||||
CourseUsersSubmissionGroupUnset count: Successfully removed #{show count} #{pluralEN count "user" "users"} from their #{pluralEN count "submission group" "respective submission groups"}
|
||||
|
||||
SubmissionGroup: Registered submission group
|
||||
NoSubmissionGroup: No registered submission group
|
||||
SubmissionGroupEmptyIsUnsetTip: Leave empty to remove users from their respective submission groups
|
||||
|
||||
ExamUserSynchronised: Synchronised
|
||||
ExamUserSyncOfficeName: Name
|
||||
@ -1853,6 +1868,7 @@ CsvColumnUserRegistration: Time of participant's enrollment (ISO 8601)
|
||||
CsvColumnUserNote: Course notes for the participant
|
||||
CsvColumnUserTutorial: Tutorials which the user is registered for, separated by semicolon (;). For each registration group among the tutorials there is a separate column. The registration group columns contain at most one tutorial per participant. If every tutorial has a registration group there is no column "tutorial".
|
||||
CsvColumnUserExam: Exams which the user is registered for, separated by semicolon (;).
|
||||
CsvColumnUserSubmissionGroup: Registered submission group
|
||||
|
||||
CsvColumnExamOfficeExamUserOccurrenceStart: Exam occurrence (ISO 8601)
|
||||
|
||||
@ -2488,3 +2504,5 @@ BearerTokenOverrideStartTip: If no start time is given, only the expiration time
|
||||
|
||||
FaqTitle: Frequently asked questions
|
||||
AdditionalFaqs: More frequently asked questions
|
||||
|
||||
MultiActionUnknownAction: In a form dependent on the value of a field a value was given for which no form is available
|
||||
|
||||
@ -140,7 +140,8 @@ for templateDirectory (templates/i18n/**/*(FN)); do
|
||||
|
||||
for ext (${templateExtensions}); do
|
||||
for lang (${requiredLangs}); do
|
||||
for prefix (${templatePrefixes}); do
|
||||
for prefixQ (${(q)templatePrefixes}); do
|
||||
prefix=${(Q)prefixQ}
|
||||
# printf ">> %s %s %s\n" ${prefix} ${lang} ${ext}
|
||||
|
||||
foundLang=1
|
||||
@ -156,7 +157,7 @@ for templateDirectory (templates/i18n/**/*(FN)); do
|
||||
|
||||
if [[ $foundLang -ne 0 ]]; then
|
||||
templateDifference=1
|
||||
[[ $fix != 0 ]] && printf "%s: %s*.%s (%s)\n" $templateDirectory $prefix $ext $lang
|
||||
[[ $fix != 0 ]] && printf "%s: %s*.%s (%s)\n" "$templateDirectory" "$prefix" "$ext" "$lang"
|
||||
|
||||
if [[ $fix == 0 ]]; then
|
||||
./translate.hs dir $templateDirectory && templateDifference=0
|
||||
|
||||
6
routes
6
routes
@ -151,15 +151,15 @@
|
||||
/edit SEditR GET POST
|
||||
/delete SDelR GET POST
|
||||
/subs SSubsR GET POST -- for lecturer only
|
||||
!/subs/new SubmissionNewR GET POST !timeANDcourse-registeredANDuser-submissions
|
||||
!/subs/new SubmissionNewR GET POST !timeANDcourse-registeredANDuser-submissionsANDsubmission-group
|
||||
!/subs/own SubmissionOwnR GET !free -- just redirect
|
||||
!/subs/assign SAssignR GET POST !lecturerANDtime
|
||||
/subs/#CryptoFileNameSubmission SubmissionR:
|
||||
/ SubShowR GET POST !ownerANDtimeANDuser-submissions !ownerANDread !correctorANDread
|
||||
/ SubShowR GET POST !ownerANDtimeANDuser-submissionsANDsubmission-group !ownerANDread !correctorANDread
|
||||
/delete SubDelR GET POST !ownerANDtimeANDuser-submissions
|
||||
/assign SubAssignR GET POST !lecturerANDtime
|
||||
/correction CorrectionR GET POST !corrector !ownerANDreadANDrated
|
||||
/invite SInviteR GET POST !ownerANDtimeANDuser-submissions
|
||||
/invite SInviteR GET POST !ownerANDtimeANDuser-submissionsANDsubmission-group
|
||||
!/#SubmissionFileType SubArchiveR GET !owner !corrector
|
||||
!/#SubmissionFileType/*FilePath SubDownloadR GET !owner !corrector
|
||||
/iscorrector SIsCorrR GET !corrector -- Route is used to check for corrector access to this sheet
|
||||
|
||||
@ -619,6 +619,29 @@ tagAccessPredicate AuthTutorControl = APDB $ \_ route _ -> case route of
|
||||
guard tutorialTutorControlled
|
||||
return Authorized
|
||||
r -> $unsupportedAuthPredicate AuthTutorControl r
|
||||
tagAccessPredicate AuthSubmissionGroup = APDB $ \mAuthId route _ -> case route of
|
||||
CSubmissionR _ _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionSubmissionGroup) $ do
|
||||
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
|
||||
groups <- $cachedHereBinary cID . lift . fmap (Set.fromList . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionUser) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. submissionUser E.^. SubmissionUserUser
|
||||
E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. E.val smId
|
||||
return $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup
|
||||
unless (Set.null groups) $ do
|
||||
uid <- hoistMaybe mAuthId
|
||||
guardM . lift $ exists [SubmissionGroupUserUser ==. uid, SubmissionGroupUserSubmissionGroup <-. Set.toList groups]
|
||||
return Authorized
|
||||
CSheetR tid ssh csh sheetn _ -> maybeT (unauthorizedI MsgUnauthorizedSheetSubmissionGroup) $ do
|
||||
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
Entity _ Sheet{..} <- $cachedHereBinary (course, sheetn) . MaybeT . getBy $ CourseSheet course sheetn
|
||||
when (is _RegisteredGroups sheetGrouping) $ do
|
||||
uid <- hoistMaybe mAuthId
|
||||
guardM . lift . E.selectExists . E.from $ \(submissionGroup `E.InnerJoin` submissionGroupUser) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val course
|
||||
E.&&. submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid
|
||||
|
||||
return Authorized
|
||||
r -> $unsupportedAuthPredicate AuthSubmissionGroup r
|
||||
tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
||||
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
|
||||
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
|
||||
@ -26,7 +26,7 @@ import Handler.Exam.List (mkExamTable)
|
||||
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCShowR tid ssh csh = do
|
||||
mbAid <- maybeAuthId
|
||||
(cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events) <- runDB . maybeT notFound $ do
|
||||
(cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup) <- runDB . maybeT notFound $ do
|
||||
[(E.Entity cid course, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
||||
<- lift . E.select . E.from $
|
||||
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
||||
@ -91,8 +91,19 @@ getCShowR tid ssh csh = do
|
||||
|
||||
events' <- fmap (sortOn $ courseEventTime . entityVal) . lift $ selectList [ CourseEventCourse ==. cid ] []
|
||||
events <- mapM (\(Entity evId ev) -> (, ev) <$> encrypt evId) events'
|
||||
|
||||
hasSubmissionGroups <- lift . E.selectExists . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
submissionGroup' <- lift . for mbAid $ \uid ->
|
||||
fmap (listToMaybe . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid
|
||||
return $ submissionGroup E.^. SubmissionGroupName
|
||||
let submissionGroup = guardOnM (hasSubmissionGroups && is _Just registration) submissionGroup'
|
||||
|
||||
return (cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events)
|
||||
return (cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup)
|
||||
|
||||
let mDereg' = maybe id min (allocationOverrideDeregister =<< mAllocation) <$> courseDeregisterUntil course
|
||||
mDereg <- traverse (formatTime SelFormatDateTime) mDereg'
|
||||
|
||||
@ -38,6 +38,9 @@ type UserTableExpr = ( E.SqlExpr (Entity User)
|
||||
`E.InnerJoin` E.SqlExpr (Maybe (Entity StudyDegree))
|
||||
`E.InnerJoin` E.SqlExpr (Maybe (Entity StudyTerms))
|
||||
)
|
||||
`E.LeftOuterJoin` ( E.SqlExpr (Maybe (Entity SubmissionGroup))
|
||||
`E.InnerJoin` E.SqlExpr (Maybe (Entity SubmissionGroupUser))
|
||||
)
|
||||
|
||||
-- forceUserTableType :: (UserTableExpr -> a) -> (UserTableExpr -> a)
|
||||
-- forceUserTableType = id
|
||||
@ -45,36 +48,44 @@ type UserTableExpr = ( E.SqlExpr (Entity User)
|
||||
-- Sql-Getters for this query, used for sorting and filtering (cannot be lenses due to being Esqueleto expressions)
|
||||
-- This ought to ease refactoring the query
|
||||
queryUser :: UserTableExpr -> E.SqlExpr (Entity User)
|
||||
queryUser = $(sqlIJproj 2 1) . $(sqlLOJproj 3 1)
|
||||
queryUser = $(sqlIJproj 2 1) . $(sqlLOJproj 4 1)
|
||||
|
||||
queryParticipant :: UserTableExpr -> E.SqlExpr (Entity CourseParticipant)
|
||||
queryParticipant = $(sqlIJproj 2 2) . $(sqlLOJproj 3 1)
|
||||
queryParticipant = $(sqlIJproj 2 2) . $(sqlLOJproj 4 1)
|
||||
|
||||
queryUserNote :: UserTableExpr -> E.SqlExpr (Maybe (Entity CourseUserNote))
|
||||
queryUserNote = $(sqlLOJproj 3 2)
|
||||
queryUserNote = $(sqlLOJproj 4 2)
|
||||
|
||||
queryFeaturesStudy :: UserTableExpr -> E.SqlExpr (Maybe (Entity StudyFeatures))
|
||||
queryFeaturesStudy = $(sqlIJproj 3 1) . $(sqlLOJproj 3 3)
|
||||
queryFeaturesStudy = $(sqlIJproj 3 1) . $(sqlLOJproj 4 3)
|
||||
|
||||
queryFeaturesDegree :: UserTableExpr -> E.SqlExpr (Maybe (Entity StudyDegree))
|
||||
queryFeaturesDegree = $(sqlIJproj 3 2) . $(sqlLOJproj 3 3)
|
||||
queryFeaturesDegree = $(sqlIJproj 3 2) . $(sqlLOJproj 4 3)
|
||||
|
||||
queryFeaturesField :: UserTableExpr -> E.SqlExpr (Maybe (Entity StudyTerms))
|
||||
queryFeaturesField = $(sqlIJproj 3 3) . $(sqlLOJproj 3 3)
|
||||
queryFeaturesField = $(sqlIJproj 3 3) . $(sqlLOJproj 4 3)
|
||||
|
||||
querySubmissionGroup :: UserTableExpr -> E.SqlExpr (Maybe (Entity SubmissionGroup))
|
||||
querySubmissionGroup = $(sqlIJproj 2 1) . $(sqlLOJproj 4 4)
|
||||
|
||||
|
||||
userTableQuery :: CourseId -> UserTableExpr -> E.SqlQuery ( E.SqlExpr (Entity User)
|
||||
, E.SqlExpr (E.Value UTCTime)
|
||||
, E.SqlExpr (E.Value (Maybe (Key CourseUserNote)))
|
||||
, StudyFeaturesDescription')
|
||||
userTableQuery cid ((user `E.InnerJoin` participant) `E.LeftOuterJoin` note `E.LeftOuterJoin` studyFeatures) = do
|
||||
, StudyFeaturesDescription'
|
||||
, E.SqlExpr (Maybe (Entity SubmissionGroup))
|
||||
)
|
||||
userTableQuery cid ((user `E.InnerJoin` participant) `E.LeftOuterJoin` note `E.LeftOuterJoin` studyFeatures `E.LeftOuterJoin` (subGroup `E.InnerJoin` subGroupUser)) = do
|
||||
-- Note that order of E.on for nested joins is seemingly right-to-left, ignoring nesting paranthesis
|
||||
E.on $ subGroup E.?. SubmissionGroupId E.==. subGroupUser E.?. SubmissionGroupUserSubmissionGroup
|
||||
E.on $ subGroupUser E.?. SubmissionGroupUserUser E.==. E.just (user E.^. UserId)
|
||||
E.&&. subGroup E.?. SubmissionGroupCourse E.==. E.just (E.val cid)
|
||||
features <- studyFeaturesQuery' (participant E.^. CourseParticipantField) studyFeatures
|
||||
E.on $ (note E.?. CourseUserNoteUser E.==. E.just (participant E.^. CourseParticipantUser))
|
||||
E.&&. (note E.?. CourseUserNoteCourse E.==. E.just (E.val cid))
|
||||
E.on $ participant E.^. CourseParticipantUser E.==. user E.^. UserId
|
||||
E.where_ $ participant E.^. CourseParticipantCourse E.==. E.val cid
|
||||
return (user, participant E.^. CourseParticipantRegistration, note E.?. CourseUserNoteId, features)
|
||||
return (user, participant E.^. CourseParticipantRegistration, note E.?. CourseUserNoteId, features, subGroup)
|
||||
|
||||
|
||||
type UserTableData = DBRow ( Entity User
|
||||
@ -83,6 +94,7 @@ type UserTableData = DBRow ( Entity User
|
||||
, (Maybe StudyFeatures, Maybe StudyDegree, Maybe StudyTerms)
|
||||
, ([Entity Tutorial], Map (CI Text) (Maybe (Entity Tutorial)))
|
||||
, [Entity Exam]
|
||||
, Maybe (Entity SubmissionGroup)
|
||||
)
|
||||
|
||||
instance HasEntity UserTableData User where
|
||||
@ -109,11 +121,14 @@ _userTutorials = _dbrOutput . _5
|
||||
_userExams :: Lens' UserTableData [Entity Exam]
|
||||
_userExams = _dbrOutput . _6
|
||||
|
||||
_userSubmissionGroup :: Traversal' UserTableData (Entity SubmissionGroup)
|
||||
_userSubmissionGroup = _dbrOutput . _7 . _Just
|
||||
|
||||
|
||||
colUserComment :: IsDBTable m c => TermId -> SchoolId -> CourseShorthand -> Colonnade Sortable UserTableData (DBCell m c)
|
||||
colUserComment tid ssh csh =
|
||||
sortable (Just "note") (i18nCell MsgCourseUserNote)
|
||||
$ \DBRow{ dbrOutput=(Entity uid _, _, mbNoteKey, _, _, _) } ->
|
||||
$ \DBRow{ dbrOutput=(Entity uid _, _, mbNoteKey, _, _, _, _) } ->
|
||||
maybeEmpty mbNoteKey $ const $
|
||||
anchorCellM (courseLink <$> encrypt uid) (hasComment True)
|
||||
where
|
||||
@ -158,6 +173,10 @@ colUserDegreeShort = sortable (Just "degree-short") (i18nCell MsgStudyFeatureDeg
|
||||
colUserSex' :: IsDBTable m c => Colonnade Sortable UserTableData (DBCell m c)
|
||||
colUserSex' = colUserSex $ hasUser . _userSex
|
||||
|
||||
colUserSubmissionGroup :: IsDBTable m c => Colonnade Sortable UserTableData (DBCell m c)
|
||||
colUserSubmissionGroup = sortable (Just "submission-group") (i18nCell MsgSubmissionGroup) $
|
||||
foldMap (cell . toWidget) . preview (_userSubmissionGroup . _entityVal . _submissionGroupName)
|
||||
|
||||
|
||||
data UserTableCsvStudyFeature = UserTableCsvStudyFeature
|
||||
{ csvUserField :: Text
|
||||
@ -173,6 +192,7 @@ data UserTableCsv = UserTableCsv
|
||||
, csvUserMatriculation :: Maybe Text
|
||||
, csvUserEmail :: CI Email
|
||||
, csvUserStudyFeatures :: Either (Maybe UserTableCsvStudyFeature) (Set UserTableCsvStudyFeature)
|
||||
, csvUserSubmissionGroup :: Maybe SubmissionGroupName
|
||||
, csvUserRegistration :: UTCTime
|
||||
, csvUserNote :: Maybe Html
|
||||
, csvUserTutorials :: ([TutorialName], Map (CI Text) (Maybe TutorialName))
|
||||
@ -199,6 +219,8 @@ instance Csv.ToNamedRecord UserTableCsv where
|
||||
in [ "study-features" Csv..= featsStr
|
||||
]
|
||||
++
|
||||
[ "submission-group" Csv..= csvUserSubmissionGroup
|
||||
] ++
|
||||
[ let tutsStr = Text.intercalate "; " . map CI.original $ csvUserTutorials ^. _1
|
||||
in "tutorial" Csv..= tutsStr
|
||||
] ++
|
||||
@ -221,6 +243,7 @@ instance CsvColumnsExplained UserTableCsv where
|
||||
, single "field" MsgCsvColumnUserField
|
||||
, single "degree" MsgCsvColumnUserDegree
|
||||
, single "semester" MsgCsvColumnUserSemester
|
||||
, single "submission-group" MsgCsvColumnUserSubmissionGroup
|
||||
, single "tutorial" MsgCsvColumnUserTutorial
|
||||
, single "exams" MsgCsvColumnUserExam
|
||||
, single "registration" MsgCsvColumnUserRegistration
|
||||
@ -255,6 +278,7 @@ data CourseUserAction = CourseUserSendMail
|
||||
| CourseUserDeregister
|
||||
| CourseUserRegisterTutorial
|
||||
| CourseUserRegisterExam
|
||||
| CourseUserSetSubmissionGroup
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
|
||||
instance Universe CourseUserAction
|
||||
@ -272,6 +296,9 @@ data CourseUserActionData = CourseUserSendMailData
|
||||
| CourseUserRegisterExamData
|
||||
{ registerExam :: (ExamId, Maybe ExamOccurrenceId)
|
||||
}
|
||||
| CourseUserSetSubmissionGroupData
|
||||
{ setSubmissionGroup :: Maybe SubmissionGroupName
|
||||
}
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
@ -298,7 +325,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
|
||||
dbtSQLQuery q = userTableQuery cid q <* E.where_ (restrict q)
|
||||
dbtRowKey = queryUser >>> (E.^. UserId)
|
||||
dbtProj = traverse $ \(user, E.Value registrationTime , E.Value userNoteId, (feature,degree,terms)) -> do
|
||||
dbtProj = traverse $ \(user, E.Value registrationTime , E.Value userNoteId, (feature,degree,terms), subGroup) -> do
|
||||
tuts'' <- selectList [ TutorialParticipantUser ==. entityKey user, TutorialParticipantTutorial <-. map entityKey tutorials ] []
|
||||
exams' <- selectList [ ExamRegistrationUser ==. entityKey user ] []
|
||||
let
|
||||
@ -306,7 +333,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
tuts' = filter (\(Entity tutId _) -> any ((== tutId) . tutorialParticipantTutorial . entityVal) tuts'') tutorials
|
||||
tuts = foldr (\tut@(Entity _ Tutorial{..}) -> maybe (over _1 $ cons tut) (over _2 . flip (Map.insertWith (<|>)) (Just tut)) tutorialRegGroup) ([], Map.fromSet (const Nothing) regGroups) tuts'
|
||||
exs = filter (\(Entity eId _) -> any ((== eId) . examRegistrationExam . entityVal) exams') exams
|
||||
return (user, registrationTime, userNoteId, (entityVal <$> feature, entityVal <$> degree, entityVal <$> terms), tuts, exs)
|
||||
return (user, registrationTime, userNoteId, (entityVal <$> feature, entityVal <$> degree, entityVal <$> terms), tuts, exs, subGroup)
|
||||
dbtColonnade = colChoices
|
||||
dbtSorting = mconcat
|
||||
[ single $ sortUserNameLink queryUser -- slower sorting through clicking name column header
|
||||
@ -340,6 +367,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId
|
||||
return . E.min_ $ exam E.^. ExamName
|
||||
)
|
||||
, single $ ("submission-group", SortColumn $ querySubmissionGroup >>> (E.?. SubmissionGroupName))
|
||||
]
|
||||
where single = uncurry Map.singleton
|
||||
dbtFilter = mconcat
|
||||
@ -378,6 +406,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
)
|
||||
-- , ("course-registration", error "TODO") -- TODO
|
||||
-- , ("course-user-note", error "TODO") -- TODO
|
||||
, single $ ("submission-group", FilterColumn $ E.mkContainsFilter $ querySubmissionGroup >>> (E.?. SubmissionGroupName))
|
||||
]
|
||||
where single = uncurry Map.singleton
|
||||
dbtFilterUI mPrev = mconcat $
|
||||
@ -387,6 +416,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
[ fltrUserSexUI mPrev | showSex ] ++
|
||||
[ prismAForm (singletonFilter "degree") mPrev $ aopt textField (fslI MsgStudyFeatureDegree)
|
||||
, prismAForm (singletonFilter "field") mPrev $ aopt textField (fslI MsgCourseStudyFeature)
|
||||
, prismAForm (singletonFilter "submission-group") mPrev $ aopt textField (fslI MsgSubmissionGroup)
|
||||
, prismAForm (singletonFilter "tutorial") mPrev $ aopt textField (fslI MsgCourseUserTutorial)
|
||||
, prismAForm (singletonFilter "exam") mPrev $ aopt textField (fslI MsgCourseUserExam)
|
||||
]
|
||||
@ -444,6 +474,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
, csvUserSemester = studyFeaturesSemester
|
||||
, csvUserType = studyFeaturesType
|
||||
}
|
||||
<*> preview (_userSubmissionGroup . _entityVal . _submissionGroupName)
|
||||
<*> view _userTableRegistration
|
||||
<*> userNote
|
||||
<*> (over (_2.traverse._Just) (tutorialName . entityVal) . over (_1.traverse) (tutorialName . entityVal) <$> view _userTutorials)
|
||||
@ -490,6 +521,11 @@ postCUsersR tid ssh csh = do
|
||||
E.on $ E.just (exam E.^. ExamId) E.==. examOccurrence E.?. ExamOccurrenceExam
|
||||
E.where_ $ exam E.^. ExamCourse E.==. E.val cid
|
||||
return (exam, examOccurrence)
|
||||
hasSubmissionGroups <- E.selectExists . E.from $ \(submissionGroup `E.InnerJoin` submissionGroupUser `E.InnerJoin` courseParticipant) -> do
|
||||
E.on $ courseParticipant E.^. CourseParticipantUser E.==. submissionGroupUser E.^. SubmissionGroupUserUser
|
||||
E.&&. courseParticipant E.^. CourseParticipantCourse E.==. submissionGroup E.^. SubmissionGroupCourse
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
let exams = nubOn entityKey $ examOccurrencesPerExam ^.. folded . _1
|
||||
let colChoices = mconcat $ catMaybes
|
||||
[ pure $ dbSelect (applying _2) id (return . view (hasEntity . _entityKey))
|
||||
@ -500,6 +536,7 @@ postCUsersR tid ssh csh = do
|
||||
, pure $ colUserDegreeShort
|
||||
, pure $ colUserField
|
||||
, pure $ colUserSemester
|
||||
, guardOn hasSubmissionGroups colUserSubmissionGroup
|
||||
, guardOn hasTutorials $ colUserTutorials tid ssh csh
|
||||
, guardOn hasExams $ colUserExams tid ssh csh
|
||||
, pure $ sortable (Just "registration") (i18nCell MsgRegisteredSince) (dateCell . view _userTableRegistration)
|
||||
@ -532,6 +569,7 @@ postCUsersR tid ssh csh = do
|
||||
optionInternalValue = entityKey
|
||||
optionDisplay = CI.original $ examName entityVal
|
||||
return Option{..}
|
||||
submissionGroupOpts = optionsPersist [SubmissionGroupCourse ==. cid] [Asc SubmissionGroupName] submissionGroupName <&> fmap (submissionGroupName . entityVal)
|
||||
acts = mconcat
|
||||
[ singletonMap CourseUserSendMail $ pure CourseUserSendMailData
|
||||
, singletonMap CourseUserRegisterTutorial $ CourseUserRegisterTutorialData <$>
|
||||
@ -540,6 +578,8 @@ postCUsersR tid ssh csh = do
|
||||
Nothing
|
||||
, singletonMap CourseUserRegisterExam $ CourseUserRegisterExamData <$>
|
||||
multiActionAOpts examOccActs examActs (fslI MsgCourseExam) Nothing
|
||||
, singletonMap CourseUserSetSubmissionGroup $ CourseUserSetSubmissionGroupData . assertM (not . Text.null . CI.original) <$>
|
||||
aopt (textField & cfStrip & cfCI & addDatalist submissionGroupOpts) (fslI MsgSubmissionGroup & setTooltip MsgSubmissionGroupEmptyIsUnsetTip) Nothing
|
||||
, if
|
||||
| mayRegister
|
||||
-> singletonMap CourseUserDeregister $ courseUserDeregisterForm cid
|
||||
@ -589,6 +629,25 @@ postCUsersR tid ssh csh = do
|
||||
return mempty
|
||||
addMessageI Success $ MsgCourseUsersExamRegistered nrReg
|
||||
redirect $ CourseR tid ssh csh CUsersR
|
||||
(CourseUserSetSubmissionGroupData{ setSubmissionGroup = Just setSubmissionGroup }, selectedUsers) -> do
|
||||
Sum nrSet <- runDB $ do
|
||||
Entity gId _ <- upsert (SubmissionGroup cid setSubmissionGroup) [ SubmissionGroupName =. setSubmissionGroup ]
|
||||
flip foldMapM selectedUsers $ \uid ->
|
||||
fmap (maybe mempty . const $ Sum 1) . insertUnique $ SubmissionGroupUser gId uid
|
||||
addMessageI Success $ MsgCourseUsersSubmissionGroupSetNew nrSet
|
||||
redirect $ CourseR tid ssh csh CUsersR
|
||||
(CourseUserSetSubmissionGroupData{ setSubmissionGroup = Nothing }, selectedUsers) -> do
|
||||
nrUnset <- runDB $ do
|
||||
nrUnset <- E.deleteCount . E.from $ \submissionGroupUser ->
|
||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser `E.in_` E.valList (Set.toList selectedUsers)
|
||||
E.&&. E.subSelectForeign submissionGroupUser SubmissionGroupUserSubmissionGroup (E.^. SubmissionGroupCourse) E.==. E.val cid
|
||||
E.delete . E.from $ \submissionGroup ->
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
E.&&. E.not_ (E.exists . E.from $ \submissionGroupUser -> E.where_ $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId)
|
||||
return nrUnset
|
||||
addMessageI Success $ MsgCourseUsersSubmissionGroupUnset nrUnset
|
||||
redirect $ CourseR tid ssh csh CUsersR
|
||||
|
||||
let headingLong = [whamlet|_{MsgMenuCourseMembers} #{courseName course} #{tid}|]
|
||||
headingShort = prependCourseTitle tid ssh csh MsgCourseMembers
|
||||
siteLayout headingLong $ do
|
||||
|
||||
@ -43,6 +43,8 @@ import Text.Hamlet (ihamlet)
|
||||
|
||||
import Data.Time.Clock.System (systemEpochDay)
|
||||
|
||||
import qualified Control.Monad.State.Class as State
|
||||
|
||||
|
||||
{-
|
||||
* Implement Handlers
|
||||
@ -61,9 +63,9 @@ data SheetForm = SheetForm
|
||||
, sfActiveTo :: Maybe UTCTime
|
||||
, sfHintFrom :: Maybe UTCTime
|
||||
, sfSolutionFrom :: Maybe UTCTime
|
||||
, sfType :: SheetType
|
||||
, sfGrouping :: SheetGroup
|
||||
, sfSubmissionMode :: SubmissionMode
|
||||
, sfGrouping :: SheetGroup
|
||||
, sfType :: SheetType
|
||||
, sfAutoDistribute :: Bool
|
||||
, sfMarkingText :: Maybe Html
|
||||
, sfAnonymousCorrection :: Bool
|
||||
@ -92,13 +94,13 @@ getFtIdMap sId = do
|
||||
return $ partitionFileType [(t,i)|(E.Value t, E.Value i) <- allfIds]
|
||||
|
||||
makeSheetForm :: Maybe SheetId -> Maybe SheetForm -> Form SheetForm
|
||||
makeSheetForm msId template = identifyForm FIDsheet $ \html -> do
|
||||
makeSheetForm msId template = identifyForm FIDsheet . validateForm validateSheet $ \html -> do
|
||||
oldFileIds <- (return.) <$> case msId of
|
||||
Nothing -> return $ partitionFileType mempty
|
||||
(Just sId) -> liftHandler $ runDB $ getFtIdMap sId
|
||||
mr'@(MsgRenderer mr) <- getMsgRenderer
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
ctime <- ceilingQuarterHour <$> liftIO getCurrentTime
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ SheetForm
|
||||
flip (renderAForm FormStandard) html $ SheetForm
|
||||
<$> areq (textField & cfStrip & cfCI) (fslI MsgSheetName) (sfName <$> template)
|
||||
<*> aopt htmlField (fslI MsgSheetDescription) (sfDescription <$> template)
|
||||
<* aformSection MsgSheetFormFiles
|
||||
@ -120,31 +122,26 @@ makeSheetForm msId template = identifyForm FIDsheet $ \html -> do
|
||||
<*> aopt utcTimeField (fslpI MsgSheetSolutionFrom (mr MsgSheetSolutionFromPlaceholder)
|
||||
& setTooltip MsgSheetSolutionFromTip) (sfSolutionFrom <$> template)
|
||||
<* aformSection MsgSheetFormType
|
||||
<*> sheetTypeAFormReq (fslI MsgSheetType
|
||||
& setTooltip (uniworxMessages [MsgSheetTypeInfoBonus, MsgSheetTypeInfoInformational, MsgSheetTypeInfoNotGraded]))
|
||||
(sfType <$> template)
|
||||
<*> sheetGroupAFormReq (fslI MsgSheetGroup) (sfGrouping <$> template)
|
||||
<*> submissionModeForm ((sfSubmissionMode <$> template) <|> pure (SubmissionMode False . Just $ UploadAny True defaultExtensionRestriction))
|
||||
<*> sheetGroupAFormReq (fslI MsgSheetGroup) ((sfGrouping <$> template) <|> pure NoGroups)
|
||||
<*> sheetTypeAFormReq (fslI MsgSheetType) (sfType <$> template)
|
||||
<*> apopt checkBoxField (fslI MsgAutoAssignCorrs) (sfAutoDistribute <$> template)
|
||||
<*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
|
||||
<*> apopt checkBoxField (fslI MsgSheetAnonymousCorrection & setTooltip MsgSheetAnonymousCorrectionTip) (sfAnonymousCorrection <$> template)
|
||||
<*> correctorForm (fromMaybe mempty $ sfCorrectors <$> template)
|
||||
return $ case result of
|
||||
FormSuccess sheetResult
|
||||
| errorMsgs <- validateSheet mr' sheetResult
|
||||
, not $ null errorMsgs ->
|
||||
(FormFailure errorMsgs, widget)
|
||||
_ -> (result, widget)
|
||||
where
|
||||
validateSheet :: MsgRenderer -> SheetForm -> [Text]
|
||||
validateSheet (MsgRenderer {..}) (SheetForm{..}) =
|
||||
[ msg | (False, msg) <-
|
||||
[ ( NTop sfVisibleFrom <= NTop sfActiveFrom , render MsgSheetErrVisibility)
|
||||
, ( NTop sfActiveFrom <= NTop sfActiveTo , render MsgSheetErrDeadlineEarly)
|
||||
, ( NTop sfHintFrom >= NTop sfActiveFrom , render MsgSheetErrHintEarly)
|
||||
, ( NTop sfSolutionFrom >= NTop sfActiveTo , render MsgSheetErrSolutionEarly)
|
||||
] ]
|
||||
validateSheet :: FormValidator SheetForm Handler ()
|
||||
validateSheet = do
|
||||
SheetForm{..} <- State.get
|
||||
|
||||
guardValidation MsgSheetErrVisibility $ NTop sfVisibleFrom <= NTop sfActiveFrom
|
||||
guardValidation MsgSheetErrDeadlineEarly $ NTop sfActiveFrom <= NTop sfActiveTo
|
||||
guardValidation MsgSheetErrHintEarly $ NTop sfHintFrom >= NTop sfActiveFrom
|
||||
guardValidation MsgSheetErrSolutionEarly $ NTop sfSolutionFrom >= NTop sfActiveTo
|
||||
|
||||
guardValidation MsgSheetErrVisibleWithoutActive $ is _Just sfActiveFrom || is _Nothing sfVisibleFrom
|
||||
|
||||
warnValidation MsgSheetWarnNoActiveTo $ is _Just sfActiveTo || is _Nothing sfActiveFrom
|
||||
|
||||
getSheetCurrentR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getSheetCurrentR tid ssh csh = do
|
||||
|
||||
@ -358,7 +358,34 @@ submissionHelper tid ssh csh shn mcid = do
|
||||
, isLecturer
|
||||
, not isLecturer
|
||||
)
|
||||
(Nothing, _) -> return (csheet, Set.empty, [], maySubmit, isLecturer, not isLecturer) -- TODO: Return registered group members
|
||||
(Nothing, RegisteredGroups) -> do
|
||||
buddies <- E.select . E.from $ \(submissionGroup `E.InnerJoin` user) -> do
|
||||
E.on . E.exists . E.from $ \submissionGroupUser ->
|
||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. user E.^. UserId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val sheetCourse
|
||||
E.where_ . E.exists . E.from $ \submissionGroupUser ->
|
||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid
|
||||
E.&&. submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ user E.^. UserId E.!=. E.val uid
|
||||
E.where_ . E.not_ . E.exists . E.from $ \(submissionUser `E.InnerJoin` submission) -> do
|
||||
E.on $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId
|
||||
E.where_ $ submission E.^. SubmissionSheet E.==. E.val shid
|
||||
E.&&. submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId
|
||||
|
||||
E.orderBy [E.asc $ user E.^. UserEmail]
|
||||
|
||||
return $ user E.^. UserId
|
||||
|
||||
return ( csheet
|
||||
, buddies
|
||||
& map (Right . E.unValue)
|
||||
& Set.fromList
|
||||
, []
|
||||
, maySubmit
|
||||
, isLecturer
|
||||
, not isLecturer
|
||||
)
|
||||
(Nothing, _) -> return (csheet, Set.empty, [], maySubmit, isLecturer, not isLecturer)
|
||||
(Just smid, _) -> do
|
||||
void $ submissionMatchesSheet tid ssh csh shn (fromJust mcid)
|
||||
|
||||
@ -478,16 +505,24 @@ submissionHelper tid ssh csh shn mcid = do
|
||||
-- Determine new submission users
|
||||
subUsers <- if
|
||||
| isLecturer -> return adhocMembers
|
||||
| otherwise -> do
|
||||
| RegisteredGroups <- sheetGrouping -> do
|
||||
-- Determine members of pre-registered group
|
||||
groupUids <- fmap (setFromList . map (Right . E.unValue)) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do
|
||||
E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser' E.^. SubmissionGroupUserSubmissionGroup
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid
|
||||
E.&&. submissionGroup E.^. SubmissionGroupCourse E.==. E.val sheetCourse
|
||||
|
||||
E.where_ . E.not_ . E.exists . E.from $ \(submissionUser `E.InnerJoin` submission) -> do
|
||||
E.on $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId
|
||||
E.where_ $ submission E.^. SubmissionSheet E.==. E.val shid
|
||||
E.&&. submissionUser E.^. SubmissionUserUser E.==. submissionGroupUser' E.^. SubmissionGroupUserUser
|
||||
E.where_ $ submission E.^. SubmissionId E.!=. E.val smid
|
||||
|
||||
return $ submissionGroupUser' E.^. SubmissionGroupUserUser
|
||||
-- SubmissionUser for all group members (pre-registered & ad-hoc)
|
||||
return $ groupUids `Set.union` adhocMembers
|
||||
return $ Set.insert (Right uid) groupUids
|
||||
| otherwise -> return adhocMembers
|
||||
|
||||
-- Since invitations carry no data we only need to consider changes to
|
||||
-- the set of users/invited emails
|
||||
|
||||
@ -16,6 +16,8 @@ import Handler.Utils.DateTime
|
||||
|
||||
import Handler.Utils.Widgets
|
||||
|
||||
import Handler.Utils.I18n
|
||||
|
||||
import Import
|
||||
import Data.Char (chr, ord)
|
||||
import qualified Data.Char as Char
|
||||
@ -239,6 +241,34 @@ multiAction' :: forall action a.
|
||||
-> (Html -> MForm Handler (FormResult a, [FieldView UniWorX]))
|
||||
multiAction' minp acts = multiActionOpts' minp acts (optionsF $ Map.keysSet acts)
|
||||
|
||||
multiActionField :: forall action a.
|
||||
Ord action
|
||||
=> (Field Handler action -> FieldSettings UniWorX -> Maybe action -> MForm Handler (FormResult action, FieldView UniWorX))
|
||||
-> Map action (AForm Handler a)
|
||||
-> (Field Handler action, action -> Maybe Text, action -> Maybe (SomeMessage UniWorX))
|
||||
-> FieldSettings UniWorX
|
||||
-> Maybe action
|
||||
-> (Html -> MForm Handler (FormResult a, [FieldView UniWorX]))
|
||||
multiActionField minp acts (actField, actExternal, actMessage) fs@FieldSettings{..} defAction csrf = do
|
||||
(actionRes, actionView) <- minp (checkBool (`Map.member` acts) MsgMultiActionUnknownAction actField) fs defAction
|
||||
results <- mapM (fmap (over _2 ($ [])) . aFormToForm) acts
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let actionResults = view _1 <$> results
|
||||
|
||||
actionViews = Map.foldrWithKey accViews [] results
|
||||
|
||||
accViews :: forall b. action -> (b, [FieldView UniWorX]) -> [FieldView UniWorX] -> [FieldView UniWorX]
|
||||
accViews act
|
||||
| Just optionExternalValue <- actExternal act
|
||||
, Just (mr -> optionDisplay) <- actMessage act
|
||||
= flip mappend . over (mapped . _fvInput) (\w -> $(widgetFile "widgets/multi-action/multi-action")) . snd
|
||||
| otherwise
|
||||
= flip const
|
||||
|
||||
return ((actionResults Map.!) =<< actionRes, over _fvInput (mappend $ toWidget csrf) actionView : actionViews)
|
||||
|
||||
|
||||
multiActionOpts' :: forall action a.
|
||||
Ord action
|
||||
=> (Field Handler action -> FieldSettings UniWorX -> Maybe action -> MForm Handler (FormResult action, FieldView UniWorX))
|
||||
@ -254,22 +284,12 @@ multiActionOpts' minp acts mActsOpts fs@FieldSettings{..} defAction csrf = do
|
||||
, olReadExternal = assertM (flip Map.member acts) . olReadExternal actsOpts
|
||||
}
|
||||
acts' = Map.filterWithKey (\a _ -> any ((== a) . optionInternalValue) $ olOptions actsOpts') acts
|
||||
|
||||
(actionRes, actionView) <- minp (selectField $ return actsOpts') fs defAction
|
||||
results <- mapM (fmap (over _2 ($ [])) . aFormToForm) acts'
|
||||
|
||||
let actionResults = view _1 <$> results
|
||||
|
||||
actionViews = Map.foldrWithKey accViews [] results
|
||||
actOption act = listToMaybe . filter (\Option{..} -> optionInternalValue == act) $ olOptions actsOpts'
|
||||
actExternal = fmap optionExternalValue . actOption
|
||||
actMessage = fmap (SomeMessage . optionDisplay) . actOption
|
||||
|
||||
accViews :: forall b. action -> (b, [FieldView UniWorX]) -> [FieldView UniWorX] -> [FieldView UniWorX]
|
||||
accViews act
|
||||
| Option{..} : _ <- filter ((== act) . optionInternalValue) $ olOptions actsOpts'
|
||||
= flip mappend . over (mapped . _fvInput) (\w -> $(widgetFile "widgets/multi-action/multi-action")) . snd
|
||||
| otherwise
|
||||
= flip const
|
||||
|
||||
return ((actionResults Map.!) =<< actionRes, over _fvInput (mappend $ toWidget csrf) actionView : actionViews)
|
||||
multiActionField minp acts' (selectField $ return actsOpts', actExternal, actMessage) fs defAction csrf
|
||||
|
||||
multiActionA :: (RenderMessage UniWorX action, PathPiece action, Ord action)
|
||||
=> Map action (AForm Handler a)
|
||||
@ -316,6 +336,88 @@ multiActionMOpts :: Ord action
|
||||
-> (Html -> MForm Handler (FormResult a, Widget))
|
||||
multiActionMOpts acts opts fSettings defAction = renderAForm FormStandard $ multiActionAOpts acts opts fSettings defAction
|
||||
|
||||
-------------------------
|
||||
-- Explained selection --
|
||||
-------------------------
|
||||
|
||||
explainedSelectionField :: forall m a.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
, Eq a
|
||||
)
|
||||
=> Maybe (SomeMessage UniWorX, Maybe Widget) -- ^ Label for none option
|
||||
-> Handler ([(Option a, Maybe Widget)], Text -> Maybe a)
|
||||
-> Field m a
|
||||
explainedSelectionField optMsg' mkOpts = Field{..}
|
||||
where
|
||||
fieldEnctype = UrlEncoded
|
||||
fieldParse ts _ = do
|
||||
(_, parser) <- liftHandler mkOpts
|
||||
if
|
||||
| t : _ <- ts
|
||||
, Just t' <- parser t
|
||||
-> return . Right $ Just t'
|
||||
| t : _ <- ts
|
||||
, null t
|
||||
-> return $ Right Nothing
|
||||
| t : _ <- ts
|
||||
-> return . Left . SomeMessage $ MsgInvalidEntry t
|
||||
| otherwise
|
||||
-> return $ Right Nothing
|
||||
fieldView theId name attrs val isReq = do
|
||||
(opts, _) <- liftHandler mkOpts
|
||||
let optMsg = guardOnM (not isReq) optMsg'
|
||||
inputId optExternal = [st|#{theId}__input--#{optExternal}|]
|
||||
matchesVal Nothing = is _Left val
|
||||
matchesVal (Just x) = val == Right x
|
||||
$(widgetFile "widgets/explained-selection-field")
|
||||
|
||||
explainOptionList :: forall a.
|
||||
Handler (OptionList a)
|
||||
-> (a -> MaybeT Handler Widget)
|
||||
-> Handler ([(Option a, Maybe Widget)], Text -> Maybe a)
|
||||
explainOptionList ol mkExplanation = do
|
||||
OptionList{..} <- ol
|
||||
olOptions' <- forM olOptions $ \opt@Option{..} -> (opt, ) <$> runMaybeT (mkExplanation optionInternalValue)
|
||||
return (olOptions', olReadExternal)
|
||||
|
||||
explainedMultiAction' :: forall action a.
|
||||
Ord action
|
||||
=> (Field Handler action -> FieldSettings UniWorX -> Maybe action -> MForm Handler (FormResult action, FieldView UniWorX))
|
||||
-> Map action (AForm Handler a)
|
||||
-> Handler ([(Option action, Maybe Widget)], Text -> Maybe action)
|
||||
-> FieldSettings UniWorX
|
||||
-> Maybe action
|
||||
-> (Html -> MForm Handler (FormResult a, [FieldView UniWorX]))
|
||||
explainedMultiAction' minp acts mActsOpts fs@FieldSettings{..} defAction csrf = do
|
||||
(actsOpts, actsReadExternal) <- liftHandler mActsOpts
|
||||
let actsOpts' = filter (flip Map.member acts . optionInternalValue . view _1) actsOpts
|
||||
actsReadExternal' = assertM (flip Map.member acts) . actsReadExternal
|
||||
acts' = Map.filterWithKey (\a _ -> any ((== a) . optionInternalValue . view _1) actsOpts') acts
|
||||
|
||||
actOption act = listToMaybe . filter (\Option{..} -> optionInternalValue == act) $ view _1 <$> actsOpts'
|
||||
actExternal = fmap optionExternalValue . actOption
|
||||
actMessage = fmap (SomeMessage . optionDisplay) . actOption
|
||||
|
||||
multiActionField minp acts' (explainedSelectionField Nothing $ return (actsOpts', actsReadExternal'), actExternal, actMessage) fs defAction csrf
|
||||
|
||||
explainedMultiAction :: forall action a.
|
||||
Ord action
|
||||
=> Map action (AForm Handler a)
|
||||
-> Handler ([(Option action, Maybe Widget)], Text -> Maybe action)
|
||||
-> FieldSettings UniWorX
|
||||
-> Maybe action
|
||||
-> (Html -> MForm Handler (FormResult a, [FieldView UniWorX]))
|
||||
explainedMultiAction = explainedMultiAction' mpopt
|
||||
|
||||
explainedMultiActionA :: forall action a.
|
||||
Ord action
|
||||
=> Map action (AForm Handler a)
|
||||
-> Handler ([(Option action, Maybe Widget)], Text -> Maybe action)
|
||||
-> FieldSettings UniWorX
|
||||
-> Maybe action
|
||||
-> AForm Handler a
|
||||
explainedMultiActionA acts mActsOpts fSettings defAction = formToAForm $ explainedMultiAction acts mActsOpts fSettings defAction mempty
|
||||
|
||||
------------
|
||||
-- Fields --
|
||||
@ -555,7 +657,7 @@ uploadModeForm prev = multiActionA actions (fslI MsgSheetUploadMode) (classifyUp
|
||||
|
||||
|
||||
submissionModeForm :: Maybe SubmissionMode -> AForm Handler SubmissionMode
|
||||
submissionModeForm prev = multiActionA actions (fslI MsgSheetSubmissionMode) $ classifySubmissionMode <$> prev
|
||||
submissionModeForm prev = explainedMultiActionA actions opts (fslI MsgSheetSubmissionMode) $ classifySubmissionMode <$> prev
|
||||
where
|
||||
actions :: Map SubmissionModeDescr (AForm Handler SubmissionMode)
|
||||
actions = Map.fromList
|
||||
@ -573,6 +675,12 @@ submissionModeForm prev = multiActionA actions (fslI MsgSheetSubmissionMode) $ c
|
||||
)
|
||||
]
|
||||
|
||||
opts = explainOptionList optionsFinite $ \case
|
||||
SubmissionModeNone -> mzero
|
||||
SubmissionModeCorrector -> return $(i18nWidgetFile "submission-mode-explanation/corrector")
|
||||
SubmissionModeUser -> return $(i18nWidgetFile "submission-mode-explanation/user")
|
||||
SubmissionModeBoth -> return $ i18n MsgBothSubmissionsTip
|
||||
|
||||
data ExamBonusRule' = ExamBonusManual'
|
||||
| ExamBonusPoints'
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
@ -847,7 +955,7 @@ nullaryPathPiece ''SheetType' (camelToPathPiece . dropSuffix "'")
|
||||
embedRenderMessage ''UniWorX ''SheetType' ("SheetType" <>)
|
||||
|
||||
|
||||
data SheetGroup' = NoGroups' | Arbitrary' | RegisteredGroups'
|
||||
data SheetGroup' = Arbitrary' | RegisteredGroups' | NoGroups'
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
||||
|
||||
instance Universe SheetGroup'
|
||||
@ -875,9 +983,9 @@ sheetGradingAFormReq fs template = multiActionA selOptions fs (classify' <$> tem
|
||||
|
||||
|
||||
sheetTypeAFormReq :: FieldSettings UniWorX -> Maybe SheetType -> AForm Handler SheetType
|
||||
sheetTypeAFormReq fs template = multiActionA selOptions fs (classify' <$> template)
|
||||
sheetTypeAFormReq fs template = explainedMultiActionA acts opts fs (classify' <$> template)
|
||||
where
|
||||
selOptions = Map.fromList
|
||||
acts = Map.fromList
|
||||
[ ( Normal', Normal <$> gradingReq )
|
||||
, ( Bonus' , Bonus <$> gradingReq )
|
||||
, ( Informational', Informational <$> gradingReq )
|
||||
@ -886,6 +994,12 @@ sheetTypeAFormReq fs template = multiActionA selOptions fs (classify' <$> templa
|
||||
gradingReq = sheetGradingAFormReq (fslI MsgSheetGrading
|
||||
& setTooltip MsgSheetGradingInfo) (template >>= preview _grading)
|
||||
|
||||
opts = explainOptionList optionsFinite $ \case
|
||||
Normal' -> return $ i18n MsgSheetTypeInfoNormalLecturer
|
||||
Bonus' -> return $ i18n MsgSheetTypeInfoBonus
|
||||
Informational' -> return $ i18n MsgSheetTypeInfoInformational
|
||||
NotGraded' -> return $ i18n MsgSheetTypeInfoNotGraded
|
||||
|
||||
classify' :: SheetType -> SheetType'
|
||||
classify' = \case
|
||||
Bonus {} -> Bonus'
|
||||
@ -894,15 +1008,19 @@ sheetTypeAFormReq fs template = multiActionA selOptions fs (classify' <$> templa
|
||||
NotGraded -> NotGraded'
|
||||
|
||||
sheetGroupAFormReq :: FieldSettings UniWorX -> Maybe SheetGroup -> AForm Handler SheetGroup
|
||||
sheetGroupAFormReq fs template = multiActionA selOptions fs (classify' <$> template)
|
||||
sheetGroupAFormReq fs template = explainedMultiActionA acts opts fs (classify' <$> template)
|
||||
where
|
||||
selOptions = Map.fromList
|
||||
acts = Map.fromList
|
||||
[ ( Arbitrary', Arbitrary
|
||||
<$> apreq (natFieldI MsgGroupSizeNotNatural) (fslI MsgSheetGroupMaxGroupsize & noValidate) (preview _maxParticipants =<< template)
|
||||
)
|
||||
, ( RegisteredGroups', pure RegisteredGroups )
|
||||
, ( NoGroups', pure NoGroups )
|
||||
]
|
||||
opts = explainOptionList optionsFinite $ \case
|
||||
Arbitrary' -> return $(i18nWidgetFile "sheet-grouping-explanation/arbitrary")
|
||||
RegisteredGroups' -> return $(i18nWidgetFile "sheet-grouping-explanation/registered")
|
||||
NoGroups' -> mzero
|
||||
classify' :: SheetGroup -> SheetGroup'
|
||||
classify' = \case
|
||||
Arbitrary _ -> Arbitrary'
|
||||
@ -1516,47 +1634,6 @@ csvOptionsForm mPrev = hoistAForm liftHandler $ CsvOptions
|
||||
<$> csvFormatOptionsForm (fslI MsgCsvFormatOptions & setTooltip MsgCsvOptionsTip) (csvFormat <$> mPrev)
|
||||
<*> apopt checkBoxField (fslI MsgCsvTimestamp & setTooltip MsgCsvTimestampTip) (csvTimestamp <$> mPrev)
|
||||
|
||||
|
||||
explainedSelectionField :: forall m a.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
, Eq a
|
||||
)
|
||||
=> Maybe (SomeMessage UniWorX, Maybe Widget) -- ^ Label for none option
|
||||
-> Handler ([(Option a, Maybe Widget)], Text -> Maybe a)
|
||||
-> Field m a
|
||||
explainedSelectionField optMsg' mkOpts = Field{..}
|
||||
where
|
||||
fieldEnctype = UrlEncoded
|
||||
fieldParse ts _ = do
|
||||
(_, parser) <- liftHandler mkOpts
|
||||
if
|
||||
| t : _ <- ts
|
||||
, Just t' <- parser t
|
||||
-> return . Right $ Just t'
|
||||
| t : _ <- ts
|
||||
, null t
|
||||
-> return $ Right Nothing
|
||||
| t : _ <- ts
|
||||
-> return . Left . SomeMessage $ MsgInvalidEntry t
|
||||
| otherwise
|
||||
-> return $ Right Nothing
|
||||
fieldView theId name attrs val isReq = do
|
||||
(opts, _) <- liftHandler mkOpts
|
||||
let optMsg = guardOnM (not isReq) optMsg'
|
||||
inputId optExternal = [st|#{theId}__input--#{optExternal}|]
|
||||
matchesVal Nothing = is _Left val
|
||||
matchesVal (Just x) = val == Right x
|
||||
$(widgetFile "widgets/explained-selection-field")
|
||||
|
||||
explainOptionList :: forall a.
|
||||
Handler (OptionList a)
|
||||
-> (a -> MaybeT Handler Widget)
|
||||
-> Handler ([(Option a, Maybe Widget)], Text -> Maybe a)
|
||||
explainOptionList ol mkExplanation = do
|
||||
OptionList{..} <- ol
|
||||
olOptions' <- forM olOptions $ \opt@Option{..} -> (opt, ) <$> runMaybeT (mkExplanation optionInternalValue)
|
||||
return (olOptions', olReadExternal)
|
||||
|
||||
courseSelectForm :: forall ident handler.
|
||||
( PathPiece ident
|
||||
|
||||
@ -71,6 +71,7 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä
|
||||
| AuthRated
|
||||
| AuthUserSubmissions
|
||||
| AuthCorrectorSubmissions
|
||||
| AuthSubmissionGroup
|
||||
| AuthCapacity
|
||||
| AuthRegisterGroup
|
||||
| AuthEmpty
|
||||
|
||||
@ -208,9 +208,9 @@ deriveJSON defaultOptions
|
||||
}''UploadMode
|
||||
derivePersistFieldJSON ''UploadMode
|
||||
|
||||
data UploadModeDescr = UploadModeNone
|
||||
| UploadModeAny
|
||||
data UploadModeDescr = UploadModeAny
|
||||
| UploadModeSpecific
|
||||
| UploadModeNone
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
instance Universe UploadModeDescr
|
||||
instance Finite UploadModeDescr
|
||||
@ -237,10 +237,10 @@ deriveJSON defaultOptions
|
||||
} ''SubmissionMode
|
||||
derivePersistFieldJSON ''SubmissionMode
|
||||
|
||||
data SubmissionModeDescr = SubmissionModeNone
|
||||
| SubmissionModeCorrector
|
||||
data SubmissionModeDescr = SubmissionModeCorrector
|
||||
| SubmissionModeUser
|
||||
| SubmissionModeBoth
|
||||
| SubmissionModeNone
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
instance Universe SubmissionModeDescr
|
||||
instance Finite SubmissionModeDescr
|
||||
|
||||
@ -165,6 +165,15 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
||||
_{MsgCourseApplicationTemplateApplication}
|
||||
$else
|
||||
_{MsgCourseApplicationTemplateRegistration}
|
||||
$maybe mGroup <- submissionGroup
|
||||
<dt .deflist__dt>
|
||||
_{MsgSubmissionGroup}
|
||||
<dd .deflist__dd>
|
||||
$maybe groupName <- mGroup
|
||||
#{groupName}
|
||||
$nothing
|
||||
<i>
|
||||
_{MsgNoSubmissionGroup}
|
||||
$if registrationOpen || isJust registration
|
||||
<dt .deflist__dt>
|
||||
_{MsgCourseRegistration}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
$newline never
|
||||
|
||||
Teilnehmer dürfen bei Anlegen der Abgabe selbstständig ihre #
|
||||
Gruppenmitglieder angeben.
|
||||
|
||||
<br>
|
||||
|
||||
Teilnehmer haben hierzu die Möglichkeit Einladungen per E-Mail #
|
||||
verschicken zu lassen und können andere Teilnehmer, mit denen sie #
|
||||
bereits einmal zuvor in diesem Kurs abgegeben haben, auch direkt #
|
||||
angeben.
|
||||
|
||||
<br>
|
||||
|
||||
Es kann eine maximale Gruppengröße festgelegt werden.
|
||||
|
||||
<br>
|
||||
|
||||
Teilnehmer müssen nicht notwendigerweise in den gleichen arbiträren #
|
||||
Gruppen abgeben wie für ein vorhergehendes Übungsblatt.
|
||||
@ -0,0 +1,18 @@
|
||||
$newline never
|
||||
|
||||
Participants may freely specify their group members when submitting.
|
||||
|
||||
<br>
|
||||
|
||||
Participants can have Uni2work send invitations via email or specify #
|
||||
other participants directly if they have submitted together before in #
|
||||
this course.
|
||||
|
||||
<br>
|
||||
|
||||
A maximum group size may be specified.
|
||||
|
||||
<br>
|
||||
|
||||
Participants need not submit in the same groups as for any previous #
|
||||
exercise sheet.
|
||||
@ -0,0 +1,18 @@
|
||||
$newline never
|
||||
|
||||
Nur Teilnehmer, die, auf der Liste der Kursteilnehmer, einer festen #
|
||||
Abgabegruppe zugewiesen wurden, können abgeben. #
|
||||
|
||||
Teilnehmer können stets nur in maximal einer festen Abgabegruppe #
|
||||
gleichzeitig sein.
|
||||
|
||||
<br>
|
||||
|
||||
Es kann stets nur für alle aktuellen Mitglieder der eigenen #
|
||||
Abgabegruppe abgegeben werden.
|
||||
|
||||
<br>
|
||||
|
||||
Wird die Zuordnung von Teilnehmern auf feste Abgabegruppen nach einer #
|
||||
erfolgten Abgabe noch angepasst, hat dies keinen Einfluss auf die #
|
||||
Abgebenden dieser konkreten Abgabe.
|
||||
@ -0,0 +1,23 @@
|
||||
$newline never
|
||||
|
||||
Only participants that are members of a registered submission group #
|
||||
may submit. #
|
||||
|
||||
Membership in registered submission groups can be configured from the #
|
||||
list of course participants. #
|
||||
|
||||
Participants can only be a member in at most one submission group at a #
|
||||
time.
|
||||
|
||||
<br>
|
||||
|
||||
Participants can only submit for their entire submission group at #
|
||||
once.
|
||||
|
||||
<br>
|
||||
|
||||
If submission group memberships are edited after a submission has been #
|
||||
made existing associations between participants and their submissions #
|
||||
remain unchanged. #
|
||||
|
||||
New submission group members are not added to existing submissions.
|
||||
@ -0,0 +1,25 @@
|
||||
$newline never
|
||||
|
||||
Teilnehmer können sich von Uni2work ein Pseudonym zufällig wählen #
|
||||
lassen (das Pseudonym ist spezifisch für dieses Übungsblatt).
|
||||
|
||||
<br>
|
||||
|
||||
Es ist dann vorgesehen, dass die Teilnehmer ihre Abgabe in einem #
|
||||
externen System (z.B. auf Papier) nur mit ihrem Pseudonym #
|
||||
kennzeichnen. #
|
||||
|
||||
So kann die Anonymität der Korrektor bewahrt werden.
|
||||
|
||||
<br>
|
||||
|
||||
Korrektoren können dann anhand der ihnen auf den Abgaben übermittelten #
|
||||
Pseudonyme die Abgaben in Uni2work registrieren.
|
||||
|
||||
<br>
|
||||
|
||||
Für die so registrierten Abgaben kann der jeweilige Korrektor dann #
|
||||
ganz regulär Punkte und Feedback geben. #
|
||||
|
||||
Die Anrechnung kann genauso wie bei Abgaben direkt in Uni2work (also, #
|
||||
wenn gewünscht, mit automatischer Berechnung) erfolgen.
|
||||
@ -0,0 +1,25 @@
|
||||
$newline never
|
||||
|
||||
Participants can have Uni2work choose a random pseudonym for them. #
|
||||
|
||||
Pseudonyms are only valid in the context of a single exercise sheet.
|
||||
|
||||
<br>
|
||||
|
||||
It is then expected that participants submit via some external system #
|
||||
(e.g. on paper) identifying themselves only by their pseudonym. #
|
||||
|
||||
Thus submission correction can remain anonymous.
|
||||
|
||||
<br>
|
||||
|
||||
Correctors can, given the pseudonyms on their submissions, register #
|
||||
the submissions in Uni2work.
|
||||
|
||||
<br>
|
||||
|
||||
Correctors can give feedback for and rate these submissions like for #
|
||||
submissions created by any other method. #
|
||||
|
||||
Ratings can thus be included exam bonuses as usual (either manually or #
|
||||
automatically).
|
||||
@ -0,0 +1,20 @@
|
||||
$newline never
|
||||
|
||||
Teilnehmer können Abgaben direkt in Uni2work anlegen und dabei #
|
||||
(je nach Einstellung unter „Abgabe von Dateien“) auch Dateien #
|
||||
hochladen.
|
||||
|
||||
<br>
|
||||
|
||||
Die Abgaben werden dann von einem Kursverwalter oder automatisiert auf #
|
||||
die eingestellten Korrektoren verteilt.
|
||||
|
||||
<br>
|
||||
|
||||
Diese haben dann die Möglichkeit Punkte (je nach Einstellung unter #
|
||||
„Bewertung“) und Text-Feedback für die Abgaben zu vergeben. #
|
||||
|
||||
Für Feedback-Zwecke können etwaige hochgeladene Dateien beliebig #
|
||||
angepasst oder neue Dateien hinterlegt werden (es werden dabei sowohl #
|
||||
die originale als auch die durch die Korrektur veränderte Version #
|
||||
permanent gespeichert).
|
||||
20
templates/i18n/submission-mode-explanation/user/en-eu.hamlet
Normal file
20
templates/i18n/submission-mode-explanation/user/en-eu.hamlet
Normal file
@ -0,0 +1,20 @@
|
||||
$newline never
|
||||
|
||||
Participants may create submissions directly in Uni2work and may be #
|
||||
permitted to upload files (“Submission of files”).
|
||||
|
||||
<br>
|
||||
|
||||
Submissions are distributed to correctors by course administrators #
|
||||
either manually or automatically.
|
||||
|
||||
<br>
|
||||
|
||||
Correctors may then grade their corrections (“Marking”) and give #
|
||||
text feedback. #
|
||||
|
||||
For feedback purposes uploaded files (if any) may be freely edited and #
|
||||
new files may be uploaded. #
|
||||
|
||||
Both the original and any changed versions of files are saved #
|
||||
permanently.
|
||||
Loading…
Reference in New Issue
Block a user