Merge branch 'master' into 409-find-implement-alternative-for-datepicker
This commit is contained in:
commit
939bbfa884
70
CHANGELOG.md
70
CHANGELOG.md
@ -2,6 +2,76 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [5.0.2](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.0.1...v5.0.2) (2019-08-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **course-deregister:** only delete relevant users exam results ([3997857](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/3997857))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### [5.0.1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.0.0...v5.0.1) (2019-08-12)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.0.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v4.14.0...v5.0.0) (2019-08-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* removed duplicated code from merge ([9fb9540](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/9fb9540))
|
||||||
|
* **course-teaser:** don't collapse unless chevron is clicked ([fca99be](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/fca99be))
|
||||||
|
* **course-teaser-css:** class name fixes ([8a92985](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/8a92985))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **course-registration:** allow independent course application ([a00698e](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a00698e))
|
||||||
|
* **course-teaser:** checkbox field for open registration filter ([e4f150d](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/e4f150d))
|
||||||
|
* **course-teaser:** display sorting "pills" for course teasers ([d964e1f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/d964e1f))
|
||||||
|
* **course-teaser:** filter by open registration ([c2c12b9](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/c2c12b9))
|
||||||
|
* **course-teaser:** final version of course-teaser for course list ([66b97d6](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/66b97d6))
|
||||||
|
* **course-teaser:** hide lecturer entry if empty ([f7fb3c1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/f7fb3c1))
|
||||||
|
* **course-teaser:** incomplete course teaser for course list ([9a97925](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/9a97925))
|
||||||
|
* **course-teaser:** moved course teaser functionality to util ([c99a3c7](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/c99a3c7))
|
||||||
|
* **course-teaser:** no display of chevron without description ([5c88c13](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/5c88c13))
|
||||||
|
* **course-teaser:** no page reload on sorting ([68b8d24](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/68b8d24))
|
||||||
|
* **course-teaser:** only true lecturers without assistants ([7926f29](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/7926f29))
|
||||||
|
* **course-teaser:** redirecting to course/ ([aa20389](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/aa20389))
|
||||||
|
* **course-teaser:** reintroduced courseId and course-teaser.julius ([3b6e700](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/3b6e700))
|
||||||
|
* **course-teaser:** show openCourses also to logged in users ([8cca548](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/8cca548))
|
||||||
|
* **course-teaser:** unpolished version of course-teaser for course list ([ea5d54b](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/ea5d54b))
|
||||||
|
* **course-teaser:** working link to course pages ([8a49979](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/8a49979))
|
||||||
|
* **course-teaser-css:** removed description label ([a25efb3](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a25efb3))
|
||||||
|
* **course-teaser-filter:** filter for lecturers ([e96e17f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/e96e17f))
|
||||||
|
* **course-teaser-filter:** working filters for semester and institute ([3b419b3](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/3b419b3))
|
||||||
|
* **courses:** rework couse registration ([79d4ae2](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/79d4ae2))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **courses:** auditing for course registrations and deregistrations, more
|
||||||
|
tightly couple exam results, exam registration, and course registration (delete
|
||||||
|
them together now)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.14.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v4.13.1...v4.14.0) (2019-08-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **exam:** fix warning message ([60869fd](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/60869fd))
|
||||||
|
* **info:** minor whitespace correction ([0ce4dd1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/0ce4dd1))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **info:** info seiten überarbeitet ([7459fc3](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/7459fc3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [4.13.1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v4.13.0...v4.13.1) (2019-08-07)
|
### [4.13.1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v4.13.0...v4.13.1) (2019-08-07)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -60,9 +60,9 @@ export class AsyncTable {
|
|||||||
this._asyncTableHeader = this._element.dataset.asyncTableDbHeader;
|
this._asyncTableHeader = this._element.dataset.asyncTableDbHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = this._element.querySelector('table');
|
const table = this._element.querySelector('table, .div__course-teaser');
|
||||||
if (!table) {
|
if (!table) {
|
||||||
throw new Error('Async Table utility needs a <table> in its element!');
|
throw new Error('Async Table utility needs a <table> or a <div .div__course-teaser> in its element!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawTableId = table.id;
|
const rawTableId = table.id;
|
||||||
@ -94,7 +94,7 @@ export class AsyncTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setupSortableHeaders() {
|
_setupSortableHeaders() {
|
||||||
this._ths = Array.from(this._scrollTable.querySelectorAll('th.sortable'))
|
this._ths = Array.from(this._scrollTable.querySelectorAll('th.sortable, .course-header'))
|
||||||
.map((th) => ({ element: th }));
|
.map((th) => ({ element: th }));
|
||||||
|
|
||||||
this._ths.forEach((th) => {
|
this._ths.forEach((th) => {
|
||||||
|
|||||||
37
frontend/src/utils/course-teaser/course-teaser.js
Normal file
37
frontend/src/utils/course-teaser/course-teaser.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Utility } from '../../core/utility';
|
||||||
|
import './course-teaser.scss';
|
||||||
|
|
||||||
|
var COURSE_TEASER_INITIALIZED_CLASS = 'course-teaser--initialized';
|
||||||
|
|
||||||
|
var COURSE_TEASER_EXPANDED_CLASS = 'course-teaser__expanded';
|
||||||
|
var COURSE_TEASER_CHEVRON_CLASS = 'course-teaser__chevron';
|
||||||
|
|
||||||
|
@Utility({
|
||||||
|
selector: '[uw-course-teaser]:not(.course-teaser__disabled)',
|
||||||
|
})
|
||||||
|
export class CourseTeaser {
|
||||||
|
|
||||||
|
_element;
|
||||||
|
|
||||||
|
constructor(element) {
|
||||||
|
if (!element) {
|
||||||
|
throw new Error('CourseTeaser utility cannot be setup without an element!');
|
||||||
|
}
|
||||||
|
if (element.classList.contains(COURSE_TEASER_INITIALIZED_CLASS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._element = element;
|
||||||
|
element.addEventListener('click', e => this._onToggleExpand(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToggleExpand(event) {
|
||||||
|
var isLink = event.target.tagName.toLowerCase() === 'a';
|
||||||
|
var isChevron = event.target.classList.contains(COURSE_TEASER_CHEVRON_CLASS);
|
||||||
|
var isExpanded = this._element.classList.contains(COURSE_TEASER_EXPANDED_CLASS);
|
||||||
|
|
||||||
|
if ((!isExpanded && !isLink) || isChevron) {
|
||||||
|
this._element.classList.toggle(COURSE_TEASER_EXPANDED_CLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
8
frontend/src/utils/course-teaser/course-teaser.md
Normal file
8
frontend/src/utils/course-teaser/course-teaser.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# CourseTeaser
|
||||||
|
|
||||||
|
Handles expanding and collapsing single course teaser widgets on click.
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
```html
|
||||||
|
<div uw-course-teaser>
|
||||||
|
```
|
||||||
181
frontend/src/utils/course-teaser/course-teaser.scss
Normal file
181
frontend/src/utils/course-teaser/course-teaser.scss
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
[uw-course-teaser] {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 5px 7px;
|
||||||
|
grid-template-columns: 50px 120px 1fr;
|
||||||
|
padding: 10px;
|
||||||
|
/* background-color: var(--course-bg-color); */
|
||||||
|
transition: background-color .1s ease-out;
|
||||||
|
|
||||||
|
&:not(.course-teaser__disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--course-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ [uw-course-teaser] {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* chevron */
|
||||||
|
.course-teaser__chevron {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
margin-top: -8px;
|
||||||
|
border-width: 0 3px 3px 0;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-color: var(--color-fontsec);
|
||||||
|
border-style: solid;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semester */
|
||||||
|
.course-teaser__semester {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
justify-self: end;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
a {
|
||||||
|
color: var(--color-fontsec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shorthand */
|
||||||
|
.course-teaser__shorthand {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 2;
|
||||||
|
justify-self: end;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* title */
|
||||||
|
.course-teaser__title {
|
||||||
|
grid-column: 3;
|
||||||
|
grid-row: 2;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
align-self: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* registration */
|
||||||
|
.course-teaser__registration {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 2;
|
||||||
|
justify-self: end;
|
||||||
|
align-self: baseline;
|
||||||
|
color: var(--color-fontsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* school */
|
||||||
|
.course-teaser__school-value {
|
||||||
|
grid-column: 3;
|
||||||
|
grid-row: 1;
|
||||||
|
align-self: end;
|
||||||
|
a {
|
||||||
|
color: var(--color-fontsec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* description */
|
||||||
|
.course-teaser__description {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
max-height: 1000px;
|
||||||
|
overflow: auto;
|
||||||
|
/* color: var(--color-fontsec); */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* subtitle */
|
||||||
|
.course-teaser__lecturer-label,
|
||||||
|
.course-teaser__duedate-label,
|
||||||
|
.course-teaser__school-label {
|
||||||
|
grid-column: 2;
|
||||||
|
justify-self: end;
|
||||||
|
color: var(--color-fontsec);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hidden in closed state */
|
||||||
|
.course-teaser__description,
|
||||||
|
.course-teaser__registration {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* registered courses */
|
||||||
|
.course-teaser__registered {
|
||||||
|
.course-teaser__registration {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expanded courses */
|
||||||
|
.course-teaser__expanded {
|
||||||
|
|
||||||
|
.course-teaser__chevron::before {
|
||||||
|
transform: translateY(7px) rotate(225deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-teaser__school-label,
|
||||||
|
.course-teaser__school-value,
|
||||||
|
.course-teaser__duedate-label,
|
||||||
|
.course-teaser__duedate-value,
|
||||||
|
.course-teaser__description {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
course teaser: header styling
|
||||||
|
*/
|
||||||
|
.course-teaser-header {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 85vw;
|
||||||
|
|
||||||
|
.course-header {
|
||||||
|
float: left;
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
position: relative;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 35px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 20px 20px 20px 20px / 50% 50% 50% 50%;
|
||||||
|
margin-right: 30px;
|
||||||
|
|
||||||
|
.course-header-link {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-teaser-header:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { InputUtils } from './inputs/inputs';
|
|||||||
import { MassInput } from './mass-input/mass-input';
|
import { MassInput } from './mass-input/mass-input';
|
||||||
import { Modal } from './modal/modal';
|
import { Modal } from './modal/modal';
|
||||||
import { Tooltip } from './tooltips/tooltips';
|
import { Tooltip } from './tooltips/tooltips';
|
||||||
|
import { CourseTeaser } from './course-teaser/course-teaser';
|
||||||
|
|
||||||
export const Utils = [
|
export const Utils = [
|
||||||
Alerts,
|
Alerts,
|
||||||
@ -23,4 +24,5 @@ export const Utils = [
|
|||||||
Modal,
|
Modal,
|
||||||
ShowHide,
|
ShowHide,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
CourseTeaser,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -7,8 +7,10 @@ BtnRegister: Anmelden
|
|||||||
BtnDeregister: Abmelden
|
BtnDeregister: Abmelden
|
||||||
BtnCourseRegister: Zum Kurs anmelden
|
BtnCourseRegister: Zum Kurs anmelden
|
||||||
BtnCourseDeregister: Vom Kurs abmelden
|
BtnCourseDeregister: Vom Kurs abmelden
|
||||||
BtnExamRegister: Anmelden zur Klausur
|
BtnCourseApply: Zum Kurs bewerben
|
||||||
BtnExamDeregister: Von der Klausur abmelden
|
BtnCourseRetractApplication: Bewerbung zum Kurs zurückziehen
|
||||||
|
BtnExamRegister: Anmelden zur Prüfung
|
||||||
|
BtnExamDeregister: Von der Prüfung abmelden
|
||||||
BtnHijack: Sitzung übernehmen
|
BtnHijack: Sitzung übernehmen
|
||||||
BtnSave: Speichern
|
BtnSave: Speichern
|
||||||
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert.
|
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert.
|
||||||
@ -86,15 +88,19 @@ CourseCapacityTip: Anzahl erlaubter Kursanmeldungen, leer lassen für unbeschrä
|
|||||||
CourseNoCapacity: In diesem Kurs sind keine Plätze mehr frei.
|
CourseNoCapacity: In diesem Kurs sind keine Plätze mehr frei.
|
||||||
TutorialNoCapacity: In dieser Übung sind keine Plätze mehr frei.
|
TutorialNoCapacity: In dieser Übung sind keine Plätze mehr frei.
|
||||||
CourseNotEmpty: In diesem Kurs sind momentan Teilnehmer angemeldet.
|
CourseNotEmpty: In diesem Kurs sind momentan Teilnehmer angemeldet.
|
||||||
CourseRegisterOk: Anmeldung erfolgreich
|
CourseRegistration: Kursanmeldung
|
||||||
CourseDeregisterOk: Erfolgreich abgemeldet
|
CourseRegisterOpen: Anmeldung möglich
|
||||||
|
CourseRegisterOk: Erfolgreich zum Kurs angemeldet
|
||||||
|
CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet
|
||||||
|
CourseApplyOk: Erfolgreich zum Kurs beworben
|
||||||
|
CourseRetractApplyOk: Bewerbung zum Kurs erfolgreich zurückgezogen
|
||||||
CourseDeregisterLecturerTip: Wenn Sie den Teilnehmer vom Kurs abmelden kann es sein, dass sie Zugriff auf diese Daten verlieren
|
CourseDeregisterLecturerTip: Wenn Sie den Teilnehmer vom Kurs abmelden kann es sein, dass sie Zugriff auf diese Daten verlieren
|
||||||
CourseStudyFeature: Assoziiertes Hauptfach
|
CourseStudyFeature: Assoziiertes Studienfach
|
||||||
CourseStudyFeatureUpdated: Assoziiertes Hauptfach geändert
|
CourseStudyFeatureTip: Dient ausschließlich der Information der Kursverwalter
|
||||||
CourseStudyFeatureNone: Kein assoziiertes Hauptfach
|
CourseStudyFeatureUpdated: Assoziiertes Studienfach geändert
|
||||||
|
CourseStudyFeatureNone: Kein assoziiertes Studienfach
|
||||||
CourseTutorial: Tutorium
|
CourseTutorial: Tutorium
|
||||||
CourseStudyFeatureTooltip: Korrekte Angabe kann Notenweiterleitungen beschleunigen
|
CourseSecretWrong: Falsches Passwort
|
||||||
CourseSecretWrong: Falsches Kennwort
|
|
||||||
CourseSecret: Zugangspasswort
|
CourseSecret: Zugangspasswort
|
||||||
CourseEditOk tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} wurde erfolgreich geändert.
|
CourseEditOk tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} wurde erfolgreich geändert.
|
||||||
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
||||||
@ -112,7 +118,7 @@ CourseMembers: Teilnehmer
|
|||||||
CourseMemberOf: Teilnehmer
|
CourseMemberOf: Teilnehmer
|
||||||
CourseMembersCount n@Int: #{n}
|
CourseMembersCount n@Int: #{n}
|
||||||
CourseMembersCountLimited n@Int max@Int: #{n}/#{max}
|
CourseMembersCountLimited n@Int max@Int: #{n}/#{max}
|
||||||
CourseMembersCountOf n@Int mbNum@IntMaybe: #{n} Anmeldungen #{maybeToMessage " von " mbNum " möglichen"}
|
CourseMembersCountOf n@Int mbNum@IntMaybe: #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
|
||||||
CourseName: Name
|
CourseName: Name
|
||||||
CourseDescription: Beschreibung
|
CourseDescription: Beschreibung
|
||||||
CourseDescriptionTip: Beliebiges HTML-Markup ist gestattet
|
CourseDescriptionTip: Beliebiges HTML-Markup ist gestattet
|
||||||
@ -136,8 +142,8 @@ CourseUserNote: Notiz
|
|||||||
CourseUserNoteTooltip: Nur für Dozenten dieses Kurses einsehbar
|
CourseUserNoteTooltip: Nur für Dozenten dieses Kurses einsehbar
|
||||||
CourseUserNoteSaved: Notizänderungen gespeichert
|
CourseUserNoteSaved: Notizänderungen gespeichert
|
||||||
CourseUserNoteDeleted: Teilnehmernotiz gelöscht
|
CourseUserNoteDeleted: Teilnehmernotiz gelöscht
|
||||||
CourseUserDeregister: Abmelden
|
CourseUserDeregister: Vom Kurs abmelden
|
||||||
CourseUsersDeregistered count@Int64: #{show count} Teilnehmer abgemeldet
|
CourseUsersDeregistered count@Int64: #{show count} Teilnehmer vom Kurs abgemeldet
|
||||||
CourseUserSendMail: Mitteilung verschicken
|
CourseUserSendMail: Mitteilung verschicken
|
||||||
TutorialUserDeregister: Vom Tutorium Abmelden
|
TutorialUserDeregister: Vom Tutorium Abmelden
|
||||||
TutorialUserSendMail: Mitteilung verschicken
|
TutorialUserSendMail: Mitteilung verschicken
|
||||||
@ -149,20 +155,48 @@ CourseAllocationOption term@Text name@Text: #{name} (#{term})
|
|||||||
CourseAllocationMinCapacity: Minimale Teilnehmeranzahl
|
CourseAllocationMinCapacity: Minimale Teilnehmeranzahl
|
||||||
CourseAllocationMinCapacityTip: Wenn der Veranstaltung bei der Zentralanmeldung weniger als diese Anzahl von Teilnehmern zugeteilt würden, werden diese stattdessen auf andere Kurse umverteilt
|
CourseAllocationMinCapacityTip: Wenn der Veranstaltung bei der Zentralanmeldung weniger als diese Anzahl von Teilnehmern zugeteilt würden, werden diese stattdessen auf andere Kurse umverteilt
|
||||||
CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein
|
CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein
|
||||||
CourseAllocationInstructions: Anweisungen zur Bewerbung
|
CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung
|
||||||
CourseAllocationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben
|
CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
|
||||||
CourseAllocationApplicationTemplate: Bewerbungsvorlagen
|
CourseApplicationTemplate: Bewerbungsvorlagen
|
||||||
CourseAllocationApplicationText: Text-Bewerbungen
|
CourseApplicationTemplateTip: Werden den Studierenden zum download angeboten, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
|
||||||
CourseAllocationApplicationTextTip: Sollen die Studierenden Bewerbungen (ggf. zusätzlich zu abgegebenen Dateien) als unformatierten Text einreichen?
|
CourseApplicationsText: Text-Bewerbungen
|
||||||
CourseAllocationApplicationRatingsVisible: Feedback für Bewerbungen
|
CourseApplicationsTextTip: Sollen die Studierenden Bewerbungen (ggf. zusätzlich zu abgegebenen Dateien) als unformatierten Text einreichen?
|
||||||
CourseAllocationApplicationRatingsVisibleTip: Sollen Bewertung und Kommentar der Bewerbungen den Studierenden nach Ende der Bewertungs-Phase angezeigt werden?
|
CourseApplicationRatingsVisible: Feedback für Bewerbungen
|
||||||
|
CourseApplicationRatingsVisibleTip: Sollen Bewertung und Kommentar der Bewerbungen den Studierenden nach Ende der Bewertungs-Phase angezeigt werden?
|
||||||
|
CourseApplicationRequired: Bewerbungsverfahren
|
||||||
|
CourseApplicationRequiredTip: Sollen Anmeldungen zu diesem Kurs zunächst provisorisch (ohne Kapazitätsbeschränkung) sein, bis sie durch einen Kursverwalter (nach Bewertung der Bewerbungen) akzeptiert werden?
|
||||||
|
CourseApplicationInstructionsApplication: Anweisungen zur Bewerbung
|
||||||
|
CourseApplicationInstructionsRegistration: Anweisungen zur Anmeldung
|
||||||
|
CourseApplicationTemplateApplication: Bewerbungsvorlage(n)
|
||||||
|
CourseApplicationTemplateRegistration: Anmeldungsvorlage(n)
|
||||||
|
CourseApplicationTemplateArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-bewerbungsvorlagen
|
||||||
|
|
||||||
|
CourseApplicationText: Text-Bewerbung
|
||||||
|
CourseApplicationFollowInstructions: Beachten Sie die Anweisungen zur Bewerbung!
|
||||||
|
CourseRegistrationText: Text zur Anmeldung
|
||||||
|
CourseRegistrationFollowInstructions: Beachten Sie die Anweisungen zur Anmeldung!
|
||||||
|
|
||||||
|
CourseApplicationFile: Bewerbung
|
||||||
|
CourseApplicationFiles: Bewerbungsdatei(en)
|
||||||
|
CourseApplicationArchive: Zip-Archiv der Bewerbungsdatei(en)
|
||||||
|
CourseRegistrationFile: Datei zur Anmeldung
|
||||||
|
CourseRegistrationFiles: Datei(en) zur Anmeldung
|
||||||
|
CourseRegistrationArchive: Zip-Archiv der Datei(en) zur Anmeldung
|
||||||
|
CourseApplicationNoFiles: Keine Datei(en)
|
||||||
|
|
||||||
|
CourseApplicationDeleteToEdit: Um Ihre Bewerbung zu editieren müssen Sie sie zunächst zurückziehen und sich erneut bewerben.
|
||||||
|
CourseRegistrationDeleteToEdit: Um Ihre Anmeldungsdaten zu editieren müssen Sie sich zunächst ab- und dann erneut anmelden.
|
||||||
|
|
||||||
|
CourseLoginToApply: Um sich zum Kurz zu bewerben müssen Sie sich zunächst in Uni2work anmelden
|
||||||
|
CourseLoginToRegister: Um sich zum Kurs anzumelden müssen Sie zunächst in Uni2work anmelden
|
||||||
|
|
||||||
|
CourseApplicationArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand appId@CryptoFileNameCourseApplication displayName@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase (toPathPiece appId)}-#{foldCase displayName}
|
||||||
|
|
||||||
CourseNoAllocationsAvailable: Es sind aktuell keine Zentralanmeldungen verfügbar
|
CourseNoAllocationsAvailable: Es sind aktuell keine Zentralanmeldungen verfügbar
|
||||||
AllocationStaffRegisterToExpired: Es dürfen keine Änderungen an der Eintragung des Kurses zur Zentralanmeldung mehr vorgenommen werden
|
AllocationStaffRegisterToExpired: Es dürfen keine Änderungen an der Eintragung des Kurses zur Zentralanmeldung mehr vorgenommen werden
|
||||||
|
|
||||||
|
|
||||||
CourseFormSectionRegistration: Anmeldung
|
CourseFormSectionRegistration: Anmeldung zum Kurs
|
||||||
CourseFormSectionAdministration: Verwaltung
|
CourseFormSectionAdministration: Verwaltung
|
||||||
|
|
||||||
CourseLecturers: Kursverwalter
|
CourseLecturers: Kursverwalter
|
||||||
@ -183,6 +217,8 @@ NoSuchSchool ssh@SchoolId: Institut #{ssh} gibt es nicht.
|
|||||||
NoSuchCourseShorthand csh@CourseShorthand: Kein Kurs mit Kürzel #{csh} bekannt.
|
NoSuchCourseShorthand csh@CourseShorthand: Kein Kurs mit Kürzel #{csh} bekannt.
|
||||||
NoSuchCourse: Keinen passenden Kurs gefunden.
|
NoSuchCourse: Keinen passenden Kurs gefunden.
|
||||||
|
|
||||||
|
NoCourseDescription: Zu diesem Kurs ist keine Beschreibung verfügbar.
|
||||||
|
|
||||||
Sheet: Blatt
|
Sheet: Blatt
|
||||||
SheetList tid@TermId ssh@SchoolId csh@CourseShorthand: #{tid}-#{ssh}-#{csh} Übersicht Übungsblätter
|
SheetList tid@TermId ssh@SchoolId csh@CourseShorthand: #{tid}-#{ssh}-#{csh} Übersicht Übungsblätter
|
||||||
SheetNewHeading tid@TermId ssh@SchoolId csh@CourseShorthand: #{tid}-#{ssh}-#{csh} Neues Übungsblatt anlegen
|
SheetNewHeading tid@TermId ssh@SchoolId csh@CourseShorthand: #{tid}-#{ssh}-#{csh} Neues Übungsblatt anlegen
|
||||||
@ -290,11 +326,13 @@ MaterialDeleteCaption: Wollen Sie das unten aufgeführte Material wirklich lösc
|
|||||||
MaterialDelHasFiles count@Int64: inklusive #{count} #{pluralDE count "Datei" "Dateien"}
|
MaterialDelHasFiles count@Int64: inklusive #{count} #{pluralDE count "Datei" "Dateien"}
|
||||||
MaterialIsVisible: Achtung, dieses Material wurde bereits veröffentlicht.
|
MaterialIsVisible: Achtung, dieses Material wurde bereits veröffentlicht.
|
||||||
MaterialDeleted materialName@MaterialName: Material "#{materialName}" gelöscht
|
MaterialDeleted materialName@MaterialName: Material "#{materialName}" gelöscht
|
||||||
|
MaterialArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand materialName@MaterialName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase materialName}
|
||||||
|
|
||||||
|
|
||||||
Unauthorized: Sie haben hierfür keine explizite Berechtigung.
|
Unauthorized: Sie haben hierfür keine explizite Berechtigung.
|
||||||
UnauthorizedAnd l@Text r@Text: (#{l} UND #{r})
|
UnauthorizedAnd l@Text r@Text: (#{l} UND #{r})
|
||||||
UnauthorizedOr l@Text r@Text: (#{l} ODER #{r})
|
UnauthorizedOr l@Text r@Text: (#{l} ODER #{r})
|
||||||
|
UnauthorizedNot i@Text: (NICHT #{i})
|
||||||
UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt.
|
UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt.
|
||||||
UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen.
|
UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen.
|
||||||
UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig.
|
UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig.
|
||||||
@ -311,12 +349,14 @@ UnauthorizedCorrector: Sie sind nicht als Korrektor für diese Veranstaltung ein
|
|||||||
UnauthorizedSheetCorrector: Sie sind nicht als Korrektor für dieses Übungsblatt eingetragen.
|
UnauthorizedSheetCorrector: Sie sind nicht als Korrektor für dieses Übungsblatt eingetragen.
|
||||||
UnauthorizedCorrectorAny: Sie sind nicht als Korrektor für eine Veranstaltung eingetragen.
|
UnauthorizedCorrectorAny: Sie sind nicht als Korrektor für eine Veranstaltung eingetragen.
|
||||||
UnauthorizedRegistered: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert.
|
UnauthorizedRegistered: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert.
|
||||||
|
UnauthorizedExamResult: Sie haben keine Ergebnisse in dieser Prüfung.
|
||||||
UnauthorizedParticipant: Angegebener Benutzer ist nicht als Teilnehmer dieser Veranstaltung registriert.
|
UnauthorizedParticipant: Angegebener Benutzer ist nicht als Teilnehmer dieser Veranstaltung registriert.
|
||||||
UnauthorizedCourseTime: Dieses Kurs erlaubt momentan keine Anmeldungen.
|
UnauthorizedCourseTime: Dieses Kurs erlaubt momentan keine Anmeldungen.
|
||||||
UnauthorizedSheetTime: Dieses Übungsblatt ist momentan nicht freigegeben.
|
UnauthorizedSheetTime: Dieses Übungsblatt ist momentan nicht freigegeben.
|
||||||
|
UnauthorizedApplicationTime: Diese Bewerbung ist momentan nicht freigegeben.
|
||||||
UnauthorizedMaterialTime: Dieses Material ist momentan nicht freigegeben.
|
UnauthorizedMaterialTime: Dieses Material ist momentan nicht freigegeben.
|
||||||
UnauthorizedTutorialTime: Dieses Tutorium erlaubt momentan keine Anmeldungen.
|
UnauthorizedTutorialTime: Dieses Tutorium erlaubt momentan keine Anmeldungen.
|
||||||
UnauthorizedExamTime: Diese Klausur ist momentan nicht freigegeben.
|
UnauthorizedExamTime: Diese Prüfung ist momentan nicht freigegeben.
|
||||||
UnauthorizedSubmissionOwner: Sie sind an dieser Abgabe nicht beteiligt.
|
UnauthorizedSubmissionOwner: Sie sind an dieser Abgabe nicht beteiligt.
|
||||||
UnauthorizedSubmissionRated: Diese Abgabe ist noch nicht korrigiert.
|
UnauthorizedSubmissionRated: Diese Abgabe ist noch nicht korrigiert.
|
||||||
UnauthorizedSubmissionCorrector: Sie sind nicht der Korrektor für diese Abgabe.
|
UnauthorizedSubmissionCorrector: Sie sind nicht der Korrektor für diese Abgabe.
|
||||||
@ -395,7 +435,7 @@ TokensResetSuccess: Authorisierungs-Tokens invalidiert
|
|||||||
|
|
||||||
HomeOpenCourses: Kurse mit offener Registrierung
|
HomeOpenCourses: Kurse mit offener Registrierung
|
||||||
HomeUpcomingSheets: Anstehende Übungsblätter
|
HomeUpcomingSheets: Anstehende Übungsblätter
|
||||||
HomeUpcomingExams: Bevorstehende Klausuren
|
HomeUpcomingExams: Bevorstehende Prüfungen
|
||||||
|
|
||||||
NumCourses num@Int64: #{num} Kurse
|
NumCourses num@Int64: #{num} Kurse
|
||||||
CloseAlert: Schliessen
|
CloseAlert: Schliessen
|
||||||
@ -445,6 +485,10 @@ UpdatedSheetCorrectorsAutoFailed n@Int: #{n} #{pluralDE n "Abgabe konnte" "Abgab
|
|||||||
CouldNotAssignCorrectorsAuto num@Int64: #{num} Abgaben konnten nicht automatisch zugewiesen werden:
|
CouldNotAssignCorrectorsAuto num@Int64: #{num} Abgaben konnten nicht automatisch zugewiesen werden:
|
||||||
SelfCorrectors num@Int64: #{num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
SelfCorrectors num@Int64: #{num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
||||||
|
|
||||||
|
SubmissionOriginal: Original
|
||||||
|
SubmissionCorrected: Korrigiert
|
||||||
|
SubmissionArchiveName: abgaben
|
||||||
|
SubmissionTypeArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName subId@CryptoFileNameSubmission renderedSfType@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-#{foldCase (toPathPiece subId)}-#{foldCase renderedSfType}
|
||||||
|
|
||||||
CorrectionSheets: Übersicht Korrekturen nach Blättern
|
CorrectionSheets: Übersicht Korrekturen nach Blättern
|
||||||
CorrectionCorrectors: Übersicht Korrekturen nach Korrektoren
|
CorrectionCorrectors: Übersicht Korrekturen nach Korrektoren
|
||||||
@ -532,7 +576,7 @@ MultiSinkException name@Text error@Text: In Abgabe #{name} ist ein Fehler aufget
|
|||||||
|
|
||||||
NoTableContent: Kein Tabelleninhalt
|
NoTableContent: Kein Tabelleninhalt
|
||||||
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
|
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
|
||||||
NoUpcomingExams: In den nächsten 14 Tagen gibt es keine Klausur mit offener Registrierung in Ihren Kursen
|
NoUpcomingExams: In den nächsten 14 Tagen gibt es keine Prüfung mit offener Registrierung in Ihren Kursen
|
||||||
|
|
||||||
AdminHeading: Administration
|
AdminHeading: Administration
|
||||||
AdminUserHeading: Benutzeradministration
|
AdminUserHeading: Benutzeradministration
|
||||||
@ -550,6 +594,8 @@ ForSchools n@Int: für #{pluralDE n "Institut" "Institute"}
|
|||||||
UserListTitle: Komprehensive Benutzerliste
|
UserListTitle: Komprehensive Benutzerliste
|
||||||
AccessRightsSaved: Berechtigungsänderungen wurden gespeichert.
|
AccessRightsSaved: Berechtigungsänderungen wurden gespeichert.
|
||||||
|
|
||||||
|
LecturersForN n@Int: #{pluralDE n "Dozent" "Dozenten"}
|
||||||
|
|
||||||
Date: Datum
|
Date: Datum
|
||||||
DateTimeFormat: Datums- und Uhrzeitformat
|
DateTimeFormat: Datums- und Uhrzeitformat
|
||||||
DateFormat: Datumsformat
|
DateFormat: Datumsformat
|
||||||
@ -642,7 +688,7 @@ StudyFeatureAge: Fachsemester
|
|||||||
StudyFeatureDegree: Abschluss
|
StudyFeatureDegree: Abschluss
|
||||||
FieldPrimary: Hauptfach
|
FieldPrimary: Hauptfach
|
||||||
FieldSecondary: Nebenfach
|
FieldSecondary: Nebenfach
|
||||||
NoPrimaryStudyField: (kein Hauptfach registriert)
|
NoStudyField: Kein Studienfach
|
||||||
StudyFeatureType:
|
StudyFeatureType:
|
||||||
StudyFeatureValid: Aktiv
|
StudyFeatureValid: Aktiv
|
||||||
StudyFeatureUpdate: Abgeglichen
|
StudyFeatureUpdate: Abgeglichen
|
||||||
@ -770,6 +816,9 @@ SheetGroupMaxGroupsize: Maximale Gruppengröße
|
|||||||
SheetFiles: Übungsblatt-Dateien
|
SheetFiles: Übungsblatt-Dateien
|
||||||
SheetFileTypeHeader: Zugehörigkeit
|
SheetFileTypeHeader: Zugehörigkeit
|
||||||
|
|
||||||
|
SheetArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}
|
||||||
|
SheetTypeArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName renderedSft@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-#{foldCase renderedSft}
|
||||||
|
|
||||||
NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert
|
NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert
|
||||||
NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert
|
NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert
|
||||||
NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen
|
NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen
|
||||||
@ -779,11 +828,11 @@ NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugetei
|
|||||||
NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Übungsblätter konnten einem Korrektor zugeteilt werden
|
NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Übungsblätter konnten einem Korrektor zugeteilt werden
|
||||||
NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert
|
NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert
|
||||||
NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert
|
NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert
|
||||||
NotificationTriggerExamResult: Ich kann ein neues Klausurergebnis einsehen
|
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
|
||||||
|
|
||||||
NotificationTriggerKindAll: Für alle Benutzer
|
NotificationTriggerKindAll: Für alle Benutzer
|
||||||
NotificationTriggerKindCourseParticipant: Für Kursteilnehmer
|
NotificationTriggerKindCourseParticipant: Für Kursteilnehmer
|
||||||
NotificationTriggerKindExamParticipant: Für Klausurteilnehmer
|
NotificationTriggerKindExamParticipant: Für Prüfungsteilnehmer
|
||||||
NotificationTriggerKindCorrector: Für Korrektoren
|
NotificationTriggerKindCorrector: Für Korrektoren
|
||||||
NotificationTriggerKindLecturer: Für Dozenten
|
NotificationTriggerKindLecturer: Für Dozenten
|
||||||
NotificationTriggerKindAdmin: Für Administratoren
|
NotificationTriggerKindAdmin: Für Administratoren
|
||||||
@ -945,11 +994,11 @@ MenuAuthPreds: Authorisierungseinstellungen
|
|||||||
MenuTutorialDelete: Tutorium löschen
|
MenuTutorialDelete: Tutorium löschen
|
||||||
MenuTutorialEdit: Tutorium editieren
|
MenuTutorialEdit: Tutorium editieren
|
||||||
MenuTutorialComm: Mitteilung an Teilnehmer
|
MenuTutorialComm: Mitteilung an Teilnehmer
|
||||||
MenuExamList: Klausuren
|
MenuExamList: Prüfungen
|
||||||
MenuExamNew: Neue Klausur anlegen
|
MenuExamNew: Neue Prüfung anlegen
|
||||||
MenuExamEdit: Bearbeiten
|
MenuExamEdit: Bearbeiten
|
||||||
MenuExamUsers: Teilnehmer
|
MenuExamUsers: Teilnehmer
|
||||||
MenuExamAddMembers: Klausurteilnehmer hinzufügen
|
MenuExamAddMembers: Prüfungsteilnehmer hinzufügen
|
||||||
MenuLecturerInvite: Dozenten hinzufügen
|
MenuLecturerInvite: Dozenten hinzufügen
|
||||||
|
|
||||||
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. 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.
|
||||||
@ -968,7 +1017,8 @@ AuthTagTime: Zeitliche Einschränkungen sind erfüllt
|
|||||||
AuthTagAllocationTime: Zeitliche Einschränkungen durch Zentralanmeldung sind erfüllt
|
AuthTagAllocationTime: Zeitliche Einschränkungen durch Zentralanmeldung sind erfüllt
|
||||||
AuthTagCourseRegistered: Nutzer ist Kursteilnehmer
|
AuthTagCourseRegistered: Nutzer ist Kursteilnehmer
|
||||||
AuthTagTutorialRegistered: Nutzer ist Tutoriumsteilnehmer
|
AuthTagTutorialRegistered: Nutzer ist Tutoriumsteilnehmer
|
||||||
AuthTagExamRegistered: Nutzer ist Klausurteilnehmer
|
AuthTagExamRegistered: Nutzer ist Prüfungsteilnehmer
|
||||||
|
AuthTagExamResult: Nutzer hat Prüfungsergebnisse
|
||||||
AuthTagParticipant: Nutzer ist mit Kurs assoziiert
|
AuthTagParticipant: Nutzer ist mit Kurs assoziiert
|
||||||
AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
|
AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
|
||||||
AuthTagCapacity: Kapazität ist ausreichend
|
AuthTagCapacity: Kapazität ist ausreichend
|
||||||
@ -1047,12 +1097,12 @@ TutorInviteExplanation: Sie wurden eingeladen, Tutor zu sein.
|
|||||||
ExamCorrectorInvitationAccepted examn@ExamName: Sie wurden als Korrektor für #{examn} eingetragen
|
ExamCorrectorInvitationAccepted examn@ExamName: Sie wurden als Korrektor für #{examn} eingetragen
|
||||||
ExamCorrectorInvitationDeclined examn@ExamName: Sie haben die Einladung, Korrektor für #{examn} zu werden, abgelehnt
|
ExamCorrectorInvitationDeclined examn@ExamName: Sie haben die Einladung, Korrektor für #{examn} zu werden, abgelehnt
|
||||||
ExamCorrectorInviteHeading examn@ExamName: Einladung zum Korrektor für #{examn}
|
ExamCorrectorInviteHeading examn@ExamName: Einladung zum Korrektor für #{examn}
|
||||||
ExamCorrectorInviteExplanation: Sie wurden eingeladen, Klausur-Korrektor zu sein.
|
ExamCorrectorInviteExplanation: Sie wurden eingeladen, Prüfungs-Korrektor zu sein.
|
||||||
|
|
||||||
ExamRegistrationInvitationAccepted examn@ExamName: Sie wurden als Teilnehmer für #{examn} eingetragen
|
ExamRegistrationInvitationAccepted examn@ExamName: Sie wurden als Teilnehmer für #{examn} eingetragen
|
||||||
ExamRegistrationInvitationDeclined examn@ExamName: Sie haben die Einladung, Teilnehmer für #{examn} zu werden, abgelehnt
|
ExamRegistrationInvitationDeclined examn@ExamName: Sie haben die Einladung, Teilnehmer für #{examn} zu werden, abgelehnt
|
||||||
ExamRegistrationInviteHeading examn@ExamName: Einladung zum Teilnehmer für #{examn}
|
ExamRegistrationInviteHeading examn@ExamName: Einladung zum Teilnehmer für #{examn}
|
||||||
ExamRegistrationInviteExplanation: Sie wurden eingeladen, Klausurteilnehmer zu sein.
|
ExamRegistrationInviteExplanation: Sie wurden eingeladen, Prüfungsteilnehmer zu sein.
|
||||||
|
|
||||||
SubmissionUserInvitationAccepted shn@SheetName: Sie wurden als Mitabgebende(r) für eine Abgabe zu #{shn} eingetragen
|
SubmissionUserInvitationAccepted shn@SheetName: Sie wurden als Mitabgebende(r) für eine Abgabe zu #{shn} eingetragen
|
||||||
SubmissionUserInvitationDeclined shn@SheetName: Sie haben die Einladung, Mitabgebende(r) für #{shn} zu werden, abgelehnt
|
SubmissionUserInvitationDeclined shn@SheetName: Sie haben die Einladung, Mitabgebende(r) für #{shn} zu werden, abgelehnt
|
||||||
@ -1102,7 +1152,7 @@ TutorialsHeading: Tutorien
|
|||||||
TutorialEdit: Bearbeiten
|
TutorialEdit: Bearbeiten
|
||||||
TutorialDelete: Löschen
|
TutorialDelete: Löschen
|
||||||
|
|
||||||
CourseExams: Klausuren
|
CourseExams: Prüfungen
|
||||||
CourseTutorials: Übungen
|
CourseTutorials: Übungen
|
||||||
|
|
||||||
ParticipantsN n@Int: #{n} Teilnehmer
|
ParticipantsN n@Int: #{n} Teilnehmer
|
||||||
@ -1146,26 +1196,26 @@ HealthActiveJobExecutors: Anteil der job-workers, die neue Befehle annehmen
|
|||||||
CourseParticipants n@Int: Derzeit #{n} angemeldete Kursteilnehmer
|
CourseParticipants n@Int: Derzeit #{n} angemeldete Kursteilnehmer
|
||||||
CourseParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
CourseParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
||||||
CourseParticipantsAlreadyRegistered n@Int: #{n} Teilnehmer #{pluralDE n "ist" "sind"} bereits angemeldet
|
CourseParticipantsAlreadyRegistered n@Int: #{n} Teilnehmer #{pluralDE n "ist" "sind"} bereits angemeldet
|
||||||
CourseParticipantsRegisteredWithoutField n@Int: #{n} Teilnehmer #{pluralDE n "wurde ohne assoziiertes Hauptfach" "wurden ohne assoziierte Hauptfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
CourseParticipantsRegisteredWithoutField n@Int: #{n} Teilnehmer #{pluralDE n "wurde ohne assoziiertes Studienfach" "wurden ohne assoziierte Studienfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
||||||
CourseParticipantsRegistered n@Int: #{n} Teilnehmer erfolgreich angemeldet
|
CourseParticipantsRegistered n@Int: #{n} Teilnehmer erfolgreich angemeldet
|
||||||
CourseParticipantsRegisterHeading: Kursteilnehmer hinzufügen
|
CourseParticipantsRegisterHeading: Kursteilnehmer hinzufügen
|
||||||
|
|
||||||
ExamRegistrationAndCourseParticipantsRegistered n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} sowohl zum Kurs, als auch zur Klausur angemeldet
|
ExamRegistrationAndCourseParticipantsRegistered n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} sowohl zum Kurs, als auch zur Prüfung angemeldet
|
||||||
ExamRegistrationNotRegisteredWithoutCourse n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} nicht zur Klausur angemeldet, da #{pluralDE n "er" "sie"} nicht zum Kurs angemeldet #{pluralDE n "ist" "sind"}
|
ExamRegistrationNotRegisteredWithoutCourse n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} nicht zur Prüfung angemeldet, da #{pluralDE n "er" "sie"} nicht zum Kurs angemeldet #{pluralDE n "ist" "sind"}
|
||||||
ExamRegistrationRegisteredWithoutField n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} sowohl zur Klausur, als auch #{pluralDE n "ohne assoziiertes Hauptfach" "ohne assoziierte Hauptfächer"} zum Kurs angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
ExamRegistrationRegisteredWithoutField n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} sowohl zur Prüfung, als auch #{pluralDE n "ohne assoziiertes Studienfach" "ohne assoziierte Studienfächer"} zum Kurs angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
||||||
ExamRegistrationParticipantsRegistered n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} zur Klausur angemeldet
|
ExamRegistrationParticipantsRegistered n@Int: #{n} Teilnehmer #{pluralDE n "wurde" "wurden"} zur Prüfung angemeldet
|
||||||
ExamRegistrationInviteDeadline: Einladung nur gültig bis
|
ExamRegistrationInviteDeadline: Einladung nur gültig bis
|
||||||
ExamRegistrationEnlistDirectly: Bekannte Nutzer sofort als Teilnehmer eintragen
|
ExamRegistrationEnlistDirectly: Bekannte Nutzer sofort als Teilnehmer eintragen
|
||||||
ExamRegistrationEnlistDirectlyTip: Sollen, wenn manche der E-Mail Addressen bereits in Uni2work mit Nutzern assoziiert sind, jene Nutzer direkt zur Klausur hinzugefügt werden? Ansonsten werden Einladung an alle E-Mail Addressen (nicht nur unbekannte) versandt, die die Nutzer zunächst akzeptieren müssen um Klausurteilnehmer zu werden.
|
ExamRegistrationEnlistDirectlyTip: Sollen, wenn manche der E-Mail Addressen bereits in Uni2work mit Nutzern assoziiert sind, jene Nutzer direkt zur Prüfung hinzugefügt werden? Ansonsten werden Einladung an alle E-Mail Addressen (nicht nur unbekannte) versandt, die die Nutzer zunächst akzeptieren müssen um Prüfungsteilnehmer zu werden.
|
||||||
ExamRegistrationRegisterCourse: Nutzer auch zum Kurs anmelden
|
ExamRegistrationRegisterCourse: Nutzer auch zum Kurs anmelden
|
||||||
ExamRegistrationRegisterCourseTip: Nutzer, die keine Kursteilnehmer sind, werden sonst nicht zur Klausur angemeldet.
|
ExamRegistrationRegisterCourseTip: Nutzer, die keine Kursteilnehmer sind, werden sonst nicht zur Prüfung angemeldet.
|
||||||
ExamRegistrationInviteField: Einzuladende EMail Addressen
|
ExamRegistrationInviteField: Einzuladende EMail Addressen
|
||||||
ExamParticipantsRegisterHeading: Klausurteilnehmer hinzufügen
|
ExamParticipantsRegisterHeading: Prüfungsteilnehmer hinzufügen
|
||||||
ExamParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
ExamParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
||||||
|
|
||||||
ExamName: Name
|
ExamName: Name
|
||||||
ExamTime: Termin
|
ExamTime: Termin
|
||||||
ExamsHeading: Klausuren
|
ExamsHeading: Prüfungen
|
||||||
ExamNameTip: Muss innerhalb der Veranstaltung eindeutig sein
|
ExamNameTip: Muss innerhalb der Veranstaltung eindeutig sein
|
||||||
ExamStart: Beginn
|
ExamStart: Beginn
|
||||||
ExamEnd: Ende
|
ExamEnd: Ende
|
||||||
@ -1173,7 +1223,7 @@ ExamDescription: Beschreibung
|
|||||||
ExamVisibleFrom: Sichtbar ab
|
ExamVisibleFrom: Sichtbar ab
|
||||||
ExamVisibleFromTip: Ohne Datum nie sichtbar und keine Anmeldung möglich
|
ExamVisibleFromTip: Ohne Datum nie sichtbar und keine Anmeldung möglich
|
||||||
ExamRegisterFrom: Anmeldung ab
|
ExamRegisterFrom: Anmeldung ab
|
||||||
ExamRegisterFromTip: Zeitpunkt ab dem sich Kursteilnehmer selbständig zur Klausur anmelden können; ohne Datum ist keine Anmeldung möglich
|
ExamRegisterFromTip: Zeitpunkt ab dem sich Kursteilnehmer selbständig zur Prüfung anmelden können; ohne Datum ist keine Anmeldung möglich
|
||||||
ExamRegisterTo: Anmeldung bis
|
ExamRegisterTo: Anmeldung bis
|
||||||
ExamDeregisterUntil: Abmeldung bis
|
ExamDeregisterUntil: Abmeldung bis
|
||||||
ExamPublishOccurrenceAssignments: Termin- bzw. Raumzuteilung den Teilnehmern mitteilen um
|
ExamPublishOccurrenceAssignments: Termin- bzw. Raumzuteilung den Teilnehmern mitteilen um
|
||||||
@ -1181,7 +1231,7 @@ ExamPublishOccurrenceAssignmentsTip: Ab diesem Zeitpunkt Teilnehmer einsehen zu
|
|||||||
ExamPublishOccurrenceAssignmentsParticipant: Termin- bzw. Raumzuteilung einsehbar ab
|
ExamPublishOccurrenceAssignmentsParticipant: Termin- bzw. Raumzuteilung einsehbar ab
|
||||||
ExamFinished: Bewertung abgeschlossen ab
|
ExamFinished: Bewertung abgeschlossen ab
|
||||||
ExamFinishedParticipant: Bewertung vorrausichtlich abgeschlossen
|
ExamFinishedParticipant: Bewertung vorrausichtlich abgeschlossen
|
||||||
ExamFinishedTip: Zeitpunkt zu dem Klausurergebnisse den Teilnehmern gemeldet werden
|
ExamFinishedTip: Zeitpunkt zu dem Prüfungergebnisse den Teilnehmern gemeldet werden
|
||||||
ExamClosed: Noten stehen fest ab
|
ExamClosed: Noten stehen fest ab
|
||||||
ExamClosedTip: Zeitpunkt ab dem keine Änderungen an den Ergebnissen zulässig sind; Prüfungsämter bekommen Einsicht
|
ExamClosedTip: Zeitpunkt ab dem keine Änderungen an den Ergebnissen zulässig sind; Prüfungsämter bekommen Einsicht
|
||||||
ExamShowGrades: Noten anzeigen
|
ExamShowGrades: Noten anzeigen
|
||||||
@ -1197,15 +1247,15 @@ Points: Punkte
|
|||||||
PointsMustBeNonNegative: Punktegrenzen dürfen nicht negativ sein
|
PointsMustBeNonNegative: Punktegrenzen dürfen nicht negativ sein
|
||||||
PointsMustBeMonotonic: Punktegrenzen müssen aufsteigend sein
|
PointsMustBeMonotonic: Punktegrenzen müssen aufsteigend sein
|
||||||
GradingFrom: Ab
|
GradingFrom: Ab
|
||||||
ExamNew: Neue Klausur
|
ExamNew: Neue Prüfung
|
||||||
ExamBonusRule: Klausurbonus aus Übungsbetrieb
|
ExamBonusRule: Prüfungsbonus aus Übungsbetrieb
|
||||||
ExamNoBonus': Kein automatischer Bonus
|
ExamNoBonus': Kein automatischer Bonus
|
||||||
ExamBonusPoints': Umrechnung von Übungspunkten
|
ExamBonusPoints': Umrechnung von Übungspunkten
|
||||||
|
|
||||||
ExamEditHeading examn@ExamName: #{examn} bearbeiten
|
ExamEditHeading examn@ExamName: #{examn} bearbeiten
|
||||||
|
|
||||||
ExamBonusMaxPoints: Maximal erreichbare Klausur-Bonuspunkte
|
ExamBonusMaxPoints: Maximal erreichbare Prüfungs-Bonuspunkte
|
||||||
ExamBonusMaxPointsNonPositive: Maximaler Klausurbonus muss positiv und größer null sein
|
ExamBonusMaxPointsNonPositive: Maximaler Prüfungsbonus muss positiv und größer null sein
|
||||||
ExamBonusOnlyPassed: Bonus nur nach Bestehen anrechnen
|
ExamBonusOnlyPassed: Bonus nur nach Bestehen anrechnen
|
||||||
|
|
||||||
ExamOccurrenceRule: Automatische Termin- bzw. Raumzuteilung
|
ExamOccurrenceRule: Automatische Termin- bzw. Raumzuteilung
|
||||||
@ -1238,7 +1288,7 @@ ExamFormCorrection: Korrektur
|
|||||||
ExamFormParts: Teile
|
ExamFormParts: Teile
|
||||||
|
|
||||||
ExamCorrectors: Korrektoren
|
ExamCorrectors: Korrektoren
|
||||||
ExamCorrectorAlreadyAdded: Ein Korrektor mit dieser E-Mail ist bereits für diese Klausur eingetragen
|
ExamCorrectorAlreadyAdded: Ein Korrektor mit dieser E-Mail ist bereits für diese Prüfung eingetragen
|
||||||
|
|
||||||
ExamParts: Teilaufgaben
|
ExamParts: Teilaufgaben
|
||||||
ExamPartWeightNegative: Gewicht aller Teilaufgaben muss größer oder gleich Null sein
|
ExamPartWeightNegative: Gewicht aller Teilaufgaben muss größer oder gleich Null sein
|
||||||
@ -1248,30 +1298,30 @@ ExamPartMaxPoints: Maximalpunktzahl
|
|||||||
ExamPartWeight: Gewichtung
|
ExamPartWeight: Gewichtung
|
||||||
ExamPartResultPoints: Erreichte Punkte
|
ExamPartResultPoints: Erreichte Punkte
|
||||||
|
|
||||||
ExamNameTaken exam@ExamName: Es existiert bereits eine Klausur mit Namen #{exam}
|
ExamNameTaken exam@ExamName: Es existiert bereits eine Prüfung mit Namen #{exam}
|
||||||
ExamCreated exam@ExamName: #{exam} erfolgreich angelegt
|
ExamCreated exam@ExamName: #{exam} erfolgreich angelegt
|
||||||
ExamEdited exam@ExamName: #{exam} erfolgreich bearbeitet
|
ExamEdited exam@ExamName: #{exam} erfolgreich bearbeitet
|
||||||
|
|
||||||
ExamNoShow: Nicht erschienen
|
ExamNoShow: Nicht erschienen
|
||||||
ExamVoided: Entwertet
|
ExamVoided: Entwertet
|
||||||
|
|
||||||
ExamBonusPoints possible@Points: Maximal #{showFixed True possible} Klausurpunkte
|
ExamBonusPoints possible@Points: Maximal #{showFixed True possible} Prüfungspunkte
|
||||||
ExamBonusPointsPassed possible@Points: Maximal #{showFixed True possible} Klausurpunkte, falls die Klausur auch ohne Bonus bereits bestanden ist
|
ExamBonusPointsPassed possible@Points: Maximal #{showFixed True possible} Prüfungspunkte, falls die Prüfung auch ohne Bonus bereits bestanden ist
|
||||||
|
|
||||||
ExamPassed: Bestanden
|
ExamPassed: Bestanden
|
||||||
ExamNotPassed: Nicht bestanden
|
ExamNotPassed: Nicht bestanden
|
||||||
ExamResult: Klausurergebnis
|
ExamResult: Prüfungsergebnis
|
||||||
|
|
||||||
ExamRegisteredSuccess exam@ExamName: Erfolgreich zur #{exam} angemeldet
|
ExamRegisteredSuccess exam@ExamName: Erfolgreich zur Prüfung #{exam} angemeldet
|
||||||
ExamDeregisteredSuccess exam@ExamName: Erfolgreich von der #{exam} abgemeldet
|
ExamDeregisteredSuccess exam@ExamName: Erfolgreich von der Prüfung #{exam} abgemeldet
|
||||||
ExamRegistered: Angemeldet
|
ExamRegistered: Zur Prüfung angemeldet
|
||||||
ExamNotRegistered: Nicht angemeldet
|
ExamNotRegistered: Nicht zur Prüfung angemeldet
|
||||||
ExamRegistration: Anmeldung
|
ExamRegistration: Prüfungsanmeldung
|
||||||
|
|
||||||
ExamRegisterToMustBeAfterRegisterFrom: "Anmeldung ab" muss vor "Anmeldung bis" liegen
|
ExamRegisterToMustBeAfterRegisterFrom: "Anmeldung ab" muss vor "Anmeldung bis" liegen
|
||||||
ExamDeregisterUntilMustBeAfterRegisterFrom: "Abmeldung bis" muss nach "Anmeldung bis" liegen
|
ExamDeregisterUntilMustBeAfterRegisterFrom: "Abmeldung bis" muss nach "Anmeldung bis" liegen
|
||||||
ExamStartMustBeAfterPublishOccurrenceAssignments: Start muss nach Veröffentlichung der Termin- bzw. Raumzuordnung liegen
|
ExamStartMustBeAfterPublishOccurrenceAssignments: Start muss nach Veröffentlichung der Termin- bzw. Raumzuordnung liegen
|
||||||
ExamEndMustBeAfterStart: Beginn der Klausur muss vor ihrem Ende liegen
|
ExamEndMustBeAfterStart: Beginn der Prüfung muss vor ihrem Ende liegen
|
||||||
ExamFinishedMustBeAfterEnd: "Bewertung abgeschlossen ab" muss nach Ende liegen
|
ExamFinishedMustBeAfterEnd: "Bewertung abgeschlossen ab" muss nach Ende liegen
|
||||||
ExamFinishedMustBeAfterStart: "Bewertung abgeschlossen ab" muss nach Start liegen
|
ExamFinishedMustBeAfterStart: "Bewertung abgeschlossen ab" muss nach Start liegen
|
||||||
ExamClosedMustBeAfterFinished: "Noten stehen fest ab" muss nach "Bewertung abgeschlossen ab" liegen
|
ExamClosedMustBeAfterFinished: "Noten stehen fest ab" muss nach "Bewertung abgeschlossen ab" liegen
|
||||||
@ -1279,18 +1329,19 @@ ExamClosedMustBeAfterStart: "Noten stehen fest ab" muss nach Start liegen
|
|||||||
ExamClosedMustBeAfterEnd: "Noten stehen fest ab" muss nach Ende liegen
|
ExamClosedMustBeAfterEnd: "Noten stehen fest ab" muss nach Ende liegen
|
||||||
|
|
||||||
ExamOccurrenceEndMustBeAfterStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss vor seinem Ende liegen
|
ExamOccurrenceEndMustBeAfterStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss vor seinem Ende liegen
|
||||||
ExamOccurrenceStartMustBeAfterExamStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss nach Beginn der Klausur liegen
|
ExamOccurrenceStartMustBeAfterExamStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss nach Beginn der Prüfung liegen
|
||||||
ExamOccurrenceEndMustBeBeforeExamEnd eoName@ExamOccurrenceName: Ende des Termins #{eoName} muss vor Ende der Klausur liegen
|
ExamOccurrenceEndMustBeBeforeExamEnd eoName@ExamOccurrenceName: Ende des Termins #{eoName} liegt nach dem Ende der Prüfung
|
||||||
ExamOccurrenceDuplicate eoRoom@Text eoRange@Text: Raum #{eoRoom}, Termin #{eoRange} kommt mehrfach mit der selben Beschreibung vor
|
ExamOccurrenceDuplicate eoRoom@Text eoRange@Text: Raum #{eoRoom}, Termin #{eoRange} kommt mehrfach mit der selben Beschreibung vor
|
||||||
ExamOccurrenceDuplicateName eoName@ExamOccurrenceName: Interne Terminbezeichnung #{eoName} kommt mehrfach vor
|
ExamOccurrenceDuplicateName eoName@ExamOccurrenceName: Interne Terminbezeichnung #{eoName} kommt mehrfach vor
|
||||||
|
|
||||||
VersionHistory: Versionsgeschichte
|
VersionHistory: Versionsgeschichte
|
||||||
KnownBugs: Bekannte Bugs
|
KnownBugs: Bekannte Bugs
|
||||||
|
ImplementationDetails: Implementierung
|
||||||
|
|
||||||
ExamUsersHeading: Klausurteilnehmer
|
ExamUsersHeading: Prüfungsteilnehmer
|
||||||
ExamUserDeregister: Teilnehmer von Klausur abmelden
|
ExamUserDeregister: Teilnehmer von Prüfung abmelden
|
||||||
ExamUserAssignOccurrence: Termin/Raum zuweisen
|
ExamUserAssignOccurrence: Termin/Raum zuweisen
|
||||||
ExamUsersDeregistered count@Int64: #{show count} Teilnehmer abgemeldet
|
ExamUsersDeregistered count@Int64: #{show count} Teilnehmer von der Prüfung abgemeldet
|
||||||
ExamUsersOccurrenceUpdated count@Int64: Termin/Raum für #{show count} Teilnehmer gesetzt
|
ExamUsersOccurrenceUpdated count@Int64: Termin/Raum für #{show count} Teilnehmer gesetzt
|
||||||
|
|
||||||
CsvFile: CSV-Datei
|
CsvFile: CSV-Datei
|
||||||
@ -1316,15 +1367,15 @@ CsvColumnExamUserSurname: Nachname(n) des Teilnehmers
|
|||||||
CsvColumnExamUserFirstName: Vorname(n) des Teilnehmers
|
CsvColumnExamUserFirstName: Vorname(n) des Teilnehmers
|
||||||
CsvColumnExamUserName: Voller Name des Teilnehmers (gewöhnlicherweise inkl. Vor- und Nachname(n))
|
CsvColumnExamUserName: Voller Name des Teilnehmers (gewöhnlicherweise inkl. Vor- und Nachname(n))
|
||||||
CsvColumnExamUserMatriculation: Matrikelnummer des Teilnehmers
|
CsvColumnExamUserMatriculation: Matrikelnummer des Teilnehmers
|
||||||
CsvColumnExamUserField: Hauptfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
|
CsvColumnExamUserField: Studienfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
|
||||||
CsvColumnExamUserDegree: Abschluss, den der Teilnehmer im assoziierten Hauptfach anstrebt
|
CsvColumnExamUserDegree: Abschluss, den der Teilnehmer im assoziierten Studienfach anstrebt
|
||||||
CsvColumnExamUserSemester: Fachsemester des Teilnehmers im assoziierten Hauptfach
|
CsvColumnExamUserSemester: Fachsemester des Teilnehmers im assoziierten Studienfach
|
||||||
CsvColumnExamUserOccurrence: Prüfungstermin/-Raum, zu dem der Teilnehmer angemeldet ist
|
CsvColumnExamUserOccurrence: Prüfungstermin/-Raum, zu dem der Teilnehmer angemeldet ist
|
||||||
CsvColumnExamUserExercisePoints: Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb erreicht hat
|
CsvColumnExamUserExercisePoints: Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb erreicht hat
|
||||||
CsvColumnExamUserExercisePointsMax: Maximale Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb bis zu seinem Klausurtermin erreichen hätte können
|
CsvColumnExamUserExercisePointsMax: Maximale Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb bis zu seinem Prüfungstermin erreichen hätte können
|
||||||
CsvColumnExamUserExercisePasses: Anzahl von Übungsblättern, die der Teilnehmer bestanden hat
|
CsvColumnExamUserExercisePasses: Anzahl von Übungsblättern, die der Teilnehmer bestanden hat
|
||||||
CsvColumnExamUserExercisePassesMax: Maximale Anzahl von Übungsblättern, die der Teilnehmer bis zu seinem Klausurtermin bestehen hätte können
|
CsvColumnExamUserExercisePassesMax: Maximale Anzahl von Übungsblättern, die der Teilnehmer bis zu seinem Prüfungstermin bestehen hätte können
|
||||||
CsvColumnExamUserResult: Erreichte Klausurleistung; "passed", "failed", "no-show", "voided", oder eine Note ("1.0", "1.3", "1.7", ..., "4.0", "5.0")
|
CsvColumnExamUserResult: Erreichte Prüfungsleistung; "passed", "failed", "no-show", "voided", oder eine Note ("1.0", "1.3", "1.7", ..., "4.0", "5.0")
|
||||||
CsvColumnExamUserCourseNote: Notizen zum Teilnehmer
|
CsvColumnExamUserCourseNote: Notizen zum Teilnehmer
|
||||||
|
|
||||||
Action: Aktion
|
Action: Aktion
|
||||||
@ -1334,18 +1385,18 @@ DBCsvDuplicateKeyTip: Entfernen Sie eine der unten aufgeführten Zeilen aus Ihre
|
|||||||
DBCsvKeyException: Für eine Zeile der CSV-Dateien konnte nicht festgestellt werden, ob sie zu einem bestehenden internen Datensatz korrespondieren.
|
DBCsvKeyException: Für eine Zeile der CSV-Dateien konnte nicht festgestellt werden, ob sie zu einem bestehenden internen Datensatz korrespondieren.
|
||||||
DBCsvException: Bei der Berechnung der auszuführenden Aktionen für einen Datensatz ist ein Fehler aufgetreten.
|
DBCsvException: Bei der Berechnung der auszuführenden Aktionen für einen Datensatz ist ein Fehler aufgetreten.
|
||||||
|
|
||||||
ExamUserCsvCourseRegister: Benutzer zum Kurs und zur Klausur anmelden
|
ExamUserCsvCourseRegister: Benutzer zum Kurs und zur Prüfung anmelden
|
||||||
ExamUserCsvRegister: Kursteilnehmer zur Klausur anmelden
|
ExamUserCsvRegister: Kursteilnehmer zur Prüfung anmelden
|
||||||
ExamUserCsvAssignOccurrence: Teilnehmern einen anderen Termin/Raum zuweisen
|
ExamUserCsvAssignOccurrence: Teilnehmern einen anderen Termin/Raum zuweisen
|
||||||
ExamUserCsvDeregister: Teilnehmer von der Klausur abmelden
|
ExamUserCsvDeregister: Teilnehmer von der Prüfung abmelden
|
||||||
ExamUserCsvSetCourseField: Kurs-assoziiertes Hauptfach ändern
|
ExamUserCsvSetCourseField: Kurs-assoziiertes Studienfach ändern
|
||||||
ExamUserCsvSetResult: Ergebnis eintragen
|
ExamUserCsvSetResult: Ergebnis eintragen
|
||||||
ExamUserCsvSetCourseNote: Teilnehmer-Notizen anpassen
|
ExamUserCsvSetCourseNote: Teilnehmer-Notizen anpassen
|
||||||
|
|
||||||
ExamUserCsvCourseNoteDeleted: Notiz wird gelöscht
|
ExamUserCsvCourseNoteDeleted: Notiz wird gelöscht
|
||||||
|
|
||||||
ExamUserCsvExceptionNoMatchingUser: Kursteilnehmer konnte nicht eindeutig identifiziert werden
|
ExamUserCsvExceptionNoMatchingUser: Kursteilnehmer konnte nicht eindeutig identifiziert werden
|
||||||
ExamUserCsvExceptionNoMatchingStudyFeatures: Das angegebene Studienfach konnte keinem Hauptfach des Kursteilnehmers zugeordnet werden
|
ExamUserCsvExceptionNoMatchingStudyFeatures: Das angegebene Studienfach konnte keinem Studienfach des Kursteilnehmers zugeordnet werden
|
||||||
ExamUserCsvExceptionNoMatchingOccurrence: Raum/Termin konnte nicht eindeutig identifiziert werden
|
ExamUserCsvExceptionNoMatchingOccurrence: Raum/Termin konnte nicht eindeutig identifiziert werden
|
||||||
|
|
||||||
TableHeadingFilter: Filter
|
TableHeadingFilter: Filter
|
||||||
@ -1355,7 +1406,7 @@ TableHeadingCsvExport: CSV-Export
|
|||||||
ExamResultAttended: Teilgenommen
|
ExamResultAttended: Teilgenommen
|
||||||
ExamResultNoShow: Nicht erschienen
|
ExamResultNoShow: Nicht erschienen
|
||||||
ExamResultVoided: Entwertet
|
ExamResultVoided: Entwertet
|
||||||
ExamResultNone: Kein Klausurergebnis
|
ExamResultNone: Kein Prüfungsergebnis
|
||||||
|
|
||||||
BtnAuthLDAP: Auf Campus-Kennung umstellen
|
BtnAuthLDAP: Auf Campus-Kennung umstellen
|
||||||
BtnAuthPWHash: Auf Uni2work-Kennung umstellen
|
BtnAuthPWHash: Auf Uni2work-Kennung umstellen
|
||||||
@ -1363,8 +1414,8 @@ BtnPasswordReset: Passwort zurücksetzen
|
|||||||
|
|
||||||
AuthLDAPLookupFailed: Nutzer konnte aufgrund eines LDAP-Fehlers nicht nachgeschlagen werden
|
AuthLDAPLookupFailed: Nutzer konnte aufgrund eines LDAP-Fehlers nicht nachgeschlagen werden
|
||||||
AuthLDAPInvalidLookup: Bestehender Nutzer konnte nicht eindeutig einem LDAP-Eintrag zugeordnet werden
|
AuthLDAPInvalidLookup: Bestehender Nutzer konnte nicht eindeutig einem LDAP-Eintrag zugeordnet werden
|
||||||
AuthLDAPAlreadyConfigured: Nutzer meldet sich bereits per Campus-Kennung an
|
AuthLDAPAlreadyConfigured: Nutzer meldet sich bereits per Campus-Kennung in Uni2work an
|
||||||
AuthLDAPConfigured: Nutzer meldet sich nun per Campus-Kennung an
|
AuthLDAPConfigured: Nutzer meldet sich nun per Campus-Kennung in Uni2work an
|
||||||
|
|
||||||
AuthPWHashAlreadyConfigured: Nutzer meldet sich bereits per Uni2work-Kennung an
|
AuthPWHashAlreadyConfigured: Nutzer meldet sich bereits per Uni2work-Kennung an
|
||||||
AuthPWHashConfigured: Nutzer meldet sich nun per Uni2work-Kennung an
|
AuthPWHashConfigured: Nutzer meldet sich nun per Uni2work-Kennung an
|
||||||
|
|||||||
@ -31,38 +31,14 @@ AllocationCourse
|
|||||||
allocation AllocationId
|
allocation AllocationId
|
||||||
course CourseId
|
course CourseId
|
||||||
minCapacity Int -- if the course would get assigned fewer than this many applicants, restart the assignment process without the course
|
minCapacity Int -- if the course would get assigned fewer than this many applicants, restart the assignment process without the course
|
||||||
instructions Html Maybe -- instructions from the lecturer to applicants
|
|
||||||
applicationText Bool -- lecturer will read application texts supplied by users
|
|
||||||
applicationFiles UploadMode -- lecturer wants to receive course specific application files
|
|
||||||
ratingsVisible Bool -- lecturer wants applicants to receive feedback on their application (Grade & comment)
|
|
||||||
UniqueAllocationCourse course
|
UniqueAllocationCourse course
|
||||||
|
|
||||||
AllocationCourseFile
|
|
||||||
allocationCourse AllocationCourseId
|
|
||||||
file FileId
|
|
||||||
UniqueAllocationCourseFile allocationCourse file
|
|
||||||
|
|
||||||
AllocationUser
|
AllocationUser
|
||||||
allocation AllocationId
|
allocation AllocationId
|
||||||
user UserId
|
user UserId
|
||||||
totalCourses Natural -- number of total allocated courses for this user must be <= than this number
|
totalCourses Natural -- number of total allocated courses for this user must be <= than this number
|
||||||
UniqueAllocationUser allocation user
|
UniqueAllocationUser allocation user
|
||||||
|
|
||||||
AllocationApplication
|
|
||||||
allocationCourse AllocationCourseId
|
|
||||||
allocationUser AllocationUserId
|
|
||||||
text Text Maybe -- free text entered by user
|
|
||||||
priority Natural -- priority, higher number means higher priority
|
|
||||||
ratingVeto Bool
|
|
||||||
ratingPoints ExamGrade Maybe
|
|
||||||
ratingComment Text Maybe
|
|
||||||
UniqueAllocationApplication allocationCourse allocationUser
|
|
||||||
|
|
||||||
AllocationApplicationFile -- supplemental file for application by a user for a certain course
|
|
||||||
application AllocationApplicationId
|
|
||||||
file FileId
|
|
||||||
UniqueAllocationUserFile application file
|
|
||||||
|
|
||||||
AllocationDeregister -- self-inflicted user-deregistrations from an allocated course
|
AllocationDeregister -- self-inflicted user-deregistrations from an allocated course
|
||||||
user UserId
|
user UserId
|
||||||
allocation AllocationId Maybe
|
allocation AllocationId Maybe
|
||||||
|
|||||||
@ -17,9 +17,20 @@ Course -- Information about a single course; contained info is always visible
|
|||||||
deregisterUntil UTCTime Maybe -- unenrolement may be prohibited from a given date onwards
|
deregisterUntil UTCTime Maybe -- unenrolement may be prohibited from a given date onwards
|
||||||
registerSecret Text Maybe -- enrolement maybe protected by a simple common passphrase
|
registerSecret Text Maybe -- enrolement maybe protected by a simple common passphrase
|
||||||
materialFree Bool -- False: only enrolled users may see course materials not stored in this table
|
materialFree Bool -- False: only enrolled users may see course materials not stored in this table
|
||||||
|
applicationsRequired Bool
|
||||||
|
applicationsInstructions Html Maybe
|
||||||
|
applicationsText Bool
|
||||||
|
applicationsFiles UploadMode
|
||||||
|
applicationsRatingsVisible Bool
|
||||||
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
|
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
|
||||||
TermSchoolCourseName term school name -- name must be unique within school and semester
|
TermSchoolCourseName term school name -- name must be unique within school and semester
|
||||||
deriving Generic
|
deriving Generic
|
||||||
|
|
||||||
|
CourseAppInstructionFile
|
||||||
|
course CourseId
|
||||||
|
file FileId
|
||||||
|
UniqueCourseAppInstructionFile course file
|
||||||
|
|
||||||
CourseEdit -- who edited when a row in table "Course", kept indefinitely (might be replaced by generic Audit Table; like all ...-Edit tables)
|
CourseEdit -- who edited when a row in table "Course", kept indefinitely (might be replaced by generic Audit Table; like all ...-Edit tables)
|
||||||
user UserId
|
user UserId
|
||||||
time UTCTime
|
time UTCTime
|
||||||
@ -59,3 +70,18 @@ CourseUserNoteEdit -- who edited a participants course note when
|
|||||||
user UserId
|
user UserId
|
||||||
time UTCTime
|
time UTCTime
|
||||||
note CourseUserNoteId -- PROBLEM: deleted notes have no modification date any more
|
note CourseUserNoteId -- PROBLEM: deleted notes have no modification date any more
|
||||||
|
|
||||||
|
CourseApplication
|
||||||
|
course CourseId
|
||||||
|
user UserId
|
||||||
|
field StudyFeaturesId Maybe -- associated degree course, user-defined; required for communicating grades
|
||||||
|
text Text Maybe -- free text entered by user
|
||||||
|
ratingPoints ExamGrade Maybe
|
||||||
|
ratingComment Text Maybe
|
||||||
|
allocation AllocationId Maybe
|
||||||
|
allocationPriority Natural Maybe
|
||||||
|
time UTCTime default=now()
|
||||||
|
CourseApplicationFile
|
||||||
|
application CourseApplicationId
|
||||||
|
file FileId
|
||||||
|
UniqueApplicationFile application file
|
||||||
15677
package-lock.json
generated
15677
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "4.13.1",
|
"version": "5.0.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: uniworx
|
name: uniworx
|
||||||
version: 4.13.1
|
version: 5.0.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
# Due to a bug in GHC 8.0.1, we block its usage
|
# Due to a bug in GHC 8.0.1, we block its usage
|
||||||
|
|||||||
7
routes
7
routes
@ -86,7 +86,8 @@
|
|||||||
!/course/new CourseNewR GET POST !lecturer
|
!/course/new CourseNewR GET POST !lecturer
|
||||||
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
||||||
/ CShowR GET !free
|
/ CShowR GET !free
|
||||||
/register CRegisterR GET POST !timeANDcapacityANDallocation-time !lecturerANDallocation-time
|
/register CRegisterR GET POST !timeANDcapacityANDallocation-timeAND¬exam-result !lecturerANDallocation-time
|
||||||
|
/register-template CRegisterTemplateR GET !free
|
||||||
/edit CEditR GET POST
|
/edit CEditR GET POST
|
||||||
/lecturer-invite CLecInviteR GET POST
|
/lecturer-invite CLecInviteR GET POST
|
||||||
/delete CDeleteR GET POST !lecturerANDemptyANDallocation-time
|
/delete CDeleteR GET POST !lecturerANDemptyANDallocation-time
|
||||||
@ -152,7 +153,9 @@
|
|||||||
/users EUsersR GET POST
|
/users EUsersR GET POST
|
||||||
/users/new EAddUserR GET POST
|
/users/new EAddUserR GET POST
|
||||||
/users/invite EInviteR GET POST
|
/users/invite EInviteR GET POST
|
||||||
/register ERegisterR POST !timeANDcourse-registered !timeANDexam-registered
|
/register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result
|
||||||
|
/apps/#CryptoFileNameCourseApplication CourseApplicationR:
|
||||||
|
/files CAFilesR GET !self !lecturerANDtime
|
||||||
|
|
||||||
/subs CorrectionsR GET POST !corrector !lecturer
|
/subs CorrectionsR GET POST !corrector !lecturer
|
||||||
/subs/upload CorrectionsUploadR GET POST !corrector !lecturer
|
/subs/upload CorrectionsUploadR GET POST !corrector !lecturer
|
||||||
|
|||||||
@ -30,6 +30,24 @@ data Transaction
|
|||||||
{ transactionExam :: ExamId
|
{ transactionExam :: ExamId
|
||||||
, transactionUser :: UserId
|
, transactionUser :: UserId
|
||||||
}
|
}
|
||||||
|
| TransactionCourseParticipantEdit
|
||||||
|
{ transactionCourse :: CourseId
|
||||||
|
, transactionUser :: UserId
|
||||||
|
}
|
||||||
|
| TransactionCourseParticipantDeleted
|
||||||
|
{ transactionCourse :: CourseId
|
||||||
|
, transactionUser :: UserId
|
||||||
|
}
|
||||||
|
| TransactionCourseApplicationEdit
|
||||||
|
{ transactionCourse :: CourseId
|
||||||
|
, transactionUser :: UserId
|
||||||
|
, transactionCourseApplication :: CourseApplicationId
|
||||||
|
}
|
||||||
|
| TransactionCourseApplicationDeleted
|
||||||
|
{ transactionCourse :: CourseId
|
||||||
|
, transactionUser :: UserId
|
||||||
|
, transactionCourseApplication :: CourseApplicationId
|
||||||
|
}
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import Data.Aeson (ToJSON(..), ToJSONKey(..), ToJSONKeyFunction(..), FromJSON(..), FromJSONKey(..), FromJSONKeyFunction(..), Value(..), withText)
|
import Data.Aeson (ToJSON(..), ToJSONKey(..), ToJSONKeyFunction(..), FromJSON(..), FromJSONKey(..), FromJSONKeyFunction(..), Value(..), withText)
|
||||||
import Data.Aeson.Encoding (text)
|
import Data.Aeson.Encoding (text)
|
||||||
|
|
||||||
|
import Text.Blaze (ToMarkup(..))
|
||||||
|
|
||||||
|
|
||||||
instance {-# OVERLAPPING #-} MonadThrow m => MonadCrypto (ReaderT CryptoIDKey m) where
|
instance {-# OVERLAPPING #-} MonadThrow m => MonadCrypto (ReaderT CryptoIDKey m) where
|
||||||
type MonadCryptoKey (ReaderT CryptoIDKey m) = CryptoIDKey
|
type MonadCryptoKey (ReaderT CryptoIDKey m) = CryptoIDKey
|
||||||
@ -46,19 +48,23 @@ decCryptoIDs [ ''SubmissionId
|
|||||||
, ''ExamOccurrenceId
|
, ''ExamOccurrenceId
|
||||||
, ''ExamPartId
|
, ''ExamPartId
|
||||||
, ''AllocationId
|
, ''AllocationId
|
||||||
|
, ''CourseApplicationId
|
||||||
]
|
]
|
||||||
|
|
||||||
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => PathPiece (E.CryptoID namespace (CI FilePath)) where
|
-- CryptoIDNamespace (CI FilePath) SubmissionId ~ "Submission"
|
||||||
|
instance {-# OVERLAPS #-} PathPiece (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
fromPathPiece (Text.unpack -> piece) = do
|
fromPathPiece (Text.unpack -> piece) = do
|
||||||
piece' <- (stripPrefix `on` map CI.mk) "uwa" piece
|
piece' <- (stripPrefix `on` map CI.mk) "uwa" piece
|
||||||
return . CryptoID . CI.mk $ map CI.original piece'
|
return . CryptoID . CI.mk $ map CI.original piece'
|
||||||
toPathPiece = Text.pack . ("uwa" <>) . CI.foldedCase . ciphertext
|
toPathPiece = Text.pack . ("uwa" <>) . CI.foldedCase . ciphertext
|
||||||
|
|
||||||
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => ToJSON (E.CryptoID namespace (CI FilePath)) where
|
instance {-# OVERLAPS #-} ToJSON (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
toJSON = String . toPathPiece
|
toJSON = String . toPathPiece
|
||||||
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => ToJSONKey (E.CryptoID namespace (CI FilePath)) where
|
instance {-# OVERLAPS #-} ToJSONKey (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
toJSONKey = ToJSONKeyText toPathPiece (text . toPathPiece)
|
toJSONKey = ToJSONKeyText toPathPiece (text . toPathPiece)
|
||||||
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => FromJSON (E.CryptoID namespace (CI FilePath)) where
|
instance {-# OVERLAPS #-} FromJSON (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
parseJSON = withText "CryptoFileNameSubmission" $ maybe (fail "Could not parse CryptoFileNameSubmission") return . fromPathPiece
|
parseJSON = withText "CryptoFileNameSubmission" $ maybe (fail "Could not parse CryptoFileNameSubmission") return . fromPathPiece
|
||||||
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => FromJSONKey (E.CryptoID namespace (CI FilePath)) where
|
instance {-# OVERLAPS #-} FromJSONKey (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
fromJSONKey = FromJSONKeyTextParser $ maybe (fail "Could not parse CryptoFileNameSubmission") return . fromPathPiece
|
fromJSONKey = FromJSONKeyTextParser $ maybe (fail "Could not parse CryptoFileNameSubmission") return . fromPathPiece
|
||||||
|
instance {-# OVERLAPS #-} ToMarkup (E.CryptoID "Submission" (CI FilePath)) where
|
||||||
|
toMarkup = toMarkup . toPathPiece
|
||||||
|
|||||||
@ -10,5 +10,11 @@ import Text.Blaze (ToMarkup(..))
|
|||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
|
||||||
|
import Data.CaseInsensitive (CI)
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
instance {-# OVERLAPS #-} ToMarkup s => ToMarkup (CID.CryptoID c (CI s)) where
|
||||||
|
toMarkup = toMarkup . CI.foldedCase . CID.ciphertext
|
||||||
|
|
||||||
instance ToMarkup s => ToMarkup (CID.CryptoID c s) where
|
instance ToMarkup s => ToMarkup (CID.CryptoID c s) where
|
||||||
toMarkup = toMarkup . CID.ciphertext
|
toMarkup = toMarkup . CID.ciphertext
|
||||||
@ -54,6 +54,7 @@ import Data.Conduit (($$))
|
|||||||
import Data.Conduit.List (sourceList)
|
import Data.Conduit.List (sourceList)
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
import Control.Monad.Except (MonadError(..), ExceptT)
|
import Control.Monad.Except (MonadError(..), ExceptT)
|
||||||
import Control.Monad.Trans.Maybe (MaybeT(..))
|
import Control.Monad.Trans.Maybe (MaybeT(..))
|
||||||
@ -150,6 +151,7 @@ deriving instance Generic SubmissionR
|
|||||||
deriving instance Generic MaterialR
|
deriving instance Generic MaterialR
|
||||||
deriving instance Generic TutorialR
|
deriving instance Generic TutorialR
|
||||||
deriving instance Generic ExamR
|
deriving instance Generic ExamR
|
||||||
|
deriving instance Generic CourseApplicationR
|
||||||
deriving instance Generic (Route UniWorX)
|
deriving instance Generic (Route UniWorX)
|
||||||
|
|
||||||
-- | Convenient Type Synonyms:
|
-- | Convenient Type Synonyms:
|
||||||
@ -179,6 +181,10 @@ pattern CSubmissionR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Cr
|
|||||||
pattern CSubmissionR tid ssh csh shn cid ptn
|
pattern CSubmissionR tid ssh csh shn cid ptn
|
||||||
= CSheetR tid ssh csh shn (SubmissionR cid ptn)
|
= CSheetR tid ssh csh shn (SubmissionR cid ptn)
|
||||||
|
|
||||||
|
pattern CApplicationR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> CourseApplicationR -> Route UniWorX
|
||||||
|
pattern CApplicationR tid ssh csh appId ptn
|
||||||
|
= CourseR tid ssh csh (CourseApplicationR appId ptn)
|
||||||
|
|
||||||
|
|
||||||
pluralDE :: (Eq a, Num a)
|
pluralDE :: (Eq a, Num a)
|
||||||
=> a -- ^ Count
|
=> a -- ^ Count
|
||||||
@ -282,6 +288,7 @@ embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
|
|||||||
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
||||||
embedRenderMessage ''UniWorX ''StudyFieldType id
|
embedRenderMessage ''UniWorX ''StudyFieldType id
|
||||||
embedRenderMessage ''UniWorX ''SheetFileType id
|
embedRenderMessage ''UniWorX ''SheetFileType id
|
||||||
|
embedRenderMessage ''UniWorX ''SubmissionFileType id
|
||||||
embedRenderMessage ''UniWorX ''CorrectorState id
|
embedRenderMessage ''UniWorX ''CorrectorState id
|
||||||
embedRenderMessage ''UniWorX ''RatingException id
|
embedRenderMessage ''UniWorX ''RatingException id
|
||||||
embedRenderMessage ''UniWorX ''SubmissionSinkException ("SubmissionSinkException" <>)
|
embedRenderMessage ''UniWorX ''SubmissionSinkException ("SubmissionSinkException" <>)
|
||||||
@ -518,6 +525,11 @@ andAR _ _ reason@(Unauthorized _) = reason
|
|||||||
andAR _ Authorized other = other
|
andAR _ Authorized other = other
|
||||||
andAR _ AuthenticationRequired _ = AuthenticationRequired
|
andAR _ AuthenticationRequired _ = AuthenticationRequired
|
||||||
|
|
||||||
|
notAR :: RenderMessage UniWorX msg => MsgRenderer -> msg -> AuthResult -> AuthResult
|
||||||
|
notAR _ _ (Unauthorized _) = Authorized
|
||||||
|
notAR _ _ AuthenticationRequired = AuthenticationRequired
|
||||||
|
notAR mr msg Authorized = Unauthorized . render mr . MsgUnauthorizedNot $ render mr msg
|
||||||
|
|
||||||
trueAR, falseAR :: MsgRendererS UniWorX -> AuthResult
|
trueAR, falseAR :: MsgRendererS UniWorX -> AuthResult
|
||||||
trueAR = const Authorized
|
trueAR = const Authorized
|
||||||
falseAR = Unauthorized . ($ MsgUnauthorized) . render
|
falseAR = Unauthorized . ($ MsgUnauthorized) . render
|
||||||
@ -580,14 +592,13 @@ tagAccessPredicate AuthAdmin = APDB $ \mAuthId route _ -> case route of
|
|||||||
-- Courses: access only to school admins
|
-- Courses: access only to school admins
|
||||||
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
|
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` userAdmin) -> do
|
isAdmin <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` userAdmin) -> do
|
||||||
E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserAdminSchool
|
E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserAdminSchool
|
||||||
E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val authId
|
E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val authId
|
||||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedSchoolAdmin)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
-- other routes: access to any admin is granted here
|
-- other routes: access to any admin is granted here
|
||||||
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
|
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
|
||||||
@ -622,14 +633,13 @@ tagAccessPredicate AuthDevelopment = APHandler $ \_ r _ -> do
|
|||||||
tagAccessPredicate AuthLecturer = APDB $ \mAuthId route _ -> case route of
|
tagAccessPredicate AuthLecturer = APDB $ \mAuthId route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
|
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` lecturer) -> do
|
isLecturer <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` lecturer) -> do
|
||||||
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
|
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
|
||||||
E.where_ $ lecturer E.^. LecturerUser E.==. E.val authId
|
E.where_ $ lecturer E.^. LecturerUser E.==. E.val authId
|
||||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT isLecturer (unauthorizedI MsgUnauthorizedLecturer)
|
||||||
guardMExceptT (c>0) (unauthorizedI MsgUnauthorizedLecturer)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
-- lecturer for any school will do
|
-- lecturer for any school will do
|
||||||
_ -> $cachedHereBinary mAuthId . exceptT return return $ do
|
_ -> $cachedHereBinary mAuthId . exceptT return return $ do
|
||||||
@ -688,6 +698,22 @@ tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return
|
|||||||
guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedTutor)
|
guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedTutor)
|
||||||
return Authorized
|
return Authorized
|
||||||
tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
||||||
|
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
|
||||||
|
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||||
|
allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course
|
||||||
|
allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation
|
||||||
|
|
||||||
|
case allocation of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just Allocation{..} -> do
|
||||||
|
cTime <- liftIO getCurrentTime
|
||||||
|
guard $ NTop allocationStaffAllocationFrom <= NTop (Just cTime)
|
||||||
|
guard $ NTop (Just cTime) <= NTop allocationStaffAllocationTo
|
||||||
|
|
||||||
|
return Authorized
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CExamR tid ssh csh examn subRoute -> maybeT (unauthorizedI MsgUnauthorizedExamTime) $ do
|
CExamR tid ssh csh examn subRoute -> maybeT (unauthorizedI MsgUnauthorizedExamTime) $ do
|
||||||
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||||
Entity eId Exam{..} <- $cachedHereBinary (course, examn) . MaybeT . getBy $ UniqueExam course examn
|
Entity eId Exam{..} <- $cachedHereBinary (course, examn) . MaybeT . getBy $ UniqueExam course examn
|
||||||
@ -781,9 +807,20 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
|||||||
| not registered
|
| not registered
|
||||||
, maybe False (now >=) courseRegisterFrom -- Nothing => no registration allowed
|
, maybe False (now >=) courseRegisterFrom -- Nothing => no registration allowed
|
||||||
, maybe True (now <=) courseRegisterTo -> return Authorized
|
, maybe True (now <=) courseRegisterTo -> return Authorized
|
||||||
(Just (Entity _ Course{courseDeregisterUntil}))
|
(Just (Entity cid Course{courseDeregisterUntil}))
|
||||||
| registered
|
| registered
|
||||||
, maybe True (now <=) courseDeregisterUntil -> return Authorized
|
-> maybeT (unauthorizedI MsgUnauthorizedCourseTime) $ do
|
||||||
|
guard $ maybe True (now <=) courseDeregisterUntil
|
||||||
|
forM_ mAuthId $ \uid -> do
|
||||||
|
exams <- lift . E.select . E.from $ \exam -> do
|
||||||
|
E.where_ . E.exists . E.from $ \examRegistration ->
|
||||||
|
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. exam E.^. ExamId
|
||||||
|
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
|
||||||
|
E.where_ $ exam E.^. ExamCourse E.==. E.val cid
|
||||||
|
return $ exam E.^. ExamDeregisterUntil
|
||||||
|
forM_ exams $ \(E.Value deregUntil) ->
|
||||||
|
guard $ NTop (Just now) >= NTop deregUntil
|
||||||
|
return Authorized
|
||||||
_other -> unauthorizedI MsgUnauthorizedCourseTime
|
_other -> unauthorizedI MsgUnauthorizedCourseTime
|
||||||
|
|
||||||
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
|
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
|
||||||
@ -844,20 +881,19 @@ tagAccessPredicate AuthAllocationTime = APDB $ \mAuthId route _ -> case route of
|
|||||||
tagAccessPredicate AuthCourseRegistered = APDB $ \mAuthId route _ -> case route of
|
tagAccessPredicate AuthCourseRegistered = APDB $ \mAuthId route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.select . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
|
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
|
||||||
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
|
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
|
||||||
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val authId
|
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val authId
|
||||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedRegistered)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate AuthCourseRegistered r
|
r -> $unsupportedAuthPredicate AuthCourseRegistered r
|
||||||
tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case route of
|
tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case route of
|
||||||
CTutorialR tid ssh csh tutn _ -> exceptT return return $ do
|
CTutorialR tid ssh csh tutn _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh, tutn) . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
|
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh, tutn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
|
||||||
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
|
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
|
||||||
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
|
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
|
||||||
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
|
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
|
||||||
@ -865,26 +901,24 @@ tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case rout
|
|||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
E.&&. tutorial E.^. TutorialName E.==. E.val tutn
|
E.&&. tutorial E.^. TutorialName E.==. E.val tutn
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedRegistered)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
|
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
|
||||||
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
|
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
|
||||||
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
|
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
|
||||||
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
|
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
|
||||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedRegistered)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate AuthTutorialRegistered r
|
r -> $unsupportedAuthPredicate AuthTutorialRegistered r
|
||||||
tagAccessPredicate AuthExamRegistered = APDB $ \mAuthId route _ -> case route of
|
tagAccessPredicate AuthExamRegistered = APDB $ \mAuthId route _ -> case route of
|
||||||
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.select . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
||||||
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
||||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
||||||
@ -892,20 +926,47 @@ tagAccessPredicate AuthExamRegistered = APDB $ \mAuthId route _ -> case route of
|
|||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedRegistered)
|
|
||||||
return Authorized
|
return Authorized
|
||||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
[E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.select . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
||||||
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
||||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
||||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedRegistered)
|
return Authorized
|
||||||
|
r -> $unsupportedAuthPredicate AuthExamRegistered r
|
||||||
|
tagAccessPredicate AuthExamResult = APDB $ \mAuthId route _ -> case route of
|
||||||
|
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
||||||
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
|
hasResult <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` (examResult `E.FullOuterJoin` (examPartResult `E.InnerJoin` examPart))) -> do
|
||||||
|
E.on $ examPartResult E.?. ExamPartResultExamPart E.==. examPart E.?. ExamPartId
|
||||||
|
E.on $ examResult E.?. ExamResultExam E.==. examPart E.?. ExamPartExam
|
||||||
|
E.on $ E.just (exam E.^. ExamId) E.==. examResult E.?. ExamResultExam
|
||||||
|
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||||
|
E.where_ $ (examResult E.?. ExamResultUser E.==. E.just (E.val authId) E.||. examPartResult E.?. ExamPartResultUser E.==. E.just (E.val authId))
|
||||||
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
|
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||||
|
guardMExceptT hasResult (unauthorizedI MsgUnauthorizedExamResult)
|
||||||
|
return Authorized
|
||||||
|
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||||
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
|
hasResult <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` (examResult `E.FullOuterJoin` (examPartResult `E.InnerJoin` examPart))) -> do
|
||||||
|
E.on $ examPartResult E.?. ExamPartResultExamPart E.==. examPart E.?. ExamPartId
|
||||||
|
E.on $ examResult E.?. ExamResultExam E.==. examPart E.?. ExamPartExam
|
||||||
|
E.on $ E.just (exam E.^. ExamId) E.==. examResult E.?. ExamResultExam
|
||||||
|
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||||
|
E.where_ $ (examResult E.?. ExamResultUser E.==. E.just (E.val authId) E.||. examPartResult E.?. ExamPartResultUser E.==. E.just (E.val authId))
|
||||||
|
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||||
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
|
guardMExceptT hasResult (unauthorizedI MsgUnauthorizedExamResult)
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate AuthExamRegistered r
|
r -> $unsupportedAuthPredicate AuthExamRegistered r
|
||||||
tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
|
tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
|
||||||
@ -1004,10 +1065,9 @@ tagAccessPredicate AuthEmpty = APDB $ \_ route _ -> case route of
|
|||||||
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||||
assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ]
|
assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ]
|
||||||
assertM_ ((<= 0) :: Int -> Bool) . $cachedHereBinary cid . lift . fmap (E.unValue . unsafeHead) $ E.select . E.from $ \(sheet `E.InnerJoin` submission) -> do
|
assertM_ not . $cachedHereBinary cid . lift $ E.selectExists . E.from $ \(sheet `E.InnerJoin` submission) -> do
|
||||||
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
||||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
||||||
return E.countRows
|
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate AuthEmpty r
|
r -> $unsupportedAuthPredicate AuthEmpty r
|
||||||
tagAccessPredicate AuthMaterials = APDB $ \_ route _ -> case route of
|
tagAccessPredicate AuthMaterials = APDB $ \_ route _ -> case route of
|
||||||
@ -1044,19 +1104,24 @@ tagAccessPredicate AuthCorrectorSubmissions = APDB $ \_ route _ -> case route of
|
|||||||
guard submissionModeCorrector
|
guard submissionModeCorrector
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
|
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
|
||||||
tagAccessPredicate AuthSelf = APHandler $ \mAuthId route _ -> exceptT return return $ do
|
tagAccessPredicate AuthSelf = APDB $ \mAuthId route _ -> exceptT return return $ do
|
||||||
referencedUser <- case route of
|
referencedUser <- case route of
|
||||||
AdminUserR cID -> return cID
|
AdminUserR cID -> decrypt cID
|
||||||
AdminUserDeleteR cID -> return cID
|
AdminUserDeleteR cID -> decrypt cID
|
||||||
AdminHijackUserR cID -> return cID
|
AdminHijackUserR cID -> decrypt cID
|
||||||
UserNotificationR cID -> return cID
|
UserNotificationR cID -> decrypt cID
|
||||||
UserPasswordR cID -> return cID
|
UserPasswordR cID -> decrypt cID
|
||||||
CourseR _ _ _ (CUserR cID) -> return cID
|
CourseR _ _ _ (CUserR cID) -> decrypt cID
|
||||||
|
CApplicationR _ _ _ cID _ -> do
|
||||||
|
appId <- decrypt cID
|
||||||
|
application <- $cachedHereBinary appId . lift $ get appId
|
||||||
|
case application of
|
||||||
|
Nothing -> throwError =<< unauthorizedI MsgUnauthorizedSelf
|
||||||
|
Just CourseApplication{..} -> return courseApplicationUser
|
||||||
_other -> throwError =<< $unsupportedAuthPredicate AuthSelf route
|
_other -> throwError =<< $unsupportedAuthPredicate AuthSelf route
|
||||||
referencedUser' <- decrypt referencedUser
|
|
||||||
case mAuthId of
|
case mAuthId of
|
||||||
Just uid
|
Just uid
|
||||||
| uid == referencedUser' -> return Authorized
|
| uid == referencedUser -> return Authorized
|
||||||
Nothing -> return AuthenticationRequired
|
Nothing -> return AuthenticationRequired
|
||||||
_other -> unauthorizedI MsgUnauthorizedSelf
|
_other -> unauthorizedI MsgUnauthorizedSelf
|
||||||
tagAccessPredicate AuthIsLDAP = APDB $ \_ route _ -> exceptT return return $ do
|
tagAccessPredicate AuthIsLDAP = APDB $ \_ route _ -> exceptT return return $ do
|
||||||
@ -1159,10 +1224,7 @@ evalAuthTags AuthTagActive{..} (map (Set.toList . toNullable) . Set.toList . dnf
|
|||||||
|
|
||||||
evalAuthLiteral :: AuthLiteral -> WriterT (Set AuthTag) m AuthResult
|
evalAuthLiteral :: AuthLiteral -> WriterT (Set AuthTag) m AuthResult
|
||||||
evalAuthLiteral PLVariable{..} = evalAuthTag plVar
|
evalAuthLiteral PLVariable{..} = evalAuthTag plVar
|
||||||
evalAuthLiteral PLNegated{..} = evalAuthTag plVar >>= \case
|
evalAuthLiteral PLNegated{..} = notAR mr plVar <$> evalAuthTag plVar
|
||||||
Unauthorized _ -> return Authorized
|
|
||||||
AuthenticationRequired -> return AuthenticationRequired
|
|
||||||
Authorized -> unauthorizedI plVar
|
|
||||||
|
|
||||||
orAR', andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult
|
orAR', andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult
|
||||||
orAR' = shortCircuitM (is _Authorized) (orAR mr)
|
orAR' = shortCircuitM (is _Authorized) (orAR mr)
|
||||||
@ -1616,13 +1678,13 @@ instance YesodBreadcrumbs UniWorX where
|
|||||||
breadcrumb (CourseR tid ssh csh CTutorialListR) = return ("Tutorien", Just $ CourseR tid ssh csh CShowR)
|
breadcrumb (CourseR tid ssh csh CTutorialListR) = return ("Tutorien", Just $ CourseR tid ssh csh CShowR)
|
||||||
breadcrumb (CourseR tid ssh csh CTutorialNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CTutorialListR)
|
breadcrumb (CourseR tid ssh csh CTutorialNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CTutorialListR)
|
||||||
|
|
||||||
breadcrumb (CourseR tid ssh csh CExamListR) = return ("Klausuren", Just $ CourseR tid ssh csh CShowR)
|
breadcrumb (CourseR tid ssh csh CExamListR) = return ("Prüfungen", Just $ CourseR tid ssh csh CShowR)
|
||||||
breadcrumb (CourseR tid ssh csh CExamNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CExamListR)
|
breadcrumb (CourseR tid ssh csh CExamNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CExamListR)
|
||||||
|
|
||||||
breadcrumb (CExamR tid ssh csh examn EShowR) = return (original examn, Just $ CourseR tid ssh csh CExamListR)
|
breadcrumb (CExamR tid ssh csh examn EShowR) = return (original examn, Just $ CourseR tid ssh csh CExamListR)
|
||||||
breadcrumb (CExamR tid ssh csh examn EEditR) = return ("Bearbeiten", Just $ CExamR tid ssh csh examn EShowR)
|
breadcrumb (CExamR tid ssh csh examn EEditR) = return ("Bearbeiten", Just $ CExamR tid ssh csh examn EShowR)
|
||||||
breadcrumb (CExamR tid ssh csh examn EUsersR) = return ("Teilnehmer", Just $ CExamR tid ssh csh examn EShowR)
|
breadcrumb (CExamR tid ssh csh examn EUsersR) = return ("Teilnehmer", Just $ CExamR tid ssh csh examn EShowR)
|
||||||
breadcrumb (CExamR tid ssh csh examn EAddUserR) = return ("Klausurteilnehmer hinzufügen", Just $ CExamR tid ssh csh examn EUsersR)
|
breadcrumb (CExamR tid ssh csh examn EAddUserR) = return ("Prüfungsteilnehmer hinzufügen", Just $ CExamR tid ssh csh examn EUsersR)
|
||||||
|
|
||||||
breadcrumb (CTutorialR tid ssh csh tutn TUsersR) = return (original tutn, Just $ CourseR tid ssh csh CTutorialListR)
|
breadcrumb (CTutorialR tid ssh csh tutn TUsersR) = return (original tutn, Just $ CourseR tid ssh csh CTutorialListR)
|
||||||
breadcrumb (CTutorialR tid ssh csh tutn TEditR) = return ("Bearbeiten", Just $ CTutorialR tid ssh csh tutn TUsersR)
|
breadcrumb (CTutorialR tid ssh csh tutn TEditR) = return ("Bearbeiten", Just $ CTutorialR tid ssh csh tutn TUsersR)
|
||||||
@ -2731,6 +2793,7 @@ routeNormalizers =
|
|||||||
, ncCourse
|
, ncCourse
|
||||||
, ncSheet
|
, ncSheet
|
||||||
, verifySubmission
|
, verifySubmission
|
||||||
|
, verifyCourseApplication
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
normalizeRender route = route <$ do
|
normalizeRender route = route <$ do
|
||||||
@ -2777,6 +2840,14 @@ routeNormalizers =
|
|||||||
let newRoute = CSubmissionR courseTerm courseSchool courseShorthand sheetName cID sr
|
let newRoute = CSubmissionR courseTerm courseSchool courseShorthand sheetName cID sr
|
||||||
tell . Any $ route /= newRoute
|
tell . Any $ route /= newRoute
|
||||||
return newRoute
|
return newRoute
|
||||||
|
verifyCourseApplication = maybeOrig $ \route -> do
|
||||||
|
CApplicationR _tid _ssh _csh cID sr <- return route
|
||||||
|
aId <- decrypt cID
|
||||||
|
CourseApplication{courseApplicationCourse} <- lift . lift $ get404 aId
|
||||||
|
Course{courseTerm, courseSchool, courseShorthand} <- lift . lift $ get404 courseApplicationCourse
|
||||||
|
let newRoute = CApplicationR courseTerm courseSchool courseShorthand cID sr
|
||||||
|
tell . Any $ route /= newRoute
|
||||||
|
return newRoute
|
||||||
|
|
||||||
|
|
||||||
-- How to run database actions.
|
-- How to run database actions.
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import Handler.Course.Register as Handler.Course
|
|||||||
import Handler.Course.Show as Handler.Course
|
import Handler.Course.Show as Handler.Course
|
||||||
import Handler.Course.User as Handler.Course
|
import Handler.Course.User as Handler.Course
|
||||||
import Handler.Course.Users as Handler.Course
|
import Handler.Course.Users as Handler.Course
|
||||||
|
import Handler.Course.Application as Handler.Course
|
||||||
|
|
||||||
|
|
||||||
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
|
|||||||
37
src/Handler/Course/Application.hs
Normal file
37
src/Handler/Course/Application.hs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
module Handler.Course.Application
|
||||||
|
( getCAFilesR
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Handler.Utils
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
import System.FilePath (addExtension)
|
||||||
|
|
||||||
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
|
|
||||||
|
getCAFilesR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> Handler TypedContent
|
||||||
|
getCAFilesR tid ssh csh cID = do
|
||||||
|
appId <- decrypt cID
|
||||||
|
User{..} <- runDB $ do
|
||||||
|
CourseApplication{..} <- get404 appId
|
||||||
|
Course{..} <- get404 courseApplicationCourse
|
||||||
|
let matches = and
|
||||||
|
[ tid == courseTerm
|
||||||
|
, ssh == courseSchool
|
||||||
|
, csh == courseShorthand
|
||||||
|
]
|
||||||
|
unless matches . redirectWith movedPermanently301 $ CApplicationR courseTerm courseSchool courseShorthand cID CAFilesR
|
||||||
|
get404 courseApplicationUser
|
||||||
|
|
||||||
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgCourseApplicationArchiveName tid ssh csh cID userDisplayName
|
||||||
|
let
|
||||||
|
fsSource = E.selectSource . E.from $ \(courseApplicationFile `E.InnerJoin` file) -> do
|
||||||
|
E.on $ courseApplicationFile E.^. CourseApplicationFileFile E.==. file E.^. FileId
|
||||||
|
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. E.val appId
|
||||||
|
return file
|
||||||
|
|
||||||
|
serveSomeFiles archiveName $ fsSource .| C.map entityVal
|
||||||
@ -41,6 +41,12 @@ data CourseForm = CourseForm
|
|||||||
, cfLink :: Maybe Text
|
, cfLink :: Maybe Text
|
||||||
, cfMatFree :: Bool
|
, cfMatFree :: Bool
|
||||||
, cfAllocation :: Maybe AllocationCourseForm
|
, cfAllocation :: Maybe AllocationCourseForm
|
||||||
|
, cfAppRequired :: Bool
|
||||||
|
, cfAppInstructions :: Maybe Html
|
||||||
|
, cfAppInstructionFiles :: Maybe (Source Handler (Either FileId File))
|
||||||
|
, cfAppText :: Bool
|
||||||
|
, cfAppFiles :: UploadMode
|
||||||
|
, cfAppRatingsVisible :: Bool
|
||||||
, cfCapacity :: Maybe Int
|
, cfCapacity :: Maybe Int
|
||||||
, cfSecret :: Maybe Text
|
, cfSecret :: Maybe Text
|
||||||
, cfRegFrom :: Maybe UTCTime
|
, cfRegFrom :: Maybe UTCTime
|
||||||
@ -51,43 +57,45 @@ data CourseForm = CourseForm
|
|||||||
|
|
||||||
data AllocationCourseForm = AllocationCourseForm
|
data AllocationCourseForm = AllocationCourseForm
|
||||||
{ acfAllocation :: AllocationId
|
{ acfAllocation :: AllocationId
|
||||||
, acfInstructions :: Maybe Html
|
|
||||||
, acfFiles :: Maybe (Source Handler (Either FileId File))
|
|
||||||
, acfApplicationText :: Bool
|
|
||||||
, acfApplicationFiles :: UploadMode
|
|
||||||
, acfApplicationRatingsVisible :: Bool
|
|
||||||
, acfMinCapacity :: Int
|
, acfMinCapacity :: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
courseToForm :: Entity Course -> [Lecturer] -> [(UserEmail, InvitationDBData Lecturer)] -> Maybe (Entity AllocationCourse) -> CourseForm
|
courseToForm :: Entity Course -> [Lecturer] -> [(UserEmail, InvitationDBData Lecturer)] -> Maybe (Entity AllocationCourse) -> CourseForm
|
||||||
courseToForm (Entity cid Course{..}) lecs lecInvites alloc = CourseForm
|
courseToForm (Entity cid Course{..}) lecs lecInvites alloc = CourseForm
|
||||||
{ cfCourseId = Just cid
|
{ cfCourseId = Just cid
|
||||||
, cfName = courseName
|
, cfName = courseName
|
||||||
, cfDesc = courseDescription
|
, cfDesc = courseDescription
|
||||||
, cfLink = courseLinkExternal
|
, cfLink = courseLinkExternal
|
||||||
, cfShort = courseShorthand
|
, cfShort = courseShorthand
|
||||||
, cfTerm = courseTerm
|
, cfTerm = courseTerm
|
||||||
, cfSchool = courseSchool
|
, cfSchool = courseSchool
|
||||||
, cfCapacity = courseCapacity
|
, cfCapacity = courseCapacity
|
||||||
, cfSecret = courseRegisterSecret
|
, cfSecret = courseRegisterSecret
|
||||||
, cfMatFree = courseMaterialFree
|
, cfMatFree = courseMaterialFree
|
||||||
, cfRegFrom = courseRegisterFrom
|
, cfAllocation = allocationCourseToForm <$> alloc
|
||||||
, cfRegTo = courseRegisterTo
|
, cfAppRequired = courseApplicationsRequired
|
||||||
, cfDeRegUntil = courseDeregisterUntil
|
, cfAppInstructions = courseApplicationsInstructions
|
||||||
, cfAllocation = allocationCourseToForm <$> alloc
|
, cfAppInstructionFiles
|
||||||
, cfLecturers = [Right (lecturerUser, lecturerType) | Lecturer{..} <- lecs]
|
, cfAppText = courseApplicationsText
|
||||||
++ [Left (email, mType) | (email, InvDBDataLecturer mType) <- lecInvites ]
|
, cfAppFiles = courseApplicationsFiles
|
||||||
|
, cfAppRatingsVisible = courseApplicationsRatingsVisible
|
||||||
|
, cfRegFrom = courseRegisterFrom
|
||||||
|
, cfRegTo = courseRegisterTo
|
||||||
|
, cfDeRegUntil = courseDeregisterUntil
|
||||||
|
, cfLecturers = [Right (lecturerUser, lecturerType) | Lecturer{..} <- lecs]
|
||||||
|
++ [Left (email, mType) | (email, InvDBDataLecturer mType) <- lecInvites ]
|
||||||
}
|
}
|
||||||
|
where
|
||||||
|
cfAppInstructionFiles = Just . transPipe runDB $ selectAppFiles .| C.map (Left . E.unValue)
|
||||||
|
where selectAppFiles = E.selectSource . E.from $ \courseAppInstructionFile -> do
|
||||||
|
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid
|
||||||
|
return $ courseAppInstructionFile E.^. CourseAppInstructionFileFile
|
||||||
|
|
||||||
|
|
||||||
allocationCourseToForm :: Entity AllocationCourse -> AllocationCourseForm
|
allocationCourseToForm :: Entity AllocationCourse -> AllocationCourseForm
|
||||||
allocationCourseToForm (Entity _ AllocationCourse{..}) = AllocationCourseForm
|
allocationCourseToForm (Entity _ AllocationCourse{..}) = AllocationCourseForm
|
||||||
{ acfAllocation = allocationCourseAllocation
|
{ acfAllocation = allocationCourseAllocation
|
||||||
, acfMinCapacity = allocationCourseMinCapacity
|
, acfMinCapacity = allocationCourseMinCapacity
|
||||||
, acfInstructions = allocationCourseInstructions
|
|
||||||
, acfFiles = Nothing
|
|
||||||
, acfApplicationText = allocationCourseApplicationText
|
|
||||||
, acfApplicationFiles = allocationCourseApplicationFiles
|
|
||||||
, acfApplicationRatingsVisible = allocationCourseRatingsVisible
|
|
||||||
}
|
}
|
||||||
|
|
||||||
makeCourseForm :: (forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)) -> Maybe CourseForm -> Form CourseForm
|
makeCourseForm :: (forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)) -> Maybe CourseForm -> Form CourseForm
|
||||||
@ -213,21 +221,9 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse $ \html -> do
|
|||||||
_ -> do
|
_ -> do
|
||||||
allocationOptions <- mkOptionList <$> mapM mkAllocationOption availableAllocations
|
allocationOptions <- mkOptionList <$> mapM mkAllocationOption availableAllocations
|
||||||
|
|
||||||
oldFileIds <- for ((,) <$> (fmap acfAllocation $ template >>= cfAllocation) <*> (template >>= cfCourseId)) $ \(allId, cId) -> fmap (Set.fromList . map E.unValue) . liftHandlerT . runDB . E.select . E.from $ \(allocationCourseFile `E.InnerJoin` allocationCourse) -> do
|
|
||||||
E.on $ allocationCourseFile E.^. AllocationCourseFileAllocationCourse E.==. allocationCourse E.^. AllocationCourseId
|
|
||||||
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cId
|
|
||||||
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val allId
|
|
||||||
return $ allocationCourseFile E.^. AllocationCourseFileFile
|
|
||||||
|
|
||||||
|
|
||||||
let
|
let
|
||||||
allocationForm' = AllocationCourseForm
|
allocationForm' = AllocationCourseForm
|
||||||
<$> apreq (selectField' Nothing $ return allocationOptions) (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation)
|
<$> apreq (selectField' Nothing $ return allocationOptions) (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation)
|
||||||
<*> (assertM (not . null . renderHtml) <$> aopt htmlField (fslI MsgCourseAllocationInstructions & setTooltip MsgCourseAllocationInstructionsTip) (fmap acfInstructions $ template >>= cfAllocation))
|
|
||||||
<*> aopt (multiFileField . return $ fromMaybe Set.empty oldFileIds) (fslI MsgCourseAllocationApplicationTemplate) (fmap acfFiles $ template >>= cfAllocation)
|
|
||||||
<*> apopt checkBoxField (fslI MsgCourseAllocationApplicationText & setTooltip MsgCourseAllocationApplicationTextTip) (fmap acfApplicationText $ template >>= cfAllocation)
|
|
||||||
<*> uploadModeForm (fmap acfApplicationFiles $ template >>= cfAllocation)
|
|
||||||
<*> apopt checkBoxField (fslI MsgCourseAllocationApplicationRatingsVisible & setTooltip MsgCourseAllocationApplicationRatingsVisibleTip) (fmap acfApplicationRatingsVisible $ template >>= cfAllocation)
|
|
||||||
<*> apreq (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation)
|
<*> apreq (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation)
|
||||||
|
|
||||||
optionalActionW allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template)
|
optionalActionW allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template)
|
||||||
@ -247,6 +243,12 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse $ \html -> do
|
|||||||
<*> apopt checkBoxField (fslI MsgMaterialFree) (cfMatFree <$> template)
|
<*> apopt checkBoxField (fslI MsgMaterialFree) (cfMatFree <$> template)
|
||||||
<* aformSection MsgCourseFormSectionRegistration
|
<* aformSection MsgCourseFormSectionRegistration
|
||||||
<*> allocationForm
|
<*> allocationForm
|
||||||
|
<*> apopt checkBoxField (fslI MsgCourseApplicationRequired & setTooltip MsgCourseApplicationRequiredTip) (cfAppRequired <$> template)
|
||||||
|
<*> (assertM (not . null . renderHtml) <$> aopt htmlField (fslI MsgCourseApplicationInstructions & setTooltip MsgCourseApplicationInstructionsTip) (cfAppInstructions <$> template))
|
||||||
|
<*> aopt (multiFileField' . fromMaybe (return ()) $ cfAppInstructionFiles =<< template) (fslI MsgCourseApplicationTemplate & setTooltip MsgCourseApplicationTemplateTip) (cfAppInstructionFiles <$> template)
|
||||||
|
<*> apopt checkBoxField (fslI MsgCourseApplicationsText & setTooltip MsgCourseApplicationsTextTip) (cfAppText <$> template)
|
||||||
|
<*> uploadModeForm (cfAppFiles <$> template)
|
||||||
|
<*> apopt checkBoxField (fslI MsgCourseApplicationRatingsVisible & setTooltip MsgCourseApplicationRatingsVisibleTip) (cfAppRatingsVisible <$> template)
|
||||||
<*> aopt (natFieldI MsgCourseCapacity) (fslI MsgCourseCapacity
|
<*> aopt (natFieldI MsgCourseCapacity) (fslI MsgCourseCapacity
|
||||||
& setTooltip MsgCourseCapacityTip) (cfCapacity <$> template)
|
& setTooltip MsgCourseCapacityTip) (cfCapacity <$> template)
|
||||||
<*> aopt textField (fslpI MsgCourseSecret (mr MsgCourseSecretFormat)
|
<*> aopt textField (fslpI MsgCourseSecret (mr MsgCourseSecretFormat)
|
||||||
@ -425,20 +427,26 @@ courseEditHandler miButtonAction mbCourseForm = do
|
|||||||
} -> do -- create new course
|
} -> do -- create new course
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
insertOkay <- runDBJobs $ do
|
insertOkay <- runDBJobs $ do
|
||||||
insertOkay <- insertUnique Course
|
insertOkay <- let CourseForm{..} = res
|
||||||
{ courseName = cfName res
|
in insertUnique Course
|
||||||
, courseDescription = cfDesc res
|
{ courseName = cfName
|
||||||
, courseLinkExternal = cfLink res
|
, courseDescription = cfDesc
|
||||||
, courseShorthand = cfShort res
|
, courseLinkExternal = cfLink
|
||||||
, courseTerm = cfTerm res
|
, courseShorthand = cfShort
|
||||||
, courseSchool = cfSchool res
|
, courseTerm = cfTerm
|
||||||
, courseCapacity = cfCapacity res
|
, courseSchool = cfSchool
|
||||||
, courseRegisterSecret = cfSecret res
|
, courseCapacity = cfCapacity
|
||||||
, courseMaterialFree = cfMatFree res
|
, courseRegisterSecret = cfSecret
|
||||||
, courseRegisterFrom = cfRegFrom res
|
, courseMaterialFree = cfMatFree
|
||||||
, courseRegisterTo = cfRegTo res
|
, courseApplicationsRequired = cfAppRequired
|
||||||
, courseDeregisterUntil = cfDeRegUntil res
|
, courseApplicationsInstructions = cfAppInstructions
|
||||||
}
|
, courseApplicationsText = cfAppText
|
||||||
|
, courseApplicationsFiles = cfAppFiles
|
||||||
|
, courseApplicationsRatingsVisible = cfAppRatingsVisible
|
||||||
|
, courseRegisterFrom = cfRegFrom
|
||||||
|
, courseRegisterTo = cfRegTo
|
||||||
|
, courseDeregisterUntil = cfDeRegUntil
|
||||||
|
}
|
||||||
whenIsJust insertOkay $ \cid -> do
|
whenIsJust insertOkay $ \cid -> do
|
||||||
let (invites, adds) = partitionEithers $ cfLecturers res
|
let (invites, adds) = partitionEithers $ cfLecturers res
|
||||||
insertMany_ $ map (\(lid, lty) -> Lecturer lid cid lty) adds
|
insertMany_ $ map (\(lid, lty) -> Lecturer lid cid lty) adds
|
||||||
@ -466,20 +474,26 @@ courseEditHandler miButtonAction mbCourseForm = do
|
|||||||
case old of
|
case old of
|
||||||
Nothing -> addMessageI Error MsgInvalidInput $> False
|
Nothing -> addMessageI Error MsgInvalidInput $> False
|
||||||
(Just _) -> do
|
(Just _) -> do
|
||||||
updOkay <- myReplaceUnique cid Course
|
updOkay <- let CourseForm{..} = res
|
||||||
{ courseName = cfName res
|
in myReplaceUnique cid Course
|
||||||
, courseDescription = cfDesc res
|
{ courseName = cfName
|
||||||
, courseLinkExternal = cfLink res
|
, courseDescription = cfDesc
|
||||||
, courseShorthand = cfShort res
|
, courseLinkExternal = cfLink
|
||||||
, courseTerm = cfTerm res -- dangerous
|
, courseShorthand = cfShort
|
||||||
, courseSchool = cfSchool res
|
, courseTerm = cfTerm -- dangerous
|
||||||
, courseCapacity = cfCapacity res
|
, courseSchool = cfSchool
|
||||||
, courseRegisterSecret = cfSecret res
|
, courseCapacity = cfCapacity
|
||||||
, courseMaterialFree = cfMatFree res
|
, courseRegisterSecret = cfSecret
|
||||||
, courseRegisterFrom = cfRegFrom res
|
, courseMaterialFree = cfMatFree
|
||||||
, courseRegisterTo = cfRegTo res
|
, courseApplicationsRequired = cfAppRequired
|
||||||
, courseDeregisterUntil = cfDeRegUntil res
|
, courseApplicationsInstructions = cfAppInstructions
|
||||||
}
|
, courseApplicationsText = cfAppText
|
||||||
|
, courseApplicationsFiles = cfAppFiles
|
||||||
|
, courseApplicationsRatingsVisible = cfAppRatingsVisible
|
||||||
|
, courseRegisterFrom = cfRegFrom
|
||||||
|
, courseRegisterTo = cfRegTo
|
||||||
|
, courseDeregisterUntil = cfDeRegUntil
|
||||||
|
}
|
||||||
case updOkay of
|
case updOkay of
|
||||||
(Just _) -> addMessageI Warning (MsgCourseEditDupShort tid ssh csh) $> False
|
(Just _) -> addMessageI Warning (MsgCourseEditDupShort tid ssh csh) $> False
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
@ -490,7 +504,19 @@ courseEditHandler miButtonAction mbCourseForm = do
|
|||||||
sinkInvitationsF lecturerInvitationConfig $ map (\(lEmail, mLty) -> (lEmail, cid, (InvDBDataLecturer mLty, InvTokenDataLecturer))) invites
|
sinkInvitationsF lecturerInvitationConfig $ map (\(lEmail, mLty) -> (lEmail, cid, (InvDBDataLecturer mLty, InvTokenDataLecturer))) invites
|
||||||
|
|
||||||
insert_ $ CourseEdit aid now cid
|
insert_ $ CourseEdit aid now cid
|
||||||
|
|
||||||
|
let
|
||||||
|
finsert val = do
|
||||||
|
fId <- lift $ either return insert val
|
||||||
|
tell $ Set.singleton fId
|
||||||
|
lift $
|
||||||
|
void . insertUnique $ CourseAppInstructionFile cid fId
|
||||||
|
keep <- execWriterT . runConduit $ transPipe liftHandlerT (traverse_ id $ cfAppInstructionFiles res) .| C.mapM_ finsert
|
||||||
|
acfs <- selectList [ CourseAppInstructionFileCourse ==. cid, CourseAppInstructionFileFile /<-. Set.toList keep ] []
|
||||||
|
mapM_ deleteCascade $ map (courseAppInstructionFileFile . entityVal) acfs
|
||||||
|
|
||||||
upsertAllocationCourse cid $ cfAllocation res
|
upsertAllocationCourse cid $ cfAllocation res
|
||||||
|
|
||||||
addMessageI Success $ MsgCourseEditOk tid ssh csh
|
addMessageI Success $ MsgCourseEditOk tid ssh csh
|
||||||
return True
|
return True
|
||||||
when success $ redirect $ CourseR tid ssh csh CShowR
|
when success $ redirect $ CourseR tid ssh csh CShowR
|
||||||
@ -522,38 +548,17 @@ upsertAllocationCourse cid cfAllocation = do
|
|||||||
|
|
||||||
when doEdit $
|
when doEdit $
|
||||||
case cfAllocation of
|
case cfAllocation of
|
||||||
Just AllocationCourseForm{..} -> do
|
Just AllocationCourseForm{..} ->
|
||||||
Entity acId _ <- upsert AllocationCourse
|
void $ upsert AllocationCourse
|
||||||
{ allocationCourseAllocation = acfAllocation
|
{ allocationCourseAllocation = acfAllocation
|
||||||
, allocationCourseCourse = cid
|
, allocationCourseCourse = cid
|
||||||
, allocationCourseMinCapacity = acfMinCapacity
|
, allocationCourseMinCapacity = acfMinCapacity
|
||||||
, allocationCourseInstructions = acfInstructions
|
|
||||||
, allocationCourseApplicationText = acfApplicationText
|
|
||||||
, allocationCourseApplicationFiles = acfApplicationFiles
|
|
||||||
, allocationCourseRatingsVisible = acfApplicationRatingsVisible
|
|
||||||
}
|
}
|
||||||
[ AllocationCourseAllocation =. acfAllocation
|
[ AllocationCourseAllocation =. acfAllocation
|
||||||
, AllocationCourseCourse =. cid
|
, AllocationCourseCourse =. cid
|
||||||
, AllocationCourseMinCapacity =. acfMinCapacity
|
, AllocationCourseMinCapacity =. acfMinCapacity
|
||||||
, AllocationCourseInstructions =. acfInstructions
|
|
||||||
, AllocationCourseApplicationText =. acfApplicationText
|
|
||||||
, AllocationCourseApplicationFiles =. acfApplicationFiles
|
|
||||||
, AllocationCourseRatingsVisible =. acfApplicationRatingsVisible
|
|
||||||
]
|
]
|
||||||
|
|
||||||
let
|
|
||||||
finsert val = do
|
|
||||||
fId <- lift $ either return insert val
|
|
||||||
tell $ Set.singleton fId
|
|
||||||
lift $
|
|
||||||
void . insertUnique $ AllocationCourseFile acId fId
|
|
||||||
keep <- execWriterT . runConduit $ transPipe liftHandlerT (traverse_ id acfFiles) .| C.mapM_ finsert
|
|
||||||
acfs <- selectList [ AllocationCourseFileAllocationCourse ==. acId, AllocationCourseFileFile /<-. Set.toList keep ] []
|
|
||||||
mapM_ deleteCascade $ map (allocationCourseFileFile . entityVal) acfs
|
|
||||||
Nothing
|
Nothing
|
||||||
| Just (Entity prevId _) <- prevAllocationCourse
|
| Just (Entity prevId _) <- prevAllocationCourse
|
||||||
-> do
|
-> delete prevId
|
||||||
acfs <- selectList [ AllocationCourseFileAllocationCourse ==. prevId ] []
|
|
||||||
mapM_ deleteCascade $ map (allocationCourseFileFile . entityVal) acfs
|
|
||||||
delete prevId
|
|
||||||
_other -> return ()
|
_other -> return ()
|
||||||
|
|||||||
@ -8,6 +8,8 @@ module Handler.Course.List
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
|
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
-- import Utils.DB
|
-- import Utils.DB
|
||||||
@ -20,83 +22,43 @@ import qualified Data.Set as Set
|
|||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
|
|
||||||
-- NOTE: Outdated way to use dbTable; see ProfileDataR Handler for a more recent method.
|
-- NOTE: Outdated way to use dbTable; see ProfileDataR Handler for a more recent method.
|
||||||
type CourseTableData = DBRow (Entity Course, Int, Bool, Entity School)
|
type CourseTableData = DBRow (Entity Course, Int, Bool, Entity School, [Entity User])
|
||||||
|
|
||||||
colCourse :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colCourse :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } ->
|
||||||
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR)
|
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR)
|
||||||
[whamlet|_{courseName}|]
|
[whamlet|_{courseName}|]
|
||||||
|
|
||||||
-- colCourseDescr :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
-- colCourseDescr = sortable (Just "course") (i18nCell MsgCourse) $ do
|
|
||||||
-- course <- view $ _dbrOutput . _1 . _entityVal
|
|
||||||
-- return $ courseCell course
|
|
||||||
|
|
||||||
colDescription :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colDescription :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colDescription = sortable Nothing mempty
|
colDescription = sortable Nothing mempty
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } ->
|
||||||
case courseDescription of
|
case courseDescription of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
(Just descr) -> cell $ modal (toWidget $ hasComment True) (Right $ toWidget descr)
|
(Just descr) -> cell $ modal (toWidget $ hasComment True) (Right $ toWidget descr)
|
||||||
|
|
||||||
colCShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colCShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } ->
|
||||||
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|_{courseShorthand}|]
|
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|_{courseShorthand}|]
|
||||||
|
|
||||||
-- colCShortDescr :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
-- colCShortDescr = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
|
||||||
-- $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } -> mappend
|
|
||||||
-- ( anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|#{display courseShorthand}|] )
|
|
||||||
-- ( case courseDescription of
|
|
||||||
-- Nothing -> mempty
|
|
||||||
-- (Just descr) -> cell
|
|
||||||
-- [whamlet|
|
|
||||||
-- $newline never
|
|
||||||
-- <div>
|
|
||||||
-- ^{modal "Beschreibung" (Right $ toWidget descr)}
|
|
||||||
-- |]
|
|
||||||
-- )
|
|
||||||
|
|
||||||
colTerm :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colTerm :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } ->
|
||||||
anchorCell (TermCourseListR courseTerm) [whamlet|#{courseTerm}|]
|
anchorCell (TermCourseListR courseTerm) [whamlet|#{courseTerm}|]
|
||||||
|
|
||||||
-- colSchool :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
-- colSchool = sortable (Just "school") (i18nCell MsgCourseSchool)
|
|
||||||
-- $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
|
||||||
-- anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|_{schoolName}|]
|
|
||||||
|
|
||||||
colSchoolShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colSchoolShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colSchoolShort = sortable (Just "schoolshort") (i18nCell MsgCourseSchoolShort)
|
colSchoolShort = sortable (Just "schoolshort") (i18nCell MsgCourseSchoolShort)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}, _) } ->
|
||||||
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|_{schoolShorthand}|]
|
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|_{schoolShorthand}|]
|
||||||
|
|
||||||
colRegFrom :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
colRegFrom = sortable (Just "register-from") (i18nCell MsgRegisterFrom)
|
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
|
||||||
maybe mempty dateTimeCell courseRegisterFrom
|
|
||||||
-- cell $ traverse (formatTime SelFormatDateTime) courseRegisterFrom >>= maybe mempty toWidget
|
|
||||||
|
|
||||||
colRegTo :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
colRegTo = sortable (Just "register-to") (i18nCell MsgRegisterTo)
|
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
|
||||||
maybe mempty dateTimeCell courseRegisterTo
|
|
||||||
|
|
||||||
colMembers :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
|
||||||
colMembers = sortable (Just "members") (i18nCell MsgCourseMembers)
|
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, currentParticipants, _, _) } -> i18nCell $ case courseCapacity of
|
|
||||||
Nothing -> MsgCourseMembersCount currentParticipants
|
|
||||||
Just limit -> MsgCourseMembersCountLimited currentParticipants limit
|
|
||||||
|
|
||||||
colRegistered :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
colRegistered :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colRegistered = sortable (Just "registered") (i18nCell MsgRegistered)
|
colRegistered = sortable (Just "registered") (i18nCell MsgRegistered)
|
||||||
$ \DBRow{ dbrOutput=(_, _, registered, _) } -> tickmarkCell registered
|
$ \DBRow{ dbrOutput=(_, _, registered, _, _) } -> tickmarkCell registered
|
||||||
|
|
||||||
type CourseTableExpr = E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School)
|
type CourseTableExpr = E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School)
|
||||||
|
|
||||||
@ -114,6 +76,7 @@ makeCourseTable :: ( IsDBTable m x, ToSortable h, Functor h, DBResult m x ~ ((),
|
|||||||
=> _ -> Colonnade h CourseTableData (DBCell m x) -> PSValidator m x -> DB Widget
|
=> _ -> Colonnade h CourseTableData (DBCell m x) -> PSValidator m x -> DB Widget
|
||||||
makeCourseTable whereClause colChoices psValidator = do
|
makeCourseTable whereClause colChoices psValidator = do
|
||||||
muid <- lift maybeAuthId
|
muid <- lift maybeAuthId
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
let dbtSQLQuery :: CourseTableExpr -> E.SqlQuery _
|
let dbtSQLQuery :: CourseTableExpr -> E.SqlQuery _
|
||||||
dbtSQLQuery qin@(course `E.InnerJoin` school) = do
|
dbtSQLQuery qin@(course `E.InnerJoin` school) = do
|
||||||
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
||||||
@ -121,8 +84,14 @@ makeCourseTable whereClause colChoices psValidator = do
|
|||||||
let registered = course2Registered muid qin
|
let registered = course2Registered muid qin
|
||||||
E.where_ $ whereClause (course, participants, registered)
|
E.where_ $ whereClause (course, participants, registered)
|
||||||
return (course, participants, registered, school)
|
return (course, participants, registered, school)
|
||||||
|
lecturerQuery cid (user `E.InnerJoin` lecturer) = do
|
||||||
|
E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser
|
||||||
|
E.where_ $ cid E.==. lecturer E.^. LecturerCourse E.&&. lecturer E.^. LecturerType E.==. E.val CourseLecturer
|
||||||
|
return user
|
||||||
dbtProj :: DBRow _ -> MaybeT (ReaderT SqlBackend (HandlerT UniWorX IO)) CourseTableData
|
dbtProj :: DBRow _ -> MaybeT (ReaderT SqlBackend (HandlerT UniWorX IO)) CourseTableData
|
||||||
dbtProj = traverse $ \(course, E.Value participants, E.Value registered, school) -> return (course, participants, registered, school)
|
dbtProj = traverse $ \(course, E.Value participants, E.Value registered, school) -> do
|
||||||
|
lecturerList <- lift $ E.select $ E.from $ lecturerQuery $ E.val $ entityKey course
|
||||||
|
return (course, participants, registered, school, lecturerList)
|
||||||
snd <$> dbTable psValidator DBTable
|
snd <$> dbTable psValidator DBTable
|
||||||
{ dbtSQLQuery
|
{ dbtSQLQuery
|
||||||
, dbtRowKey = \(course `E.InnerJoin` _) -> course E.^. CourseId
|
, dbtRowKey = \(course `E.InnerJoin` _) -> course E.^. CourseId
|
||||||
@ -163,6 +132,18 @@ makeCourseTable whereClause colChoices psValidator = do
|
|||||||
| Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool)
|
| Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
| otherwise -> school E.^. SchoolShorthand `E.in_` E.valList (Set.toList criterias)
|
| otherwise -> school E.^. SchoolShorthand `E.in_` E.valList (Set.toList criterias)
|
||||||
)
|
)
|
||||||
|
, ( "lecturer", FilterColumn $ \(course `E.InnerJoin` _school :: CourseTableExpr) criterias -> if
|
||||||
|
| Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
|
| otherwise -> E.exists $ E.from $ \t -> do
|
||||||
|
user <- lecturerQuery (course E.^. CourseId) t
|
||||||
|
E.where_ $ E.any (E.hasInfix (user E.^. UserSurname) . E.val) (criterias :: Set.Set Text)
|
||||||
|
)
|
||||||
|
, ( "openregistration", FilterColumn $ \(course `E.InnerJoin` _school :: CourseTableExpr) criterion -> case getLast (criterion :: Last Bool) of
|
||||||
|
Nothing -> E.val True
|
||||||
|
Just b -> let regTo = course E.^. CourseRegisterTo
|
||||||
|
regFrom = course E.^. CourseRegisterFrom
|
||||||
|
in (E.==.) (E.val b) $ (E.isNothing regTo E.||. E.val (Just now) E.<=. regTo) E.&&. E.val (Just now) E.>=. regFrom
|
||||||
|
)
|
||||||
, ( "registered", FilterColumn $ \tExpr criterion -> case getLast (criterion :: Last Bool) of
|
, ( "registered", FilterColumn $ \tExpr criterion -> case getLast (criterion :: Last Bool) of
|
||||||
Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
|
Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
Just needle -> course2Registered muid tExpr E.==. E.val needle
|
Just needle -> course2Registered muid tExpr E.==. E.val needle
|
||||||
@ -175,10 +156,18 @@ makeCourseTable whereClause colChoices psValidator = do
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
, dbtFilterUI = \mPrev -> mconcat $ catMaybes
|
, dbtFilterUI = \mPrev -> mconcat $ catMaybes
|
||||||
[ Just $ prismAForm (singletonFilter "search") mPrev $ aopt textField (fslI MsgCourseFilterSearch)
|
[ Just $ prismAForm (singletonFilter "term" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift termField) (fslI MsgTerm)
|
||||||
|
, Just $ prismAForm (singletonFilter "schoolshort" . maybePrism (_PathPiece . from _SchoolId)) mPrev $ aopt (hoistField lift schoolField) (fslI MsgCourseSchool)
|
||||||
|
, Just $ prismAForm (singletonFilter "lecturer") mPrev $ aopt textField (fslI MsgCourseLecturer)
|
||||||
|
, Just $ prismAForm (singletonFilter "search") mPrev $ aopt textField (fslI MsgCourseFilterSearch)
|
||||||
|
, Just $ prismAForm (singletonFilter "openregistration" . maybePrism _PathPiece) mPrev $ fmap (\x -> if isJust x && not (fromJust x) then Nothing else x) . aopt checkBoxField (fslI MsgCourseRegisterOpen)
|
||||||
, muid $> prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt boolField (fslI MsgCourseFilterRegistered))
|
, muid $> prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt boolField (fslI MsgCourseFilterRegistered))
|
||||||
]
|
]
|
||||||
, dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
|
, dbtStyle = def
|
||||||
|
{ dbsFilterLayout = defaultDBSFilterLayout
|
||||||
|
, dbsTemplate = DBSTCourse (_dbrOutput . _1) (_dbrOutput . _5) (_dbrOutput . _3) (_dbrOutput . _4)
|
||||||
|
-- ^ course ^ lecturer list ^ isRegistered ^ school
|
||||||
|
}
|
||||||
, dbtParams = def
|
, dbtParams = def
|
||||||
, dbtIdent = "courses" :: Text
|
, dbtIdent = "courses" :: Text
|
||||||
, dbtCsvEncode = noCsvEncode
|
, dbtCsvEncode = noCsvEncode
|
||||||
@ -208,53 +197,13 @@ getTermCurrentR :: Handler Html
|
|||||||
getTermCurrentR = do
|
getTermCurrentR = do
|
||||||
termIds <- runDB $ selectKeysList [TermActive ==. True] [Desc TermName]
|
termIds <- runDB $ selectKeysList [TermActive ==. True] [Desc TermName]
|
||||||
case fromNullable termIds of
|
case fromNullable termIds of
|
||||||
Nothing -> notFound
|
Nothing
|
||||||
(Just (maximum -> tid)) ->
|
-> notFound
|
||||||
redirect $ TermCourseListR tid -- redirect avoids problematic breadcrumbs, headings, etc.
|
Just (maximum -> tid)
|
||||||
|
-> redirect (CourseListR, [("courses-term", toPathPiece tid)]) -- redirect avoids problematic breadcrumbs, headings, etc.
|
||||||
|
|
||||||
getTermSchoolCourseListR :: TermId -> SchoolId -> Handler Html
|
getTermSchoolCourseListR :: TermId -> SchoolId -> Handler Html
|
||||||
getTermSchoolCourseListR tid ssh = do
|
getTermSchoolCourseListR tid ssh = redirect (CourseListR, [("courses-term", toPathPiece tid), ("courses-schoolshort", toPathPiece ssh)])
|
||||||
void . runDB $ get404 tid -- Just ensure the term exists
|
|
||||||
School{schoolName=school} <- runDB $ get404 ssh -- Just ensure the term exists
|
|
||||||
muid <- maybeAuthId
|
|
||||||
let colonnade = widgetColonnade $ mconcat
|
|
||||||
[ dbRow
|
|
||||||
, colCShort
|
|
||||||
, colDescription
|
|
||||||
, colRegFrom
|
|
||||||
, colRegTo
|
|
||||||
, colMembers
|
|
||||||
, maybe mempty (const colRegistered) muid
|
|
||||||
]
|
|
||||||
whereClause (course, _, _) =
|
|
||||||
course E.^. CourseTerm E.==. E.val tid
|
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
|
||||||
validator = def
|
|
||||||
& defaultSorting [SortAscBy "cshort"]
|
|
||||||
coursesTable <- runDB $ makeCourseTable whereClause colonnade validator
|
|
||||||
defaultLayout $ do
|
|
||||||
setTitleI $ MsgTermSchoolCourseListTitle tid school
|
|
||||||
$(widgetFile "courses")
|
|
||||||
|
|
||||||
|
|
||||||
getTermCourseListR :: TermId -> Handler Html
|
getTermCourseListR :: TermId -> Handler Html
|
||||||
getTermCourseListR tid = do
|
getTermCourseListR tid = redirect (CourseListR, [("courses-term", toPathPiece tid)])
|
||||||
void . runDB $ get404 tid -- Just ensure the term exists
|
|
||||||
muid <- maybeAuthId
|
|
||||||
let colonnade = widgetColonnade $ mconcat
|
|
||||||
[ dbRow
|
|
||||||
, colCShort
|
|
||||||
, colDescription
|
|
||||||
, colSchoolShort
|
|
||||||
, colRegFrom
|
|
||||||
, colRegTo
|
|
||||||
, colMembers
|
|
||||||
, maybe mempty (const colRegistered) muid
|
|
||||||
]
|
|
||||||
whereClause (course, _, _) = course E.^. CourseTerm E.==. E.val tid
|
|
||||||
validator = def
|
|
||||||
& defaultSorting [SortAscBy "cshort"]
|
|
||||||
coursesTable <- runDB $ makeCourseTable whereClause colonnade validator
|
|
||||||
defaultLayout $ do
|
|
||||||
setTitleI . MsgTermCourseListTitle $ tid
|
|
||||||
$(widgetFile "courses")
|
|
||||||
|
|||||||
@ -82,10 +82,13 @@ participantInvitationConfig = InvitationConfig{..}
|
|||||||
invitationRestriction _ _ = return Authorized
|
invitationRestriction _ _ = return Authorized
|
||||||
invitationForm (Entity _ Course{..}) _ uid = hoistAForm lift . wFormToAForm $ do
|
invitationForm (Entity _ Course{..}) _ uid = hoistAForm lift . wFormToAForm $ do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
studyFeatures <- wreq (studyFeaturesPrimaryFieldFor False [] $ Just uid)
|
studyFeatures <- wreq (studyFeaturesFieldFor Nothing False [] $ Just uid)
|
||||||
(fslI MsgCourseStudyFeature & setTooltip MsgCourseStudyFeatureTooltip) Nothing
|
(fslI MsgCourseStudyFeature & setTooltip MsgCourseStudyFeatureTip) Nothing
|
||||||
return . fmap (, ()) $ JunctionParticipant <$> pure now <*> studyFeatures <*> pure False
|
return . fmap (, ()) $ JunctionParticipant <$> pure now <*> studyFeatures <*> pure False
|
||||||
invitationInsertHook _ _ _ _ = id
|
invitationInsertHook _ _ CourseParticipant{..} _ act = do
|
||||||
|
res <- act
|
||||||
|
audit $ TransactionCourseParticipantEdit courseParticipantCourse courseParticipantUser
|
||||||
|
return res
|
||||||
invitationSuccessMsg (Entity _ Course{..}) _ =
|
invitationSuccessMsg (Entity _ Course{..}) _ =
|
||||||
return . SomeMessage $ MsgCourseParticipantInvitationAccepted (CI.original courseName)
|
return . SomeMessage $ MsgCourseParticipantInvitationAccepted (CI.original courseName)
|
||||||
invitationUltDest (Entity _ Course{..}) _ = return . SomeRoute $ CourseR courseTerm courseSchool courseShorthand CShowR
|
invitationUltDest (Entity _ Course{..}) _ = return . SomeRoute $ CourseR courseTerm courseSchool courseShorthand CShowR
|
||||||
@ -165,6 +168,7 @@ postCAddUserR tid ssh csh = do
|
|||||||
, courseParticipantAllocated = False
|
, courseParticipantAllocated = False
|
||||||
, ..
|
, ..
|
||||||
}
|
}
|
||||||
|
lift . lift . audit $ TransactionCourseParticipantEdit cid uid
|
||||||
|
|
||||||
return $ case courseParticipantField of
|
return $ case courseParticipantField of
|
||||||
Nothing -> mempty { aurNoUniquePrimaryField = pure userEmail }
|
Nothing -> mempty { aurNoUniquePrimaryField = pure userEmail }
|
||||||
|
|||||||
@ -1,64 +1,153 @@
|
|||||||
module Handler.Course.Register
|
module Handler.Course.Register
|
||||||
( ButtonCourseRegister(..)
|
( ButtonCourseRegister(..)
|
||||||
|
, CourseRegisterForm(..)
|
||||||
, courseRegisterForm
|
, courseRegisterForm
|
||||||
, getCRegisterR, postCRegisterR
|
, getCRegisterR, postCRegisterR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
import Utils.Form
|
import Utils.Lens
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
|
|
||||||
import Data.Function ((&))
|
import Data.Function ((&))
|
||||||
|
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
|
import Database.Persist.Sql (transactionUndo)
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
|
||||||
-- Dedicated CourseRegistrationButton
|
-- Dedicated CourseRegistrationButton
|
||||||
data ButtonCourseRegister = BtnCourseRegister | BtnCourseDeregister
|
data ButtonCourseRegister = BtnCourseRegister | BtnCourseDeregister | BtnCourseApply | BtnCourseRetractApplication
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||||
instance Universe ButtonCourseRegister
|
instance Universe ButtonCourseRegister
|
||||||
instance Finite ButtonCourseRegister
|
instance Finite ButtonCourseRegister
|
||||||
nullaryPathPiece ''ButtonCourseRegister $ camelToPathPiece' 1
|
nullaryPathPiece ''ButtonCourseRegister $ camelToPathPiece' 1
|
||||||
embedRenderMessage ''UniWorX ''ButtonCourseRegister id
|
embedRenderMessage ''UniWorX ''ButtonCourseRegister id
|
||||||
instance Button UniWorX ButtonCourseRegister where
|
instance Button UniWorX ButtonCourseRegister where
|
||||||
btnClasses BtnCourseRegister = [BCIsButton, BCPrimary]
|
btnClasses BtnCourseRegister = [BCIsButton, BCPrimary]
|
||||||
btnClasses BtnCourseDeregister = [BCIsButton, BCDanger]
|
btnClasses BtnCourseDeregister = [BCIsButton, BCDanger]
|
||||||
|
btnClasses BtnCourseApply = [BCIsButton, BCPrimary]
|
||||||
|
btnClasses BtnCourseRetractApplication = [BCIsButton, BCDanger]
|
||||||
|
|
||||||
btnLabel BtnCourseRegister = [whamlet|#{iconEnrol True} _{MsgBtnCourseRegister}|]
|
btnLabel BtnCourseRegister = [whamlet|#{iconEnrol True} _{MsgBtnCourseRegister}|]
|
||||||
btnLabel BtnCourseDeregister = [whamlet|#{iconEnrol False} _{MsgBtnCourseDeregister}|]
|
btnLabel BtnCourseDeregister = [whamlet|#{iconEnrol False} _{MsgBtnCourseDeregister}|]
|
||||||
|
btnLabel BtnCourseApply = [whamlet|#{iconApply True} _{MsgBtnCourseApply}|]
|
||||||
|
btnLabel BtnCourseRetractApplication = [whamlet|#{iconApply False} _{MsgBtnCourseRetractApplication}|]
|
||||||
|
|
||||||
|
|
||||||
-- | Registration button with maybe a userid if logged in
|
data CourseRegisterForm = CourseRegisterForm
|
||||||
-- , maybe existing features if already registered
|
{ crfStudyFeatures :: Maybe StudyFeaturesId
|
||||||
-- , maybe some default study features
|
, crfApplicationText :: Maybe Text
|
||||||
-- , maybe a course secret
|
, crfApplicationFiles :: Maybe (Source Handler File)
|
||||||
courseRegisterForm :: Maybe UserId -> Maybe CourseParticipant -> Maybe StudyFeaturesId -> Maybe Text -> Form (Maybe StudyFeaturesId, Bool)
|
}
|
||||||
-- unfinished WIP: must take study features if registred and show as mforced field
|
|
||||||
courseRegisterForm loggedin participant defSFid msecret = identifyForm FIDcourseRegister $ \extra -> do
|
|
||||||
-- secret fields
|
|
||||||
(msecretRes', msecretView) <- case msecret of
|
|
||||||
(Just _) | not isRegistered -> bimap Just Just <$> mreq textField (fslpI MsgCourseSecret "Code") Nothing
|
|
||||||
_ -> return (Nothing,Nothing)
|
|
||||||
-- study features
|
|
||||||
(msfRes', msfView) <- case loggedin of
|
|
||||||
Nothing -> return (Nothing,Nothing)
|
|
||||||
Just _ -> bimap Just Just <$> case participant of
|
|
||||||
Just CourseParticipant{courseParticipantField=Just sfid}
|
|
||||||
-> mforced (studyFeaturesPrimaryFieldFor False [sfid] loggedin) (fslI MsgCourseStudyFeature) (Just sfid)
|
|
||||||
_other -> mreq (studyFeaturesPrimaryFieldFor False [ ] loggedin) (fslI MsgCourseStudyFeature
|
|
||||||
& setTooltip MsgCourseStudyFeatureTooltip) (Just defSFid)
|
|
||||||
-- button de-/register
|
|
||||||
(btnRes, btnView) <- mreq (buttonField $ bool BtnCourseRegister BtnCourseDeregister isRegistered) "buttonField ignores settings anyway" Nothing
|
|
||||||
|
|
||||||
let widget = $(widgetFile "widgets/register-form/register-form")
|
courseRegisterForm :: (MonadHandler m, HandlerSite m ~ UniWorX) => Entity Course -> m (AForm Handler CourseRegisterForm, ButtonCourseRegister)
|
||||||
let msecretRes | Just res <- msecretRes' = Just <$> res
|
-- ^ `CourseRegisterForm` for current user
|
||||||
| otherwise = FormSuccess Nothing
|
courseRegisterForm (Entity cid Course{..}) = liftHandlerT $ do
|
||||||
let msfRes | Just res <- msfRes' = res
|
muid <- maybeAuthId
|
||||||
| otherwise = FormSuccess Nothing
|
(registration, application) <- runDB $ do
|
||||||
-- checks that correct button was pressed, and ignores result of btnRes
|
registration <- fmap join . for muid $ getBy . flip UniqueParticipant cid
|
||||||
let formRes = (,) <$ btnRes <*> msfRes <*> ((==msecret) <$> msecretRes)
|
application <- fmap (listToMaybe =<<) . for muid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] []
|
||||||
return (formRes, widget)
|
return (registration, application)
|
||||||
where
|
let btn | courseApplicationsRequired
|
||||||
isRegistered = isJust participant
|
, is _Just application
|
||||||
|
= BtnCourseRetractApplication
|
||||||
|
| is _Just registration
|
||||||
|
= BtnCourseDeregister
|
||||||
|
| courseApplicationsRequired
|
||||||
|
= BtnCourseApply
|
||||||
|
| otherwise
|
||||||
|
= BtnCourseRegister
|
||||||
|
isRegistered = btn `elem` [BtnCourseRetractApplication, BtnCourseDeregister]
|
||||||
|
return . (, btn) . wFormToAForm $ do
|
||||||
|
MsgRenderer mr <- getMsgRenderer
|
||||||
|
|
||||||
|
secretRes <- if
|
||||||
|
| Just secret <- courseRegisterSecret
|
||||||
|
, not isRegistered
|
||||||
|
-> let guardSecret (FormSuccess secret')
|
||||||
|
| secret == secret' = return $ FormSuccess ()
|
||||||
|
| otherwise = formFailure [MsgCourseSecretWrong]
|
||||||
|
guardSecret FormMissing = return FormMissing
|
||||||
|
guardSecret (FormFailure errs) = return $ FormFailure errs
|
||||||
|
in guardSecret =<< wreq textField (fslpI MsgCourseSecret $ mr MsgCourseSecret) Nothing
|
||||||
|
| otherwise
|
||||||
|
-> return $ FormSuccess ()
|
||||||
|
|
||||||
|
fieldRes <- if
|
||||||
|
| is _Nothing muid
|
||||||
|
-> return $ FormSuccess Nothing
|
||||||
|
| is _Just muid
|
||||||
|
, isRegistered
|
||||||
|
, Just mFeature <- courseApplicationField . entityVal <$> application
|
||||||
|
<|> courseParticipantField . entityVal <$> registration
|
||||||
|
-> wforced (studyFeaturesFieldFor Nothing True (maybeToList mFeature) muid) (fslI MsgCourseStudyFeature & setTooltip MsgCourseStudyFeatureTip) mFeature
|
||||||
|
| otherwise
|
||||||
|
-> wreq (studyFeaturesFieldFor Nothing False [] muid) (fslI MsgCourseStudyFeature & setTooltip MsgCourseStudyFeatureTip) Nothing
|
||||||
|
|
||||||
|
appTextRes <- let fs | courseApplicationsRequired
|
||||||
|
, is _Just courseApplicationsInstructions
|
||||||
|
= fslI MsgCourseApplicationText & setTooltip MsgCourseApplicationFollowInstructions
|
||||||
|
| courseApplicationsRequired
|
||||||
|
= fslI MsgCourseApplicationText
|
||||||
|
| is _Just courseApplicationsInstructions
|
||||||
|
= fslI MsgCourseRegistrationText & setTooltip MsgCourseRegistrationFollowInstructions
|
||||||
|
| otherwise
|
||||||
|
= fslI MsgCourseRegistrationText
|
||||||
|
textField' = convertField unTextarea Textarea textareaField
|
||||||
|
in if
|
||||||
|
| not courseApplicationsText
|
||||||
|
-> return $ FormSuccess Nothing
|
||||||
|
| is _Just muid
|
||||||
|
, isRegistered
|
||||||
|
-> wforced (convertField Just (fromMaybe Text.empty) textField') fs (application >>= courseApplicationText . entityVal)
|
||||||
|
| otherwise
|
||||||
|
-> fmap (assertM (not . Text.null) . fmap Text.strip) <$> wopt textField' fs (Just $ application >>= courseApplicationText . entityVal)
|
||||||
|
|
||||||
|
hasFiles <- for application $ \(Entity appId _)
|
||||||
|
-> fmap (not . null) . liftHandlerT . runDB $ selectKeysList [ CourseApplicationFileApplication ==. appId ] [ LimitTo 1 ]
|
||||||
|
appCID <- for application $ encrypt . entityKey
|
||||||
|
let appFilesInfo = (,) <$> hasFiles <*> appCID
|
||||||
|
filesMsg = bool MsgCourseRegistrationFiles MsgCourseApplicationFiles courseApplicationsRequired
|
||||||
|
|
||||||
|
if
|
||||||
|
| isn't _NoUpload courseApplicationsFiles || fromMaybe False hasFiles
|
||||||
|
-> let filesLinkField = Field{..}
|
||||||
|
where
|
||||||
|
fieldParse _ _ = return $ Right Nothing
|
||||||
|
fieldEnctype = mempty
|
||||||
|
fieldView theId _ attrs _ _
|
||||||
|
= [whamlet|
|
||||||
|
$newline never
|
||||||
|
$case appFilesInfo
|
||||||
|
$of Just (True, appCID)
|
||||||
|
<a ##{theId} *{attrs} href=@{CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR}>
|
||||||
|
_{filesMsg}
|
||||||
|
$of _
|
||||||
|
<span ##{theId} *{attrs}>
|
||||||
|
_{MsgCourseApplicationNoFiles}
|
||||||
|
|]
|
||||||
|
in void $ wforced filesLinkField (fslI filesMsg) Nothing
|
||||||
|
| otherwise
|
||||||
|
-> return ()
|
||||||
|
|
||||||
|
appFilesRes <- let mkFs | courseApplicationsRequired = bool MsgCourseApplicationFile MsgCourseApplicationArchive
|
||||||
|
| otherwise = bool MsgCourseRegistrationFile MsgCourseRegistrationArchive
|
||||||
|
in if
|
||||||
|
| isRegistered
|
||||||
|
-> return $ FormSuccess Nothing
|
||||||
|
| otherwise
|
||||||
|
-> aFormToWForm $ fileUploadForm False (fslI . mkFs) courseApplicationsFiles
|
||||||
|
|
||||||
|
return $ CourseRegisterForm
|
||||||
|
<$ secretRes
|
||||||
|
<*> fieldRes
|
||||||
|
<*> appTextRes
|
||||||
|
<*> appFilesRes
|
||||||
|
|
||||||
|
|
||||||
-- | Workaround for klicking register button without being logged in.
|
-- | Workaround for klicking register button without being logged in.
|
||||||
@ -76,21 +165,87 @@ getCRegisterR tid ssh csh = do
|
|||||||
|
|
||||||
postCRegisterR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
postCRegisterR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
postCRegisterR tid ssh csh = do
|
postCRegisterR tid ssh csh = do
|
||||||
aid <- requireAuthId
|
uid <- requireAuthId
|
||||||
(cid, course, registration) <- runDB $ do
|
course@(Entity cid Course{..}) <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
(Entity cid course) <- getBy404 $ TermSchoolCourseShort tid ssh csh
|
(courseRegisterForm', courseRegisterButton) <- courseRegisterForm course
|
||||||
registration <- getBy (UniqueParticipant aid cid)
|
((regResult,_), _) <- runFormPost $ renderAForm FormStandard courseRegisterForm'
|
||||||
return (cid, course, entityVal <$> registration)
|
formResult regResult $ \CourseRegisterForm{..} -> do
|
||||||
let isRegistered = isJust registration
|
cTime <- liftIO getCurrentTime
|
||||||
((regResult,_), _) <- runFormPost $ courseRegisterForm (Just aid) registration Nothing $ courseRegisterSecret course
|
let
|
||||||
formResult regResult $ \(mbSfId,codeOk) -> if
|
mkApplication
|
||||||
| isRegistered -> do
|
| courseApplicationsRequired || is _Just (void crfApplicationText <|> void crfApplicationFiles)
|
||||||
runDB $ deleteBy $ UniqueParticipant aid cid
|
= void <$> do
|
||||||
addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk
|
appIds <- selectKeysList [ CourseApplicationAllocation ==. Nothing, CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid ] []
|
||||||
| codeOk -> do
|
appRes <- case appIds of
|
||||||
actTime <- liftIO getCurrentTime
|
[] -> insertUnique $ CourseApplication cid uid crfStudyFeatures crfApplicationText Nothing Nothing Nothing Nothing cTime
|
||||||
regOk <- runDB $ insertUnique $ CourseParticipant cid aid actTime mbSfId False
|
(prevId:ps) -> do
|
||||||
when (isJust regOk) $ addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
|
forM_ ps $ \appId -> do
|
||||||
| otherwise -> addMessageI Warning MsgCourseSecretWrong
|
deleteApplicationFiles appId
|
||||||
-- addMessage Info $ toHtml $ show regResult -- For debugging only
|
delete appId
|
||||||
|
audit $ TransactionCourseApplicationDeleted cid uid appId
|
||||||
|
|
||||||
|
deleteApplicationFiles prevId
|
||||||
|
update prevId [ CourseApplicationField =. crfStudyFeatures, CourseApplicationText =. crfApplicationText, CourseApplicationTime =. cTime ]
|
||||||
|
|
||||||
|
return $ Just prevId
|
||||||
|
|
||||||
|
whenIsJust appRes $
|
||||||
|
audit . TransactionCourseApplicationEdit cid uid
|
||||||
|
whenIsJust ((,) <$> appRes <*> crfApplicationFiles) $ \(appId, fSource) -> do
|
||||||
|
runConduit $ transPipe liftHandlerT fSource .| C.mapM_ (\f -> insert f >>= insert_ . CourseApplicationFile appId)
|
||||||
|
return appRes
|
||||||
|
| otherwise
|
||||||
|
= return $ Just ()
|
||||||
|
mkRegistration = do
|
||||||
|
audit $ TransactionCourseParticipantEdit cid uid
|
||||||
|
insertUnique $ CourseParticipant cid uid cTime crfStudyFeatures False
|
||||||
|
|
||||||
|
deleteApplications = do
|
||||||
|
appIds <- selectKeysList [ CourseApplicationAllocation ==. Nothing, CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid ] []
|
||||||
|
forM_ appIds $ \appId -> do
|
||||||
|
deleteApplicationFiles appId
|
||||||
|
delete appId
|
||||||
|
audit $ TransactionCourseApplicationDeleted cid uid appId
|
||||||
|
|
||||||
|
deleteApplicationFiles appId = do
|
||||||
|
fs <- selectList [ CourseApplicationFileApplication ==. appId ] []
|
||||||
|
deleteCascadeWhere [ FileId <-. map (courseApplicationFileFile . entityVal) fs ]
|
||||||
|
case courseRegisterButton of
|
||||||
|
BtnCourseRegister -> runDB $ do
|
||||||
|
regOk <- (\app reg -> (,) <$> app <*> reg) <$> mkApplication <*> mkRegistration
|
||||||
|
case regOk of
|
||||||
|
Nothing -> transactionUndo
|
||||||
|
Just _ -> addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
|
||||||
|
BtnCourseDeregister -> runDB $ do
|
||||||
|
deleteApplications
|
||||||
|
deleteBy $ UniqueParticipant uid cid
|
||||||
|
audit $ TransactionCourseParticipantDeleted cid uid
|
||||||
|
|
||||||
|
examRegistrations <- E.select . E.from $ \(examRegistration `E.InnerJoin` exam) -> do
|
||||||
|
E.on $ examRegistration E.^. ExamRegistrationExam E.==. exam E.^. ExamId
|
||||||
|
E.where_ $ exam E.^. ExamCourse E.==. E.val cid
|
||||||
|
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
|
||||||
|
return examRegistration
|
||||||
|
forM_ examRegistrations $ \(Entity erId ExamRegistration{..}) -> do
|
||||||
|
delete erId
|
||||||
|
audit $ TransactionExamDeregister examRegistrationExam uid
|
||||||
|
|
||||||
|
examResults <- E.select . E.from $ \(examResult `E.InnerJoin` exam) -> do
|
||||||
|
E.on $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId
|
||||||
|
E.where_ $ exam E.^. ExamCourse E.==. E.val cid
|
||||||
|
E.&&. examResult E.^. ExamResultUser E.==. E.val uid
|
||||||
|
return examResult
|
||||||
|
forM_ examResults $ \(Entity erId ExamResult{..}) -> do
|
||||||
|
delete erId
|
||||||
|
audit $ TransactionExamResultDeleted examResultExam uid
|
||||||
|
|
||||||
|
addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk
|
||||||
|
BtnCourseApply -> runDB $ do
|
||||||
|
regOk <- mkApplication
|
||||||
|
case regOk of
|
||||||
|
Nothing -> transactionUndo
|
||||||
|
Just _ -> addMessageIconI Success IconApplyTrue MsgCourseApplyOk
|
||||||
|
BtnCourseRetractApplication -> runDB $ do
|
||||||
|
deleteApplications
|
||||||
|
addMessageIconI Info IconApplyFalse MsgCourseRetractApplyOk
|
||||||
redirect $ CourseR tid ssh csh CShowR
|
redirect $ CourseR tid ssh csh CShowR
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
module Handler.Course.Show
|
module Handler.Course.Show
|
||||||
( getCShowR
|
( getCShowR
|
||||||
|
, getCRegisterTemplateR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
@ -11,7 +12,7 @@ import qualified Database.Esqueleto.Utils as E
|
|||||||
import Database.Esqueleto.Utils.TH
|
import Database.Esqueleto.Utils.TH
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
import Data.Function ((&))
|
import Utils.Lens
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
@ -19,11 +20,15 @@ import qualified Database.Esqueleto as E
|
|||||||
|
|
||||||
import Handler.Course.Register
|
import Handler.Course.Register
|
||||||
|
|
||||||
|
import System.FilePath (addExtension)
|
||||||
|
|
||||||
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
|
|
||||||
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
getCShowR tid ssh csh = do
|
getCShowR tid ssh csh = do
|
||||||
mbAid <- maybeAuthId
|
mbAid <- maybeAuthId
|
||||||
(cid,course,schoolName,participants,registration,defSFid,lecturers,assistants,correctors,tutors,mAllocation) <- runDB . maybeT notFound $ do
|
(cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication) <- runDB . maybeT notFound $ do
|
||||||
[(E.Entity cid course, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
[(E.Entity cid course, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
||||||
<- lift . E.select . E.from $
|
<- lift . E.select . E.from $
|
||||||
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
||||||
@ -38,7 +43,6 @@ getCShowR tid ssh csh = do
|
|||||||
E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId
|
E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId
|
||||||
return ( E.countRows :: E.SqlExpr (E.Value Int))
|
return ( E.countRows :: E.SqlExpr (E.Value Int))
|
||||||
return (course,school E.^. SchoolName, numParticipants, participant)
|
return (course,school E.^. SchoolName, numParticipants, participant)
|
||||||
defSFid <- ifMaybeM mbAid Nothing $ \uid -> lift $ selectFirst [StudyFeaturesUser ==. uid, StudyFeaturesType ==. FieldPrimary, StudyFeaturesValid ==. True] [Desc StudyFeaturesUpdated, Desc StudyFeaturesDegree, Desc StudyFeaturesField] -- sorting by degree & field is an heuristic only, but this is okay for a default suggestion
|
|
||||||
staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do
|
staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do
|
||||||
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
|
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
|
||||||
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
||||||
@ -66,19 +70,27 @@ getCShowR tid ssh csh = do
|
|||||||
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
|
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
|
||||||
E.limit 1
|
E.limit 1
|
||||||
return allocation
|
return allocation
|
||||||
return (cid,course,schoolName,participants,registration,entityKey <$> defSFid,lecturers,assistants,correctors,tutors,mAllocation)
|
hasApplicationTemplate <- lift . E.selectExists . E.from $ \courseAppInstructionFile ->
|
||||||
|
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid
|
||||||
|
mApplication <- lift . fmap (listToMaybe =<<) . for mbAid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] []
|
||||||
|
return (cid,course,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication)
|
||||||
|
|
||||||
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
|
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
|
||||||
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
|
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
|
||||||
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
|
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
|
||||||
mRegAt <- traverse (formatTime SelFormatDateTime) $ courseParticipantRegistration <$> registration
|
mRegAt <- traverse (formatTime SelFormatDateTime) $ courseParticipantRegistration <$> registration
|
||||||
(regWidget, regEnctype) <- generateFormPost $ courseRegisterForm mbAid registration defSFid $ courseRegisterSecret course
|
regForm <- if
|
||||||
let regForm = wrapForm regWidget def
|
| is _Just mbAid -> do
|
||||||
{ formAction = Just . SomeRoute $ CourseR tid ssh csh CRegisterR
|
(courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course)
|
||||||
, formEncoding = regEnctype
|
(regWidget, regEnctype) <- generateFormPost $ renderAForm FormStandard courseRegisterForm'
|
||||||
, formSubmit = FormNoSubmit
|
return $ wrapForm' regButton regWidget def
|
||||||
}
|
{ formAction = Just . SomeRoute $ CourseR tid ssh csh CRegisterR
|
||||||
registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid ssh csh CRegisterR) True
|
, formEncoding = regEnctype
|
||||||
|
, formSubmit = FormSubmit
|
||||||
|
}
|
||||||
|
| otherwise
|
||||||
|
-> return . modal $(widgetFile "course/login-to-register") . Left . SomeRoute $ AuthR LoginR
|
||||||
|
registrationOpen <- hasWriteAccessTo $ CourseR tid ssh csh CRegisterR
|
||||||
|
|
||||||
let
|
let
|
||||||
tutorialDBTable = DBTable{..}
|
tutorialDBTable = DBTable{..}
|
||||||
@ -224,3 +236,15 @@ getCShowR tid ssh csh = do
|
|||||||
siteLayout (toWgt $ courseName course) $ do
|
siteLayout (toWgt $ courseName course) $ do
|
||||||
setTitleI $ prependCourseTitle tid ssh csh (""::Text)
|
setTitleI $ prependCourseTitle tid ssh csh (""::Text)
|
||||||
$(widgetFile "course")
|
$(widgetFile "course")
|
||||||
|
|
||||||
|
getCRegisterTemplateR :: TermId -> SchoolId -> CourseShorthand -> Handler TypedContent
|
||||||
|
getCRegisterTemplateR tid ssh csh = do
|
||||||
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack). ap getMessageRender . pure $ MsgCourseApplicationTemplateArchiveName tid ssh csh
|
||||||
|
let source = (.| C.map entityVal) . E.selectSource . E.from $ \(file `E.InnerJoin` courseAppInstructionFile `E.InnerJoin` course) -> do
|
||||||
|
E.on $ course E.^. CourseId E.==. courseAppInstructionFile E.^. CourseAppInstructionFileCourse
|
||||||
|
E.on $ courseAppInstructionFile E.^. CourseAppInstructionFileFile E.==. file E.^. FileId
|
||||||
|
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||||
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
|
return file
|
||||||
|
serveSomeFiles archiveName source
|
||||||
|
|||||||
@ -95,7 +95,7 @@ postCUserR tid ssh csh uCId = do
|
|||||||
((regFieldRes, regFieldView), regFieldEnctype) <- runFormPost . identifyForm FIDcRegField $ \csrf ->
|
((regFieldRes, regFieldView), regFieldEnctype) <- runFormPost . identifyForm FIDcRegField $ \csrf ->
|
||||||
let currentField :: Maybe (Maybe StudyFeaturesId)
|
let currentField :: Maybe (Maybe StudyFeaturesId)
|
||||||
currentField = courseParticipantField . entityVal <$> mRegistration
|
currentField = courseParticipantField . entityVal <$> mRegistration
|
||||||
in over _2 ((toWidget csrf <>) . fvInput) <$> mreq (studyFeaturesPrimaryFieldFor True (maybeToList $ join currentField) $ Just uid) ("" & addAutosubmit) currentField
|
in over _2 ((toWidget csrf <>) . fvInput) <$> mreq (studyFeaturesFieldFor Nothing True (maybeToList $ join currentField) $ Just uid) ("" & addAutosubmit) currentField
|
||||||
|
|
||||||
let registrationFieldFrag :: Text
|
let registrationFieldFrag :: Text
|
||||||
registrationFieldFrag = "registration-field"
|
registrationFieldFrag = "registration-field"
|
||||||
@ -111,6 +111,7 @@ postCUserR tid ssh csh uCId = do
|
|||||||
formResult regFieldRes $ \courseParticipantField' -> do
|
formResult regFieldRes $ \courseParticipantField' -> do
|
||||||
runDB $ do
|
runDB $ do
|
||||||
update pId [ CourseParticipantField =. courseParticipantField' ]
|
update pId [ CourseParticipantField =. courseParticipantField' ]
|
||||||
|
audit $ TransactionCourseParticipantEdit cid uid
|
||||||
addMessageI Success MsgCourseStudyFeatureUpdated
|
addMessageI Success MsgCourseStudyFeatureUpdated
|
||||||
redirect $ currentRoute :#: registrationFieldFrag
|
redirect $ currentRoute :#: registrationFieldFrag
|
||||||
|
|
||||||
@ -140,17 +141,22 @@ postCUserR tid ssh csh uCId = do
|
|||||||
-> invalidArgs ["User not registered"]
|
-> invalidArgs ["User not registered"]
|
||||||
BtnCourseRegister -> do
|
BtnCourseRegister -> do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
let primaryField
|
let field
|
||||||
| [(Entity featId _, _, _)] <- filter (\(Entity _ StudyFeatures{..}, _, _) -> studyFeaturesType == FieldPrimary && studyFeaturesValid) studies
|
| [(Entity featId _, _, _)] <- filter (\(Entity _ StudyFeatures{..}, _, _) -> studyFeaturesValid) studies
|
||||||
= Just featId
|
= Just featId
|
||||||
| otherwise
|
| otherwise
|
||||||
= Nothing
|
= Nothing
|
||||||
pId <- runDB . insertUnique $ CourseParticipant cid uid now primaryField False
|
pId <- runDB $ do
|
||||||
|
pId <- insertUnique $ CourseParticipant cid uid now field False
|
||||||
|
when (is _Just pId) $
|
||||||
|
audit $ TransactionCourseParticipantEdit cid uid
|
||||||
|
return pId
|
||||||
case pId of
|
case pId of
|
||||||
Just _ -> do
|
Just _ -> do
|
||||||
addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
|
addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
|
||||||
redirect currentRoute
|
redirect currentRoute
|
||||||
Nothing -> invalidArgs ["User already registered"]
|
Nothing -> invalidArgs ["User already registered"]
|
||||||
|
_other -> fail "Invalid @regButton@"
|
||||||
|
|
||||||
mRegAt <- for (courseParticipantRegistration . entityVal <$> mRegistration) $ formatTime SelFormatDateTime
|
mRegAt <- for (courseParticipantRegistration . entityVal <$> mRegistration) $ formatTime SelFormatDateTime
|
||||||
|
|
||||||
|
|||||||
@ -251,10 +251,14 @@ postCUsersR tid ssh csh = do
|
|||||||
cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser]
|
cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser]
|
||||||
redirect (CourseR tid ssh csh CCommR, [(toPathPiece GetRecipient, toPathPiece cID) | cID <- cids])
|
redirect (CourseR tid ssh csh CCommR, [(toPathPiece GetRecipient, toPathPiece cID) | cID <- cids])
|
||||||
(CourseUserDeregister,selectedUsers) -> do
|
(CourseUserDeregister,selectedUsers) -> do
|
||||||
nrDel <- runDB $ deleteWhereCount
|
Sum nrDel <- fmap mconcat . runDB . forM (Set.toList selectedUsers) $ \uid -> do
|
||||||
[ CourseParticipantCourse ==. cid
|
nrDel <- deleteWhereCount
|
||||||
, CourseParticipantUser <-. Set.toList selectedUsers
|
[ CourseParticipantCourse ==. cid
|
||||||
]
|
, CourseParticipantUser ==. uid
|
||||||
|
]
|
||||||
|
unless (nrDel == 0) $
|
||||||
|
audit $ TransactionCourseParticipantDeleted cid uid
|
||||||
|
return $ Sum nrDel
|
||||||
addMessageI Success $ MsgCourseUsersDeregistered nrDel
|
addMessageI Success $ MsgCourseUsersDeregistered nrDel
|
||||||
redirect $ CourseR tid ssh csh CUsersR
|
redirect $ CourseR tid ssh csh CUsersR
|
||||||
let headingLong = [whamlet|_{MsgMenuCourseMembers} #{courseName course} #{tid}|]
|
let headingLong = [whamlet|_{MsgMenuCourseMembers} #{courseName course} #{tid}|]
|
||||||
|
|||||||
@ -149,6 +149,7 @@ postEAddUserR tid ssh csh examn = do
|
|||||||
, courseParticipantAllocated = False
|
, courseParticipantAllocated = False
|
||||||
, ..
|
, ..
|
||||||
}
|
}
|
||||||
|
lift . lift . audit $ TransactionCourseParticipantEdit cid uid
|
||||||
lift $ lift examRegister
|
lift $ lift examRegister
|
||||||
|
|
||||||
return $ case courseParticipantField of
|
return $ case courseParticipantField of
|
||||||
|
|||||||
@ -90,12 +90,13 @@ examRegistrationInvitationConfig = InvitationConfig{..}
|
|||||||
case (isRegistered, invDBExamRegistrationCourseRegister) of
|
case (isRegistered, invDBExamRegistrationCourseRegister) of
|
||||||
(False, False) -> permissionDeniedI MsgUnauthorizedParticipant
|
(False, False) -> permissionDeniedI MsgUnauthorizedParticipant
|
||||||
(False, True ) -> do
|
(False, True ) -> do
|
||||||
fieldRes <- wreq (studyFeaturesPrimaryFieldFor False [] $ Just uid) (fslI MsgCourseStudyFeature) Nothing
|
fieldRes <- wreq (studyFeaturesFieldFor Nothing False [] $ Just uid) (fslI MsgCourseStudyFeature) Nothing
|
||||||
return $ (JunctionExamRegistration invDBExamRegistrationOccurrence now, ) . Just <$> fieldRes
|
return $ (JunctionExamRegistration invDBExamRegistrationOccurrence now, ) . Just <$> fieldRes
|
||||||
(True , _ ) -> return $ pure (JunctionExamRegistration invDBExamRegistrationOccurrence now, Nothing)
|
(True , _ ) -> return $ pure (JunctionExamRegistration invDBExamRegistrationOccurrence now, Nothing)
|
||||||
invitationInsertHook (Entity eid Exam{..}) _ ExamRegistration{..} mField act = do
|
invitationInsertHook (Entity eid Exam{..}) _ ExamRegistration{..} mField act = do
|
||||||
whenIsJust mField $ \cpField ->
|
whenIsJust mField $ \cpField -> do
|
||||||
insert_ $ CourseParticipant examCourse examRegistrationUser examRegistrationTime cpField False
|
insert_ $ CourseParticipant examCourse examRegistrationUser examRegistrationTime cpField False
|
||||||
|
audit $ TransactionCourseParticipantEdit examCourse examRegistrationUser
|
||||||
|
|
||||||
let doAudit = audit $ TransactionExamRegister eid examRegistrationUser
|
let doAudit = audit $ TransactionExamRegister eid examRegistrationUser
|
||||||
act <* doAudit
|
act <* doAudit
|
||||||
|
|||||||
@ -443,6 +443,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
, courseParticipantField = examUserCsvActCourseField
|
, courseParticipantField = examUserCsvActCourseField
|
||||||
, courseParticipantAllocated = False
|
, courseParticipantAllocated = False
|
||||||
}
|
}
|
||||||
|
audit $ TransactionCourseParticipantEdit examCourse examUserCsvActUser
|
||||||
insert_ ExamRegistration
|
insert_ ExamRegistration
|
||||||
{ examRegistrationExam = eid
|
{ examRegistrationExam = eid
|
||||||
, examRegistrationUser = examUserCsvActUser
|
, examRegistrationUser = examUserCsvActUser
|
||||||
@ -461,8 +462,10 @@ postEUsersR tid ssh csh examn = do
|
|||||||
audit $ TransactionExamRegister eid examUserCsvActUser
|
audit $ TransactionExamRegister eid examUserCsvActUser
|
||||||
ExamUserCsvAssignOccurrenceData{..} ->
|
ExamUserCsvAssignOccurrenceData{..} ->
|
||||||
update examUserCsvActRegistration [ ExamRegistrationOccurrence =. examUserCsvActOccurrence ]
|
update examUserCsvActRegistration [ ExamRegistrationOccurrence =. examUserCsvActOccurrence ]
|
||||||
ExamUserCsvSetCourseFieldData{..} ->
|
ExamUserCsvSetCourseFieldData{..} -> do
|
||||||
update examUserCsvActCourseParticipant [ CourseParticipantField =. examUserCsvActCourseField ]
|
update examUserCsvActCourseParticipant [ CourseParticipantField =. examUserCsvActCourseField ]
|
||||||
|
CourseParticipant{..} <- getJust examUserCsvActCourseParticipant
|
||||||
|
audit $ TransactionCourseParticipantEdit examCourse courseParticipantUser
|
||||||
ExamUserCsvSetResultData{..} -> case examUserCsvActExamResult of
|
ExamUserCsvSetResultData{..} -> case examUserCsvActExamResult of
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
deleteBy $ UniqueExamResult eid examUserCsvActUser
|
deleteBy $ UniqueExamResult eid examUserCsvActUser
|
||||||
@ -481,6 +484,10 @@ postEUsersR tid ssh csh examn = do
|
|||||||
ExamRegistration{examRegistrationUser} <- getJust examUserCsvActRegistration
|
ExamRegistration{examRegistrationUser} <- getJust examUserCsvActRegistration
|
||||||
audit $ TransactionExamDeregister eid examRegistrationUser
|
audit $ TransactionExamDeregister eid examRegistrationUser
|
||||||
delete examUserCsvActRegistration
|
delete examUserCsvActRegistration
|
||||||
|
result <- getBy $ UniqueExamResult eid examRegistrationUser
|
||||||
|
forM_ result $ \(Entity erId _) -> do
|
||||||
|
delete erId
|
||||||
|
audit $ TransactionExamResultDeleted eid examRegistrationUser
|
||||||
ExamUserCsvSetCourseNoteData{ examUserCsvActCourseNote = Nothing, .. } -> do
|
ExamUserCsvSetCourseNoteData{ examUserCsvActCourseNote = Nothing, .. } -> do
|
||||||
noteId <- getKeyBy $ UniqueCourseUserNote examUserCsvActUser examCourse
|
noteId <- getKeyBy $ UniqueCourseUserNote examUserCsvActUser examCourse
|
||||||
whenIsJust noteId $ \nid -> do
|
whenIsJust noteId $ \nid -> do
|
||||||
@ -631,7 +638,6 @@ postEUsersR tid ssh csh examn = do
|
|||||||
, (studyFeatures E.^. StudyFeaturesSemester E.==.) . E.val <$> csvEUserSemester
|
, (studyFeatures E.^. StudyFeaturesSemester E.==.) . E.val <$> csvEUserSemester
|
||||||
]
|
]
|
||||||
E.where_ $ studyFeatures E.^. StudyFeaturesUser E.==. E.val uid
|
E.where_ $ studyFeatures E.^. StudyFeaturesUser E.==. E.val uid
|
||||||
E.&&. studyFeatures E.^. StudyFeaturesType E.==. E.val FieldPrimary
|
|
||||||
E.&&. studyFeatures E.^. StudyFeaturesValid E.==. E.val True
|
E.&&. studyFeatures E.^. StudyFeaturesValid E.==. E.val True
|
||||||
E.limit 2
|
E.limit 2
|
||||||
return $ studyFeatures E.^. StudyFeaturesId
|
return $ studyFeatures E.^. StudyFeaturesId
|
||||||
|
|||||||
@ -33,9 +33,10 @@ getInfoR :: Handler Html
|
|||||||
getInfoR = -- do
|
getInfoR = -- do
|
||||||
siteLayoutMsg MsgInfoHeading $ do
|
siteLayoutMsg MsgInfoHeading $ do
|
||||||
setTitleI MsgInfoHeading
|
setTitleI MsgInfoHeading
|
||||||
let features = $(i18nWidgetFile "featureList")
|
let features = $(i18nWidgetFile "featureList")
|
||||||
changeLog = $(i18nWidgetFile "changelog")
|
changeLog = $(i18nWidgetFile "changelog")
|
||||||
knownBugs = $(i18nWidgetFile "knownBugs")
|
knownBugs = $(i18nWidgetFile "knownBugs")
|
||||||
|
implementation = $(i18nWidgetFile "implementation")
|
||||||
gitInfo :: Text
|
gitInfo :: Text
|
||||||
gitInfo = $gitDescribe <> " (" <> $gitCommitDate <> ")"
|
gitInfo = $gitDescribe <> " (" <> $gitCommitDate <> ")"
|
||||||
$(widgetFile "versionHistory")
|
$(widgetFile "versionHistory")
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import Handler.Utils.Table.Columns
|
|||||||
|
|
||||||
import Control.Monad.Writer (MonadWriter(..), execWriterT)
|
import Control.Monad.Writer (MonadWriter(..), execWriterT)
|
||||||
|
|
||||||
|
import System.FilePath (addExtension)
|
||||||
|
|
||||||
|
|
||||||
data MaterialForm = MaterialForm
|
data MaterialForm = MaterialForm
|
||||||
{ mfName :: MaterialName
|
{ mfName :: MaterialName
|
||||||
@ -358,16 +360,19 @@ postMDelR tid ssh csh mnm = do
|
|||||||
|
|
||||||
-- | Serve all material-files
|
-- | Serve all material-files
|
||||||
getMArchiveR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> Handler TypedContent
|
getMArchiveR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> Handler TypedContent
|
||||||
getMArchiveR tid ssh csh mnm = serveSomeFiles archivename getMatQuery
|
getMArchiveR tid ssh csh mnm = do
|
||||||
where
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack). ap getMessageRender . pure $ MsgMaterialArchiveName tid ssh csh mnm
|
||||||
archivename = unpack (termToText (unTermKey tid) <> "-" <> toPathPiece (unSchoolKey ssh <> "-" <> csh <> "-" <> mnm)) <.> "zip"
|
|
||||||
getMatQuery = (.| C.map entityVal) . E.selectSource . E.from $
|
let getMatQuery = (.| C.map entityVal) . E.selectSource . E.from $
|
||||||
\(course `E.InnerJoin` material `E.InnerJoin` materialFile `E.InnerJoin` file) -> do
|
\(course `E.InnerJoin` material `E.InnerJoin` materialFile `E.InnerJoin` file) -> do
|
||||||
E.on $ file E.^. FileId E.==. materialFile E.^. MaterialFileFile
|
E.on $ file E.^. FileId E.==. materialFile E.^. MaterialFileFile
|
||||||
E.on $ material E.^. MaterialId E.==. materialFile E.^. MaterialFileMaterial
|
E.on $ material E.^. MaterialId E.==. materialFile E.^. MaterialFileMaterial
|
||||||
E.on $ material E.^. MaterialCourse E.==. course E.^. CourseId
|
E.on $ material E.^. MaterialCourse E.==. course E.^. CourseId
|
||||||
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
E.&&. material E.^. MaterialName E.==. E.val mnm
|
E.&&. material E.^. MaterialName E.==. E.val mnm
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
serveSomeFiles archiveName getMatQuery
|
||||||
|
|
||||||
|
|||||||
@ -274,7 +274,7 @@ makeProfileData (Entity uid User{..}) = do
|
|||||||
submissionTable <- mkSubmissionTable uid -- Tabelle mit allen Abgaben und Abgabe-Gruppen
|
submissionTable <- mkSubmissionTable uid -- Tabelle mit allen Abgaben und Abgabe-Gruppen
|
||||||
submissionGroupTable <- mkSubmissionGroupTable uid -- Tabelle mit allen Abgabegruppen
|
submissionGroupTable <- mkSubmissionGroupTable uid -- Tabelle mit allen Abgabegruppen
|
||||||
correctionsTable <- mkCorrectionsTable uid -- Tabelle mit allen Korrektor-Aufgaben
|
correctionsTable <- mkCorrectionsTable uid -- Tabelle mit allen Korrektor-Aufgaben
|
||||||
let examTable = [whamlet|Klausuren werden momentan leider noch nicht unterstützt.|]
|
let examTable = [whamlet|Prüfungen werden hier momentan leider noch nicht unterstützt.|]
|
||||||
let ownTutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
let ownTutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
||||||
let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
||||||
lastLogin <- traverse (formatTime SelFormatDateTime) userLastAuthentication
|
lastLogin <- traverse (formatTime SelFormatDateTime) userLastAuthentication
|
||||||
|
|||||||
@ -60,6 +60,8 @@ import Utils.Sql
|
|||||||
import Data.Aeson hiding (Result(..))
|
import Data.Aeson hiding (Result(..))
|
||||||
import Text.Hamlet (ihamlet)
|
import Text.Hamlet (ihamlet)
|
||||||
|
|
||||||
|
import System.FilePath (addExtension)
|
||||||
|
|
||||||
|
|
||||||
{-
|
{-
|
||||||
* Implement Handlers
|
* Implement Handlers
|
||||||
@ -439,9 +441,8 @@ getSShowR tid ssh csh shn = do
|
|||||||
|
|
||||||
getSArchiveR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler TypedContent
|
getSArchiveR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler TypedContent
|
||||||
getSArchiveR tid ssh csh shn = do
|
getSArchiveR tid ssh csh shn = do
|
||||||
MsgRenderer mr <- getMsgRenderer
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack). ap getMessageRender . pure $ MsgSheetArchiveName tid ssh csh shn
|
||||||
let archiveName = (unpack . stripAll $ mr (prependCourseTitle tid ssh csh $ SomeMessage shn)) <.> "zip"
|
let sftArchive = CSheetR tid ssh csh shn . SZipR -- used to check access to SheetFileTypes
|
||||||
sftArchive = CSheetR tid ssh csh shn . SZipR -- used to check access to SheetFileTypes
|
|
||||||
allowedSFTs <- filterM (hasReadAccessTo . sftArchive) [minBound..maxBound]
|
allowedSFTs <- filterM (hasReadAccessTo . sftArchive) [minBound..maxBound]
|
||||||
serveZipArchive archiveName $ sheetFilesSFTsQuery tid ssh csh shn allowedSFTs .| C.map entityVal
|
serveZipArchive archiveName $ sheetFilesSFTsQuery tid ssh csh shn allowedSFTs .| C.map entityVal
|
||||||
|
|
||||||
@ -476,8 +477,8 @@ getSFileR tid ssh csh shn sft file = serveOneFile $ sheetFileQuery tid ssh csh s
|
|||||||
|
|
||||||
getSZipR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> Handler TypedContent
|
getSZipR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> Handler TypedContent
|
||||||
getSZipR tid ssh csh shn sft = do
|
getSZipR tid ssh csh shn sft = do
|
||||||
MsgRenderer mr <- getMsgRenderer
|
sft' <- ap getMessageRender $ pure sft
|
||||||
let archiveName = (unpack . stripAll $ mr (prependCourseTitle tid ssh csh $ SomeMessage shn)) <> "_" <> (unpack $ toPathPiece sft) <.> "zip"
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack). ap getMessageRender . pure $ MsgSheetTypeArchiveName tid ssh csh shn sft'
|
||||||
serveSomeFiles archiveName $ sheetFilesAllQuery tid ssh csh shn sft .| C.map entityVal
|
serveSomeFiles archiveName $ sheetFilesAllQuery tid ssh csh shn sft .| C.map entityVal
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,8 @@ import Text.Hamlet (ihamlet)
|
|||||||
-- import qualified Yesod.Colonnade as Yesod
|
-- import qualified Yesod.Colonnade as Yesod
|
||||||
-- import qualified Text.Blaze.Html5.Attributes as HA
|
-- import qualified Text.Blaze.Html5.Attributes as HA
|
||||||
|
|
||||||
|
import System.FilePath (addExtension)
|
||||||
|
|
||||||
-- DEPRECATED: We always show all edits!
|
-- DEPRECATED: We always show all edits!
|
||||||
-- numberOfSubmissionEditDates :: Int64
|
-- numberOfSubmissionEditDates :: Int64
|
||||||
-- numberOfSubmissionEditDates = 3 -- for debugging only, should be 1 in production.
|
-- numberOfSubmissionEditDates = 3 -- for debugging only, should be 1 in production.
|
||||||
@ -124,26 +126,9 @@ submissionUserInvitationConfig = InvitationConfig{..}
|
|||||||
|
|
||||||
makeSubmissionForm :: CourseId -> Maybe SubmissionId -> UploadMode -> SheetGroup -> Bool -> Set (Either UserEmail UserId) -> Form (Maybe (Source Handler File), Set (Either UserEmail UserId))
|
makeSubmissionForm :: CourseId -> Maybe SubmissionId -> UploadMode -> SheetGroup -> Bool -> Set (Either UserEmail UserId) -> Form (Maybe (Source Handler File), Set (Either UserEmail UserId))
|
||||||
makeSubmissionForm cid msmid uploadMode grouping isLecturer prefillUsers = identifyForm FIDsubmission . renderAForm FormStandard $ (,)
|
makeSubmissionForm cid msmid uploadMode grouping isLecturer prefillUsers = identifyForm FIDsubmission . renderAForm FormStandard $ (,)
|
||||||
<$> fileUploadForm
|
<$> fileUploadForm (is _Just msmid) (fslI . bool MsgSubmissionFile MsgSubmissionArchive) uploadMode
|
||||||
<*> wFormToAForm submittorsForm
|
<*> wFormToAForm submittorsForm
|
||||||
where
|
where
|
||||||
fileUploadForm = case uploadMode of
|
|
||||||
NoUpload
|
|
||||||
-> pure Nothing
|
|
||||||
UploadAny{..}
|
|
||||||
-> (bool (\f fs _ -> Just <$> areq f fs Nothing) aopt $ isJust msmid) (zipFileField unpackZips extensionRestriction) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing
|
|
||||||
UploadSpecific{..}
|
|
||||||
-> mergeFileSources <$> sequenceA (map specificFileForm . Set.toList $ toNullable specificFiles)
|
|
||||||
|
|
||||||
specificFileForm :: UploadSpecificFile -> AForm Handler (Maybe (Source Handler File))
|
|
||||||
specificFileForm spec@UploadSpecificFile{..}
|
|
||||||
= bool (\f fs d -> aopt f fs $ fmap Just d) (\f fs d -> Just <$> areq f fs d) specificFileRequired (specificFileField spec) (fsl specificFileLabel) Nothing
|
|
||||||
|
|
||||||
mergeFileSources :: [Maybe (Source Handler File)] -> Maybe (Source Handler File)
|
|
||||||
mergeFileSources (catMaybes -> sources) = case sources of
|
|
||||||
[] -> Nothing
|
|
||||||
fs -> Just $ sequence_ fs
|
|
||||||
|
|
||||||
miCell' :: Markup -> Either UserEmail UserId -> Widget
|
miCell' :: Markup -> Either UserEmail UserId -> Widget
|
||||||
miCell' csrf (Left email) = $(widgetFile "widgets/massinput/submissionUsers/cellInvitation")
|
miCell' csrf (Left email) = $(widgetFile "widgets/massinput/submissionUsers/cellInvitation")
|
||||||
miCell' csrf (Right uid) = do
|
miCell' csrf (Right uid) = do
|
||||||
@ -574,11 +559,10 @@ getSubArchiveR tid ssh csh shn cID sfType = do
|
|||||||
when (sfType == SubmissionCorrected) $
|
when (sfType == SubmissionCorrected) $
|
||||||
guardAuthResult =<< evalAccess (CSubmissionR tid ssh csh shn cID CorrectionR) False
|
guardAuthResult =<< evalAccess (CSubmissionR tid ssh csh shn cID CorrectionR) False
|
||||||
|
|
||||||
let filename
|
sfType' <- ap getMessageRender $ pure sfType
|
||||||
| SubmissionOriginal <- sfType = toPathPiece cID <> "-" <> toPathPiece sfType
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgSubmissionTypeArchiveName tid ssh csh shn cID sfType'
|
||||||
| otherwise = toPathPiece cID
|
|
||||||
|
|
||||||
source = do
|
let source = do
|
||||||
submissionID <- lift $ submissionMatchesSheet tid ssh csh shn cID
|
submissionID <- lift $ submissionMatchesSheet tid ssh csh shn cID
|
||||||
rating <- lift $ getRating submissionID
|
rating <- lift $ getRating submissionID
|
||||||
|
|
||||||
@ -593,7 +577,7 @@ getSubArchiveR tid ssh csh shn cID sfType = do
|
|||||||
when (sfType == SubmissionCorrected) $
|
when (sfType == SubmissionCorrected) $
|
||||||
maybe (return ()) (yieldM . ratingFile cID) rating
|
maybe (return ()) (yieldM . ratingFile cID) rating
|
||||||
|
|
||||||
serveSomeFiles (unpack filename <.> "zip") source
|
serveSomeFiles archiveName source
|
||||||
|
|
||||||
getSubDelR, postSubDelR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> Handler Html
|
getSubDelR, postSubDelR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> Handler Html
|
||||||
getSubDelR = postSubDelR
|
getSubDelR = postSubDelR
|
||||||
|
|||||||
@ -75,7 +75,7 @@ serveSomeFiles archiveName source = do
|
|||||||
[file] -> sendThisFile file
|
[file] -> sendThisFile file
|
||||||
_moreFiles -> do
|
_moreFiles -> do
|
||||||
setContentDisposition' $ Just archiveName
|
setContentDisposition' $ Just archiveName
|
||||||
respondSourceDB "application/zip" $ do
|
respondSourceDB typeZip $ do
|
||||||
let zipComment = T.encodeUtf8 $ pack archiveName
|
let zipComment = T.encodeUtf8 $ pack archiveName
|
||||||
source .| produceZip ZipInfo{..} .| Conduit.map toFlushBuilder
|
source .| produceZip ZipInfo{..} .| Conduit.map toFlushBuilder
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ serveZipArchive archiveName source = do
|
|||||||
[] -> notFound
|
[] -> notFound
|
||||||
_moreFiles -> do
|
_moreFiles -> do
|
||||||
setContentDisposition' $ Just archiveName
|
setContentDisposition' $ Just archiveName
|
||||||
respondSourceDB "application/zip" $ do
|
respondSourceDB typeZip $ do
|
||||||
let zipComment = T.encodeUtf8 $ pack archiveName
|
let zipComment = T.encodeUtf8 $ pack archiveName
|
||||||
source .| produceZip ZipInfo{..} .| Conduit.map toFlushBuilder
|
source .| produceZip ZipInfo{..} .| Conduit.map toFlushBuilder
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import qualified Data.Map as Map
|
|||||||
import qualified Data.Vector as Vector
|
import qualified Data.Vector as Vector
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
import qualified Data.ByteString as BS
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
|
|
||||||
import qualified Data.Attoparsec.ByteString.Lazy as A
|
import qualified Data.Attoparsec.ByteString.Lazy as A
|
||||||
@ -43,8 +42,8 @@ instance Exception CsvParseError
|
|||||||
|
|
||||||
|
|
||||||
typeCsv, typeCsv' :: ContentType
|
typeCsv, typeCsv' :: ContentType
|
||||||
typeCsv = "text/csv"
|
typeCsv = simpleContentType typeCsv'
|
||||||
typeCsv' = BS.intercalate "; " [typeCsv, "charset=UTF-8", "header=present"]
|
typeCsv' = "text/csv; charset=UTF-8; header=present"
|
||||||
|
|
||||||
extensionCsv :: Extension
|
extensionCsv :: Extension
|
||||||
extensionCsv = fromMaybe "csv" $ listToMaybe [ ext | (ext, mime) <- Map.toList mimeMap, mime == typeCsv ]
|
extensionCsv = fromMaybe "csv" $ listToMaybe [ ext | (ext, mime) <- Map.toList mimeMap, mime == typeCsv ]
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import Handler.Utils.Zip
|
|||||||
import qualified Data.Conduit.List as C
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
import Data.Set (Set)
|
import Data.Set (Set)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
@ -312,6 +313,9 @@ termsAllowedField = selectField $ do
|
|||||||
| otherwise = [TermActive ==. True]
|
| otherwise = [TermActive ==. True]
|
||||||
optionsPersistKey termFilter [Desc TermStart] termName
|
optionsPersistKey termFilter [Desc TermStart] termName
|
||||||
|
|
||||||
|
termField :: Field Handler TermId
|
||||||
|
termField = selectField $ optionsPersistKey [] [Asc TermName] termName
|
||||||
|
|
||||||
termsSetField :: [TermId] -> Field Handler TermId
|
termsSetField :: [TermId] -> Field Handler TermId
|
||||||
termsSetField tids = selectField $ optionsPersistKey [TermName <-. (unTermKey <$> tids)] [Desc TermStart] termName
|
termsSetField tids = selectField $ optionsPersistKey [TermName <-. (unTermKey <$> tids)] [Desc TermStart] termName
|
||||||
-- termsSetField tids = selectFieldList [(unTermKey t, t)| t <- tids ]
|
-- termsSetField tids = selectFieldList [(unTermKey t, t)| t <- tids ]
|
||||||
@ -334,25 +338,35 @@ schoolFieldEnt = selectField $ optionsPersist [] [Asc SchoolName] schoolName
|
|||||||
schoolFieldFor :: [SchoolId] -> Field Handler SchoolId
|
schoolFieldFor :: [SchoolId] -> Field Handler SchoolId
|
||||||
schoolFieldFor userSchools = selectField $ optionsPersistKey [SchoolShorthand <-. map unSchoolKey userSchools] [Asc SchoolName] schoolName
|
schoolFieldFor userSchools = selectField $ optionsPersistKey [SchoolShorthand <-. map unSchoolKey userSchools] [Asc SchoolName] schoolName
|
||||||
|
|
||||||
-- | Select one of the user's primary active courses, or from a given list of StudyFeatures (regardless of user)
|
|
||||||
|
-- | Select one of the user's primary active study features, or from a given list of StudyFeatures (regardless of user)
|
||||||
studyFeaturesPrimaryFieldFor :: Bool -- ^ Allow user to select `Nothing` (only applies if set of options is nonempty)?
|
studyFeaturesPrimaryFieldFor :: Bool -- ^ Allow user to select `Nothing` (only applies if set of options is nonempty)?
|
||||||
-> [StudyFeaturesId] -> Maybe UserId -> Field Handler (Maybe StudyFeaturesId)
|
-> [StudyFeaturesId] -> Maybe UserId -> Field Handler (Maybe StudyFeaturesId)
|
||||||
studyFeaturesPrimaryFieldFor isOptional oldFeatures mbuid = selectField $ do
|
{-# DEPRECATED studyFeaturesPrimaryFieldFor "Use studyFeaturesFieldFor" #-}
|
||||||
|
studyFeaturesPrimaryFieldFor = studyFeaturesFieldFor . Just $ Set.singleton FieldPrimary
|
||||||
|
|
||||||
|
-- | Select one of the user's active study features, or from a given list of StudyFeatures (regardless of user)
|
||||||
|
studyFeaturesFieldFor :: Maybe (Set StudyFieldType) -- ^ Optionally restrict fields to only given types
|
||||||
|
-> Bool -- ^ Allow user to select `Nothing` (only applies if set of options is nonempty)?
|
||||||
|
-> [StudyFeaturesId] -> Maybe UserId -> Field Handler (Maybe StudyFeaturesId)
|
||||||
|
studyFeaturesFieldFor mRestr isOptional oldFeatures mbuid = selectField $ do
|
||||||
-- we need a join, so we cannot just use optionsPersistCryptoId
|
-- we need a join, so we cannot just use optionsPersistCryptoId
|
||||||
rawOptions <- runDB $ E.select $ E.from $ \(feature `E.InnerJoin` degree `E.InnerJoin` field) -> do
|
rawOptions <- runDB $ E.select $ E.from $ \(feature `E.InnerJoin` degree `E.InnerJoin` field) -> do
|
||||||
E.on $ feature E.^. StudyFeaturesField E.==. field E.^. StudyTermsId
|
E.on $ feature E.^. StudyFeaturesField E.==. field E.^. StudyTermsId
|
||||||
E.on $ feature E.^. StudyFeaturesDegree E.==. degree E.^. StudyDegreeId
|
E.on $ feature E.^. StudyFeaturesDegree E.==. degree E.^. StudyDegreeId
|
||||||
E.where_ $ ((feature E.^. StudyFeaturesId) `E.in_` E.valList oldFeatures)
|
E.where_ $ ((feature E.^. StudyFeaturesId) `E.in_` E.valList oldFeatures)
|
||||||
E.||. isPrimaryActiveUserStudyFeature feature
|
E.||. (isActiveUserStudyFeature feature E.&&. isCorrectType feature)
|
||||||
return (feature E.^. StudyFeaturesId, degree, field)
|
return (feature E.^. StudyFeaturesId, degree, field)
|
||||||
MsgRenderer mr <- getMsgRenderer
|
MsgRenderer mr <- getMsgRenderer
|
||||||
mkOptionList . nonEmptyOptions (mr MsgNoPrimaryStudyField) <$> mapM (procOptions mr) rawOptions
|
mkOptionList . nonEmptyOptions (mr MsgNoStudyField) <$> mapM (procOptions mr) rawOptions
|
||||||
where
|
where
|
||||||
isPrimaryActiveUserStudyFeature feature = case mbuid of
|
isActiveUserStudyFeature feature = case mbuid of
|
||||||
Nothing -> E.val False
|
Nothing -> E.false
|
||||||
(Just uid) -> feature E.^. StudyFeaturesUser E.==. E.val uid
|
Just uid -> feature E.^. StudyFeaturesUser E.==. E.val uid
|
||||||
E.&&. feature E.^. StudyFeaturesValid E.==. E.val True
|
E.&&. feature E.^. StudyFeaturesValid
|
||||||
E.&&. feature E.^. StudyFeaturesType E.==. E.val FieldPrimary
|
isCorrectType feature = case mRestr of
|
||||||
|
Nothing -> E.true
|
||||||
|
Just restr -> feature E.^. StudyFeaturesType `E.in_` E.valList (Set.toList restr)
|
||||||
|
|
||||||
procOptions :: (StudyDegreeTerm -> Text) -> (E.Value StudyFeaturesId, Entity StudyDegree, Entity StudyTerms) -> Handler (Option (Maybe StudyFeaturesId))
|
procOptions :: (StudyDegreeTerm -> Text) -> (E.Value StudyFeaturesId, Entity StudyDegree, Entity StudyTerms) -> Handler (Option (Maybe StudyFeaturesId))
|
||||||
procOptions mr (E.Value sfid, Entity _dgid sdegree, Entity _stid sterm) = do
|
procOptions mr (E.Value sfid, Entity _dgid sdegree, Entity _stid sterm) = do
|
||||||
@ -372,7 +386,7 @@ studyFeaturesPrimaryFieldFor isOptional oldFeatures mbuid = selectField $ do
|
|||||||
nullOption = Option
|
nullOption = Option
|
||||||
{ optionDisplay = emptyOpt
|
{ optionDisplay = emptyOpt
|
||||||
, optionInternalValue = Nothing
|
, optionInternalValue = Nothing
|
||||||
, optionExternalValue = "NoPrimaryStudyField"
|
, optionExternalValue = "NoStudyField"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -384,7 +398,7 @@ uploadModeForm prev = multiActionA actions (fslI MsgSheetUploadMode) (classifyUp
|
|||||||
[ ( UploadModeNone, pure NoUpload)
|
[ ( UploadModeNone, pure NoUpload)
|
||||||
, ( UploadModeAny
|
, ( UploadModeAny
|
||||||
, UploadAny
|
, UploadAny
|
||||||
<$> apreq checkBoxField (fslI MsgUploadModeUnpackZips & setTooltip MsgUploadModeUnpackZipsTip) (prev ^? _Just . _unpackZips)
|
<$> (fromMaybe False <$> aopt checkBoxField (fslI MsgUploadModeUnpackZips & setTooltip MsgUploadModeUnpackZipsTip) (Just $ prev ^? _Just . _unpackZips))
|
||||||
<*> aopt extensionRestrictionField (fslI MsgUploadModeExtensionRestriction & setTooltip MsgUploadModeExtensionRestrictionTip) ((prev ^? _Just . _extensionRestriction) <|> fmap Just defaultExtensionRestriction)
|
<*> aopt extensionRestrictionField (fslI MsgUploadModeExtensionRestriction & setTooltip MsgUploadModeExtensionRestrictionTip) ((prev ^? _Just . _extensionRestriction) <|> fmap Just defaultExtensionRestriction)
|
||||||
)
|
)
|
||||||
, ( UploadModeSpecific
|
, ( UploadModeSpecific
|
||||||
@ -455,6 +469,8 @@ uploadModeForm prev = multiActionA actions (fslI MsgSheetUploadMode) (classifyUp
|
|||||||
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/uploadSpecificFiles/layout")
|
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/uploadSpecificFiles/layout")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
submissionModeForm :: Maybe SubmissionMode -> AForm Handler SubmissionMode
|
submissionModeForm :: Maybe SubmissionMode -> AForm Handler SubmissionMode
|
||||||
submissionModeForm prev = multiActionA actions (fslI MsgSheetSubmissionMode) $ classifySubmissionMode <$> prev
|
submissionModeForm prev = multiActionA actions (fslI MsgSheetSubmissionMode) $ classifySubmissionMode <$> prev
|
||||||
where
|
where
|
||||||
@ -645,12 +661,37 @@ zipFileField doUnpack permittedExtensions = Field{..}
|
|||||||
| otherwise = return . Left $ SomeMessage MsgOnlyUploadOneFile
|
| otherwise = return . Left $ SomeMessage MsgOnlyUploadOneFile
|
||||||
fieldView fieldId fieldName attrs _ req = $(widgetFile "widgets/zipFileField")
|
fieldView fieldId fieldName attrs _ req = $(widgetFile "widgets/zipFileField")
|
||||||
|
|
||||||
zipExtensions = mimeExtensions "application/zip"
|
zipExtensions = mimeExtensions typeZip
|
||||||
|
|
||||||
acceptRestricted = isJust permittedExtensions
|
acceptRestricted = isJust permittedExtensions
|
||||||
accept = Text.intercalate "," . map ("." <>) $ bool [] (Set.toList zipExtensions) doUnpack ++ toListOf (_Just . re _nullable . folded) permittedExtensions
|
accept = Text.intercalate "," . map ("." <>) $ bool [] (Set.toList zipExtensions) doUnpack ++ toListOf (_Just . re _nullable . folded) permittedExtensions
|
||||||
|
|
||||||
multiFileField :: Handler (Set FileId) -> Field Handler (Source Handler (Either FileId File))
|
fileUploadForm :: Bool -- ^ Required?
|
||||||
|
-> (Bool -> FieldSettings UniWorX) -- ^ given @unpackZips@ generate `FieldSettings` in the case of `UploadAny`
|
||||||
|
-> UploadMode -> AForm Handler (Maybe (Source Handler File))
|
||||||
|
fileUploadForm isReq mkFs = \case
|
||||||
|
NoUpload
|
||||||
|
-> pure Nothing
|
||||||
|
UploadAny{..}
|
||||||
|
-> (bool (\f fs _ -> Just <$> areq f fs Nothing) aopt isReq) (zipFileField unpackZips extensionRestriction) (mkFs unpackZips) Nothing
|
||||||
|
UploadSpecific{..}
|
||||||
|
-> mergeFileSources <$> sequenceA (map specificFileForm . Set.toList $ toNullable specificFiles)
|
||||||
|
where
|
||||||
|
specificFileForm :: UploadSpecificFile -> AForm Handler (Maybe (Source Handler File))
|
||||||
|
specificFileForm spec@UploadSpecificFile{..}
|
||||||
|
= bool (\f fs d -> aopt f fs $ fmap Just d) (\f fs d -> Just <$> areq f fs d) specificFileRequired (specificFileField spec) (fsl specificFileLabel) Nothing
|
||||||
|
|
||||||
|
mergeFileSources :: [Maybe (Source Handler File)] -> Maybe (Source Handler File)
|
||||||
|
mergeFileSources (catMaybes -> sources) = case sources of
|
||||||
|
[] -> Nothing
|
||||||
|
fs -> Just $ sequence_ fs
|
||||||
|
|
||||||
|
multiFileField' :: Source Handler (Either FileId File) -- ^ Permitted files in same format as produced by `multiFileField`
|
||||||
|
-> Field Handler (Source Handler (Either FileId File))
|
||||||
|
multiFileField' permittedFiles = multiFileField . runConduit $ permittedFiles .| C.mapMaybe (preview _Left) .| C.foldMap Set.singleton
|
||||||
|
|
||||||
|
multiFileField :: Handler (Set FileId) -- ^ Set of files that may be submitted by id-reference
|
||||||
|
-> Field Handler (Source Handler (Either FileId File))
|
||||||
multiFileField permittedFiles' = Field{..}
|
multiFileField permittedFiles' = Field{..}
|
||||||
where
|
where
|
||||||
fieldEnctype = Multipart
|
fieldEnctype = Multipart
|
||||||
|
|||||||
@ -25,12 +25,11 @@ gradeSummaryWidget title sts =
|
|||||||
hasMarkedPasses = positiveSum $ numMarkedPasses sumSummaries
|
hasMarkedPasses = positiveSum $ numMarkedPasses sumSummaries
|
||||||
hasPoints = positiveSum $ numSheetsPoints sumSummaries
|
hasPoints = positiveSum $ numSheetsPoints sumSummaries
|
||||||
hasMarkedPoints = positiveSum $ numMarkedPoints sumSummaries
|
hasMarkedPoints = positiveSum $ numMarkedPoints sumSummaries
|
||||||
rowWdgts = [ $(widgetFile "widgets/grading-summary/grading-summary-row")
|
rowWgt (sumHeader, summary) = $(widgetFile "widgets/grading-summary/grading-summary-row") -- diese Funktonsdefinition darf leider nicht im .hamlet stehen!
|
||||||
| (sumHeader,summary) <-
|
rowsShown = [ (MsgSheetTypeNormal' ,normalSummary)
|
||||||
[ (MsgSheetTypeNormal' ,normalSummary)
|
, (MsgSheetTypeBonus' ,bonusSummary)
|
||||||
, (MsgSheetTypeBonus' ,bonusSummary)
|
, (MsgSheetTypeInformational' ,informationalSummary)
|
||||||
, (MsgSheetTypeInformational' ,informationalSummary)
|
] -- diese Liste könnte auch im .hamlet definiert werden
|
||||||
] ]
|
|
||||||
in if 0 == numSheets sumSummaries
|
in if 0 == numSheets sumSummaries
|
||||||
then mempty
|
then mempty
|
||||||
else $(widgetFile "widgets/grading-summary/grading-summary")
|
else $(widgetFile "widgets/grading-summary/grading-summary")
|
||||||
|
|||||||
@ -279,8 +279,9 @@ submissionMultiArchive (Set.toList -> ids) = do
|
|||||||
execWriter . forM ratedSubmissions $ \(_rating,_submission,(shn,csh,ssh,tid)) ->
|
execWriter . forM ratedSubmissions $ \(_rating,_submission,(shn,csh,ssh,tid)) ->
|
||||||
tell (Set.singleton shn, Set.singleton csh, Set.singleton ssh, Set.singleton tid)
|
tell (Set.singleton shn, Set.singleton csh, Set.singleton ssh, Set.singleton tid)
|
||||||
|
|
||||||
setContentDisposition' $ Just "submissions.zip"
|
archiveName <- ap getMessageRender $ pure MsgSubmissionArchiveName
|
||||||
(<* cleanup) . respondSource "application/zip" . transPipe (runDBRunner dbrunner) $ do
|
setContentDisposition' $ Just ((addExtension `on` unpack) archiveName extensionZip)
|
||||||
|
(<* cleanup) . respondSource typeZip . transPipe (runDBRunner dbrunner) $ do
|
||||||
let
|
let
|
||||||
fileEntitySource' :: (Rating, Entity Submission, (SheetName,CourseShorthand,SchoolId,TermId)) -> Source (YesodDB UniWorX) File
|
fileEntitySource' :: (Rating, Entity Submission, (SheetName,CourseShorthand,SchoolId,TermId)) -> Source (YesodDB UniWorX) File
|
||||||
fileEntitySource' (rating, Entity submissionID Submission{..},(shn,csh,ssh,tid)) = do
|
fileEntitySource' (rating, Entity submissionID Submission{..},(shn,csh,ssh,tid)) = do
|
||||||
|
|||||||
@ -34,6 +34,7 @@ module Handler.Utils.Table.Pagination
|
|||||||
, (&)
|
, (&)
|
||||||
, module Control.Monad.Trans.Maybe
|
, module Control.Monad.Trans.Maybe
|
||||||
, module Colonnade
|
, module Colonnade
|
||||||
|
, DBSTemplateMode(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Handler.Utils.Table.Pagination.Types
|
import Handler.Utils.Table.Pagination.Types
|
||||||
@ -42,7 +43,7 @@ import Handler.Utils.Form
|
|||||||
import Handler.Utils.Csv
|
import Handler.Utils.Csv
|
||||||
import Handler.Utils.ContentDisposition
|
import Handler.Utils.ContentDisposition
|
||||||
import Utils
|
import Utils
|
||||||
import Utils.Lens.TH
|
import Utils.Lens
|
||||||
|
|
||||||
import Import hiding (pi)
|
import Import hiding (pi)
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
@ -79,7 +80,6 @@ import Text.Hamlet (hamletFile)
|
|||||||
|
|
||||||
import Data.Ratio ((%))
|
import Data.Ratio ((%))
|
||||||
|
|
||||||
import Control.Lens hiding ((<.>))
|
|
||||||
import Control.Lens.Extras (is)
|
import Control.Lens.Extras (is)
|
||||||
|
|
||||||
import Data.List (elemIndex)
|
import Data.List (elemIndex)
|
||||||
@ -104,6 +104,7 @@ import Data.Semigroup as Sem (Semigroup(..))
|
|||||||
|
|
||||||
import qualified Data.Conduit.List as C
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
|
import Handler.Utils.DateTime (formatTimeW)
|
||||||
import qualified Control.Monad.Catch as Catch
|
import qualified Control.Monad.Catch as Catch
|
||||||
|
|
||||||
|
|
||||||
@ -428,7 +429,7 @@ data DBEmptyStyle = DBESNoHeading | DBESHeading
|
|||||||
instance Default DBEmptyStyle where
|
instance Default DBEmptyStyle where
|
||||||
def = DBESHeading
|
def = DBESHeading
|
||||||
|
|
||||||
data DBStyle = DBStyle
|
data DBStyle r = DBStyle
|
||||||
{ dbsEmptyStyle :: DBEmptyStyle
|
{ dbsEmptyStyle :: DBEmptyStyle
|
||||||
, dbsEmptyMessage :: UniWorXMessage
|
, dbsEmptyMessage :: UniWorXMessage
|
||||||
, dbsAttrs :: [(Text, Text)]
|
, dbsAttrs :: [(Text, Text)]
|
||||||
@ -438,9 +439,13 @@ data DBStyle = DBStyle
|
|||||||
-> Widget
|
-> Widget
|
||||||
-> Widget
|
-> Widget
|
||||||
-- ^ Filter UI, Filter Encoding, Filter action, table
|
-- ^ Filter UI, Filter Encoding, Filter action, table
|
||||||
|
, dbsTemplate :: DBSTemplateMode r
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Default DBStyle where
|
data DBSTemplateMode r = DBSTDefault
|
||||||
|
| DBSTCourse (Lens' r (Entity Course)) (Lens' r [Entity User]) (Lens' r Bool) (Lens' r (Entity School))
|
||||||
|
|
||||||
|
instance Default (DBStyle r) where
|
||||||
def = DBStyle
|
def = DBStyle
|
||||||
{ dbsEmptyStyle = def
|
{ dbsEmptyStyle = def
|
||||||
, dbsEmptyMessage = MsgNoTableContent
|
, dbsEmptyMessage = MsgNoTableContent
|
||||||
@ -451,6 +456,7 @@ instance Default DBStyle where
|
|||||||
<!-- No Filter UI -->
|
<!-- No Filter UI -->
|
||||||
^{scrolltable}
|
^{scrolltable}
|
||||||
|]
|
|]
|
||||||
|
, dbsTemplate = DBSTDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultDBSFilterLayout :: Widget -- ^ Filter UI
|
defaultDBSFilterLayout :: Widget -- ^ Filter UI
|
||||||
@ -523,7 +529,7 @@ data DBTable m x = forall a r r' h i t k k' csv.
|
|||||||
, dbtSorting :: Map SortingKey (SortColumn t)
|
, dbtSorting :: Map SortingKey (SortColumn t)
|
||||||
, dbtFilter :: Map FilterKey (FilterColumn t)
|
, dbtFilter :: Map FilterKey (FilterColumn t)
|
||||||
, dbtFilterUI :: Maybe (Map FilterKey [Text]) -> AForm (YesodDB UniWorX) (Map FilterKey [Text])
|
, dbtFilterUI :: Maybe (Map FilterKey [Text]) -> AForm (YesodDB UniWorX) (Map FilterKey [Text])
|
||||||
, dbtStyle :: DBStyle
|
, dbtStyle :: DBStyle r'
|
||||||
, dbtParams :: DBParams m x
|
, dbtParams :: DBParams m x
|
||||||
, dbtCsvEncode :: DBTCsvEncode r' csv
|
, dbtCsvEncode :: DBTCsvEncode r' csv
|
||||||
, dbtCsvDecode :: Maybe (DBTCsvDecode r' k' csv)
|
, dbtCsvDecode :: Maybe (DBTCsvDecode r' k' csv)
|
||||||
@ -1024,29 +1030,40 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
. setParam (wIdent "pagination") Nothing
|
. setParam (wIdent "pagination") Nothing
|
||||||
|
|
||||||
table' :: HandlerSite m ~ UniWorX => WriterT x m Widget
|
table' :: HandlerSite m ~ UniWorX => WriterT x m Widget
|
||||||
table' = do
|
table' = let
|
||||||
let
|
columnCount :: Int64
|
||||||
|
columnCount = olength64 $ getColonnade dbtColonnade
|
||||||
|
|
||||||
genHeaders SortableP{..} = forM (toSortable . oneColonnadeHead <$> getColonnade dbtColonnade) $ \Sortable{..} -> do
|
genHeaders SortableP{..} = forM (toSortable . oneColonnadeHead <$> getColonnade dbtColonnade) $ \Sortable{..} -> do
|
||||||
widget <- sortableContent ^. cellContents
|
widget <- sortableContent ^. cellContents
|
||||||
let
|
let
|
||||||
directions = [dir | SortingSetting k dir <- psSorting, Just k == sortableKey ]
|
directions = [dir | SortingSetting k dir <- psSorting, Just k == sortableKey ]
|
||||||
isSortable = isJust sortableKey
|
isSortable = isJust sortableKey
|
||||||
isSorted = (`elem` directions)
|
isSorted dir = fromMaybe False $ (==) <$> (SortingSetting <$> sortableKey <*> pure dir) <*> listToMaybe psSorting
|
||||||
attrs = sortableContent ^. cellAttrs
|
attrs = sortableContent ^. cellAttrs
|
||||||
piSorting' = [ sSet | sSet <- fromMaybe [] piSorting, Just (sortKey sSet) /= sortableKey ]
|
piSorting' = [ sSet | sSet <- fromMaybe [] piSorting, Just (sortKey sSet) /= sortableKey ]
|
||||||
return $(widgetFile "table/cell/header")
|
case dbsTemplate of
|
||||||
|
DBSTCourse _ _ _ _ -> return $(widgetFile "table/course/header")
|
||||||
columnCount :: Int64
|
DBSTDefault -> return $(widgetFile "table/cell/header")
|
||||||
columnCount = olength64 $ getColonnade dbtColonnade
|
in do
|
||||||
|
wHeaders <- maybe (return Nothing) (fmap Just . genHeaders) pSortable
|
||||||
wHeaders <- maybe (return Nothing) (fmap Just . genHeaders) pSortable
|
case dbsTemplate of
|
||||||
|
DBSTCourse c l r s -> do
|
||||||
wRows <- forM rows $ \row' -> forM (oneColonnadeEncode <$> getColonnade dbtColonnade) $ \(($ row') -> cell') -> do
|
wRows <- forM rows $ \row' -> let
|
||||||
widget <- cell' ^. cellContents
|
Course{..} = row' ^. c . _entityVal
|
||||||
let attrs = cell' ^. cellAttrs
|
lecturerUsers = row' ^. l
|
||||||
return $(widgetFile "table/cell/body")
|
courseLecturers = userSurname . entityVal <$> lecturerUsers
|
||||||
|
isRegistered = row' ^. r
|
||||||
return $(widgetFile "table/colonnade")
|
courseSchoolName = schoolName $ row' ^. s . _entityVal
|
||||||
|
courseSemester = (termToText . unTermKey) courseTerm
|
||||||
|
in return $(widgetFile "table/course/course-teaser")
|
||||||
|
return $(widgetFile "table/course/colonnade")
|
||||||
|
DBSTDefault -> do
|
||||||
|
wRows <- forM rows $ \row' -> forM (oneColonnadeEncode <$> getColonnade dbtColonnade) $ \(($ row') -> cell') -> do
|
||||||
|
widget <- cell' ^. cellContents
|
||||||
|
let attrs = cell' ^. cellAttrs
|
||||||
|
return $(widgetFile "table/cell/body")
|
||||||
|
return $(widgetFile "table/colonnade")
|
||||||
|
|
||||||
pageCount
|
pageCount
|
||||||
| PagesizeLimit l <- psLimit
|
| PagesizeLimit l <- psLimit
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
module Handler.Utils.Zip
|
module Handler.Utils.Zip
|
||||||
( ZipError(..)
|
( typeZip, extensionZip
|
||||||
|
, ZipError(..)
|
||||||
, ZipInfo(..)
|
, ZipInfo(..)
|
||||||
, produceZip
|
, produceZip
|
||||||
, consumeZip
|
, consumeZip
|
||||||
@ -27,6 +28,16 @@ import Data.Time.LocalTime (localTimeToUTC, utcToLocalTime)
|
|||||||
|
|
||||||
import Data.List (dropWhileEnd)
|
import Data.List (dropWhileEnd)
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
|
typeZip :: ContentType
|
||||||
|
typeZip = "application/zip"
|
||||||
|
|
||||||
|
extensionZip :: Extension
|
||||||
|
extensionZip = fromMaybe "zip" $ listToMaybe [ ext | (ext, mime) <- Map.toList mimeMap, mime == typeZip ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
instance Default ZipInfo where
|
instance Default ZipInfo where
|
||||||
def = ZipInfo
|
def = ZipInfo
|
||||||
@ -95,7 +106,7 @@ modifyFileTitle f = mapC $ \x@File{..} -> x{ fileTitle = f fileTitle }
|
|||||||
-- Takes FileInfo and if it is a ZIP-Archive, extract files, otherwiese yield fileinfo
|
-- Takes FileInfo and if it is a ZIP-Archive, extract files, otherwiese yield fileinfo
|
||||||
sourceFiles :: (MonadLogger m, MonadResource m, MonadThrow m, MonadIO m) => FileInfo -> Source m File
|
sourceFiles :: (MonadLogger m, MonadResource m, MonadThrow m, MonadIO m) => FileInfo -> Source m File
|
||||||
sourceFiles fInfo
|
sourceFiles fInfo
|
||||||
| mimeType == "application/zip" = do
|
| ((==) `on` simpleContentType) mimeType typeZip = do
|
||||||
$logInfoS "sourceFiles" "Unpacking ZIP"
|
$logInfoS "sourceFiles" "Unpacking ZIP"
|
||||||
fileSource fInfo =$= void consumeZip
|
fileSource fInfo =$= void consumeZip
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
|
|||||||
@ -34,8 +34,11 @@ import Text.Shakespeare.Text (st)
|
|||||||
import Control.Monad.Trans.Reader (mapReaderT)
|
import Control.Monad.Trans.Reader (mapReaderT)
|
||||||
import Control.Monad.Except (MonadError(..))
|
import Control.Monad.Except (MonadError(..))
|
||||||
import Utils (exceptT, allM, whenIsJust, guardM)
|
import Utils (exceptT, allM, whenIsJust, guardM)
|
||||||
|
import Utils.Lens (_NoUpload)
|
||||||
import Utils.DB (getKeyBy)
|
import Utils.DB (getKeyBy)
|
||||||
|
|
||||||
|
import Control.Lens
|
||||||
|
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
|
|
||||||
import qualified Net.IP as IP
|
import qualified Net.IP as IP
|
||||||
@ -398,6 +401,50 @@ customMigrations = Map.fromListWith (>>)
|
|||||||
updateTransactionInfo _ = return ()
|
updateTransactionInfo _ = return ()
|
||||||
runConduit $ getLogEntries .| C.mapM_ updateTransactionInfo
|
runConduit $ getLogEntries .| C.mapM_ updateTransactionInfo
|
||||||
)
|
)
|
||||||
|
, ( AppliedMigrationKey [migrationVersion|16.0.0|] [version|17.0.0|]
|
||||||
|
, do
|
||||||
|
whenM (tableExists "allocation_course") $ do
|
||||||
|
vals <- [sqlQQ| SELECT "course", "instructions", "application_text", "application_files", "ratings_visible" FROM "allocation_course"; |]
|
||||||
|
|
||||||
|
whenM (tableExists "course") $ do
|
||||||
|
[executeQQ|
|
||||||
|
ALTER TABLE "course" ADD COLUMN "applications_required" boolean not null default #{False}, ADD COLUMN "applications_instructions" varchar null, ADD COLUMN "applications_text" boolean not null default #{False}, ADD COLUMN "applications_files" jsonb not null default #{NoUpload}, ADD COLUMN "applications_ratings_visible" boolean not null default #{False};
|
||||||
|
ALTER TABLE "course" ALTER COLUMN "applications_required" DROP DEFAULT, ALTER COLUMN "applications_text" DROP DEFAULT, ALTER COLUMN "applications_files" DROP DEFAULT, ALTER COLUMN "applications_ratings_visible" DROP DEFAULT;
|
||||||
|
|]
|
||||||
|
|
||||||
|
forM_ vals $ \(cid :: CourseId, Single applicationsInstructions :: Single (Maybe Html), Single applicationsText :: Single Bool, Single applicationsFiles :: Single UploadMode, Single applicationsRatingsVisible :: Single Bool) -> do
|
||||||
|
let appRequired = applicationsText || isn't _NoUpload applicationsFiles
|
||||||
|
[executeQQ|
|
||||||
|
UPDATE "course" SET ("applications_required", "applications_instructions", "applications_text", "applications_files", "applications_ratings_visible") = (#{appRequired}, #{applicationsInstructions}, #{applicationsText}, #{applicationsFiles}, #{applicationsRatingsVisible}) WHERE "id" = #{cid};
|
||||||
|
|]
|
||||||
|
|
||||||
|
[executeQQ|
|
||||||
|
ALTER TABLE "allocation_course" DROP COLUMN "instructions", DROP COLUMN "application_text", DROP COLUMN "application_files", DROP COLUMN "ratings_visible";
|
||||||
|
|]
|
||||||
|
|
||||||
|
whenM ((&&) <$> tableExists "allocation_course_file" <*> (not <$> tableExists "course_app_instruction_file")) $ do
|
||||||
|
[executeQQ|
|
||||||
|
CREATe TABLE "course_app_instruction_file"("id" SERIAL8 PRIMARY KEY UNIQUE,"course" INT8 NOT NULL,"file" INT8 NOT NULL);
|
||||||
|
ALTER TABLE "course_app_instruction_file" ADD CONSTRAINT "unique_course_app_instruction_file" UNIQUE("course","file");
|
||||||
|
ALTER TABLE "course_app_instruction_file" ADD CONSTRAINT "course_app_instruction_file_course_fkey" FOREIGN KEY("course") REFERENCES "course"("id");
|
||||||
|
ALTER TABLE "course_app_instruction_file" ADD CONSTRAINT "course_app_instruction_file_file_fkey" FOREIGN KEY("file") REFERENCES "file"("id");
|
||||||
|
|]
|
||||||
|
|
||||||
|
let getFileEntries = rawQuery [st|SELECT "allocation_course_file"."id", "allocation_course"."course", "allocation_course_file"."file" FROM "allocation_course_file" INNER JOIN "allocation_course" ON "allocation_course"."id" = "allocation_course_file"."allocation_course"|] []
|
||||||
|
moveFileEntry [fromPersistValue -> Right (acfId :: Int64), fromPersistValue -> Right (cid :: CourseId), fromPersistValue -> Right (fid :: FileId)] =
|
||||||
|
[executeQQ|
|
||||||
|
INSERT INTO "course_app_instruction_file" ("course", "file") VALUES (#{cid}, #{fid});
|
||||||
|
DELETE FROM "allocation_course_file" WHERE "id" = #{acfId};
|
||||||
|
|]
|
||||||
|
moveFileEntry _ = return ()
|
||||||
|
runConduit $ getFileEntries .| C.mapM_ moveFileEntry
|
||||||
|
tableDropEmpty "allocation_course_file"
|
||||||
|
|
||||||
|
whenM (tableExists "allocation_application") $
|
||||||
|
tableDropEmpty "allocation_application"
|
||||||
|
whenM (tableExists "allocation_application_file") $
|
||||||
|
tableDropEmpty "allocation_application_file"
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,7 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä
|
|||||||
| AuthCourseRegistered
|
| AuthCourseRegistered
|
||||||
| AuthTutorialRegistered
|
| AuthTutorialRegistered
|
||||||
| AuthExamRegistered
|
| AuthExamRegistered
|
||||||
|
| AuthExamResult
|
||||||
| AuthParticipant
|
| AuthParticipant
|
||||||
| AuthTime
|
| AuthTime
|
||||||
| AuthAllocationTime
|
| AuthAllocationTime
|
||||||
|
|||||||
@ -54,6 +54,9 @@ data Icon
|
|||||||
| IconSFTSolution -- for SheetFileType only
|
| IconSFTSolution -- for SheetFileType only
|
||||||
| IconSFTMarking -- for SheetFileType only
|
| IconSFTMarking -- for SheetFileType only
|
||||||
| IconEmail
|
| IconEmail
|
||||||
|
| IconRegisterTemplate
|
||||||
|
| IconApplyTrue
|
||||||
|
| IconApplyFalse
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
||||||
|
|
||||||
iconText :: Icon -> Text
|
iconText :: Icon -> Text
|
||||||
@ -82,6 +85,9 @@ iconText = \case
|
|||||||
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
||||||
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
||||||
IconEmail -> "envelope"
|
IconEmail -> "envelope"
|
||||||
|
IconRegisterTemplate -> "file-alt"
|
||||||
|
IconApplyTrue -> "file-alt"
|
||||||
|
IconApplyFalse -> "trash"
|
||||||
|
|
||||||
instance Universe Icon
|
instance Universe Icon
|
||||||
instance Finite Icon
|
instance Finite Icon
|
||||||
@ -150,6 +156,10 @@ iconEnrol :: Bool -> Markup
|
|||||||
iconEnrol True = icon IconEnrolTrue
|
iconEnrol True = icon IconEnrolTrue
|
||||||
iconEnrol False = icon IconEnrolFalse
|
iconEnrol False = icon IconEnrolFalse
|
||||||
|
|
||||||
|
iconApply :: Bool -> Markup
|
||||||
|
iconApply True = icon IconApplyTrue
|
||||||
|
iconApply False = icon IconApplyFalse
|
||||||
|
|
||||||
iconExamRegister :: Bool -> Markup
|
iconExamRegister :: Bool -> Markup
|
||||||
iconExamRegister True = icon IconExamRegisterTrue
|
iconExamRegister True = icon IconExamRegisterTrue
|
||||||
iconExamRegister False = icon IconExamRegisterFalse
|
iconExamRegister False = icon IconExamRegisterFalse
|
||||||
|
|||||||
@ -35,6 +35,9 @@ _InnerJoinRight f (E.InnerJoin l r) = (l `E.InnerJoin`) <$> f r
|
|||||||
_nullable :: MonoFoldable mono => Prism' mono (NonNull mono)
|
_nullable :: MonoFoldable mono => Prism' mono (NonNull mono)
|
||||||
_nullable = prism' toNullable fromNullable
|
_nullable = prism' toNullable fromNullable
|
||||||
|
|
||||||
|
_SchoolId :: Iso' SchoolId SchoolShorthand
|
||||||
|
_SchoolId = iso unSchoolKey SchoolKey
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
-- Lens Definitions for our Types
|
-- Lens Definitions for our Types
|
||||||
@ -120,6 +123,7 @@ makePrisms ''HandlerContents
|
|||||||
|
|
||||||
makePrisms ''ErrorResponse
|
makePrisms ''ErrorResponse
|
||||||
|
|
||||||
|
makePrisms ''UploadMode
|
||||||
makeLenses_ ''UploadMode
|
makeLenses_ ''UploadMode
|
||||||
|
|
||||||
makeLenses_ ''SubmissionMode
|
makeLenses_ ''SubmissionMode
|
||||||
|
|||||||
@ -13,6 +13,6 @@
|
|||||||
Achtung, dieser Link löscht momentan noch den kompletten Benutzer
|
Achtung, dieser Link löscht momentan noch den kompletten Benutzer
|
||||||
unwiderruflich aus der Live-Datenbank mit
|
unwiderruflich aus der Live-Datenbank mit
|
||||||
<code>DELETE CASCADE uid
|
<code>DELETE CASCADE uid
|
||||||
\ Klausurdaten müssen jedoch langfristig gespeichert werden!
|
\ Prüfungs- und Klausurdaten müssen jedoch langfristig gespeichert werden!
|
||||||
<p>
|
<p>
|
||||||
^{modal "Benutzer löschen" (Right deleteWidget)}
|
^{modal "Benutzer löschen" (Right deleteWidget)}
|
||||||
|
|||||||
@ -79,13 +79,38 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
|||||||
<div>
|
<div>
|
||||||
\ <em>Achtung:</em>
|
\ <em>Achtung:</em>
|
||||||
\ Abmeldung nur bis #{dereg} erlaubt.
|
\ Abmeldung nur bis #{dereg} erlaubt.
|
||||||
|
$maybe aInst <- courseApplicationsInstructions course
|
||||||
|
<dt .deflist__dt>
|
||||||
|
$if courseApplicationsRequired course
|
||||||
|
_{MsgCourseApplicationInstructionsApplication}
|
||||||
|
$else
|
||||||
|
_{MsgCourseApplicationInstructionsRegistration}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<div>
|
||||||
|
#{aInst}
|
||||||
|
$if hasApplicationTemplate
|
||||||
|
<p>
|
||||||
|
<a href=@{CourseR tid ssh csh CRegisterTemplateR}>
|
||||||
|
#{iconRegisterTemplate} #
|
||||||
|
$if courseApplicationsRequired course
|
||||||
|
_{MsgCourseApplicationTemplateApplication}
|
||||||
|
$else
|
||||||
|
_{MsgCourseApplicationTemplateRegistration}
|
||||||
$if registrationOpen || isJust mRegAt
|
$if registrationOpen || isJust mRegAt
|
||||||
<dt .deflist__dt>
|
<dt .deflist__dt>
|
||||||
|
_{MsgCourseRegistration}
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
<div .course__registration>
|
<div .course__registration>
|
||||||
$if registrationOpen
|
$if registrationOpen
|
||||||
$# regForm is defined through templates/widgets/registerForm
|
$# regForm is defined through templates/widgets/registerForm
|
||||||
^{regForm}
|
^{regForm}
|
||||||
|
$if isJust mApplication && courseApplicationsRequired course
|
||||||
|
<p>
|
||||||
|
_{MsgCourseApplicationDeleteToEdit}
|
||||||
|
$else
|
||||||
|
$if isJust mRegAt
|
||||||
|
<p>
|
||||||
|
_{MsgCourseRegistrationDeleteToEdit}
|
||||||
$maybe date <- mRegAt
|
$maybe date <- mRegAt
|
||||||
_{MsgRegisteredSince} #{date}
|
_{MsgRegisteredSince} #{date}
|
||||||
<dt .deflist__dt>
|
<dt .deflist__dt>
|
||||||
@ -106,16 +131,3 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
|||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
^{tutorialTable}
|
^{tutorialTable}
|
||||||
|
|
||||||
|
|
||||||
$# <div .container>
|
|
||||||
$# <div .tab-group>
|
|
||||||
$# <div .tab data-tab-name="Übungsblätter">
|
|
||||||
$# ^{modal "#modal-toggler__new-sheet" Nothing}
|
|
||||||
$# <h3 .tab-title>Übungsblätter
|
|
||||||
$# <h1>TODO: Sortierbare Tabelle der bisherigen Übungsblätter
|
|
||||||
$# <div .tab data-tab-name="Übungsgruppen">
|
|
||||||
$# <h3 .tab-title>Übungsgruppen
|
|
||||||
$# <h1>TODO: Sortierbare Tabelle der Übungsgruppen
|
|
||||||
$# <div .tab data-tab-name="Klausuren">
|
|
||||||
$# <h3 .tab-title>Klausuren
|
|
||||||
$# <div>...
|
|
||||||
|
|||||||
5
templates/course/login-to-register.hamlet
Normal file
5
templates/course/login-to-register.hamlet
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
$newline never
|
||||||
|
$if courseApplicationsRequired course
|
||||||
|
_{MsgCourseLoginToApply}
|
||||||
|
$else
|
||||||
|
_{MsgCourseLoginToRegister}
|
||||||
@ -328,6 +328,9 @@ input[type="button"].btn-info:hover,
|
|||||||
/* SCROLLTABLE */
|
/* SCROLLTABLE */
|
||||||
.scrolltable {
|
.scrolltable {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolltable--bordered {
|
||||||
box-shadow: 0 0 1px 1px var(--color-grey-light);
|
box-shadow: 0 0 1px 1px var(--color-grey-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,8 @@
|
|||||||
<p>
|
<p>
|
||||||
<h3>
|
<h3>
|
||||||
Momentan noch unimplementierte Funktionalitäten
|
Neue geplante Funktionalitäten, moment noch nicht implementiert
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li> Stundenplan/Kalender mit allen Veranstaltungen und Klausuren
|
||||||
<h4>
|
<li> Vollständige Vorlesungshomepages
|
||||||
aus UniWorX bekannt:
|
<li> Vollständige Internationalisierung deutsch/englisch/...
|
||||||
<ul>
|
|
||||||
<li> Zentralanmeldungen
|
|
||||||
<li>
|
|
||||||
<h4>
|
|
||||||
neue geplante Features:
|
|
||||||
<ul>
|
|
||||||
<li> Stundenplan/Kalender mit allen Veranstaltungen und Klausuren
|
|
||||||
<li> Vollständige Vorlesungshomepages
|
|
||||||
<li> Vollständige Internationalisierung deutsch/englisch/...
|
|
||||||
|
|
||||||
|
|||||||
32
templates/i18n/implementation/de.hamlet
Normal file
32
templates/i18n/implementation/de.hamlet
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
$newline text
|
||||||
|
<p>
|
||||||
|
Uni2Work wird mit Hilfe des
|
||||||
|
<a href="https://www.yesodweb.com/">
|
||||||
|
Yesod-Frameworks
|
||||||
|
\ in
|
||||||
|
<a href="https://www.haskell.org/">
|
||||||
|
Haskell
|
||||||
|
\ /
|
||||||
|
<a href="https://www.haskell.org/ghc/">
|
||||||
|
GHC
|
||||||
|
\ implementiert.
|
||||||
|
|
||||||
|
Die Implementierung genügt
|
||||||
|
<a href="https://de.wikipedia.org/wiki/Representational_State_Transfer">
|
||||||
|
REST-Prinzipien
|
||||||
|
, so dass eine gute horizontale Skalierbarkeit der Webapplikation gewährleistet wird.
|
||||||
|
|
||||||
|
Als Datenbank wird ein
|
||||||
|
<a href="https://www.postgresql.org/">
|
||||||
|
PostgreSQL
|
||||||
|
\ Server verwendet.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<h4>
|
||||||
|
An der Entwicklung von Uni2Work beteiligte Personen
|
||||||
|
<ul .list--iconless .list--inline .list--comma-separated>
|
||||||
|
<li>Felix Hamann (Frontend)
|
||||||
|
<li>Steffen Jost
|
||||||
|
<li>Gregor Kleen
|
||||||
|
<li>Sarah Vaupel
|
||||||
@ -7,11 +7,31 @@ $newline text
|
|||||||
<h2>Bekannte Probleme in Bearbeitung
|
<h2>Bekannte Probleme in Bearbeitung
|
||||||
|
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
<dt .deflist__dt>Klausuren #{iconNew}
|
<dt .deflist__dt>#{iconNew} Zentralanmeldung
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
Klausuren werden ab sofort teilweise unterstüzt.
|
<p>
|
||||||
Der genaue Stand der Entwicklung ist weiter unter auf dieser
|
Veranstalter können eigene Veranstaltungen zu verschiedenen Zentralanmeldungen hinzufügen.
|
||||||
Seite in einem eigenem Abschnitt detailliert.
|
Dies findet man unter dem Menupunkt "Kurs editieren"
|
||||||
|
<p>
|
||||||
|
Die Zentralanmeldungen selbst sind momentan aber noch nicht sichtbar; dies folgt in Kürze.
|
||||||
|
<p>
|
||||||
|
Weitere Details finden sich weiter unter auf dieser Seite in einem
|
||||||
|
<a href="#allocations">
|
||||||
|
eigenem Abschnitt
|
||||||
|
|
||||||
|
<dt .deflist__dt>Klausuren und Prüfungen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<p>
|
||||||
|
Klausuren werden bereits teilweise unterstüzt.
|
||||||
|
Im Gegensatz zu UniWorX werden nun auch allgemeinere Prüfungsformen unterstüzt,
|
||||||
|
z.B. mündlcihe Prüfungen oder auch Praktika, bei denen die Teilnehmer in kleineren Gruppen
|
||||||
|
zu verschiedenen Zeitpunkten geprüft werden.
|
||||||
|
<p>
|
||||||
|
Der genaue Stand der Entwicklung ist weiter unter auf dieser Seite in einem
|
||||||
|
<a href="#exams">
|
||||||
|
eigenem Abschnitt
|
||||||
|
\ detailliert.
|
||||||
|
|
||||||
|
|
||||||
<dt .deflist__dt>Benachrichtigungen
|
<dt .deflist__dt>Benachrichtigungen
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
@ -41,10 +61,20 @@ $newline text
|
|||||||
|
|
||||||
Dabei werden vor allem Kurskürzel und die Kursbeschreibung übernommen;
|
Dabei werden vor allem Kurskürzel und die Kursbeschreibung übernommen;
|
||||||
nicht jedoch Übungsblätter, Klausuren oder Anmeldungen.
|
nicht jedoch Übungsblätter, Klausuren oder Anmeldungen.
|
||||||
<pr>
|
<p>
|
||||||
Die Kursbeschreibung kann in Html verfasst werden und
|
Die Kursbeschreibung kann in Html verfasst werden und
|
||||||
<em>sollte die Modulbeschreibung enthalten!
|
<em>sollte die Modulbeschreibung enthalten!
|
||||||
|
|
||||||
|
<dt .deflist__dt> #{iconNew} Unterstützung für verschiedene Institute
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<p>
|
||||||
|
Uni2work unterstüzt die Verwaltung von mehreren Instituten, d.h.
|
||||||
|
Kursnamen wie "[MATH]" für Kurse des mathematischen Instituts werden ab sofort nicht mehr benötigt.
|
||||||
|
Stattdessen gibt es nun Instituts-Filter für Kurslisten.
|
||||||
|
<p>
|
||||||
|
Die Berechtigungen der Uni2work-Administratoren sind auf Kurse des jeweiligen Instituts eingeschränkt,
|
||||||
|
d.h. ein Uni2work Administrator der Informatik kann keine Noten aus Kursen der Mathematik einsehen.
|
||||||
|
|
||||||
<dt .deflist__dt> Materialzugriff
|
<dt .deflist__dt> Materialzugriff
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
Der Zugriff auf Übungsblätter, Folien und andere Materialien
|
Der Zugriff auf Übungsblätter, Folien und andere Materialien
|
||||||
@ -248,9 +278,13 @@ $newline text
|
|||||||
Um die Anmeldung in beliebig viele Tutoriumsgruppen zuzulassen können alle Registrierungs-Gruppen leer gelassen werden.
|
Um die Anmeldung in beliebig viele Tutoriumsgruppen zuzulassen können alle Registrierungs-Gruppen leer gelassen werden.
|
||||||
|
|
||||||
|
|
||||||
<section>
|
<section id="exams">
|
||||||
<h2> Klausuren
|
<h2> Klausuren und Prüfungen
|
||||||
<p> Das Verwalten von Klausuren und Notenmeldungen wurde nun teilweise implementiert und ist ab sofort einsetzbar.
|
<p> Das Verwalten von Klausuren und Prüfungen im Allgemeinen sind bereits teilweise implementiert und einsetzbar.
|
||||||
|
In Erweiterung zu UniWorX werden nun auch allgemeinere Prüfungsformen unterstüzt,
|
||||||
|
z.B. mündlcihe Prüfungen oder auch Praktika, bei denen die Teilnehmer in kleineren Gruppen
|
||||||
|
zu verschiedenen Zeitpunkten und in verschiedenen Räumen geprüft werden.
|
||||||
|
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
<dt .deflist__dt> Anlegen/Editieren
|
<dt .deflist__dt> Anlegen/Editieren
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
@ -267,26 +301,22 @@ $newline text
|
|||||||
Es lassen sich aber auch zeitlich getrennte Prüfungen verwalten, wie z.B. mündliche Prüfungen bei Seminaren oder Praktika.
|
Es lassen sich aber auch zeitlich getrennte Prüfungen verwalten, wie z.B. mündliche Prüfungen bei Seminaren oder Praktika.
|
||||||
Teilnehmern wird eine übersichtliche Tabelle aller Prüfungen angezeigt.
|
Teilnehmern wird eine übersichtliche Tabelle aller Prüfungen angezeigt.
|
||||||
|
|
||||||
|
<dt .deflist__dt> #{iconNew} Anmeldungen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Teilnehmer können sich zu sichtbaren Klausuren innerhalb des eingestellten
|
||||||
|
Anmeldezeitraums anmelden.
|
||||||
|
Die Teilnehmerlisten können online oder per CSV Export/Import bearbeitet werden.
|
||||||
|
|
||||||
<dt .deflist__dt> #{iconProblem} Prüfungszuteilung
|
<dt .deflist__dt> #{iconProblem} Prüfungszuteilung
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
Auf Wunsch kann Uni2work die Zuteilung der Teilnehmer auf die Prüfungen (Räume bzw. Prüfungstermine)
|
Auf Wunsch kann Uni2work die Zuteilung der Teilnehmer auf die Prüfungen (Räume bzw. Prüfungstermine)
|
||||||
nach verschiedenen Kriterien wie Name oder Matrikelnummer vornehmen.
|
nach verschiedenen Kriterien wie Name oder Matrikelnummer vornehmen.
|
||||||
|
|
||||||
<dt .deflist__dt> #{iconWarning} Anmeldungen
|
|
||||||
<dd .deflist__dd>
|
|
||||||
Teilnehmer können sich bereits zu sichtbaren Klausur innerhalb des eingestellten
|
|
||||||
Anmeldezeitraums anmelden.
|
|
||||||
<p>
|
|
||||||
<em>
|
|
||||||
Achtung: #
|
|
||||||
Die Liste der angemeldeten Teilnehmer ist momentan noch nicht einsehbar oder exportierbar, wird aber sicher gespeichert.
|
|
||||||
Dieses Feature folgt in Kürze.
|
|
||||||
|
|
||||||
<dt .deflist__dt> #{iconProblem} Korrekturen
|
<dt .deflist__dt> #{iconProblem} Korrekturen
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
<p>
|
<p>
|
||||||
Korrekturen können derzeit noch nicht eingetragen werden.
|
Korrekturen können derzeit noch nicht eingetragen werden.
|
||||||
Die Realisierung sollte in wenigen Wochen erfolgen.
|
Die Realisierung sollte bald erfolgen.
|
||||||
<p>
|
<p>
|
||||||
Die Eintragung von Korrekturen erfolgt immer pro Teilaufgabe.
|
Die Eintragung von Korrekturen erfolgt immer pro Teilaufgabe.
|
||||||
Optional kann aus der erreichten Punktesumme dann automatisch eine Gesamtnote berechnet werden.
|
Optional kann aus der erreichten Punktesumme dann automatisch eine Gesamtnote berechnet werden.
|
||||||
@ -299,7 +329,12 @@ $newline text
|
|||||||
Es werden verschiedene Möglichkeiten angebotenen werden,
|
Es werden verschiedene Möglichkeiten angebotenen werden,
|
||||||
die erzielten Bewertungen der Hausübungen
|
die erzielten Bewertungen der Hausübungen
|
||||||
unter einstellbaren Bedingungen
|
unter einstellbaren Bedingungen
|
||||||
in einen Klausurbonus umzurechnen (z.B. anrechnung nur, falls bereits ohne Bonus bestanden).
|
in einen Klausurbonus umzurechnen (z.B. Anrechnung nur, falls bereits ohne Bonus bestanden).
|
||||||
|
|
||||||
|
<dt .deflist__dt> #{iconProblem} Türschilder
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Das Drucken von passenden Türschildern "Bitte Ruhe!" mit den passenden Eckdaten der Klausur
|
||||||
|
wird momentan noch nicht unterstüzt.
|
||||||
|
|
||||||
<dt .deflist__dt> #{iconProblem} Notenmeldung
|
<dt .deflist__dt> #{iconProblem} Notenmeldung
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
@ -319,6 +354,35 @@ $newline text
|
|||||||
Prüfungsamt übermitteln. Für größere Änderungen kann das Prüfungsamt
|
Prüfungsamt übermitteln. Für größere Änderungen kann das Prüfungsamt
|
||||||
die Klausur auch wieder an den Dozenten zurück übergeben;
|
die Klausur auch wieder an den Dozenten zurück übergeben;
|
||||||
der Dozent trägt dann einfach ein späteres Datum für die Übergabe ein.
|
der Dozent trägt dann einfach ein späteres Datum für die Übergabe ein.
|
||||||
|
|
||||||
|
|
||||||
|
<section id="allocations">
|
||||||
|
<h2> Zentralanmeldungen
|
||||||
|
|
||||||
|
<dl .deflist>
|
||||||
|
<dt .deflist__dt> Namensschema
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Veranstaltungen können einen beliebigen Namen tragen.
|
||||||
|
Die behelfsmäßigen Kürzel wie [SB], [ZP], usw sind nicht mehr notwendig!
|
||||||
|
|
||||||
|
<dt .deflist__dt> Kurseinstellung
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Die Kurseinstellungen werden ggf. von den notwendigen Einstellungen
|
||||||
|
der jeweiligen Zentralanmeldung überschrieben, d.h. Veranstalter
|
||||||
|
können hier keine Fehler mehr machen.
|
||||||
|
|
||||||
|
<dt .deflist__dt> Individuelle Bewerbungen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Studierende können nun pro Kurs eine individuelle Bewerbung abgeben,
|
||||||
|
welche nur den jeweiligen Kursverwaltern zugestelt wird.
|
||||||
|
|
||||||
|
|
||||||
|
<dt .deflist__dt> Feedback zu Bewerbungen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Veranstalter können, wenn sie das möchten, den Bewerbern ein Feedback
|
||||||
|
zu ihren Bewerbungen geben.
|
||||||
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Sonstiges
|
<h2>Sonstiges
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<p>
|
<p>
|
||||||
Stand: July 2019
|
Stand: August 2019
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Benachrichtigungen per eMail treffen manchmal mit mehreren Tagen Verzögerung ein.
|
Zentralanmledungen sind noch nicht für Studierende sichtbar (folgt in Kürze)
|
||||||
<li>
|
<li>
|
||||||
Format von Bewertungsdateien ist noch provisorisch
|
Format von Bewertungsdateien ist noch provisorisch
|
||||||
|
|||||||
@ -134,7 +134,7 @@
|
|||||||
Benutzerdaten bleiben prinzipiell so lange gespeichert,
|
Benutzerdaten bleiben prinzipiell so lange gespeichert,
|
||||||
bis ein Institutsadministrator über die Exmatrikulation informiert wurde.
|
bis ein Institutsadministrator über die Exmatrikulation informiert wurde.
|
||||||
Dann wird der Account mit einer angemessenen zeitverzögerung gelöscht.
|
Dann wird der Account mit einer angemessenen zeitverzögerung gelöscht.
|
||||||
Anonymisierte Klausurnoten verbleiben aus statistischen Gründen dauerhaft im System.
|
Anonymisierte Prüfungsnoten verbleiben aus statistischen Gründen dauerhaft im System.
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
Bei gemeinsamen Gruppenabgaben wird nur die Zuordnung zu diesem Benutzer gelöscht.
|
Bei gemeinsamen Gruppenabgaben wird nur die Zuordnung zu diesem Benutzer gelöscht.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<div .scrolltable>
|
<div .scrolltable .scrolltable--bordered>
|
||||||
<table *{dbsAttrs'}>
|
<table *{dbsAttrs'}>
|
||||||
$maybe wHeaders' <- wHeaders
|
$maybe wHeaders' <- wHeaders
|
||||||
<thead>
|
<thead>
|
||||||
@ -17,5 +17,4 @@ $newline never
|
|||||||
$forall row <- wRows
|
$forall row <- wRows
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
$forall widget <- row
|
$forall widget <- row
|
||||||
$# cell/body.hamlet
|
|
||||||
^{widget}
|
^{widget}
|
||||||
|
|||||||
12
templates/table/course/colonnade.hamlet
Normal file
12
templates/table/course/colonnade.hamlet
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
$newline never
|
||||||
|
<div .scrolltable .div__course-teaser *{dbsAttrs'}>
|
||||||
|
$maybe wHeaders' <- wHeaders
|
||||||
|
<div .course-teaser-header>
|
||||||
|
$forall widget <- wHeaders'
|
||||||
|
^{widget}
|
||||||
|
$nothing
|
||||||
|
$if null wRows && (dbsEmptyStyle == DBESHeading)
|
||||||
|
<p>_{dbsEmptyMessage}
|
||||||
|
$else
|
||||||
|
$forall row <- wRows
|
||||||
|
^{row}
|
||||||
45
templates/table/course/colonnade.lucius
Normal file
45
templates/table/course/colonnade.lucius
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
:root {
|
||||||
|
--color-grey-light: #efefef;
|
||||||
|
--color-grey-lighter: #f5f5f5;
|
||||||
|
--color-fontsec: #5b5861;
|
||||||
|
--course-bg-color: var(--color-grey-lighter);
|
||||||
|
--course-expanded-bg-color: var(--color-grey-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolltable {
|
||||||
|
box-shadow: none!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header::after,
|
||||||
|
.course-header::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 20px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header::before {
|
||||||
|
/* magic numbers to move arrow back in the right position after flipping it.
|
||||||
|
this allows us to use the same border for the up and the down arrow */
|
||||||
|
transform: translateY(150%) scale(1, -1);
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header::after {
|
||||||
|
transform: translateY(-150%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header:hover::before,
|
||||||
|
.course-header:hover::after {
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorted-asc::before,
|
||||||
|
.sorted-desc::after {
|
||||||
|
border-bottom-color: white !important;
|
||||||
|
}
|
||||||
25
templates/table/course/course-teaser.hamlet
Normal file
25
templates/table/course/course-teaser.hamlet
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div uw-course-teaser :isRegistered:.course-teaser__registered :is _Nothing courseDescription:.course-teaser__disabled :is _Just courseDescription:tabindex="1">
|
||||||
|
<div .course-teaser__semester>
|
||||||
|
<a href=@{TermCourseListR courseTerm}>_{courseSemester}
|
||||||
|
<div .course-teaser__school-value>
|
||||||
|
<a href=@{TermSchoolCourseListR courseTerm courseSchool}>_{courseSchoolName}
|
||||||
|
<div .course-teaser__shorthand>_{courseShorthand}
|
||||||
|
<div .course-teaser__title>
|
||||||
|
<a href=@{CourseR courseTerm courseSchool courseShorthand CShowR}>
|
||||||
|
_{courseName}
|
||||||
|
$if isRegistered
|
||||||
|
<div .course-teaser__registration>_{MsgRegistered}
|
||||||
|
$if not $ null courseLecturers
|
||||||
|
<div .course-teaser__lecturer-label>
|
||||||
|
_{MsgLecturersForN (length courseLecturers)}
|
||||||
|
<div .course-teaser__lecturer-value>
|
||||||
|
<ul .list--inline .list--comma-separated>
|
||||||
|
$forall lecturer <- courseLecturers
|
||||||
|
<li>
|
||||||
|
#{lecturer}
|
||||||
|
$maybe regTo <- courseRegisterTo
|
||||||
|
<div .course-teaser__duedate-label>_{MsgRegisterTo}
|
||||||
|
<div .course-teaser__duedate-value>^{formatTimeW SelFormatDateTime regTo}
|
||||||
|
$maybe desc <- courseDescription
|
||||||
|
<div .course-teaser__chevron>
|
||||||
|
<div .course-teaser__description>#{desc}
|
||||||
12
templates/table/course/header.hamlet
Normal file
12
templates/table/course/header.hamlet
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
$newline never
|
||||||
|
|
||||||
|
$maybe flag <- sortableKey
|
||||||
|
<span .course-header *{attrs} :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc>
|
||||||
|
$case directions
|
||||||
|
$of [SortAsc]
|
||||||
|
<a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting'))}>
|
||||||
|
^{widget}
|
||||||
|
$of _
|
||||||
|
<a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting'))}>
|
||||||
|
^{widget}
|
||||||
|
$nothing
|
||||||
@ -7,6 +7,11 @@ $newline never
|
|||||||
_{MsgKnownBugs}
|
_{MsgKnownBugs}
|
||||||
^{knownBugs}
|
^{knownBugs}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>
|
||||||
|
_{MsgImplementationDetails}
|
||||||
|
^{implementation}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
_{MsgVersionHistory}
|
_{MsgVersionHistory}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
werden Benutzer hiermit vollständig aus der Live-Datenbank mit
|
werden Benutzer hiermit vollständig aus der Live-Datenbank mit
|
||||||
<code>DELETE CASCADE uid
|
<code>DELETE CASCADE uid
|
||||||
gelöscht.
|
gelöscht.
|
||||||
Klausurdaten müssen jedoch unbedingt 5 Jahre bis nach Exmatrikulation
|
Klausur und Prüfungsdaten müssen jedoch unbedingt 5 Jahre bis nach Exmatrikulation
|
||||||
aufbewahrt werden!
|
aufbewahrt werden!
|
||||||
<p>
|
<p>
|
||||||
Benutzer können sich mit Ihrem Campus-Account
|
Benutzer können sich mit Ihrem Campus-Account
|
||||||
|
|||||||
@ -24,8 +24,8 @@ $# --
|
|||||||
<th .table__th>_{MsgSheetGradingPoints'}
|
<th .table__th>_{MsgSheetGradingPoints'}
|
||||||
<th .table__th>_{MsgSheetGradingCount'}
|
<th .table__th>_{MsgSheetGradingCount'}
|
||||||
$# Number of Sheet/Submissions used for calculating maximum passes/points
|
$# Number of Sheet/Submissions used for calculating maximum passes/points
|
||||||
$forall row <- rowWdgts
|
$forall someRow <- rowsShown
|
||||||
^{row}
|
^{rowWgt someRow}
|
||||||
$maybe nrNoGrade <- positiveSum $ numNotGraded
|
$maybe nrNoGrade <- positiveSum $ numNotGraded
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
<th .table__th>_{MsgSheetTypeNotGraded}
|
<th .table__th>_{MsgSheetTypeNotGraded}
|
||||||
|
|||||||
@ -420,6 +420,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Nothing
|
, courseRegisterSecret = Nothing
|
||||||
, courseMaterialFree = True
|
, courseMaterialFree = True
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit jost now ffp
|
insert_ $ CourseEdit jost now ffp
|
||||||
void . insert $ DegreeCourse ffp sdBsc sdInf
|
void . insert $ DegreeCourse ffp sdBsc sdInf
|
||||||
@ -452,6 +457,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Nothing
|
, courseRegisterSecret = Nothing
|
||||||
, courseMaterialFree = True
|
, courseMaterialFree = True
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit fhamann now eip
|
insert_ $ CourseEdit fhamann now eip
|
||||||
void . insert' $ DegreeCourse eip sdBsc sdInf
|
void . insert' $ DegreeCourse eip sdBsc sdInf
|
||||||
@ -470,6 +480,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Nothing
|
, courseRegisterSecret = Nothing
|
||||||
, courseMaterialFree = True
|
, courseMaterialFree = True
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit fhamann now ixd
|
insert_ $ CourseEdit fhamann now ixd
|
||||||
void . insert' $ DegreeCourse ixd sdBsc sdInf
|
void . insert' $ DegreeCourse ixd sdBsc sdInf
|
||||||
@ -488,6 +503,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Nothing
|
, courseRegisterSecret = Nothing
|
||||||
, courseMaterialFree = True
|
, courseMaterialFree = True
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit fhamann now ux3
|
insert_ $ CourseEdit fhamann now ux3
|
||||||
void . insert' $ DegreeCourse ux3 sdBsc sdInf
|
void . insert' $ DegreeCourse ux3 sdBsc sdInf
|
||||||
@ -506,6 +526,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Nothing
|
, courseRegisterSecret = Nothing
|
||||||
, courseMaterialFree = True
|
, courseMaterialFree = True
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit jost now pmo
|
insert_ $ CourseEdit jost now pmo
|
||||||
void . insert $ DegreeCourse pmo sdBsc sdInf
|
void . insert $ DegreeCourse pmo sdBsc sdInf
|
||||||
@ -662,6 +687,11 @@ fillDb = do
|
|||||||
, courseDeregisterUntil = Nothing
|
, courseDeregisterUntil = Nothing
|
||||||
, courseRegisterSecret = Just "dbs"
|
, courseRegisterSecret = Just "dbs"
|
||||||
, courseMaterialFree = False
|
, courseMaterialFree = False
|
||||||
|
, courseApplicationsRequired = False
|
||||||
|
, courseApplicationsInstructions = Nothing
|
||||||
|
, courseApplicationsText = False
|
||||||
|
, courseApplicationsFiles = NoUpload
|
||||||
|
, courseApplicationsRatingsVisible = False
|
||||||
}
|
}
|
||||||
insert_ $ CourseEdit gkleen now dbs
|
insert_ $ CourseEdit gkleen now dbs
|
||||||
void . insert' $ DegreeCourse dbs sdBsc sdInf
|
void . insert' $ DegreeCourse dbs sdBsc sdInf
|
||||||
|
|||||||
@ -16,7 +16,13 @@ instance Arbitrary (Route Auth) where
|
|||||||
]
|
]
|
||||||
|
|
||||||
instance Arbitrary (Route EmbeddedStatic) where
|
instance Arbitrary (Route EmbeddedStatic) where
|
||||||
arbitrary = embeddedResourceR <$> arbitrary <*> arbitrary
|
arbitrary = do
|
||||||
|
let printableText = pack . filter (/= '/') . getPrintableString <$> arbitrary
|
||||||
|
pathLength <- getPositive <$> arbitrary
|
||||||
|
path <- replicateM pathLength printableText
|
||||||
|
paramNum <- getNonNegative <$> arbitrary
|
||||||
|
params <- replicateM paramNum $ (,) <$> printableText <*> printableText
|
||||||
|
return $ embeddedResourceR path params
|
||||||
|
|
||||||
instance Arbitrary CourseR where
|
instance Arbitrary CourseR where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
@ -42,6 +48,10 @@ instance Arbitrary ExamR where
|
|||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary CourseApplicationR where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary (Route UniWorX) where
|
instance Arbitrary (Route UniWorX) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|||||||
Reference in New Issue
Block a user