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.
## [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)

View File

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

View File

@ -776,10 +776,13 @@ section
.allocation__courses
margin: 20px 0 0 40px
.form-group__input > &
margin: 0
.allocation-course
display: grid
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
margin: 12px 0
padding: 0 10px 12px 7px
@ -830,10 +833,14 @@ section
text-align: right
padding-top: 6px
.allocation-course__admin-info
@extend .explanation
grid-area: admin-info
@media (max-width: 426px)
.allocation-course
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
padding-top: 0

View File

@ -204,6 +204,8 @@ CourseAllocationOption term@Text name@Text: #{name} (#{term})
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
CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein
CourseAllocationCourseAcceptsSubstitutesUntil: Akzeptiert Nachrücker bis
CourseAllocationCourseAcceptsSubstitutesNever: Akzeptiert keine Nachrücker
CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung
CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
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.
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
InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet
@ -1376,6 +1380,7 @@ MenuAllocationUsers: Bewerber
MenuAllocationPriorities: Zentrale Dringlichkeiten
MenuAllocationCompute: Platzvergabe berechnen
MenuAllocationAccept: Platzvergabe akzeptieren
MenuAllocationAddUser: Bewerber hinzufügen
MenuFaq: FAQ
MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen
MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
@ -1463,6 +1468,7 @@ BreadcrumbAllocationUsers: Bewerber
BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten
BreadcrumbAllocationCompute: Platzvergabe berechnen
BreadcrumbAllocationAccept: Platzvergabe akzeptieren
BreadcrumbAllocationAddUser: Bewerber hinzufügen
BreadcrumbMessageHide: Verstecken
BreadcrumbFaq: FAQ
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
@ -1496,9 +1502,9 @@ ExternalExamUsers coursen@CourseName examn@ExamName: Teilnehmer: #{coursen}, #{e
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
AuthPredsActiveChanged: Authorisierungseinstellungen für aktuelle Sitzung gespeichert
AuthPredsActiveChanged: Authorisierungseinstellungen gespeichert
AuthTagFree: Seite ist universell zugänglich
AuthTagAdmin: Nutzer ist Administrator
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
ExamRoomAssigned: Zugeteilt
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
@ -1876,6 +1887,7 @@ ExamFormAutomaticFunctions: Automatische Funktionen
ExamFormCorrection: Korrektur
ExamFormParts: Teile
ExamFormMode: Ausgestaltung der Prüfung
ExamFormGrades: Prüfungsleistungen
ExamModeFormNone: Keine Angabe
ExamModeFormCustom: Benutzerdefiniert
@ -2290,6 +2302,8 @@ AllocationNotificationNewCourseSuccessForceOff: Sie werden nicht benachrichtigt,
AllocationNotificationNewCourseCurrentlyOff: Aktuell würden Sie keine Benachrichtigung erhalten.
AllocationNotificationNewCourseCurrentlyOn: Aktuell würden Sie benachrichtigt werden.
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
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
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.
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
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
@ -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}
AllocationResultLecturerAll csh@CourseShorthand count@Int64: #{count} 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:
AllocationNoResultsStudent: Sie haben leider keine Plätze 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
AllocationPrioritiesNumeric: Numerische Dringlichkeiten
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
AllocationPrioritiesFile: CSV-Datei
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"}
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
ExampleUser1Surname: 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.
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.
AllocationCourseRestrictionNone: Nicht einschränken
AllocationCourseRestrictionSubstitutes: Kurse, die aktuell Nachrücker azkeptieren
AllocationCourseRestrictionCustom: Benutzerdefiniert
AllocationRestrictCoursesSelection: Kurse
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!
@ -2774,6 +2816,7 @@ AllocationOfferedPlaces: Angebotene Plätze
AllocationUserNewMatches: Neue Zuteilungen
AllocationUsersCount: Teilnehmer
AllocationCoursesCount: Kurse
AllocationCourseEligible: Berücksichtigt
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
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
CourseAllocationCourseAcceptsSubstitutesUntil: Accepts substitutes until
CourseAllocationCourseAcceptsSubstitutesNever: Does not accept substitutes
CourseApplicationInstructions: Instructions for application
CourseApplicationInstructionsTip: Will be shown to students if they decide to apply for this course
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.
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
InvalidDateTimeFormat: Invalid date and time format. YYYY-MM-DDTHH:MM[:SS] expected
@ -1376,6 +1381,7 @@ MenuAllocationUsers: Applicants
MenuAllocationPriorities: Central priorities
MenuAllocationCompute: Compute allocation
MenuAllocationAccept: Accept allocation
MenuAllocationAddUser: Add applicant
MenuFaq: FAQ
MenuSheetPersonalisedFiles: Download personalised sheet files
MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files
@ -1453,6 +1459,7 @@ BreadcrumbAllocationUsers: Applicants
BreadcrumbAllocationPriorities: Central priorities
BreadcrumbAllocationCompute: Compute allocation
BreadcrumbAllocationAccept: Accept allocation
BreadcrumbAllocationAddUser: Add applicant
BreadcrumbMessageHide: Hide
BreadcrumbFaq: FAQ
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
@ -1470,9 +1477,9 @@ ExternalExamUsers coursen examn: Exam participants: #{coursen}, #{examn}
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
AuthPredsActiveChanged: Authorisation settings saved for the current session
AuthPredsActiveChanged: Successfully saved authorisation settings
AuthTagFree: Page is freely accessable
AuthTagAdmin: User is administrator
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
ExamRoomAssigned: Assigned
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
@ -1848,6 +1860,7 @@ ExamFormAutomaticFunctions: Automatic functions
ExamFormCorrection: Correction
ExamFormParts: Exam parts
ExamFormMode: Exam design
ExamFormGrades: Exam achievements
ExamModeFormNone: Not specified
ExamModeFormCustom: Custom
@ -2262,6 +2275,8 @@ AllocationNotificationNewCourseSuccessForceOff: You will not be notified if a ne
AllocationNotificationNewCourseCurrentlyOff: Currently you would not receive a notification.
AllocationNotificationNewCourseCurrentlyOn: Currently you would be notified.
AllocationNotificationLoginFirst: To change your notification settings, please log in first.
AllocationNextSubstitutesDeadline: Next course accepts substitutes until
AllocationNextSubstitutesDeadlineNever: No course currently accepts substitutes
AllocationSchoolShort: Department
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
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.
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
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
@ -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}
AllocationResultLecturerAll csh count: #{count} #{pluralEN count "participant" "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:
AllocationNoResultsStudent: Unfortunately you were not placed in any courses.
AllocationResultStudent csh: You were placed in #{csh}.
@ -2709,12 +2727,34 @@ AllocationUsersCsvName tid ssh ash: #{foldCase (termToText (unTermKey tid))}-#{f
AllocationPrioritiesMode: Mode
AllocationPrioritiesNumeric: Numeric priorities
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
AllocationPrioritiesFile: CSV file
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
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
ExampleUser1Surname: 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.
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.
AllocationCourseRestrictionNone: Don't restrict
AllocationCourseRestrictionSubstitutes: Courses which currently allow substitute registrations
AllocationCourseRestrictionCustom: Custom
AllocationRestrictCoursesSelection: Courses
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!
@ -2747,6 +2790,7 @@ AllocationOfferedPlaces: Offered places
AllocationUserNewMatches: New allocations
AllocationUsersCount: Participants
AllocationCoursesCount: Courses
AllocationCourseEligible: Considered
CourseOption tid ssh coursen: #{tid} - #{ssh} - #{coursen}

View File

@ -36,6 +36,7 @@ AllocationCourse
allocation AllocationId
course CourseId
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
AllocationUser

View File

@ -18,6 +18,7 @@ Exam
gradingMode ExamGradingMode
description Html Maybe
examMode ExamMode
staff Text Maybe
UniqueExam course name
ExamPart
exam ExamId
@ -67,4 +68,8 @@ ExamCorrector
ExamPartCorrector
part ExamPartId
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",
"version": "20.8.1",
"version": "20.11.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

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

1
routes
View File

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

View File

@ -160,6 +160,7 @@ instance YesodAuth UniWorX where
app <- getYesod
let mr | Just lang <- mlang = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang
| otherwise = renderMessage app []
addMessage Success . toHtml $ mr Auth.NowLoggedIn
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
AComputeR -> i18nCrumb MsgBreadcrumbAllocationCompute . 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 (ParticipantsR _ _) = i18nCrumb MsgBreadcrumbParticipants $ Just ParticipantsListR
@ -1382,6 +1383,17 @@ pageActions (AllocationR tid ssh ash AUsersR) = return
}
, 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
participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR

View File

@ -26,7 +26,7 @@ yesodMiddleware :: ( BearerAuthSite UniWorX
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
)
=> 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
dryRunMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
dryRunMiddleware handler = do
@ -98,6 +98,14 @@ yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . ob
Nothing -> return ()
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.
( 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.List 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.Compute as Handler.Allocation
import Handler.Allocation.Accept as Handler.Allocation

View File

@ -11,6 +11,7 @@ import Handler.Utils.Allocation
import Data.Map ((!?))
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Database.Esqueleto as E
import qualified Control.Monad.State.Class as State
@ -25,12 +26,13 @@ newtype SessionDataAllocationResults = SessionDataAllocationResults
)
( UTCTime
, AllocationFingerprint
, Set CourseId
, Set (UserId, CourseId)
, Seq MatchingLogRun
)
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
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
@ -47,11 +49,11 @@ instance Button UniWorX AllocationAcceptButton where
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
Allocation{..} <- MaybeT $ get aId
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
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
return (allocationCourse, course, participants)
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
allocationCourses' <- hoistMaybe $
@ -137,9 +140,9 @@ postAAcceptR tid ssh ash = do
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) _) $
Map.filterWithKey (\(tid', ssh', ash') (_, allocFp', _, _) ->
Map.filterWithKey (\(tid', ssh', ash') (_, allocFp', _, _, _) ->
or [ tid' /= tid
, ssh' /= ssh
, 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
-> CourseId
-> UserId
-> Maybe UserId
-> ApplicationFormMode -- ^ Which parts of the shared form to display
-> Html -> MForm Handler (FormResult ApplicationForm, ApplicationFormView)
applicationForm maId@(is _Just -> isAlloc) cid uid ApplicationFormMode{..} csrf = do
-> Maybe Html -- ^ If @Just@ also include action buttons for usage as standalone form
-> MForm Handler (FormResult ApplicationForm, ApplicationFormView)
applicationForm maId@(is _Just -> isAlloc) cid muid ApplicationFormMode{..} mcsrf = 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])
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.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.val maId
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 _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
| afmLecturer
@ -251,7 +254,7 @@ editApplicationR :: Maybe AllocationId
editApplicationR maId uid cid mAppId afMode allowAction postAction = do
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
if

View File

@ -13,6 +13,7 @@ import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
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)
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 aId = hoistAForm liftHandler $
optionalActionA selectCourses (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just False)
restrictCourses aId = hoistAForm liftHandler $ multiActionA restrictOpts (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just AllocationCourseRestrictionNone)
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
where
query = E.from $ \(course `E.InnerJoin` allocationCourse) -> do
@ -115,9 +134,9 @@ postAComputeR tid ssh ash = do
formResult computeFormRes $ \AllocationComputeForm{..} -> do
now <- liftIO getCurrentTime
(allocFp, allocMatching, allocLog) <- computeAllocation aEnt acfRestrictCourses
(allocFp, eligibleCourses, allocMatching, allocLog) <- computeAllocation aEnt acfRestrictCourses
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
redirect $ AllocationR tid ssh ash AUsersR -- Redirect aborts transaction for safety

View File

@ -49,18 +49,20 @@ postAShowR tid ssh ash = do
ata <- getSessionActiveAuthTags
let
resultCourse :: Simple Field1 a (Entity Course) => Lens' a (Entity Course)
resultCourse :: _ => Lens' a (Entity Course)
resultCourse = _1
resultCourseApplication :: Simple Field2 a (Maybe (Entity CourseApplication)) => Traversal' a (Entity CourseApplication)
resultCourseApplication :: _ => Traversal' a (Entity CourseApplication)
resultCourseApplication = _2 . _Just
resultHasTemplate :: Simple Field3 a (E.Value Bool) => Lens' a Bool
resultHasTemplate :: _ => Lens' a Bool
resultHasTemplate = _3 . _Value
resultIsRegistered :: Simple Field4 a (E.Value Bool) => Lens' a Bool
resultIsRegistered :: _ => Lens' a Bool
resultIsRegistered = _4 . _Value
resultCourseVisible :: Simple Field5 a (E.Value Bool) => Lens' a Bool
resultCourseVisible :: _ => Lens' a Bool
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
school <- getJust allocationSchool
@ -79,15 +81,24 @@ postAShowR tid ssh ash = do
E.orderBy [E.asc $ course E.^. CourseName]
let hasTemplate = E.exists . E.from $ \courseAppInstructionFile ->
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
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
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
let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
@ -146,11 +157,12 @@ postAShowR tid ssh ash = do
mApp = cEntry ^? resultCourseApplication
isRegistered = cEntry ^. resultIsRegistered
courseVisible = cEntry ^. resultCourseVisible
AllocationCourse{..} = cEntry ^. resultAllocationCourse
cID <- encrypt cid :: WidgetFor UniWorX CryptoUUIDCourse
mayApply <- hasWriteAccessTo . AllocationR tid ssh ash $ AApplyR cID
mayEdit <- hasWriteAccessTo $ CourseR tid ssh 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
Nothing -> return . AllocationR tid ssh ash $ AApplyR cID
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
allocMatching <- runMaybeT $ do
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)))
csvName <- getMessageRender <*> pure (MsgAllocationUsersCsvName tid ssh ash)

View File

@ -57,6 +57,7 @@ data CourseForm = CourseForm
data AllocationCourseForm = AllocationCourseForm
{ acfAllocation :: AllocationId
, acfMinCapacity :: Int
, acfAcceptSubstitutes :: Maybe UTCTime
, acfDeregisterNoShow :: Bool
}
@ -98,6 +99,7 @@ allocationCourseToForm :: Entity Course -> Entity AllocationCourse -> Allocation
allocationCourseToForm (Entity _ Course{..}) (Entity _ AllocationCourse{..}) = AllocationCourseForm
{ acfAllocation = allocationCourseAllocation
, acfMinCapacity = allocationCourseMinCapacity
, acfAcceptSubstitutes = allocationCourseAcceptSubstitutes
, acfDeregisterNoShow = courseDeregisterNoShow
}
@ -265,6 +267,7 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
in AllocationCourseForm
<$> ainp allocField (fslI MsgCourseAllocation) (fmap acfAllocation $ 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)
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
Just AllocationCourseForm{..} -> do
void $ upsert AllocationCourse
{ allocationCourseAllocation = acfAllocation
, allocationCourseCourse = cid
, allocationCourseMinCapacity = acfMinCapacity
{ allocationCourseAllocation = acfAllocation
, allocationCourseCourse = cid
, allocationCourseMinCapacity = acfMinCapacity
, allocationCourseAcceptSubstitutes = acfAcceptSubstitutes
}
[ AllocationCourseAllocation =. acfAllocation
, AllocationCourseCourse =. cid
, AllocationCourseMinCapacity =. acfMinCapacity
[ AllocationCourseAllocation =. acfAllocation
, AllocationCourseCourse =. cid
, AllocationCourseMinCapacity =. acfMinCapacity
, AllocationCourseAcceptSubstitutes =. acfAcceptSubstitutes
]
when (Just acfAllocation /= fmap entityKey prevAllocation) $

View File

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

View File

@ -28,7 +28,6 @@ import Text.Blaze.Html.Renderer.String (renderHtml)
data ExamForm = ExamForm
{ efName :: ExamName
, efDescription :: Maybe Html
, efGradingMode :: ExamGradingMode
, efStart :: Maybe UTCTime
, efEnd :: Maybe UTCTime
, efVisibleFrom :: Maybe UTCTime
@ -43,6 +42,9 @@ data ExamForm = ExamForm
, efBonusRule :: Maybe ExamBonusRule
, efOccurrenceRule :: ExamOccurrenceRule
, efExamMode :: ExamMode
, efGradingMode :: ExamGradingMode
, efOfficeSchools :: Set SchoolId
, efStaff :: Maybe Text
, efCorrectors :: Set (Either UserEmail UserId)
, efExamParts :: Set ExamPartForm
}
@ -103,7 +105,6 @@ examForm template html = do
flip (renderAForm FormStandard) html $ ExamForm
<$> areq ciField (fslpI MsgExamName (mr MsgExamName) & setTooltip MsgExamNameTip) (efName <$> template)
<*> aopt htmlField (fslI MsgExamDescription) (efDescription <$> template)
<*> apopt (selectField optionsFinite) (fslI MsgExamGradingMode & setTooltip MsgExamGradingModeTip) (efGradingMode <$> template <|> Just ExamGradingMixed)
<* aformSection MsgExamFormTimes
<*> aopt utcTimeField (fslpI MsgExamStart (mr MsgDate) & setTooltip MsgExamTimeTip) (efStart <$> template)
<*> aopt utcTimeField (fslpI MsgExamEnd (mr MsgDate) & setTooltip MsgExamTimeTip) (efEnd <$> template)
@ -122,11 +123,39 @@ examForm template html = do
<*> examOccurrenceRuleForm (efOccurrenceRule <$> template)
<* aformSection MsgExamFormMode
<*> 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
<*> examCorrectorsForm (efCorrectors <$> template)
<* aformSection MsgExamFormParts
<*> 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 mPrev = wFormToAForm $ do
MsgRenderer mr <- getMsgRenderer
@ -261,6 +290,7 @@ examFormTemplate (Entity eId Exam{..}) = do
occurrences <- selectList [ ExamOccurrenceExam ==. eId ] []
correctors <- selectList [ ExamCorrectorExam ==. eId ] []
invitations <- Map.keysSet <$> sourceInvitationsF @ExamCorrector eId
extraSchools <- selectList [ ExamOfficeSchoolExam ==. eId ] []
examParts' <- forM examParts $ \(Entity pid part) -> (,) <$> encrypt pid <*> pure part
occurrences' <- forM occurrences $ \(Entity oid occ) -> (,) <$> encrypt oid <*> pure occ
@ -308,13 +338,15 @@ examFormTemplate (Entity eId Exam{..}) = do
return examCorrectorUser
]
, efExamMode = examExamMode
, efOfficeSchools = Set.fromList $ examOfficeSchoolSchool . entityVal <$> extraSchools
, efStaff = examStaff
}
examTemplate :: CourseId -> DB (Maybe ExamForm)
examTemplate cid = runMaybeT $ do
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.where_ $ ( course E.^. CourseShorthand E.==. E.val (courseShorthand newCourse)
E.||. course E.^. CourseName E.==. E.val (courseName newCourse)
@ -327,6 +359,8 @@ examTemplate cid = runMaybeT $ do
E.limit 1
E.orderBy [ E.desc $ course E.^. CourseTerm, E.asc $ exam E.^. ExamVisibleFrom ]
return (course, exam)
extraSchools <- lift $ selectList [ ExamOfficeSchoolExam ==. oldExamId ] []
oldTerm <- MaybeT . get $ courseTerm oldCourse
newTerm <- MaybeT . get $ courseTerm newCourse
@ -354,6 +388,8 @@ examTemplate cid = runMaybeT $ do
, efExamParts = Set.empty
, efCorrectors = Set.empty
, 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
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
, examDescription = efDescription
, examExamMode = efExamMode
, examStaff = efStaff
}
whenIsJust insertRes $ \examid -> do
insertMany_
@ -74,6 +75,8 @@ postCExamNewR tid ssh csh = do
examOccurrenceDescription = eofDescription
]
insertMany_ [ ExamOfficeSchool ssh' examid | ssh' <- Set.toList efOfficeSchools ]
let (invites, adds) = partitionEithers $ Set.toList efCorrectors
insertMany_ [ ExamCorrector{..}
| let examCorrectorExam = examid

View File

@ -26,7 +26,7 @@ getEShowR tid ssh csh examn = do
cTime <- liftIO getCurrentTime
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
school <- getJust examCourse >>= belongsToJust courseSchool
@ -83,7 +83,14 @@ getEShowR tid ssh csh examn = do
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
partNumbersShown = lecturerInfoShown

View File

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

View File

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

View File

@ -107,6 +107,7 @@ sinkAllocationPriorities allocId = fmap getSum . C.foldMapM . ifoldMapM $ \matr
computeAllocation :: Entity Allocation
-> Maybe (Set CourseId) -- ^ Optionally restrict allocation to only consider the given courses
-> DB ( AllocationFingerprint
, Set CourseId
, Set (UserId, CourseId)
, Seq MatchingLogRun
)
@ -162,6 +163,7 @@ computeAllocation (Entity allocId Allocation{allocationMatchingSeed}) cRestr = d
, allocationCourse E.^. AllocationCourseMinCapacity E.-. participants
)
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 ] []
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
| 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

View File

@ -34,7 +34,7 @@ resultIsSynced authId examResult = (hasSchool E.&&. allSchools) E.||. (E.not_ ha
examOfficeExamResultAuth :: E.SqlExpr (E.Value UserId) -- ^ office
-> E.SqlExpr (Entity ExamResult)
-> E.SqlExpr (E.Value Bool)
examOfficeExamResultAuth authId examResult = authByUser E.||. authByField E.||. authBySchool
examOfficeExamResultAuth authId examResult = authByUser E.||. authByField E.||. authBySchool E.||. authByExtraSchool
where
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.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
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 qualified Data.Map as Map
import qualified Data.Vector as Vector
import qualified Data.HashMap.Lazy as HashMap
import Control.Monad.Writer.Class
@ -495,7 +497,7 @@ termsAllowedField = selectField $ do
optionsPersistKey termFilter [Desc TermStart] termName
termField :: Field Handler TermId
termField = selectField $ optionsPersistKey [] [Asc TermName] termName
termField = selectField $ optionsPersistKey [] [Desc TermStart] termName
termsSetField :: [TermId] -> Field Handler TermId
termsSetField tids = selectField $ optionsPersistKey [TermName <-. (unTermKey <$> tids)] [Desc TermStart] termName
@ -1660,6 +1662,96 @@ multiUserField onlySuggested suggestions = Field{..}
)
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.
( MonadHandler m
, HandlerSite m ~ UniWorX
@ -2084,3 +2176,30 @@ examModeForm mPrev = examMode
examRequiredEquipmentToEither (ExamRequiredEquipmentCustom c) = Left c
examRequiredEquipmentFromEither (Right p) = ExamRequiredEquipmentPreset p
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 nAllocation jRecipient = userMailT jRecipient $ do
(Allocation{..}, lecturerResults, participantResults) <- liftHandler . runDB $ do
(Allocation{..}, lecturerResults, warnSubstituteCourses, participantResults) <- liftHandler . runDB $ do
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.where_ $ lecturer E.^. LecturerUser E.==. E.val jRecipient
E.&&. E.exists (E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation
)
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation
let allocatedCount :: E.SqlExpr (E.Value Int64)
allocatedCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
@ -157,11 +155,12 @@ dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipi
participantCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
E.&&. participant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return (course, allocatedCount, participantCount)
let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, E.Value allocCount, E.Value partCount) -> SomeMessage $ if
return (course, allocationCourse, allocatedCount, participantCount)
let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, _, E.Value allocCount, E.Value partCount) -> SomeMessage $ if
| allocCount == partCount -> MsgAllocationResultLecturerAll courseShorthand allocCount
| allocCount == 0 -> MsgAllocationResultLecturerNone courseShorthand
| 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 ->
E.where_ $ application E.^. CourseApplicationAllocation E.==. E.just (E.val nAllocation)
@ -177,7 +176,7 @@ dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipi
| otherwise -> Nothing
cs -> Just $ map (courseShorthand . entityVal) cs
return (allocation, lecturerResults, participantResults)
return (allocation, lecturerResults, warnSubstituteCourses, participantResults)
replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
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)" )
]
recordedChangelogItems <- lift . lift $ selectList [] []
recordedChangelogItems <- lift . lift $ selectList [ ChangelogItemFirstSeenItem <-. universeF ] []
let missingChangelogItems = Set.toList $ Set.fromList universeF `Set.difference` recordedChangelogItems'
where recordedChangelogItems' = Set.fromList [ changelogItemFirstSeenItem | Entity _ ChangelogItemFirstSeen{..} <- recordedChangelogItems ]
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|])
, (ChangelogPersonalisedSheetFiles, [day|2020-08-10|])
, (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
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
parseJSON = Aeson.withObject "AuthTagActive" $ \o -> do
o' <- parseJSON $ Aeson.Object o :: Aeson.Parser (HashMap AuthTag Bool)
return . AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n o'
o' <- parseJSON $ Aeson.Object o :: Aeson.Parser (HashMap Text Bool)
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
hashWithSalt s = foldl' hashWithSalt s . authTagIsActive
@ -128,6 +131,27 @@ derivePersistFieldJSON ''AuthTagActive
getSessionActiveAuthTags :: MonadHandler m => m AuthTagActive
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 }
deriving (Eq, Ord, Read, Show, Data, Generic, Typeable)

View File

@ -31,7 +31,7 @@ import Data.Char (isAscii)
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 anyclass (Universe, Finite, Hashable)

View File

@ -1269,8 +1269,7 @@ mpreq :: (RenderMessage site (ValueRequired site), HandlerSite m ~ site, MonadHa
-- Otherwise acts exactly like `mopt`.
mpreq f fs@FieldSettings{..} mx = do
mr <- getMessageRender
(res, fv) <- mopt f fs (Just <$> mx)
let fv' = fv { fvRequired = True }
(res, fv') <- mpreq' f fs $ Just <$> mx
return $ case res of
FormSuccess (Just res')
-> (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
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)
=> Field m a -> FieldSettings site -> Maybe a -> MForm m (FormResult a, FieldView site)
-- ^ Pseudo optional

View File

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

View File

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

View File

@ -53,6 +53,15 @@ $newline never
^{iconTooltip (i18n MsgAllocationRegisterByStaffFromTip) Nothing True}
<dd .deflist__dd>
^{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
<dt .deflist__dt>
_{MsgAllocationRegisterByCourseFrom}

View File

@ -14,6 +14,16 @@ $if is _Just muid
#{courseName}
$if not courseVisible && mayEdit
\ #{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
<div .allocation-course__instructions-label .allocation__label>
_{MsgCourseApplicationInstructionsApplication}

View File

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

View File

@ -94,6 +94,16 @@ $maybe desc <- examDescription
$maybe closed <- examClosed
<dt .deflist__dt>_{MsgExamClosed} ^{isVisible False}
<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
$maybe gradingRule <- examGradingRule
<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>
_{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)
<p>
_{SomeMessage MsgAllocationResultsLecturer}

View File

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