Merge branch 'master' into stundenplan
This commit is contained in:
commit
acb663c480
@ -1,7 +1,7 @@
|
||||
default:
|
||||
image:
|
||||
name: fpco/stack-build:lts-16.11
|
||||
cache:
|
||||
cache: &global_cache
|
||||
paths:
|
||||
- node_modules
|
||||
- .stack
|
||||
@ -57,6 +57,9 @@ npm install:
|
||||
interruptible: true
|
||||
|
||||
frontend:build:
|
||||
cache:
|
||||
<<: *global_cache
|
||||
policy: pull
|
||||
stage: frontend:build
|
||||
script:
|
||||
- npm run frontend:build
|
||||
@ -146,6 +149,9 @@ yesod:build:
|
||||
resource_group: ram
|
||||
|
||||
frontend:test:
|
||||
cache:
|
||||
<<: *global_cache
|
||||
policy: pull
|
||||
stage: test
|
||||
script:
|
||||
- npm run frontend:test
|
||||
@ -167,9 +173,10 @@ frontend:test:
|
||||
interruptible: true
|
||||
|
||||
deploy:uniworx3:
|
||||
cache: {}
|
||||
stage: deploy
|
||||
script:
|
||||
- ssh -i ~/.ssh/id root@uniworx3.ifi.lmu.de <bin/uniworx
|
||||
- zip -qj - bin/uniworx bin/uniworxdb | ssh root@uniworx3.ifi.lmu.de /root/bin/accept_uni2work
|
||||
needs:
|
||||
- yesod:build
|
||||
- frontend:test # For sanity
|
||||
|
||||
83
CHANGELOG.md
83
CHANGELOG.md
@ -2,6 +2,89 @@
|
||||
|
||||
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.13.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.12.1...v20.13.0) (2020-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **allocations:** display participant counts to admins ([b79bac7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b79bac777c6d349a626ea4efa6c43141b7f669d0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **allocations:** fix allocation-course-accept-substitutes ([b4df980](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b4df98069982752e36e69571f5557a6179b44cff))
|
||||
|
||||
### [20.12.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.12.0...v20.12.1) (2020-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** prettier active directory errors in help messages ([b631ed7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b631ed7d0620748fd833c4cda4b421dc147d0906))
|
||||
* **migration:** don't consider changelog in requiresMigration ([ea95d74](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ea95d74cb5572688531ba0fdeed3983fb70ab236))
|
||||
|
||||
## [20.12.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.11.1...v20.12.0) (2020-10-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ldap:** expose active directory errors ([51ed7e0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/51ed7e0a26a94d2178a4ca10ad7ea36b99076b54))
|
||||
|
||||
### [20.11.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.11.0...v20.11.1) (2020-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **allocations:** work around yesod weirdness wrt "none" ([4a731ec](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4a731eca4e69b5ee080f229a602e76f5ae165c64))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **authorization:** have AllocationTime consider ParticipantState ([b69481e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b69481e88fb20890b4ece7a0023dcfdad21604d6))
|
||||
|
||||
## [20.8.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.7.0...v20.8.0) (2020-10-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **allocations:** csv-export new-assigned ([a4114a7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a4114a79f1bfd968bb9d300f0c39400a8904ee7c))
|
||||
|
||||
## [20.7.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.6.0...v20.7.0) (2020-10-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **allocations:** include study features in users table ([7f7d2c7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7f7d2c795767fd6fac1fa4a10a304e3e3d2280c3))
|
||||
|
||||
## [20.6.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.5.1...v20.6.0) (2020-10-06)
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -785,10 +785,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
|
||||
@ -839,10 +842,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
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
FAQNoCampusAccount: Ich habe keine LMU-Benutzerkennung (ehem. Campus-Kennung); kann ich trotzdem Zugang zum System erhalten?
|
||||
FAQForgottenPassword: Ich habe mein Passwort vergessen
|
||||
FAQCampusCantLogin: Ich kann mich mit meiner LMU-Benutzerkennung (ehem. Campus-Kennung) nicht anmelden
|
||||
FAQCourseCorrectorsTutors: Wie kann ich Tutoren oder Korrektoren für meinen Kurs einstellen?
|
||||
FAQCourseCorrectorsTutors: Wie kann ich Tutoren oder Korrektoren für meinen Kurs konfigurieren?
|
||||
FAQNotLecturerHowToCreateCourses: Wie kann ich einen neuen Kurs anlegen?
|
||||
FAQExamPoints: Warum kann ich bei meiner Klausur keine Punkte eintragen?
|
||||
FAQExamPoints: Warum kann ich bei meiner Klausur keine Punkte eintragen?
|
||||
FAQInvalidCredentialsAdAccountDisabled: Ich kann mich nicht anmelden und bekomme die Meldung „Benutzereintrag gesperrt“
|
||||
FAQAllocationNoPlaces: Ich habe über eine Zentralanmeldung keine Plätze/nicht die Plätze, die ich möchte, erhalten
|
||||
@ -4,3 +4,5 @@ FAQCampusCantLogin: I can't log in using my LMU user ID (formerly Campus-ID)
|
||||
FAQCourseCorrectorsTutors: How can I add tutors or correctors to my course?
|
||||
FAQNotLecturerHowToCreateCourses: How can I create new courses?
|
||||
FAQExamPoints: Why can't I enter achievements for my exam as points?
|
||||
FAQInvalidCredentialsAdAccountDisabled: I can't log in and am instead given the message “Account disabled”
|
||||
FAQAllocationNoPlaces: I did not receive any places/the places I wanted from a central allocation
|
||||
@ -225,6 +225,9 @@ 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
|
||||
CourseAllocationCourseParticipants: Teilnehmer
|
||||
CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung
|
||||
CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
|
||||
CourseApplicationTemplate: Bewerbungsvorlagen
|
||||
@ -281,7 +284,9 @@ CourseApplicationsAllocatedDirectory: zentral
|
||||
CourseApplicationsNotAllocatedDirectory: direkt
|
||||
|
||||
CourseNoAllocationsAvailable: Es sind aktuell keine Zentralanmeldungen verfügbar
|
||||
AllocationStaffRegisterToExpired: Es dürfen keine Änderungen an der Eintragung des Kurses zur Zentralanmeldung mehr vorgenommen werden. Ihre Änderungen wurden ignoriert.
|
||||
AllocationStaffRegisterToExpiredAllocation: Die Frist zur Eintrageng von Kursen in die Zentralanmeldung ist verstrichen. Die Teilnahme darf nicht mehr verändert werden.
|
||||
AllocationStaffRegisterToExpiredMinCapacity: Die Frist zur Eintrageng von Kursen in die Zentralanmeldung ist verstrichen. Die minimale Kapazität darf nicht mehr verändert werden.
|
||||
|
||||
|
||||
|
||||
CourseFormSectionRegistration: Anmeldung zum Kurs
|
||||
@ -824,6 +829,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
|
||||
@ -1397,6 +1404,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
|
||||
@ -1471,6 +1479,7 @@ BreadcrumbAllocationUsers: Bewerber
|
||||
BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten
|
||||
BreadcrumbAllocationCompute: Platzvergabe berechnen
|
||||
BreadcrumbAllocationAccept: Platzvergabe akzeptieren
|
||||
BreadcrumbAllocationAddUser: Bewerber hinzufügen
|
||||
BreadcrumbMessageHide: Verstecken
|
||||
BreadcrumbFaq: FAQ
|
||||
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
|
||||
@ -1484,9 +1493,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
|
||||
@ -1855,6 +1864,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
|
||||
|
||||
@ -1864,6 +1878,7 @@ ExamFormAutomaticFunctions: Automatische Funktionen
|
||||
ExamFormCorrection: Korrektur
|
||||
ExamFormParts: Teile
|
||||
ExamFormMode: Ausgestaltung der Prüfung
|
||||
ExamFormGrades: Prüfungsleistungen
|
||||
|
||||
ExamModeFormNone: Keine Angabe
|
||||
ExamModeFormCustom: Benutzerdefiniert
|
||||
@ -2278,6 +2293,9 @@ 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
|
||||
AllocationFreeCapacity: Freie Plätze
|
||||
|
||||
AllocationSchoolShort: Institut
|
||||
Allocation: Zentralanmeldung
|
||||
@ -2517,6 +2535,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
|
||||
@ -2528,6 +2548,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.
|
||||
@ -2712,22 +2733,46 @@ CsvColumnAllocationUserSurname: Nachname(n) des Bewerbers
|
||||
CsvColumnAllocationUserFirstName: Vorname(n) des Bewerbers
|
||||
CsvColumnAllocationUserName: Voller Name des Bewerbers
|
||||
CsvColumnAllocationUserMatriculation: Matrikelnummer des Bewerber
|
||||
CsvColumnAllocationUserStudyFeatures: Studiendaten
|
||||
CsvColumnAllocationUserRequested: Maximale Anzahl von Plätzen, die der Bewerber bereit ist, zu akzeptieren
|
||||
CsvColumnAllocationUserApplied: Anzahl von Bewerbungen, die der Bewerber eingereicht hat
|
||||
CsvColumnAllocationUserVetos: Anzahl von Bewerbungen, die von Kursverwaltern ein Veto oder eine Note erhalten haben, die äquivalent ist zu "Nicht Bestanden" (5.0)
|
||||
CsvColumnAllocationUserAssigned: Anzahl von Plätzen, die der Bewerber durch diese Zentralanmeldung bereits erhalten hat
|
||||
CsvColumnAllocationUserNewAssigned: Anzahl von Plätzen, die der Bewerber, nach Akzeptieren der berechneten Verteilung, zusätzlich erhalten würde
|
||||
CsvColumnAllocationUserPriority: Zentrale Dringlichkeit des Bewerbers; entweder einzelne Zahl für Sortierungsbasierte Dringlichkeiten (höhere Dringlichkeit entspricht größerer Zahl) oder Komma-separierte Liste von numerischen Dringlichkeiten in eckigen Klammern (z.B. [1, 2, 3])
|
||||
AllocationUsersCsvName tid@TermId ssh@SchoolId ash@AllocationShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash}-bewerber
|
||||
|
||||
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
|
||||
@ -2743,6 +2788,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.
|
||||
AllocationCourseRestrictionDontRestrict: 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!
|
||||
@ -2760,6 +2808,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}
|
||||
|
||||
@ -2858,4 +2907,16 @@ SystemExamOffice: Prüfungsverwaltung
|
||||
SystemFaculty: Fakultätsmitglied
|
||||
|
||||
ChangelogItemFeature: Feature
|
||||
ChangelogItemBugfix: Bugfix
|
||||
ChangelogItemBugfix: Bugfix
|
||||
|
||||
InvalidCredentialsADNoSuchObject: Benutzereintrag existiert nicht
|
||||
InvalidCredentialsADLogonFailure: Ungültiges Passwort
|
||||
InvalidCredentialsADAccountRestriction: Kontobeschränkungen verhindern Login
|
||||
InvalidCredentialsADInvalidLogonHours: Benutzer darf sich zur aktuellen Tageszeit nicht anmelden
|
||||
InvalidCredentialsADInvalidWorkstation: Benutzer darf sich von diesem System aus nicht anmelden
|
||||
InvalidCredentialsADPasswordExpired: Passwort abgelaufen
|
||||
InvalidCredentialsADAccountDisabled: Benutzereintrag gesperrt
|
||||
InvalidCredentialsADTooManyContextIds: Benutzereintrag trägt zu viele Sicherheitskennzeichen
|
||||
InvalidCredentialsADAccountExpired: Benutzereintrag abgelaufen
|
||||
InvalidCredentialsADPasswordMustChange: Passwort muss geändert werden
|
||||
InvalidCredentialsADAccountLockedOut: Benutzereintrag wurde durch Eindringlingserkennung gesperrt
|
||||
@ -226,6 +226,9 @@ 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
|
||||
CourseAllocationCourseParticipants: Participants
|
||||
CourseApplicationInstructions: Instructions for application
|
||||
CourseApplicationInstructionsTip: Will be shown to students if they decide to apply for this course
|
||||
CourseApplicationTemplate: Application template
|
||||
@ -282,7 +285,8 @@ CourseApplicationsAllocatedDirectory: central
|
||||
CourseApplicationsNotAllocatedDirectory: direct
|
||||
|
||||
CourseNoAllocationsAvailable: There are no ongoing central allocations
|
||||
AllocationStaffRegisterToExpired: You cannot change course properties concerning the central allocation after the course registration period. Your changes may have been discarded.
|
||||
AllocationStaffRegisterToExpiredAllocation: The course registration period for the central allocation is over. Participation may not be changed.
|
||||
AllocationStaffRegisterToExpiredMinCapacity: The course registration period for the central allocation is over. Minimum capacity may not be changed.
|
||||
|
||||
CourseFormSectionRegistration: Registration
|
||||
CourseFormSectionAdministration: Administration
|
||||
@ -822,6 +826,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
|
||||
@ -1398,6 +1405,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
|
||||
@ -1472,6 +1480,7 @@ BreadcrumbAllocationUsers: Applicants
|
||||
BreadcrumbAllocationPriorities: Central priorities
|
||||
BreadcrumbAllocationCompute: Compute allocation
|
||||
BreadcrumbAllocationAccept: Accept allocation
|
||||
BreadcrumbAllocationAddUser: Add applicant
|
||||
BreadcrumbMessageHide: Hide
|
||||
BreadcrumbFaq: FAQ
|
||||
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
|
||||
@ -1485,9 +1494,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
|
||||
@ -1854,6 +1863,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
|
||||
|
||||
@ -1863,6 +1877,7 @@ ExamFormAutomaticFunctions: Automatic functions
|
||||
ExamFormCorrection: Correction
|
||||
ExamFormParts: Exam parts
|
||||
ExamFormMode: Exam design
|
||||
ExamFormGrades: Exam achievements
|
||||
|
||||
ExamModeFormNone: Not specified
|
||||
ExamModeFormCustom: Custom
|
||||
@ -2277,6 +2292,9 @@ 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
|
||||
AllocationFreeCapacity: Free capacity
|
||||
|
||||
AllocationSchoolShort: Department
|
||||
Allocation: Central allocation
|
||||
@ -2517,6 +2535,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
|
||||
@ -2528,6 +2548,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}.
|
||||
@ -2712,22 +2733,46 @@ CsvColumnAllocationUserSurname: Applicant's surname(s)
|
||||
CsvColumnAllocationUserFirstName: Applicants's first name(s)
|
||||
CsvColumnAllocationUserName: Applicant's full name
|
||||
CsvColumnAllocationUserMatriculation: Applicant's matriculation
|
||||
CsvColumnAllocationUserStudyFeatures: Features of study
|
||||
CsvColumnAllocationUserRequested: Maximum number of placements the applicant is prepared to accept
|
||||
CsvColumnAllocationUserApplied: Number of applications the applicant has provided
|
||||
CsvColumnAllocationUserVetos: Number of applications that have received a veto from a course administrator or have been rated with a grade that is equivalent to "failed" (5.0)
|
||||
CsvColumnAllocationUserAssigned: Number of assignments the applicant has already received
|
||||
CsvColumnAllocationUserNewAssigned: Number of assignments the applicant would receive, if the calculated matching is accepted
|
||||
CsvColumnAllocationUserPriority: Central priority of this applicant; either a number based on the applicants position in the list sorted by priority (higher numbers mean a higher priority) or a comma-separated list of numerical priorities in square brackets (e.g. [1, 2, 3])
|
||||
AllocationUsersCsvName tid ssh ash: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash}-applicants
|
||||
|
||||
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
|
||||
@ -2743,6 +2788,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.
|
||||
AllocationCourseRestrictionDontRestrict: 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!
|
||||
@ -2760,6 +2808,7 @@ AllocationOfferedPlaces: Offered places
|
||||
AllocationUserNewMatches: New allocations
|
||||
AllocationUsersCount: Participants
|
||||
AllocationCoursesCount: Courses
|
||||
AllocationCourseEligible: Considered
|
||||
|
||||
CourseOption tid ssh coursen: #{tid} - #{ssh} - #{coursen}
|
||||
|
||||
@ -2859,4 +2908,16 @@ SystemExamOffice: Exam office
|
||||
SystemFaculty: Faculty member
|
||||
|
||||
ChangelogItemFeature: Feature
|
||||
ChangelogItemBugfix: Bugfix
|
||||
ChangelogItemBugfix: Bugfix
|
||||
|
||||
InvalidCredentialsADNoSuchObject: User entry does not exist
|
||||
InvalidCredentialsADLogonFailure: Invalid passwod
|
||||
InvalidCredentialsADAccountRestriction: Account restrictions are preventing login
|
||||
InvalidCredentialsADInvalidLogonHours: User may not login at the current time of day
|
||||
InvalidCredentialsADInvalidWorkstation: User may not login from this system
|
||||
InvalidCredentialsADPasswordExpired: Password expired
|
||||
InvalidCredentialsADAccountDisabled: Account disabled
|
||||
InvalidCredentialsADTooManyContextIds: Account carries to many security identifiers
|
||||
InvalidCredentialsADAccountExpired: Account expired
|
||||
InvalidCredentialsADPasswordMustChange: Password needs to be changed
|
||||
InvalidCredentialsADAccountLockedOut: Account disabled by intruder detection
|
||||
|
||||
@ -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
|
||||
|
||||
@ -18,6 +18,7 @@ Exam
|
||||
gradingMode ExamGradingMode
|
||||
description Html Maybe
|
||||
examMode ExamMode
|
||||
staff Text Maybe
|
||||
UniqueExam course name
|
||||
ExamPart
|
||||
exam ExamId
|
||||
@ -68,3 +69,7 @@ ExamPartCorrector
|
||||
part ExamPartId
|
||||
corrector ExamCorrectorId
|
||||
UniqueExamPartCorrector part corrector
|
||||
ExamOfficeSchool
|
||||
school SchoolId
|
||||
exam ExamId
|
||||
UniqueExamOfficeSchool exam school
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uni2work",
|
||||
"version": "20.6.0",
|
||||
"version": "20.13.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uni2work",
|
||||
"version": "20.6.0",
|
||||
"version": "20.13.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
name: uniworx
|
||||
version: 20.6.0
|
||||
version: 20.13.0
|
||||
|
||||
dependencies:
|
||||
- base
|
||||
@ -70,6 +70,7 @@ dependencies:
|
||||
- blaze-html
|
||||
- conduit-resumablesink >=0.2
|
||||
- parsec
|
||||
- parsec-numbers
|
||||
- attoparsec
|
||||
- uuid
|
||||
- exceptions
|
||||
|
||||
1
routes
1
routes
@ -113,6 +113,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
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
|
||||
module Application
|
||||
( getAppDevSettings
|
||||
( getAppSettings, getAppDevSettings
|
||||
, appMain
|
||||
, develMain
|
||||
, makeFoundation
|
||||
@ -11,8 +11,8 @@ module Application
|
||||
, getApplicationRepl
|
||||
, shutdownApp
|
||||
-- * for GHCI
|
||||
, handler
|
||||
, db
|
||||
, handler, handler'
|
||||
, db, db'
|
||||
, addPWEntry
|
||||
) where
|
||||
|
||||
@ -235,7 +235,7 @@ makeFoundation appSettings'@AppSettings{..} = do
|
||||
migrateAll `runSqlPool` sqlPool
|
||||
| otherwise -> whenM (requiresMigration `runSqlPool` sqlPool) $ do
|
||||
$logErrorS "setup" "Migration required"
|
||||
liftIO . exitWith $ ExitFailure 2
|
||||
liftIO . exitWith $ ExitFailure 130
|
||||
|
||||
$logDebugS "setup" "Cluster-Config"
|
||||
appCryptoIDKey <- clusterSetting (Proxy :: Proxy 'ClusterCryptoIDKey) `runSqlPool` sqlPool
|
||||
@ -620,17 +620,19 @@ shutdownApp app = do
|
||||
---------------------------------------------
|
||||
|
||||
-- | Run a handler
|
||||
handler :: Handler a -> IO a
|
||||
handler, handler' :: Handler a -> IO a
|
||||
handler h = runResourceT $ getAppDevSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h
|
||||
handler' h = runResourceT $ getAppSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h
|
||||
|
||||
-- | Run DB queries
|
||||
db :: DB a -> IO a
|
||||
db, db' :: DB a -> IO a
|
||||
db = handler . runDB
|
||||
db' = handler' . runDB
|
||||
|
||||
addPWEntry :: User
|
||||
-> Text {-^ Password -}
|
||||
-> IO ()
|
||||
addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db $ do
|
||||
addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db' $ do
|
||||
PWHashConf{..} <- getsYesod $ view _appAuthPWHash
|
||||
(AuthPWHash . Text.decodeUtf8 -> userAuthentication) <- liftIO $ makePasswordWith pwHashAlgorithm pw pwHashStrength
|
||||
void $ insert User{..}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
module Auth.LDAP
|
||||
( apLdap
|
||||
, ADError(..), ADInvalidCredentials(..)
|
||||
, campusLogin
|
||||
, CampusUserException(..)
|
||||
, campusUser, campusUser'
|
||||
@ -26,6 +27,8 @@ import qualified Data.Text.Encoding as Text
|
||||
|
||||
import qualified Yesod.Auth.Message as Msg
|
||||
|
||||
import Auth.LDAP.AD
|
||||
|
||||
|
||||
data CampusLogin = CampusLogin
|
||||
{ campusIdent :: CI Text
|
||||
@ -155,6 +158,14 @@ campusUserMatr' pool mode
|
||||
|
||||
|
||||
|
||||
newtype ADInvalidCredentials = ADInvalidCredentials ADError
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
deriving newtype (Universe, Finite, Enum, Bounded, PathPiece, ToJSON, FromJSON, ToJSONKey, FromJSONKey)
|
||||
|
||||
isUnusualADError :: ADError -> Bool
|
||||
isUnusualADError = flip notElem [ADNoSuchObject, ADLogonFailure]
|
||||
|
||||
|
||||
campusForm :: ( RenderMessage (HandlerSite m) FormMessage
|
||||
, RenderMessage (HandlerSite m) (ValueRequired (HandlerSite m))
|
||||
, RenderMessage (HandlerSite m) CampusMessage
|
||||
@ -174,6 +185,7 @@ campusLogin :: forall site.
|
||||
, RenderMessage site CampusMessage
|
||||
, RenderMessage site AFormMessage
|
||||
, RenderMessage site (ValueRequired site)
|
||||
, RenderMessage site ADInvalidCredentials
|
||||
, Button site ButtonSubmit
|
||||
) => Failover (LdapConf, LdapPool) -> FailoverMode -> AuthPlugin site
|
||||
campusLogin pool mode = AuthPlugin{..}
|
||||
@ -203,6 +215,14 @@ campusLogin pool mode = AuthPlugin{..}
|
||||
$logErrorS apName $ "Error during login: " <> tshow err
|
||||
observeLoginOutcome apName LoginError
|
||||
loginErrorMessageI LoginR Msg.AuthError
|
||||
Right (Left (Ldap.ResponseErrorCode _ errCode _ errTxt))
|
||||
| Right adError <- parseADError errCode errTxt
|
||||
, isUnusualADError adError -> do
|
||||
$logInfoS apName [st|#{campusIdent}: #{toPathPiece adError}|]
|
||||
observeLoginOutcome apName LoginADInvalidCredentials
|
||||
MsgRenderer mr <- liftHandler getMsgRenderer
|
||||
setSessionJson SessionError . PermissionDenied . toPathPiece $ ADInvalidCredentials adError
|
||||
loginErrorMessage (tp LoginR) . mr $ ADInvalidCredentials adError
|
||||
Right (Left bindErr) -> do
|
||||
case bindErr of
|
||||
Ldap.ResponseErrorCode _ _ _ errTxt ->
|
||||
|
||||
76
src/Auth/LDAP/AD.hs
Normal file
76
src/Auth/LDAP/AD.hs
Normal file
@ -0,0 +1,76 @@
|
||||
module Auth.LDAP.AD
|
||||
( ADError(..)
|
||||
, parseADError
|
||||
) where
|
||||
|
||||
import Import.NoFoundation hiding (try)
|
||||
|
||||
import Model.Types.TH.PathPiece
|
||||
|
||||
import qualified Data.IntMap.Strict as IntMap
|
||||
import qualified Data.Map.Strict as Map
|
||||
|
||||
import Text.Parsec hiding ((<|>))
|
||||
import Text.Parsec.String
|
||||
import Text.ParserCombinators.Parsec.Number (hexnum)
|
||||
|
||||
import Ldap.Client (ResultCode(..))
|
||||
|
||||
|
||||
-- | Copied from <https://ldapwiki.com/wiki/Common%20Active%20Directory%20Bind%20Errors>
|
||||
data ADError
|
||||
= ADNoSuchObject
|
||||
| ADLogonFailure
|
||||
| ADAccountRestriction
|
||||
| ADInvalidLogonHours
|
||||
| ADInvalidWorkstation
|
||||
| ADPasswordExpired
|
||||
| ADAccountDisabled
|
||||
| ADTooManyContextIds
|
||||
| ADAccountExpired
|
||||
| ADPasswordMustChange
|
||||
| ADAccountLockedOut
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
deriving anyclass (Universe, Finite)
|
||||
|
||||
nullaryPathPiece ''ADError $ camelToPathPiece' 1
|
||||
pathPieceJSON ''ADError
|
||||
pathPieceJSONKey ''ADError
|
||||
derivePersistFieldPathPiece ''ADError
|
||||
|
||||
|
||||
fromADErrorCode :: ResultCode -> Word32 -> Maybe ADError
|
||||
fromADErrorCode resCode subResCode = IntMap.lookup (fromIntegral subResCode) =<< Map.lookup resCode errorCodes
|
||||
where
|
||||
errorCodes = Map.fromList
|
||||
[ ( InvalidCredentials
|
||||
, IntMap.fromList
|
||||
[ ( 0x525, ADNoSuchObject )
|
||||
, ( 0x52e, ADLogonFailure )
|
||||
, ( 0x52f, ADAccountRestriction )
|
||||
, ( 0x530, ADInvalidLogonHours )
|
||||
, ( 0x531, ADInvalidWorkstation )
|
||||
, ( 0x532, ADPasswordExpired )
|
||||
, ( 0x533, ADAccountDisabled )
|
||||
, ( 0x568, ADTooManyContextIds )
|
||||
, ( 0x701, ADAccountExpired )
|
||||
, ( 0x773, ADPasswordMustChange )
|
||||
, ( 0x775, ADAccountLockedOut )
|
||||
, ( 0x80090346, ADAccountLockedOut )
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
parseADError :: ResultCode -> Text -> Either ParseError ADError
|
||||
parseADError resCode = parse (pADError resCode <* eof) "LDAP" . unpack
|
||||
|
||||
pADError :: ResultCode -> Parser ADError
|
||||
pADError resCode = do
|
||||
void . manyTill anyChar . try $ string ": "
|
||||
let pItem = asum
|
||||
[ do
|
||||
void $ string "data "
|
||||
fmap Just $ hexnum >>= hoistMaybe . fromADErrorCode resCode
|
||||
, Nothing <$ manyTill anyChar (lookAhead . try $ void (string ", ") <|> eof)
|
||||
]
|
||||
(hoistMaybe =<<) $ ala First foldMap <$> pItem `sepBy1` try (string ", ")
|
||||
@ -784,7 +784,7 @@ tagAccessPredicate AuthAllocationTime = APDB $ \mAuthId route _ -> case route of
|
||||
Nothing -> return Authorized
|
||||
Just (cid, Allocation{..}) -> do
|
||||
registered <- case mAuthId of
|
||||
Just uid -> $cachedHereBinary (uid, cid) . existsBy $ UniqueParticipant uid cid
|
||||
Just uid -> $cachedHereBinary (uid, cid) $ exists [ CourseParticipantUser ==. uid, CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
|
||||
_ -> return False
|
||||
if
|
||||
| not registered
|
||||
|
||||
@ -231,6 +231,8 @@ embedRenderMessage ''UniWorX ''AuthenticationMode id
|
||||
|
||||
embedRenderMessage ''UniWorX ''RatingValidityException id
|
||||
|
||||
embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials" <>)
|
||||
|
||||
newtype ShortSex = ShortSex Sex
|
||||
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)
|
||||
|
||||
|
||||
@ -161,6 +161,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
|
||||
|
||||
@ -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
|
||||
@ -1334,6 +1335,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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
174
src/Handler/Allocation/AddUser.hs
Normal file
174
src/Handler/Allocation/AddUser.hs
Normal 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{..})
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
= AllocationCourseRestrictionDontRestrict
|
||||
| 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 AllocationCourseRestrictionDontRestrict)
|
||||
where
|
||||
restrictOpts = mapF $ \case
|
||||
AllocationCourseRestrictionDontRestrict -> 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
|
||||
|
||||
|
||||
@ -49,18 +49,22 @@ 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
|
||||
resultParticipantCount :: _ => Lens' a Int
|
||||
resultParticipantCount = _7 . _Value
|
||||
|
||||
(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 +83,29 @@ 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)))
|
||||
participantCount = E.subSelectCount . E.from $ \courseParticipant ->
|
||||
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
|
||||
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
|
||||
return ( course
|
||||
, courseApplication
|
||||
, hasTemplate
|
||||
, E.not_ . E.isNothing $ registration E.?. CourseParticipantId
|
||||
, courseIsVisible now course . Just $ E.val aId
|
||||
, allocationCourse
|
||||
, participantCount
|
||||
)
|
||||
|
||||
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
|
||||
freeCapacity = fmap getSum . getAp . flip foldMap courses $ \cEntry -> Ap . fmap (Sum . max 0) $ subtract (cEntry ^. resultParticipantCount) <$> preview (resultCourse . _entityVal . _courseCapacity . _Just) cEntry
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
|
||||
@ -146,11 +164,13 @@ postAShowR tid ssh ash = do
|
||||
mApp = cEntry ^? resultCourseApplication
|
||||
isRegistered = cEntry ^. resultIsRegistered
|
||||
courseVisible = cEntry ^. resultCourseVisible
|
||||
AllocationCourse{..} = cEntry ^. resultAllocationCourse
|
||||
partCount = cEntry ^. resultParticipantCount
|
||||
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
|
||||
|
||||
@ -10,6 +10,7 @@ import Handler.Allocation.Accept
|
||||
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Allocation
|
||||
import Handler.Utils.StudyFeatures
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
@ -22,6 +23,8 @@ import qualified Data.Set as Set
|
||||
|
||||
import Text.Blaze (toMarkup)
|
||||
|
||||
import qualified Data.Conduit.Combinators as C
|
||||
|
||||
|
||||
type UserTableExpr = E.SqlExpr (Entity User)
|
||||
`E.InnerJoin` E.SqlExpr (Entity AllocationUser)
|
||||
@ -59,6 +62,7 @@ queryVetoedCourses = queryAllocationUser . to queryVetoedCourses'
|
||||
|
||||
|
||||
type UserTableData = DBRow ( Entity User
|
||||
, UserTableStudyFeatures
|
||||
, Entity AllocationUser
|
||||
, Int -- ^ Applied
|
||||
, Int -- ^ Assigned
|
||||
@ -68,13 +72,16 @@ type UserTableData = DBRow ( Entity User
|
||||
resultUser :: Lens' UserTableData (Entity User)
|
||||
resultUser = _dbrOutput . _1
|
||||
|
||||
resultStudyFeatures :: Lens' UserTableData UserTableStudyFeatures
|
||||
resultStudyFeatures = _dbrOutput . _2
|
||||
|
||||
resultAllocationUser :: Lens' UserTableData (Entity AllocationUser)
|
||||
resultAllocationUser = _dbrOutput . _2
|
||||
resultAllocationUser = _dbrOutput . _3
|
||||
|
||||
resultAppliedCourses, resultAssignedCourses, resultVetoedCourses :: Lens' UserTableData Int
|
||||
resultAppliedCourses = _dbrOutput . _3
|
||||
resultAssignedCourses = _dbrOutput . _4
|
||||
resultVetoedCourses = _dbrOutput . _5
|
||||
resultAppliedCourses = _dbrOutput . _4
|
||||
resultAssignedCourses = _dbrOutput . _5
|
||||
resultVetoedCourses = _dbrOutput . _6
|
||||
|
||||
|
||||
data AllocationUserTableCsv = AllocationUserTableCsv
|
||||
@ -82,10 +89,12 @@ data AllocationUserTableCsv = AllocationUserTableCsv
|
||||
, csvAUserFirstName :: Text
|
||||
, csvAUserName :: Text
|
||||
, csvAUserMatriculation :: Maybe Text
|
||||
, csvAUserStudyFeatures :: UserTableStudyFeatures
|
||||
, csvAUserRequested
|
||||
, csvAUserApplied
|
||||
, csvAUserVetos
|
||||
, csvAUserAssigned :: Natural
|
||||
, csvAUserNewAssigned :: Maybe Natural
|
||||
, csvAUserPriority :: Maybe AllocationPriority
|
||||
} deriving (Generic)
|
||||
makeLenses_ ''AllocationUserTableCsv
|
||||
@ -94,10 +103,22 @@ allocationUserTableCsvOptions :: Csv.Options
|
||||
allocationUserTableCsvOptions = Csv.defaultOptions { Csv.fieldLabelModifier = camelToPathPiece' 3}
|
||||
|
||||
instance Csv.ToNamedRecord AllocationUserTableCsv where
|
||||
toNamedRecord = Csv.genericToNamedRecord allocationUserTableCsvOptions
|
||||
|
||||
instance Csv.DefaultOrdered AllocationUserTableCsv where
|
||||
headerOrder = Csv.genericHeaderOrder allocationUserTableCsvOptions
|
||||
toNamedRecord AllocationUserTableCsv{..} = Csv.namedRecord $
|
||||
[ "surname" Csv..= csvAUserSurname
|
||||
, "first-name" Csv..= csvAUserFirstName
|
||||
, "name" Csv..= csvAUserName
|
||||
, "matriculation" Csv..= csvAUserMatriculation
|
||||
, "study-features" Csv..= csvAUserStudyFeatures
|
||||
, "requested" Csv..= csvAUserRequested
|
||||
, "applied" Csv..= csvAUserApplied
|
||||
, "vetos" Csv..= csvAUserVetos
|
||||
, "assigned" Csv..= csvAUserAssigned
|
||||
] ++
|
||||
[ "new-assigned" Csv..= newAssigned
|
||||
| newAssigned <- hoistMaybe csvAUserNewAssigned
|
||||
] ++
|
||||
[ "priority" Csv..= csvAUserPriority
|
||||
]
|
||||
|
||||
instance CsvColumnsExplained AllocationUserTableCsv where
|
||||
csvColumnsExplanations = genericCsvColumnsExplanations allocationUserTableCsvOptions $ mconcat
|
||||
@ -105,13 +126,33 @@ instance CsvColumnsExplained AllocationUserTableCsv where
|
||||
, singletonMap 'csvAUserFirstName MsgCsvColumnAllocationUserFirstName
|
||||
, singletonMap 'csvAUserName MsgCsvColumnAllocationUserName
|
||||
, singletonMap 'csvAUserMatriculation MsgCsvColumnAllocationUserMatriculation
|
||||
, singletonMap 'csvAUserStudyFeatures MsgCsvColumnAllocationUserStudyFeatures
|
||||
, singletonMap 'csvAUserRequested MsgCsvColumnAllocationUserRequested
|
||||
, singletonMap 'csvAUserApplied MsgCsvColumnAllocationUserApplied
|
||||
, singletonMap 'csvAUserVetos MsgCsvColumnAllocationUserVetos
|
||||
, singletonMap 'csvAUserAssigned MsgCsvColumnAllocationUserAssigned
|
||||
, singletonMap 'csvAUserNewAssigned MsgCsvColumnAllocationUserNewAssigned
|
||||
, singletonMap 'csvAUserPriority MsgCsvColumnAllocationUserPriority
|
||||
]
|
||||
|
||||
userTableCsvHeader :: Bool -> Csv.Header
|
||||
userTableCsvHeader hasNewAssigned = Csv.header $
|
||||
[ "surname"
|
||||
, "first-name"
|
||||
, "name"
|
||||
, "matriculation"
|
||||
, "study-features"
|
||||
, "requested"
|
||||
, "applied"
|
||||
, "vetos"
|
||||
, "assigned"
|
||||
] ++
|
||||
[ "new-assigned"
|
||||
| hasNewAssigned
|
||||
] ++
|
||||
[ "priority"
|
||||
]
|
||||
|
||||
|
||||
getAUsersR, postAUsersR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
|
||||
getAUsersR = postAUsersR
|
||||
@ -121,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)
|
||||
@ -148,13 +189,15 @@ postAUsersR tid ssh ash = do
|
||||
, assigned
|
||||
, vetoed)
|
||||
dbtRowKey = views queryAllocationUser (E.^. AllocationUserId)
|
||||
dbtProj = runReaderT $ (asks . set _dbrOutput) <=< magnify _dbrOutput $
|
||||
(,,,,)
|
||||
<$> view _1 <*> view _2 <*> view (_3 . _Value) <*> view (_4 . _Value) <*> view (_5 . _Value)
|
||||
dbtProj = runReaderT $ (asks . set _dbrOutput) <=< magnify _dbrOutput $ do
|
||||
feats <- lift . allocationUserStudyFeatures aId =<< views _1 entityKey
|
||||
(,,,,,)
|
||||
<$> view _1 <*> pure feats <*> view _2 <*> view (_3 . _Value) <*> view (_4 . _Value) <*> view (_5 . _Value)
|
||||
dbtColonnade :: Colonnade Sortable _ _
|
||||
dbtColonnade = mconcat . catMaybes $
|
||||
[ pure $ colUserDisplayName (resultUser . _entityVal . $(multifocusL 2) _userDisplayName _userSurname)
|
||||
, pure $ colUserMatriculation (resultUser . _entityVal . _userMatrikelnummer)
|
||||
, pure $ colStudyFeatures resultStudyFeatures
|
||||
, pure $ colAllocationRequested (resultAllocationUser . _entityVal . _allocationUserTotalCourses)
|
||||
, pure . coursesModalApplied $ colAllocationApplied resultAppliedCourses
|
||||
, pure . coursesModalVetoed $ colAllocationVetoed resultVetoedCourses
|
||||
@ -253,16 +296,26 @@ postAUsersR tid ssh ash = do
|
||||
dbtParams = def
|
||||
dbtIdent :: Text
|
||||
dbtIdent = "allocation-users"
|
||||
dbtCsvEncode = simpleCsvEncode csvName $ AllocationUserTableCsv
|
||||
<$> view (resultUser . _entityVal . _userSurname)
|
||||
<*> view (resultUser . _entityVal . _userFirstName)
|
||||
<*> view (resultUser . _entityVal . _userDisplayName)
|
||||
<*> view (resultUser . _entityVal . _userMatrikelnummer)
|
||||
<*> view (resultAllocationUser . _entityVal . _allocationUserTotalCourses)
|
||||
<*> view (resultAppliedCourses . to fromIntegral)
|
||||
<*> view (resultVetoedCourses . to fromIntegral)
|
||||
<*> view (resultAssignedCourses . to fromIntegral)
|
||||
<*> view (resultAllocationUser . _entityVal . _allocationUserPriority)
|
||||
dbtCsvEncode = return DBTCsvEncode
|
||||
{ dbtCsvExportForm = pure ()
|
||||
, dbtCsvDoEncode = \() -> C.mapM $ \(_, row) -> flip runReaderT row $
|
||||
AllocationUserTableCsv
|
||||
<$> view (resultUser . _entityVal . _userSurname)
|
||||
<*> view (resultUser . _entityVal . _userFirstName)
|
||||
<*> view (resultUser . _entityVal . _userDisplayName)
|
||||
<*> view (resultUser . _entityVal . _userMatrikelnummer)
|
||||
<*> view resultStudyFeatures
|
||||
<*> view (resultAllocationUser . _entityVal . _allocationUserTotalCourses)
|
||||
<*> view (resultAppliedCourses . to fromIntegral)
|
||||
<*> view (resultVetoedCourses . to fromIntegral)
|
||||
<*> view (resultAssignedCourses . to fromIntegral)
|
||||
<*> views (resultUser . _entityKey) (\uid -> maybe 0 (fromIntegral . olength) . Map.lookup uid <$> allocMatching)
|
||||
<*> view (resultAllocationUser . _entityVal . _allocationUserPriority)
|
||||
, dbtCsvName = unpack csvName
|
||||
, dbtCsvNoExportData = Just id
|
||||
, dbtCsvHeader = \_ -> return . userTableCsvHeader $ is _Just allocMatching
|
||||
, dbtCsvExampleData = Nothing
|
||||
}
|
||||
dbtCsvDecode = Nothing
|
||||
allocationUsersDBTableValidator = def
|
||||
& defaultSorting [SortAscBy "priority", SortAscBy "user-matriculation"]
|
||||
|
||||
@ -57,9 +57,13 @@ data CourseForm = CourseForm
|
||||
data AllocationCourseForm = AllocationCourseForm
|
||||
{ acfAllocation :: AllocationId
|
||||
, acfMinCapacity :: Int
|
||||
, acfAcceptSubstitutes :: Maybe UTCTime
|
||||
, acfDeregisterNoShow :: Bool
|
||||
}
|
||||
|
||||
makeLenses_ ''CourseForm
|
||||
makeLenses_ ''AllocationCourseForm
|
||||
|
||||
courseToForm :: Entity Course -> [Lecturer] -> Map UserEmail (InvitationDBData Lecturer) -> Maybe (Entity AllocationCourse) -> CourseForm
|
||||
courseToForm cEnt@(Entity cid Course{..}) lecs lecInvites alloc = CourseForm
|
||||
{ cfCourseId = Just cid
|
||||
@ -98,6 +102,7 @@ allocationCourseToForm :: Entity Course -> Entity AllocationCourse -> Allocation
|
||||
allocationCourseToForm (Entity _ Course{..}) (Entity _ AllocationCourse{..}) = AllocationCourseForm
|
||||
{ acfAllocation = allocationCourseAllocation
|
||||
, acfMinCapacity = allocationCourseMinCapacity
|
||||
, acfAcceptSubstitutes = allocationCourseAcceptSubstitutes
|
||||
, acfDeregisterNoShow = courseDeregisterNoShow
|
||||
}
|
||||
|
||||
@ -265,6 +270,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)
|
||||
@ -323,20 +329,28 @@ validateCourse = do
|
||||
now <- liftIO getCurrentTime
|
||||
uid <- liftHandler requireAuthId
|
||||
userAdmin <- hasWriteAccessTo $ SchoolR cfSchool SchoolEditR
|
||||
allocationTerm <- for (acfAllocation <$> cfAllocation) $ lift . fmap allocationTerm . getJust
|
||||
newAllocationTerm <- for (acfAllocation <$> cfAllocation) $ lift . fmap allocationTerm . getJust
|
||||
|
||||
oldAllocatedCapacity <- fmap join . for cfCourseId $ \cid -> lift $ do
|
||||
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
|
||||
prevAllocation <- fmap join . traverse get $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
|
||||
prevAllocationCourse <- join <$> traverse (lift . getBy . UniqueAllocationCourse) cfCourseId
|
||||
prevAllocation <- fmap join . traverse (lift . getEntity) $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
|
||||
|
||||
fmap join . for prevAllocation $ \Allocation{allocationStaffAllocationTo, allocationRegisterByCourse} -> if
|
||||
| userAdmin
|
||||
-> return Nothing
|
||||
| NTop allocationStaffAllocationTo <= NTop (Just now)
|
||||
, NTop allocationRegisterByCourse > NTop (Just now)
|
||||
-> Just . courseCapacity <$> getJust cid
|
||||
| otherwise
|
||||
-> return Nothing
|
||||
oldAllocatedCapacity <- if
|
||||
| Just (Entity _ Allocation{..}) <- prevAllocation
|
||||
, Just (Entity _ AllocationCourse{..}) <- prevAllocationCourse
|
||||
, NTop allocationStaffAllocationTo <= NTop (Just now)
|
||||
, NTop allocationRegisterByCourse > NTop (Just now)
|
||||
-> lift $ Just . courseCapacity <$> getJust allocationCourseCourse
|
||||
| otherwise
|
||||
-> return Nothing
|
||||
let oldAllocation = do
|
||||
Entity allocId Allocation{..} <- prevAllocation
|
||||
guard $ NTop (Just now) > NTop allocationStaffRegisterTo
|
||||
pure $ Just allocId
|
||||
oldAllocatedMinCapacity = do
|
||||
Entity _ Allocation{..} <- prevAllocation
|
||||
Entity _ AllocationCourse{..} <- prevAllocationCourse
|
||||
guard $ NTop (Just now) > NTop allocationStaffRegisterTo
|
||||
pure $ Just allocationCourseMinCapacity
|
||||
|
||||
guardValidation MsgCourseVisibilityEndMustBeAfterStart
|
||||
$ NTop cfVisFrom <= NTop cfVisTo
|
||||
@ -344,15 +358,19 @@ validateCourse = do
|
||||
$ NTop cfRegFrom <= NTop cfRegTo
|
||||
guardValidation MsgCourseDeregistrationEndMustBeAfterStart
|
||||
$ Just False /= ((<=) <$> cfRegFrom <*> cfDeRegUntil)
|
||||
unless userAdmin $
|
||||
guardValidation MsgCourseUserMustBeLecturer
|
||||
$ anyOf (traverse . _Right . _1) (== uid) cfLecturers
|
||||
guardValidation MsgCourseAllocationRequiresCapacity
|
||||
$ is _Nothing cfAllocation || is _Just cfCapacity
|
||||
guardValidation MsgCourseAllocationTermMustMatch
|
||||
$ maybe True (== cfTerm) allocationTerm
|
||||
guardValidation MsgCourseAllocationCapacityMayNotBeChanged
|
||||
$ maybe True (== cfCapacity) oldAllocatedCapacity
|
||||
$ maybe True (== cfTerm) newAllocationTerm
|
||||
unless userAdmin $ do
|
||||
guardValidation MsgCourseUserMustBeLecturer
|
||||
$ anyOf (traverse . _Right . _1) (== uid) cfLecturers
|
||||
guardValidation MsgCourseAllocationCapacityMayNotBeChanged
|
||||
$ maybe True (== cfCapacity) oldAllocatedCapacity
|
||||
guardValidation MsgAllocationStaffRegisterToExpiredAllocation
|
||||
$ maybe True (== fmap acfAllocation cfAllocation) oldAllocation
|
||||
guardValidation MsgAllocationStaffRegisterToExpiredMinCapacity
|
||||
$ maybe True (== fmap acfMinCapacity cfAllocation) oldAllocatedMinCapacity
|
||||
|
||||
warnValidation MsgCourseShorthandTooLong
|
||||
$ length (CI.original cfShort) <= 10
|
||||
@ -564,46 +582,23 @@ courseEditHandler miButtonAction mbCourseForm = do
|
||||
}
|
||||
|
||||
upsertAllocationCourse :: CourseId -> Maybe AllocationCourseForm -> YesodJobDB UniWorX ()
|
||||
upsertAllocationCourse cid cfAllocation = do
|
||||
now <- liftIO getCurrentTime
|
||||
Course{} <- getJust cid
|
||||
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
|
||||
prevAllocation <- fmap join . traverse getEntity $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
|
||||
userAdmin <- fromMaybe False <$> for prevAllocation (\(Entity _ Allocation{..}) -> hasWriteAccessTo $ SchoolR allocationSchool SchoolEditR)
|
||||
upsertAllocationCourse cid = \case
|
||||
Just AllocationCourseForm{..} -> do
|
||||
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
|
||||
|
||||
doEdit <- if
|
||||
| userAdmin
|
||||
-> return True
|
||||
| Just (Entity _ Allocation{allocationStaffRegisterTo}) <- prevAllocation
|
||||
, NTop allocationStaffRegisterTo <= NTop (Just now)
|
||||
-> let anyChanges
|
||||
| Just AllocationCourseForm{..} <- cfAllocation
|
||||
, Just (Entity _ AllocationCourse{..}) <- prevAllocationCourse
|
||||
= or [ acfAllocation /= allocationCourseAllocation
|
||||
, acfMinCapacity /= allocationCourseMinCapacity
|
||||
]
|
||||
| otherwise
|
||||
= True
|
||||
in False <$ when anyChanges (addMessageI Error MsgAllocationStaffRegisterToExpired)
|
||||
| otherwise
|
||||
-> return True
|
||||
void $ upsert AllocationCourse
|
||||
{ allocationCourseAllocation = acfAllocation
|
||||
, allocationCourseCourse = cid
|
||||
, allocationCourseMinCapacity = acfMinCapacity
|
||||
, allocationCourseAcceptSubstitutes = acfAcceptSubstitutes
|
||||
}
|
||||
[ AllocationCourseAllocation =. acfAllocation
|
||||
, AllocationCourseCourse =. cid
|
||||
, AllocationCourseMinCapacity =. acfMinCapacity
|
||||
, AllocationCourseAcceptSubstitutes =. acfAcceptSubstitutes
|
||||
]
|
||||
|
||||
when doEdit $
|
||||
case cfAllocation of
|
||||
Just AllocationCourseForm{..} -> do
|
||||
void $ upsert AllocationCourse
|
||||
{ allocationCourseAllocation = acfAllocation
|
||||
, allocationCourseCourse = cid
|
||||
, allocationCourseMinCapacity = acfMinCapacity
|
||||
}
|
||||
[ AllocationCourseAllocation =. acfAllocation
|
||||
, AllocationCourseCourse =. cid
|
||||
, AllocationCourseMinCapacity =. acfMinCapacity
|
||||
]
|
||||
|
||||
when (Just acfAllocation /= fmap entityKey prevAllocation) $
|
||||
queueDBJob . JobQueueNotification $ NotificationAllocationNewCourse acfAllocation cid
|
||||
Nothing
|
||||
| Just (Entity prevId _) <- prevAllocationCourse
|
||||
-> delete prevId
|
||||
_other -> return ()
|
||||
when (Just acfAllocation /= fmap (allocationCourseAllocation . entityVal) prevAllocationCourse) $
|
||||
queueDBJob . JobQueueNotification $ NotificationAllocationNewCourse acfAllocation cid
|
||||
Nothing ->
|
||||
deleteWhere [ AllocationCourseCourse ==. cid ]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -14,6 +14,8 @@ import qualified Database.Esqueleto.Utils as E
|
||||
|
||||
import Development.GitRev
|
||||
|
||||
import Auth.LDAP (ADError(..), ADInvalidCredentials(..))
|
||||
|
||||
-- | Versionsgeschichte
|
||||
getVersionR :: Handler TypedContent
|
||||
getVersionR = selectRep $ do
|
||||
@ -37,7 +39,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'
|
||||
@ -181,6 +183,26 @@ showFAQ (CExamR tid ssh csh examn _) FAQExamPoints
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
showFAQ _ FAQInvalidCredentialsAdAccountDisabled = maybeT (return False) $ do
|
||||
guardM $ is _Nothing <$> maybeAuthId
|
||||
sessionError <- MaybeT $ lookupSessionJson SessionError
|
||||
guard $ sessionError == PermissionDenied (toPathPiece $ ADInvalidCredentials ADAccountDisabled)
|
||||
return True
|
||||
showFAQ _ FAQAllocationNoPlaces = maybeT (return False) $ do
|
||||
uid <- MaybeT maybeAuthId
|
||||
now <- liftIO getCurrentTime
|
||||
liftHandler . runDB . E.selectExists . E.from $ \allocation -> do
|
||||
let doneSince = E.subSelectMaybe . E.from $ \participant -> do
|
||||
E.where_ $ participant E.^. CourseParticipantAllocated E.==. E.just (allocation E.^. AllocationId)
|
||||
return . E.max_ $ participant E.^. CourseParticipantRegistration
|
||||
isAllocationUser = E.exists . E.from $ \allocationUser ->
|
||||
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. allocation E.^. AllocationId
|
||||
E.&&. allocationUser E.^. AllocationUserUser E.==. E.val uid
|
||||
isApplicant = E.exists . E.from $ \courseApplication ->
|
||||
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocation E.^. AllocationId)
|
||||
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val uid
|
||||
E.where_ $ isAllocationUser E.||. isApplicant
|
||||
E.where_ $ E.maybe E.false (\done -> done E.>=. E.val (addUTCTime (-7 * nominalDay) now)) doneSince
|
||||
showFAQ _ _ = return False
|
||||
|
||||
prioFAQ :: Monad m
|
||||
@ -191,3 +213,5 @@ prioFAQ _ FAQForgottenPassword = return 1
|
||||
prioFAQ _ FAQNotLecturerHowToCreateCourses = return 1
|
||||
prioFAQ _ FAQCourseCorrectorsTutors = return 1
|
||||
prioFAQ _ FAQExamPoints = return 2
|
||||
prioFAQ _ FAQAllocationNoPlaces = return 2
|
||||
prioFAQ _ FAQInvalidCredentialsAdAccountDisabled = return 3
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -488,7 +490,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
|
||||
@ -1608,6 +1610,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
|
||||
@ -2032,3 +2124,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
|
||||
|
||||
@ -9,6 +9,7 @@ module Handler.Utils.StudyFeatures
|
||||
, isCourseStudyFeature, courseUserStudyFeatures
|
||||
, isExternalExamStudyFeature, externalExamUserStudyFeatures
|
||||
, isTermStudyFeature
|
||||
, isAllocationStudyFeature, allocationUserStudyFeatures
|
||||
) where
|
||||
|
||||
import Import.NoFoundation
|
||||
@ -184,3 +185,24 @@ externalExamUserStudyFeatures eeId uid = do
|
||||
|
||||
isTermStudyFeature :: E.SqlExpr (Entity Term) -> E.SqlExpr (Entity StudyFeatures) -> E.SqlExpr (E.Value Bool)
|
||||
isTermStudyFeature = isRelevantStudyFeatureCached TermId
|
||||
|
||||
|
||||
isAllocationStudyFeature :: E.SqlExpr (Entity Allocation) -> E.SqlExpr (Entity StudyFeatures) -> E.SqlExpr (E.Value Bool)
|
||||
isAllocationStudyFeature = isRelevantStudyFeatureCached AllocationTerm
|
||||
|
||||
allocationUserStudyFeatures :: MonadIO m => AllocationId -> UserId -> SqlPersistT m UserTableStudyFeatures
|
||||
allocationUserStudyFeatures aId uid = do
|
||||
feats <- E.select . E.from $ \(allocation `E.InnerJoin` studyFeatures `E.InnerJoin` terms `E.InnerJoin` degree) -> do
|
||||
E.on $ degree E.^. StudyDegreeId E.==. studyFeatures E.^. StudyFeaturesDegree
|
||||
E.on $ terms E.^. StudyTermsId E.==. studyFeatures E.^. StudyFeaturesField
|
||||
E.on $ isAllocationStudyFeature allocation studyFeatures
|
||||
E.where_ $ allocation E.^. AllocationId E.==. E.val aId
|
||||
E.where_ $ studyFeatures E.^. StudyFeaturesUser E.==. E.val uid
|
||||
return (terms, degree, studyFeatures)
|
||||
return . UserTableStudyFeatures . Set.fromList . flip map feats $
|
||||
\(Entity _ StudyTerms{..}, Entity _ StudyDegree{..}, Entity _ StudyFeatures{..}) -> UserTableStudyFeature
|
||||
{ userTableField = fromMaybe (tshow studyTermsKey) studyTermsName
|
||||
, userTableDegree = fromMaybe (tshow studyDegreeKey) studyDegreeName
|
||||
, userTableSemester = studyFeaturesSemester
|
||||
, userTableFieldType = studyFeaturesType
|
||||
}
|
||||
|
||||
@ -175,6 +175,7 @@ import Data.Word.Word24.Instances as Import ()
|
||||
import Control.Monad.Trans.Memo.StateCache.Instances as Import (hoistStateCache)
|
||||
import Database.Persist.Sql.Types.Instances as Import ()
|
||||
import Control.Monad.Catch.Instances as Import ()
|
||||
import Ldap.Client.Instances as Import ()
|
||||
|
||||
import Crypto.Hash as Import (Digest, SHA3_256, SHA3_512)
|
||||
import Crypto.Random as Import (ChaChaDRG, Seed)
|
||||
|
||||
@ -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
|
||||
|
||||
11
src/Ldap/Client/Instances.hs
Normal file
11
src/Ldap/Client/Instances.hs
Normal file
@ -0,0 +1,11 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
|
||||
module Ldap.Client.Instances
|
||||
(
|
||||
) where
|
||||
|
||||
import ClassyPrelude
|
||||
import Ldap.Client
|
||||
|
||||
|
||||
deriving instance Ord ResultCode
|
||||
@ -110,6 +110,9 @@ migrateAll = do
|
||||
$logDebugS "Migration" "Persistent automatic migration"
|
||||
mapM_ ($logInfoS "Migration") =<< runMigrationSilent migrateAll'
|
||||
|
||||
$logDebugS "Migration" "Migrations marked as ‘always safe’"
|
||||
mapM_ ($logInfoS "Migration") =<< runMigrationSilent migrateAlwaysSafe
|
||||
|
||||
requiresMigration :: forall m.
|
||||
( MonadLogger m
|
||||
, MonadResource m
|
||||
@ -131,6 +134,8 @@ requiresMigration = mapReaderT (exceptT return return) $ do
|
||||
$logInfoS "Migration" $ intercalate "; " automatic
|
||||
throwError True
|
||||
|
||||
-- Does not consider `migrateAlwaysSafe`
|
||||
|
||||
return False
|
||||
|
||||
initialMigration :: Migration
|
||||
@ -172,19 +177,6 @@ migrateManual = do
|
||||
, ("user_ldap_primary_key", "CREATE INDEX user_ldap_primary_key ON \"user\" (ldap_primary_key)" )
|
||||
, ("file_content_entry_chunk_hash", "CREATE INDEX file_content_entry_chunk_hash ON \"file_content_entry\" (chunk_hash)" )
|
||||
]
|
||||
|
||||
recordedChangelogItems <- lift . lift $ selectList [] []
|
||||
let missingChangelogItems = Set.toList $ Set.fromList universeF `Set.difference` recordedChangelogItems'
|
||||
where recordedChangelogItems' = Set.fromList [ changelogItemFirstSeenItem | Entity _ ChangelogItemFirstSeen{..} <- recordedChangelogItems ]
|
||||
unless (null missingChangelogItems) $ do
|
||||
now <- iso8601Show . localDay . TZ.utcToLocalTimeTZ appTZ <$> liftIO getCurrentTime
|
||||
addMigration False $
|
||||
let sql = [st|INSERT INTO changelog_item_first_seen (item, first_seen) VALUES #{vals}|]
|
||||
vals = Text.intercalate ", " $ do
|
||||
item <- missingChangelogItems
|
||||
return [st|('#{toPathPiece item}', '#{now}')|]
|
||||
in sql
|
||||
|
||||
where
|
||||
addIndex :: Text -> Sql -> Migration
|
||||
addIndex ixName ixDef = do
|
||||
@ -194,6 +186,21 @@ migrateManual = do
|
||||
_other -> return True
|
||||
unless alreadyDefined $ addMigration False ixDef
|
||||
|
||||
migrateAlwaysSafe :: Migration
|
||||
-- | Part of `migrateAll` but not checked in `requiresMigration`
|
||||
migrateAlwaysSafe = do
|
||||
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
|
||||
today <- localDay . TZ.utcToLocalTimeTZ appTZ <$> liftIO getCurrentTime
|
||||
addMigration False $ do
|
||||
let sql = [st|INSERT INTO changelog_item_first_seen (item, first_seen) VALUES #{vals}|]
|
||||
vals = Text.intercalate ", " $ do
|
||||
item <- missingChangelogItems
|
||||
let itemDay = Map.findWithDefault today item changelogItemDays
|
||||
return [st|('#{toPathPiece item}', '#{iso8601Show itemDay}')|]
|
||||
in sql
|
||||
|
||||
{-
|
||||
Confusion about quotes, from the PostgreSQL Manual:
|
||||
@ -979,13 +986,7 @@ customMigrations = Map.fromListWith (>>)
|
||||
|]
|
||||
)
|
||||
, ( AppliedMigrationKey [migrationVersion|42.0.0|] [version|43.0.0|]
|
||||
, unlessM (tableExists "changelog_item_first_seen") $ do
|
||||
[executeQQ|
|
||||
CREATE TABLE "changelog_item_first_seen" (PRIMARY KEY ("item"), "item" VARCHAR NOT NULL, "first_seen" DATE NOT NULL);
|
||||
|]
|
||||
insertMany_ [ ChangelogItemFirstSeen{..}
|
||||
| (changelogItemFirstSeenItem, changelogItemFirstSeenFirstSeen) <- Map.toList changelogItemDays
|
||||
]
|
||||
, return () -- Unused; used to create and fill `ChangelogItemFirstSeen`
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@ -29,15 +29,16 @@ makePrisms ''ChangelogItemKind
|
||||
|
||||
classifyChangelogItem :: ChangelogItem -> ChangelogItemKind
|
||||
classifyChangelogItem = \case
|
||||
ChangelogHaskellCampusLogin -> ChangelogItemBugfix
|
||||
ChangelogTooltipsWithoutJavascript -> ChangelogItemBugfix
|
||||
ChangelogButtonsWorkWithoutJavascript -> ChangelogItemBugfix
|
||||
ChangelogTableFormsWorkAfterAjax -> ChangelogItemBugfix
|
||||
ChangelogPassingByPointsWorks -> ChangelogItemBugfix
|
||||
ChangelogErrorMessagesForTableItemVanish -> ChangelogItemBugfix
|
||||
ChangelogExamAchievementParticipantDuplication -> ChangelogItemBugfix
|
||||
ChangelogFormsTimesReset -> ChangelogItemBugfix
|
||||
_other -> ChangelogItemFeature
|
||||
ChangelogHaskellCampusLogin -> ChangelogItemBugfix
|
||||
ChangelogTooltipsWithoutJavascript -> ChangelogItemBugfix
|
||||
ChangelogButtonsWorkWithoutJavascript -> ChangelogItemBugfix
|
||||
ChangelogTableFormsWorkAfterAjax -> ChangelogItemBugfix
|
||||
ChangelogPassingByPointsWorks -> ChangelogItemBugfix
|
||||
ChangelogErrorMessagesForTableItemVanish -> ChangelogItemBugfix
|
||||
ChangelogExamAchievementParticipantDuplication -> ChangelogItemBugfix
|
||||
ChangelogFormsTimesReset -> ChangelogItemBugfix
|
||||
ChangelogAllocationCourseAcceptSubstitutesFixed -> ChangelogItemBugfix
|
||||
_other -> ChangelogItemFeature
|
||||
|
||||
changelogItemDays :: Map ChangelogItem Day
|
||||
changelogItemDays = Map.fromListWithKey (\k d1 d2 -> bool (error $ "Duplicate changelog days for " <> show k) d1 $ d1 /= d2)
|
||||
@ -142,4 +143,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|])
|
||||
]
|
||||
|
||||
@ -108,12 +108,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
|
||||
@ -127,6 +130,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, Generic, Typeable)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -1261,8 +1261,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')
|
||||
@ -1293,6 +1292,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
|
||||
|
||||
@ -233,6 +233,8 @@ makeLenses_ ''Rating'
|
||||
|
||||
makeLenses_ ''FallbackPersonalisedSheetFilesKey
|
||||
|
||||
makePrisms ''AllocationPriority
|
||||
|
||||
-- makeClassy_ ''Load
|
||||
|
||||
--------------------------
|
||||
|
||||
@ -305,6 +305,7 @@ observeFavouritesQuickActionsDuration act = do
|
||||
data LoginOutcome
|
||||
= LoginSuccessful
|
||||
| LoginInvalidCredentials
|
||||
| LoginADInvalidCredentials
|
||||
| LoginError
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
deriving anyclass (Universe, Finite)
|
||||
|
||||
@ -30,7 +30,7 @@ extra-deps:
|
||||
- serversession
|
||||
- serversession-backend-acid-state
|
||||
- git: git@gitlab2.rz.ifi.lmu.de:uni2work/xss-sanitize.git
|
||||
commit: 074ed7c8810aca81f60f2c535f9e7bad67e9d95a
|
||||
commit: dc928c3a456074b8777603bea20e81937321777f
|
||||
- git: git@gitlab2.rz.ifi.lmu.de:uni2work/colonnade.git
|
||||
commit: f8170266ab25b533576e96715bedffc5aa4f19fa
|
||||
subdirs:
|
||||
@ -39,7 +39,7 @@ extra-deps:
|
||||
commit: 42103ab247057c04c8ce7a83d9d4c160713a3df1
|
||||
|
||||
- git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
subdirs:
|
||||
- cryptoids-class
|
||||
- cryptoids-types
|
||||
|
||||
@ -101,12 +101,12 @@ packages:
|
||||
version: 0.3.6
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/xss-sanitize.git
|
||||
pantry-tree:
|
||||
size: 691
|
||||
sha256: 7cada516aa3cad4adc214f5eb90dd07c3a8ecabdc5551f761366fc270ae2e086
|
||||
commit: 074ed7c8810aca81f60f2c535f9e7bad67e9d95a
|
||||
size: 750
|
||||
sha256: f567a1c834448daaa164f2029fad164e6c8df2d4c92b51f811bae19cc0c95975
|
||||
commit: dc928c3a456074b8777603bea20e81937321777f
|
||||
original:
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/xss-sanitize.git
|
||||
commit: 074ed7c8810aca81f60f2c535f9e7bad67e9d95a
|
||||
commit: dc928c3a456074b8777603bea20e81937321777f
|
||||
- completed:
|
||||
subdir: colonnade
|
||||
name: colonnade
|
||||
@ -137,65 +137,65 @@ packages:
|
||||
version: 0.0.0
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
pantry-tree:
|
||||
size: 350
|
||||
sha256: f014c9ff9666a4d4bab82dd2b3092fd2004b40ebf2bcd32cf7d90035e08ce75b
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
size: 412
|
||||
sha256: 30466648d273ffb1d580b7961188d67a0bedb3703d6d5f8cca3c15a45295f203
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
original:
|
||||
subdir: cryptoids-class
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
- completed:
|
||||
subdir: cryptoids-types
|
||||
name: cryptoids-types
|
||||
version: 1.0.0
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
pantry-tree:
|
||||
size: 258
|
||||
sha256: d1465d25a1a1807d5a88d9a09085fd4a2f49f2e57b8398496691ffad30e8f88c
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
size: 320
|
||||
sha256: 824ac5c55c2ad553bd401bb5a99731bbdccc828ecc5d71f174e9375c4e03c46e
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
original:
|
||||
subdir: cryptoids-types
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
- completed:
|
||||
subdir: cryptoids
|
||||
name: cryptoids
|
||||
version: 0.5.1.0
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
pantry-tree:
|
||||
size: 510
|
||||
sha256: 7c16ce6b5de6988ba628027a055fe7faa8b3a2e2bc77d7088e8dad23e9bac7a1
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
size: 566
|
||||
sha256: b1f49dde76ff7e78b76e7f2f3b3f76c55e5e61555d1df5415ad3b6eb80dda2cb
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
original:
|
||||
subdir: cryptoids
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
- completed:
|
||||
subdir: filepath-crypto
|
||||
name: filepath-crypto
|
||||
version: 0.1.0.0
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
pantry-tree:
|
||||
size: 614
|
||||
sha256: 2f5d7053ba61d8727b2a0b4443017e9af013196d2d53064c98f21bbd196ccd52
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
size: 676
|
||||
sha256: 9c31a2ffb2b1c86f9ba34eb83529c7a5a7dc68a49f89813c9b553427474654d9
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
original:
|
||||
subdir: filepath-crypto
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
- completed:
|
||||
subdir: uuid-crypto
|
||||
name: uuid-crypto
|
||||
version: 1.4.0.0
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
pantry-tree:
|
||||
size: 359
|
||||
sha256: 1861593e0b304b8a09db3e7b435ae6763f57d2051a1c8770a051adc5aa0f0edd
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
size: 417
|
||||
sha256: 852e59807df1f2cf4b5a3748c46fa149d15a78651c93addfe5fc31d2d94c47f4
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
original:
|
||||
subdir: uuid-crypto
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/cryptoids.git
|
||||
commit: 4d91394475b144ea5bf7ba111f93756cc0de8a3f
|
||||
commit: 130b0dcbf2b09ccdf387b50262f1efbbbf1819e3
|
||||
- completed:
|
||||
subdir: gearhash
|
||||
name: gearhash
|
||||
|
||||
@ -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
|
||||
|
||||
@ -53,6 +53,25 @@ $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}
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationFreeCapacity} #
|
||||
^{iconInvisible}
|
||||
<dd .deflist__dd>
|
||||
$maybe freeCap <- freeCapacity
|
||||
#{freeCap}
|
||||
$if freeCap <= 0
|
||||
\ ^{iconOK}
|
||||
$nothing
|
||||
∞
|
||||
$maybe fromT <- allocationRegisterByCourse
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationRegisterByCourseFrom}
|
||||
|
||||
@ -14,6 +14,25 @@ $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}
|
||||
<p>
|
||||
_{MsgCourseAllocationCourseParticipants}:
|
||||
$maybe capacity <- courseCapacity
|
||||
\ _{MsgCourseMembersCountLimited partCount capacity}
|
||||
$if partCount < capacity
|
||||
\ ^{iconProblem}
|
||||
$nothing
|
||||
\ _{MsgCourseMembersCount partCount}
|
||||
\ ^{iconProblem}
|
||||
$if hasApplicationTemplate || is _Just courseApplicationsInstructions
|
||||
<div .allocation-course__instructions-label .allocation__label>
|
||||
_{MsgCourseApplicationInstructionsApplication}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
6
templates/exam/schoolMassInput/add.hamlet
Normal file
6
templates/exam/schoolMassInput/add.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$newline never
|
||||
<td>
|
||||
#{csrf}
|
||||
^{fvWidget addView}
|
||||
<td>
|
||||
^{fvWidget submitView}
|
||||
3
templates/exam/schoolMassInput/cell.hamlet
Normal file
3
templates/exam/schoolMassInput/cell.hamlet
Normal file
@ -0,0 +1,3 @@
|
||||
$newline never
|
||||
<td>
|
||||
#{schoolName}
|
||||
11
templates/exam/schoolMassInput/layout.hamlet
Normal file
11
templates/exam/schoolMassInput/layout.hamlet
Normal 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)}
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Das Eintragen von Fristen bis zu denen Nachrücker aus Zentralanmeldungen akzeptiert werden ist nun möglich
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
It is now possible to specify deadlines up to which substitute registrations from central allocations are accepted
|
||||
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Aktive Authorisierungsprädikate können nun in einem persistenten Cookie gespeichert werden
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Active authorisation predicates can now be saved as a persistent cookie
|
||||
@ -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
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Exams associated with courses may now also specify additional departments that then have full access to exam achievements
|
||||
2
templates/i18n/changelog/exam-staff.de-de-formal.hamlet
Normal file
2
templates/i18n/changelog/exam-staff.de-de-formal.hamlet
Normal file
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Mit Kursen assoziierte Prüfungen müssen nun auch verantwortliche Hochschullehrer bzw. Prüfer angeben
|
||||
2
templates/i18n/changelog/exam-staff.en-eu.hamlet
Normal file
2
templates/i18n/changelog/exam-staff.en-eu.hamlet
Normal file
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Exams associated with courses now also need to specify responsible university teachers/examiners
|
||||
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Bessere Fehlermeldungen bei fehlgeschlagenem Login
|
||||
2
templates/i18n/changelog/ldap-ad-errors.en-eu.hamlet
Normal file
2
templates/i18n/changelog/ldap-ad-errors.en-eu.hamlet
Normal file
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
Better error messages on failed login
|
||||
108
templates/i18n/faq/allocation-no-places.de-de-formal.hamlet
Normal file
108
templates/i18n/faq/allocation-no-places.de-de-formal.hamlet
Normal file
@ -0,0 +1,108 @@
|
||||
$newline never
|
||||
<p>
|
||||
Die Plätze in den Zentralanmeldungen werden nach den folgenden #
|
||||
Kriterien verteilt (in grober Reihenfolge des Einfluss, den sie auf #
|
||||
die Verteilung haben):
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Die eigene Priorisierung der Bewerbung (1. Wahl, etc.)
|
||||
|
||||
<br />
|
||||
|
||||
Die Priorisierung hat jedoch nur eine ordnende Funktion und #
|
||||
diese auch nur innerhalb der Bewerbungen eines einzelnen #
|
||||
Bewerbers. #
|
||||
|
||||
Die genauen Zahlen sind also bedeutungslos und werden auch nicht #
|
||||
unter den Bewerbern verglichen.
|
||||
|
||||
<li>
|
||||
|
||||
Studienfortschritt (gemessen am Prozentsatz der für den Abschluss #
|
||||
erforderlichen Veranstaltungen, die bereits bestanden wurden), #
|
||||
nicht jedoch das Fach- oder Hochschulsemester
|
||||
|
||||
<br />
|
||||
|
||||
Den aus dem Studienfortschritt errechnet Parameter nennt Uni2work #
|
||||
die „zentrale Dringlichkeit“.
|
||||
|
||||
<li>
|
||||
|
||||
Etwaige Bewertungen der Bewerbungen durch die Kursverwalter
|
||||
|
||||
<p>
|
||||
|
||||
Wenn Sie also keine Plätze in der Zentralanmeldung erhalten haben, #
|
||||
liegt dies für gewöhnlich daran, dass Ihre zentrale Dringlichkeit in #
|
||||
dieser Vergabe zu gering war und stattdessen andere Bewerber, mit #
|
||||
weiter fortgeschrittenem Studium, Plätze erhalten haben.
|
||||
|
||||
<br />
|
||||
|
||||
Ebenso kann es sein, dass Sie nicht Ihre erste Wahl erhalten, wenn #
|
||||
diese unter Studierenden mit höherer Dringlichkeit beliebt ist.
|
||||
|
||||
<br />
|
||||
|
||||
So wird sichergestellt, dass der Studienabschluss nicht durch #
|
||||
fehlende Credits verzögert wird, die nur in Kursen erreicht werden #
|
||||
können, die an einer Zentralanmeldung teilnehmen.
|
||||
|
||||
<p>
|
||||
|
||||
Für gewöhnlich gibt es zu jeder Zentralanmeldung auch ein #
|
||||
Nachrückerverfahren. #
|
||||
|
||||
Es werden hierfür auf Basis der Bewerbungen für die #
|
||||
Zentralanmeldungen Plätze, die wieder frei werden, erneut verteilt.
|
||||
|
||||
<br />
|
||||
|
||||
Die Kriterien für diese Verteilungen sind die selben, wie auch bei #
|
||||
der ursprünglichen Verteilung. #
|
||||
|
||||
<br />
|
||||
|
||||
Wenn Sie sich bereits in der Zentralanmeldung beworben haben, ist #
|
||||
eine gesonderte Anmeldung oder Bewerbung als Nachrücker nicht #
|
||||
erforderlich. #
|
||||
|
||||
Sie werden automatisch benachrichtigt, falls Sie über das #
|
||||
Nachrückerverfahren doch noch einen Platz bzw. zusätzliche Plätze #
|
||||
erhalten (außer Sie haben diese Benachrichtigung aktiv unter #
|
||||
„Anpassen“ ausgeschaltet).
|
||||
|
||||
<p>
|
||||
Um in der nächsten Zentralanmeldung eine bessere Chance auf einen #
|
||||
Platz zu haben können Sie folgende Schritte ergreifen:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Für möglichst viele der angebotenen Kurse bewerben
|
||||
|
||||
<br />
|
||||
|
||||
Bei gleicher zentraler Dringlichkeit haben Bewerber, die mehr #
|
||||
Bewerbungen einreichen, eine signifikant bessere Chance einen #
|
||||
Platz zu erhalten.
|
||||
|
||||
<li>
|
||||
Normal weiter studieren
|
||||
|
||||
<br />
|
||||
|
||||
Durch zusätzliche bestandene Leistungen wird sich Ihr #
|
||||
Studienfortschritt und somit Ihre zentrale Dringlichkeit erhöhen.
|
||||
|
||||
<li>
|
||||
Bessere Bewerbungen einreichen
|
||||
|
||||
<br />
|
||||
|
||||
Eine gute Bewertung der Bewerbung kann einen beträchtlichen #
|
||||
Unterschied in zentraler Dringlichkeit ausgleichen. #
|
||||
|
||||
Wenn Ihre Bewerbungen von den Kursverwaltern gut bewertet werden, #
|
||||
haben Sie eine bessere Chance auf einen Platz.
|
||||
105
templates/i18n/faq/allocation-no-places.en-eu.hamlet
Normal file
105
templates/i18n/faq/allocation-no-places.en-eu.hamlet
Normal file
@ -0,0 +1,105 @@
|
||||
$newline never
|
||||
<p>
|
||||
Placements in central allocations are allocated according to the #
|
||||
following criteria (ordered roughly by their impact on the #
|
||||
allocation):
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
The priority of the application (1st Choice, etc.)
|
||||
|
||||
<br />
|
||||
|
||||
The priority is only used to order the applications in the context #
|
||||
of a single applicant. #
|
||||
|
||||
Therefore the exact numerical values are inconsequential and are #
|
||||
not compared between applicants.
|
||||
|
||||
<li>
|
||||
Study progress (measured by the number ECTS credits achieved as a #
|
||||
percentage of those required for graduation) but not (university) #
|
||||
semesters
|
||||
|
||||
<br />
|
||||
|
||||
The parameter calculated from study progress is referred to within #
|
||||
Uni2work as “central priority”.
|
||||
|
||||
<li>
|
||||
Ratings of applications by course administrators
|
||||
|
||||
<p>
|
||||
If you were not allocated any placements this is usually because #
|
||||
your central priority was too low. #
|
||||
|
||||
Instead other applicants with higher central priority, and thus a #
|
||||
higher degree of study progress, have received placements.
|
||||
|
||||
<br />
|
||||
|
||||
Accordingly you may not have received the placements you wanted #
|
||||
because the respective courses were popular among applicants with #
|
||||
higher central priority.
|
||||
|
||||
<br />
|
||||
|
||||
This method of allocation ensures that graduation is not impeded by #
|
||||
missing credits which can only be gained through courses which #
|
||||
participate in a central allocation.
|
||||
|
||||
<p>
|
||||
|
||||
There usually is a process for substitute registrations. #
|
||||
|
||||
Places that become free after the initial allocation are assigned #
|
||||
again on the basis of the existing applications.
|
||||
|
||||
<br />
|
||||
|
||||
The criteria for the allocation of placements are the same as for #
|
||||
the initial allocation.
|
||||
|
||||
<br />
|
||||
|
||||
If you have already applied for the central allocation no further #
|
||||
registration or application is necessary to be assigned a substitute #
|
||||
registration. #
|
||||
|
||||
You will be notified automatically if you are assigned additional #
|
||||
placements (unless you have actively disabled the notification under #
|
||||
“Settings”).
|
||||
|
||||
<p>
|
||||
|
||||
To improve your chances of being allocated a placement during the #
|
||||
next central allocation, you may try the following:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Apply for as many courses as possible
|
||||
|
||||
<br />
|
||||
|
||||
Of two applicants with the same central priority, the one who #
|
||||
applied for more courses has a significantly better chance of #
|
||||
being allocated a placement.
|
||||
|
||||
<li>
|
||||
Continue your studies normally
|
||||
|
||||
<br />
|
||||
|
||||
Through achieving additional credits your degree of study progress #
|
||||
will improve and thus your central priority will, too.
|
||||
|
||||
<li>
|
||||
Write better applications
|
||||
|
||||
<br />
|
||||
|
||||
Having an application rated well can ameliorate a considerable #
|
||||
difference in central priority. #
|
||||
|
||||
If your applications are rated well by course administrators your #
|
||||
chances to be allocated a placement improve.
|
||||
@ -0,0 +1,17 @@
|
||||
$newline never
|
||||
|
||||
<p>
|
||||
Gewöhnlicherweise wird Ihr Benutzereintrag gesperrt, wenn sie #
|
||||
exmatrikuliert werden bzw. Ihr Beschäftigungsverhältnis endet. #
|
||||
|
||||
Es kommt gelegentlich vor, dass Ihr Benutzereintrag nicht korrekt #
|
||||
entsperrt wird, wenn Sie wieder immatrikuliert bzw. eingestellt #
|
||||
werden.
|
||||
|
||||
<p>
|
||||
Falls Sie aktuell immatrikuliert bzw. eingestellt sind, oder Sie #
|
||||
einen anderen triftigen Grund vorweisen können, warum Sie Zugang zu #
|
||||
Uni2work brauchen, wenden Sie sich bitte über #
|
||||
das <a href=@{HelpR}>Hilfe-Formular</a>, oben rechts auf jeder #
|
||||
Seite, an die Uni2work-Administration und schildern Sie Ihre #
|
||||
Situation.
|
||||
@ -0,0 +1,14 @@
|
||||
$newline never
|
||||
|
||||
<p>
|
||||
Usually your account is disabled once you are no longer matriculated #
|
||||
(i.e. registered as a student) or employed. #
|
||||
|
||||
Occasionally accounts are not correctly re-enabled once you are #
|
||||
matriculated or employed, again.
|
||||
|
||||
<p>
|
||||
If you are currently matriculated, employed, or have another good #
|
||||
reason why you should have access to Uni2work, please contact a #
|
||||
Uni2work-Administrator using the <a href=@{HelpR}>Support form</a> #
|
||||
(at the top right of every page) and describe your situation.
|
||||
@ -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}
|
||||
|
||||
18
test/Auth/LDAP/ADSpec.hs
Normal file
18
test/Auth/LDAP/ADSpec.hs
Normal file
@ -0,0 +1,18 @@
|
||||
module Auth.LDAP.ADSpec where
|
||||
|
||||
import TestImport
|
||||
|
||||
import Auth.LDAP.AD
|
||||
import Ldap.Client
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
describe "parseADError" $ do
|
||||
it "parses some examples" . mapM_ exampleEntry $
|
||||
[ ( InvalidCredentials, ADAccountDisabled, "80090308: LdapErr: DSID-0C090446, comment: AcceptSecurityContext error, data 533, v2580")
|
||||
, ( InvalidCredentials, ADLogonFailure , "80090308: LdapErr: DSID-0C090446, comment: AcceptSecurityContext error, data 52e, v2580")
|
||||
]
|
||||
|
||||
exampleEntry :: ( ResultCode, ADError, Text ) -> Expectation
|
||||
exampleEntry ( resCode, adError, errMsg ) = example $ parseADError resCode errMsg `shouldBe` Right adError
|
||||
@ -4,8 +4,8 @@ module Database
|
||||
, module Database.Fill
|
||||
) where
|
||||
|
||||
import "uniworx" Import hiding (Option(..))
|
||||
import "uniworx" Application (db, getAppDevSettings)
|
||||
import "uniworx" Import hiding (Option(..), getArgs)
|
||||
import "uniworx" Application (db', getAppSettings)
|
||||
|
||||
import UnliftIO.Pool (destroyAllResources)
|
||||
|
||||
@ -15,6 +15,7 @@ import Control.Monad.Logger
|
||||
import System.Console.GetOpt
|
||||
import System.Exit (exitWith, ExitCode(..))
|
||||
import System.IO (hPutStrLn)
|
||||
import System.Environment (getArgs, withArgs)
|
||||
|
||||
import Database.Persist.Sql.Raw.QQ
|
||||
|
||||
@ -39,19 +40,19 @@ argsDescr =
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- map unpack <$> getArgs
|
||||
case getOpt Permute argsDescr args of
|
||||
(acts@(_:_), [], []) -> forM_ acts $ \case
|
||||
case getOpt' Permute argsDescr args of
|
||||
(acts@(_:_), nonOpts, unrecOpts, []) -> withArgs (unrecOpts ++ nonOpts) . forM_ acts $ \case
|
||||
DBClear -> runStderrLoggingT $ do -- We don't use `db` here, since we do /not/ want any migrations to run, yet
|
||||
settings <- liftIO getAppDevSettings
|
||||
settings <- liftIO getAppSettings
|
||||
withPostgresqlConn (pgConnStr $ appDatabaseConf settings) . runSqlConn $ do
|
||||
[executeQQ|drop owned by current_user|] :: ReaderT SqlBackend _ ()
|
||||
DBTruncate -> db $ do
|
||||
DBTruncate -> db' $ do
|
||||
foundation <- getYesod
|
||||
liftIO . destroyAllResources $ appConnPool foundation
|
||||
truncateDb
|
||||
DBMigrate -> db $ return ()
|
||||
DBFill -> db $ fillDb
|
||||
(_, _, errs) -> do
|
||||
DBMigrate -> db' $ return ()
|
||||
DBFill -> db' $ fillDb
|
||||
(_, _, _, errs) -> do
|
||||
forM_ errs $ hPutStrLn stderr
|
||||
hPutStrLn stderr $ usageInfo "uniworxdb" argsDescr
|
||||
exitWith $ ExitFailure 2
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user