Merge branch 'master' into 155-zentralanmeldungen
This commit is contained in:
commit
104ab8f994
5
.vscode/tasks.json
vendored
5
.vscode/tasks.json
vendored
@ -69,6 +69,11 @@
|
|||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "lint",
|
"script": "lint",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "release",
|
||||||
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
40
CHANGELOG.md
40
CHANGELOG.md
@ -2,6 +2,46 @@
|
|||||||
|
|
||||||
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.2.3](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.2.2...v5.2.3) (2019-08-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **csv exam import:** ignore unchanged noshow and voided ([a346524](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a346524))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### [5.2.2](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.2.1...v5.2.2) (2019-08-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### [5.2.1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.2.0...v5.2.1) (2019-08-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **csv upload exams:** allow ambiguous harmless study fields ([7d2937c](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/7d2937c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.1.0...v5.2.0) (2019-08-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **csv import:** csv import preview help text adjusted ([b7321df](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/b7321df))
|
||||||
|
* **csv import:** fix spelling and expand help text ([2c57a77](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/2c57a77))
|
||||||
|
* **exam import:** inactive registered features may be selected ([3c4172c](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/3c4172c))
|
||||||
|
* **routes:** change ex to sheet ([9d9ead9](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/9d9ead9))
|
||||||
|
* **sheet list:** do not show icons for inaccessible items ([0bb9a0f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/0bb9a0f)), closes [#421](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/421)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **csv import:** add explanation text ([6d0a4c1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/6d0a4c1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.0.2...v5.1.0) (2019-08-19)
|
## [5.1.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.0.2...v5.1.0) (2019-08-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1377,11 +1377,12 @@ BtnCsvImport: CSV-Datei importieren
|
|||||||
BtnCsvImportConfirm: CSV-Import abschließen
|
BtnCsvImportConfirm: CSV-Import abschließen
|
||||||
|
|
||||||
CsvImportNotConfigured: CSV-Import nicht vorgesehen
|
CsvImportNotConfigured: CSV-Import nicht vorgesehen
|
||||||
CsvImportConfirmationHeading: CSV-Import abschließen
|
CsvImportConfirmationHeading: CSV-Import Vorschau (noch keine Änderungen importiert)
|
||||||
CsvImportConfirmationTip: Durch den CSV-Import würden die unten aufgeführten Änderungen vorgenommen. Bitte überprüfen Sie diese zunächst sorgfältig.
|
CsvImportConfirmationTip: Es wurden noch keine Änderungen übernommen! Durch den CSV-Import könnten die unten aufgeführten Änderungen vorgenommen werden. Wählen Sie jetzt die gewünschten Änderungen aus, bevor Sie den CSV-Import abschließen.
|
||||||
CsvImportUnnecessary: Durch den CSV-Import würden keine Änderungen vorgenommen werden
|
CsvImportUnnecessary: Durch den CSV-Import würden keine Änderungen vorgenommen werden
|
||||||
CsvImportSuccessful n@Int: CSV-Import erfolgreich, es #{pluralDE n "wurde eine Aktion" (mappend (mappend "wurden " (toMessage n)) " Aktionen")} durchgeführt
|
CsvImportSuccessful n@Int: CSV-Import erfolgreich, es #{pluralDE n "wurde eine Aktion" (mappend (mappend "wurden " (toMessage n)) " Aktionen")} durchgeführt
|
||||||
CsvImportAborted: CSV-Import abgebrochen
|
CsvImportAborted: CSV-Import abgebrochen
|
||||||
|
CsvImportExplanationLabel: Hinweise zum CSV-Import
|
||||||
|
|
||||||
Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
|
Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
|
||||||
|
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "5.1.0",
|
"version": "5.2.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "5.1.0",
|
"version": "5.2.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: uniworx
|
name: uniworx
|
||||||
version: 5.1.0
|
version: 5.2.3
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
10
routes
10
routes
@ -107,11 +107,11 @@
|
|||||||
/notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access!
|
/notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access!
|
||||||
/subs CCorrectionsR GET POST
|
/subs CCorrectionsR GET POST
|
||||||
/subs/assigned CAssignR GET POST
|
/subs/assigned CAssignR GET POST
|
||||||
/ex SheetListR GET !course-registered !materials !corrector
|
/sheet SheetListR GET !course-registered !materials !corrector
|
||||||
/ex/new SheetNewR GET POST
|
/sheet/new SheetNewR GET POST
|
||||||
/ex/current SheetCurrentR GET !course-registered !materials !corrector
|
/sheet/current SheetCurrentR GET !course-registered !materials !corrector
|
||||||
/ex/unassigned SheetOldUnassignedR GET
|
/sheet/unassigned SheetOldUnassignedR GET
|
||||||
/ex/#SheetName SheetR:
|
/sheet/#SheetName SheetR:
|
||||||
/show SShowR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
|
/show SShowR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
|
||||||
/show/download SArchiveR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
|
/show/download SArchiveR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
|
||||||
/edit SEditR GET POST
|
/edit SEditR GET POST
|
||||||
|
|||||||
@ -368,6 +368,11 @@ instance RenderMessage UniWorX a => RenderMessage UniWorX (ExamResult' a) where
|
|||||||
mr :: RenderMessage UniWorX msg => msg -> Text
|
mr :: RenderMessage UniWorX msg => msg -> Text
|
||||||
mr = renderMessage foundation ls
|
mr = renderMessage foundation ls
|
||||||
|
|
||||||
|
instance RenderMessage UniWorX (Either ExamPassed ExamGrade) where
|
||||||
|
renderMessage foundation ls = either mr mr
|
||||||
|
where
|
||||||
|
mr :: RenderMessage UniWorX msg => msg -> Text
|
||||||
|
mr = renderMessage foundation ls
|
||||||
|
|
||||||
-- ToMessage instances for converting raw numbers to Text (no internationalization)
|
-- ToMessage instances for converting raw numbers to Text (no internationalization)
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ data ExamUserTableCsv = ExamUserTableCsv
|
|||||||
, csvEUserExerciseNumPasses :: Maybe Int
|
, csvEUserExerciseNumPasses :: Maybe Int
|
||||||
, csvEUserExercisePointsMax :: Maybe Points
|
, csvEUserExercisePointsMax :: Maybe Points
|
||||||
, csvEUserExerciseNumPassesMax :: Maybe Int
|
, csvEUserExerciseNumPassesMax :: Maybe Int
|
||||||
, csvEUserExamResult :: Maybe (Either ExamResultPassed ExamResultGrade)
|
, csvEUserExamResult :: Maybe ExamResultPassedGrade
|
||||||
, csvEUserCourseNote :: Maybe Html
|
, csvEUserCourseNote :: Maybe Html
|
||||||
}
|
}
|
||||||
deriving (Generic)
|
deriving (Generic)
|
||||||
@ -208,7 +208,7 @@ data ExamUserCsvAction
|
|||||||
}
|
}
|
||||||
| ExamUserCsvSetResultData
|
| ExamUserCsvSetResultData
|
||||||
{ examUserCsvActUser :: UserId
|
{ examUserCsvActUser :: UserId
|
||||||
, examUserCsvActExamResult :: Maybe (Either ExamResultPassed ExamResultGrade)
|
, examUserCsvActExamResult :: Maybe ExamResultPassedGrade
|
||||||
}
|
}
|
||||||
| ExamUserCsvSetCourseNoteData
|
| ExamUserCsvSetCourseNoteData
|
||||||
{ examUserCsvActUser :: UserId
|
{ examUserCsvActUser :: UserId
|
||||||
@ -243,8 +243,8 @@ postEUsersR tid ssh csh examn = do
|
|||||||
showPasses = numSheetsPasses allBoni /= 0
|
showPasses = numSheetsPasses allBoni /= 0
|
||||||
showPoints = getSum (numSheetsPoints allBoni) /= 0
|
showPoints = getSum (numSheetsPoints allBoni) /= 0
|
||||||
|
|
||||||
resultView :: ExamResultGrade -> Either ExamResultPassed ExamResultGrade
|
resultView :: ExamResultGrade -> ExamResultPassedGrade
|
||||||
resultView = bool (Left . over _examResult (view passingGrade)) Right examShowGrades
|
resultView = fmap $ bool (Left . view passingGrade) Right examShowGrades
|
||||||
|
|
||||||
let
|
let
|
||||||
examUsersDBTable = DBTable{..}
|
examUsersDBTable = DBTable{..}
|
||||||
@ -470,7 +470,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
deleteBy $ UniqueExamResult eid examUserCsvActUser
|
deleteBy $ UniqueExamResult eid examUserCsvActUser
|
||||||
audit $ TransactionExamResultDeleted eid examUserCsvActUser
|
audit $ TransactionExamResultDeleted eid examUserCsvActUser
|
||||||
Just res -> do
|
Just res -> do
|
||||||
let res' = either (over _examResult $ review passingGrade) id res
|
let res' = either (review passingGrade) id <$> res
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
void $ upsertBy
|
void $ upsertBy
|
||||||
(UniqueExamResult eid examUserCsvActUser)
|
(UniqueExamResult eid examUserCsvActUser)
|
||||||
@ -549,11 +549,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
$newline never
|
$newline never
|
||||||
^{nameWidget userDisplayName userSurname}
|
^{nameWidget userDisplayName userSurname}
|
||||||
$maybe newResult <- examUserCsvActExamResult
|
$maybe newResult <- examUserCsvActExamResult
|
||||||
$case newResult
|
, _{newResult}
|
||||||
$of Left pResult
|
|
||||||
, _{pResult}
|
|
||||||
$of Right gResult
|
|
||||||
, _{gResult}
|
|
||||||
$nothing
|
$nothing
|
||||||
, _{MsgExamResultNone}
|
, _{MsgExamResultNone}
|
||||||
|]
|
|]
|
||||||
@ -616,30 +612,40 @@ postEUsersR tid ssh csh examn = do
|
|||||||
lookupStudyFeatures :: ExamUserTableCsv -> DB (Maybe StudyFeaturesId)
|
lookupStudyFeatures :: ExamUserTableCsv -> DB (Maybe StudyFeaturesId)
|
||||||
lookupStudyFeatures csv@ExamUserTableCsv{..} = do
|
lookupStudyFeatures csv@ExamUserTableCsv{..} = do
|
||||||
uid <- view _2 <$> guessUser csv
|
uid <- view _2 <$> guessUser csv
|
||||||
studyFeatures <- E.select . E.from $ \(studyFeatures `E.InnerJoin` studyDegree `E.InnerJoin` studyTerms) -> do
|
oldFeatures <- getBy $ UniqueParticipant uid examCourse
|
||||||
E.on $ studyTerms E.^. StudyTermsId E.==. studyFeatures E.^. StudyFeaturesField
|
studyFeatures <- E.select . E.from $ \(studyFeatures `E.InnerJoin` studyDegree `E.InnerJoin` studyTerms) ->
|
||||||
E.on $ studyDegree E.^. StudyDegreeId E.==. studyFeatures E.^. StudyFeaturesDegree
|
E.distinctOnOrderBy [ E.asc (studyFeatures E.^. StudyFeaturesField)
|
||||||
E.where_ . E.and $ catMaybes
|
, E.asc (studyFeatures E.^. StudyFeaturesDegree)
|
||||||
[ do
|
, E.asc (studyFeatures E.^. StudyFeaturesSemester)] $ do
|
||||||
field <- csvEUserField
|
E.on $ studyTerms E.^. StudyTermsId E.==. studyFeatures E.^. StudyFeaturesField
|
||||||
return . E.or $ catMaybes
|
E.on $ studyDegree E.^. StudyDegreeId E.==. studyFeatures E.^. StudyFeaturesDegree
|
||||||
[ Just $ studyTerms E.^. StudyTermsName `E.ciEq` E.just (E.val field)
|
E.where_ . E.and $ catMaybes
|
||||||
, Just $ studyTerms E.^. StudyTermsShorthand `E.ciEq` E.just (E.val field)
|
[ do
|
||||||
, (studyTerms E.^. StudyTermsKey E.==.) . E.val <$> readMay field
|
field <- csvEUserField
|
||||||
]
|
return . E.or $ catMaybes
|
||||||
, do
|
[ Just $ studyTerms E.^. StudyTermsName `E.ciEq` E.just (E.val field)
|
||||||
degree <- csvEUserDegree
|
, Just $ studyTerms E.^. StudyTermsShorthand `E.ciEq` E.just (E.val field)
|
||||||
return . E.or $ catMaybes
|
, (studyTerms E.^. StudyTermsKey E.==.) . E.val <$> readMay field
|
||||||
[ Just $ studyDegree E.^. StudyDegreeName `E.ciEq` E.just (E.val degree)
|
]
|
||||||
, Just $ studyDegree E.^. StudyDegreeShorthand `E.ciEq` E.just (E.val degree)
|
, do
|
||||||
, (studyDegree E.^. StudyDegreeKey E.==.) . E.val <$> readMay degree
|
degree <- csvEUserDegree
|
||||||
]
|
return . E.or $ catMaybes
|
||||||
, (studyFeatures E.^. StudyFeaturesSemester E.==.) . E.val <$> csvEUserSemester
|
[ Just $ studyDegree E.^. StudyDegreeName `E.ciEq` E.just (E.val degree)
|
||||||
]
|
, Just $ studyDegree E.^. StudyDegreeShorthand `E.ciEq` E.just (E.val degree)
|
||||||
E.where_ $ studyFeatures E.^. StudyFeaturesUser E.==. E.val uid
|
, (studyDegree E.^. StudyDegreeKey E.==.) . E.val <$> readMay degree
|
||||||
E.&&. studyFeatures E.^. StudyFeaturesValid E.==. E.val True
|
]
|
||||||
E.limit 2
|
, (studyFeatures E.^. StudyFeaturesSemester E.==.) . E.val <$> csvEUserSemester
|
||||||
return $ studyFeatures E.^. StudyFeaturesId
|
]
|
||||||
|
E.where_ $ studyFeatures E.^. StudyFeaturesUser E.==. E.val uid
|
||||||
|
let isActive = studyFeatures E.^. StudyFeaturesValid E.==. E.val True
|
||||||
|
isActiveOrPrevious = case oldFeatures of
|
||||||
|
Just (Entity _ CourseParticipant{courseParticipantField = Just sfid})
|
||||||
|
-> isActive E.||. (E.val sfid E.==. studyFeatures E.^. StudyFeaturesId)
|
||||||
|
_ -> isActive
|
||||||
|
E.where_ isActiveOrPrevious -- either active studyFeature or the one previously associated with this course
|
||||||
|
E.orderBy [E.desc isActiveOrPrevious, E.asc (E.orderByOrd $ studyFeatures E.^. StudyFeaturesType)]
|
||||||
|
E.limit 2 -- we just need to know whether there is a unique one, none, or more than one
|
||||||
|
return $ studyFeatures E.^. StudyFeaturesId
|
||||||
case studyFeatures of
|
case studyFeatures of
|
||||||
[E.Value fid] -> return $ Just fid
|
[E.Value fid] -> return $ Just fid
|
||||||
_other
|
_other
|
||||||
@ -647,6 +653,11 @@ postEUsersR tid ssh csh examn = do
|
|||||||
, is _Nothing csvEUserDegree
|
, is _Nothing csvEUserDegree
|
||||||
, is _Nothing csvEUserSemester
|
, is _Nothing csvEUserSemester
|
||||||
-> return Nothing
|
-> return Nothing
|
||||||
|
_other
|
||||||
|
| Just (Entity _ CourseParticipant{..}) <- oldFeatures
|
||||||
|
, Just sfid <- courseParticipantField
|
||||||
|
, E.Value sfid `elem` studyFeatures
|
||||||
|
-> return Nothing
|
||||||
_other -> throwM ExamUserCsvExceptionNoMatchingStudyFeatures
|
_other -> throwM ExamUserCsvExceptionNoMatchingStudyFeatures
|
||||||
|
|
||||||
examUsersDBTableValidator = def & defaultSorting [SortAscBy "user-name"]
|
examUsersDBTableValidator = def & defaultSorting [SortAscBy "user-name"]
|
||||||
|
|||||||
@ -181,7 +181,7 @@ getSheetListR tid ssh csh = do
|
|||||||
let
|
let
|
||||||
hasSFT :: (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool) -> [SheetFileType]
|
hasSFT :: (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool) -> [SheetFileType]
|
||||||
hasSFT (E.Value hasExercise, E.Value hasHint, E.Value hasSolution, E.Value hasMarking)
|
hasSFT (E.Value hasExercise, E.Value hasHint, E.Value hasSolution, E.Value hasMarking)
|
||||||
= [ sft | sft <- [minBound..maxBound]
|
= [ sft | sft <- universeF
|
||||||
, sft /= SheetExercise || hasExercise
|
, sft /= SheetExercise || hasExercise
|
||||||
, sft /= SheetHint || hasHint
|
, sft /= SheetHint || hasHint
|
||||||
, sft /= SheetSolution || hasSolution
|
, sft /= SheetSolution || hasSolution
|
||||||
@ -199,7 +199,7 @@ getSheetListR tid ssh csh = do
|
|||||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
||||||
|
|
||||||
sheetFilter :: SheetName -> DB Bool
|
sheetFilter :: SheetName -> DB Bool
|
||||||
sheetFilter sheetName = (== Authorized) <$> evalAccessDB (CSheetR tid ssh csh sheetName SShowR) False
|
sheetFilter sheetName = hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
||||||
|
|
||||||
sheetCol = widgetColonnade . mconcat $
|
sheetCol = widgetColonnade . mconcat $
|
||||||
[ -- dbRow ,
|
[ -- dbRow ,
|
||||||
@ -215,9 +215,9 @@ getSheetListR tid ssh csh = do
|
|||||||
| let existingSFTs = hasSFT existFiles
|
| let existingSFTs = hasSFT existFiles
|
||||||
, sft <- [minBound..maxBound]
|
, sft <- [minBound..maxBound]
|
||||||
, let link = CSheetR tid ssh csh sheetName $ SZipR sft
|
, let link = CSheetR tid ssh csh sheetName $ SZipR sft
|
||||||
, let icn = toWidget $ sheetFile2markup sft
|
, let icn = toWgt $ sheetFile2markup sft
|
||||||
, let icnCell = if sft `elem` existingSFTs
|
, let icnCell = if sft `elem` existingSFTs
|
||||||
then linkEmptyCell link icn
|
then linkEitherCell link (icn, [whamlet| |])
|
||||||
else spacerCell
|
else spacerCell
|
||||||
] id & cellAttrs <>~ [("class","list--inline list--space-separated")]
|
] id & cellAttrs <>~ [("class","list--inline list--space-separated")]
|
||||||
, sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom)
|
, sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom)
|
||||||
|
|||||||
@ -13,10 +13,6 @@ import Data.CaseInsensitive (original)
|
|||||||
-- import qualified Data.CaseInsensitive as CI
|
-- import qualified Data.CaseInsensitive as CI
|
||||||
import qualified Data.Conduit.List as Conduit
|
import qualified Data.Conduit.List as Conduit
|
||||||
|
|
||||||
import Language.Haskell.TH
|
|
||||||
import Language.Haskell.TH.Syntax (qRunIO)
|
|
||||||
-- import Language.Haskell.TH.Datatype
|
|
||||||
|
|
||||||
import Text.Hamlet (shamletFile)
|
import Text.Hamlet (shamletFile)
|
||||||
|
|
||||||
import Handler.Utils.DateTime as Handler.Utils
|
import Handler.Utils.DateTime as Handler.Utils
|
||||||
@ -30,12 +26,9 @@ import Handler.Utils.Rating as Handler.Utils hiding (extractRatings)
|
|||||||
import Handler.Utils.Sheet as Handler.Utils
|
import Handler.Utils.Sheet as Handler.Utils
|
||||||
import Handler.Utils.Mail as Handler.Utils
|
import Handler.Utils.Mail as Handler.Utils
|
||||||
import Handler.Utils.ContentDisposition as Handler.Utils
|
import Handler.Utils.ContentDisposition as Handler.Utils
|
||||||
|
import Handler.Utils.I18n as Handler.Utils
|
||||||
|
|
||||||
import System.Directory (listDirectory)
|
import System.FilePath.Posix (takeFileName)
|
||||||
import System.FilePath.Posix (takeBaseName, takeFileName)
|
|
||||||
|
|
||||||
import qualified Data.List as List
|
|
||||||
import qualified Data.List.NonEmpty as NonEmpty
|
|
||||||
|
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
|
|
||||||
@ -216,36 +209,6 @@ warnTermDays tid timeNames = do
|
|||||||
forM_ outoflecture $ warnI MsgDayIsOutOfLecture
|
forM_ outoflecture $ warnI MsgDayIsOutOfLecture
|
||||||
forM_ outoftermdays $ warnI MsgDayIsOutOfTerm
|
forM_ outoftermdays $ warnI MsgDayIsOutOfTerm
|
||||||
|
|
||||||
-- | Add language dependent template files
|
|
||||||
--
|
|
||||||
-- For large files which are translated as a whole.
|
|
||||||
--
|
|
||||||
-- Argument musst be a directory under @/templates@,
|
|
||||||
-- which contains a file for each language,
|
|
||||||
-- eg. @imprint@ for choosing between
|
|
||||||
-- @/templates/imprint/de.hamlet@, @/templates/imprint/de-at.hamlet@,
|
|
||||||
-- and @/templates/imprint/en.hamlet@
|
|
||||||
--
|
|
||||||
-- Dependency detection cannot work properly (no `addDependentFile`-equivalent
|
|
||||||
-- for directories)
|
|
||||||
-- @$ stack clean@ is required so new translations show up
|
|
||||||
i18nWidgetFile :: FilePath -> Q Exp
|
|
||||||
i18nWidgetFile basename = do
|
|
||||||
-- Construct list of available translations (@de@, @en@, ...) at compile time
|
|
||||||
let i18nDirectory = "templates" </> "i18n" </> basename
|
|
||||||
availableFiles <- qRunIO $ listDirectory i18nDirectory
|
|
||||||
let availableTranslations = sortWith (NTop . flip List.elemIndex (NonEmpty.toList appLanguages)) . List.nub $ pack . takeBaseName <$> availableFiles
|
|
||||||
availableTranslations' <- maybe (fail $ "‘" <> i18nDirectory <> "’ is empty") return $ NonEmpty.nonEmpty availableTranslations
|
|
||||||
|
|
||||||
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
|
|
||||||
ws <- newName "ws" -- Name for dispatch function
|
|
||||||
letE
|
|
||||||
[ funD ws $ [ clause [litP $ stringL l] (normalB . widgetFile $ "i18n" </> basename </> l) []
|
|
||||||
| l <- unpack <$> NonEmpty.toList availableTranslations' -- One function definition for every available language
|
|
||||||
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
|
|
||||||
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | return a value only if the current user ist authorized for a given route
|
-- | return a value only if the current user ist authorized for a given route
|
||||||
guardAuthorizedFor :: ( HandlerSite h ~ UniWorX, MonadHandler h, MonadLogger h
|
guardAuthorizedFor :: ( HandlerSite h ~ UniWorX, MonadHandler h, MonadLogger h
|
||||||
|
|||||||
43
src/Handler/Utils/I18n.hs
Normal file
43
src/Handler/Utils/I18n.hs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
module Handler.Utils.I18n
|
||||||
|
where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Language.Haskell.TH.Syntax (qRunIO)
|
||||||
|
|
||||||
|
import qualified Data.List as List
|
||||||
|
import qualified Data.List.NonEmpty as NonEmpty
|
||||||
|
|
||||||
|
import System.Directory (listDirectory)
|
||||||
|
import System.FilePath.Posix (takeBaseName)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Add language dependent template files
|
||||||
|
--
|
||||||
|
-- For large files which are translated as a whole.
|
||||||
|
--
|
||||||
|
-- Argument musst be a directory under @/templates@,
|
||||||
|
-- which contains a file for each language,
|
||||||
|
-- eg. @imprint@ for choosing between
|
||||||
|
-- @/templates/imprint/de.hamlet@, @/templates/imprint/de-at.hamlet@,
|
||||||
|
-- and @/templates/imprint/en.hamlet@
|
||||||
|
--
|
||||||
|
-- Dependency detection cannot work properly (no `addDependentFile`-equivalent
|
||||||
|
-- for directories)
|
||||||
|
-- @$ stack clean@ is required so new translations show up
|
||||||
|
i18nWidgetFile :: FilePath -> Q Exp
|
||||||
|
i18nWidgetFile basename = do
|
||||||
|
-- Construct list of available translations (@de@, @en@, ...) at compile time
|
||||||
|
let i18nDirectory = "templates" </> "i18n" </> basename
|
||||||
|
availableFiles <- qRunIO $ listDirectory i18nDirectory
|
||||||
|
let availableTranslations = sortWith (NTop . flip List.elemIndex (NonEmpty.toList appLanguages)) . List.nub $ pack . takeBaseName <$> availableFiles
|
||||||
|
availableTranslations' <- maybe (fail $ "‘" <> i18nDirectory <> "’ is empty") return $ NonEmpty.nonEmpty availableTranslations
|
||||||
|
|
||||||
|
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
|
||||||
|
ws <- newName "ws" -- Name for dispatch function
|
||||||
|
letE
|
||||||
|
[ funD ws $ [ clause [litP $ stringL l] (normalB . widgetFile $ "i18n" </> basename </> l) []
|
||||||
|
| l <- unpack <$> NonEmpty.toList availableTranslations' -- One function definition for every available language
|
||||||
|
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
|
||||||
|
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
|
||||||
@ -44,6 +44,7 @@ import Handler.Utils.Table.Pagination.CsvColumnExplanations
|
|||||||
import Handler.Utils.Form
|
import Handler.Utils.Form
|
||||||
import Handler.Utils.Csv
|
import Handler.Utils.Csv
|
||||||
import Handler.Utils.ContentDisposition
|
import Handler.Utils.ContentDisposition
|
||||||
|
import Handler.Utils.I18n
|
||||||
import Utils
|
import Utils
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
@ -863,6 +864,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
, formSubmit = FormSubmit
|
, formSubmit = FormSubmit
|
||||||
, formAnchor = Nothing :: Maybe Text
|
, formAnchor = Nothing :: Maybe Text
|
||||||
}
|
}
|
||||||
|
csvImportExplanation = modal [whamlet|_{MsgCsvImportExplanationLabel}|] $ Right $(i18nWidgetFile "table/csv-import-explanation")
|
||||||
csvColExplanations = case dbtCsvEncode of
|
csvColExplanations = case dbtCsvEncode of
|
||||||
(Just (Dict, _) :: DBTCsvEncode _ csv) -> assertM' (not . null) . Map.toList . csvColumnsExplanations $ Proxy @csv
|
(Just (Dict, _) :: DBTCsvEncode _ csv) -> assertM' (not . null) . Map.toList . csvColumnsExplanations $ Proxy @csv
|
||||||
Nothing -> Nothing
|
Nothing -> Nothing
|
||||||
|
|||||||
@ -216,8 +216,10 @@ type ExamResultPoints = ExamResult' Points
|
|||||||
type ExamResultGrade = ExamResult' ExamGrade
|
type ExamResultGrade = ExamResult' ExamGrade
|
||||||
type ExamResultPassed = ExamResult' ExamPassed
|
type ExamResultPassed = ExamResult' ExamPassed
|
||||||
|
|
||||||
instance Csv.ToField (Either ExamResultPassed ExamResultGrade) where
|
type ExamResultPassedGrade = ExamResult' (Either ExamPassed ExamGrade)
|
||||||
|
|
||||||
|
instance Csv.ToField (Either ExamPassed ExamGrade) where
|
||||||
toField = either Csv.toField Csv.toField
|
toField = either Csv.toField Csv.toField
|
||||||
|
|
||||||
instance Csv.FromField (Either ExamResultPassed ExamResultGrade) where
|
instance Csv.FromField (Either ExamPassed ExamGrade) where
|
||||||
parseField x = (Left <$> Csv.parseField x) <|> (Right <$> Csv.parseField x) -- encodings are disjoint
|
parseField x = (Left <$> Csv.parseField x) <|> (Right <$> Csv.parseField x) -- encodings are disjoint
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import qualified Data.Text.Lens as Text
|
|||||||
data StudyFieldType = FieldPrimary | FieldSecondary
|
data StudyFieldType = FieldPrimary | FieldSecondary
|
||||||
deriving (Eq, Ord, Enum, Show, Read, Bounded, Generic)
|
deriving (Eq, Ord, Enum, Show, Read, Bounded, Generic)
|
||||||
derivePersistField "StudyFieldType"
|
derivePersistField "StudyFieldType"
|
||||||
|
instance Universe StudyFieldType
|
||||||
|
instance Finite StudyFieldType
|
||||||
|
|
||||||
|
|
||||||
data Theme
|
data Theme
|
||||||
|
|||||||
@ -10,13 +10,13 @@ packages:
|
|||||||
sha256: 2cab90bba4d15bf6a17e3cb8e50bc8708c1091de503dd4e91d3954240e89f37b
|
sha256: 2cab90bba4d15bf6a17e3cb8e50bc8708c1091de503dd4e91d3954240e89f37b
|
||||||
name: zip-stream
|
name: zip-stream
|
||||||
version: 0.1.0.1
|
version: 0.1.0.1
|
||||||
git: https://github.com/pngwjpgh/zip-stream.git
|
git: https://github.com/uni2work/zip-stream.git
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
size: 657
|
size: 657
|
||||||
sha256: d1626bbc3fb88a48ce9c5c37199f8cbf426be6410740891d76a8343de4f3c109
|
sha256: d1626bbc3fb88a48ce9c5c37199f8cbf426be6410740891d76a8343de4f3c109
|
||||||
commit: 9272bbed000928d500febad1cdc98d1da29d399e
|
commit: 9272bbed000928d500febad1cdc98d1da29d399e
|
||||||
original:
|
original:
|
||||||
git: https://github.com/pngwjpgh/zip-stream.git
|
git: https://github.com/uni2work/zip-stream.git
|
||||||
commit: 9272bbed000928d500febad1cdc98d1da29d399e
|
commit: 9272bbed000928d500febad1cdc98d1da29d399e
|
||||||
- completed:
|
- completed:
|
||||||
cabal-file:
|
cabal-file:
|
||||||
@ -24,13 +24,13 @@ packages:
|
|||||||
sha256: 88537113b855381b8d70da2442ae644dc979ad6b32aaaec2ebf55306764c8f1a
|
sha256: 88537113b855381b8d70da2442ae644dc979ad6b32aaaec2ebf55306764c8f1a
|
||||||
name: encoding
|
name: encoding
|
||||||
version: 0.8.2
|
version: 0.8.2
|
||||||
git: https://github.com/pngwjpgh/encoding.git
|
git: https://github.com/uni2work/encoding.git
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
size: 5668
|
size: 5668
|
||||||
sha256: 57160d758802aba6a0d2cc88c53f2f0bb60df7d5e6822938351618b7eca0beab
|
sha256: 57160d758802aba6a0d2cc88c53f2f0bb60df7d5e6822938351618b7eca0beab
|
||||||
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
||||||
original:
|
original:
|
||||||
git: https://github.com/pngwjpgh/encoding.git
|
git: https://github.com/uni2work/encoding.git
|
||||||
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
||||||
- completed:
|
- completed:
|
||||||
cabal-file:
|
cabal-file:
|
||||||
@ -38,13 +38,13 @@ packages:
|
|||||||
sha256: 7b25a0ef819e8a01b485d6d0865baa3445faa826ffb3876c94109dd2469ffbd3
|
sha256: 7b25a0ef819e8a01b485d6d0865baa3445faa826ffb3876c94109dd2469ffbd3
|
||||||
name: memcached-binary
|
name: memcached-binary
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
git: https://github.com/pngwjpgh/memcached-binary.git
|
git: https://github.com/uni2work/memcached-binary.git
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
size: 1170
|
size: 1170
|
||||||
sha256: c466f91129410bae1f53e25aec4026f6984ce2dff0ada4516e2548048aba549a
|
sha256: c466f91129410bae1f53e25aec4026f6984ce2dff0ada4516e2548048aba549a
|
||||||
commit: b5461747e7be226d3b67daebc3c9aefe8a4490ad
|
commit: b5461747e7be226d3b67daebc3c9aefe8a4490ad
|
||||||
original:
|
original:
|
||||||
git: https://github.com/pngwjpgh/memcached-binary.git
|
git: https://github.com/uni2work/memcached-binary.git
|
||||||
commit: b5461747e7be226d3b67daebc3c9aefe8a4490ad
|
commit: b5461747e7be226d3b67daebc3c9aefe8a4490ad
|
||||||
- completed:
|
- completed:
|
||||||
hackage: colonnade-1.2.0@sha256:5620e999a68a394abfe157da6302dd6d8ce8a89b527ea9c294519efd7c4edb2c,2092
|
hackage: colonnade-1.2.0@sha256:5620e999a68a394abfe157da6302dd6d8ce8a89b527ea9c294519efd7c4edb2c,2092
|
||||||
|
|||||||
@ -482,6 +482,11 @@ ul.list--inline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list--icon-width li {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* DEFINITION LIST */
|
/* DEFINITION LIST */
|
||||||
.deflist {
|
.deflist {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
37
templates/i18n/table/csv-import-explanation/de.hamlet
Normal file
37
templates/i18n/table/csv-import-explanation/de.hamlet
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<h3>Hinweise zum Import von CSV-Dateien
|
||||||
|
<dl .deflist>
|
||||||
|
<dt .deflist__dt>Änderungen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Einige Zellen können durch den Import verändert werden.
|
||||||
|
Nicht-änderbare Zellen werden ignoriert, falls diese verändert wurden.
|
||||||
|
<dt .deflist__dt>Vorschau
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Es wird eine Vorschau angezeigt, bevor irgendetwas tatsächlich geändert wird.
|
||||||
|
In der Vorschau können dann auch nur teilweise Änderungen ausgewählt werden.
|
||||||
|
<dt .deflist__dt>Leere Zellen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Löschbare Zellen werden durch leere Zellen gelöscht oder auf eindeutige Werte gesetzt.
|
||||||
|
<dt .deflist__dt>Konsistenz
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<p>
|
||||||
|
Es werden nur konsistente Änderungen akzeptiert!
|
||||||
|
<p>
|
||||||
|
Daraus folgt, dass es sinnvoll sein kann, gewisse Zellen frei zu lassen;
|
||||||
|
z.B. ändert man ein Studienfachzuordnung eines Teilnehmers ab,
|
||||||
|
dann müsste man auch Abschluss und Semesterzahl passend ändern.
|
||||||
|
Da diese jedoch eindeutig sind, kann man diese Zellen einfach frei lassen.
|
||||||
|
<dt .deflist__dt>Zeilen Identifikation
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Mehrere Spalten werden zur Identifikation der Zeile verwendet.
|
||||||
|
Es muss nicht in jeder Spalte der Zeile ein Wert vorhanden sein,
|
||||||
|
so lange die Identifikation noch eindeutig ist.
|
||||||
|
Sind mehrere Werte vorhanden, so müssen diese natürlich zueinander passen.
|
||||||
|
<dt .deflist__dt>Zeilen hinzufügen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Es können auch neue Zeilen hinzugefügt werden, so fern ausreichend
|
||||||
|
eindeutige Informationen vorhanden sind;
|
||||||
|
z.B. können so Prüfungsteilnehmer nachgemeldet werden.
|
||||||
|
<dt .deflist__dt>Zeilen löschen
|
||||||
|
<dd .deflist__dd>
|
||||||
|
Fehlende Zeilen werden in der Vorschau zur Löschung angeboten
|
||||||
|
und dann ggf. gelöscht.
|
||||||
@ -4,11 +4,15 @@ $if is _Just dbtCsvDecode
|
|||||||
<h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-import data-show-hide-collapsed>
|
<h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-import data-show-hide-collapsed>
|
||||||
_{MsgTableHeadingCsvImport}
|
_{MsgTableHeadingCsvImport}
|
||||||
<div .csv-import__content>
|
<div .csv-import__content>
|
||||||
|
<p>
|
||||||
|
^{csvImportExplanation}
|
||||||
^{csvImportWdgt'}
|
^{csvImportWdgt'}
|
||||||
$if is _Just dbtCsvEncode
|
$if is _Just dbtCsvEncode
|
||||||
<div .csv-export>
|
<div .csv-export>
|
||||||
<h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-export>
|
<h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-export>
|
||||||
_{MsgTableHeadingCsvExport}
|
_{MsgTableHeadingCsvExport}
|
||||||
<div .csv-export__content>
|
<div .csv-export__content>
|
||||||
|
<p>
|
||||||
|
^{csvColExplanations'}
|
||||||
^{csvExportWdgt'}
|
^{csvExportWdgt'}
|
||||||
^{csvColExplanations'}
|
|
||||||
|
|||||||
@ -1,19 +1,6 @@
|
|||||||
.csv-export {
|
.csv-export {
|
||||||
margin-bottom: 13px;
|
margin-bottom: 13px;
|
||||||
|
|
||||||
.csv-export__content {
|
|
||||||
display: flex;
|
|
||||||
align-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
margin-right: 10px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.csv-import {
|
.csv-import {
|
||||||
|
|||||||
Reference in New Issue
Block a user