Merge branch 'master' into workflows

This commit is contained in:
Gregor Kleen 2020-10-13 14:31:48 +02:00
commit 6cd9f9bbfd
56 changed files with 758 additions and 75 deletions

View File

@ -2,6 +2,34 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [20.11.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.10.0...v20.11.0) (2020-10-13)
### Features
* **allocations:** allocation-course-accept-substitutes ([8abcd65](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8abcd65edf2a1bf5b6de62103af7427fa7ed7db3))
* **authorisation:** cookie-active-auth-tags ([0d372c6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0d372c636a735b4003448ab2518f6354b08ca042))
### Bug Fixes
* **changelog:** try not to crash on unknown changelog items ([850c8d4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/850c8d4dae47489e0dbf0eb46276eaf0002bf123))
## [20.10.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.9.0...v20.10.0) (2020-10-12)
### Features
* **allocations:** ui for adding applicants ([7b7f11e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7b7f11e72853e11717c671d434397c707eff3b7f))
## [20.9.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.8.1...v20.9.0) (2020-10-12)
### Features
* **exams:** auth ExamResults by ExamExamOfficeSchools ([29a3e24](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/29a3e24bcf01cd9c893857eda00dcd249e6cbbe2))
* **exams:** exam staff & additional schools ([94436ee](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/94436ee0e1ce2cbf13a66f9ad81883d7286acb9b))
### [20.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.8.0...v20.8.1) (2020-10-12) ### [20.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.8.0...v20.8.1) (2020-10-12)

View File

@ -196,6 +196,11 @@ cookies:
same-site: lax same-site: lax
http-only: false http-only: false
secure: "_env:COOKIES_SECURE:true" secure: "_env:COOKIES_SECURE:true"
ACTIVE-AUTH-TAGS:
expires: 12622780800
same-site: lax
http-only: true
secure: "_env:COOKIES_SECURE:true"
user-defaults: user-defaults:
max-favourites: 12 max-favourites: 12

View File

@ -776,10 +776,13 @@ section
.allocation__courses .allocation__courses
margin: 20px 0 0 40px margin: 20px 0 0 40px
.form-group__input > &
margin: 0
.allocation-course .allocation-course
display: grid display: grid
grid-template-columns: minmax(105px, 1fr) 9fr grid-template-columns: minmax(105px, 1fr) 9fr
grid-template-areas: 'name name ' '. registered ' 'prio-label prio ' 'instr-label instr ' 'form-label form ' grid-template-areas: 'name name' '. admin-info' '. registered' 'prio-label prio' 'instr-label instr' 'form-label form'
grid-gap: 5px 7px grid-gap: 5px 7px
margin: 12px 0 margin: 12px 0
padding: 0 10px 12px 7px padding: 0 10px 12px 7px
@ -830,10 +833,14 @@ section
text-align: right text-align: right
padding-top: 6px padding-top: 6px
.allocation-course__admin-info
@extend .explanation
grid-area: admin-info
@media (max-width: 426px) @media (max-width: 426px)
.allocation-course .allocation-course
grid-template-columns: 1fr grid-template-columns: 1fr
grid-template-areas: 'name ' 'registered ' 'prio-label ' 'prio ' 'instr-label' 'instr ' 'form-label ' 'form ' grid-template-areas: 'name' 'admin-info' 'registered' 'prio-label' 'prio' 'instr-label' 'instr' 'form-label' 'form'
.allocation-course__application-label .allocation-course__application-label
padding-top: 0 padding-top: 0

View File

@ -204,6 +204,8 @@ CourseAllocationOption term@Text name@Text: #{name} (#{term})
CourseAllocationMinCapacity: Minimale Teilnehmeranzahl CourseAllocationMinCapacity: Minimale Teilnehmeranzahl
CourseAllocationMinCapacityTip: Wenn der Veranstaltung bei der Zentralanmeldung weniger als diese Anzahl von Teilnehmern zugeteilt würden, werden diese stattdessen auf andere Kurse umverteilt CourseAllocationMinCapacityTip: Wenn der Veranstaltung bei der Zentralanmeldung weniger als diese Anzahl von Teilnehmern zugeteilt würden, werden diese stattdessen auf andere Kurse umverteilt
CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein
CourseAllocationCourseAcceptsSubstitutesUntil: Akzeptiert Nachrücker bis
CourseAllocationCourseAcceptsSubstitutesNever: Akzeptiert keine Nachrücker
CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung
CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
CourseApplicationTemplate: Bewerbungsvorlagen CourseApplicationTemplate: Bewerbungsvorlagen
@ -804,6 +806,8 @@ PersonalInfoExamAchievementsWip: Die Anzeige von Prüfungsergebnissen wird momen
PersonalInfoOwnTutorialsWip: Die Anzeige von Tutorien, zu denen Sie als Tutor eingetragen sind wird momentan an dieser Stelle leider noch nicht unterstützt. PersonalInfoOwnTutorialsWip: Die Anzeige von Tutorien, zu denen Sie als Tutor eingetragen sind wird momentan an dieser Stelle leider noch nicht unterstützt.
PersonalInfoTutorialsWip: Die Anzeige von Tutorien, zu denen Sie angemeldet sind wird momentan an dieser Stelle leider noch nicht unterstützt. PersonalInfoTutorialsWip: Die Anzeige von Tutorien, zu denen Sie angemeldet sind wird momentan an dieser Stelle leider noch nicht unterstützt.
ActiveAuthTagsSaveCookie: In Cookie speichern?
ActiveAuthTagsSaveCookieTip: Falls gesetzt werden die aktivierten Authorisierungsprädikate zusätzlich zur aktiven Session auch in einem persistenten Cookie gespeichert. Dies kann vor Allem in Kombination mit Tab-Containern nützlich sein.
ActiveAuthTags: Aktivierte Authorisierungsprädikate ActiveAuthTags: Aktivierte Authorisierungsprädikate
InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet
@ -1376,6 +1380,7 @@ MenuAllocationUsers: Bewerber
MenuAllocationPriorities: Zentrale Dringlichkeiten MenuAllocationPriorities: Zentrale Dringlichkeiten
MenuAllocationCompute: Platzvergabe berechnen MenuAllocationCompute: Platzvergabe berechnen
MenuAllocationAccept: Platzvergabe akzeptieren MenuAllocationAccept: Platzvergabe akzeptieren
MenuAllocationAddUser: Bewerber hinzufügen
MenuFaq: FAQ MenuFaq: FAQ
MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen
MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
@ -1463,6 +1468,7 @@ BreadcrumbAllocationUsers: Bewerber
BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten
BreadcrumbAllocationCompute: Platzvergabe berechnen BreadcrumbAllocationCompute: Platzvergabe berechnen
BreadcrumbAllocationAccept: Platzvergabe akzeptieren BreadcrumbAllocationAccept: Platzvergabe akzeptieren
BreadcrumbAllocationAddUser: Bewerber hinzufügen
BreadcrumbMessageHide: Verstecken BreadcrumbMessageHide: Verstecken
BreadcrumbFaq: FAQ BreadcrumbFaq: FAQ
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
@ -1496,9 +1502,9 @@ ExternalExamUsers coursen@CourseName examn@ExamName: Teilnehmer: #{coursen}, #{e
TitleMetrics: Metriken TitleMetrics: Metriken
AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären. Diese Einstellungen gelten nur temporär bis Ihre Sitzung abgelaufen ist, d.h. bis ihr Browser-Cookie abgelaufen ist. Durch Abwahl von Prädikaten kann man sich höchstens temporär aussperren. AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären.
AuthPredsActive: Aktive Authorisierungsprädikate AuthPredsActive: Aktive Authorisierungsprädikate
AuthPredsActiveChanged: Authorisierungseinstellungen für aktuelle Sitzung gespeichert AuthPredsActiveChanged: Authorisierungseinstellungen gespeichert
AuthTagFree: Seite ist universell zugänglich AuthTagFree: Seite ist universell zugänglich
AuthTagAdmin: Nutzer ist Administrator AuthTagAdmin: Nutzer ist Administrator
AuthTagExamOffice: Nutzer ist mit Prüfungsverwaltung beauftragt AuthTagExamOffice: Nutzer ist mit Prüfungsverwaltung beauftragt
@ -1867,6 +1873,11 @@ ExamRoomDescription: Beschreibung
ExamTimeTip: Nur zur Information der Studierenden, die tatsächliche Zeitangabe erfolgt pro Prüfungstermin/Raum ExamTimeTip: Nur zur Information der Studierenden, die tatsächliche Zeitangabe erfolgt pro Prüfungstermin/Raum
ExamRoomAssigned: Zugeteilt ExamRoomAssigned: Zugeteilt
ExamRoomRegistered: Anmeldung ExamRoomRegistered: Anmeldung
ExamStaff: Prüfer/Verantwortliche Hochschullehrer
ExamStaffTip: Geben Sie bitte in jedem Fall einen Namen an, der den Prüfer/Veranstalter/Verantwortlichen Hochschullehrer eindeutig identifiziert! Sollte der Name des Prüfers allein womöglich nicht eindeutig sein, so geben Sie bitte eindeutig identifizierende Zusatzinfos, wie beispielsweise den Lehrstuhl bzw. die LFE o.Ä., an.
ExamStaffRequired: „Prüfer/Verantwortilche Hochschullehrer” muss angegeben werden
ExamExamOfficeSchools: Zusätzliche Institute
ExamExamOfficeSchoolsTip: Prüfungsbeauftragte von Instituten, die Sie hier angeben, erhalten im System (zusätzlich zum primären Institut des zugehörigen Kurses) volle Einsicht in sämtliche für diese Prüfung hinterlegten Leistungen, unabhängig von den Studiendaten der Teilnehmer.
ExamOccurrenceStart: Prüfungsbeginn ExamOccurrenceStart: Prüfungsbeginn
@ -1876,6 +1887,7 @@ ExamFormAutomaticFunctions: Automatische Funktionen
ExamFormCorrection: Korrektur ExamFormCorrection: Korrektur
ExamFormParts: Teile ExamFormParts: Teile
ExamFormMode: Ausgestaltung der Prüfung ExamFormMode: Ausgestaltung der Prüfung
ExamFormGrades: Prüfungsleistungen
ExamModeFormNone: Keine Angabe ExamModeFormNone: Keine Angabe
ExamModeFormCustom: Benutzerdefiniert ExamModeFormCustom: Benutzerdefiniert
@ -2290,6 +2302,8 @@ AllocationNotificationNewCourseSuccessForceOff: Sie werden nicht benachrichtigt,
AllocationNotificationNewCourseCurrentlyOff: Aktuell würden Sie keine Benachrichtigung erhalten. AllocationNotificationNewCourseCurrentlyOff: Aktuell würden Sie keine Benachrichtigung erhalten.
AllocationNotificationNewCourseCurrentlyOn: Aktuell würden Sie benachrichtigt werden. AllocationNotificationNewCourseCurrentlyOn: Aktuell würden Sie benachrichtigt werden.
AllocationNotificationLoginFirst: Um Ihre Benachrichtigungseinstellungen zu ändern, loggen Sie sich bitte zunächst ein. AllocationNotificationLoginFirst: Um Ihre Benachrichtigungseinstellungen zu ändern, loggen Sie sich bitte zunächst ein.
AllocationNextSubstitutesDeadline: Nächster Kurs akzeptiert Nachrücker bis
AllocationNextSubstitutesDeadlineNever: Keine Kurse akzeptieren mehr Nachrücker
AllocationSchoolShort: Institut AllocationSchoolShort: Institut
Allocation: Zentralanmeldung Allocation: Zentralanmeldung
@ -2529,6 +2543,8 @@ CourseDeregistrationAllocationReason: Grund
CourseDeregistrationAllocationReasonTip: Der angegebene Grund wird permanent im System hinterlegt und ist i.A. einziger Anhaltspunkt zur Schlichtung etwaiger Konflikte CourseDeregistrationAllocationReasonTip: Der angegebene Grund wird permanent im System hinterlegt und ist i.A. einziger Anhaltspunkt zur Schlichtung etwaiger Konflikte
CourseDeregistrationAllocationNoShow: „Nicht erschienen“ eintragen CourseDeregistrationAllocationNoShow: „Nicht erschienen“ eintragen
CourseDeregistrationAllocationNoShowTip: Soll für alle Prüfungen dieses Kurses „nicht erschienen“ als Prüfungsleistung eingetragen werden? Dies geschieht einmalig bei der Abmeldung (sofern nicht bereits eine Prüfungsleistung existiert) und automatisch beim Anlegen von neuen Prüfungen. CourseDeregistrationAllocationNoShowTip: Soll für alle Prüfungen dieses Kurses „nicht erschienen“ als Prüfungsleistung eingetragen werden? Dies geschieht einmalig bei der Abmeldung (sofern nicht bereits eine Prüfungsleistung existiert) und automatisch beim Anlegen von neuen Prüfungen.
CourseAcceptSubstitutesUntil: Nachrücker akzeptieren bis
CourseAcceptSubstitutesUntilTip: Bis zu welchem Zeitpunkt sollen durch die Zentralanmeldung Nachrücker diesem Kurs zugewiesen werden? Wird kein Datum angegeben werden nach der Initialen Verteilung nie Nachrücker zugewiesen. Diese Frist sollte nicht willkürlich früh bzw. nicht gesetzt werden, um für die Studierenden keine unnötige Beschränkung darzustellen. Geeignet ist z.B. bei einem Seminar wenige Stunden vor dem ersten Treffen zum Verteilen der Themen.
CourseDeregisterNoShow: „Nicht erschienen“ bei Abmeldung CourseDeregisterNoShow: „Nicht erschienen“ bei Abmeldung
CourseDeregisterNoShowTip: Soll, wenn sich Teilnehmer selbstständig abmelden, für alle Prüfungen dieses Kurses „nicht erschienen“ als Prüfungsleistung eingetragen werden? Dies geschieht einmalig bei der Abmeldung (sofern nicht bereits eine Prüfungsleistung existiert) und automatisch beim Anlegen von neuen Prüfungen. CourseDeregisterNoShowTip: Soll, wenn sich Teilnehmer selbstständig abmelden, für alle Prüfungen dieses Kurses „nicht erschienen“ als Prüfungsleistung eingetragen werden? Dies geschieht einmalig bei der Abmeldung (sofern nicht bereits eine Prüfungsleistung existiert) und automatisch beim Anlegen von neuen Prüfungen.
CourseDeregistrationAllocationShouldLog: Selbstverschuldet CourseDeregistrationAllocationShouldLog: Selbstverschuldet
@ -2540,6 +2556,7 @@ AllocationResultsLecturer: Im Rahmen der oben genannten Zentralanmeldung wurden
AllocationResultLecturer csh@CourseShorthand count@Int64 count2@Int64: #{count} Teilnehmer (von insgesamt #{count2}) für #{csh} AllocationResultLecturer csh@CourseShorthand count@Int64 count2@Int64: #{count} Teilnehmer (von insgesamt #{count2}) für #{csh}
AllocationResultLecturerAll csh@CourseShorthand count@Int64: #{count} Teilnehmer für #{csh} AllocationResultLecturerAll csh@CourseShorthand count@Int64: #{count} Teilnehmer für #{csh}
AllocationResultLecturerNone csh@CourseShorthand: Keine Teilnehmer für #{csh} AllocationResultLecturerNone csh@CourseShorthand: Keine Teilnehmer für #{csh}
AllocationResultsLecturerSubstituteCoursesWarning: Bitte konfigurieren Sie so bald wie möglich einen Zeitrahmen in dem Sie bereit sind etwaige Nachrücker in den folgenden Kursen zu akzeptieren:
AllocationResultsStudent: Sie haben Plätze erhalten in: AllocationResultsStudent: Sie haben Plätze erhalten in:
AllocationNoResultsStudent: Sie haben leider keine Plätze erhalten. AllocationNoResultsStudent: Sie haben leider keine Plätze erhalten.
AllocationResultStudent csh@CourseShorthand: Sie haben einen Platz in #{csh} erhalten. AllocationResultStudent csh@CourseShorthand: Sie haben einen Platz in #{csh} erhalten.
@ -2736,12 +2753,34 @@ AllocationUsersCsvName tid@TermId ssh@SchoolId ash@AllocationShorthand: #{foldCa
AllocationPrioritiesMode: Modus AllocationPrioritiesMode: Modus
AllocationPrioritiesNumeric: Numerische Dringlichkeiten AllocationPrioritiesNumeric: Numerische Dringlichkeiten
AllocationPrioritiesOrdinal: Dringlichkeiten durch Sortierung AllocationPrioritiesOrdinal: Dringlichkeiten durch Sortierung
AllocationPriorityNumeric': Numerisch
AllocationPriorityOrdinal': Nach Sortierung
AllocationPriorityNumericValues: Numerische Werte
AllocationPriorityNumericValuesTip: Komma-separierte ganze Zahlen
AllocationPriorityNumericNoValues: Es wurden keine numerischen Werte angegeben
AllocationPriorityNumericNoParse val@Text: Ganze Zahl konnte nicht geparst werden: „#{val}“
AllocationPriorityOrdinalValueNegative: Sortier-Index darf nicht negativ sein
AllocationPriorityOrdinalValue: Sortier-Index
AllocationPriorityOrdinalValueTip: Null entspricht dem ersten Eintrag der Liste, höhere Indizes entsprechen später in der sortierten Liste vorkommenden Bewerbern und damit einer höheren Dringlichkeit
AllocationPrioritiesTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Zentrale Dringlichkeiten AllocationPrioritiesTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Zentrale Dringlichkeiten
AllocationPrioritiesFile: CSV-Datei AllocationPrioritiesFile: CSV-Datei
AllocationPrioritiesSunk num@Int64: Zentrale Prioritäten für #{num} Bewerber erfolgreich hinterlegt AllocationPrioritiesSunk num@Int64: Zentrale Prioritäten für #{num} Bewerber erfolgreich hinterlegt
AllocationPrioritiesMissing num@Int64: Für #{num} Bewerber ist keine zentrale Priorität hinterlegt, da in der hochgeladenen CSV-Datei die #{pluralDE num "entsprechende Matrikelnummer" "entsprechenden Matrikelnummern"} nicht gefunden #{pluralDE num "wurde" "wurden"} AllocationPrioritiesMissing num@Int64: Für #{num} Bewerber ist keine zentrale Priorität hinterlegt, da in der hochgeladenen CSV-Datei die #{pluralDE num "entsprechende Matrikelnummer" "entsprechenden Matrikelnummern"} nicht gefunden #{pluralDE num "wurde" "wurden"}
AllocationMissingPrioritiesIgnored: Bewerber, für die keine zentrale Priorität angegeben wird, werden bei der Vergabe ignoriert! AllocationMissingPrioritiesIgnored: Bewerber, für die keine zentrale Priorität angegeben wird, werden bei der Vergabe ignoriert!
AllocationAddUserUserNotFound: E-Mail Adresse konnte keinem Benutzer zugeordnet werden
AllocationAddUserUser: Benutzer
AllocationAddUserUserPlaceholder: E-Mail
AllocationAddUserTotalCoursesLessThanOne: Anzahl angefragter Plätze muss größer null sein
AllocationAddUserTotalCourses: Angefragte Plätze
AllocationAddUserSetPriority: Zentrale Dringlichkeit eintragen?
AllocationAddUserPriority: Zentrale Dringlichkeit
AllocationAddUserApplications: Bewerbungen/Bewertungen
AllocationAddUserTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'} - #{allocation}: Bewerber hinzufügen
AllocationAddUserShortTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Bewerber hinzufügen
AllocationAddUserUserAdded: Bewerber erfolgreich zur Zentralanmeldung hinzugefügt
AllocationAddUserUserExists: Der angegebene Benutzer ist bereits ein Bewerber zur Zentralanmeldung
ExampleUser1FirstName: Max ZweiterName ExampleUser1FirstName: Max ZweiterName
ExampleUser1Surname: Mustermann ExampleUser1Surname: Mustermann
ExampleUser1DisplayName: Max Mustermann ExampleUser1DisplayName: Max Mustermann
@ -2757,6 +2796,9 @@ AllocationUsersMissingPrioritiesTip: Es muss sichergestellt sein, dass keine Tei
AllocationUsersMissingPrioritiesOk: Es wurde sichergestellt, dass es für jeden der genannten Benutzer einen zulässigen Grund gibt, warum dieser nicht an der Zentralanmeldung teilnehmen sollte. AllocationUsersMissingPrioritiesOk: Es wurde sichergestellt, dass es für jeden der genannten Benutzer einen zulässigen Grund gibt, warum dieser nicht an der Zentralanmeldung teilnehmen sollte.
AllocationRestrictCourses: Kurse einschränken AllocationRestrictCourses: Kurse einschränken
AllocationRestrictCoursesTip: Sollen nur Plätze für eine Teilmenge von Kursen zugewiesen werden? So können u.A. Nachrücker verteilt werden. Diese Funktionalität sollte nur verwendet werden, wenn manche Kurse aus zulässigen Gründen ausgeschlossen werden müssen; z.B. weil ein Seminar bereits ein Treffen zur Organisation hatte und nun keine weiteren Teilnehmer mehr akzeptieren kann. AllocationRestrictCoursesTip: Sollen nur Plätze für eine Teilmenge von Kursen zugewiesen werden? So können u.A. Nachrücker verteilt werden. Diese Funktionalität sollte nur verwendet werden, wenn manche Kurse aus zulässigen Gründen ausgeschlossen werden müssen; z.B. weil ein Seminar bereits ein Treffen zur Organisation hatte und nun keine weiteren Teilnehmer mehr akzeptieren kann.
AllocationCourseRestrictionNone: Nicht einschränken
AllocationCourseRestrictionSubstitutes: Kurse, die aktuell Nachrücker azkeptieren
AllocationCourseRestrictionCustom: Benutzerdefiniert
AllocationRestrictCoursesSelection: Kurse AllocationRestrictCoursesSelection: Kurse
AllocationRestrictCoursesSelectionTip: Teilnehmer werden nur auf die Kurse verteilt, die hier angegeben werden. AllocationRestrictCoursesSelectionTip: Teilnehmer werden nur auf die Kurse verteilt, die hier angegeben werden.
AllocationUsersMissingPrioritiesNotOk: Zentralvergabe kann nicht erfolgen, solange nicht allen Teilnehmern, die nicht explizit von der Vergabe ausgeschlossen wurden („Teilnehmer ohne zentrale Dringlichkeit”), eine zentrale Dringlichkeit zugewiesen wurde! AllocationUsersMissingPrioritiesNotOk: Zentralvergabe kann nicht erfolgen, solange nicht allen Teilnehmern, die nicht explizit von der Vergabe ausgeschlossen wurden („Teilnehmer ohne zentrale Dringlichkeit”), eine zentrale Dringlichkeit zugewiesen wurde!
@ -2774,6 +2816,7 @@ AllocationOfferedPlaces: Angebotene Plätze
AllocationUserNewMatches: Neue Zuteilungen AllocationUserNewMatches: Neue Zuteilungen
AllocationUsersCount: Teilnehmer AllocationUsersCount: Teilnehmer
AllocationCoursesCount: Kurse AllocationCoursesCount: Kurse
AllocationCourseEligible: Berücksichtigt
CourseOption tid@TermId ssh@SchoolId coursen@CourseName: #{tid} - #{ssh} - #{coursen} CourseOption tid@TermId ssh@SchoolId coursen@CourseName: #{tid} - #{ssh} - #{coursen}

View File

@ -204,6 +204,8 @@ CourseAllocationOption term name: #{name} (#{term})
CourseAllocationMinCapacity: Minimum number of participants CourseAllocationMinCapacity: Minimum number of participants
CourseAllocationMinCapacityTip: If fewer students than this number were to be assigned to this course, then these students would instead be assigned to other courses CourseAllocationMinCapacityTip: If fewer students than this number were to be assigned to this course, then these students would instead be assigned to other courses
CourseAllocationMinCapacityMustBeNonNegative: Minimum number of participants must not be negative CourseAllocationMinCapacityMustBeNonNegative: Minimum number of participants must not be negative
CourseAllocationCourseAcceptsSubstitutesUntil: Accepts substitutes until
CourseAllocationCourseAcceptsSubstitutesNever: Does not accept substitutes
CourseApplicationInstructions: Instructions for application CourseApplicationInstructions: Instructions for application
CourseApplicationInstructionsTip: Will be shown to students if they decide to apply for this course CourseApplicationInstructionsTip: Will be shown to students if they decide to apply for this course
CourseApplicationTemplate: Application template CourseApplicationTemplate: Application template
@ -801,6 +803,9 @@ PersonalInfoExamAchievementsWip: The feature to display your exam achievements h
PersonalInfoOwnTutorialsWip: The feature to display tutorials you have been assigned to as tutor has not yet been implemented. PersonalInfoOwnTutorialsWip: The feature to display tutorials you have been assigned to as tutor has not yet been implemented.
PersonalInfoTutorialsWip: The feature to display tutorials you have registered for has not yet been implemented. PersonalInfoTutorialsWip: The feature to display tutorials you have registered for has not yet been implemented.
ActiveAuthTagsSaveCookie: Save in cookie?
ActiveAuthTagsSaveCookieTip: Should the configuration of active authorisation predicates be additionally saved in a persistent cookie? This may be especially useful if using container tabs.
ActiveAuthTags: Active authorisation predicates ActiveAuthTags: Active authorisation predicates
InvalidDateTimeFormat: Invalid date and time format. YYYY-MM-DDTHH:MM[:SS] expected InvalidDateTimeFormat: Invalid date and time format. YYYY-MM-DDTHH:MM[:SS] expected
@ -1376,6 +1381,7 @@ MenuAllocationUsers: Applicants
MenuAllocationPriorities: Central priorities MenuAllocationPriorities: Central priorities
MenuAllocationCompute: Compute allocation MenuAllocationCompute: Compute allocation
MenuAllocationAccept: Accept allocation MenuAllocationAccept: Accept allocation
MenuAllocationAddUser: Add applicant
MenuFaq: FAQ MenuFaq: FAQ
MenuSheetPersonalisedFiles: Download personalised sheet files MenuSheetPersonalisedFiles: Download personalised sheet files
MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files
@ -1453,6 +1459,7 @@ BreadcrumbAllocationUsers: Applicants
BreadcrumbAllocationPriorities: Central priorities BreadcrumbAllocationPriorities: Central priorities
BreadcrumbAllocationCompute: Compute allocation BreadcrumbAllocationCompute: Compute allocation
BreadcrumbAllocationAccept: Accept allocation BreadcrumbAllocationAccept: Accept allocation
BreadcrumbAllocationAddUser: Add applicant
BreadcrumbMessageHide: Hide BreadcrumbMessageHide: Hide
BreadcrumbFaq: FAQ BreadcrumbFaq: FAQ
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
@ -1470,9 +1477,9 @@ ExternalExamUsers coursen examn: Exam participants: #{coursen}, #{examn}
TitleMetrics: Metrics TitleMetrics: Metrics
AuthPredsInfo: To view their own courses like a participant would, administrators and correctors can deactivate the checking of their credentials temporarily. Disabled authorisation predicates always fail. This means that deactivated predicates are not checked to grant access where it would otherwise not be permitted. These settings are only temporary, until your session expires i.e. your browser-cookie does. By deactivating predicates you can lock yourself out temporarily, at most. AuthPredsInfo: To view their own courses like a participant would, administrators and correctors can deactivate the checking of their credentials temporarily. Disabled authorisation predicates always fail. This means that deactivated predicates are not checked to grant access where it would otherwise not be permitted.
AuthPredsActive: Active authorisation predicates AuthPredsActive: Active authorisation predicates
AuthPredsActiveChanged: Authorisation settings saved for the current session AuthPredsActiveChanged: Successfully saved authorisation settings
AuthTagFree: Page is freely accessable AuthTagFree: Page is freely accessable
AuthTagAdmin: User is administrator AuthTagAdmin: User is administrator
AuthTagExamOffice: User is part of an exam office AuthTagExamOffice: User is part of an exam office
@ -1839,6 +1846,11 @@ ExamRoomDescription: Description
ExamTimeTip: Only for informational purposes. The actual times are set for each occurrence/room ExamTimeTip: Only for informational purposes. The actual times are set for each occurrence/room
ExamRoomAssigned: Assigned ExamRoomAssigned: Assigned
ExamRoomRegistered: Registration ExamRoomRegistered: Registration
ExamStaff: Examiner/Responsible university teacher
ExamStaffTip: Please always specify a name that uniquely identifies the examiner/organiser/repsonsible university teacher! If there is a possibility that the name alone is ambiguous please also specify some additional information e.g. the professorial chair or the educational and research unit.
ExamStaffRequired: “Examiner/Responsible university teacher” must be specified
ExamExamOfficeSchools: Additional departments
ExamExamOfficeSchoolsTip: 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.
ExamOccurrenceStart: Exam starts ExamOccurrenceStart: Exam starts
@ -1848,6 +1860,7 @@ ExamFormAutomaticFunctions: Automatic functions
ExamFormCorrection: Correction ExamFormCorrection: Correction
ExamFormParts: Exam parts ExamFormParts: Exam parts
ExamFormMode: Exam design ExamFormMode: Exam design
ExamFormGrades: Exam achievements
ExamModeFormNone: Not specified ExamModeFormNone: Not specified
ExamModeFormCustom: Custom ExamModeFormCustom: Custom
@ -2262,6 +2275,8 @@ AllocationNotificationNewCourseSuccessForceOff: You will not be notified if a ne
AllocationNotificationNewCourseCurrentlyOff: Currently you would not receive a notification. AllocationNotificationNewCourseCurrentlyOff: Currently you would not receive a notification.
AllocationNotificationNewCourseCurrentlyOn: Currently you would be notified. AllocationNotificationNewCourseCurrentlyOn: Currently you would be notified.
AllocationNotificationLoginFirst: To change your notification settings, please log in first. AllocationNotificationLoginFirst: To change your notification settings, please log in first.
AllocationNextSubstitutesDeadline: Next course accepts substitutes until
AllocationNextSubstitutesDeadlineNever: No course currently accepts substitutes
AllocationSchoolShort: Department AllocationSchoolShort: Department
Allocation: Central allocation Allocation: Central allocation
@ -2502,6 +2517,8 @@ CourseDeregistrationAllocationReason: Reason
CourseDeregistrationAllocationReasonTip: The specified reason will be permanently stored and might be the only information available during conflict resolution CourseDeregistrationAllocationReasonTip: The specified reason will be permanently stored and might be the only information available during conflict resolution
CourseDeregistrationAllocationNoShow: Record as “no show” CourseDeregistrationAllocationNoShow: Record as “no show”
CourseDeregistrationAllocationNoShowTip: Should, for all exams associated with this course, “no show” be recorded as the exam achievement automatically? This would be done once immediately (if no other achievement exists for the given exam) and automatically whenever a new exam is created. CourseDeregistrationAllocationNoShowTip: Should, for all exams associated with this course, “no show” be recorded as the exam achievement automatically? This would be done once immediately (if no other achievement exists for the given exam) and automatically whenever a new exam is created.
CourseAcceptSubstitutesUntil: Accept substitute registrations until
CourseAcceptSubstitutesUntilTip: Until which time should substitute registrations through the central allocation be accepted to fill free places in the course? If left empty no substitute registrations will be made. This deadline should not arbitrarily be set early or ommitted so as to not be an unneccesarily restrictive for students. For a seminar a valid choice might be a few hours before the first meeting in which topics will be assigned.
CourseDeregisterNoShow: Record “no show” when deregistering CourseDeregisterNoShow: Record “no show” when deregistering
CourseDeregisterNoShowTip: Should “no show” be recorded as the exam achievement for all exams associated with this course automatically whenever a course participant deregisters themselves? This would be done once upon deregistration (if no other achievement exists for the given exam) and automatically whenever a new exam is created. CourseDeregisterNoShowTip: Should “no show” be recorded as the exam achievement for all exams associated with this course automatically whenever a course participant deregisters themselves? This would be done once upon deregistration (if no other achievement exists for the given exam) and automatically whenever a new exam is created.
CourseDeregistrationAllocationShouldLog: Self imposed CourseDeregistrationAllocationShouldLog: Self imposed
@ -2513,6 +2530,7 @@ AllocationResultsLecturer: In the course of the central allocations placements h
AllocationResultLecturer csh count count2: #{count} #{pluralEN count "participant" "participants"} (of #{count2}) for #{csh} AllocationResultLecturer csh count count2: #{count} #{pluralEN count "participant" "participants"} (of #{count2}) for #{csh}
AllocationResultLecturerAll csh count: #{count} #{pluralEN count "participant" "participants"} for #{csh} AllocationResultLecturerAll csh count: #{count} #{pluralEN count "participant" "participants"} for #{csh}
AllocationResultLecturerNone csh: No participants for #{csh} AllocationResultLecturerNone csh: No participants for #{csh}
AllocationResultsLecturerSubstituteCoursesWarning: Please configure a deadline up to which you are able to accept substitute registrations for the following courses as soon as possible:
AllocationResultsStudent: You have been placed in: AllocationResultsStudent: You have been placed in:
AllocationNoResultsStudent: Unfortunately you were not placed in any courses. AllocationNoResultsStudent: Unfortunately you were not placed in any courses.
AllocationResultStudent csh: You were placed in #{csh}. AllocationResultStudent csh: You were placed in #{csh}.
@ -2709,12 +2727,34 @@ AllocationUsersCsvName tid ssh ash: #{foldCase (termToText (unTermKey tid))}-#{f
AllocationPrioritiesMode: Mode AllocationPrioritiesMode: Mode
AllocationPrioritiesNumeric: Numeric priorities AllocationPrioritiesNumeric: Numeric priorities
AllocationPrioritiesOrdinal: Priorities based on sorted list AllocationPrioritiesOrdinal: Priorities based on sorted list
AllocationPriorityNumeric': Numerical
AllocationPriorityOrdinal': Based on sorted list
AllocationPriorityNumericValues: Numerical values
AllocationPriorityNumericValuesTip: Comma separated whole numbers
AllocationPriorityNumericNoValues: No numerical values were provided
AllocationPriorityNumericNoParse val: Whole number could not be parsed: “#{val}”
AllocationPriorityOrdinalValueNegative: Sorting index may not be negative
AllocationPriorityOrdinalValue: Sorting index
AllocationPriorityOrdinalValueTip: Zero corresponds to the first entry in the list; higher indices correspond to applicants occurring later in the sorted list and thus to higher central priorities
AllocationPrioritiesTitle tid ssh ash: #{tid}-#{ssh}-#{ash}: Central priorities AllocationPrioritiesTitle tid ssh ash: #{tid}-#{ssh}-#{ash}: Central priorities
AllocationPrioritiesFile: CSV file AllocationPrioritiesFile: CSV file
AllocationPrioritiesSunk num: Successfully registered central priorities for #{num} #{pluralEN num "applicant" "applicants"} AllocationPrioritiesSunk num: Successfully registered central priorities for #{num} #{pluralEN num "applicant" "applicants"}
AllocationPrioritiesMissing num: Could not register central priorities for #{num} #{pluralEN num "applicant" "applicants"} because their matriculation was not found in the uploaded CSV file AllocationPrioritiesMissing num: Could not register central priorities for #{num} #{pluralEN num "applicant" "applicants"} because their matriculation was not found in the uploaded CSV file
AllocationMissingPrioritiesIgnored: Applicants for whom no central priority has been registered will be ignored during assignment! AllocationMissingPrioritiesIgnored: Applicants for whom no central priority has been registered will be ignored during assignment!
AllocationAddUserUserNotFound: Email could not be resolved to an user
AllocationAddUserUser: User
AllocationAddUserUserPlaceholder: Email
AllocationAddUserTotalCoursesLessThanOne: Number of requested courses needs to be greater than zero
AllocationAddUserTotalCourses: Requested courses
AllocationAddUserSetPriority: Set central priority?
AllocationAddUserPriority: Central priority
AllocationAddUserApplications: Applications/Ratings
AllocationAddUserTitle termText ssh' allocation: #{termText} - #{ssh'} - #{allocation}: Add applicant
AllocationAddUserShortTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Add applicant
AllocationAddUserUserAdded: Successfully added applicant to central allocation
AllocationAddUserUserExists: The specified user is already an applicant for the central allocation
ExampleUser1FirstName: Max SecondName ExampleUser1FirstName: Max SecondName
ExampleUser1Surname: Mustermann ExampleUser1Surname: Mustermann
ExampleUser1DisplayName: Max Mustermann ExampleUser1DisplayName: Max Mustermann
@ -2730,6 +2770,9 @@ AllocationUsersMissingPrioritiesTip: Care must be taken, that no participant is
AllocationUsersMissingPrioritiesOk: It was ensured, that all participants mentioned above, are excluded from the allocation on valid grounds. AllocationUsersMissingPrioritiesOk: It was ensured, that all participants mentioned above, are excluded from the allocation on valid grounds.
AllocationRestrictCourses: Restrict courses AllocationRestrictCourses: Restrict courses
AllocationRestrictCoursesTip: Should places be assigned only in a subset of courses? This functionality can be used to make alternate placements in the case that some participants withdraw from their assigned courses. This functionality should only be used to exclude courses on valid grounds. E.g. if a seminar already had a planning meeting and is thus unable to accept new participants. AllocationRestrictCoursesTip: Should places be assigned only in a subset of courses? This functionality can be used to make alternate placements in the case that some participants withdraw from their assigned courses. This functionality should only be used to exclude courses on valid grounds. E.g. if a seminar already had a planning meeting and is thus unable to accept new participants.
AllocationCourseRestrictionNone: Don't restrict
AllocationCourseRestrictionSubstitutes: Courses which currently allow substitute registrations
AllocationCourseRestrictionCustom: Custom
AllocationRestrictCoursesSelection: Courses AllocationRestrictCoursesSelection: Courses
AllocationRestrictCoursesSelectionTip: Participants will only be assigned to courses listed here. AllocationRestrictCoursesSelectionTip: Participants will only be assigned to courses listed here.
AllocationUsersMissingPrioritiesNotOk: Central allocation cannot occur until all participants, that were not excluded explicitly (“Participants without central priority”), have been assigned a central priority! AllocationUsersMissingPrioritiesNotOk: Central allocation cannot occur until all participants, that were not excluded explicitly (“Participants without central priority”), have been assigned a central priority!
@ -2747,6 +2790,7 @@ AllocationOfferedPlaces: Offered places
AllocationUserNewMatches: New allocations AllocationUserNewMatches: New allocations
AllocationUsersCount: Participants AllocationUsersCount: Participants
AllocationCoursesCount: Courses AllocationCoursesCount: Courses
AllocationCourseEligible: Considered
CourseOption tid ssh coursen: #{tid} - #{ssh} - #{coursen} CourseOption tid ssh coursen: #{tid} - #{ssh} - #{coursen}

View File

@ -36,6 +36,7 @@ AllocationCourse
allocation AllocationId allocation AllocationId
course CourseId course CourseId
minCapacity Int -- if the course would get assigned fewer than this many applicants, restart the assignment process without the course minCapacity Int -- if the course would get assigned fewer than this many applicants, restart the assignment process without the course
acceptSubstitutes UTCTime Maybe
UniqueAllocationCourse course UniqueAllocationCourse course
AllocationUser AllocationUser

View File

@ -18,6 +18,7 @@ Exam
gradingMode ExamGradingMode gradingMode ExamGradingMode
description Html Maybe description Html Maybe
examMode ExamMode examMode ExamMode
staff Text Maybe
UniqueExam course name UniqueExam course name
ExamPart ExamPart
exam ExamId exam ExamId
@ -67,4 +68,8 @@ ExamCorrector
ExamPartCorrector ExamPartCorrector
part ExamPartId part ExamPartId
corrector ExamCorrectorId corrector ExamCorrectorId
UniqueExamPartCorrector part corrector UniqueExamPartCorrector part corrector
ExamOfficeSchool
school SchoolId
exam ExamId
UniqueExamOfficeSchool exam school

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "uni2work", "name": "uni2work",
"version": "20.8.1", "version": "20.11.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "uni2work", "name": "uni2work",
"version": "20.8.1", "version": "20.11.0",
"description": "", "description": "",
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@ -1,5 +1,5 @@
name: uniworx name: uniworx
version: 20.8.1 version: 20.11.0
dependencies: dependencies:
- base - base

1
routes
View File

@ -137,6 +137,7 @@
/register ARegisterR POST !time /register ARegisterR POST !time
/course/#CryptoUUIDCourse/apply AApplyR POST !timeANDallocation-registered /course/#CryptoUUIDCourse/apply AApplyR POST !timeANDallocation-registered
/users AUsersR GET POST !allocation-admin /users AUsersR GET POST !allocation-admin
/users/add AAddUserR GET POST !allocation-admin
/priorities APriosR GET POST !allocation-admin /priorities APriosR GET POST !allocation-admin
/compute AComputeR GET POST !allocation-admin /compute AComputeR GET POST !allocation-admin
/accept AAcceptR GET POST !allocation-admin /accept AAcceptR GET POST !allocation-admin

View File

@ -160,6 +160,7 @@ instance YesodAuth UniWorX where
app <- getYesod app <- getYesod
let mr | Just lang <- mlang = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang let mr | Just lang <- mlang = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang
| otherwise = renderMessage app [] | otherwise = renderMessage app []
addMessage Success . toHtml $ mr Auth.NowLoggedIn addMessage Success . toHtml $ mr Auth.NowLoggedIn
onErrorHtml dest msg = do onErrorHtml dest msg = do

View File

@ -159,6 +159,7 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where
APriosR -> i18nCrumb MsgBreadcrumbAllocationPriorities . Just $ AllocationR tid ssh ash AUsersR APriosR -> i18nCrumb MsgBreadcrumbAllocationPriorities . Just $ AllocationR tid ssh ash AUsersR
AComputeR -> i18nCrumb MsgBreadcrumbAllocationCompute . Just $ AllocationR tid ssh ash AUsersR AComputeR -> i18nCrumb MsgBreadcrumbAllocationCompute . Just $ AllocationR tid ssh ash AUsersR
AAcceptR -> i18nCrumb MsgBreadcrumbAllocationAccept . Just $ AllocationR tid ssh ash AUsersR AAcceptR -> i18nCrumb MsgBreadcrumbAllocationAccept . Just $ AllocationR tid ssh ash AUsersR
AAddUserR -> i18nCrumb MsgBreadcrumbAllocationAddUser . Just $ AllocationR tid ssh ash AUsersR
breadcrumb ParticipantsListR = i18nCrumb MsgBreadcrumbParticipantsList $ Just CourseListR breadcrumb ParticipantsListR = i18nCrumb MsgBreadcrumbParticipantsList $ Just CourseListR
breadcrumb (ParticipantsR _ _) = i18nCrumb MsgBreadcrumbParticipants $ Just ParticipantsListR breadcrumb (ParticipantsR _ _) = i18nCrumb MsgBreadcrumbParticipants $ Just ParticipantsListR
@ -1382,6 +1383,17 @@ pageActions (AllocationR tid ssh ash AUsersR) = return
} }
, navChildren = [] , navChildren = []
} }
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationAddUser
, navRoute = AllocationR tid ssh ash AAddUserR
, navAccess' = return True
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
] ]
pageActions CourseListR = do pageActions CourseListR = do
participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR

View File

@ -26,7 +26,7 @@ yesodMiddleware :: ( BearerAuthSite UniWorX
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX) , BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
) )
=> HandlerFor UniWorX res -> HandlerFor UniWorX res => HandlerFor UniWorX res -> HandlerFor UniWorX res
yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . observeYesodCacheSizeMiddleware . languagesMiddleware appLanguages . headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . updateFavouritesMiddleware yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . observeYesodCacheSizeMiddleware . languagesMiddleware appLanguages . headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . updateFavouritesMiddleware . setActiveAuthTagsMiddleware
where where
dryRunMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a dryRunMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
dryRunMiddleware handler = do dryRunMiddleware handler = do
@ -98,6 +98,14 @@ yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . ob
Nothing -> return () Nothing -> return ()
handler handler
setActiveAuthTagsMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
setActiveAuthTagsMiddleware handler = do
mtagActive <- lookupSessionJson SessionActiveAuthTags :: HandlerFor UniWorX (Maybe AuthTagActive)
when (is _Nothing mtagActive) $ do
mAuthTagActive <- lookupRegisteredCookieJson CookieActiveAuthTags
for_ mAuthTagActive $ setSessionJson SessionActiveAuthTags . review _ReducedActiveAuthTags
handler
updateFavourites :: forall m backend. updateFavourites :: forall m backend.
( MonadHandler m, HandlerSite m ~ UniWorX ( MonadHandler m, HandlerSite m ~ UniWorX

View File

@ -8,6 +8,7 @@ import Handler.Allocation.Application as Handler.Allocation
import Handler.Allocation.Register as Handler.Allocation import Handler.Allocation.Register as Handler.Allocation
import Handler.Allocation.List as Handler.Allocation import Handler.Allocation.List as Handler.Allocation
import Handler.Allocation.Users as Handler.Allocation import Handler.Allocation.Users as Handler.Allocation
import Handler.Allocation.AddUser as Handler.Allocation
import Handler.Allocation.Prios as Handler.Allocation import Handler.Allocation.Prios as Handler.Allocation
import Handler.Allocation.Compute as Handler.Allocation import Handler.Allocation.Compute as Handler.Allocation
import Handler.Allocation.Accept as Handler.Allocation import Handler.Allocation.Accept as Handler.Allocation

View File

@ -11,6 +11,7 @@ import Handler.Utils.Allocation
import Data.Map ((!?)) import Data.Map ((!?))
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import qualified Control.Monad.State.Class as State import qualified Control.Monad.State.Class as State
@ -25,12 +26,13 @@ newtype SessionDataAllocationResults = SessionDataAllocationResults
) )
( UTCTime ( UTCTime
, AllocationFingerprint , AllocationFingerprint
, Set CourseId
, Set (UserId, CourseId) , Set (UserId, CourseId)
, Seq MatchingLogRun , Seq MatchingLogRun
) )
} deriving (Eq, Ord, Read, Show, Generic, Typeable) } deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriving newtype (ToJSON, FromJSON) deriving newtype (ToJSON, FromJSON)
deriving (Monoid, Semigroup) via Dual (Map (TermId, SchoolId, AllocationShorthand) (UTCTime, AllocationFingerprint, Set (UserId, CourseId), Seq MatchingLogRun)) deriving (Monoid, Semigroup) via Dual (Map (TermId, SchoolId, AllocationShorthand) (UTCTime, AllocationFingerprint, Set CourseId, Set (UserId, CourseId), Seq MatchingLogRun))
makeWrapped ''SessionDataAllocationResults makeWrapped ''SessionDataAllocationResults
@ -47,11 +49,11 @@ instance Button UniWorX AllocationAcceptButton where
btnClasses BtnAllocationAccept = [BCIsButton, BCPrimary] btnClasses BtnAllocationAccept = [BCIsButton, BCPrimary]
allocationAcceptForm :: AllocationId -> DB (Maybe (Form (UTCTime, AllocationFingerprint, Set (UserId, CourseId), Seq MatchingLogRun))) allocationAcceptForm :: AllocationId -> DB (Maybe (Form (UTCTime, AllocationFingerprint, Set CourseId, Set (UserId, CourseId), Seq MatchingLogRun)))
allocationAcceptForm aId = runMaybeT $ do allocationAcceptForm aId = runMaybeT $ do
Allocation{..} <- MaybeT $ get aId Allocation{..} <- MaybeT $ get aId
SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults
allocRes@(allocTime, allocFp, allocMatching, _ :|> MatchingLogRun{..}) <- hoistMaybe $ allocMap !? (allocationTerm, allocationSchool, allocationShorthand) allocRes@(allocTime, allocFp, eligibleCourses, allocMatching, _ :|> MatchingLogRun{..}) <- hoistMaybe $ allocMap !? (allocationTerm, allocationSchool, allocationShorthand)
allocationUsers <- fmap (map $ bimap E.unValue E.unValue) . lift . E.select . E.from $ \allocationUser -> do allocationUsers <- fmap (map $ bimap E.unValue E.unValue) . lift . E.select . E.from $ \allocationUser -> do
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
@ -85,6 +87,7 @@ allocationAcceptForm aId = runMaybeT $ do
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return (allocationCourse, course, participants) return (allocationCourse, course, participants)
let allocationCapacity = sumOf (folded . _2 . _entityVal . _courseCapacity . _Just) allocationCourses let allocationCapacity = sumOf (folded . _2 . _entityVal . _courseCapacity . _Just) allocationCourses
allocCourses = setOf (folded . _1 . _entityVal . _allocationCourseCourse) allocationCourses
let courseAllocations = ofoldr (\(_uid, cid) -> Map.insertWith (+) cid 1) Map.empty allocMatching let courseAllocations = ofoldr (\(_uid, cid) -> Map.insertWith (+) cid 1) Map.empty allocMatching
allocationCourses' <- hoistMaybe $ allocationCourses' <- hoistMaybe $
@ -137,9 +140,9 @@ postAAcceptR tid ssh ash = do
formRes@((acceptRes, _), _) <- liftHandler $ runFormPost acceptForm formRes@((acceptRes, _), _) <- liftHandler $ runFormPost acceptForm
didStore <- formResultMaybe acceptRes $ \(now, allocFp, allocMatchings, allocLog) -> do didStore <- formResultMaybe acceptRes $ \(now, allocFp, _, allocMatchings, allocLog) -> do
modifySessionJson SessionAllocationResults . fmap (assertM $ not . views _Wrapped onull) . over (mapped . _Wrapped :: Setter' (Maybe SessionDataAllocationResults) _) $ modifySessionJson SessionAllocationResults . fmap (assertM $ not . views _Wrapped onull) . over (mapped . _Wrapped :: Setter' (Maybe SessionDataAllocationResults) _) $
Map.filterWithKey (\(tid', ssh', ash') (_, allocFp', _, _) -> Map.filterWithKey (\(tid', ssh', ash') (_, allocFp', _, _, _) ->
or [ tid' /= tid or [ tid' /= tid
, ssh' /= ssh , ssh' /= ssh
, ash' /= ash , ash' /= ash

View File

@ -0,0 +1,174 @@
module Handler.Allocation.AddUser
( getAAddUserR, postAAddUserR
) where
import Import
import Handler.Allocation.Application
import Handler.Utils
import qualified Data.Map as Map
import qualified Data.Conduit.Combinators as C
import qualified Database.Esqueleto as E
data AllocationAddUserForm = AllocationAddUserForm
{ aauUser :: UserId
, aauTotalCourses :: Natural
, aauPriority :: Maybe AllocationPriority
, aauApplications :: Map CourseId ApplicationForm
}
getAAddUserR, postAAddUserR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAAddUserR = postAAddUserR
postAAddUserR tid ssh ash = do
(Entity _ Allocation{..}, (addUserAct, addUserForm, addUserEnctype)) <- runDB $ do
alloc@(Entity aId _) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
allocCourses <- E.select . E.from $ \(course `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
return ( course
, E.exists . E.from $ \courseAppInstructionFile ->
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. course E.^. CourseId
, allocationCourse
)
MsgRenderer mr <- getMsgRenderer
((addUserRes, addUserForm), addUserEnctype) <- liftHandler . runFormPost . renderAForm FormStandard $ AllocationAddUserForm
<$> areq (checkMap (first $ const MsgAllocationAddUserUserNotFound) Right $ userField False Nothing) (fslpI MsgAllocationAddUserUser (mr MsgAllocationAddUserUserPlaceholder)) Nothing
<*> areq (posIntFieldI MsgAllocationAddUserTotalCoursesLessThanOne) (fslI MsgAllocationAddUserTotalCourses) (Just 1)
<*> optionalActionA (allocationPriorityForm (fslI MsgAllocationAddUserPriority) Nothing) (fslI MsgAllocationAddUserSetPriority) (Just True)
<*> allocationApplicationsForm aId (Map.fromList [ (cId, (course, allocationCourse, hasTemplate)) | (Entity cId course, E.Value hasTemplate, Entity _ allocationCourse) <- allocCourses ]) (fslI MsgAllocationAddUserApplications) False
addUserAct <- formResultMaybe addUserRes $ \AllocationAddUserForm{..} -> Just <$> do
now <- liftIO getCurrentTime
didInsert <- is _Just <$> insertUnique AllocationUser
{ allocationUserAllocation = aId
, allocationUserUser = aauUser
, allocationUserTotalCourses = aauTotalCourses
, allocationUserPriority = aauPriority
}
if
| didInsert -> do
oldApps <- selectList [CourseApplicationUser ==. aauUser, CourseApplicationAllocation ==. Just aId] []
forM_ oldApps $ \(Entity appId CourseApplication{..}) -> do
delete appId
unless (courseApplicationCourse `Map.member` aauApplications) $
audit $ TransactionCourseApplicationDeleted courseApplicationCourse courseApplicationUser appId
iforM_ aauApplications $ \cId ApplicationForm{..} -> maybeT (return ()) $ do
prio <- hoistMaybe afPriority
let rated = afRatingVeto || is _Just afRatingPoints
appId <- lift $ insert CourseApplication
{ courseApplicationCourse = cId
, courseApplicationUser = aauUser
, courseApplicationText = afText
, courseApplicationRatingVeto = afRatingVeto
, courseApplicationRatingPoints = afRatingPoints
, courseApplicationRatingComment = afRatingComment
, courseApplicationAllocation = Just aId
, courseApplicationAllocationPriority = Just prio
, courseApplicationTime = now
, courseApplicationRatingTime = guardOn rated now
}
lift . runConduit $ transPipe liftHandler (sequence_ afFiles) .| C.mapM_ (insert_ . review _FileReference . (, CourseApplicationFileResidual appId))
lift . audit $ TransactionCourseApplicationEdit cId aauUser appId
return $ do
addMessageI Success MsgAllocationAddUserUserAdded
redirect $ AllocationR tid ssh ash AAddUserR
| otherwise -> return $ addMessageI Error MsgAllocationAddUserUserExists
return (alloc, (addUserAct, addUserForm, addUserEnctype))
sequence_ addUserAct
MsgRenderer mr <- getMsgRenderer
let title = MsgAllocationAddUserTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
shortTitle = MsgAllocationAddUserShortTitle allocationTerm allocationSchool allocationShorthand
siteLayoutMsg title $ do
setTitleI shortTitle
wrapForm addUserForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AllocationR tid ssh ash AAddUserR
, formEncoding = addUserEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text
}
allocationApplicationsForm :: AllocationId
-> Map CourseId (Course, AllocationCourse, Bool)
-> FieldSettings UniWorX
-> Bool
-> AForm Handler (Map CourseId ApplicationForm)
allocationApplicationsForm aId courses FieldSettings{..} fvRequired = formToAForm $ do
now <- liftIO getCurrentTime
let afmApplicant = True
afmApplicantEdit = True
afmLecturer = True
appsRes' <- iforM courses $ \cId (course, allocCourse, hasApplicationTemplate) -> over _2 (course, allocCourse, hasApplicationTemplate, ) <$> applicationForm (Just aId) cId Nothing ApplicationFormMode{..} Nothing
let appsRes = sequenceA $ view _1 <$> appsRes'
appsViews = view _2 <$> appsRes'
let fvInput =
[whamlet|
$newline never
<div .allocation__courses>
$forall (Course{courseTerm, courseSchool, courseShorthand, courseName, courseApplicationsInstructions}, AllocationCourse{allocationCourseAcceptSubstitutes}, hasApplicationTemplate, ApplicationFormView{afvPriority, afvForm}) <- Map.elems appsViews
<div .allocation-course>
<div .allocation-course__priority-label .allocation__label>
_{MsgAllocationPriority}
<div .allocation-course__priority>
$maybe prioView <- afvPriority
^{fvWidget prioView}
<a .allocation-course__name href=@{CourseR courseTerm courseSchool courseShorthand CShowR} target="_blank">
#{courseName}
<div .allocation-course__admin-info>
<p>
$maybe deadline <- allocationCourseAcceptSubstitutes
_{MsgCourseAllocationCourseAcceptsSubstitutesUntil}: #
^{formatTimeW SelFormatDateTime deadline}
$nothing
_{MsgCourseAllocationCourseAcceptsSubstitutesNever}
$if allocationCourseAcceptSubstitutes >= Just now
\ ^{iconOK}
$if hasApplicationTemplate || is _Just courseApplicationsInstructions
<div .allocation-course__instructions-label .allocation__label>
_{MsgCourseApplicationInstructionsApplication}
<div .allocation-course__instructions>
$maybe aInst <- courseApplicationsInstructions
<p>
#{aInst}
$if hasApplicationTemplate
<p>
<a href=@{CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR}>
#{iconRegisterTemplate} _{MsgCourseApplicationTemplateApplication}
<div .allocation-course__application-label .interactive-fieldset__target .allocation__label uw-interactive-fieldset data-conditional-input=#{maybe "" fvId afvPriority} data-conditional-value="" data-conditional-negated>
_{MsgCourseApplication}
<div .allocation-course__application .interactive-fieldset__target uw-interactive-fieldset data-conditional-input=#{maybe "" fvId afvPriority} data-conditional-value="" data-conditional-negated>
^{renderFieldViews FormStandard afvForm}
|]
MsgRenderer mr <- getMsgRenderer
let fvLabel = toHtml $ mr fsLabel
fvTooltip = toHtml . mr <$> fsTooltip
fvErrors = case appsRes of
FormFailure errs -> Just
[shamlet|
$newline never
<ul>
$forall err <- errs
<li>#{err}
|]
_other -> Nothing
fvId <- maybe newIdent return fsId
return (appsRes, pure FieldView{..})

View File

@ -71,16 +71,17 @@ instance Exception ApplicationFormException
applicationForm :: Maybe AllocationId applicationForm :: Maybe AllocationId
-> CourseId -> CourseId
-> UserId -> Maybe UserId
-> ApplicationFormMode -- ^ Which parts of the shared form to display -> ApplicationFormMode -- ^ Which parts of the shared form to display
-> Html -> MForm Handler (FormResult ApplicationForm, ApplicationFormView) -> Maybe Html -- ^ If @Just@ also include action buttons for usage as standalone form
applicationForm maId@(is _Just -> isAlloc) cid uid ApplicationFormMode{..} csrf = do -> MForm Handler (FormResult ApplicationForm, ApplicationFormView)
applicationForm maId@(is _Just -> isAlloc) cid muid ApplicationFormMode{..} mcsrf = do
(mApp, coursesNum, Course{..}, maxPrio) <- liftHandler . runDB $ do (mApp, coursesNum, Course{..}, maxPrio) <- liftHandler . runDB $ do
mApplication <- listToMaybe <$> selectList [CourseApplicationAllocation ==. maId, CourseApplicationUser ==. uid, CourseApplicationCourse ==. cid] [LimitTo 1] mApplication <- fmap join . for muid $ \uid -> listToMaybe <$> selectList [CourseApplicationAllocation ==. maId, CourseApplicationUser ==. uid, CourseApplicationCourse ==. cid] [LimitTo 1]
coursesNum <- fromIntegral . fromMaybe 1 <$> for maId (\aId -> count [AllocationCourseAllocation ==. aId]) coursesNum <- fromIntegral . fromMaybe 1 <$> for maId (\aId -> count [AllocationCourseAllocation ==. aId])
course <- getJust cid course <- getJust cid
(fromMaybe 0 -> maxPrio) <- fmap (E.unValue <=< listToMaybe) . E.select . E.from $ \courseApplication -> do (fromMaybe 0 -> maxPrio) <- fmap join . for muid $ \uid -> fmap (E.unValue <=< listToMaybe) . E.select . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val uid E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val uid
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.val maId E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.val maId
E.&&. E.not_ (E.isNothing $ courseApplication E.^. CourseApplicationAllocationPriority) E.&&. E.not_ (E.isNothing $ courseApplication E.^. CourseApplicationAllocationPriority)
@ -202,7 +203,9 @@ applicationForm maId@(is _Just -> isAlloc) cid uid ApplicationFormMode{..} csrf
, guardOn ( afmApplicantEdit && is _Nothing mApp ) BtnAllocationApply , guardOn ( afmApplicantEdit && is _Nothing mApp ) BtnAllocationApply
, guardOn ( afmApplicantEdit && is _Just mApp ) BtnAllocationApplicationRetract , guardOn ( afmApplicantEdit && is _Just mApp ) BtnAllocationApplicationRetract
] ]
(actionRes, buttonsView) <- buttonForm' buttons csrf (actionRes, buttonsView) <- case mcsrf of
Just csrf -> buttonForm' buttons csrf
Nothing -> return (pure BtnAllocationApplicationEdit, mempty)
ratingSection <- if ratingSection <- if
| afmLecturer | afmLecturer
@ -251,7 +254,7 @@ editApplicationR :: Maybe AllocationId
editApplicationR maId uid cid mAppId afMode allowAction postAction = do editApplicationR maId uid cid mAppId afMode allowAction postAction = do
Course{..} <- runDB $ get404 cid Course{..} <- runDB $ get404 cid
((appRes, appView), appEnc) <- runFormPost $ applicationForm maId cid uid afMode ((appRes, appView), appEnc) <- runFormPost $ applicationForm maId cid (Just uid) afMode . Just
formResult appRes $ \ApplicationForm{..} -> do formResult appRes $ \ApplicationForm{..} -> do
if if

View File

@ -13,6 +13,7 @@ import qualified Data.Set as Set
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
import qualified Control.Monad.State.Class as State import qualified Control.Monad.State.Class as State
@ -70,10 +71,28 @@ missingPriorities aId = wFormToAForm $ do
-> fmap (bool Set.empty $ Map.keysSet usersWithoutPrio) <$> wpreq missingPriosField (fslI MsgAllocationUsersMissingPriorities & setTooltip MsgAllocationUsersMissingPrioritiesTip) (Just False) -> fmap (bool Set.empty $ Map.keysSet usersWithoutPrio) <$> wpreq missingPriosField (fslI MsgAllocationUsersMissingPriorities & setTooltip MsgAllocationUsersMissingPrioritiesTip) (Just False)
data AllocationCourseRestrictionMode
= AllocationCourseRestrictionNone
| AllocationCourseRestrictionSubstitutes
| AllocationCourseRestrictionCustom
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''AllocationCourseRestrictionMode $ camelToPathPiece' 3
embedRenderMessage ''UniWorX ''AllocationCourseRestrictionMode id
restrictCourses :: (MonadHandler m, HandlerSite m ~ UniWorX) => AllocationId -> AForm m (Maybe (Set CourseId)) restrictCourses :: (MonadHandler m, HandlerSite m ~ UniWorX) => AllocationId -> AForm m (Maybe (Set CourseId))
restrictCourses aId = hoistAForm liftHandler $ restrictCourses aId = hoistAForm liftHandler $ multiActionA restrictOpts (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just AllocationCourseRestrictionNone)
optionalActionA selectCourses (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just False)
where where
restrictOpts = mapF $ \case
AllocationCourseRestrictionNone -> pure Nothing
AllocationCourseRestrictionSubstitutes -> wFormToAForm $ do
now <- liftIO getCurrentTime
allocCourses <- fmap (setOf $ folded . _Value) . liftHandler . runDB . E.select . E.from $ \allocationCourse -> do
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
E.where_ . E.maybe E.false (E.>=. E.val now) $ allocationCourse E.^. AllocationCourseAcceptSubstitutes
return $ allocationCourse E.^. AllocationCourseCourse
return . pure $ Just allocCourses
AllocationCourseRestrictionCustom -> Just <$> selectCourses
selectCourses = courseSelectForm query coursePred miButtonAction' miIdent' fSettings fRequired mPrev selectCourses = courseSelectForm query coursePred miButtonAction' miIdent' fSettings fRequired mPrev
where where
query = E.from $ \(course `E.InnerJoin` allocationCourse) -> do query = E.from $ \(course `E.InnerJoin` allocationCourse) -> do
@ -115,9 +134,9 @@ postAComputeR tid ssh ash = do
formResult computeFormRes $ \AllocationComputeForm{..} -> do formResult computeFormRes $ \AllocationComputeForm{..} -> do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
(allocFp, allocMatching, allocLog) <- computeAllocation aEnt acfRestrictCourses (allocFp, eligibleCourses, allocMatching, allocLog) <- computeAllocation aEnt acfRestrictCourses
tellSessionJson SessionAllocationResults . SessionDataAllocationResults $ tellSessionJson SessionAllocationResults . SessionDataAllocationResults $
Map.singleton (tid, ssh, ash) (now, allocFp, allocMatching, allocLog) Map.singleton (tid, ssh, ash) (now, allocFp, eligibleCourses, allocMatching, allocLog)
addMessageI Success MsgAllocationComputed addMessageI Success MsgAllocationComputed
redirect $ AllocationR tid ssh ash AUsersR -- Redirect aborts transaction for safety redirect $ AllocationR tid ssh ash AUsersR -- Redirect aborts transaction for safety

View File

@ -49,18 +49,20 @@ postAShowR tid ssh ash = do
ata <- getSessionActiveAuthTags ata <- getSessionActiveAuthTags
let let
resultCourse :: Simple Field1 a (Entity Course) => Lens' a (Entity Course) resultCourse :: _ => Lens' a (Entity Course)
resultCourse = _1 resultCourse = _1
resultCourseApplication :: Simple Field2 a (Maybe (Entity CourseApplication)) => Traversal' a (Entity CourseApplication) resultCourseApplication :: _ => Traversal' a (Entity CourseApplication)
resultCourseApplication = _2 . _Just resultCourseApplication = _2 . _Just
resultHasTemplate :: Simple Field3 a (E.Value Bool) => Lens' a Bool resultHasTemplate :: _ => Lens' a Bool
resultHasTemplate = _3 . _Value resultHasTemplate = _3 . _Value
resultIsRegistered :: Simple Field4 a (E.Value Bool) => Lens' a Bool resultIsRegistered :: _ => Lens' a Bool
resultIsRegistered = _4 . _Value resultIsRegistered = _4 . _Value
resultCourseVisible :: Simple Field5 a (E.Value Bool) => Lens' a Bool resultCourseVisible :: _ => Lens' a Bool
resultCourseVisible = _5 . _Value resultCourseVisible = _5 . _Value
resultAllocationCourse :: _ => Lens' a AllocationCourse
resultAllocationCourse = _6 . _entityVal
(Entity aId Allocation{..}, School{..}, isAnyLecturer, courses, registration, wouldNotifyNewCourse) <- runDB $ do (Entity aId Allocation{..}, School{..}, isAnyLecturer, isAdmin, courses, registration, wouldNotifyNewCourse) <- runDB $ do
alloc@(Entity aId Allocation{allocationSchool}) <- getBy404 $ TermSchoolAllocationShort tid ssh ash alloc@(Entity aId Allocation{allocationSchool}) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
school <- getJust allocationSchool school <- getJust allocationSchool
@ -79,15 +81,24 @@ postAShowR tid ssh ash = do
E.orderBy [E.asc $ course E.^. CourseName] E.orderBy [E.asc $ course E.^. CourseName]
let hasTemplate = E.exists . E.from $ \courseAppInstructionFile -> let hasTemplate = E.exists . E.from $ \courseAppInstructionFile ->
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. course E.^. CourseId E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. course E.^. CourseId
return (course, courseApplication, hasTemplate, E.not_ . E.isNothing $ registration E.?. CourseParticipantId, courseIsVisible now course (Just (E.val aId))) return ( course
, courseApplication
, hasTemplate
, E.not_ . E.isNothing $ registration E.?. CourseParticipantId
, courseIsVisible now course . Just $ E.val aId
, allocationCourse
)
registration <- fmap join . for muid $ getBy . UniqueAllocationUser aId registration <- fmap join . for muid $ getBy . UniqueAllocationUser aId
isAnyLecturer <- hasWriteAccessTo CourseNewR isAnyLecturer <- hasWriteAccessTo CourseNewR
isAdmin <- hasReadAccessTo $ AllocationR tid ssh ash AUsersR
wouldNotifyNewCourse <- fmap (maybe False E.unValue . join) . for muid $ E.selectMaybe . pure . allocationNotifyNewCourses (E.val aId) . E.val wouldNotifyNewCourse <- fmap (maybe False E.unValue . join) . for muid $ E.selectMaybe . pure . allocationNotifyNewCourses (E.val aId) . E.val
return (alloc, school, isAnyLecturer, nubOn (view $ resultCourse . _entityKey) courses, registration, wouldNotifyNewCourse) return (alloc, school, isAnyLecturer, isAdmin, nubOn (view $ resultCourse . _entityKey) courses, registration, wouldNotifyNewCourse)
let nextSubstitutesDeadline = minimumOf (folded . resultAllocationCourse . _allocationCourseAcceptSubstitutes . _Just . filtered (>= now)) courses
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
@ -146,11 +157,12 @@ postAShowR tid ssh ash = do
mApp = cEntry ^? resultCourseApplication mApp = cEntry ^? resultCourseApplication
isRegistered = cEntry ^. resultIsRegistered isRegistered = cEntry ^. resultIsRegistered
courseVisible = cEntry ^. resultCourseVisible courseVisible = cEntry ^. resultCourseVisible
AllocationCourse{..} = cEntry ^. resultAllocationCourse
cID <- encrypt cid :: WidgetFor UniWorX CryptoUUIDCourse cID <- encrypt cid :: WidgetFor UniWorX CryptoUUIDCourse
mayApply <- hasWriteAccessTo . AllocationR tid ssh ash $ AApplyR cID mayApply <- hasWriteAccessTo . AllocationR tid ssh ash $ AApplyR cID
mayEdit <- hasWriteAccessTo $ CourseR tid ssh courseShorthand CEditR mayEdit <- hasWriteAccessTo $ CourseR tid ssh courseShorthand CEditR
isLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR isLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR
mApplyFormView <- liftHandler . for muid $ \uid -> generateFormPost . applicationForm (Just aId) cid uid $ ApplicationFormMode True mayApply isLecturer mApplyFormView <- liftHandler . for muid $ \uid -> generateFormPost $ applicationForm (Just aId) cid (Just uid) (ApplicationFormMode True mayApply isLecturer) . Just
tRoute <- case mApp of tRoute <- case mApp of
Nothing -> return . AllocationR tid ssh ash $ AApplyR cID Nothing -> return . AllocationR tid ssh ash $ AApplyR cID
Just (Entity appId _) -> CApplicationR courseTerm courseSchool courseShorthand <$> encrypt appId <*> pure CAEditR Just (Entity appId _) -> CApplicationR courseTerm courseSchool courseShorthand <$> encrypt appId <*> pure CAEditR

View File

@ -162,7 +162,7 @@ postAUsersR tid ssh ash = do
resultsDone <- is _Just <$> allocationStarted aId resultsDone <- is _Just <$> allocationStarted aId
allocMatching <- runMaybeT $ do allocMatching <- runMaybeT $ do
SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults
allocMatching <- fmap (view _3) . hoistMaybe $ allocMap !? (tid, ssh, ash) allocMatching <- fmap (view _4) . hoistMaybe $ allocMap !? (tid, ssh, ash)
return $ Map.fromListWith (<>) [ (uid, opoint cid) | (uid, cid) <- Set.toList allocMatching ] :: _ (Map UserId (NonNull (Set CourseId))) return $ Map.fromListWith (<>) [ (uid, opoint cid) | (uid, cid) <- Set.toList allocMatching ] :: _ (Map UserId (NonNull (Set CourseId)))
csvName <- getMessageRender <*> pure (MsgAllocationUsersCsvName tid ssh ash) csvName <- getMessageRender <*> pure (MsgAllocationUsersCsvName tid ssh ash)

View File

@ -57,6 +57,7 @@ data CourseForm = CourseForm
data AllocationCourseForm = AllocationCourseForm data AllocationCourseForm = AllocationCourseForm
{ acfAllocation :: AllocationId { acfAllocation :: AllocationId
, acfMinCapacity :: Int , acfMinCapacity :: Int
, acfAcceptSubstitutes :: Maybe UTCTime
, acfDeregisterNoShow :: Bool , acfDeregisterNoShow :: Bool
} }
@ -98,6 +99,7 @@ allocationCourseToForm :: Entity Course -> Entity AllocationCourse -> Allocation
allocationCourseToForm (Entity _ Course{..}) (Entity _ AllocationCourse{..}) = AllocationCourseForm allocationCourseToForm (Entity _ Course{..}) (Entity _ AllocationCourse{..}) = AllocationCourseForm
{ acfAllocation = allocationCourseAllocation { acfAllocation = allocationCourseAllocation
, acfMinCapacity = allocationCourseMinCapacity , acfMinCapacity = allocationCourseMinCapacity
, acfAcceptSubstitutes = allocationCourseAcceptSubstitutes
, acfDeregisterNoShow = courseDeregisterNoShow , acfDeregisterNoShow = courseDeregisterNoShow
} }
@ -265,6 +267,7 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
in AllocationCourseForm in AllocationCourseForm
<$> ainp allocField (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation) <$> ainp allocField (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation)
<*> ainp (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation) <*> ainp (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation)
<*> aopt utcTimeField (fslI MsgCourseAcceptSubstitutesUntil & setTooltip MsgCourseAcceptSubstitutesUntilTip) (fmap acfAcceptSubstitutes $ template >>= cfAllocation)
<*> apopt checkBoxField (fslI MsgCourseDeregisterNoShow & setTooltip MsgCourseDeregisterNoShowTip) ((<|> Just True) . fmap acfDeregisterNoShow $ template >>= cfAllocation) <*> apopt checkBoxField (fslI MsgCourseDeregisterNoShow & setTooltip MsgCourseDeregisterNoShowTip) ((<|> Just True) . fmap acfDeregisterNoShow $ template >>= cfAllocation)
optionalActionW' (bool mforcedJust mpopt mayChange) allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template) optionalActionW' (bool mforcedJust mpopt mayChange) allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template)
@ -592,13 +595,15 @@ upsertAllocationCourse cid cfAllocation = do
case cfAllocation of case cfAllocation of
Just AllocationCourseForm{..} -> do Just AllocationCourseForm{..} -> do
void $ upsert AllocationCourse void $ upsert AllocationCourse
{ allocationCourseAllocation = acfAllocation { allocationCourseAllocation = acfAllocation
, allocationCourseCourse = cid , allocationCourseCourse = cid
, allocationCourseMinCapacity = acfMinCapacity , allocationCourseMinCapacity = acfMinCapacity
, allocationCourseAcceptSubstitutes = acfAcceptSubstitutes
} }
[ AllocationCourseAllocation =. acfAllocation [ AllocationCourseAllocation =. acfAllocation
, AllocationCourseCourse =. cid , AllocationCourseCourse =. cid
, AllocationCourseMinCapacity =. acfMinCapacity , AllocationCourseMinCapacity =. acfMinCapacity
, AllocationCourseAcceptSubstitutes =. acfAcceptSubstitutes
] ]
when (Just acfAllocation /= fmap entityKey prevAllocation) $ when (Just acfAllocation /= fmap entityKey prevAllocation) $

View File

@ -49,6 +49,7 @@ postEEditR tid ssh csh examn = do
, examGradingMode = efGradingMode , examGradingMode = efGradingMode
, examDescription = efDescription , examDescription = efDescription
, examExamMode = efExamMode , examExamMode = efExamMode
, examStaff = efStaff
} }
when (is _Nothing insertRes) $ do when (is _Nothing insertRes) $ do
@ -80,7 +81,6 @@ postEEditR tid ssh csh examn = do
, examOccurrenceDescription = eofDescription , examOccurrenceDescription = eofDescription
} }
pIds <- fmap catMaybes . forM (Set.toList efExamParts) $ traverse decrypt . epfId pIds <- fmap catMaybes . forM (Set.toList efExamParts) $ traverse decrypt . epfId
deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ] deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ]
forM_ (Set.toList efExamParts) $ \case forM_ (Set.toList efExamParts) $ \case
@ -105,6 +105,8 @@ postEEditR tid ssh csh examn = do
, examPartWeight = epfWeight , examPartWeight = epfWeight
} }
deleteWhere [ ExamOfficeSchoolExam ==. eId ]
insertMany_ [ ExamOfficeSchool ssh' eId | ssh' <- Set.toList efOfficeSchools ]
let (invites, adds) = partitionEithers $ Set.toList efCorrectors let (invites, adds) = partitionEithers $ Set.toList efCorrectors

View File

@ -28,7 +28,6 @@ import Text.Blaze.Html.Renderer.String (renderHtml)
data ExamForm = ExamForm data ExamForm = ExamForm
{ efName :: ExamName { efName :: ExamName
, efDescription :: Maybe Html , efDescription :: Maybe Html
, efGradingMode :: ExamGradingMode
, efStart :: Maybe UTCTime , efStart :: Maybe UTCTime
, efEnd :: Maybe UTCTime , efEnd :: Maybe UTCTime
, efVisibleFrom :: Maybe UTCTime , efVisibleFrom :: Maybe UTCTime
@ -43,6 +42,9 @@ data ExamForm = ExamForm
, efBonusRule :: Maybe ExamBonusRule , efBonusRule :: Maybe ExamBonusRule
, efOccurrenceRule :: ExamOccurrenceRule , efOccurrenceRule :: ExamOccurrenceRule
, efExamMode :: ExamMode , efExamMode :: ExamMode
, efGradingMode :: ExamGradingMode
, efOfficeSchools :: Set SchoolId
, efStaff :: Maybe Text
, efCorrectors :: Set (Either UserEmail UserId) , efCorrectors :: Set (Either UserEmail UserId)
, efExamParts :: Set ExamPartForm , efExamParts :: Set ExamPartForm
} }
@ -103,7 +105,6 @@ examForm template html = do
flip (renderAForm FormStandard) html $ ExamForm flip (renderAForm FormStandard) html $ ExamForm
<$> areq ciField (fslpI MsgExamName (mr MsgExamName) & setTooltip MsgExamNameTip) (efName <$> template) <$> areq ciField (fslpI MsgExamName (mr MsgExamName) & setTooltip MsgExamNameTip) (efName <$> template)
<*> aopt htmlField (fslI MsgExamDescription) (efDescription <$> template) <*> aopt htmlField (fslI MsgExamDescription) (efDescription <$> template)
<*> apopt (selectField optionsFinite) (fslI MsgExamGradingMode & setTooltip MsgExamGradingModeTip) (efGradingMode <$> template <|> Just ExamGradingMixed)
<* aformSection MsgExamFormTimes <* aformSection MsgExamFormTimes
<*> aopt utcTimeField (fslpI MsgExamStart (mr MsgDate) & setTooltip MsgExamTimeTip) (efStart <$> template) <*> aopt utcTimeField (fslpI MsgExamStart (mr MsgDate) & setTooltip MsgExamTimeTip) (efStart <$> template)
<*> aopt utcTimeField (fslpI MsgExamEnd (mr MsgDate) & setTooltip MsgExamTimeTip) (efEnd <$> template) <*> aopt utcTimeField (fslpI MsgExamEnd (mr MsgDate) & setTooltip MsgExamTimeTip) (efEnd <$> template)
@ -122,11 +123,39 @@ examForm template html = do
<*> examOccurrenceRuleForm (efOccurrenceRule <$> template) <*> examOccurrenceRuleForm (efOccurrenceRule <$> template)
<* aformSection MsgExamFormMode <* aformSection MsgExamFormMode
<*> examModeForm (efExamMode <$> template) <*> examModeForm (efExamMode <$> template)
<* aformSection MsgExamFormGrades
<*> apopt (selectField optionsFinite) (fslI MsgExamGradingMode & setTooltip MsgExamGradingModeTip) (efGradingMode <$> template <|> Just ExamGradingMixed)
<*> officeSchoolsForm (efOfficeSchools <$> template)
<*> apreq' (textField & cfStrip) (fslpI MsgExamStaff (mr MsgExamStaff) & setTooltip MsgExamStaffTip) (efStaff <$> template)
<* aformSection MsgExamFormCorrection <* aformSection MsgExamFormCorrection
<*> examCorrectorsForm (efCorrectors <$> template) <*> examCorrectorsForm (efCorrectors <$> template)
<* aformSection MsgExamFormParts <* aformSection MsgExamFormParts
<*> examPartsForm (efExamParts <$> template) <*> examPartsForm (efExamParts <$> template)
officeSchoolsForm :: Maybe (Set SchoolId) -> AForm Handler (Set SchoolId)
officeSchoolsForm mPrev = wFormToAForm $ do
currentRoute <- fromMaybe (error "officeSchoolsForm called from 404-handler") <$> getCurrentRoute
let
miButtonAction' :: forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)
miButtonAction' frag = Just . SomeRoute $ currentRoute :#: frag
miAdd' :: (Text -> Text) -> FieldView UniWorX -> Form ([SchoolId] -> FormResult [SchoolId])
miAdd' nudge submitView csrf = do
(schoolRes, addView) <- mpopt schoolField ("" & addName (nudge "school")) Nothing
let schoolRes' = schoolRes <&> \newDat oldDat -> FormSuccess (guardOn (newDat `notElem` oldDat) newDat)
return (schoolRes', $(widgetFile "exam/schoolMassInput/add"))
miCell' :: SchoolId -> Widget
miCell' ssh = do
School{..} <- liftHandler . runDB $ getJust ssh
$(widgetFile "exam/schoolMassInput/cell")
miLayout' :: MassInputLayout ListLength SchoolId ()
miLayout' lLength _ cellWdgts delButtons addWdgts = $(widgetFile "exam/schoolMassInput/layout")
fmap Set.fromList <$> massInputAccumW miAdd' miCell' miButtonAction' miLayout' ("exam-schools" :: Text) (fslI MsgExamExamOfficeSchools & setTooltip MsgExamExamOfficeSchoolsTip) False (Set.toList <$> mPrev)
examCorrectorsForm :: Maybe (Set (Either UserEmail UserId)) -> AForm Handler (Set (Either UserEmail UserId)) examCorrectorsForm :: Maybe (Set (Either UserEmail UserId)) -> AForm Handler (Set (Either UserEmail UserId))
examCorrectorsForm mPrev = wFormToAForm $ do examCorrectorsForm mPrev = wFormToAForm $ do
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
@ -261,6 +290,7 @@ examFormTemplate (Entity eId Exam{..}) = do
occurrences <- selectList [ ExamOccurrenceExam ==. eId ] [] occurrences <- selectList [ ExamOccurrenceExam ==. eId ] []
correctors <- selectList [ ExamCorrectorExam ==. eId ] [] correctors <- selectList [ ExamCorrectorExam ==. eId ] []
invitations <- Map.keysSet <$> sourceInvitationsF @ExamCorrector eId invitations <- Map.keysSet <$> sourceInvitationsF @ExamCorrector eId
extraSchools <- selectList [ ExamOfficeSchoolExam ==. eId ] []
examParts' <- forM examParts $ \(Entity pid part) -> (,) <$> encrypt pid <*> pure part examParts' <- forM examParts $ \(Entity pid part) -> (,) <$> encrypt pid <*> pure part
occurrences' <- forM occurrences $ \(Entity oid occ) -> (,) <$> encrypt oid <*> pure occ occurrences' <- forM occurrences $ \(Entity oid occ) -> (,) <$> encrypt oid <*> pure occ
@ -308,13 +338,15 @@ examFormTemplate (Entity eId Exam{..}) = do
return examCorrectorUser return examCorrectorUser
] ]
, efExamMode = examExamMode , efExamMode = examExamMode
, efOfficeSchools = Set.fromList $ examOfficeSchoolSchool . entityVal <$> extraSchools
, efStaff = examStaff
} }
examTemplate :: CourseId -> DB (Maybe ExamForm) examTemplate :: CourseId -> DB (Maybe ExamForm)
examTemplate cid = runMaybeT $ do examTemplate cid = runMaybeT $ do
newCourse <- MaybeT $ get cid newCourse <- MaybeT $ get cid
[(Entity _ oldCourse, Entity _ oldExam)] <- lift . E.select . E.from $ \(course `E.InnerJoin` exam) -> do [(Entity _ oldCourse, Entity oldExamId oldExam)] <- lift . E.select . E.from $ \(course `E.InnerJoin` exam) -> do
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ ( course E.^. CourseShorthand E.==. E.val (courseShorthand newCourse) E.where_ $ ( course E.^. CourseShorthand E.==. E.val (courseShorthand newCourse)
E.||. course E.^. CourseName E.==. E.val (courseName newCourse) E.||. course E.^. CourseName E.==. E.val (courseName newCourse)
@ -327,6 +359,8 @@ examTemplate cid = runMaybeT $ do
E.limit 1 E.limit 1
E.orderBy [ E.desc $ course E.^. CourseTerm, E.asc $ exam E.^. ExamVisibleFrom ] E.orderBy [ E.desc $ course E.^. CourseTerm, E.asc $ exam E.^. ExamVisibleFrom ]
return (course, exam) return (course, exam)
extraSchools <- lift $ selectList [ ExamOfficeSchoolExam ==. oldExamId ] []
oldTerm <- MaybeT . get $ courseTerm oldCourse oldTerm <- MaybeT . get $ courseTerm oldCourse
newTerm <- MaybeT . get $ courseTerm newCourse newTerm <- MaybeT . get $ courseTerm newCourse
@ -354,6 +388,8 @@ examTemplate cid = runMaybeT $ do
, efExamParts = Set.empty , efExamParts = Set.empty
, efCorrectors = Set.empty , efCorrectors = Set.empty
, efExamMode = examExamMode oldExam , efExamMode = examExamMode oldExam
, efStaff = examStaff oldExam
, efOfficeSchools = Set.fromList $ examOfficeSchoolSchool . entityVal <$> extraSchools
} }
@ -431,3 +467,6 @@ validateExam cId oldExam = do
] ]
warnValidation MsgExamModeSchoolDiscouraged . not $ evalExamModeDNF schoolExamDiscouragedModes efExamMode warnValidation MsgExamModeSchoolDiscouraged . not $ evalExamModeDNF schoolExamDiscouragedModes efExamMode
unless (has (_Just . _examStaff . _Nothing) oldExam) $
guardValidation MsgExamStaffRequired $ isn't _Nothing efStaff

View File

@ -50,6 +50,7 @@ postCExamNewR tid ssh csh = do
, examPublicStatistics = efPublicStatistics , examPublicStatistics = efPublicStatistics
, examDescription = efDescription , examDescription = efDescription
, examExamMode = efExamMode , examExamMode = efExamMode
, examStaff = efStaff
} }
whenIsJust insertRes $ \examid -> do whenIsJust insertRes $ \examid -> do
insertMany_ insertMany_
@ -74,6 +75,8 @@ postCExamNewR tid ssh csh = do
examOccurrenceDescription = eofDescription examOccurrenceDescription = eofDescription
] ]
insertMany_ [ ExamOfficeSchool ssh' examid | ssh' <- Set.toList efOfficeSchools ]
let (invites, adds) = partitionEithers $ Set.toList efCorrectors let (invites, adds) = partitionEithers $ Set.toList efCorrectors
insertMany_ [ ExamCorrector{..} insertMany_ [ ExamCorrector{..}
| let examCorrectorExam = examid | let examCorrectorExam = examid

View File

@ -26,7 +26,7 @@ getEShowR tid ssh csh examn = do
cTime <- liftIO getCurrentTime cTime <- liftIO getCurrentTime
mUid <- maybeAuthId mUid <- maybeAuthId
(Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown) <- runDB $ do (Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools) <- runDB $ do
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
school <- getJust examCourse >>= belongsToJust courseSchool school <- getJust examCourse >>= belongsToJust courseSchool
@ -83,7 +83,14 @@ getEShowR tid ssh csh examn = do
lecturerInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR lecturerInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR
return (exam, school, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown) staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
extraSchools <- E.select . E.from $ \(school' `E.InnerJoin` examOfficeSchool) -> do
E.on $ school' E.^. SchoolId E.==. examOfficeSchool E.^. ExamOfficeSchoolSchool
E.where_ $ examOfficeSchool E.^. ExamOfficeSchoolExam E.==. E.val eId
return school'
return (exam, school, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools)
let occurrenceNamesShown = lecturerInfoShown let occurrenceNamesShown = lecturerInfoShown
partNumbersShown = lecturerInfoShown partNumbersShown = lecturerInfoShown

View File

@ -37,7 +37,7 @@ getLegalR =
-- | Allgemeine Informationen -- | Allgemeine Informationen
getInfoR :: Handler Html getInfoR :: Handler Html
getInfoR = do getInfoR = do
changelogEntries' <- runDB $ selectList [] [] changelogEntries' <- runDB $ selectList [ ChangelogItemFirstSeenItem <-. universeF ] []
let changelogEntries = Map.fromListWith Set.union let changelogEntries = Map.fromListWith Set.union
[ (Down changelogItemFirstSeenFirstSeen, Set.singleton changelogItemFirstSeenItem) [ (Down changelogItemFirstSeenFirstSeen, Set.singleton changelogItemFirstSeenItem)
| Entity _ ChangelogItemFirstSeen{..} <- changelogEntries' | Entity _ ChangelogItemFirstSeen{..} <- changelogEntries'

View File

@ -817,8 +817,9 @@ postAuthPredsR = do
| authTag `Set.member` blacklist = aforced checkBoxField (fslI authTag) (authTagIsActive def authTag) | authTag `Set.member` blacklist = aforced checkBoxField (fslI authTag) (authTagIsActive def authTag)
| otherwise = fromMaybe False <$> aopt checkBoxField (fslI authTag) (Just . Just $ authTagCurrentActive authTag) | otherwise = fromMaybe False <$> aopt checkBoxField (fslI authTag) (Just . Just $ authTagCurrentActive authTag)
((authActiveRes, authActiveWidget), authActiveEnctype) <- runFormPost . renderAForm FormStandard ((authActiveRes, authActiveWidget), authActiveEnctype) <- runFormPost . renderAForm FormStandard $ (,)
$ AuthTagActive <$> funcForm taForm (fslI MsgActiveAuthTags) True <$> apopt checkBoxField (fslI MsgActiveAuthTagsSaveCookie & setTooltip MsgActiveAuthTagsSaveCookieTip) (Just False)
<*> fmap AuthTagActive (funcForm taForm (fslI MsgActiveAuthTags) True)
mReferer <- runMaybeT $ do mReferer <- runMaybeT $ do
param <- MaybeT (lookupGetParam $ toPathPiece GetReferer) <|> MaybeT (lookupPostParam $ toPathPiece GetReferer) param <- MaybeT (lookupGetParam $ toPathPiece GetReferer) <|> MaybeT (lookupPostParam $ toPathPiece GetReferer)
@ -837,7 +838,10 @@ postAuthPredsR = do
^{authActiveWidget} ^{authActiveWidget}
|] |]
formResult authActiveRes $ \authTagActive -> do formResult authActiveRes $ \(saveCookie, authTagActive) -> do
when saveCookie $ if
| authTagActive == def -> deleteRegisteredCookie CookieActiveAuthTags
| otherwise -> setRegisteredCookieJson CookieActiveAuthTags $ authTagActive ^. _ReducedActiveAuthTags
setSessionJson SessionActiveAuthTags authTagActive setSessionJson SessionActiveAuthTags authTagActive
modifySessionJson SessionInactiveAuthTags . fmap $ Set.filter (not . authTagIsActive authTagActive) modifySessionJson SessionInactiveAuthTags . fmap $ Set.filter (not . authTagIsActive authTagActive)
addMessageI Success MsgAuthPredsActiveChanged addMessageI Success MsgAuthPredsActiveChanged

View File

@ -107,6 +107,7 @@ sinkAllocationPriorities allocId = fmap getSum . C.foldMapM . ifoldMapM $ \matr
computeAllocation :: Entity Allocation computeAllocation :: Entity Allocation
-> Maybe (Set CourseId) -- ^ Optionally restrict allocation to only consider the given courses -> Maybe (Set CourseId) -- ^ Optionally restrict allocation to only consider the given courses
-> DB ( AllocationFingerprint -> DB ( AllocationFingerprint
, Set CourseId
, Set (UserId, CourseId) , Set (UserId, CourseId)
, Seq MatchingLogRun , Seq MatchingLogRun
) )
@ -162,6 +163,7 @@ computeAllocation (Entity allocId Allocation{allocationMatchingSeed}) cRestr = d
, allocationCourse E.^. AllocationCourseMinCapacity E.-. participants , allocationCourse E.^. AllocationCourseMinCapacity E.-. participants
) )
let capacities = Map.filter (maybe True (> 0)) . Map.fromList $ (view (_1 . _entityVal . _allocationCourseCourse) &&& view (_2 . _Value)) <$> courses' let capacities = Map.filter (maybe True (> 0)) . Map.fromList $ (view (_1 . _entityVal . _allocationCourseCourse) &&& view (_2 . _Value)) <$> courses'
eligibleCourses = setOf (folded . _1 . _entityVal . _allocationCourseCourse) courses'
applications' <- selectList [ CourseApplicationAllocation ==. Just allocId ] [] applications' <- selectList [ CourseApplicationAllocation ==. Just allocId ] []
excludedMatchings <- flip execStateT mempty . forM_ applications' $ \(Entity _ CourseApplication{..}) -> do excludedMatchings <- flip execStateT mempty . forM_ applications' $ \(Entity _ CourseApplication{..}) -> do
@ -254,7 +256,7 @@ computeAllocation (Entity allocId Allocation{allocationMatchingSeed}) cRestr = d
| not $ null belowMin -> allocationLoop $ cs <> Set.fromList belowMin | not $ null belowMin -> allocationLoop $ cs <> Set.fromList belowMin
| otherwise -> return allocs | otherwise -> return allocs
return . (\(ms, mLog) -> (fingerprint, ms, mLog)) $!! runWriter (allocationLoop Set.empty) return . (\(ms, mLog) -> (fingerprint, eligibleCourses, ms, mLog)) $!! runWriter (allocationLoop Set.empty)
doAllocation :: AllocationId doAllocation :: AllocationId

View File

@ -34,7 +34,7 @@ resultIsSynced authId examResult = (hasSchool E.&&. allSchools) E.||. (E.not_ ha
examOfficeExamResultAuth :: E.SqlExpr (E.Value UserId) -- ^ office examOfficeExamResultAuth :: E.SqlExpr (E.Value UserId) -- ^ office
-> E.SqlExpr (Entity ExamResult) -> E.SqlExpr (Entity ExamResult)
-> E.SqlExpr (E.Value Bool) -> E.SqlExpr (E.Value Bool)
examOfficeExamResultAuth authId examResult = authByUser E.||. authByField E.||. authBySchool examOfficeExamResultAuth authId examResult = authByUser E.||. authByField E.||. authBySchool E.||. authByExtraSchool
where where
cId = E.subSelectForeign examResult ExamResultExam (\exam -> E.subSelectForeign exam ExamCourse (E.^. CourseId)) cId = E.subSelectForeign examResult ExamResultExam (\exam -> E.subSelectForeign exam ExamCourse (E.^. CourseId))
@ -67,3 +67,9 @@ examOfficeExamResultAuth authId examResult = authByUser E.||. authByField E.||.
E.on $ course E.^. CourseSchool E.==. userFunction E.^. UserFunctionSchool E.on $ course E.^. CourseSchool E.==. userFunction E.^. UserFunctionSchool
E.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice E.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
E.where_ $ userFunction E.^. UserFunctionUser E.==. authId E.where_ $ userFunction E.^. UserFunctionUser E.==. authId
authByExtraSchool = E.exists . E.from $ \(userFunction `E.InnerJoin` examSchool) -> do
E.on $ userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
E.&&. userFunction E.^. UserFunctionSchool E.==. examSchool E.^. ExamOfficeSchoolSchool
E.where_ $ examSchool E.^. ExamOfficeSchoolExam E.==. examResult E.^. ExamResultExam
E.where_ $ userFunction E.^. UserFunctionUser E.==. authId

View File

@ -44,6 +44,8 @@ import qualified Data.Set as Set
import Data.Map ((!), (!?)) import Data.Map ((!), (!?))
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Vector as Vector
import qualified Data.HashMap.Lazy as HashMap import qualified Data.HashMap.Lazy as HashMap
import Control.Monad.Writer.Class import Control.Monad.Writer.Class
@ -495,7 +497,7 @@ termsAllowedField = selectField $ do
optionsPersistKey termFilter [Desc TermStart] termName optionsPersistKey termFilter [Desc TermStart] termName
termField :: Field Handler TermId termField :: Field Handler TermId
termField = selectField $ optionsPersistKey [] [Asc TermName] termName termField = selectField $ optionsPersistKey [] [Desc TermStart] termName
termsSetField :: [TermId] -> Field Handler TermId termsSetField :: [TermId] -> Field Handler TermId
termsSetField tids = selectField $ optionsPersistKey [TermName <-. (unTermKey <$> tids)] [Desc TermStart] termName termsSetField tids = selectField $ optionsPersistKey [TermName <-. (unTermKey <$> tids)] [Desc TermStart] termName
@ -1660,6 +1662,96 @@ multiUserField onlySuggested suggestions = Field{..}
) )
Nothing -> E.true Nothing -> E.true
userField :: forall m.
( MonadHandler m
, HandlerSite m ~ UniWorX
)
=> Bool -- ^ Only resolve suggested users?
-> Maybe (E.SqlQuery (E.SqlExpr (Entity User))) -- ^ Suggested users
-> Field m (Either UserEmail UserId)
userField onlySuggested suggestions = Field{..}
where
lookupExpr
| onlySuggested = suggestions
| otherwise = Just $ E.from return
fieldEnctype = UrlEncoded
fieldView theId name attrs val isReq = do
val' <- case val of
Left t -> return t
Right v -> case v of
Right uid -> case lookupExpr of
Nothing -> return mempty
Just lookupExpr' -> do
dbRes <- liftHandler . runDB . E.select $ do
user <- lookupExpr'
E.where_ $ user E.^. UserId E.==. E.val uid
return $ user E.^. UserEmail
case dbRes of
[E.Value email] -> return $ CI.original email
_other -> return mempty
Left email -> return $ CI.original email
datalistId <- maybe (return $ error "Not to be used") (const newIdent) suggestions
[whamlet|
$newline never
<input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{val'}" :isJust suggestions:list=#{datalistId}>
|]
whenIsJust suggestions $ \suggestions' -> do
suggestedEmails <- fmap (Map.assocs . Map.fromListWith min . map (over _2 E.unValue . over _1 E.unValue)) . liftHandler . runDB . E.select $ do
user <- suggestions'
return ( E.case_
[ E.when_ (unique UserDisplayEmail user)
E.then_ (user E.^. UserDisplayEmail)
, E.when_ (unique UserEmail user)
E.then_ (user E.^. UserEmail)
]
( E.else_ $ user E.^. UserIdent)
, user E.^. UserDisplayName
)
[whamlet|
$newline never
<datalist id=#{datalistId}>
$forall (email, dName) <- suggestedEmails
<option value=#{email}>
#{email} (#{dName})
|]
fieldParse (filter (not . Text.null) -> t : _) _ = runExceptT . fmap Just $ do
email <- either (\errStr -> throwE . SomeMessage $ MsgInvalidEmail [st|#{t} (#{errStr})|]) (return . CI.mk . decodeUtf8 . Email.toByteString) $ Email.validate (encodeUtf8 t)
case lookupExpr of
Nothing -> return $ Left email
Just lookupExpr' -> do
dbRes <- fmap (setOf $ folded . _Value). liftHandler . runDB . E.select $ do
user <- lookupExpr'
E.where_ $ user E.^. UserIdent `E.ciEq` E.val email
E.||. ( user E.^. UserDisplayEmail `E.ciEq` E.val email
E.&&. unique UserDisplayEmail user
)
E.||. ( user E.^. UserEmail `E.ciEq` E.val email
E.&&. unique UserEmail user
)
return $ user E.^. UserId
if | Set.null dbRes
-> return $ Left email
| [uid] <- Set.toList dbRes
-> return $ Right uid
| otherwise
-> throwE $ SomeMessage MsgAmbiguousEmail
fieldParse _ _ = return $ Right Nothing
unique field user = case lookupExpr of
Just lookupExpr' -> E.not_ . E.exists $ do
user' <- lookupExpr'
E.where_ $ user' E.^. UserId E.!=. user E.^. UserId
E.&&. ( user' E.^. UserIdent `E.ciEq` user E.^. field
E.||. user' E.^. UserEmail `E.ciEq` user E.^. field
E.||. user' E.^. UserDisplayEmail `E.ciEq` user E.^. field
)
Nothing -> E.true
examResultField :: forall m res. examResultField :: forall m res.
( MonadHandler m ( MonadHandler m
, HandlerSite m ~ UniWorX , HandlerSite m ~ UniWorX
@ -2084,3 +2176,30 @@ examModeForm mPrev = examMode
examRequiredEquipmentToEither (ExamRequiredEquipmentCustom c) = Left c examRequiredEquipmentToEither (ExamRequiredEquipmentCustom c) = Left c
examRequiredEquipmentFromEither (Right p) = ExamRequiredEquipmentPreset p examRequiredEquipmentFromEither (Right p) = ExamRequiredEquipmentPreset p
examRequiredEquipmentFromEither (Left c) = ExamRequiredEquipmentCustom c examRequiredEquipmentFromEither (Left c) = ExamRequiredEquipmentCustom c
data AllocationPriority' = AllocationPriorityNumeric' | AllocationPriorityOrdinal'
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''AllocationPriority' $ camelToPathPiece' 2 . dropSuffix "'"
embedRenderMessage ''UniWorX ''AllocationPriority' id
classifyAllocationPriority :: AllocationPriority -> AllocationPriority'
classifyAllocationPriority = \case
AllocationPriorityNumeric{} -> AllocationPriorityNumeric'
AllocationPriorityOrdinal{} -> AllocationPriorityOrdinal'
allocationPriorityForm :: FieldSettings UniWorX
-> Maybe AllocationPriority
-> AForm Handler AllocationPriority
allocationPriorityForm fs mPrev = multiActionA opts fs $ classifyAllocationPriority <$> mPrev
where
opts = flip Map.fromSet (Set.fromList universeF) $ \case
AllocationPriorityNumeric' -> AllocationPriorityNumeric <$> apreq (checkMap toInts fromInts textField) (fslI MsgAllocationPriorityNumericValues & setTooltip MsgAllocationPriorityNumericValuesTip) (mPrev ^? _Just . _AllocationPriorityNumeric)
AllocationPriorityOrdinal' -> AllocationPriorityOrdinal <$> apreq (natFieldI MsgAllocationPriorityOrdinalValueNegative) (fslI MsgAllocationPriorityOrdinalValue & setTooltip MsgAllocationPriorityOrdinalValueTip) (mPrev ^? _Just . _AllocationPriorityOrdinal)
toInts t = fmap Vector.fromList . runExcept $ do
let ts = filter (not . Text.null) . map Text.strip $ Text.splitOn "," t
whenExceptT (null ts) MsgAllocationPriorityNumericNoValues
forM ts $ \t' -> maybeExceptT (MsgAllocationPriorityNumericNoParse t') . return $ readMay t'
fromInts = Text.intercalate ", " . map tshow . Vector.toList

View File

@ -138,16 +138,14 @@ dispatchNotificationAllocationUnratedApplications (otoList -> nAllocations) jRec
dispatchNotificationAllocationResults :: AllocationId -> UserId -> Handler () dispatchNotificationAllocationResults :: AllocationId -> UserId -> Handler ()
dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipient $ do dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipient $ do
(Allocation{..}, lecturerResults, participantResults) <- liftHandler . runDB $ do (Allocation{..}, lecturerResults, warnSubstituteCourses, participantResults) <- liftHandler . runDB $ do
allocation <- getJust nAllocation allocation <- getJust nAllocation
lecturerResults' <- E.select . E.from $ \(lecturer `E.InnerJoin` course) -> do lecturerResults' <- E.select . E.from $ \(lecturer `E.InnerJoin` course `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.on $ lecturer E.^. LecturerCourse E.==. course E.^. CourseId E.on $ lecturer E.^. LecturerCourse E.==. course E.^. CourseId
E.where_ $ lecturer E.^. LecturerUser E.==. E.val jRecipient E.where_ $ lecturer E.^. LecturerUser E.==. E.val jRecipient
E.&&. E.exists (E.from $ \allocationCourse -> E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation
)
let allocatedCount :: E.SqlExpr (E.Value Int64) let allocatedCount :: E.SqlExpr (E.Value Int64)
allocatedCount = E.subSelectCount . E.from $ \participant -> allocatedCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
@ -157,11 +155,12 @@ dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipi
participantCount = E.subSelectCount . E.from $ \participant -> participantCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
E.&&. participant E.^. CourseParticipantState E.==. E.val CourseParticipantActive E.&&. participant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return (course, allocatedCount, participantCount) return (course, allocationCourse, allocatedCount, participantCount)
let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, E.Value allocCount, E.Value partCount) -> SomeMessage $ if let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, _, E.Value allocCount, E.Value partCount) -> SomeMessage $ if
| allocCount == partCount -> MsgAllocationResultLecturerAll courseShorthand allocCount | allocCount == partCount -> MsgAllocationResultLecturerAll courseShorthand allocCount
| allocCount == 0 -> MsgAllocationResultLecturerNone courseShorthand | allocCount == 0 -> MsgAllocationResultLecturerNone courseShorthand
| otherwise -> MsgAllocationResultLecturer courseShorthand allocCount partCount | otherwise -> MsgAllocationResultLecturer courseShorthand allocCount partCount
warnSubstituteCourses = flip mapMaybe lecturerResults' $ \(Entity _ course, Entity _ AllocationCourse{..}, _, _) -> guardOn (isn't _Just allocationCourseAcceptSubstitutes) course
doParticipantResults <- E.selectExists . E.from $ \application -> doParticipantResults <- E.selectExists . E.from $ \application ->
E.where_ $ application E.^. CourseApplicationAllocation E.==. E.just (E.val nAllocation) E.where_ $ application E.^. CourseApplicationAllocation E.==. E.just (E.val nAllocation)
@ -177,7 +176,7 @@ dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipi
| otherwise -> Nothing | otherwise -> Nothing
cs -> Just $ map (courseShorthand . entityVal) cs cs -> Just $ map (courseShorthand . entityVal) cs
return (allocation, lecturerResults, participantResults) return (allocation, lecturerResults, warnSubstituteCourses, participantResults)
replaceMailHeader "Auto-Submitted" $ Just "auto-generated" replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
setSubjectI $ MsgMailSubjectAllocationResults allocationName setSubjectI $ MsgMailSubjectAllocationResults allocationName

View File

@ -173,7 +173,7 @@ migrateManual = do
, ("file_content_entry_chunk_hash", "CREATE INDEX file_content_entry_chunk_hash ON \"file_content_entry\" (chunk_hash)" ) , ("file_content_entry_chunk_hash", "CREATE INDEX file_content_entry_chunk_hash ON \"file_content_entry\" (chunk_hash)" )
] ]
recordedChangelogItems <- lift . lift $ selectList [] [] recordedChangelogItems <- lift . lift $ selectList [ ChangelogItemFirstSeenItem <-. universeF ] []
let missingChangelogItems = Set.toList $ Set.fromList universeF `Set.difference` recordedChangelogItems' let missingChangelogItems = Set.toList $ Set.fromList universeF `Set.difference` recordedChangelogItems'
where recordedChangelogItems' = Set.fromList [ changelogItemFirstSeenItem | Entity _ ChangelogItemFirstSeen{..} <- recordedChangelogItems ] where recordedChangelogItems' = Set.fromList [ changelogItemFirstSeenItem | Entity _ ChangelogItemFirstSeen{..} <- recordedChangelogItems ]
unless (null missingChangelogItems) $ do unless (null missingChangelogItems) $ do

View File

@ -142,4 +142,6 @@ changelogItemDays = Map.fromListWithKey (\k d1 d2 -> bool (error $ "Duplicate ch
, (ChangelogCourseVisibility, [day|2020-08-10|]) , (ChangelogCourseVisibility, [day|2020-08-10|])
, (ChangelogPersonalisedSheetFiles, [day|2020-08-10|]) , (ChangelogPersonalisedSheetFiles, [day|2020-08-10|])
, (ChangelogAbolishCourseAssociatedStudyFeatures, [day|2020-08-28|]) , (ChangelogAbolishCourseAssociatedStudyFeatures, [day|2020-08-28|])
, (ChangelogExamStaff, [day|2020-10-12|])
, (ChangelogExamAdditionalSchools, [day|2020-10-12|])
] ]

View File

@ -109,12 +109,15 @@ instance Default AuthTagActive where
_ -> True _ -> True
instance ToJSON AuthTagActive where instance ToJSON AuthTagActive where
toJSON v = toJSON . HashMap.fromList $ map (id &&& authTagIsActive v) universeF toJSON v = toJSON . HashMap.fromList $ map (toPathPiece &&& authTagIsActive v) universeF
instance FromJSON AuthTagActive where instance FromJSON AuthTagActive where
parseJSON = Aeson.withObject "AuthTagActive" $ \o -> do parseJSON = Aeson.withObject "AuthTagActive" $ \o -> do
o' <- parseJSON $ Aeson.Object o :: Aeson.Parser (HashMap AuthTag Bool) o' <- parseJSON $ Aeson.Object o :: Aeson.Parser (HashMap Text Bool)
return . AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n o' fmap toAuthTagActive . flip ifoldMapM o' $ \k v -> maybeT mempty $ do
k' <- hoistMaybe $ fromPathPiece k
return $ HashMap.singleton k' v
where toAuthTagActive o = AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n o
instance Hashable AuthTagActive where instance Hashable AuthTagActive where
hashWithSalt s = foldl' hashWithSalt s . authTagIsActive hashWithSalt s = foldl' hashWithSalt s . authTagIsActive
@ -128,6 +131,27 @@ derivePersistFieldJSON ''AuthTagActive
getSessionActiveAuthTags :: MonadHandler m => m AuthTagActive getSessionActiveAuthTags :: MonadHandler m => m AuthTagActive
getSessionActiveAuthTags = fromMaybe def <$> lookupSessionJson SessionActiveAuthTags getSessionActiveAuthTags = fromMaybe def <$> lookupSessionJson SessionActiveAuthTags
newtype ReducedActiveAuthTags = ReducedActiveAuthTags (HashMap AuthTag Bool)
deriving newtype (Monoid, Semigroup)
instance ToJSON ReducedActiveAuthTags where
toJSON (ReducedActiveAuthTags a) = toJSON $ HashMap.fromList [ (toPathPiece k, v) | (k, v) <- HashMap.toList a ]
instance FromJSON ReducedActiveAuthTags where
parseJSON = Aeson.withObject "ReducedActiveAuthTags" $ \o -> do
o' <- parseJSON $ Aeson.Object o :: Aeson.Parser (HashMap Text Bool)
fmap ReducedActiveAuthTags . flip ifoldMap o' $ \k v -> maybeT mempty $ do
k' <- hoistMaybe $ fromPathPiece k
return $ HashMap.singleton k' v
_ReducedActiveAuthTags :: Iso' AuthTagActive ReducedActiveAuthTags
_ReducedActiveAuthTags = iso toReducedActiveAuthTags fromReducedActiveAuthTags
where
toReducedActiveAuthTags a = ReducedActiveAuthTags . flip foldMap universeF $ \n -> if
| authTagIsActive a n /= authTagIsActive def n -> HashMap.singleton n $ authTagIsActive a n
| otherwise -> mempty
fromReducedActiveAuthTags (ReducedActiveAuthTags hm) = AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n hm
data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a } data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a }
deriving (Eq, Ord, Read, Show, Data, Generic, Typeable) deriving (Eq, Ord, Read, Show, Data, Generic, Typeable)

View File

@ -31,7 +31,7 @@ import Data.Char (isAscii)
import Data.Monoid (Last(..)) import Data.Monoid (Last(..))
data RegisteredCookie = CookieSession | CookieXSRFToken | CookieLang | CookieSystemMessageState data RegisteredCookie = CookieSession | CookieXSRFToken | CookieLang | CookieSystemMessageState | CookieActiveAuthTags
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
deriving anyclass (Universe, Finite, Hashable) deriving anyclass (Universe, Finite, Hashable)

View File

@ -1269,8 +1269,7 @@ mpreq :: (RenderMessage site (ValueRequired site), HandlerSite m ~ site, MonadHa
-- Otherwise acts exactly like `mopt`. -- Otherwise acts exactly like `mopt`.
mpreq f fs@FieldSettings{..} mx = do mpreq f fs@FieldSettings{..} mx = do
mr <- getMessageRender mr <- getMessageRender
(res, fv) <- mopt f fs (Just <$> mx) (res, fv') <- mpreq' f fs $ Just <$> mx
let fv' = fv { fvRequired = True }
return $ case res of return $ case res of
FormSuccess (Just res') FormSuccess (Just res')
-> (FormSuccess res', fv') -> (FormSuccess res', fv')
@ -1301,6 +1300,25 @@ wpreq :: (RenderMessage site (ValueRequired site), HandlerSite m ~ site, MonadHa
wpreq f fs mx = mFormToWForm $ mpreq f fs mx wpreq f fs mx = mFormToWForm $ mpreq f fs mx
mpreq' :: (HandlerSite m ~ site, MonadHandler m)
=> Field m a -> FieldSettings site -> Maybe (Maybe a) -> MForm m (FormResult (Maybe a), FieldView site)
-- ^ Pseudo required
--
-- `FieldView` has `fvRequired` set to `True`.
-- Otherwise acts exactly like `mopt`.
mpreq' f fs mx = do
(res, fv) <- mopt f fs mx
return (res, fv { fvRequired = True })
apreq' :: (HandlerSite m ~ site, MonadHandler m)
=> Field m a -> FieldSettings site -> Maybe (Maybe a) -> AForm m (Maybe a)
apreq' f fs mx = formToAForm $ over _2 pure <$> mpreq' f fs mx
wpreq' :: (HandlerSite m ~ site, MonadHandler m)
=> Field m a -> FieldSettings site -> Maybe (Maybe a) -> WForm m (FormResult (Maybe a))
wpreq' f fs mx = mFormToWForm $ mpreq' f fs mx
mpopt :: (RenderMessage site (ValueRequired site), HandlerSite m ~ site, MonadHandler m) mpopt :: (RenderMessage site (ValueRequired site), HandlerSite m ~ site, MonadHandler m)
=> Field m a -> FieldSettings site -> Maybe a -> MForm m (FormResult a, FieldView site) => Field m a -> FieldSettings site -> Maybe a -> MForm m (FormResult a, FieldView site)
-- ^ Pseudo optional -- ^ Pseudo optional

View File

@ -254,6 +254,8 @@ makeLenses_ ''WorkflowInstanceDescription
makeWrapped ''Textarea makeWrapped ''Textarea
makePrisms ''AllocationPriority
-- makeClassy_ ''Load -- makeClassy_ ''Load
-------------------------- --------------------------

View File

@ -47,6 +47,9 @@ $newline never
_{MsgSchool} _{MsgSchool}
<th .table__th> <th .table__th>
_{MsgCourse} _{MsgCourse}
$if eligibleCourses /= allocCourses
<th .table__th>
_{MsgAllocationCourseEligible}
<th .table__th> <th .table__th>
_{MsgCourseCapacity} _{MsgCourseCapacity}
<th .table__th> <th .table__th>
@ -72,6 +75,9 @@ $newline never
<div .table__td-content> <div .table__td-content>
<a href=@{CourseR courseTerm courseSchool courseShorthand CShowR}> <a href=@{CourseR courseTerm courseSchool courseShorthand CShowR}>
#{courseName} #{courseName}
$if eligibleCourses /= allocCourses
<td .table__td>
#{hasTickmark $ Set.member cid eligibleCourses}
<td .table__td> <td .table__td>
<div .table__td-content> <div .table__td-content>
$maybe capN <- courseCapacity $maybe capN <- courseCapacity

View File

@ -53,6 +53,15 @@ $newline never
^{iconTooltip (i18n MsgAllocationRegisterByStaffFromTip) Nothing True} ^{iconTooltip (i18n MsgAllocationRegisterByStaffFromTip) Nothing True}
<dd .deflist__dd> <dd .deflist__dd>
^{formatTimeRangeW SelFormatDateTime fromT allocationRegisterByStaffTo} ^{formatTimeRangeW SelFormatDateTime fromT allocationRegisterByStaffTo}
$if isAdmin
<dt .deflist__dt>
_{MsgAllocationNextSubstitutesDeadline} #
^{iconInvisible}
<dd .deflist__dd>
$maybe deadline <- nextSubstitutesDeadline
^{formatTimeW SelFormatDateTime deadline}
$nothing
_{MsgAllocationNextSubstitutesDeadlineNever}
$maybe fromT <- allocationRegisterByCourse $maybe fromT <- allocationRegisterByCourse
<dt .deflist__dt> <dt .deflist__dt>
_{MsgAllocationRegisterByCourseFrom} _{MsgAllocationRegisterByCourseFrom}

View File

@ -14,6 +14,16 @@ $if is _Just muid
#{courseName} #{courseName}
$if not courseVisible && mayEdit $if not courseVisible && mayEdit
\ #{iconInvisible} \ #{iconInvisible}
$if isAdmin
<div .allocation-course__admin-info>
<p>
$maybe deadline <- allocationCourseAcceptSubstitutes
_{MsgCourseAllocationCourseAcceptsSubstitutesUntil}: #
^{formatTimeW SelFormatDateTime deadline}
$nothing
_{MsgCourseAllocationCourseAcceptsSubstitutesNever}
$if allocationCourseAcceptSubstitutes >= Just now
\ ^{iconOK}
$if hasApplicationTemplate || is _Just courseApplicationsInstructions $if hasApplicationTemplate || is _Just courseApplicationsInstructions
<div .allocation-course__instructions-label .allocation__label> <div .allocation-course__instructions-label .allocation__label>
_{MsgCourseApplicationInstructionsApplication} _{MsgCourseApplicationInstructionsApplication}

View File

@ -136,6 +136,7 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
<p> <p>
$maybe visFrom <- courseVisibleFrom $maybe visFrom <- courseVisibleFrom
^{formatTimeRangeW SelFormatDateTime visFrom courseVisibleTo} ^{formatTimeRangeW SelFormatDateTime visFrom courseVisibleTo}
<br />
$if NTop (Just now) < NTop courseVisibleFrom $if NTop (Just now) < NTop courseVisibleFrom
$if hasAllocationRegistrationOpen $if hasAllocationRegistrationOpen
_{MsgCourseInvisibleOverridenByAllocation} _{MsgCourseInvisibleOverridenByAllocation}

View File

@ -94,6 +94,16 @@ $maybe desc <- examDescription
$maybe closed <- examClosed $maybe closed <- examClosed
<dt .deflist__dt>_{MsgExamClosed} ^{isVisible False} <dt .deflist__dt>_{MsgExamClosed} ^{isVisible False}
<dd .deflist__dd>^{formatTimeW SelFormatDateTime closed} <dd .deflist__dd>^{formatTimeW SelFormatDateTime closed}
$maybe staff <- examStaff
$if staffInfoShown
<dd .deflist__dt>_{MsgExamStaff} ^{isVisible False}
<dt .deflist__dd>#{staff}
$if staffInfoShown && not (onull extraSchools)
<dd .deflist__dt>_{MsgExamExamOfficeSchools} ^{isVisible False}
<dt .deflist__dd>
<ul>
$forall Entity _ School{schoolName} <- extraSchools
<li>#{schoolName}
$if gradingShown $if gradingShown
$maybe gradingRule <- examGradingRule $maybe gradingRule <- examGradingRule
<dt .deflist__dt> <dt .deflist__dt>

View File

@ -0,0 +1,6 @@
$newline never
<td>
#{csrf}
^{fvWidget addView}
<td>
^{fvWidget 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>
^{fvWidget (delButtons ! coord)}
<tfoot>
<tr .massinput__cell.massinput__cell--add>
^{addWdgts ! (0, 0)}

View File

@ -0,0 +1,2 @@
$newline never
Kurse, die an Zentralanmeldungen teilnehmen, können nun angeben bis zu welcher Frist sie Nachrücker akzeptieren können

View File

@ -0,0 +1,2 @@
$newline never
Courses which participate in a central allocation may now specify a deadline up to which they are able to accept substitute registrations

View File

@ -0,0 +1,2 @@
$newline never
Aktive Authorisierungsprädikate können nun in einem persistenten Cookie gespeichert werden

View File

@ -0,0 +1,2 @@
$newline never
Active authorisation predicates can now be saved as a persistent cookie

View File

@ -0,0 +1,2 @@
$newline never
Mit Kursen assoziierte Prüfungen können nun auch weitere Institute angeben, die vollen Zugriff auf die Prüfungsleistungen erhalten

View File

@ -0,0 +1,2 @@
$newline never
Exams associated with courses may now also specify additional departments that then have full access to exam achievements

View File

@ -0,0 +1,2 @@
$newline never
Mit Kursen assoziierte Prüfungen müssen nun auch verantwortliche Hochschullehrer bzw. Prüfer angeben

View File

@ -0,0 +1,2 @@
$newline never
Exams associated with courses now also need to specify responsible university teachers/examiners

View File

@ -16,6 +16,15 @@ $newline never
<p> <p>
_{SomeMessage MsgAllocationResultsTip} _{SomeMessage MsgAllocationResultsTip}
$if not (null warnSubstituteCourses)
<p>
_{SomeMessage MsgAllocationResultsLecturerSubstituteCoursesWarning}
<ul>
$forall Course{courseTerm, courseSchool, courseShorthand, courseName} <- warnSubstituteCourses
<li>
<a href=@{CourseR courseTerm courseSchool courseShorthand CEditR}>
#{courseName}
$if not (null lecturerResults) $if not (null lecturerResults)
<p> <p>
_{SomeMessage MsgAllocationResultsLecturer} _{SomeMessage MsgAllocationResultsLecturer}

View File

@ -677,6 +677,7 @@ fillDb = do
, examSynchronicity = Just $ ExamSynchronicityPreset ExamSynchronous , examSynchronicity = Just $ ExamSynchronicityPreset ExamSynchronous
, examRequiredEquipment = Just $ ExamRequiredEquipmentPreset ExamRequiredEquipmentNone , examRequiredEquipment = Just $ ExamRequiredEquipmentPreset ExamRequiredEquipmentNone
} }
, examStaff = Just "Hofmann"
} }
void . insertMany $ map (\u -> ExamRegistration examFFP u Nothing now) void . insertMany $ map (\u -> ExamRegistration examFFP u Nothing now)
[ fhamann [ fhamann
@ -1055,8 +1056,8 @@ fillDb = do
, allocationOverrideDeregister = Just $ termTime True Summer 1 False Monday toMidnight , allocationOverrideDeregister = Just $ termTime True Summer 1 False Monday toMidnight
, allocationMatchingSeed = aSeedFunc , allocationMatchingSeed = aSeedFunc
} }
insert_ $ AllocationCourse funAlloc pmo 100 insert_ $ AllocationCourse funAlloc pmo 100 Nothing
insert_ $ AllocationCourse funAlloc ffp 2 insert_ . AllocationCourse funAlloc ffp 2 . Just $ 2300 `addUTCTime` now
void . insertMany $ map (\(u, pState) -> CourseParticipant ffp u now (Just funAlloc) pState) void . insertMany $ map (\(u, pState) -> CourseParticipant ffp u now (Just funAlloc) pState)
[ (svaupel, CourseParticipantInactive False) [ (svaupel, CourseParticipantInactive False)
@ -1195,6 +1196,8 @@ fillDb = do
cap <- getRandomR (10,50) cap <- getRandomR (10,50)
minCap <- round . (* fromIntegral cap) <$> getRandomR (0, 0.5 :: Double) minCap <- round . (* fromIntegral cap) <$> getRandomR (0, 0.5 :: Double)
substitutesUntil <- (`addUTCTime` now) . fromInteger <$> getRandomR (900,2300)
cid <- insert' Course cid <- insert' Course
{ courseName = CI.mk [st|Zentralanmeldungskurs #{n} (#{csh})|] { courseName = CI.mk [st|Zentralanmeldungskurs #{n} (#{csh})|]
@ -1219,7 +1222,7 @@ fillDb = do
, courseDeregisterNoShow = False , courseDeregisterNoShow = False
} }
insert_ $ CourseEdit gkleen now cid insert_ $ CourseEdit gkleen now cid
insert_ $ AllocationCourse bigAlloc cid minCap insert_ . AllocationCourse bigAlloc cid minCap $ Just substitutesUntil
-- void . insert' $ Lecturer gkleen cid CourseLecturer -- void . insert' $ Lecturer gkleen cid CourseLecturer
return cid return cid