feat(course-registration): allow independent course application
This commit is contained in:
parent
e54d6e4acf
commit
a00698e99e
@ -86,8 +86,9 @@ 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
|
CourseRegisterOk: Erfolgreich zum Kurs angemeldet
|
||||||
|
CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet
|
||||||
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 Hauptfach
|
||||||
CourseStudyFeatureUpdated: Assoziiertes Hauptfach geändert
|
CourseStudyFeatureUpdated: Assoziiertes Hauptfach geändert
|
||||||
@ -112,7 +113,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 +137,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 +150,28 @@ 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?
|
CourseApplicationText: Text-Bewerbungen
|
||||||
CourseAllocationApplicationRatingsVisible: Feedback für Bewerbungen
|
CourseApplicationTextTip: 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
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@ -290,6 +299,7 @@ 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.
|
||||||
@ -445,6 +455,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
|
||||||
@ -770,6 +784,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
|
||||||
@ -1262,11 +1279,11 @@ ExamPassed: Bestanden
|
|||||||
ExamNotPassed: Nicht bestanden
|
ExamNotPassed: Nicht bestanden
|
||||||
ExamResult: Prüfungsergebnis
|
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
|
||||||
@ -1291,7 +1308,7 @@ ImplementationDetails: Implementierung
|
|||||||
ExamUsersHeading: Prüfungsteilnehmer
|
ExamUsersHeading: Prüfungsteilnehmer
|
||||||
ExamUserDeregister: Teilnehmer von Prüfung 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
|
||||||
@ -1364,8 +1381,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,16 @@ 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
|
||||||
|
text Text Maybe -- free text entered by user
|
||||||
|
ratingPoints ExamGrade Maybe
|
||||||
|
ratingComment Text Maybe
|
||||||
|
allocation AllocationId Maybe
|
||||||
|
allocationPriority Natural Maybe
|
||||||
|
CourseApplicationFile
|
||||||
|
application CourseApplication
|
||||||
|
file FileId
|
||||||
|
UniqueApplicationFile application file
|
||||||
1
routes
1
routes
@ -87,6 +87,7 @@
|
|||||||
/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-time !lecturerANDallocation-time
|
||||||
|
/register-template CRegisterTemplateR GET
|
||||||
/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
|
||||||
|
|||||||
@ -282,6 +282,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" <>)
|
||||||
|
|||||||
@ -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 MsgCourseApplicationText & setTooltip MsgCourseApplicationTextTip) (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 ()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
module Handler.Course.Show
|
module Handler.Course.Show
|
||||||
( getCShowR
|
( getCShowR
|
||||||
|
, getCRegisterTemplateR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
@ -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,defSFid,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate) <- 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
|
||||||
@ -66,7 +71,9 @@ 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
|
||||||
|
return (cid,course,schoolName,participants,registration,entityKey <$> defSFid,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate)
|
||||||
|
|
||||||
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
|
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
|
||||||
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
|
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
|
||||||
@ -224,3 +231,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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
@ -574,11 +576,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 +594,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 ]
|
||||||
|
|||||||
@ -645,12 +645,17 @@ 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))
|
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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,7 @@ data Icon
|
|||||||
| IconSFTSolution -- for SheetFileType only
|
| IconSFTSolution -- for SheetFileType only
|
||||||
| IconSFTMarking -- for SheetFileType only
|
| IconSFTMarking -- for SheetFileType only
|
||||||
| IconEmail
|
| IconEmail
|
||||||
|
| IconRegisterTemplate
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
||||||
|
|
||||||
iconText :: Icon -> Text
|
iconText :: Icon -> Text
|
||||||
@ -82,6 +83,7 @@ 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"
|
||||||
|
|
||||||
instance Universe Icon
|
instance Universe Icon
|
||||||
instance Finite Icon
|
instance Finite Icon
|
||||||
|
|||||||
@ -120,6 +120,7 @@ makePrisms ''HandlerContents
|
|||||||
|
|
||||||
makePrisms ''ErrorResponse
|
makePrisms ''ErrorResponse
|
||||||
|
|
||||||
|
makePrisms ''UploadMode
|
||||||
makeLenses_ ''UploadMode
|
makeLenses_ ''UploadMode
|
||||||
|
|
||||||
makeLenses_ ''SubmissionMode
|
makeLenses_ ''SubmissionMode
|
||||||
|
|||||||
@ -79,8 +79,26 @@ $# $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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user