Merge branch 'fix/sub-study-terms'

This commit is contained in:
Gregor Kleen 2019-11-27 12:15:12 +01:00
commit 77ae311935
52 changed files with 1399 additions and 768 deletions

View File

@ -17,9 +17,13 @@ BtnHijack: Sitzung übernehmen
BtnSave: Speichern BtnSave: Speichern
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert. PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert.
BtnHandIn: Abgeben BtnHandIn: Abgeben
BtnCandidatesInfer: Studienfachzuordnung automatisch lernen BtnNameCandidatesInfer: Studienfach-Namens-Zuordnung automatisch lernen
BtnCandidatesDeleteConflicts: Konflikte löschen BtnNameCandidatesDeleteConflicts: Namenskonflikte löschen
BtnCandidatesDeleteAll: Alle Beobachtungen löschen BtnNameCandidatesDeleteAll: Alle Namens-Beobachtungen löschen
BtnParentCandidatesInfer: Unterstudiengangs-Zuordnung automatisch lernen
BtnParentCandidatesDeleteAll: Alle Unterstudiengangs-Beobachtungen löschen
BtnStandaloneCandidatesDeleteAll: Alle Einzelstudiengangs-Beobachtungen löschen
BtnStandaloneCandidatesDeleteRedundant: Redundante Einzelstudiengangs-Beobachtungen löschen
BtnResetTokens: Authorisierungs-Tokens invalidieren BtnResetTokens: Authorisierungs-Tokens invalidieren
BtnLecInvAccept: Annehmen BtnLecInvAccept: Annehmen
BtnLecInvDecline: Ablehnen BtnLecInvDecline: Ablehnen
@ -761,9 +765,15 @@ AdminFeaturesHeading: Studiengänge
StudyTerms: Studiengänge StudyTerms: Studiengänge
StudyTerm: Studiengang StudyTerm: Studiengang
NoStudyTermsKnown: Keine Studiengänge bekannt NoStudyTermsKnown: Keine Studiengänge bekannt
StudyFeatureInference: Studiengangschlüssel-Inferenz StudyFeaturesDegrees: Abschlüsse
StudyFeatureInferenceNoConflicts: Keine Konflikte beobachtet StudyFeaturesTerms: Studiengänge
StudyFeatureInferenceConflictsHeading: Studiengangseinträge mit beobachteten Konflikten StudyFeaturesNameCandidates: Namens-Kandidaten
StudyFeaturesParentCandidates: Kandidaten für Unterstudiengänge
StudyFeaturesStandaloneCandidates: Kandidaten für Einzelstudiengänge
StudyFeatureNameInference: Studiengangschlüssel-Inferenz
StudyFeatureParentInference: Unterstudiengang-Inferenz
StudyFeatureInferenceNoNameConflicts: Keine Konflikte beobachtet
StudyFeatureInferenceNameConflictsHeading: Studiengangseinträge mit beobachteten Konflikten
StudyFeatureAge: Fachsemester StudyFeatureAge: Fachsemester
StudyFeatureDegree: Abschluss StudyFeatureDegree: Abschluss
FieldPrimary: Hauptfach FieldPrimary: Hauptfach
@ -784,11 +794,17 @@ StudyTermsShort: Studiengangkürzel
StudyTermsChangeSuccess: Zuordnung Studiengänge aktualisiert StudyTermsChangeSuccess: Zuordnung Studiengänge aktualisiert
StudyDegreeChangeSuccess: Zuordnung Abschlüsse aktualisiert StudyDegreeChangeSuccess: Zuordnung Abschlüsse aktualisiert
StudyCandidateIncidence: Synchronisation StudyCandidateIncidence: Synchronisation
AmbiguousCandidatesRemoved n@Int: #{show n} #{pluralDE n "uneindeutiger Kandidat" "uneindeutige Kandiaten"} entfernt AmbiguousNameCandidatesRemoved n@Int: #{show n} #{pluralDE n "uneindeutiger Kandidat" "uneindeutige Kandiaten"} entfernt
RedundantCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Kandidat" "bekannte Kandiaten"} entfernt RedundantNameCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Namenskandidat" "bekannte Namenskandiaten"} entfernt
CandidatesInferred n@Int: #{show n} neue #{pluralDE n "Studiengangszuordnung" "Studiengangszuordnungen"} inferiert RedundantParentCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Elternkandidat" "bekannte Elternkandiaten"} entfernt
NoCandidatesInferred: Keine neuen Studienganszuordnungen inferiert RedundantStandaloneCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Einzelstudiengangskandidat" "bekannte Einzelstudiengangskandiaten"} entfernt
AllIncidencesDeleted: Alle Beobachtungen wurden gelöscht. NameCandidatesInferred n@Int: #{show n} neue #{pluralDE n "Studiengangszuordnung" "Studiengangszuordnungen"} inferiert
NoNameCandidatesInferred: Keine neuen Studienganszuordnungen inferiert
ParentCandidatesInferred n@Int: #{show n} #{pluralDE n "neuer Unterstudiengang" "neue Unterstudiengänge"} inferiert
NoParentCandidatesInferred: Keine neuen Unterstudiengänge inferiert
AllNameIncidencesDeleted: Alle Namens-Beobachtungen wurden gelöscht.
AllParentIncidencesDeleted: Alle Unterstudiengang-Beobachtungen wurden gelöscht.
AllStandaloneIncidencesDeleted: Alle Einzelstudiengang-Beobachtungen wurden gelöscht.
IncidencesDeleted n@Int: #{show n} #{pluralDE n "Beobachtung" "Beobachtungen"} gelöscht IncidencesDeleted n@Int: #{show n} #{pluralDE n "Beobachtung" "Beobachtungen"} gelöscht
StudyTermIsNew: Neu StudyTermIsNew: Neu
StudyFeatureConflict: Es wurden Konflikte in der Studiengang-Zuordnung gefunden StudyFeatureConflict: Es wurden Konflikte in der Studiengang-Zuordnung gefunden
@ -2077,6 +2093,13 @@ ShortSexNotApplicable: k.A.
ShowSex: Geschlechter anderer Nutzer anzeigen ShowSex: Geschlechter anderer Nutzer anzeigen
ShowSexTip: Sollen in Kursteilnehmer-Tabellen u.Ä. die Geschlechter der Nutzer angezeigt werden? ShowSexTip: Sollen in Kursteilnehmer-Tabellen u.Ä. die Geschlechter der Nutzer angezeigt werden?
StudySubTermsChildKey: Kind
StudySubTermsChildName: Kindname
StudySubTermsParentKey: Elter
StudySubTermsParentName: Eltername
StudyTermsDefaultDegree: Default Abschluss
StudyTermsDefaultFieldType: Default Typ
MenuLanguage: Sprache MenuLanguage: Sprache
LanguageChanged: Sprache erfolgreich geändert LanguageChanged: Sprache erfolgreich geändert
@ -2137,4 +2160,4 @@ Deficit: Defizit
MetricNoSamples: Keine Messwerte MetricNoSamples: Keine Messwerte
MetricName: Name MetricName: Name
MetricValue: Wert MetricValue: Wert

View File

@ -17,9 +17,13 @@ BtnHijack: Hijack session
BtnSave: Save BtnSave: Save
PressSaveToSave: Changes will only be saved after clicking "Save". PressSaveToSave: Changes will only be saved after clicking "Save".
BtnHandIn: Hand in submission BtnHandIn: Hand in submission
BtnCandidatesInfer: Infer mapping BtnNameCandidatesInfer: Infer name-mapping
BtnCandidatesDeleteConflicts: Delete conflicts BtnNameCandidatesDeleteConflicts: Delete name-conflicts
BtnCandidatesDeleteAll: Delete all observations BtnNameCandidatesDeleteAll: Delete all name-observations
BtnParentCandidatesInfer: Infer parent-relation
BtnParentCandidatesDeleteAll: Delete all parent-observations
BtnStandaloneCandidatesDeleteAll: Delete all standalone-observations
BtnStandaloneCandidatesDeleteRedundant: Delete redundant standalone-observations
BtnResetTokens: Invalidate tokens BtnResetTokens: Invalidate tokens
BtnLecInvAccept: Accept BtnLecInvAccept: Accept
BtnLecInvDecline: Decline BtnLecInvDecline: Decline
@ -384,6 +388,8 @@ UnauthorizedTokenNotStarted: Your authorisation-token is not yet valid.
UnauthorizedTokenInvalid: Your authorisation-token could not be processed. UnauthorizedTokenInvalid: Your authorisation-token could not be processed.
UnauthorizedTokenInvalidRoute: Your authorisation-token is not valid for this page. UnauthorizedTokenInvalidRoute: Your authorisation-token is not valid for this page.
UnauthorizedTokenInvalidAuthority: Your authorisation-token is based in an user's rights who does not exist anymore. UnauthorizedTokenInvalidAuthority: Your authorisation-token is based in an user's rights who does not exist anymore.
UnauthorizedTokenInvalidAuthorityGroup: Your authorisation-token is based in an user groups rights which does not exist anymore.
UnauthorizedTokenInvalidAuthorityValue: The specification of the rights in which your authorisation-token is based, could not be interpreted.
UnauthorizedToken404: Authorisation-tokens cannot be processed on error pages. UnauthorizedToken404: Authorisation-tokens cannot be processed on error pages.
UnauthorizedSiteAdmin: You are no system-wide administrator. UnauthorizedSiteAdmin: You are no system-wide administrator.
UnauthorizedSchoolAdmin: You are no administrator for this department. UnauthorizedSchoolAdmin: You are no administrator for this department.
@ -756,9 +762,15 @@ AdminFeaturesHeading: Features of study
StudyTerms: Fields of study StudyTerms: Fields of study
StudyTerm: Field of study StudyTerm: Field of study
NoStudyTermsKnown: No known features of study NoStudyTermsKnown: No known features of study
StudyFeatureInference: Infer field of study mapping StudyFeaturesDegrees: Degrees
StudyFeatureInferenceNoConflicts: No observed conflicts StudyFeaturesTerms: Terms of Study
StudyFeatureInferenceConflictsHeading: Fields of study with observed conflicts StudyFeaturesNameCandidates: Name candidates
StudyFeaturesParentCandidates: Parent candidates
StudyFeaturesStandaloneCandidates: Standalone candidates
StudyFeatureNameInference: Infer field of study mapping
StudyFeatureParentInference: Infer field of study parent relation
StudyFeatureInferenceNoNameConflicts: No observed conflicts
StudyFeatureInferenceNameConflictsHeading: Fields of study with observed conflicts
StudyFeatureAge: Semester StudyFeatureAge: Semester
StudyFeatureDegree: Degree StudyFeatureDegree: Degree
FieldPrimary: Major FieldPrimary: Major
@ -779,11 +791,17 @@ StudyTermsShort: Field shorthand
StudyTermsChangeSuccess: Successfully updated fields of study StudyTermsChangeSuccess: Successfully updated fields of study
StudyDegreeChangeSuccess: Successfully updated degrees StudyDegreeChangeSuccess: Successfully updated degrees
StudyCandidateIncidence: Synchronisation StudyCandidateIncidence: Synchronisation
AmbiguousCandidatesRemoved n: Successfully removed #{n} ambiguous #{pluralEN n "candidate" "candidates"} AmbiguousNameCandidatesRemoved n: Successfully removed #{n} ambiguous #{pluralEN n "candidate" "candidates"}
RedundantCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "candidate" "candidates"} RedundantNameCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "name-candidate" "name-candidates"}
CandidatesInferred n: Successfully inferred #{n} field #{pluralEN n "mapping" "mappings"} RedundantParentCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "parent-candidate" "parent-candidates"}
NoCandidatesInferred: No new mappings inferred RedundantStandaloneCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "standalone-candidate" "standalone-candidates"}
AllIncidencesDeleted: Successfully deleted all observations NameCandidatesInferred n: Successfully inferred #{n} field #{pluralEN n "mapping" "mappings"}
NoNameCandidatesInferred: No new name-mappings inferred
AllNameIncidencesDeleted: Successfully deleted all name observations
AllParentIncidencesDeleted: Successfully deleted all parent-relation observations
AllStandaloneIncidencesDeleted: Successfully deleted all standalone observations
ParentCandidatesInferred n: Successfully inferred #{n} field #{pluralEN n "parent-relation" "parent-reliations"}
NoParentCandidatesInferred: No new parent-relations inferred
IncidencesDeleted n: Successfully deleted #{show n} #{pluralEN n "observation" "observations"} IncidencesDeleted n: Successfully deleted #{show n} #{pluralEN n "observation" "observations"}
StudyTermIsNew: New StudyTermIsNew: New
StudyFeatureConflict: Observed conflicts in field mapping StudyFeatureConflict: Observed conflicts in field mapping
@ -2070,6 +2088,12 @@ ShortSexNotApplicable: N/A
ShowSex: Show sex of other users ShowSex: Show sex of other users
ShowSexTip: Should users' sex be displayed in (among others) lists of course participants? ShowSexTip: Should users' sex be displayed in (among others) lists of course participants?
StudySubTermsChildKey: Child
StudySubTermsChildName: Child-Name
StudySubTermsParentKey: Parent
StudySubTermsParentName: Parent-Name
StudyTermsDefaultDegree: Default degree
StudyTermsDefaultFieldType: Default type
MenuLanguage: Language MenuLanguage: Language
LanguageChanged: Language changed successfully LanguageChanged: Language changed successfully

View File

@ -64,5 +64,5 @@ ExamCorrector
UniqueExamCorrector exam user UniqueExamCorrector exam user
ExamPartCorrector ExamPartCorrector
part ExamPartId part ExamPartId
corrector ExamCorrector corrector ExamCorrectorId
UniqueExamPartCorrector part corrector UniqueExamPartCorrector part corrector

View File

@ -55,11 +55,13 @@ StudyFeatures -- multiple entries possible for students pursuing several degree
user UserId user UserId
degree StudyDegreeId -- Abschluss, i.e. Master, Bachelor, etc. degree StudyDegreeId -- Abschluss, i.e. Master, Bachelor, etc.
field StudyTermsId -- Fach, i.e. Informatics, Philosophy, etc. field StudyTermsId -- Fach, i.e. Informatics, Philosophy, etc.
subField StudyTermsId Maybe
type StudyFieldType -- Major or minor, i.e. Haupt-/Nebenfach type StudyFieldType -- Major or minor, i.e. Haupt-/Nebenfach
semester Int semester Int
updated UTCTime default=now() -- last update from LDAP updated UTCTime default=now() -- last update from LDAP
valid Bool default=true -- marked as active in LDAP (students may switch, but LDAP never forgets) valid Bool default=true -- marked as active in LDAP (students may switch, but LDAP never forgets)
UniqueStudyFeatures user degree field type semester UniqueStudyFeatures user degree field type semester
deriving Eq Show
-- UniqueUserSubject ubuser degree field -- There exists a counterexample -- UniqueUserSubject ubuser degree field -- There exists a counterexample
StudyDegree -- Studienabschluss StudyDegree -- Studienabschluss
key Int -- LMU-internal key key Int -- LMU-internal key
@ -67,22 +69,37 @@ StudyDegree -- Studienabschluss
name Text Maybe -- description given by LDAP name Text Maybe -- description given by LDAP
Primary key -- column key is used as actual DB row key Primary key -- column key is used as actual DB row key
-- newtype Key StudyDegree = StudyDegreeKey' { unStudyDegreeKey :: Int } -- newtype Key StudyDegree = StudyDegreeKey' { unStudyDegreeKey :: Int }
deriving Show deriving Eq Show
StudyTerms -- Studiengang StudyTerms -- Studiengang
key Int -- LMU-internal key key Int -- standardised key
shorthand Text Maybe -- admin determined shorthand shorthand Text Maybe -- admin determined shorthand
name Text Maybe -- description given by LDAP name Text Maybe -- description given by LDAP
defaultDegree StudyDegreeId Maybe
defaultType StudyFieldType Maybe
Primary key -- column key is used as actual DB row key Primary key -- column key is used as actual DB row key
-- newtype Key StudyTerms = StudyTermsKey' { unStudyTermsKey :: Int } -- newtype Key StudyTerms = StudyTermsKey' { unStudyTermsKey :: Int }
deriving Show deriving Eq Ord Show
StudyTermCandidate -- No one at LMU is willing and able to tell us the meaning of the keys for StudyDegrees and StudyTerms. StudySubTerms
child StudyTermsId
parent StudyTermsId
UniqueStudySubTerms child parent
StudyTermNameCandidate -- No one at LMU is willing and able to tell us the meaning of the keys for StudyDegrees and StudyTerms.
-- Each LDAP login provides an unordered set of keys and an unordered set of plain text description with an unknown 1-1 correspondence. -- Each LDAP login provides an unordered set of keys and an unordered set of plain text description with an unknown 1-1 correspondence.
-- This table helps us to infer which key belongs to which plain text by recording possible combinations at login. -- This table helps us to infer which key belongs to which plain text by recording possible combinations at login.
-- If a login provides n keys and n plan texts, then n^2 rows with the same incidence are created, storing all combinations -- If a login provides n keys and n plan texts, then n^2 rows with the same incidence are created, storing all combinations
incidence TermCandidateIncidence -- random id, generated once per login to associate matching pairs incidence TermCandidateIncidence -- random id, generated once per login to associate matching pairs
key Int -- a possible key for the studyTermName key Int -- a possible key for the studyTermName or studySubTermName
name Text -- studyTermName as plain text from LDAP name Text -- studyTermName as plain text from LDAP
deriving Show Eq Ord deriving Show Eq Ord
StudySubTermParentCandidate
incidence TermCandidateIncidence
key Int
parent Int
deriving Show Eq Ord
StudyTermStandaloneCandidate
incidence TermCandidateIncidence
key Int
deriving Show Eq Ord
UserGroupMember UserGroupMember
group UserGroupName group UserGroupName
@ -91,4 +108,4 @@ UserGroupMember
UniquePrimaryUserGroupMember group primary !force UniquePrimaryUserGroupMember group primary !force
UniqueUserGroupMember group user UniqueUserGroupMember group user

View File

@ -68,7 +68,7 @@ dependencies:
- cereal - cereal
- mtl - mtl
- sandi - sandi
- esqueleto - esqueleto >=3.1.0
- mime-types - mime-types
- generic-deriving - generic-deriving
- blaze-html - blaze-html

View File

@ -7,7 +7,7 @@ module Auth.LDAP
, ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName , ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName
, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname , ldapUserMatriculation, ldapUserFirstName, ldapUserSurname
, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName , ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName
, ldapUserSchoolAssociation, ldapSex , ldapUserSchoolAssociation, ldapUserSubTermsSemester, ldapSex
) where ) where
import Import.NoFoundation import Import.NoFoundation
@ -62,7 +62,7 @@ findUser LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not
, Ldap.derefAliases Ldap.DerefAlways , Ldap.derefAliases Ldap.DerefAlways
] ]
ldapUserPrincipalName, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName, ldapUserSchoolAssociation, ldapSex :: Ldap.Attr ldapUserPrincipalName, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName, ldapUserSchoolAssociation, ldapSex, ldapUserSubTermsSemester :: Ldap.Attr
ldapUserPrincipalName = Ldap.Attr "userPrincipalName" ldapUserPrincipalName = Ldap.Attr "userPrincipalName"
ldapUserDisplayName = Ldap.Attr "displayName" ldapUserDisplayName = Ldap.Attr "displayName"
ldapUserMatriculation = Ldap.Attr "LMU-Stud-Matrikelnummer" ldapUserMatriculation = Ldap.Attr "LMU-Stud-Matrikelnummer"
@ -70,9 +70,10 @@ ldapUserFirstName = Ldap.Attr "givenName"
ldapUserSurname = Ldap.Attr "sn" ldapUserSurname = Ldap.Attr "sn"
ldapUserTitle = Ldap.Attr "title" ldapUserTitle = Ldap.Attr "title"
ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy" ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy"
ldapUserFieldName = Ldap.Attr "dfnEduPersonFieldOfStudyString" ldapUserFieldName = Ldap.Attr "LMU-Stg-Fach"
ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString" ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString"
ldapSex = Ldap.Attr "schacGender" ldapSex = Ldap.Attr "schacGender"
ldapUserSubTermsSemester = Ldap.Attr "LMU-Stg-FachundFS"
ldapUserEmail :: NonEmpty Ldap.Attr ldapUserEmail :: NonEmpty Ldap.Attr
ldapUserEmail = Ldap.Attr "mail" :| ldapUserEmail = Ldap.Attr "mail" :|

View File

@ -20,6 +20,7 @@ module Database.Esqueleto.Utils
, maybe , maybe
, SqlProject(..) , SqlProject(..)
, (->.) , (->.)
, fromSqlKey
, module Database.Esqueleto.Utils.TH , module Database.Esqueleto.Utils.TH
) where ) where
@ -250,3 +251,6 @@ instance (PersistEntity val, PersistField typ) => SqlProject val typ (Maybe (E.E
(->.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value b) (->.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value b)
(->.) expr t = E.unsafeSqlBinOp "->" expr $ E.val t (->.) expr t = E.unsafeSqlBinOp "->" expr $ E.val t
fromSqlKey :: (ToBackendKey SqlBackend entity, PersistField (Key entity)) => E.SqlExpr (E.Value (Key entity)) -> E.SqlExpr (E.Value Int64)
fromSqlKey = E.veryUnsafeCoerceSqlExprValue

View File

@ -1,8 +1,10 @@
{-# LANGUAGE UndecidableInstances #-}
module Database.Esqueleto.Utils.TH module Database.Esqueleto.Utils.TH
( SqlIn(..) ( SqlIn(..)
, sqlInTuple, sqlInTuples , sqlInTuple, sqlInTuples
, unValueN, unValueNIs , unValueN, unValueNIs
, sqlIJproj, sqlLOJproj , sqlIJproj, sqlLOJproj, sqlFOJproj
) where ) where
import ClassyPrelude import ClassyPrelude
@ -21,8 +23,17 @@ import Utils.TH
class E.SqlSelect a r => SqlIn a r | a -> r, r -> a where class E.SqlSelect a r => SqlIn a r | a -> r, r -> a where
sqlIn :: a -> [r] -> E.SqlExpr (E.Value Bool) sqlIn :: a -> [r] -> E.SqlExpr (E.Value Bool)
instance PersistField a => SqlIn (E.SqlExpr (E.Value a)) (E.Value a) where instance SqlEq a => SqlIn (E.SqlExpr (E.Value a)) (E.Value a) where
x `sqlIn` xs = x `E.in_` E.valList (map E.unValue xs) sqlIn x = foldr (\x' e -> e E.||. sqlEq (E.val $ E.unValue x') x) (E.val False)
class PersistField a => SqlEq a where
sqlEq :: E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Bool)
instance {-# OVERLAPPABLE #-} PersistField a => SqlEq a where
sqlEq = (E.==.)
instance PersistField a => SqlEq (Maybe a) where
sqlEq a b = (E.isNothing a E.&&. E.isNothing b) E.||. a E.==. b
sqlInTuples :: [Int] -> DecsQ sqlInTuples :: [Int] -> DecsQ
sqlInTuples = mapM sqlInTuple sqlInTuples = mapM sqlInTuple
@ -35,10 +46,10 @@ sqlInTuple arity = do
xsV <- newName "xs" xsV <- newName "xs"
let let
matchE = lam1E (tupP $ map (\vV -> conP 'E.Value [varP vV]) vVs) (foldr1 (\e1 e2 -> [e|$(e1) E.&&. $(e2)|]) . map (\(varE -> vE, varE -> xE) -> [e|E.val $(vE) E.==. $(xE)|]) $ zip vVs xVs) matchE = lam1E (tupP $ map (\vV -> conP 'E.Value [varP vV]) vVs) (foldr1 (\e1 e2 -> [e|$(e1) E.&&. $(e2)|]) . map (\(varE -> vE, varE -> xE) -> [e|E.val $(vE) `sqlEq` $(xE)|]) $ zip vVs xVs)
tupTy f = foldl (\typ v -> typ `appT` f (varT v)) (tupleT arity) tyVars tupTy f = foldl (\typ v -> typ `appT` f (varT v)) (tupleT arity) tyVars
instanceD (cxt $ map (\v -> [t|PersistField $(varT v)|]) tyVars) [t|SqlIn $(tupTy $ \v -> [t|E.SqlExpr (E.Value $(v))|]) $(tupTy $ \v -> [t|E.Value $(v)|])|] instanceD (cxt $ map (\v -> [t|SqlEq $(varT v)|]) tyVars) [t|SqlIn $(tupTy $ \v -> [t|E.SqlExpr (E.Value $(v))|]) $(tupTy $ \v -> [t|E.Value $(v)|])|]
[ funD 'sqlIn [ funD 'sqlIn
[ clause [tupP $ map varP xVs, varP xsV] [ clause [tupP $ map varP xVs, varP xsV]
( guardedB ( guardedB
@ -84,3 +95,6 @@ sqlIJproj = leftAssociativePairProjection 'E.InnerJoin
sqlLOJproj :: Int -> Int -> ExpQ sqlLOJproj :: Int -> Int -> ExpQ
sqlLOJproj = leftAssociativePairProjection 'E.LeftOuterJoin sqlLOJproj = leftAssociativePairProjection 'E.LeftOuterJoin
sqlFOJproj :: Int -> Int -> ExpQ
sqlFOJproj = leftAssociativePairProjection 'E.FullOuterJoin

View File

@ -34,6 +34,3 @@ uniqueToMap = fmap Map.fromList $ zip <$> persistUniqueToFieldNames <*> persistU
instance PersistEntity record => Eq (Unique record) where instance PersistEntity record => Eq (Unique record) where
(==) = (==) `on` uniqueToMap (==) = (==) `on` uniqueToMap
instance PersistEntity record => Show (Unique record) where
showsPrec p = showsPrec p . uniqueToMap

View File

@ -47,6 +47,7 @@ import qualified Data.Map as Map
import qualified Data.HashSet as HashSet import qualified Data.HashSet as HashSet
import Data.List (nubBy, (!!), findIndex, inits) import Data.List (nubBy, (!!), findIndex, inits)
import qualified Data.List as List
import Web.Cookie import Web.Cookie
@ -3390,36 +3391,113 @@ upsertCampusUser ldapData Creds{..} = do
Right str <- return $ Text.decodeUtf8' v' Right str <- return $ Text.decodeUtf8' v'
return str return str
fs <- either (throwM . CampusUserInvalidFeaturesOfStudy . tshow) return userStudyFeatures userSubTermsSemesters = forM userSubTermsSemesters' parseSubTermsSemester
userSubTermsSemesters' = do
(k, v) <- ldapData
guard $ k == ldapUserSubTermsSemester
v' <- v
Right str <- return $ Text.decodeUtf8' v'
return str
fs' <- either (throwM . CampusUserInvalidFeaturesOfStudy . tshow) return userStudyFeatures
sts <- either (throwM . CampusUserInvalidFeaturesOfStudy . tshow) return userSubTermsSemesters
let let
studyTermCandidates = Set.fromList $ do studyTermCandidates = Set.fromList $ do
name <- termNames let sfKeys = unStudyTermsKey . studyFeaturesField <$> fs'
StudyFeatures{ studyFeaturesField = StudyTermsKey' key } <- fs subTermsKeys = unStudyTermsKey . fst <$> sts
return (key, name)
(,) <$> sfKeys ++ subTermsKeys <*> termNames
let
assimilateSubTerms :: [(StudyTermsId, Int)] -> [StudyFeatures] -> WriterT (Set (StudyTermsId, Maybe StudyTermsId)) DB [StudyFeatures]
assimilateSubTerms [] xs = return xs
assimilateSubTerms ((subterm, subSemester) : subterms) unusedFeats = do
standalone <- lift $ get subterm
case standalone of
_other
| (match : matches, unusedFeats') <- partition
(\StudyFeatures{..} -> subterm == studyFeaturesField
&& subSemester == studyFeaturesSemester
) unusedFeats
-> do
$logDebugS "Campus" [st|Ignoring subterm #{tshow subterm} and matching feature #{tshow match}|]
(:) match <$> assimilateSubTerms subterms (matches ++ unusedFeats')
| any ((== subterm) . studyFeaturesField) unusedFeats
-> do
$logDebugS "Campus" [st|Ignoring subterm #{tshow subterm} due to feature of matching field|]
assimilateSubTerms subterms unusedFeats
Just StudyTerms{..}
| Just defDegree <- studyTermsDefaultDegree
, Just defType <- studyTermsDefaultType
-> do
$logDebugS "Campus" [st|Applying default for standalone study term #{tshow subterm}|]
(:) (StudyFeatures userId defDegree subterm Nothing defType subSemester now True) <$> assimilateSubTerms subterms unusedFeats
Nothing
| [] <- unusedFeats -> do
$logDebugS "Campus" [st|Saw subterm #{tshow subterm} when no fos-terms remain|]
tell $ Set.singleton (subterm, Nothing)
assimilateSubTerms subterms []
_other -> do
knownParents <- lift $ map (studySubTermsParent . entityVal) <$> selectList [ StudySubTermsChild ==. subterm ] []
let matchingFeatures = case knownParents of
[] -> filter ((== subSemester) . studyFeaturesSemester) unusedFeats
ps -> filter (\StudyFeatures{studyFeaturesField, studyFeaturesSemester} -> any (== studyFeaturesField) ps && studyFeaturesSemester == subSemester) unusedFeats
when (null knownParents) . forM_ matchingFeatures $ \StudyFeatures{..} ->
tell $ Set.singleton (subterm, Just studyFeaturesField)
if
| not $ null knownParents -> do
$logDebugS "Campus" [st|Applying subterm #{tshow subterm} to #{tshow matchingFeatures}|]
(++) (matchingFeatures & traverse . _studyFeaturesSubField %~ (<|> Just subterm)) <$> assimilateSubTerms subterms (unusedFeats List.\\ matchingFeatures)
| otherwise -> do
$logDebugS "Campus" [st|Ignoring subterm #{tshow subterm}|]
assimilateSubTerms subterms unusedFeats
$logDebugS "Campus" [st|Terms for #{credsIdent}: #{tshow (sts, fs')}|]
(fs, studyFieldParentCandidates) <- runWriterT $ assimilateSubTerms sts fs'
let
studyTermCandidateIncidence studyTermCandidateIncidence
= fromMaybe (error "Could not convert studyTermCandidateIncidence-Hash to UUID") -- Should never happen = fromMaybe (error "Could not convert studyTermCandidateIncidence-Hash to UUID") -- Should never happen
. UUID.fromByteString . UUID.fromByteString
. fromStrict . fromStrict
. (convert :: Digest (SHAKE128 128) -> ByteString) . (convert :: Digest (SHAKE128 128) -> ByteString)
. runConduitPure . runConduitPure
$ sourceList (toStrict . Binary.encode <$> Set.toList studyTermCandidates) .| sinkHash $ sourceList ((toStrict . Binary.encode <$> Set.toList studyTermCandidates) ++ (toStrict . Binary.encode <$> Set.toList studyFieldParentCandidates)) .| sinkHash
candidatesRecorded <- E.selectExists . E.from $ \candidate -> candidatesRecorded <- E.selectExists . E.from $ \(candidate `E.FullOuterJoin` parentCandidate `E.FullOuterJoin` standaloneCandidate) -> do
E.where_ $ candidate E.^. StudyTermCandidateIncidence E.==. E.val studyTermCandidateIncidence E.on $ candidate E.?. StudyTermNameCandidateIncidence E.==. standaloneCandidate E.?. StudyTermStandaloneCandidateIncidence
E.on $ candidate E.?. StudyTermNameCandidateIncidence E.==. parentCandidate E.?. StudySubTermParentCandidateIncidence
E.where_ $ candidate E.?. StudyTermNameCandidateIncidence E.==. E.just (E.val studyTermCandidateIncidence)
E.||. parentCandidate E.?. StudySubTermParentCandidateIncidence E.==. E.just (E.val studyTermCandidateIncidence)
E.||. standaloneCandidate E.?. StudyTermStandaloneCandidateIncidence E.==. E.just (E.val studyTermCandidateIncidence)
unless candidatesRecorded $ do unless candidatesRecorded $ do
let let
studyTermCandidates' = do studyTermCandidates' = do
(studyTermCandidateKey, studyTermCandidateName) <- Set.toList studyTermCandidates (studyTermNameCandidateKey, studyTermNameCandidateName) <- Set.toList studyTermCandidates
return StudyTermCandidate{..} let studyTermNameCandidateIncidence = studyTermCandidateIncidence
return StudyTermNameCandidate{..}
insertMany_ studyTermCandidates' insertMany_ studyTermCandidates'
let
studySubTermParentCandidates' = do
(StudyTermsKey' studySubTermParentCandidateKey, Just (StudyTermsKey' studySubTermParentCandidateParent)) <- Set.toList studyFieldParentCandidates
let studySubTermParentCandidateIncidence = studyTermCandidateIncidence
return StudySubTermParentCandidate{..}
insertMany_ studySubTermParentCandidates'
let
studyTermStandaloneCandidates' = do
(StudyTermsKey' studyTermStandaloneCandidateKey, Nothing) <- Set.toList studyFieldParentCandidates
let studyTermStandaloneCandidateIncidence = studyTermCandidateIncidence
return StudyTermStandaloneCandidate{..}
insertMany_ studyTermStandaloneCandidates'
E.updateWhere [StudyFeaturesUser ==. userId] [StudyFeaturesValid =. False] E.updateWhere [StudyFeaturesUser ==. userId] [StudyFeaturesValid =. False]
forM_ fs $ \f@StudyFeatures{..} -> do forM_ fs $ \f@StudyFeatures{..} -> do
insertMaybe studyFeaturesDegree $ StudyDegree (unStudyDegreeKey studyFeaturesDegree) Nothing Nothing insertMaybe studyFeaturesDegree $ StudyDegree (unStudyDegreeKey studyFeaturesDegree) Nothing Nothing
insertMaybe studyFeaturesField $ StudyTerms (unStudyTermsKey studyFeaturesField) Nothing Nothing insertMaybe studyFeaturesField $ StudyTerms (unStudyTermsKey studyFeaturesField) Nothing Nothing Nothing Nothing
void $ upsert f [StudyFeaturesUpdated =. now, StudyFeaturesValid =. True] void $ upsert f [StudyFeaturesUpdated =. now, StudyFeaturesValid =. True, StudyFeaturesSubField =. studyFeaturesSubField]
associateUserSchoolsByTerms userId associateUserSchoolsByTerms userId
let let

View File

@ -1,507 +1,18 @@
module Handler.Admin where module Handler.Admin
( module Handler.Admin
) where
import Import import Import
import Handler.Utils import Handler.Utils
import Jobs
import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder)
import Control.Monad.Trans.Except import Handler.Admin.Test as Handler.Admin
import Control.Monad.Trans.Writer (mapWriterT) import Handler.Admin.ErrorMessage as Handler.Admin
import Handler.Admin.StudyFeatures as Handler.Admin
-- import Data.Time
import Data.Char (isDigit)
import qualified Data.Text as Text
-- import Data.Function ((&))
-- import Yesod.Form.Bootstrap3
import qualified Data.Set as Set
import qualified Data.Map as Map
import Database.Persist.Sql (fromSqlKey)
import qualified Database.Esqueleto as E
import Database.Esqueleto.Utils (mkExactFilter, mkContainsFilter)
import qualified Handler.Utils.TermCandidates as Candidates
-- import Colonnade hiding (fromMaybe)
-- import Yesod.Colonnade
-- import qualified Data.UUID.Cryptographic as UUID
getAdminR :: Handler Html getAdminR :: Handler Html
getAdminR = -- do getAdminR =
siteLayoutMsg MsgAdminHeading $ do siteLayoutMsg MsgAdminHeading $ do
setTitleI MsgAdminHeading setTitleI MsgAdminHeading
i18n MsgAdminPageEmpty i18n MsgAdminPageEmpty
-- BEGIN - Buttons needed only here
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonCreate
instance Finite ButtonCreate
nullaryPathPiece ''ButtonCreate camelToPathPiece
instance Button UniWorX ButtonCreate where
btnLabel CreateMath = [whamlet|Ma<i>thema</i>tik|]
btnLabel CreateInf = "Informatik"
btnClasses CreateMath = [BCIsButton, BCInfo]
btnClasses CreateInf = [BCIsButton, BCPrimary]
-- END Button needed only here
emailTestForm :: AForm (HandlerFor UniWorX) (Email, MailContext)
emailTestForm = (,)
<$> areq emailField (fslI MsgMailTestFormEmail) Nothing
<*> ( MailContext
<$> (Languages <$> areq (reorderField appLanguagesOpts) (fslI MsgMailTestFormLanguages) Nothing)
<*> (toMailDateTimeFormat
<$> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) Nothing
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) Nothing
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) Nothing
)
)
where
toMailDateTimeFormat dt d t = \case
SelFormatDateTime -> dt
SelFormatDate -> d
SelFormatTime -> t
makeDemoForm :: Int -> Form (Int,Bool,Double)
makeDemoForm n = identifyForm ("adminTestForm" :: Text) $ \html -> do
(result, widget) <- flip (renderAForm FormStandard) html $ (,,)
<$> areq (minIntFieldI n ("Zahl" :: Text)) (fromString $ "Ganzzahl > " ++ show n) Nothing
<* aformSection MsgFormBehaviour
<*> areq checkBoxField "Muss nächste Zahl größer sein?" (Just True)
<*> areq doubleField "Fliesskommazahl" Nothing
-- NO LONGER DESIRED IN AFORMS:
-- <* submitButton
return $ case result of
FormSuccess fsres
| errorMsgs <- validateResult fsres
, not $ null errorMsgs -> (FormFailure errorMsgs, widget)
_otherwise -> (result, widget)
where
validateResult :: (Int,Bool,Double) -> [Text]
validateResult (i,True,d) | fromIntegral i >= d = [tshow d <> " ist nicht größer als " <> tshow i, "Zweite Fehlermeldung", "Dritte Fehlermeldung"]
validateResult _other = []
getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden!
getAdminTestR = postAdminTestR
postAdminTestR = do
((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm ("buttons" :: Text) (buttonForm :: Form ButtonCreate)
let btnForm = wrapForm btnWdgt def
{ formAction = Just $ SomeRoute AdminTestR
, formEncoding = btnEnctype
, formSubmit = FormNoSubmit
}
case btnResult of
(FormSuccess CreateInf) -> addMessage Info "Informatik-Knopf gedrückt"
(FormSuccess CreateMath) -> addMessage Warning "Knopf Mathematik erkannt"
FormMissing -> return ()
_other -> addMessage Warning "KEIN Knopf erkannt"
((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm ("email" :: Text) $ renderAForm FormStandard emailTestForm
formResultModal emailResult AdminTestR $ \(email, ls) -> do
jId <- mapWriterT runDB $ do
jId <- queueJob $ JobSendTestEmail email ls
tell . pure $ Message Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] (Just IconEmail)
return jId
runReaderT (writeJobCtl $ JobCtlPerform jId) =<< getYesod
addMessage Warning [shamlet|Inkorrekt ausgegebener Alert|] -- For testing alert handling when short circuiting; for proper (not fallback-solution) handling always use `tell` within `formResultModal`
let emailWidget' = wrapForm emailWidget def
{ formAction = Just . SomeRoute $ AdminTestR
, formEncoding = emailEnctype
, formAttrs = [("uw-async-form", "")]
}
let demoFormAction (_i,_b,_d) = addMessage Info "All ok."
((demoResult, formWidget),formEnctype) <- runFormPost $ makeDemoForm 7
formResult demoResult demoFormAction
let showDemoResult = [whamlet|
$maybe (i,b,d) <- formResult' demoResult
Received values:
<ul>
<li>#{show i}
<li>#{show b}
<li>#{show d}
$nothing
No form values received, due to #
$# Using formResult' above means that we usually to not distinguish the following two cases here, sind formResult does this already:
$case demoResult
$of FormSuccess _
$# Already dealt with above, to showecase usage of formResult' as normally done.
success, which should not happen here.
$of FormMissing
Form data missing, probably empty.
$of FormFailure msgs
<ul>
$forall m <- msgs
<li>#{m}
|]
let testTooltipMsg = toWidget [whamlet| So sehen aktuell Tooltips via iconTooltip aus. |] :: WidgetFor UniWorX ()
msgInfoTooltip <- messageI Info ("Info-Tooltip via messageI" :: Text)
msgSuccessTooltip <- messageI Success ("Success-Tooltip via messageI" :: Text)
msgWarningTooltip <- messageI Warning ("Warning-Tooltip via messageI" :: Text)
msgErrorTooltip <- messageI Error ("Error-Tooltip via messageI" :: Text)
msgNonDefaultIconTooltip <- messageIconI Info IconEmail ("Info-Tooltip mit lustigem Icon" :: Text)
{- The following demonstrates the use of @massInput@.
@massInput@ takes as arguments:
- A configuration struct describing how the Widget should behave (how is the space of sub-forms structured, how many dimensions does it have, which additions/deletions are permitted, what data do they need to operate and what should their effect on the overall shape be?)
- Information on how the resulting field fits into the form as a whole (@FieldSettings@ and whether the @massInput@ should be marked required)
- An initial value to pre-fill the field with
@massInput@ then returns an @MForm@ structured for easy downstream consumption of the result
-}
let
-- We define the fields of the configuration struct @MassInput@:
-- | Make a form for adding a point/line/plane/hyperplane/... (in this case: cell)
--
-- This /needs/ to replace all occurrences of @mreq@ with @mpreq@ (no fields should be /actually/ required)
mkAddForm :: ListPosition -- ^ Approximate position of the add-widget
-> Natural -- ^ Dimension Index, outermost dimension ist 0 i.e. if dimension is 3 hyperplane-adders get passed 0, planes get passed 1, lines get 2, and points get 3
-> (Text -> Text) -- ^ Nudge deterministic field ids so they're unique
-> FieldView UniWorX -- ^ Submit-Button for this add-widget
-> Maybe (Form (Map ListPosition Int -> FormResult (Map ListPosition Int))) -- ^ Nothing iff adding further cells in this position/dimension makes no sense; returns callback to determine index of new cells and data needed to initialize cells
mkAddForm 0 0 nudge submitBtn = Just $ \csrf -> do
(addRes, addView) <- mpreq textField ("" & addName (nudge "text")) Nothing -- Any old field; for demonstration
let addRes' = fromMaybe 0 . readMay . Text.filter isDigit <$> addRes -- Do something semi-interesting on the result of the @textField@ to demonstrate that further processing can be done
addRes'' = addRes' <&> \dat prev -> FormSuccess (Map.singleton (maybe 0 succ . fmap fst $ Map.lookupMax prev) dat) -- Construct the callback to determine new cell positions and data within @FormResult@ as required, nested @FormResult@ allows aborting the add depending on previous data
return (addRes'', toWidget csrf >> fvInput addView >> fvInput submitBtn)
mkAddForm _pos _dim _ _ = error "Dimension and Position is always 0 for our 1-dimensional form"
-- | Make a single massInput-Cell
--
-- This /needs/ to use @nudge@ and deterministic field naming (this allows for correct value-shifting when cells are deleted)
mkCellForm :: ListPosition -- ^ Position of this cell
-> Int -- ^ Data needed to initialize the cell (see return of @mkAddForm@)
-> Maybe Int -- ^ Initial cell result from Argument to `massInput`
-> (Text -> Text) -- ^ Nudge deterministic field ids so they're unique
-> Form Int
mkCellForm _pos cData initial nudge csrf = do -- Extremely simple cell
(intRes, intView) <- mreq intField ("" & addName (nudge "int")) $ initial <|> Just cData
return (intRes, toWidget csrf >> fvInput intView)
-- | How does the shape (`ListLength`) change if a certain cell is deleted?
deleteCell :: Map ListPosition Int -- ^ Current shape, including initialisation data
-> ListPosition -- ^ Coordinate to delete
-> MaybeT (MForm (HandlerFor UniWorX)) (Map ListPosition ListPosition) -- ^ Monadically compute a set of new positions and their associated old positions
deleteCell = miDeleteList
-- | Make a decision on whether an add widget should be allowed to further cells, given the current @liveliness@ (i.e. before performing the addition)
allowAdd :: ListPosition -> Natural -> ListLength -> Bool
allowAdd _ _ l = l < 7 -- Limit list length; much more complicated checks are possible (this could in principle be monadic, but @massInput@ is probably already complicated enough to cover just current (2019-03) usecases)
-- | Where to send the user when they click a shape-changing button, given the id of the Wrapper of the `massInput`-`Widget`
buttonAction :: PathPiece p => p -> Maybe (SomeRoute UniWorX)
buttonAction frag = Just . SomeRoute $ AdminTestR :#: frag
-- The actual call to @massInput@ is comparatively simple:
((miResult, fvInput -> miForm), miEnc) <- runFormPost . identifyForm ("massinput" :: Text) $ massInput (MassInput mkAddForm mkCellForm deleteCell allowAdd (\_ _ _ -> Set.empty) buttonAction defaultMiLayout ("massinput" :: Text)) "" True Nothing
let locallyDefinedPageHeading = [whamlet|Admin TestPage for Uni2work|]
siteLayout locallyDefinedPageHeading $ do
-- defaultLayout $ do
setTitle "Uni2work Admin Testpage"
$(i18nWidgetFile "admin-test")
[whamlet|<h2>Formular Demonstration|]
wrapForm formWidget FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AdminTestR :#: FIDAdminDemo
, formEncoding = formEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Just FIDAdminDemo
}
showDemoResult
miIdent <- newIdent
let miForm' = wrapForm miForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AdminTestR :#: miIdent
, formEncoding = miEnc
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Just miIdent
}
[whamlet|
<h2>Mass-Input
^{miForm'}
$case miResult
$of FormMissing
$of FormFailure errs
<ul>
$forall err <- errs
<li>#{err}
$of FormSuccess res
<p style="white-space:pre-wrap; font-family:monospace;">
#{tshow res}
|]
getAdminErrMsgR, postAdminErrMsgR :: Handler Html
getAdminErrMsgR = postAdminErrMsgR
postAdminErrMsgR = do
MsgRenderer mr <- getMsgRenderer
((ctResult, ctView), ctEncoding) <- runFormPost . renderAForm FormStandard $
unTextarea <$> areq textareaField (fslpI MsgErrMsgCiphertext (mr MsgErrMsgCiphertext)) Nothing
plaintext <- formResultMaybe ctResult $ exceptT (\err -> Nothing <$ addMessageI Error err) (return . Just) . (encodedSecretBoxOpen :: Text -> ExceptT EncodedSecretBoxException Handler Value)
let ctView' = wrapForm ctView def{ formAction = Just . SomeRoute $ AdminErrMsgR, formEncoding = ctEncoding }
defaultLayout
[whamlet|
$maybe t <- plaintext
<pre style="white-space:pre-wrap; font-family:monospace">
$case t
$of String t'
#{t'}
$of t'
#{encodePrettyToTextBuilder t'}
^{ctView'}
|]
-- BEGIN - Buttons needed only for StudyTermCandidateManagement
data ButtonAdminStudyTerms
= BtnCandidatesInfer
| BtnCandidatesDeleteConflicts
| BtnCandidatesDeleteAll
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAdminStudyTerms
instance Finite ButtonAdminStudyTerms
nullaryPathPiece ''ButtonAdminStudyTerms camelToPathPiece
embedRenderMessage ''UniWorX ''ButtonAdminStudyTerms id
instance Button UniWorX ButtonAdminStudyTerms where
btnClasses BtnCandidatesInfer = [BCIsButton, BCPrimary]
btnClasses BtnCandidatesDeleteConflicts = [BCIsButton, BCDanger]
btnClasses BtnCandidatesDeleteAll = [BCIsButton, BCDanger]
-- END Button needed only here
getAdminFeaturesR, postAdminFeaturesR :: Handler Html
getAdminFeaturesR = postAdminFeaturesR
postAdminFeaturesR = do
uid <- requireAuthId
((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm ("infer-button" :: Text) (buttonForm :: Form ButtonAdminStudyTerms)
let btnForm = wrapForm btnWdgt def
{ formAction = Just $ SomeRoute AdminFeaturesR
, formEncoding = btnEnctype
, formSubmit = FormNoSubmit
}
infConflicts <- case btnResult of
FormSuccess BtnCandidatesInfer -> do
(infConflicts, infAmbiguous, infRedundant, infAccepted) <- Candidates.inferHandler
unless (null infAmbiguous) . addMessageI Info . MsgAmbiguousCandidatesRemoved $ length infAmbiguous
unless (null infRedundant) . addMessageI Info . MsgRedundantCandidatesRemoved $ length infRedundant
let newKeys = map (StudyTermsKey' . fst) infAccepted
setSessionJson SessionNewStudyTerms newKeys
if | null infAccepted
-> addMessageI Info MsgNoCandidatesInferred
| otherwise
-> addMessageI Success . MsgCandidatesInferred $ length infAccepted
return infConflicts
FormSuccess BtnCandidatesDeleteConflicts -> runDB $ do
confs <- Candidates.conflicts
incis <- Candidates.getIncidencesFor (entityKey <$> confs)
deleteWhere [StudyTermCandidateIncidence <-. (E.unValue <$> incis)]
addMessageI Success $ MsgIncidencesDeleted $ length incis
return []
FormSuccess BtnCandidatesDeleteAll -> runDB $ do
deleteWhere ([] :: [Filter StudyTermCandidate])
addMessageI Success MsgAllIncidencesDeleted
Candidates.conflicts
_other -> runDB Candidates.conflicts
newStudyTermKeys <- fromMaybe [] <$> lookupSessionJson SessionNewStudyTerms
( (degreeResult,degreeTable)
, (studyTermsResult,studytermsTable)
, ((), candidateTable)
, userSchools) <- runDB $ do
schools <- E.select . E.from $ \school -> do
E.where_ . E.exists . E.from $ \schoolFunction ->
E.where_ $ schoolFunction E.^. UserFunctionSchool E.==. school E.^. SchoolId
E.&&. schoolFunction E.^. UserFunctionUser E.==. E.val uid
E.&&. schoolFunction E.^. UserFunctionFunction E.==. E.val SchoolAdmin
return school
(,,,)
<$> mkDegreeTable
<*> mkStudytermsTable (Set.fromList newStudyTermKeys)
(Set.fromList $ map entityKey infConflicts)
(Set.fromList schools)
<*> mkCandidateTable
<*> pure schools
-- This needs to happen after calls to `dbTable` so they can short-circuit correctly
unless (null infConflicts) $ addMessageI Warning MsgStudyFeatureConflict
let degreeResult' :: FormResult (Map (Key StudyDegree) (Maybe Text, Maybe Text))
degreeResult' = degreeResult <&> getDBFormResult
(\row -> ( row ^. _dbrOutput . _entityVal . _studyDegreeName
, row ^. _dbrOutput . _entityVal . _studyDegreeShorthand
))
updateDegree degreeKey (name,short) = update degreeKey [StudyDegreeName =. name, StudyDegreeShorthand =. short]
formResult degreeResult' $ \res -> do
void . runDB $ Map.traverseWithKey updateDegree res
addMessageI Success MsgStudyDegreeChangeSuccess
let studyTermsResult' :: FormResult (Map (Key StudyTerms) (Maybe Text, Maybe Text, Set SchoolId))
studyTermsResult' = studyTermsResult <&> getDBFormResult
(\row -> ( row ^. _dbrOutput . _1 . _entityVal . _studyTermsName
, row ^. _dbrOutput . _1 . _entityVal . _studyTermsShorthand
, row ^. _dbrOutput . _2
))
updateStudyTerms studyTermsKey (name,short,schools) = do
update studyTermsKey [StudyTermsName =. name, StudyTermsShorthand =. short]
forM_ schools $ \ssh -> void . insertUnique $ SchoolTerms ssh studyTermsKey
deleteWhere [SchoolTermsTerms ==. studyTermsKey, SchoolTermsSchool /<-. Set.toList schools, SchoolTermsSchool <-. toListOf (folded . _entityKey) userSchools]
formResult studyTermsResult' $ \res -> do
void . runDB $ Map.traverseWithKey updateStudyTerms res
addMessageI Success MsgStudyTermsChangeSuccess
siteLayoutMsg MsgAdminFeaturesHeading $ do
setTitleI MsgAdminFeaturesHeading
$(widgetFile "adminFeatures")
where
textInputCell :: Ord i
=> Lens' a (Maybe Text)
-> Getter (DBRow r) (Maybe Text)
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
textInputCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
(\row _mkUnique -> (\(res,fieldView) -> (set lensRes . assertM (not . Text.null) <$> res, fvInput fieldView))
<$> mopt textField "" (Just $ row ^. lensDefault)
)
checkboxCell :: Ord i
=> Lens' a Bool
-> Getter (DBRow r) Bool
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
checkboxCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView))
<$> mpopt checkBoxField "" (Just $ row ^. lensDefault)
)
mkDegreeTable :: DB (FormResult (DBFormResult (Key StudyDegree) (Maybe Text, Maybe Text) (DBRow (Entity StudyDegree))), Widget)
mkDegreeTable =
let dbtIdent = "admin-studydegrees" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyDegree) -> E.SqlQuery (E.SqlExpr (Entity StudyDegree))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyDegreeKey)
dbtProj = return
dbtColonnade = formColonnade $ mconcat
[ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _entityVal . _studyDegreeKey))
, sortable (Just "name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName) (_dbrOutput . _entityKey))
, sortable (Just "short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand) (_dbrOutput . _entityKey))
, dbRow
]
dbtSorting = Map.fromList
[ ("key" , SortColumn (E.^. StudyDegreeKey))
, ("name" , SortColumn (E.^. StudyDegreeName))
, ("short", SortColumn (E.^. StudyDegreeShorthand))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAction = Just . SomeRoute $ AdminFeaturesR :#: ("admin-studydegrees-table-wrapper" :: Text)
}
psValidator = def -- & defaultSorting [SortAscBy "name", SortAscBy "short", SortAscBy "key"]
& defaultSorting [SortAscBy "key"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
in dbTable psValidator DBTable{..}
mkStudytermsTable :: Set (Key StudyTerms) -> Set (Key StudyTerms) -> Set (Entity School) -> DB (FormResult (DBFormResult (Key StudyTerms) (Maybe Text, Maybe Text, Set SchoolId) (DBRow (Entity StudyTerms, Set SchoolId))), Widget)
mkStudytermsTable newKeys badKeys schools =
let dbtIdent = "admin-studyterms" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyTerms) -> E.SqlQuery (E.SqlExpr (Entity StudyTerms))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyTermsKey)
dbtProj field = do
fieldSchools <- fmap (setOf $ folded . _Value) . lift . E.select . E.from $ \school -> do
E.where_ . E.exists . E.from $ \schoolTerms ->
E.where_ $ schoolTerms E.^. SchoolTermsSchool E.==. school E.^. SchoolId
E.&&. schoolTerms E.^. SchoolTermsTerms E.==. E.val (field ^. _dbrOutput . _entityKey)
E.where_ $ school E.^. SchoolShorthand `E.in_` E.valList (toListOf (folded . _entityKey . _SchoolId) schools)
return $ school E.^. SchoolId
return $ field & _dbrOutput %~ (, fieldSchools)
dbtColonnade = formColonnade $ mconcat
[ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _1 . _entityVal . _studyTermsKey))
, sortable (Just "isnew") (i18nCell MsgGenericIsNew) (isNewCell . flip Set.member newKeys . view (_dbrOutput . _1 . _entityKey))
, sortable (Just "isbad") (i18nCell MsgGenericHasConflict) (isBadCell . flip Set.member badKeys . view (_dbrOutput . _1 . _entityKey))
, sortable (Just "name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _1 . _entityVal . _studyTermsName) (_dbrOutput . _1 . _entityKey))
, sortable (Just "short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _1 . _entityVal . _studyTermsShorthand) (_dbrOutput . _1 . _entityKey))
, flip foldMap schools $ \(Entity ssh School{schoolName}) ->
sortable Nothing (cell $ toWidget schoolName) (checkboxCell (_3 . at ssh . _Maybe) (_dbrOutput . _2 . at ssh . _Maybe) (_dbrOutput . _1 . _entityKey))
, dbRow
]
dbtSorting = Map.fromList
[ ("key" , SortColumn (E.^. StudyTermsKey))
, ("isnew" , SortColumn (\studyTerm -> studyTerm E.^. StudyTermsKey `E.in_` E.valList (unStudyTermsKey <$> Set.toList newKeys))) -- works only once
-- Remember: sorting with E.in_ by StudyTermsId instead will produce esqueleto-error "unsafeSqlBinOp: non-id/composite keys not expected here"
, ("isbad" , SortColumn (\studyTerm -> studyTerm E.^. StudyTermsKey `E.in_` E.valList (unStudyTermsKey <$> Set.toList badKeys)))
, ("name" , SortColumn (E.^. StudyTermsName))
, ("short" , SortColumn (E.^. StudyTermsShorthand))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAction = Just . SomeRoute $ AdminFeaturesR :#: ("admin-studyterms-table-wrapper" :: Text)
}
psValidator = def
-- & defaultSorting [SortAscBy "name", SortAscBy "short", SortAscBy "key"]
& defaultSorting [SortDescBy "isnew", SortDescBy "isbad", SortAscBy "key"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
in dbTable psValidator DBTable{..}
mkCandidateTable =
let dbtIdent = "admin-termcandidate" :: Text
dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
dbtSQLQuery :: E.SqlExpr (Entity StudyTermCandidate) -> E.SqlQuery ( E.SqlExpr (Entity StudyTermCandidate))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyTermCandidateId)
dbtProj = return
dbtColonnade = dbColonnade $ mconcat
[ dbRow
, sortable (Just "key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermCandidateKey))
, sortable (Just "name") (i18nCell MsgStudyTermsName) (textCell . view (_dbrOutput . _entityVal . _studyTermCandidateName))
, sortable (Just "incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _entityVal . _studyTermCandidateIncidence))
]
dbtSorting = Map.fromList
[ ("key" , SortColumn (E.^. StudyTermCandidateKey))
, ("name" , SortColumn (E.^. StudyTermCandidateName))
, ("incidence", SortColumn (E.^. StudyTermCandidateIncidence))
]
dbtFilter = Map.fromList
[ ("key", FilterColumn $ mkExactFilter (E.^. StudyTermCandidateKey))
, ("name", FilterColumn $ mkContainsFilter (E.^. StudyTermCandidateName))
, ("incidence", FilterColumn $ mkExactFilter (E.^. StudyTermCandidateIncidence)) -- contains filter desired, but impossible here
]
dbtFilterUI mPrev = mconcat
-- [ prismAForm (singletonFilter "key") mPrev $ aopt intField (fslI MsgStudyTermsKey) -- Typing problem exactFilter suffices here
[ prismAForm (singletonFilter "key") mPrev $ aopt textField (fslI MsgStudyTermsKey)
, prismAForm (singletonFilter "name") mPrev $ aopt textField (fslI MsgStudyTermsName)
, prismAForm (singletonFilter "incidence") mPrev $ aopt textField (fslI MsgStudyCandidateIncidence)
]
dbtParams = def
psValidator = def & defaultSorting [SortAscBy "incidence", SortAscBy "key", SortAscBy "name"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
in dbTable psValidator DBTable{..}

View File

@ -0,0 +1,32 @@
module Handler.Admin.ErrorMessage
( getAdminErrMsgR, postAdminErrMsgR
) where
import Import
import Handler.Utils
import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder)
import Control.Monad.Trans.Except
getAdminErrMsgR, postAdminErrMsgR :: Handler Html
getAdminErrMsgR = postAdminErrMsgR
postAdminErrMsgR = do
((ctResult, ctView), ctEncoding) <- runFormPost . renderAForm FormStandard $
unTextarea <$> areq textareaField (fslpI MsgErrMsgCiphertext "Ciphertext") Nothing
plaintext <- formResultMaybe ctResult $ exceptT (\err -> Nothing <$ addMessageI Error err) (return . Just) . (encodedSecretBoxOpen :: Text -> ExceptT EncodedSecretBoxException Handler Value)
let ctView' = wrapForm ctView def{ formAction = Just . SomeRoute $ AdminErrMsgR, formEncoding = ctEncoding }
defaultLayout
[whamlet|
$maybe t <- plaintext
<pre style="white-space:pre-wrap; font-family:monospace">
$case t
$of String t'
#{t'}
$of t'
#{encodePrettyToTextBuilder t'}
^{ctView'}
|]

View File

@ -0,0 +1,528 @@
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Handler.Admin.StudyFeatures
( getAdminFeaturesR, postAdminFeaturesR
) where
import Import
import Handler.Utils
import qualified Data.Text as Text
import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified Database.Esqueleto as E
import Database.Esqueleto.Utils (mkExactFilter, mkContainsFilter)
import qualified Handler.Utils.TermCandidates as Candidates
data ButtonAdminStudyTermsNames
= BtnNameCandidatesInfer
| BtnNameCandidatesDeleteConflicts
| BtnNameCandidatesDeleteAll
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAdminStudyTermsNames
instance Finite ButtonAdminStudyTermsNames
nullaryPathPiece ''ButtonAdminStudyTermsNames $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''ButtonAdminStudyTermsNames id
instance Button UniWorX ButtonAdminStudyTermsNames where
btnClasses BtnNameCandidatesInfer = [BCIsButton, BCPrimary]
btnClasses BtnNameCandidatesDeleteConflicts = [BCIsButton, BCDanger]
btnClasses BtnNameCandidatesDeleteAll = [BCIsButton, BCDanger]
data ButtonAdminStudyTermsParents
= BtnParentCandidatesInfer
| BtnParentCandidatesDeleteAll
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAdminStudyTermsParents
instance Finite ButtonAdminStudyTermsParents
nullaryPathPiece ''ButtonAdminStudyTermsParents $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''ButtonAdminStudyTermsParents id
instance Button UniWorX ButtonAdminStudyTermsParents where
btnClasses BtnParentCandidatesInfer = [BCIsButton, BCPrimary]
btnClasses BtnParentCandidatesDeleteAll = [BCIsButton, BCDanger]
data ButtonAdminStudyTermsStandalone
= BtnStandaloneCandidatesDeleteRedundant
| BtnStandaloneCandidatesDeleteAll
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAdminStudyTermsStandalone
instance Finite ButtonAdminStudyTermsStandalone
nullaryPathPiece ''ButtonAdminStudyTermsStandalone $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''ButtonAdminStudyTermsStandalone id
instance Button UniWorX ButtonAdminStudyTermsStandalone where
btnClasses BtnStandaloneCandidatesDeleteRedundant = [BCIsButton, BCPrimary]
btnClasses BtnStandaloneCandidatesDeleteAll = [BCIsButton, BCDanger]
getAdminFeaturesR, postAdminFeaturesR :: Handler Html
getAdminFeaturesR = postAdminFeaturesR
postAdminFeaturesR = do
uid <- requireAuthId
((nameBtnResult, nameBtnWdgt), nameBtnEnctype) <- runFormPost $ identifyForm ("infer-names-button" :: Text) buttonForm
let nameBtnForm = wrapForm nameBtnWdgt def
{ formAction = Just $ SomeRoute AdminFeaturesR
, formEncoding = nameBtnEnctype
, formSubmit = FormNoSubmit
}
infNameConflicts <- case nameBtnResult of
FormSuccess BtnNameCandidatesInfer -> do
(infConflicts, infAmbiguous, infRedundantNames, infAccepted) <- Candidates.inferNamesHandler
unless (null infAmbiguous) . addMessageI Info . MsgAmbiguousNameCandidatesRemoved $ length infAmbiguous
unless (null infRedundantNames) . addMessageI Info . MsgRedundantNameCandidatesRemoved $ length infRedundantNames
unless (null infConflicts) $ do
let badKeys = map entityKey infConflicts
setSessionJson SessionConflictingStudyTerms badKeys
addMessageI Warning MsgStudyFeatureConflict
let newKeys = map fst infAccepted
setSessionJson SessionNewStudyTerms newKeys
if | null infAccepted
-> addMessageI Info MsgNoNameCandidatesInferred
| otherwise
-> addMessageI Success . MsgNameCandidatesInferred $ length infAccepted
redirect AdminFeaturesR
FormSuccess BtnNameCandidatesDeleteConflicts -> do
runDB $ do
confs <- Candidates.nameConflicts
incis <- Candidates.getNameIncidencesFor $ map entityKey confs
deleteWhere [StudyTermNameCandidateIncidence <-. (E.unValue <$> incis)]
addMessageI Success $ MsgIncidencesDeleted $ length incis
redirect AdminFeaturesR
FormSuccess BtnNameCandidatesDeleteAll -> do
runDB $ do
deleteWhere ([] :: [Filter StudyTermNameCandidate])
addMessageI Success MsgAllNameIncidencesDeleted
redirect AdminFeaturesR
_other -> runDB Candidates.nameConflicts
((parentsBtnResult, parentsBtnWdgt), parentsBtnEnctype) <- runFormPost $ identifyForm ("infer-parents-button" :: Text) buttonForm
let parentsBtnForm = wrapForm parentsBtnWdgt def
{ formAction = Just $ SomeRoute AdminFeaturesR
, formEncoding = parentsBtnEnctype
, formSubmit = FormNoSubmit
}
formResult parentsBtnResult $ \case
BtnParentCandidatesInfer -> do
(infRedundantParents, infAccepted) <- Candidates.inferParentsHandler
unless (null infRedundantParents) . addMessageI Info . MsgRedundantParentCandidatesRemoved $ length infRedundantParents
let newKeys = map (studySubTermsChild . entityVal) infAccepted
setSessionJson SessionNewStudyTerms newKeys
if | null infAccepted
-> addMessageI Info MsgNoParentCandidatesInferred
| otherwise
-> addMessageI Success . MsgParentCandidatesInferred $ length infAccepted
redirect AdminFeaturesR
BtnParentCandidatesDeleteAll -> do
runDB $ do
deleteWhere ([] :: [Filter StudySubTermParentCandidate])
addMessageI Success MsgAllParentIncidencesDeleted
redirect AdminFeaturesR
((standaloneBtnResult, standaloneBtnWdgt), standaloneBtnEnctype) <- runFormPost $ identifyForm ("infer-standalone-button" :: Text) buttonForm
let standaloneBtnForm = wrapForm standaloneBtnWdgt def
{ formAction = Just $ SomeRoute AdminFeaturesR
, formEncoding = standaloneBtnEnctype
, formSubmit = FormNoSubmit
}
formResult standaloneBtnResult $ \case
BtnStandaloneCandidatesDeleteRedundant -> do
infRedundantStandalone <- runDB Candidates.removeRedundantStandalone
unless (null infRedundantStandalone) . addMessageI Info . MsgRedundantStandaloneCandidatesRemoved $ length infRedundantStandalone
redirect AdminFeaturesR
BtnStandaloneCandidatesDeleteAll -> do
runDB $ do
deleteWhere ([] :: [Filter StudyTermStandaloneCandidate])
addMessageI Success MsgAllStandaloneIncidencesDeleted
redirect AdminFeaturesR
newStudyTermKeys <- fromMaybe [] <$> lookupSessionJson SessionNewStudyTerms
badStudyTermKeys <- lookupSessionJson SessionConflictingStudyTerms
( (degreeResult,degreeTable)
, (studyTermsResult,studytermsTable)
, ((), candidateTable)
, userSchools
, ((), parentCandidateTable)
, (standaloneResult, standaloneCandidateTable)) <- runDB $ do
schools <- E.select . E.from $ \school -> do
E.where_ . E.exists . E.from $ \schoolFunction ->
E.where_ $ schoolFunction E.^. UserFunctionSchool E.==. school E.^. SchoolId
E.&&. schoolFunction E.^. UserFunctionUser E.==. E.val uid
E.&&. schoolFunction E.^. UserFunctionFunction E.==. E.val SchoolAdmin
return school
(,,,,,)
<$> mkDegreeTable
<*> mkStudytermsTable (Set.fromList newStudyTermKeys)
(Set.fromList $ fromMaybe (map entityKey infNameConflicts) badStudyTermKeys)
(Set.fromList schools)
<*> mkCandidateTable
<*> pure schools
<*> mkParentCandidateTable
<*> mkStandaloneCandidateTable
let degreeResult' :: FormResult (Map (Key StudyDegree) (Maybe Text, Maybe Text))
degreeResult' = degreeResult <&> getDBFormResult
(\row -> ( row ^. _dbrOutput . _entityVal . _studyDegreeName
, row ^. _dbrOutput . _entityVal . _studyDegreeShorthand
))
updateDegree degreeKey (name,short) = update degreeKey [StudyDegreeName =. name, StudyDegreeShorthand =. short]
formResult degreeResult' $ \res -> do
void . runDB $ Map.traverseWithKey updateDegree res
addMessageI Success MsgStudyDegreeChangeSuccess
redirect $ AdminFeaturesR :#: ("admin-studydegrees-table-wrapper" :: Text)
let standaloneResult' :: FormResult (Map (Key StudyTermStandaloneCandidate) (Maybe StudyDegreeId, Maybe StudyFieldType))
standaloneResult' = standaloneResult <&> getDBFormResult
(\row -> ( row ^? _dbrOutput . _2 . _Just . _entityVal . _studyTermsDefaultDegree . _Just
, row ^? _dbrOutput . _2 . _Just . _entityVal . _studyTermsDefaultType . _Just
))
formResult standaloneResult' $ \res -> do
updated <- runDB . iforM res $ \candidateId (mDegree, mType) -> do
StudyTermStandaloneCandidate{..} <- getJust candidateId
let termsId = StudyTermsKey' studyTermStandaloneCandidateKey
updated <- case (,) <$> mDegree <*> mType of
Nothing -> return Nothing
Just (degree, typ) -> do
ifM (existsKey termsId)
( update termsId
[ StudyTermsDefaultDegree =. Just degree
, StudyTermsDefaultType =. Just typ
]
)
( insert_ $ StudyTerms studyTermStandaloneCandidateKey Nothing Nothing (Just degree) (Just typ)
)
return $ Just termsId
infRedundantStandalone <- Candidates.removeRedundantStandalone
unless (null infRedundantStandalone) . addMessageI Info . MsgRedundantStandaloneCandidatesRemoved $ length infRedundantStandalone
return updated
let newKeys = catMaybes $ Map.elems updated
unless (null newKeys) $ do
setSessionJson SessionNewStudyTerms newKeys
redirect $ AdminFeaturesR :#: ("admin-studyterms-table-wrapper" :: Text)
let studyTermsResult' :: FormResult (Map StudyTermsId (Maybe Text, Maybe Text, Set SchoolId, Set StudyTermsId, Maybe StudyDegreeId, Maybe StudyFieldType))
studyTermsResult' = studyTermsResult <&> getDBFormResult
(\row -> ( row ^? _dbrOutput . _1 . _entityVal . _studyTermsName . _Just
, row ^? _dbrOutput . _1 . _entityVal . _studyTermsShorthand . _Just
, row ^. _dbrOutput . _3
, row ^. _dbrOutput . _2 . to (Set.map entityKey)
, row ^? _dbrOutput . _1 . _entityVal . _studyTermsDefaultDegree . _Just
, row ^? _dbrOutput . _1 . _entityVal . _studyTermsDefaultType . _Just
))
updateStudyTerms studyTermsKey (name,short,schools,parents,degree,sType) = do
degreeExists <- fmap (fromMaybe False) . for degree $ fmap (is _Just) . get
update studyTermsKey [StudyTermsName =. name, StudyTermsShorthand =. short, StudyTermsDefaultDegree =. guard degreeExists *> degree, StudyTermsDefaultType =. sType]
forM_ schools $ \ssh -> void . insertUnique $ SchoolTerms ssh studyTermsKey
deleteWhere [SchoolTermsTerms ==. studyTermsKey, SchoolTermsSchool /<-. Set.toList schools, SchoolTermsSchool <-. toListOf (folded . _entityKey) userSchools]
forM_ parents $ void . insertUnique . StudySubTerms studyTermsKey
deleteWhere [StudySubTermsChild ==. studyTermsKey, StudySubTermsParent /<-. Set.toList parents]
formResult studyTermsResult' $ \res -> do
void . runDB $ Map.traverseWithKey updateStudyTerms res
addMessageI Success MsgStudyTermsChangeSuccess
redirect $ AdminFeaturesR :#: ("admin-studyterms-table-wrapper" :: Text)
siteLayoutMsg MsgAdminFeaturesHeading $ do
setTitleI MsgAdminFeaturesHeading
$(widgetFile "adminFeatures")
where
textInputCell :: Ord i
=> Lens' a (Maybe Text)
-> Getter (DBRow r) (Maybe Text)
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
textInputCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
(\row _mkUnique -> (\(res,fieldView) -> (set lensRes . assertM (not . Text.null) <$> res, fvInput fieldView))
<$> mopt (textField & cfStrip) "" (Just $ row ^. lensDefault)
)
checkboxCell :: Ord i
=> Lens' a Bool
-> Getter (DBRow r) Bool
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
checkboxCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView))
<$> mpopt checkBoxField "" (Just $ row ^. lensDefault)
)
-- termKeyCell :: Ord i
-- => Lens' a (Maybe StudyTermsId)
-- -> Getter (DBRow r) (Maybe StudyTermsId)
-- -> Getter (DBRow r) i
-- -> DBRow r
-- -> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
-- termKeyCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
-- ( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView))
-- <$> mopt (intField & isoField (from _StudyTermsId)) "" (Just $ row ^. lensDefault)
-- )
parentsCell :: Ord i
=> Lens' a (Set StudyTermsId)
-> Getter (DBRow r) (Set StudyTermsId)
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
parentsCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
( \row mkUnique -> (\(res, fieldView) -> (set lensRes . Set.fromList <$> res, fvInput fieldView))
<$> massInputList
(intField & isoField (from _StudyTermsId))
(const "")
(Just . SomeRoute . (AdminFeaturesR :#:))
(mkUnique ("parents" :: Text))
""
False
(Just . Set.toList $ row ^. lensDefault)
mempty
)
degreeCell :: Ord i
=> Lens' a (Maybe StudyDegreeId)
-> Getter (DBRow r) (Maybe StudyDegreeId)
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
degreeCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView))
<$> mopt degreeField "" (Just $ row ^. lensDefault)
)
fieldTypeCell :: Ord i
=> Lens' a (Maybe StudyFieldType)
-> Getter (DBRow r) (Maybe StudyFieldType)
-> Getter (DBRow r) i
-> DBRow r
-> DBCell (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult i a (DBRow r)))
fieldTypeCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex)
( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView))
<$> mopt (selectField optionsFinite) "" (Just $ row ^. lensDefault)
)
mkDegreeTable :: DB (FormResult (DBFormResult (Key StudyDegree) (Maybe Text, Maybe Text) (DBRow (Entity StudyDegree))), Widget)
mkDegreeTable =
let dbtIdent = "admin-studydegrees" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyDegree) -> E.SqlQuery (E.SqlExpr (Entity StudyDegree))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyDegreeKey)
dbtProj = return
dbtColonnade = formColonnade $ mconcat
[ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _entityVal . _studyDegreeKey))
, sortable (Just "name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName) (_dbrOutput . _entityKey))
, sortable (Just "short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand) (_dbrOutput . _entityKey))
, dbRow
]
dbtSorting = Map.fromList
[ ("key" , SortColumn (E.^. StudyDegreeKey))
, ("name" , SortColumn (E.^. StudyDegreeName))
, ("short", SortColumn (E.^. StudyDegreeShorthand))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAction = Just . SomeRoute $ AdminFeaturesR :#: ("admin-studydegrees-table-wrapper" :: Text)
}
psValidator = def -- & defaultSorting [SortAscBy "name", SortAscBy "short", SortAscBy "key"]
& defaultPagesize PagesizeAll
& defaultSorting [SortAscBy "key"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
in dbTable psValidator DBTable{..}
mkStudytermsTable :: Set StudyTermsId -> Set StudyTermsId -> Set (Entity School) -> DB (FormResult (DBFormResult StudyTermsId (Maybe Text, Maybe Text, Set SchoolId, Set StudyTermsId, Maybe StudyDegreeId, Maybe StudyFieldType) (DBRow (Entity StudyTerms, Set (Entity StudyTerms), Set SchoolId))), Widget)
mkStudytermsTable newKeys badKeys schools =
let dbtIdent = "admin-studyterms" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyTerms) -> E.SqlQuery (E.SqlExpr (Entity StudyTerms))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyTermsKey)
dbtProj field@(view _dbrOutput -> Entity fId _) = do
fieldSchools <- fmap (setOf $ folded . _Value) . lift . E.select . E.from $ \school -> do
E.where_ . E.exists . E.from $ \schoolTerms ->
E.where_ $ schoolTerms E.^. SchoolTermsSchool E.==. school E.^. SchoolId
E.&&. schoolTerms E.^. SchoolTermsTerms E.==. E.val fId
E.where_ $ school E.^. SchoolShorthand `E.in_` E.valList (toListOf (folded . _entityKey . _SchoolId) schools)
return $ school E.^. SchoolId
fieldParents <- fmap (setOf folded) . lift . E.select . E.from $ \terms -> do
E.where_ . E.exists . E.from $ \subTerms ->
E.where_ $ subTerms E.^. StudySubTermsChild E.==. E.val fId
E.&&. subTerms E.^. StudySubTermsParent E.==. terms E.^. StudyTermsId
return terms
return $ field & _dbrOutput %~ (\field' -> (field', fieldParents, fieldSchools))
dbtColonnade = formColonnade $ mconcat
[ sortable (Just "key") (i18nCell MsgGenericKey) (maybe mempty numCell . preview (_dbrOutput . _1 . _entityVal . _studyTermsKey))
, sortable Nothing (i18nCell MsgStudySubTermsParentKey) (parentsCell _4 (_dbrOutput . _2 . to (Set.map entityKey)) _dbrKey')
, sortable (Just "isnew") (i18nCell MsgGenericIsNew) (isNewCell . flip Set.member newKeys . view (_dbrOutput . _1 . _entityKey))
, sortable (Just "isbad") (i18nCell MsgGenericHasConflict) (isBadCell . flip Set.member badKeys . view (_dbrOutput . _1 . _entityKey))
, sortable (Just "name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _1 . _entityVal . _studyTermsName) _dbrKey')
, sortable (Just "short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _1 . _entityVal . _studyTermsShorthand) _dbrKey')
, sortable (Just "degree") (i18nCell MsgStudyTermsDefaultDegree) (degreeCell _5 (_dbrOutput . _1 . _entityVal . _studyTermsDefaultDegree) _dbrKey')
, sortable (Just "field-type") (i18nCell MsgStudyTermsDefaultFieldType) (fieldTypeCell _6 (_dbrOutput . _1 . _entityVal . _studyTermsDefaultType) _dbrKey')
, flip foldMap schools $ \(Entity ssh School{schoolName}) ->
sortable Nothing (cell $ toWidget schoolName) (checkboxCell (_3 . at ssh . _Maybe) (_dbrOutput . _3 . at ssh . _Maybe) _dbrKey')
, dbRow
]
dbtSorting = Map.fromList
[ ("key" , SortColumn $ queryField >>> (E.^. StudyTermsKey))
-- , ("parent", SortColumn $ \t -> querySubField t E.?. StudySubTermsParent)
, ("isnew" , SortColumn $ queryField >>> (E.^. StudyTermsKey) >>> (`E.in_` E.valList (unStudyTermsKey <$> Set.toList newKeys))
)
, ("isbad" , SortColumn $ queryField >>> (E.^. StudyTermsKey) >>> (`E.in_` E.valList (unStudyTermsKey <$> Set.toList badKeys))
)
, ("name" , SortColumn $ queryField >>> (E.^. StudyTermsName))
, ("short" , SortColumn $ queryField >>> (E.^. StudyTermsShorthand))
, ("degree" , SortColumn $ queryField >>> (E.^. StudyTermsDefaultDegree))
, ("field-type" , SortColumn $ queryField >>> (E.^. StudyTermsDefaultType))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAction = Just . SomeRoute $ AdminFeaturesR :#: ("admin-studyterms-table-wrapper" :: Text)
}
psValidator = def
& defaultPagesize PagesizeAll
& defaultSorting [SortAscBy "isnew", SortAscBy "isbad", SortAscBy "key"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
queryField = id
_dbrKey' :: Getter (DBRow (Entity StudyTerms, _, _)) StudyTermsId
_dbrKey' = _dbrOutput . _1 . _entityKey
in dbTable psValidator DBTable{..}
mkCandidateTable =
let dbtIdent = "admin-termcandidate" :: Text
dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
dbtSQLQuery :: E.SqlExpr (Entity StudyTermNameCandidate) -> E.SqlQuery ( E.SqlExpr (Entity StudyTermNameCandidate))
dbtSQLQuery = return
dbtRowKey = (E.^. StudyTermNameCandidateId)
dbtProj = return
dbtColonnade = dbColonnade $ mconcat
[ dbRow
, sortable (Just "key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermNameCandidateKey))
, sortable (Just "name") (i18nCell MsgStudyTermsName) (textCell . view (_dbrOutput . _entityVal . _studyTermNameCandidateName))
, sortable (Just "incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _entityVal . _studyTermNameCandidateIncidence))
]
dbtSorting = Map.fromList
[ ("key" , SortColumn (E.^. StudyTermNameCandidateKey))
, ("name" , SortColumn (E.^. StudyTermNameCandidateName))
, ("incidence", SortColumn (E.^. StudyTermNameCandidateIncidence))
]
dbtFilter = Map.fromList
[ ("key", FilterColumn $ mkExactFilter (E.^. StudyTermNameCandidateKey))
, ("name", FilterColumn $ mkContainsFilter (E.^. StudyTermNameCandidateName))
, ("incidence", FilterColumn $ mkExactFilter (E.^. StudyTermNameCandidateIncidence)) -- contains filter desired, but impossible here
]
dbtFilterUI mPrev = mconcat
[ prismAForm (singletonFilter "key" . maybePrism _PathPiece) mPrev $ aopt (intField :: Field _ Int) (fslI MsgStudyTermsKey)
, prismAForm (singletonFilter "name") mPrev $ aopt textField (fslI MsgStudyTermsName)
, prismAForm (singletonFilter "incidence") mPrev $ aopt textField (fslI MsgStudyCandidateIncidence)
]
dbtParams = def
psValidator = def & defaultSorting [SortAscBy "incidence", SortAscBy "key", SortAscBy "name"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
in dbTable psValidator DBTable{..}
mkParentCandidateTable =
let dbtIdent = "admin-termparentcandidate" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudySubTermParentCandidate)
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity StudyTerms))
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity StudyTerms))
-> E.SqlQuery ( E.SqlExpr (Entity StudySubTermParentCandidate)
, E.SqlExpr (Maybe (Entity StudyTerms))
, E.SqlExpr (Maybe (Entity StudyTerms))
)
dbtSQLQuery (candidate `E.LeftOuterJoin` parent `E.LeftOuterJoin` child) = do
E.on $ child E.?. StudyTermsKey E.==. E.just (candidate E.^. StudySubTermParentCandidateKey)
E.on $ parent E.?. StudyTermsKey E.==. E.just (candidate E.^. StudySubTermParentCandidateParent)
return (candidate, parent, child)
dbtRowKey = queryCandidate >>> (E.^. StudySubTermParentCandidateId)
dbtProj = return
dbtColonnade = dbColonnade $ mconcat
[ dbRow
, sortable (Just "child") (i18nCell MsgStudySubTermsChildKey) (numCell . view (_dbrOutput . _1 . _entityVal . _studySubTermParentCandidateKey))
, sortable (Just "child-name") (i18nCell MsgStudySubTermsChildName) (maybe mempty i18nCell . preview (_dbrOutput . _3 . _Just . _entityVal . _studyTermsName . _Just))
, sortable (Just "parent") (i18nCell MsgStudySubTermsParentKey) (numCell . view (_dbrOutput . _1 . _entityVal . _studySubTermParentCandidateParent))
, sortable (Just "parent-name") (i18nCell MsgStudySubTermsParentName) (maybe mempty i18nCell . preview (_dbrOutput . _2 . _Just . _entityVal . _studyTermsName . _Just))
, sortable (Just "incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _1 . _entityVal . _studySubTermParentCandidateIncidence))
]
dbtSorting = Map.fromList
[ ("child" , SortColumn $ queryCandidate >>> (E.^. StudySubTermParentCandidateKey))
, ("child-name", SortColumn $ queryChild >>> (E.?. StudyTermsName) >>> E.joinV)
, ("parent" , SortColumn $ queryCandidate >>> (E.^. StudySubTermParentCandidateParent))
, ("parent-name", SortColumn $ queryParent >>> (E.?. StudyTermsName) >>> E.joinV)
, ("incidence", SortColumn $ queryCandidate >>> (E.^. StudySubTermParentCandidateIncidence))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def
psValidator = def
& defaultSorting [SortAscBy "child", SortAscBy "incidence", SortAscBy "parent"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
queryCandidate (c `E.LeftOuterJoin` _ `E.LeftOuterJoin` _) = c
queryParent (_ `E.LeftOuterJoin` p `E.LeftOuterJoin` _) = p
queryChild (_ `E.LeftOuterJoin` _ `E.LeftOuterJoin` c) = c
in dbTable psValidator DBTable{..}
mkStandaloneCandidateTable :: DB (FormResult (DBFormResult StudyTermStandaloneCandidateId (Maybe StudyDegreeId, Maybe StudyFieldType) (DBRow (Entity StudyTermStandaloneCandidate, Maybe (Entity StudyTerms)))), Widget)
mkStandaloneCandidateTable =
let dbtIdent = "admin-termstandalonecandidate" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyTermStandaloneCandidate)
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity StudyTerms))
-> E.SqlQuery ( E.SqlExpr (Entity StudyTermStandaloneCandidate)
, E.SqlExpr (Maybe (Entity StudyTerms))
)
dbtSQLQuery (candidate `E.LeftOuterJoin` sterm) = do
E.on $ sterm E.?. StudyTermsKey E.==. E.just (candidate E.^. StudyTermStandaloneCandidateKey)
return (candidate, sterm)
dbtRowKey = queryCandidate >>> (E.^. StudyTermStandaloneCandidateId)
dbtProj = return
dbtColonnade = formColonnade $ mconcat
[ dbRow
, sortable (Just "key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _1 . _entityVal . _studyTermStandaloneCandidateKey))
, sortable (Just "name") (i18nCell MsgStudyTermsName) (maybe mempty i18nCell . preview (_dbrOutput . _2 . _Just . _entityVal . _studyTermsName . _Just))
, sortable (Just "incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _1 . _entityVal . _studyTermStandaloneCandidateIncidence))
, sortable Nothing (i18nCell MsgStudyTermsDefaultDegree) (degreeCell _1 (pre $ _dbrOutput . _2 . _Just . _studyTermsDefaultDegree . _Just) _dbrKey')
, sortable Nothing (i18nCell MsgStudyTermsDefaultFieldType) (fieldTypeCell _2 (pre $ _dbrOutput . _2 . _Just . _studyTermsDefaultType . _Just) _dbrKey')
]
dbtSorting = Map.fromList
[ ("key" , SortColumn $ queryCandidate >>> (E.^. StudyTermStandaloneCandidateKey))
, ("name" , SortColumn $ queryTerm >>> (E.?. StudyTermsName) >>> E.joinV)
, ("incidence", SortColumn $ queryCandidate >>> (E.^. StudyTermStandaloneCandidateIncidence))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAction = Just . SomeRoute $ AdminFeaturesR :#: ("admin-studyterms-table-wrapper" :: Text)
}
psValidator = def
& defaultSorting [SortAscBy "key", SortAscBy "incidence"]
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
queryCandidate (c `E.LeftOuterJoin` _) = c
queryTerm (_ `E.LeftOuterJoin` t) = t
_dbrKey' :: Getter (DBRow (Entity StudyTermStandaloneCandidate, _)) StudyTermStandaloneCandidateId
_dbrKey' = _dbrOutput . _1 . _entityKey
in dbTable psValidator DBTable{..}

231
src/Handler/Admin/Test.hs Normal file
View File

@ -0,0 +1,231 @@
module Handler.Admin.Test
( getAdminTestR
, postAdminTestR
) where
import Import
import Handler.Utils
import Jobs
import Control.Monad.Trans.Writer (mapWriterT)
import Data.Char (isDigit)
import qualified Data.Text as Text
import qualified Data.Set as Set
import qualified Data.Map as Map
import Database.Persist.Sql (fromSqlKey)
-- BEGIN - Buttons needed only here
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonCreate
instance Finite ButtonCreate
nullaryPathPiece ''ButtonCreate camelToPathPiece
instance Button UniWorX ButtonCreate where
btnLabel CreateMath = [whamlet|Ma<i>thema</i>tik|]
btnLabel CreateInf = "Informatik"
btnClasses CreateMath = [BCIsButton, BCInfo]
btnClasses CreateInf = [BCIsButton, BCPrimary]
-- END Button needed only here
emailTestForm :: AForm (HandlerFor UniWorX) (Email, MailContext)
emailTestForm = (,)
<$> areq emailField (fslI MsgMailTestFormEmail) Nothing
<*> ( MailContext
<$> (Languages <$> areq (reorderField appLanguagesOpts) (fslI MsgMailTestFormLanguages) Nothing)
<*> (toMailDateTimeFormat
<$> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) Nothing
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) Nothing
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) Nothing
)
)
where
toMailDateTimeFormat dt d t = \case
SelFormatDateTime -> dt
SelFormatDate -> d
SelFormatTime -> t
makeDemoForm :: Int -> Form (Int,Bool,Double)
makeDemoForm n = identifyForm ("adminTestForm" :: Text) $ \html -> do
(result, widget) <- flip (renderAForm FormStandard) html $ (,,)
<$> areq (minIntFieldI n ("Zahl" :: Text)) (fromString $ "Ganzzahl > " ++ show n) Nothing
<* aformSection MsgFormBehaviour
<*> areq checkBoxField "Muss nächste Zahl größer sein?" (Just True)
<*> areq doubleField "Fliesskommazahl" Nothing
-- NO LONGER DESIRED IN AFORMS:
-- <* submitButton
return $ case result of
FormSuccess fsres
| errorMsgs <- validateResult fsres
, not $ null errorMsgs -> (FormFailure errorMsgs, widget)
_otherwise -> (result, widget)
where
validateResult :: (Int,Bool,Double) -> [Text]
validateResult (i,True,d) | fromIntegral i >= d = [tshow d <> " ist nicht größer als " <> tshow i, "Zweite Fehlermeldung", "Dritte Fehlermeldung"]
validateResult _other = []
getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden!
getAdminTestR = postAdminTestR
postAdminTestR = do
((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm ("buttons" :: Text) (buttonForm :: Form ButtonCreate)
let btnForm = wrapForm btnWdgt def
{ formAction = Just $ SomeRoute AdminTestR
, formEncoding = btnEnctype
, formSubmit = FormNoSubmit
}
case btnResult of
(FormSuccess CreateInf) -> addMessage Info "Informatik-Knopf gedrückt"
(FormSuccess CreateMath) -> addMessage Warning "Knopf Mathematik erkannt"
FormMissing -> return ()
_other -> addMessage Warning "KEIN Knopf erkannt"
((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm ("email" :: Text) $ renderAForm FormStandard emailTestForm
formResultModal emailResult AdminTestR $ \(email, ls) -> do
jId <- mapWriterT runDB $ do
jId <- queueJob $ JobSendTestEmail email ls
tell . pure $ Message Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] (Just IconEmail)
return jId
runReaderT (writeJobCtl $ JobCtlPerform jId) =<< getYesod
addMessage Warning [shamlet|Inkorrekt ausgegebener Alert|] -- For testing alert handling when short circuiting; for proper (not fallback-solution) handling always use `tell` within `formResultModal`
let emailWidget' = wrapForm emailWidget def
{ formAction = Just . SomeRoute $ AdminTestR
, formEncoding = emailEnctype
, formAttrs = [("uw-async-form", "")]
}
let demoFormAction (_i,_b,_d) = addMessage Info "All ok."
((demoResult, formWidget),formEnctype) <- runFormPost $ makeDemoForm 7
formResult demoResult demoFormAction
let showDemoResult = [whamlet|
$maybe (i,b,d) <- formResult' demoResult
Received values:
<ul>
<li>#{show i}
<li>#{show b}
<li>#{show d}
$nothing
No form values received, due to #
$# Using formResult' above means that we usually to not distinguish the following two cases here, sind formResult does this already:
$case demoResult
$of FormSuccess _
$# Already dealt with above, to showecase usage of formResult' as normally done.
success, which should not happen here.
$of FormMissing
Form data missing, probably empty.
$of FormFailure msgs
<ul>
$forall m <- msgs
<li>#{m}
|]
let testTooltipMsg = toWidget [whamlet| So sehen aktuell Tooltips via iconTooltip aus. |] :: WidgetFor UniWorX ()
msgInfoTooltip <- messageI Info ("Info-Tooltip via messageI" :: Text)
msgSuccessTooltip <- messageI Success ("Success-Tooltip via messageI" :: Text)
msgWarningTooltip <- messageI Warning ("Warning-Tooltip via messageI" :: Text)
msgErrorTooltip <- messageI Error ("Error-Tooltip via messageI" :: Text)
msgNonDefaultIconTooltip <- messageIconI Info IconEmail ("Info-Tooltip mit lustigem Icon" :: Text)
{- The following demonstrates the use of @massInput@.
@massInput@ takes as arguments:
- A configuration struct describing how the Widget should behave (how is the space of sub-forms structured, how many dimensions does it have, which additions/deletions are permitted, what data do they need to operate and what should their effect on the overall shape be?)
- Information on how the resulting field fits into the form as a whole (@FieldSettings@ and whether the @massInput@ should be marked required)
- An initial value to pre-fill the field with
@massInput@ then returns an @MForm@ structured for easy downstream consumption of the result
-}
let
-- We define the fields of the configuration struct @MassInput@:
-- | Make a form for adding a point/line/plane/hyperplane/... (in this case: cell)
--
-- This /needs/ to replace all occurrences of @mreq@ with @mpreq@ (no fields should be /actually/ required)
mkAddForm :: ListPosition -- ^ Approximate position of the add-widget
-> Natural -- ^ Dimension Index, outermost dimension ist 0 i.e. if dimension is 3 hyperplane-adders get passed 0, planes get passed 1, lines get 2, and points get 3
-> (Text -> Text) -- ^ Nudge deterministic field ids so they're unique
-> FieldView UniWorX -- ^ Submit-Button for this add-widget
-> Maybe (Form (Map ListPosition Int -> FormResult (Map ListPosition Int))) -- ^ Nothing iff adding further cells in this position/dimension makes no sense; returns callback to determine index of new cells and data needed to initialize cells
mkAddForm 0 0 nudge submitBtn = Just $ \csrf -> do
(addRes, addView) <- mpreq textField ("" & addName (nudge "text")) Nothing -- Any old field; for demonstration
let addRes' = fromMaybe 0 . readMay . Text.filter isDigit <$> addRes -- Do something semi-interesting on the result of the @textField@ to demonstrate that further processing can be done
addRes'' = addRes' <&> \dat prev -> FormSuccess (Map.singleton (maybe 0 succ . fmap fst $ Map.lookupMax prev) dat) -- Construct the callback to determine new cell positions and data within @FormResult@ as required, nested @FormResult@ allows aborting the add depending on previous data
return (addRes'', toWidget csrf >> fvInput addView >> fvInput submitBtn)
mkAddForm _pos _dim _ _ = error "Dimension and Position is always 0 for our 1-dimensional form"
-- | Make a single massInput-Cell
--
-- This /needs/ to use @nudge@ and deterministic field naming (this allows for correct value-shifting when cells are deleted)
mkCellForm :: ListPosition -- ^ Position of this cell
-> Int -- ^ Data needed to initialize the cell (see return of @mkAddForm@)
-> Maybe Int -- ^ Initial cell result from Argument to `massInput`
-> (Text -> Text) -- ^ Nudge deterministic field ids so they're unique
-> Form Int
mkCellForm _pos cData initial nudge csrf = do -- Extremely simple cell
(intRes, intView) <- mreq intField ("" & addName (nudge "int")) $ initial <|> Just cData
return (intRes, toWidget csrf >> fvInput intView)
-- | How does the shape (`ListLength`) change if a certain cell is deleted?
deleteCell :: Map ListPosition Int -- ^ Current shape, including initialisation data
-> ListPosition -- ^ Coordinate to delete
-> MaybeT (MForm (HandlerFor UniWorX)) (Map ListPosition ListPosition) -- ^ Monadically compute a set of new positions and their associated old positions
deleteCell = miDeleteList
-- | Make a decision on whether an add widget should be allowed to further cells, given the current @liveliness@ (i.e. before performing the addition)
allowAdd :: ListPosition -> Natural -> ListLength -> Bool
allowAdd _ _ l = l < 7 -- Limit list length; much more complicated checks are possible (this could in principle be monadic, but @massInput@ is probably already complicated enough to cover just current (2019-03) usecases)
-- | Where to send the user when they click a shape-changing button, given the id of the Wrapper of the `massInput`-`Widget`
buttonAction :: PathPiece p => p -> Maybe (SomeRoute UniWorX)
buttonAction frag = Just . SomeRoute $ AdminTestR :#: frag
-- The actual call to @massInput@ is comparatively simple:
((miResult, fvInput -> miForm), miEnc) <- runFormPost . identifyForm ("massinput" :: Text) $ massInput (MassInput mkAddForm mkCellForm deleteCell allowAdd (\_ _ _ -> Set.empty) buttonAction defaultMiLayout ("massinput" :: Text)) "" True Nothing
let locallyDefinedPageHeading = [whamlet|Admin TestPage for Uni2work|]
siteLayout locallyDefinedPageHeading $ do
-- defaultLayout $ do
setTitle "Uni2work Admin Testpage"
$(i18nWidgetFile "admin-test")
[whamlet|<h2>Formular Demonstration|]
wrapForm formWidget FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AdminTestR :#: FIDAdminDemo
, formEncoding = formEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Just FIDAdminDemo
}
showDemoResult
miIdent <- newIdent
let miForm' = wrapForm miForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AdminTestR :#: miIdent
, formEncoding = miEnc
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Just miIdent
}
[whamlet|
<h2>Mass-Input
^{miForm'}
$case miResult
$of FormMissing
$of FormFailure errs
<ul>
$forall err <- errs
<li>#{err}
$of FormSuccess res
<p style="white-space:pre-wrap; font-family:monospace;">
#{tshow res}
|]

View File

@ -26,10 +26,9 @@ countCourses :: (Num n, PersistField n)
=> (E.SqlExpr (Entity AllocationCourse) -> E.SqlExpr (E.Value Bool)) => (E.SqlExpr (Entity AllocationCourse) -> E.SqlExpr (E.Value Bool))
-> E.SqlExpr (Entity Allocation) -> E.SqlExpr (Entity Allocation)
-> E.SqlExpr (E.Value n) -> E.SqlExpr (E.Value n)
countCourses addWhere allocation = E.sub_select . E.from $ \allocationCourse -> do countCourses addWhere allocation = E.subSelectCount . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId
E.&&. addWhere allocationCourse E.&&. addWhere allocationCourse
return E.countRows
queryAvailable :: Getter AllocationTableExpr (E.SqlExpr (E.Value Natural)) queryAvailable :: Getter AllocationTableExpr (E.SqlExpr (E.Value Natural))
queryAvailable = queryAllocation . to (countCourses $ const E.true) queryAvailable = queryAllocation . to (countCourses $ const E.true)

View File

@ -75,7 +75,7 @@ correctionsTableQuery whereClause returnStatement t@((course `E.InnerJoin` sheet
lastEditQuery :: Database.Esqueleto.Internal.Language.From (E.SqlExpr (Entity SubmissionEdit)) lastEditQuery :: Database.Esqueleto.Internal.Language.From (E.SqlExpr (Entity SubmissionEdit))
=> E.SqlExpr (Entity Submission) -> E.SqlExpr (E.Value (Maybe UTCTime)) => E.SqlExpr (Entity Submission) -> E.SqlExpr (E.Value (Maybe UTCTime))
lastEditQuery submission = E.sub_select $ E.from $ \edit -> do lastEditQuery submission = E.subSelectMaybe $ E.from $ \edit -> do
E.where_ $ edit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId E.where_ $ edit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId
return $ E.max_ $ edit E.^. SubmissionEditTime return $ E.max_ $ edit E.^. SubmissionEditTime
@ -297,7 +297,7 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI psValidator dbtProj' d
) )
, ( "submittors" , ( "submittors"
, SortColumn $ \((_ `E.InnerJoin` _ `E.InnerJoin` submission) `E.LeftOuterJoin` _) -> , SortColumn $ \((_ `E.InnerJoin` _ `E.InnerJoin` submission) `E.LeftOuterJoin` _) ->
E.sub_select . E.from $ \(submissionUser `E.InnerJoin` user) -> do E.subSelectUnsafe . E.from $ \(submissionUser `E.InnerJoin` user) -> do
E.on $ submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId E.on $ submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId
E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId
E.orderBy [E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName] E.orderBy [E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName]
@ -1215,9 +1215,8 @@ assignHandler tid ssh csh cid assignSids = do
submissions <- E.select . E.from $ \submission -> do submissions <- E.select . E.from $ \submission -> do
E.where_ $ submission E.^. SubmissionSheet `E.in_` E.valList sheetIds E.where_ $ submission E.^. SubmissionSheet `E.in_` E.valList sheetIds
let numSubmittors = E.sub_select . E.from $ \subUser -> do let numSubmittors = E.subSelectCount . E.from $ \subUser ->
E.where_ $ submission E.^. SubmissionId E.==. subUser E.^. SubmissionUserSubmission E.where_ $ submission E.^. SubmissionId E.==. subUser E.^. SubmissionUserSubmission
return E.countRows
return (submission, numSubmittors) return (submission, numSubmittors)
-- prepare map -- prepare map
let infoMap :: Map SheetName (Map (Maybe UserId) CorrectionInfo) let infoMap :: Map SheetName (Map (Maybe UserId) CorrectionInfo)

View File

@ -599,11 +599,10 @@ postCApplicationsR tid ssh csh = do
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
E.&&. allocationCourse E.^. AllocationCourseCourse E.==. E.val cid E.&&. allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
let numApps addWhere = E.sub_select . E.from $ \courseApplication -> do let numApps addWhere = E.subSelectCount . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation) E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
addWhere courseApplication addWhere courseApplication
return E.countRows
numApps' = numApps . const $ return () numApps' = numApps . const $ return ()

View File

@ -371,7 +371,7 @@ getCourseNewR = do
E.&&. user E.^. UserFunctionSchool E.==. course E.^. CourseSchool E.&&. user E.^. UserFunctionSchool E.==. course E.^. CourseSchool
E.&&. user E.^. UserFunctionFunction E.==. E.val SchoolLecturer E.&&. user E.^. UserFunctionFunction E.==. E.val SchoolLecturer
let courseCreated c = let courseCreated c =
E.sub_select . E.from $ \edit -> do -- oldest edit must be creation E.subSelectMaybe . E.from $ \edit -> do -- oldest edit must be creation
E.where_ $ edit E.^. CourseEditCourse E.==. c E.^. CourseId E.where_ $ edit E.^. CourseEditCourse E.==. c E.^. CourseId
return $ E.min_ $ edit E.^. CourseEditTime return $ E.min_ $ edit E.^. CourseEditTime
E.orderBy [ E.desc $ E.case_ [(lecturersCourse, E.val (1 :: Int64))] (E.val 0) -- prefer courses from lecturer E.orderBy [ E.desc $ E.case_ [(lecturersCourse, E.val (1 :: Int64))] (E.val 0) -- prefer courses from lecturer

View File

@ -61,9 +61,8 @@ colRegistered = sortable (Just "registered") (i18nCell MsgRegistered)
type CourseTableExpr = E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School) type CourseTableExpr = E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School)
course2Participants :: CourseTableExpr -> E.SqlExpr (E.Value Int) course2Participants :: CourseTableExpr -> E.SqlExpr (E.Value Int)
course2Participants (course `E.InnerJoin` _school) = E.sub_select . E.from $ \courseParticipant -> do course2Participants (course `E.InnerJoin` _school) = E.subSelectCount . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
return (E.countRows :: E.SqlExpr (E.Value Int))
course2Registered :: Maybe UserId -> CourseTableExpr -> E.SqlExpr (E.Value Bool) course2Registered :: Maybe UserId -> CourseTableExpr -> E.SqlExpr (E.Value Bool)
course2Registered muid (course `E.InnerJoin` _school) = E.exists . E.from $ \courseParticipant -> course2Registered muid (course `E.InnerJoin` _school) = E.exists . E.from $ \courseParticipant ->

View File

@ -269,9 +269,7 @@ deregisterParticipant uid cid = do
audit $ TransactionExamResultDeleted examResultExam uid audit $ TransactionExamResultDeleted examResultExam uid
E.delete . E.from $ \tutorialParticipant -> do E.delete . E.from $ \tutorialParticipant -> do
let tutorialCourse = E.sub_select . E.from $ \tutorial -> do let tutorialCourse = E.subSelectForeign tutorialParticipant TutorialParticipantTutorial (E.^. TutorialCourse)
E.where_ $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
return $ tutorial E.^. TutorialCourse
E.where_ $ tutorialCourse E.==. E.val cid E.where_ $ tutorialCourse E.==. E.val cid
E.&&. tutorialParticipant E.^. TutorialParticipantUser E.==. E.val uid E.&&. tutorialParticipant E.^. TutorialParticipantUser E.==. E.val uid

View File

@ -37,9 +37,9 @@ getCShowR tid ssh csh = do
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.limit 1 -- we know that there is at most one match, but we tell the DB this info too E.limit 1 -- we know that there is at most one match, but we tell the DB this info too
let numParticipants = E.sub_select . E.from $ \part -> do let numParticipants :: E.SqlExpr (E.Value Int)
numParticipants = E.subSelectCount . E.from $ \part ->
E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId
return ( E.countRows :: E.SqlExpr (E.Value Int))
return (course,school E.^. SchoolName, numParticipants, participant) return (course,school E.^. SchoolName, numParticipants, participant)
staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
@ -146,9 +146,9 @@ getCShowR tid ssh csh = do
Nothing -> mempty Nothing -> mempty
Just tutorialCapacity' -> sqlCell $ do Just tutorialCapacity' -> sqlCell $ do
freeCapacity <- fmap (maybe 0 (max 0 . E.unValue) . listToMaybe) freeCapacity <- fmap (maybe 0 (max 0 . E.unValue) . listToMaybe)
. E.select $ let numParticipants = E.sub_select . E.from $ \participant -> do . E.select $ let numParticipants :: E.SqlExpr (E.Value Int)
numParticipants = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. TutorialParticipantTutorial E.==. E.val tutid E.where_ $ participant E.^. TutorialParticipantTutorial E.==. E.val tutid
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int))
in return $ E.val tutorialCapacity' E.-. numParticipants in return $ E.val tutorialCapacity' E.-. numParticipants
return . toWidget $ tshow freeCapacity return . toWidget $ tshow freeCapacity
, sortable Nothing mempty $ \DBRow{ dbrOutput = Entity tutId Tutorial{..} } -> sqlCell $ do , sortable Nothing mempty $ \DBRow{ dbrOutput = Entity tutId Tutorial{..} } -> sqlCell $ do

View File

@ -301,12 +301,12 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
, single $ ("semesternr" , SortColumn $ queryFeaturesStudy >>> (E.?. StudyFeaturesSemester)) , single $ ("semesternr" , SortColumn $ queryFeaturesStudy >>> (E.?. StudyFeaturesSemester))
, single $ ("registration", SortColumn $ queryParticipant >>> (E.^. CourseParticipantRegistration)) , single $ ("registration", SortColumn $ queryParticipant >>> (E.^. CourseParticipantRegistration))
, single $ ("note" , SortColumn $ queryUserNote >>> \note -> -- sort by last edit date , single $ ("note" , SortColumn $ queryUserNote >>> \note -> -- sort by last edit date
E.sub_select . E.from $ \edit -> do E.subSelectMaybe . E.from $ \edit -> do
E.where_ $ note E.?. CourseUserNoteId E.==. E.just (edit E.^. CourseUserNoteEditNote) E.where_ $ note E.?. CourseUserNoteId E.==. E.just (edit E.^. CourseUserNoteEditNote)
return . E.max_ $ edit E.^. CourseUserNoteEditTime return . E.max_ $ edit E.^. CourseUserNoteEditTime
) )
, single $ ("tutorials" , SortColumn $ queryUser >>> \user -> , single $ ("tutorials" , SortColumn $ queryUser >>> \user ->
E.sub_select . E.from $ \(tutorial `E.InnerJoin` participant) -> do E.subSelectMaybe . E.from $ \(tutorial `E.InnerJoin` participant) -> do
E.on $ tutorial E.^. TutorialId E.==. participant E.^. TutorialParticipantTutorial E.on $ tutorial E.^. TutorialId E.==. participant E.^. TutorialParticipantTutorial
E.&&. tutorial E.^. TutorialCourse E.==. E.val cid E.&&. tutorial E.^. TutorialCourse E.==. E.val cid
E.where_ $ participant E.^. TutorialParticipantUser E.==. user E.^. UserId E.where_ $ participant E.^. TutorialParticipantUser E.==. user E.^. UserId

View File

@ -110,7 +110,7 @@ queryExamPart :: forall a.
-> (E.SqlExpr (Entity ExamPart) -> E.SqlExpr (Maybe (Entity ExamPartResult)) -> E.SqlQuery (E.SqlExpr (E.Value a))) -> (E.SqlExpr (Entity ExamPart) -> E.SqlExpr (Maybe (Entity ExamPartResult)) -> E.SqlQuery (E.SqlExpr (E.Value a)))
-> ExamUserTableExpr -> ExamUserTableExpr
-> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
queryExamPart epId cont inp = E.sub_select . E.from $ \(examPart `E.LeftOuterJoin` examPartResult) -> flip runReaderT inp $ do queryExamPart epId cont inp = E.subSelectUnsafe . E.from $ \(examPart `E.LeftOuterJoin` examPartResult) -> flip runReaderT inp $ do
examRegistration <- asks queryExamRegistration examRegistration <- asks queryExamRegistration
lift $ do lift $ do
@ -528,7 +528,7 @@ postEUsersR tid ssh csh examn = do
, singletonMap "result" . SortColumn $ queryExamResult >>> (E.?. ExamResultResult) , singletonMap "result" . SortColumn $ queryExamResult >>> (E.?. ExamResultResult)
, singletonMap "result-bool" . SortColumn $ queryExamResult >>> (E.?. ExamResultResult) >>> E.orderByList [Just ExamVoided, Just ExamNoShow, Just $ ExamAttended Grade50] , singletonMap "result-bool" . SortColumn $ queryExamResult >>> (E.?. ExamResultResult) >>> E.orderByList [Just ExamVoided, Just ExamNoShow, Just $ ExamAttended Grade50]
, singletonMap "note" . SortColumn $ queryCourseNote >>> \note -> -- sort by last edit date , singletonMap "note" . SortColumn $ queryCourseNote >>> \note -> -- sort by last edit date
E.sub_select . E.from $ \edit -> do E.subSelectMaybe . E.from $ \edit -> do
E.where_ $ note E.?. CourseUserNoteId E.==. E.just (edit E.^. CourseUserNoteEditNote) E.where_ $ note E.?. CourseUserNoteId E.==. E.just (edit E.^. CourseUserNoteEditNote)
return . E.max_ $ edit E.^. CourseUserNoteEditTime return . E.max_ $ edit E.^. CourseUserNoteEditTime
] ]

View File

@ -33,21 +33,19 @@ querySynchronised :: E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlE
querySynchronised office = to . runReader $ do querySynchronised office = to . runReader $ do
exam <- view queryExam exam <- view queryExam
let let
synchronised = E.sub_select . E.from $ \examResult -> do synchronised = E.subSelectCount . E.from $ \examResult -> do
E.where_ $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId E.where_ $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId
E.where_ $ Exam.examOfficeExamResultAuth office examResult E.where_ $ Exam.examOfficeExamResultAuth office examResult
E.where_ $ Exam.resultIsSynced office examResult E.where_ $ Exam.resultIsSynced office examResult
return E.countRows
return synchronised return synchronised
queryResults :: E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Natural)) queryResults :: E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Natural))
queryResults office = to . runReader $ do queryResults office = to . runReader $ do
exam <- view queryExam exam <- view queryExam
let let
results = E.sub_select . E.from $ \examResult -> do results = E.subSelectCount . E.from $ \examResult -> do
E.where_ $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId E.where_ $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId
E.where_ $ Exam.examOfficeExamResultAuth office examResult E.where_ $ Exam.examOfficeExamResultAuth office examResult
return E.countRows
return results return results
queryIsSynced :: UTCTime -> E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Bool)) queryIsSynced :: UTCTime -> E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Bool))

View File

@ -158,7 +158,7 @@ homeUpcomingExams uid = do
startOccurFortnight = occurrence E.?. ExamOccurrenceStart E.<=. E.just (E.val fortnight) startOccurFortnight = occurrence E.?. ExamOccurrenceStart E.<=. E.just (E.val fortnight)
E.&&. occurrence E.?. ExamOccurrenceStart E.>=. E.just (E.val now) E.&&. occurrence E.?. ExamOccurrenceStart E.>=. E.just (E.val now)
E.&&. E.isJust (register E.?. ExamRegistrationId) E.&&. E.isJust (register E.?. ExamRegistrationId)
earliestOccurrence = E.sub_select $ E.from $ \occ -> do earliestOccurrence = E.subSelectMaybe $ E.from $ \occ -> do
E.where_ $ occ E.^. ExamOccurrenceExam E.==. exam E.^. ExamId E.where_ $ occ E.^. ExamOccurrenceExam E.==. exam E.^. ExamId
E.&&. occ E.^. ExamOccurrenceStart E.>=. E.val now E.&&. occ E.^. ExamOccurrenceStart E.>=. E.val now
return $ E.min_ $ occ E.^. ExamOccurrenceStart return $ E.min_ $ occ E.^. ExamOccurrenceStart

View File

@ -114,9 +114,9 @@ getMaterialListR tid ssh csh = do
, dbtParams = def , dbtParams = def
, dbtSQLQuery = \material -> do , dbtSQLQuery = \material -> do
E.where_ $ material E.^. MaterialCourse E.==. E.val cid E.where_ $ material E.^. MaterialCourse E.==. E.val cid
let filesNum = E.sub_select . E.from $ \materialFile -> do let filesNum :: E.SqlExpr (E.Value Int64)
filesNum = E.subSelectCount . E.from $ \materialFile ->
E.where_ $ materialFile E.^. MaterialFileMaterial E.==. material E.^. MaterialId E.where_ $ materialFile E.^. MaterialFileMaterial E.==. material E.^. MaterialId
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int64))
return (material, filesNum) return (material, filesNum)
, dbtRowKey = (E.^. MaterialId) , dbtRowKey = (E.^. MaterialId)
-- , dbtProj = \dbr -> guardAuthorizedFor (matLink . materialName $ dbr ^. _dbrOutput . _entityVal) dbr -- , dbtProj = \dbr -> guardAuthorizedFor (matLink . materialName $ dbr ^. _dbrOutput . _entityVal) dbr
@ -331,9 +331,9 @@ postMDelR tid ssh csh mnm = do
{ drRecords = Set.singleton $ entityKey matEnt { drRecords = Set.singleton $ entityKey matEnt
, drGetInfo = \(material `E.InnerJoin` course) -> do , drGetInfo = \(material `E.InnerJoin` course) -> do
E.on $ material E.^. MaterialCourse E.==. course E.^. CourseId E.on $ material E.^. MaterialCourse E.==. course E.^. CourseId
let filecount = E.sub_select . E.from $ \matfile -> do let filecount :: E.SqlExpr (E.Value Int64)
filecount = E.subSelectCount . E.from $ \matfile ->
E.where_ $ matfile E.^. MaterialFileMaterial E.==. material E.^. MaterialId E.where_ $ matfile E.^. MaterialFileMaterial E.==. material E.^. MaterialId
return (E.countRows :: E.SqlExpr (E.Value Int64))
return (material,course,filecount) return (material,course,filecount)
, drUnjoin = \(material `E.InnerJoin` _course) -> material , drUnjoin = \(material `E.InnerJoin` _course) -> material
, drRenderRecord = \(Entity _ Material{..}, Entity _ Course{..}, E.Value fileCount) -> do , drRenderRecord = \(Entity _ Material{..}, Entity _ Course{..}, E.Value fileCount) -> do

View File

@ -359,10 +359,13 @@ makeProfileData (Entity uid User{..}) = do
E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet
E.where_ $ corrector E.^. SheetCorrectorUser E.==. E.val uid E.where_ $ corrector E.^. SheetCorrectorUser E.==. E.val uid
return (course E.^. CourseTerm, course E.^. CourseSchool, course E.^. CourseShorthand) return (course E.^. CourseTerm, course E.^. CourseSchool, course E.^. CourseShorthand)
studies <- E.select $ E.from $ \(studydegree `E.InnerJoin` studyfeat `E.InnerJoin` studyterms) -> do studies <- E.select $ E.from $ \(studydegree `E.InnerJoin` studyfeat `E.InnerJoin` studyterms) ->
E.distinctOnOrderBy [ E.asc $ studyfeat E.^. StudyFeaturesId ] $ do
E.orderBy [ E.desc $ studyfeat E.^. StudyFeaturesSubField E.==. E.just (studyterms E.^. StudyTermsId) ]
E.where_ $ studyfeat E.^. StudyFeaturesUser E.==. E.val uid E.where_ $ studyfeat E.^. StudyFeaturesUser E.==. E.val uid
E.on $ studyfeat E.^. StudyFeaturesField E.==. studyterms E.^. StudyTermsId E.on $ studyfeat E.^. StudyFeaturesField E.==. studyterms E.^. StudyTermsId
E.on $ studyfeat E.^. StudyFeaturesDegree E.==. studydegree E.^. StudyDegreeId E.||. studyfeat E.^. StudyFeaturesSubField E.==. E.just (studyterms E.^. StudyTermsId)
E.on $ studyfeat E.^. StudyFeaturesDegree E.==. studydegree E.^. StudyDegreeId
return (studyfeat, studydegree, studyterms) return (studyfeat, studydegree, studyterms)
--Tables --Tables
(hasRows, ownedCoursesTable) <- mkOwnedCoursesTable uid -- Tabelle mit eigenen Kursen (hasRows, ownedCoursesTable) <- mkOwnedCoursesTable uid -- Tabelle mit eigenen Kursen
@ -507,7 +510,7 @@ mkSubmissionTable =
dbtRowKey (_ `E.InnerJoin` _ `E.InnerJoin` submission `E.InnerJoin` _) = submission E.^. SubmissionId dbtRowKey (_ `E.InnerJoin` _ `E.InnerJoin` submission `E.InnerJoin` _) = submission E.^. SubmissionId
lastSubEdit uid submission = -- latest Edit-Time of this user for submission lastSubEdit uid submission = -- latest Edit-Time of this user for submission
E.sub_select . E.from $ \subEdit -> do E.subSelectMaybe . E.from $ \subEdit -> do
E.where_ $ subEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId E.where_ $ subEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId
E.&&. subEdit E.^. SubmissionEditUser E.==. E.val uid E.&&. subEdit E.^. SubmissionEditUser E.==. E.val uid
return . E.max_ $ subEdit E.^. SubmissionEditTime return . E.max_ $ subEdit E.^. SubmissionEditTime
@ -590,7 +593,7 @@ mkSubmissionGroupTable =
dbtRowKey (_ `E.InnerJoin` sgroup `E.InnerJoin` _) = sgroup E.^. SubmissionGroupId dbtRowKey (_ `E.InnerJoin` sgroup `E.InnerJoin` _) = sgroup E.^. SubmissionGroupId
lastSGEdit sgroup = -- latest Edit-Time of this Submission Group by a user lastSGEdit sgroup = -- latest Edit-Time of this Submission Group by a user
E.sub_select . E.from $ \(user `E.InnerJoin` sgEdit) -> do E.subSelectMaybe . E.from $ \(user `E.InnerJoin` sgEdit) -> do
E.on $ user E.^. UserId E.==. sgEdit E.^. SubmissionGroupEditUser E.on $ user E.^. UserId E.==. sgEdit E.^. SubmissionGroupEditUser
E.where_ $ sgEdit E.^. SubmissionGroupEditSubmissionGroup E.==. sgroup E.^. SubmissionGroupId E.where_ $ sgEdit E.^. SubmissionGroupEditSubmissionGroup E.==. sgroup E.^. SubmissionGroupId
return . E.max_ $ sgEdit E.^. SubmissionGroupEditTime return . E.max_ $ sgEdit E.^. SubmissionGroupEditTime
@ -649,16 +652,14 @@ mkCorrectionsTable =
-> ((E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity Sheet) `E.InnerJoin` E.SqlExpr (Entity SheetCorrector))->a) -> ((E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity Sheet) `E.InnerJoin` E.SqlExpr (Entity SheetCorrector))->a)
withType = id withType = id
corrsAssigned uid sheet = E.sub_select . E.from $ \submission -> do corrsAssigned uid sheet = E.subSelectCount . E.from $ \submission ->
E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId
E.&&. submission E.^. SubmissionRatingBy E.==. E.just (E.val uid) E.&&. submission E.^. SubmissionRatingBy E.==. E.just (E.val uid)
return E.countRows
corrsCorrected uid sheet = E.sub_select . E.from $ \submission -> do corrsCorrected uid sheet = E.subSelectCount . E.from $ \submission ->
E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId
E.&&. submission E.^. SubmissionRatingBy E.==. E.just (E.val uid) E.&&. submission E.^. SubmissionRatingBy E.==. E.just (E.val uid)
E.&&. E.not_ (E.isNothing $ submission E.^. SubmissionRatingTime) E.&&. E.not_ (E.isNothing $ submission E.^. SubmissionRatingTime)
return E.countRows
dbtSQLQuery' uid (course `E.InnerJoin` sheet `E.InnerJoin` corrector) = do dbtSQLQuery' uid (course `E.InnerJoin` sheet `E.InnerJoin` corrector) = do
E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet

View File

@ -201,7 +201,7 @@ getSheetListR tid ssh csh = do
, sft /= SheetSolution || hasSolution , sft /= SheetSolution || hasSolution
, sft /= SheetMarking || hasMarking , sft /= SheetMarking || hasMarking
] ]
lastSheetEdit sheet = E.sub_select . E.from $ \sheetEdit -> do lastSheetEdit sheet = E.subSelectMaybe . E.from $ \sheetEdit -> do
E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
return . E.max_ $ sheetEdit E.^. SheetEditTime return . E.max_ $ sheetEdit E.^. SheetEditTime
@ -504,14 +504,14 @@ getSheetNewR tid ssh csh = do
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
searchShn sheet searchShn sheet
-- let lastSheetEdit = E.sub_select . E.from $ \sheetEdit -> do -- let lastSheetEdit = E.subSelectMaybe . E.from $ \sheetEdit -> do
-- E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId -- E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
-- return . E.max_ $ sheetEdit E.^. SheetEditTime -- return . E.max_ $ sheetEdit E.^. SheetEditTime
-- Preferring last edited sheet may lead to suggesting duplicated sheet name numbers -- Preferring last edited sheet may lead to suggesting duplicated sheet name numbers
-- E.orderBy [E.desc lastSheetEdit, E.desc (sheet E.^. SheetActiveFrom)] -- E.orderBy [E.desc lastSheetEdit, E.desc (sheet E.^. SheetActiveFrom)]
E.orderBy [E.desc (sheet E.^. SheetActiveFrom)] E.orderBy [E.desc (sheet E.^. SheetActiveFrom)]
E.limit 1 E.limit 1
let firstEdit = E.sub_select . E.from $ \sheetEdit -> do let firstEdit = E.subSelectMaybe . E.from $ \sheetEdit -> do
E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
return . E.min_ $ sheetEdit E.^. SheetEditTime return . E.min_ $ sheetEdit E.^. SheetEditTime
return (sheet, firstEdit) return (sheet, firstEdit)
@ -711,7 +711,7 @@ defaultLoads cId = do
fmap toMap . E.select . E.from $ \(sheet `E.InnerJoin` sheetCorrector) -> E.distinctOnOrderBy [E.asc (sheetCorrector E.^. SheetCorrectorUser)] $ do fmap toMap . E.select . E.from $ \(sheet `E.InnerJoin` sheetCorrector) -> E.distinctOnOrderBy [E.asc (sheetCorrector E.^. SheetCorrectorUser)] $ do
E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet
let creationTime = E.sub_select . E.from $ \sheetEdit -> do let creationTime = E.subSelectMaybe . E.from $ \sheetEdit -> do
E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
return . E.min_ $ sheetEdit E.^. SheetEditTime return . E.min_ $ sheetEdit E.^. SheetEditTime

View File

@ -43,9 +43,8 @@ getTermShowR = do
termData :: E.SqlExpr (Entity Term) -> E.SqlQuery (E.SqlExpr (Entity Term), E.SqlExpr (E.Value Int64)) termData :: E.SqlExpr (Entity Term) -> E.SqlQuery (E.SqlExpr (Entity Term), E.SqlExpr (E.Value Int64))
termData term = do termData term = do
-- E.orderBy [E.desc $ term E.^. TermStart ] -- E.orderBy [E.desc $ term E.^. TermStart ]
let courseCount = E.sub_select . E.from $ \course -> do let courseCount = E.subSelectCount . E.from $ \course ->
E.where_ $ term E.^. TermId E.==. course E.^. CourseTerm E.where_ $ term E.^. TermId E.==. course E.^. CourseTerm
return E.countRows
return (term, courseCount) return (term, courseCount)
selectRep $ do selectRep $ do
provideRep $ toJSON . map fst <$> runDB (E.select $ E.from termData) provideRep $ toJSON . map fst <$> runDB (E.select $ E.from termData)

View File

@ -23,9 +23,8 @@ postTDeleteR tid ssh csh tutn = do
, drUnjoin = \(_ `E.InnerJoin` tutorial) -> tutorial , drUnjoin = \(_ `E.InnerJoin` tutorial) -> tutorial
, drGetInfo = \(course `E.InnerJoin` tutorial) -> do , drGetInfo = \(course `E.InnerJoin` tutorial) -> do
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
let participants = E.sub_select . E.from $ \participant -> do let participants = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId E.where_ $ participant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId
return E.countRows
return (course, tutorial, participants :: E.SqlExpr (E.Value Int)) return (course, tutorial, participants :: E.SqlExpr (E.Value Int))
, drRenderRecord = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) -> , drRenderRecord = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) ->
return [whamlet|_{prependCourseTitle courseTerm courseSchool courseShorthand (CI.original tutorialName)} (_{MsgParticipantsN ps})|] return [whamlet|_{prependCourseTitle courseTerm courseSchool courseShorthand (CI.original tutorialName)} (_{MsgParticipantsN ps})|]

View File

@ -22,9 +22,9 @@ getCTutorialListR tid ssh csh = do
where where
dbtSQLQuery tutorial = do dbtSQLQuery tutorial = do
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
let participants = E.sub_select . E.from $ \tutorialParticipant -> do let participants :: E.SqlExpr (E.Value Int)
participants = E.subSelectCount . E.from $ \tutorialParticipant ->
E.where_ $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId E.where_ $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int))
return (tutorial, participants) return (tutorial, participants)
dbtRowKey = (E.^. TutorialId) dbtRowKey = (E.^. TutorialId)
dbtProj = return . over (_dbrOutput . _2) E.unValue dbtProj = return . over (_dbrOutput . _2) E.unValue
@ -58,9 +58,10 @@ getCTutorialListR tid ssh csh = do
dbtSorting = Map.fromList dbtSorting = Map.fromList
[ ("type", SortColumn $ \tutorial -> tutorial E.^. TutorialType ) [ ("type", SortColumn $ \tutorial -> tutorial E.^. TutorialType )
, ("name", SortColumn $ \tutorial -> tutorial E.^. TutorialName ) , ("name", SortColumn $ \tutorial -> tutorial E.^. TutorialName )
, ("participants", SortColumn $ \tutorial -> E.sub_select . E.from $ \tutorialParticipant -> do , ("participants", SortColumn $ \tutorial -> let participantCount :: E.SqlExpr (E.Value Int)
E.where_ $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId participantCount = E.subSelectCount . E.from $ \tutorialParticipant ->
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int)) E.where_ $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId
in participantCount
) )
, ("capacity", SortColumn $ \tutorial -> tutorial E.^. TutorialCapacity ) , ("capacity", SortColumn $ \tutorial -> tutorial E.^. TutorialCapacity )
, ("room", SortColumn $ \tutorial -> tutorial E.^. TutorialRoom ) , ("room", SortColumn $ \tutorial -> tutorial E.^. TutorialRoom )

View File

@ -439,10 +439,9 @@ deleteUser duid = do
selectSubmissionsWhere :: (E.SqlExpr (E.Value Int64) -> E.SqlExpr (E.Value Bool)) -> DB [E.Value (Key Submission)] selectSubmissionsWhere :: (E.SqlExpr (E.Value Int64) -> E.SqlExpr (E.Value Bool)) -> DB [E.Value (Key Submission)]
selectSubmissionsWhere whereBuddies = E.select $ E.from $ \(submission `E.InnerJoin` suser) -> do selectSubmissionsWhere whereBuddies = E.select $ E.from $ \(submission `E.InnerJoin` suser) -> do
E.on $ submission E.^. SubmissionId E.==. suser E.^. SubmissionUserSubmission E.on $ submission E.^. SubmissionId E.==. suser E.^. SubmissionUserSubmission
let numBuddies = E.sub_select $ E.from $ \subUsers -> do let numBuddies = E.subSelectCount $ E.from $ \subUsers ->
E.where_ $ subUsers E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId E.where_ $ subUsers E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId
E.&&. subUsers E.^. SubmissionUserUser E.!=. E.val duid E.&&. subUsers E.^. SubmissionUserUser E.!=. E.val duid
return E.countRows
E.where_ $ suser E.^. SubmissionUserUser E.==. E.val duid E.where_ $ suser E.^. SubmissionUserUser E.==. E.val duid
E.&&. whereBuddies numBuddies E.&&. whereBuddies numBuddies
return $ submission E.^. SubmissionId return $ submission E.^. SubmissionId

View File

@ -91,12 +91,11 @@ computeAllocation allocId cRestr = do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val allocId E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val allocId
let participants = E.sub_select . E.from $ \participant -> do let participants = E.subSelectCount . E.from $ \participant -> do
E.where_ $ participant E.^. CourseParticipantCourse E.==. course E.^. CourseId E.where_ $ participant E.^. CourseParticipantCourse E.==. course E.^. CourseId
E.where_ . E.not_ . E.exists . E.from $ \lecturer -> do E.where_ . E.not_ . E.exists . E.from $ \lecturer ->
E.where_ $ lecturer E.^. LecturerCourse E.==. course E.^. CourseId E.where_ $ lecturer E.^. LecturerCourse E.==. course E.^. CourseId
E.&&. lecturer E.^. LecturerUser E.==. participant E.^. CourseParticipantUser E.&&. lecturer E.^. LecturerUser E.==. participant E.^. CourseParticipantUser
return E.countRows
whenIsJust cRestr $ \restrSet -> whenIsJust cRestr $ \restrSet ->
E.where_ $ course E.^. CourseId `E.in_` E.valList (Set.toList restrSet) E.where_ $ course E.^. CourseId `E.in_` E.valList (Set.toList restrSet)

View File

@ -353,6 +353,12 @@ schoolFieldEnt = selectField $ optionsPersist [] [Asc SchoolName] schoolName
schoolFieldFor :: [SchoolId] -> Field Handler SchoolId schoolFieldFor :: [SchoolId] -> Field Handler SchoolId
schoolFieldFor userSchools = selectField $ optionsPersistKey [SchoolShorthand <-. map unSchoolKey userSchools] [Asc SchoolName] schoolName schoolFieldFor userSchools = selectField $ optionsPersistKey [SchoolShorthand <-. map unSchoolKey userSchools] [Asc SchoolName] schoolName
degreeField :: Field Handler StudyDegreeId
degreeField = selectField $ optionsPersistKey [] [Asc StudyDegreeName, Asc StudyDegreeShorthand, Asc StudyDegreeKey] id
degreeFieldEnt :: Field Handler (Entity StudyDegree)
degreeFieldEnt = selectField $ optionsPersist [] [Asc StudyDegreeName, Asc StudyDegreeShorthand, Asc StudyDegreeKey] id
-- | Select one of the user's primary active study features, or from a given list of StudyFeatures (regardless of user) -- | Select one of the user's primary active study features, or from a given list of StudyFeatures (regardless of user)
studyFeaturesPrimaryFieldFor :: Bool -- ^ Allow user to select `Nothing` (only applies if set of options is nonempty)? studyFeaturesPrimaryFieldFor :: Bool -- ^ Allow user to select `Nothing` (only applies if set of options is nonempty)?

View File

@ -274,6 +274,7 @@ sourceInvitations :: forall junction m backend.
, MonadThrow m , MonadThrow m
, PersistRecordBackend Invitation backend , PersistRecordBackend Invitation backend
, HasPersistBackend backend , HasPersistBackend backend
, PersistQueryRead backend
) )
=> Key (InvitationFor junction) => Key (InvitationFor junction)
-> ConduitT () (UserEmail, InvitationDBData junction) (ReaderT backend m) () -> ConduitT () (UserEmail, InvitationDBData junction) (ReaderT backend m) ()
@ -293,6 +294,7 @@ sourceInvitationsF :: forall junction map m backend.
, MonadThrow m , MonadThrow m
, PersistRecordBackend Invitation backend , PersistRecordBackend Invitation backend
, HasPersistBackend backend , HasPersistBackend backend
, PersistQueryRead backend
) )
=> Key (InvitationFor junction) => Key (InvitationFor junction)
-> ReaderT backend m map -> ReaderT backend m map

View File

@ -58,9 +58,8 @@ sheetDeleteRoute drRecords = DeleteRoute
, drGetInfo = \(sheet `E.InnerJoin` course `E.InnerJoin` school) -> do , drGetInfo = \(sheet `E.InnerJoin` course `E.InnerJoin` school) -> do
E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
let submissions = E.sub_select . E.from $ \submission -> do let submissions = E.subSelectCount . E.from $ \submission ->
E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId
return E.countRows
E.orderBy [E.asc $ sheet E.^. SheetName] E.orderBy [E.asc $ sheet E.^. SheetName]
return (submissions, sheet E.^. SheetName, course E.^. CourseShorthand, course E.^. CourseName, school E.^. SchoolShorthand, school E.^. SchoolName, course E.^. CourseTerm) return (submissions, sheet E.^. SheetName, course E.^. CourseShorthand, course E.^. CourseName, school E.^. SchoolShorthand, school E.^. SchoolName, course E.^. CourseTerm)
, drUnjoin = \(sheet `E.InnerJoin` _ `E.InnerJoin` _) -> sheet , drUnjoin = \(sheet `E.InnerJoin` _ `E.InnerJoin` _) -> sheet

View File

@ -1,5 +1,6 @@
module Handler.Utils.StudyFeatures module Handler.Utils.StudyFeatures
( parseStudyFeatures ( parseStudyFeatures
, parseSubTermsSemester
) where ) where
import Import.NoFoundation hiding (try, (<|>)) import Import.NoFoundation hiding (try, (<|>))
@ -7,9 +8,19 @@ import Import.NoFoundation hiding (try, (<|>))
import Text.Parsec import Text.Parsec
import Text.Parsec.Text import Text.Parsec.Text
import Auth.LDAP (ldapUserSubTermsSemester, ldapUserStudyFeatures)
import qualified Ldap.Client as Ldap
parseStudyFeatures :: UserId -> UTCTime -> Text -> Either ParseError [StudyFeatures] parseStudyFeatures :: UserId -> UTCTime -> Text -> Either ParseError [StudyFeatures]
parseStudyFeatures uId now = parse (pStudyFeatures uId now <* eof) "" parseStudyFeatures uId now = parse (pStudyFeatures uId now <* eof) (unpack key)
where
Ldap.Attr key = ldapUserStudyFeatures
parseSubTermsSemester :: Text -> Either ParseError (StudyTermsId, Int)
parseSubTermsSemester = parse (pLMUTermsSemester <* eof) (unpack key)
where
Ldap.Attr key = ldapUserSubTermsSemester
pStudyFeatures :: UserId -> UTCTime -> Parser [StudyFeatures] pStudyFeatures :: UserId -> UTCTime -> Parser [StudyFeatures]
@ -19,9 +30,9 @@ pStudyFeatures studyFeaturesUser studyFeaturesUpdated = do
let let
pStudyFeature = do pStudyFeature = do
_ <- pKey -- Meaning unknown at this time _ <- pKey -- "Fächergruppe"
void $ char '!' void $ char '!'
_ <- pKey -- Meaning unknown _ <- pKey -- "Studienbereich"
void $ char '!' void $ char '!'
studyFeaturesField <- StudyTermsKey' <$> pKey studyFeaturesField <- StudyTermsKey' <$> pKey
void $ char '!' void $ char '!'
@ -29,6 +40,7 @@ pStudyFeatures studyFeaturesUser studyFeaturesUpdated = do
void $ char '!' void $ char '!'
studyFeaturesSemester <- decimal studyFeaturesSemester <- decimal
let studyFeaturesValid = True let studyFeaturesValid = True
studyFeaturesSubField = Nothing
return StudyFeatures{..} return StudyFeatures{..}
pStudyFeature `sepBy1` char '#' pStudyFeature `sepBy1` char '#'
@ -45,3 +57,12 @@ decimal = foldl' (\now next -> now * 10 + next) 0 <$> many1 digit'
where where
digit' = dVal <$> digit digit' = dVal <$> digit
dVal c = fromEnum c - fromEnum '0' dVal c = fromEnum c - fromEnum '0'
pLMUTermsSemester :: Parser (StudyTermsId, Int)
pLMUTermsSemester = do
subTermsKey <- StudyTermsKey' <$> pKey
void $ char '$'
semester <- decimal
return (subTermsKey, semester)

View File

@ -724,7 +724,7 @@ submissionDeleteRoute drRecords = DeleteRoute
E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
let lastEdit = E.sub_select . E.from $ \submissionEdit -> do let lastEdit = E.subSelectMaybe . E.from $ \submissionEdit -> do
E.where_ $ submissionEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId E.where_ $ submissionEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId
return . E.max_ $ submissionEdit E.^. SubmissionEditTime return . E.max_ $ submissionEdit E.^. SubmissionEditTime
E.orderBy [E.desc lastEdit] E.orderBy [E.desc lastEdit]

View File

@ -25,11 +25,11 @@ import qualified Data.Map as Map
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
-- import Database.Esqueleto.Utils as E import qualified Database.Esqueleto.Internal.Sql as E
{-# ANN module ("HLint: ignore Use newtype instead of data"::String) #-} {-# ANN module ("HLint: ignore Use newtype instead of data"::String) #-}
type STKey = Int -- for convenience, assmued identical to field StudyTermCandidateKey type STKey = Int -- for convenience, assmued identical to field StudyTermNameCandidateKey
data FailedCandidateInference = FailedCandidateInference [Entity StudyTerms] data FailedCandidateInference = FailedCandidateInference [Entity StudyTerms]
deriving (Typeable, Show) deriving (Typeable, Show)
@ -46,26 +46,44 @@ instance Exception FailedCandidateInference
-- * list of problems, ie. StudyTerms that contradict observed incidences -- * list of problems, ie. StudyTerms that contradict observed incidences
-- * list of redundants, i.e. redundant observed incidences -- * list of redundants, i.e. redundant observed incidences
-- * list of accepted, i.e. newly accepted key/name pairs -- * list of accepted, i.e. newly accepted key/name pairs
inferHandler :: Handler ([Entity StudyTerms],[TermCandidateIncidence],[Entity StudyTermCandidate],[(STKey,Text)]) inferNamesHandler :: Handler ([Entity StudyTerms],[TermCandidateIncidence],[Entity StudyTermNameCandidate],[(StudyTermsId,Text)])
inferHandler = runDB $ inferAcc ([],[],[]) inferNamesHandler = runDB $ inferAcc mempty
where where
inferAcc (accAmbiguous, accRedundants, accAccepted) = inferAcc (accAmbiguous, accRedundants, accAccepted) =
handle (\(FailedCandidateInference fails) -> (fails,accAmbiguous,accRedundants,accAccepted) <$ E.transactionUndo) $ do handle (\(FailedCandidateInference fails) -> (fails, accAmbiguous, accRedundants, accAccepted') <$ E.transactionUndo) $ do
(infAmbis, infReds,infAccs) <- inferStep (infAmbis, infReds, infAccs) <- inferStep
if null infAccs if
then return ([], accAmbiguous, infReds ++ accRedundants, accAccepted) | null infAccs ->
else do return ([], accAmbiguous, infReds <> accRedundants, accAccepted')
| otherwise -> do
E.transactionSave -- commit transaction if there are no problems E.transactionSave -- commit transaction if there are no problems
inferAcc (infAmbis ++ accAmbiguous, infReds ++ accRedundants, infAccs ++ accAccepted) inferAcc (infAmbis <> accAmbiguous, infReds <> accRedundants, infAccs <> accAccepted)
where
accAccepted' = over (traversed . _1) StudyTermsKey' accAccepted
inferStep = do inferStep = do
ambiguous <- removeAmbiguous ambiguous <- removeAmbiguousNames
redundants <- removeRedundant redundants <- removeRedundantNames
accepted <- acceptSingletons accepted <- acceptSingletonNames
problems <- conflicts problems <- nameConflicts
unless (null problems) $ throwM $ FailedCandidateInference problems unless (null problems) $ throwM $ FailedCandidateInference problems
return (ambiguous, redundants, accepted) return (ambiguous, redundants, accepted)
inferParentsHandler :: Handler ([Entity StudySubTermParentCandidate], [Entity StudySubTerms])
inferParentsHandler = runDB $ inferAcc mempty
where
inferAcc (infReds', infAccs') = do
(infReds, infAccs) <- inferStep
if
| null infAccs ->
return (infReds' <> infReds, infAccs')
| otherwise ->
inferAcc (infReds' <> infReds, infAccs' <> infAccs)
inferStep = do
redundants <- removeRedundantParents
accepted <- acceptSingletonParents
return (redundants, accepted)
{- {-
Candidate 1 11 "A" Candidate 1 11 "A"
Candidate 1 11 "B" Candidate 1 11 "B"
@ -87,35 +105,65 @@ as a fix we simply eliminate all observations that have the same name twice, see
-- | remove candidates with ambiguous observations, -- | remove candidates with ambiguous observations,
-- ie. candidates that have duplicated term names with differing keys -- ie. candidates that have duplicated term names with differing keys
-- which may happen in rare cases -- which may happen in rare cases
removeAmbiguous :: DB [TermCandidateIncidence] removeAmbiguousNames :: DB [TermCandidateIncidence]
removeAmbiguous = do removeAmbiguousNames = do
ambiList <- E.select $ E.from $ \candidate -> do ambiList <- E.select $ E.from $ \candidate -> do
E.groupBy ( candidate E.^. StudyTermCandidateIncidence E.groupBy ( candidate E.^. StudyTermNameCandidateIncidence
, candidate E.^. StudyTermCandidateKey , candidate E.^. StudyTermNameCandidateKey
, candidate E.^. StudyTermCandidateName , candidate E.^. StudyTermNameCandidateName
) )
E.having $ E.countRows E.!=. E.val (1 :: Int64) E.having $ E.countRows E.!=. E.val (1 :: Int64)
return $ candidate E.^. StudyTermCandidateIncidence return $ candidate E.^. StudyTermNameCandidateIncidence
let ambiSet = E.unValue <$> List.nub ambiList let ambiSet = E.unValue <$> List.nub ambiList
-- Most SQL dialects won't allow deletion and queries on the same table at once, hence we delete in two steps. -- Most SQL dialects won't allow deletion and queries on the same table at once, hence we delete in two steps.
deleteWhere [StudyTermCandidateIncidence <-. ambiSet] deleteWhere [StudyTermNameCandidateIncidence <-. ambiSet]
return ambiSet return ambiSet
-- | remove known StudyTerm from candidates that have the _exact_ name, -- | remove known StudyTerm from candidates that have the _exact_ name,
-- ie. if a candidate contains a known key, we remove it and its associated fullname -- ie. if a candidate contains a known key, we remove it and its associated fullname
-- only save if ambiguous candidates haven been removed -- only save if ambiguous candidates haven been removed
removeRedundant :: DB [Entity StudyTermCandidate] removeRedundantNames :: DB [Entity StudyTermNameCandidate]
removeRedundant = do removeRedundantNames = do
redundants <- E.select $ E.distinct $ E.from $ \(candidate `E.InnerJoin` sterm) -> do redundants <- E.select $ E.distinct $ E.from $ \(candidate `E.InnerJoin` sterm) -> do
E.on $ candidate E.^. StudyTermCandidateKey E.==. sterm E.^. StudyTermsKey E.on $ candidate E.^. StudyTermNameCandidateKey E.==. sterm E.^. StudyTermsKey
E.&&. E.just (candidate E.^. StudyTermCandidateName) E.==. sterm E.^. StudyTermsName E.&&. ( E.just (candidate E.^. StudyTermNameCandidateName) E.==. sterm E.^. StudyTermsName
E.||. E.exists (E.from $ \(subTerm `E.InnerJoin` sterm2) -> do
E.on $ subTerm E.^. StudySubTermsParent E.==. sterm E.^. StudyTermsId
E.&&. subTerm E.^. StudySubTermsChild E.==. sterm2 E.^. StudyTermsId
E.where_ $ E.just (candidate E.^. StudyTermNameCandidateName) E.==. sterm2 E.^. StudyTermsName
)
)
return candidate return candidate
-- Most SQL dialects won't allow deletion and queries on the same table at once, hence we delete in two steps. -- Most SQL dialects won't allow deletion and queries on the same table at once, hence we delete in two steps.
forM_ redundants $ \Entity{entityVal=StudyTermCandidate{..}} -> forM_ redundants $ \Entity{entityVal=StudyTermNameCandidate{..}} ->
deleteWhere $ ( StudyTermCandidateIncidence ==. studyTermCandidateIncidence ) deleteWhere $ ( StudyTermNameCandidateIncidence ==. studyTermNameCandidateIncidence )
: ([ StudyTermCandidateKey ==. studyTermCandidateKey ] : ([ StudyTermNameCandidateKey ==. studyTermNameCandidateKey ]
||. [ StudyTermCandidateName ==. studyTermCandidateName ]) ||. [ StudyTermNameCandidateName ==. studyTermNameCandidateName ])
return redundants
removeRedundantParents :: DB [Entity StudySubTermParentCandidate]
removeRedundantParents = do
redundants <- E.select . E.distinct . E.from $ \(candidate `E.InnerJoin` subTerm) -> do
E.on $ candidate E.^. StudySubTermParentCandidateKey E.==. E.veryUnsafeCoerceSqlExprValue (subTerm E.^. StudySubTermsChild)
E.&&. candidate E.^. StudySubTermParentCandidateParent E.==. E.veryUnsafeCoerceSqlExprValue (subTerm E.^. StudySubTermsParent)
return candidate
forM_ redundants $ \(Entity _ StudySubTermParentCandidate{..}) ->
E.delete . E.from $ \candidate ->
E.where_ $ candidate E.^. StudySubTermParentCandidateIncidence E.==. E.val studySubTermParentCandidateIncidence
E.&&. ( candidate E.^. StudySubTermParentCandidateParent `E.in_` E.valList [studySubTermParentCandidateParent, studySubTermParentCandidateKey]
E.||. candidate E.^. StudySubTermParentCandidateKey `E.in_` E.valList [studySubTermParentCandidateParent, studySubTermParentCandidateKey]
)
return redundants
removeRedundantStandalone :: DB [Entity StudyTermStandaloneCandidate]
removeRedundantStandalone = do
redundants <- E.select . E.distinct . E.from $ \(candidate `E.InnerJoin` sterm) -> do
E.on $ candidate E.^. StudyTermStandaloneCandidateKey E.==. sterm E.^. StudyTermsKey
E.&&. E.not_ (E.isNothing $ sterm E.^. StudyTermsDefaultDegree)
E.&&. E.not_ (E.isNothing $ sterm E.^. StudyTermsDefaultType)
return candidate
deleteWhere [ StudyTermStandaloneCandidateId <-. map entityKey redundants ]
return redundants return redundants
@ -124,12 +172,12 @@ removeRedundant = do
-- Does not delete the used candidates, user @removeRedundant@ for this later on. -- Does not delete the used candidates, user @removeRedundant@ for this later on.
-- Esqueleto does not provide the INTERESECT operator, thus -- Esqueleto does not provide the INTERESECT operator, thus
-- we load the table into Haskell and operate there. Memory usage problem? StudyTermsCandidate may become huge. -- we load the table into Haskell and operate there. Memory usage problem? StudyTermsCandidate may become huge.
acceptSingletons :: DB [(STKey,Text)] acceptSingletonNames :: DB [(STKey,Text)]
acceptSingletons = do acceptSingletonNames = do
knownKeys <- fmap unStudyTermsKey <$> selectKeysList [StudyTermsName !=. Nothing] [Asc StudyTermsKey] knownKeys <- fmap unStudyTermsKey <$> selectKeysList [StudyTermsName !=. Nothing] [Asc StudyTermsKey]
-- let knownKeysSet = Set.fromAscList knownKeys -- let knownKeysSet = Set.fromAscList knownKeys
-- In case of memory problems, change next lines to conduit proper: -- In case of memory problems, change next lines to conduit proper:
incidences <- fmap entityVal <$> selectList [StudyTermCandidateKey /<-. knownKeys] [] -- LimitTo might be dangerous here, if we get a partial incidence. Possibly first select N incidences, then retrieving all those only. incidences <- fmap entityVal <$> selectList [StudyTermNameCandidateKey /<-. knownKeys] [] -- LimitTo might be dangerous here, if we get a partial incidence. Possibly first select N incidences, then retrieving all those only.
-- incidences <- E.select $ E.from $ \candidate -> do -- incidences <- E.select $ E.from $ \candidate -> do
-- E.where_ $ candidate E.^. StudyTermCandidayeKey `E.notIn` E.valList knownKeys -- E.where_ $ candidate E.^. StudyTermCandidayeKey `E.notIn` E.valList knownKeys
-- return candidate -- return candidate
@ -139,11 +187,11 @@ acceptSingletons = do
groupedCandidates = foldl' groupFun mempty incidences groupedCandidates = foldl' groupFun mempty incidences
-- given a key, map each incidence to set of possible names for this key -- given a key, map each incidence to set of possible names for this key
groupFun :: Map STKey (Map TermCandidateIncidence (Set Text)) -> StudyTermCandidate -> Map STKey (Map TermCandidateIncidence (Set Text)) groupFun :: Map STKey (Map TermCandidateIncidence (Set Text)) -> StudyTermNameCandidate -> Map STKey (Map TermCandidateIncidence (Set Text))
groupFun m StudyTermCandidate{..} = groupFun m StudyTermNameCandidate{..} =
insertWith (Map.unionWith Set.union) insertWith (Map.unionWith Set.union)
studyTermCandidateKey studyTermNameCandidateKey
(Map.singleton studyTermCandidateIncidence $ Set.singleton studyTermCandidateName) (Map.singleton studyTermNameCandidateIncidence $ Set.singleton studyTermNameCandidateName)
m m
-- pointwise intersection per incidence gives possible candidates per key -- pointwise intersection per incidence gives possible candidates per key
@ -152,36 +200,99 @@ acceptSingletons = do
-- filter candidates having a unique possibility left -- filter candidates having a unique possibility left
fixedKeys :: [(STKey,Text)] fixedKeys :: [(STKey,Text)]
fixedKeys = Map.foldlWithKey' combFixed [] keyCandidates fixedKeys = fst $ Map.foldlWithKey' combFixed mempty keyCandidates
where
combFixed :: [(STKey,Text)] -> STKey -> Set Text -> [(STKey,Text)] combFixed :: ([(STKey,Text)], Set STKey) -> STKey -> Set Text -> ([(STKey,Text)], Set STKey)
combFixed acc k s | Set.size s == 1 -- possibly redundant combFixed (acc, bad) k s
, [n] <- Set.elems s = (k,n):acc | Set.member k bad
-- empty sets should not occur here , if LDAP is consistent. Maybe raise a warning?! = (acc, bad)
| otherwise = acc | maybe False (`Set.notMember` s) (lookup k acc)
= (filter (\(k', _) -> k /= k') acc, Set.insert k bad)
| [n] <- Set.elems s
= ((k,n) : acc, bad)
| otherwise = (acc, bad)
-- registerFixed :: (STKey, Text) -> DB (Key StudyTerms) -- registerFixed :: (STKey, Text) -> DB (Key StudyTerms)
registerFixed :: (STKey, Text) -> DB () registerFixed :: (STKey, Text) -> DB ()
registerFixed (key, name) = repsert (StudyTermsKey' key) $ StudyTerms key Nothing (Just name) registerFixed (key, name) =
repsert (StudyTermsKey' key) $ StudyTerms key Nothing (Just name) Nothing Nothing
-- register newly fixed candidates -- register newly fixed candidates
forM_ fixedKeys registerFixed forM_ fixedKeys registerFixed
return fixedKeys return fixedKeys
acceptSingletonParents :: DB [Entity StudySubTerms]
acceptSingletonParents = do
candidates <- map entityVal <$> selectList [] []
let
groupedCandidates :: Map STKey (Map UUID (Set STKey))
groupedCandidates = foldl' groupFun mempty candidates
where
groupFun :: Map STKey (Map UUID (Set STKey)) -> StudySubTermParentCandidate -> Map STKey (Map UUID (Set STKey))
groupFun m StudySubTermParentCandidate{..} =
Map.insertWith (Map.unionWith Set.union)
studySubTermParentCandidateKey
(Map.singleton studySubTermParentCandidateIncidence $ Set.singleton studySubTermParentCandidateParent)
m
parentCandidates :: Map STKey (Set STKey)
parentCandidates = Map.map (setIntersections . Map.elems) groupedCandidates
fixedParents :: [(STKey, STKey)]
fixedParents = fst $ Map.foldlWithKey' combFixed mempty parentCandidates
where
combFixed :: ([(STKey, STKey)], Set STKey) -> STKey -> Set STKey -> ([(STKey, STKey)], Set STKey)
combFixed (acc, bad) k s
| Set.member k bad
= (acc, bad)
| maybe False (`Set.notMember` s) (lookup k acc)
= (filter (\(k', _) -> k /= k') acc, Set.insert k bad)
| [p] <- Set.elems s
= ((k, p) : acc, bad)
| otherwise = (acc, bad)
inserted <- forM fixedParents $ \(key, parent) -> do
unlessM (existsKey $ StudyTermsKey' key) $
insert_ (StudyTerms key Nothing Nothing Nothing Nothing)
unlessM (existsKey $ StudyTermsKey' parent) $
insert_ (StudyTerms parent Nothing Nothing Nothing Nothing)
insertUnique $ StudySubTerms
{ studySubTermsChild = StudyTermsKey' key
, studySubTermsParent = StudyTermsKey' parent
}
mapM getJustEntity $ catMaybes inserted
-- | all existing StudyTerms that are contradiced by current observations -- | all existing StudyTerms that are contradiced by current observations
conflicts :: DB [Entity StudyTerms] nameConflicts :: DB [Entity StudyTerms]
conflicts = E.select $ E.from $ \studyTerms -> do nameConflicts = E.select $ E.from $ \studyTerms -> do
E.where_ $ E.not_ $ E.isNothing $ studyTerms E.^. StudyTermsName E.where_ $ E.not_ $ E.isNothing $ studyTerms E.^. StudyTermsName
E.where_ $ E.exists $ E.from $ \candidateOne -> do E.where_ $ E.exists $ E.from $ \candidateOne -> do
E.where_ $ candidateOne E.^. StudyTermCandidateKey E.==. studyTerms E.^. StudyTermsKey E.where_ $ candidateOne E.^. StudyTermNameCandidateKey E.==. studyTerms E.^. StudyTermsKey
E.where_ $ E.notExists . E.from $ \candidateTwo -> do E.where_ $ E.notExists . E.from $ \candidateTwo -> do
E.where_ $ candidateTwo E.^. StudyTermCandidateIncidence E.==. candidateOne E.^. StudyTermCandidateIncidence E.where_ $ candidateTwo E.^. StudyTermNameCandidateIncidence E.==. candidateOne E.^. StudyTermNameCandidateIncidence
E.where_ $ studyTerms E.^. StudyTermsName E.==. E.just (candidateTwo E.^. StudyTermCandidateName) E.where_ $ studyTerms E.^. StudyTermsName E.==. E.just (candidateTwo E.^. StudyTermNameCandidateName)
E.||. E.exists ( E.from $ \(pCandidate `E.LeftOuterJoin` termsTwo) -> do
E.on $ pCandidate E.^. StudySubTermParentCandidateParent E.==. studyTerms E.^. StudyTermsKey
E.&&. E.just (pCandidate E.^. StudySubTermParentCandidateKey) E.==. termsTwo E.?. StudyTermsKey
E.where_ $ E.joinV (termsTwo E.?. StudyTermsName) E.==. E.just (candidateTwo E.^. StudyTermNameCandidateName)
E.||. E.isNothing (E.joinV $ termsTwo E.?. StudyTermsName)
)
E.||. E.exists ( E.from $ \(subTerms `E.InnerJoin` termsTwo) -> do
E.on $ subTerms E.^. StudySubTermsParent E.==. studyTerms E.^. StudyTermsId
E.&&. subTerms E.^. StudySubTermsChild E.==. termsTwo E.^. StudyTermsId
E.where_ $ termsTwo E.^. StudyTermsName E.==. E.just (candidateTwo E.^. StudyTermNameCandidateName)
E.||. E.isNothing (termsTwo E.^. StudyTermsName)
)
return studyTerms return studyTerms
-- | retrieve all incidence keys having containing a certain @StudyTermKey @ -- | retrieve all incidence keys having containing a certain @StudyTermKey @
getIncidencesFor :: [Key StudyTerms] -> DB [E.Value TermCandidateIncidence] getNameIncidencesFor :: [StudyTermsId] -> DB [E.Value TermCandidateIncidence]
getIncidencesFor stks = E.select $ E.distinct $ E.from $ \candidate -> do getNameIncidencesFor stks = E.select $ E.distinct $ E.from $ \candidate -> do
E.where_ $ candidate E.^. StudyTermCandidateKey `E.in_` E.valList (unStudyTermsKey <$> stks) E.where_ $ candidate E.^. StudyTermNameCandidateKey `E.in_` E.valList stks'
return $ candidate E.^. StudyTermCandidateIncidence return $ candidate E.^. StudyTermNameCandidateIncidence
where
stks' = stks <&> unStudyTermsKey

View File

@ -86,11 +86,10 @@ dispatchNotificationAllocationUnratedApplications nAllocation jRecipient = do
let let
unratedAppCount :: E.SqlExpr (E.Value Natural) unratedAppCount :: E.SqlExpr (E.Value Natural)
unratedAppCount = E.sub_select . E.from $ \application -> do unratedAppCount = E.subSelectCount . E.from $ \application ->
E.where_ $ application E.^. CourseApplicationCourse E.==. course E.^. CourseId E.where_ $ application E.^. CourseApplicationCourse E.==. course E.^. CourseId
E.&&. application E.^. CourseApplicationAllocation E.==. E.val (Just nAllocation) E.&&. application E.^. CourseApplicationAllocation E.==. E.val (Just nAllocation)
E.&&. E.isNothing (application E.^. CourseApplicationRatingTime) E.&&. E.isNothing (application E.^. CourseApplicationRatingTime)
return E.countRows
return ( course E.^. CourseTerm return ( course E.^. CourseTerm
, course E.^. CourseSchool , course E.^. CourseSchool
@ -128,11 +127,10 @@ dispatchNotificationAllocationOutdatedRatings nAllocation jRecipient = do
let let
outdatedRatingsAppCount :: E.SqlExpr (E.Value Natural) outdatedRatingsAppCount :: E.SqlExpr (E.Value Natural)
outdatedRatingsAppCount = E.sub_select . E.from $ \application -> do outdatedRatingsAppCount = E.subSelectCount . E.from $ \application ->
E.where_ $ application E.^. CourseApplicationCourse E.==. course E.^. CourseId E.where_ $ application E.^. CourseApplicationCourse E.==. course E.^. CourseId
E.&&. application E.^. CourseApplicationAllocation E.==. E.val (Just nAllocation) E.&&. application E.^. CourseApplicationAllocation E.==. E.val (Just nAllocation)
E.&&. E.maybe E.false (E.<. application E.^. CourseApplicationTime) (application E.^. CourseApplicationRatingTime) E.&&. E.maybe E.false (E.<. application E.^. CourseApplicationTime) (application E.^. CourseApplicationRatingTime)
return E.countRows
return ( course E.^. CourseTerm return ( course E.^. CourseTerm
, course E.^. CourseSchool , course E.^. CourseSchool
@ -170,13 +168,13 @@ dispatchNotificationAllocationResults nAllocation jRecipient = userMailT jRecipi
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val nAllocation
) )
let allocatedCount = E.sub_select . E.from $ \participant -> do let allocatedCount :: E.SqlExpr (E.Value Int64)
allocatedCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
E.&&. participant E.^. CourseParticipantAllocated E.==. E.just (E.val nAllocation) E.&&. participant E.^. CourseParticipantAllocated E.==. E.just (E.val nAllocation)
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int64)) let participantCount :: E.SqlExpr (E.Value Int64)
let participantCount = E.sub_select . E.from $ \participant -> do participantCount = E.subSelectCount . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse E.where_ $ participant E.^. CourseParticipantCourse E.==. lecturer E.^. LecturerCourse
return E.countRows :: E.SqlQuery (E.SqlExpr (E.Value Int64))
return (course, allocatedCount, participantCount) return (course, allocatedCount, participantCount)
let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, E.Value allocCount, E.Value partCount) -> SomeMessage $ if let lecturerResults = flip map lecturerResults' $ \(Entity _ Course{..}, E.Value allocCount, E.Value partCount) -> SomeMessage $ if
| allocCount == partCount -> MsgAllocationResultLecturerAll courseShorthand allocCount | allocCount == partCount -> MsgAllocationResultLecturerAll courseShorthand allocCount

View File

@ -1,4 +1,4 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances #-}
module Model module Model
( module Model ( module Model

View File

@ -1,3 +1,5 @@
{-# LANGUAGE UndecidableInstances #-}
module Model.Migration module Model.Migration
( migrateAll ( migrateAll
, requiresMigration , requiresMigration
@ -585,6 +587,10 @@ customMigrations = Map.fromListWith (>>)
ALTER TABLE "user" DROP COLUMN "mail_languages"; ALTER TABLE "user" DROP COLUMN "mail_languages";
|] |]
) )
, ( AppliedMigrationKey [migrationVersion|27.0.0|] [version|28.0.0|]
, whenM (tableExists "exam_part_corrector") $
tableDropEmpty "exam_part_corrector"
)
] ]

View File

@ -30,6 +30,7 @@ type StudyDegreeKey = Int
type StudyTermsName = Text type StudyTermsName = Text
type StudyTermsShorthand = Text type StudyTermsShorthand = Text
type StudyTermsKey = Int type StudyTermsKey = Int
type StudySubTermsKey = Int
type SchoolName = CI Text type SchoolName = CI Text
type SchoolShorthand = CI Text type SchoolShorthand = CI Text

View File

@ -34,6 +34,7 @@ data StudyFieldType = FieldPrimary | FieldSecondary
derivePersistField "StudyFieldType" derivePersistField "StudyFieldType"
instance Universe StudyFieldType instance Universe StudyFieldType
instance Finite StudyFieldType instance Finite StudyFieldType
nullaryPathPiece ''StudyFieldType $ camelToPathPiece' 1
data Theme data Theme

View File

@ -237,10 +237,6 @@ stepTextCounter text
-- Data.Text.groupBy ((==) `on` isDigit) $ Data.Text.pack "12.ProMo Ue3bung00322 34 (H)" -- Data.Text.groupBy ((==) `on` isDigit) $ Data.Text.pack "12.ProMo Ue3bung00322 34 (H)"
-- ["12",".ProMo Ue","3","bung","00322"," ","34"," (H)"] -- ["12",".ProMo Ue","3","bung","00322"," ","34"," (H)"]
-- | Ignore warnings for unused variables with a more specific type
notUsedT :: a -> Text
notUsedT = notUsed
fromText :: (IsString a, Textual t) => t -> a fromText :: (IsString a, Textual t) => t -> a
fromText = fromString . unpack fromText = fromString . unpack
@ -732,7 +728,7 @@ choice = foldr (<|>) empty
-------------- --------------
data SessionKey = SessionActiveAuthTags | SessionInactiveAuthTags data SessionKey = SessionActiveAuthTags | SessionInactiveAuthTags
| SessionNewStudyTerms | SessionNewStudyTerms | SessionConflictingStudyTerms
| SessionBearer | SessionBearer
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe SessionKey instance Universe SessionKey

View File

@ -49,6 +49,12 @@ _nullable = prism' toNullable fromNullable
_SchoolId :: Iso' SchoolId SchoolShorthand _SchoolId :: Iso' SchoolId SchoolShorthand
_SchoolId = iso unSchoolKey SchoolKey _SchoolId = iso unSchoolKey SchoolKey
_StudyTermsId :: Iso' StudyTermsId StudyTermsKey
_StudyTermsId = iso unStudyTermsKey StudyTermsKey'
_StudyDegreeId :: Iso' StudyDegreeId StudyDegreeKey
_StudyDegreeId = iso unStudyDegreeKey StudyDegreeKey'
_Maybe :: Iso' (Maybe ()) Bool _Maybe :: Iso' (Maybe ()) Bool
_Maybe = iso (is _Just) (bool Nothing (Just ())) _Maybe = iso (is _Just) (bool Nothing (Just ()))
@ -83,6 +89,7 @@ makeClassyFor_ ''StudyFeatures
makeClassyFor_ ''StudyDegree makeClassyFor_ ''StudyDegree
makeClassyFor_ ''StudyTerms makeClassyFor_ ''StudyTerms
makeClassyFor_ ''StudySubTerms
_entityKey :: Getter (Entity record) (Key record) _entityKey :: Getter (Entity record) (Key record)
@ -126,7 +133,6 @@ hasEntityUser = hasEntity
-- instance (HasUser a) => HasUser (Entity a) where -- instance (HasUser a) => HasUser (Entity a) where
-- hasUser = _entityVal . hasUser -- hasUser = _entityVal . hasUser
makeLenses_ ''SheetCorrector makeLenses_ ''SheetCorrector
makeLenses_ ''Load makeLenses_ ''Load
@ -143,7 +149,9 @@ makePrisms ''AuthResult
makePrisms ''FormResult makePrisms ''FormResult
makeLenses_ ''StudyTermCandidate makeLenses_ ''StudyTermNameCandidate
makeLenses_ ''StudySubTermParentCandidate
makeLenses_ ''StudyTermStandaloneCandidate
makeLenses_ ''FieldView makeLenses_ ''FieldView
makeLenses_ ''FieldSettings makeLenses_ ''FieldSettings

View File

@ -39,7 +39,11 @@ extra-deps:
- directory-1.3.4.0 - directory-1.3.4.0
- HaXml-1.25.5 - HaXml-1.25.5
- esqueleto-3.0.0
- persistent-2.10.4
- persistent-postgresql-2.10.1
- persistent-template-2.7.3
- esqueleto-3.2.3
- HaskellNet-SSL-0.3.4.1 - HaskellNet-SSL-0.3.4.1
- sandi-0.5 - sandi-0.5

View File

@ -0,0 +1,3 @@
#admin-studyterms
select, option, input
min-width: 50px

View File

@ -1,19 +1,40 @@
$newline never $newline never
<section> <section>
<h2>
_{MsgStudyFeaturesDegrees}
^{degreeTable} ^{degreeTable}
<section> <section>
<h2>
_{MsgStudyFeaturesTerms}
^{studytermsTable} ^{studytermsTable}
<section> <section>
<h2>_{MsgStudyFeatureInference} <h2>
$if null infConflicts _{MsgStudyFeatureNameInference}
<p> <p>
_{MsgStudyFeatureInferenceNoConflicts} $if null infNameConflicts
$else _{MsgStudyFeatureInferenceNoNameConflicts}
<h3>_{MsgStudyFeatureInferenceConflictsHeading} $else
<ul> <h3>_{MsgStudyFeatureInferenceNameConflictsHeading}
$forall (Entity _ (StudyTerms ky _ nm)) <- infConflicts <ul>
<li> #{show ky} - #{foldMap id nm} $forall Entity _ (StudyTerms ky _ nm _ _) <- infNameConflicts
^{btnForm} <li>
#{show ky} - #{foldMap id nm}
^{nameBtnForm}
<h2>
_{MsgStudyFeaturesNameCandidates}
^{candidateTable}
<section>
<h2>
_{MsgStudyFeatureParentInference}
^{parentsBtnForm}
<div .container> <h2>
^{candidateTable} _{MsgStudyFeaturesParentCandidates}
^{parentCandidateTable}
<section>
<h2>
_{MsgStudyFeaturesStandaloneCandidates}
^{standaloneBtnForm}
^{standaloneCandidateTable}

View File

@ -37,11 +37,10 @@ $newline never
<th .table__th>_{MsgStudyFeatureAge} <th .table__th>_{MsgStudyFeatureAge}
<th .table__th>_{MsgStudyFeatureValid} <th .table__th>_{MsgStudyFeatureValid}
<th .table__th>_{MsgStudyFeatureUpdate} <th .table__th>_{MsgStudyFeatureUpdate}
$forall ((Entity _ StudyFeatures{..}), (Entity _ degree), (Entity _ field)) <- studies $forall ((Entity _ StudyFeatures{studyFeaturesType, studyFeaturesSemester, studyFeaturesValid, studyFeaturesUpdated}), (Entity _ degree), (Entity _ field)) <- studies
$with _ <- notUsedT studyFeaturesUser
<tr .table__row> <tr .table__row>
<td .table__td>_{field}#{notUsedT studyFeaturesField} <td .table__td>_{field}
<td .table__td>_{degree}#{notUsedT studyFeaturesDegree} <td .table__td>_{degree}
<td .table__td>_{studyFeaturesType} <td .table__td>_{studyFeaturesType}
<td .table__td>#{studyFeaturesSemester} <td .table__td>#{studyFeaturesSemester}
<td .table__td>#{hasTickmark studyFeaturesValid} <td .table__td>#{hasTickmark studyFeaturesValid}

View File

@ -43,11 +43,10 @@ $newline never
<th .table__th>_{MsgStudyFeatureValid} <th .table__th>_{MsgStudyFeatureValid}
<th .table__th>_{MsgStudyFeatureUpdate} <th .table__th>_{MsgStudyFeatureUpdate}
$forall ((Entity _ StudyFeatures{..}), (Entity _ degree), (Entity _ field)) <- studies $forall ((Entity _ StudyFeatures{studyFeaturesType, studyFeaturesSemester, studyFeaturesValid, studyFeaturesUpdated}), (Entity _ degree), (Entity _ field)) <- studies
$with _ <- notUsedT studyFeaturesUser
<tr.table__row> <tr.table__row>
<td .table__td>_{field}#{notUsedT studyFeaturesField} <td .table__td>_{field}
<td .table__td>_{degree}#{notUsedT studyFeaturesDegree} <td .table__td>_{degree}
<td .table__td>_{studyFeaturesType} <td .table__td>_{studyFeaturesType}
<td .table__td>#{studyFeaturesSemester} <td .table__td>#{studyFeaturesSemester}
<td .table__td>#{hasTickmark studyFeaturesValid} <td .table__td>#{hasTickmark studyFeaturesValid}

View File

@ -317,87 +317,88 @@ fillDb = do
sdChem2 = StudyTermsKey' 113 sdChem2 = StudyTermsKey' 113
sdBWL = StudyTermsKey' 21 sdBWL = StudyTermsKey' 21
sdDeut = StudyTermsKey' 103 sdDeut = StudyTermsKey' 103
repsert sdInf $ StudyTerms 79 (Just "Inf") (Just "Informatikk") repsert sdInf $ StudyTerms 79 (Just "Inf") (Just "Informatikk") Nothing Nothing
repsert sdMath $ StudyTerms 105 (Just "Math" ) (Just "Mathematik") repsert sdMath $ StudyTerms 105 (Just "Math" ) (Just "Mathematik") Nothing Nothing
repsert sdMedi $ StudyTerms 121 Nothing (Just "Fehler hier") repsert sdMedi $ StudyTerms 121 Nothing (Just "Fehler hier") Nothing Nothing
repsert sdPhys $ StudyTerms 128 Nothing Nothing -- intentionally left unknown repsert sdPhys $ StudyTerms 128 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdBioI1 $ StudyTerms 221 Nothing Nothing -- intentionally left unknown repsert sdBioI1 $ StudyTerms 221 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdBioI2 $ StudyTerms 228 Nothing Nothing -- intentionally left unknown repsert sdBioI2 $ StudyTerms 228 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdBiol $ StudyTerms 26 Nothing Nothing -- intentionally left unknown repsert sdBiol $ StudyTerms 26 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdChem1 $ StudyTerms 61 Nothing Nothing -- intentionally left unknown repsert sdChem1 $ StudyTerms 61 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdChem2 $ StudyTerms 113 Nothing Nothing -- intentionally left unknown repsert sdChem2 $ StudyTerms 113 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdBWL $ StudyTerms 21 Nothing Nothing -- intentionally left unknown repsert sdBWL $ StudyTerms 21 Nothing Nothing Nothing Nothing -- intentionally left unknown
repsert sdDeut $ StudyTerms 103 Nothing Nothing -- intentionally left unknown repsert sdDeut $ StudyTerms 103 Nothing Nothing Nothing Nothing -- intentionally left unknown
incidence1 <- liftIO getRandom incidence1 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence1 221 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence1 221 "Bioinformatik"
void . insert $ StudyTermCandidate incidence1 221 "Mathematik" void . insert $ StudyTermNameCandidate incidence1 221 "Mathematik"
void . insert $ StudyTermCandidate incidence1 105 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence1 105 "Bioinformatik"
void . insert $ StudyTermCandidate incidence1 105 "Mathematik" void . insert $ StudyTermNameCandidate incidence1 105 "Mathematik"
incidence2 <- liftIO getRandom incidence2 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence2 221 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence2 221 "Bioinformatik"
void . insert $ StudyTermCandidate incidence2 221 "Chemie" void . insert $ StudyTermNameCandidate incidence2 221 "Chemie"
void . insert $ StudyTermCandidate incidence2 61 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence2 61 "Bioinformatik"
void . insert $ StudyTermCandidate incidence2 61 "Chemie" void . insert $ StudyTermNameCandidate incidence2 61 "Chemie"
incidence3 <- liftIO getRandom incidence3 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence3 113 "Chemie" void . insert $ StudyTermNameCandidate incidence3 113 "Chemie"
incidence4 <- liftIO getRandom -- ambiguous incidence incidence4 <- liftIO getRandom -- ambiguous incidence
void . insert $ StudyTermCandidate incidence4 221 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence4 221 "Bioinformatik"
void . insert $ StudyTermCandidate incidence4 221 "Chemie" void . insert $ StudyTermNameCandidate incidence4 221 "Chemie"
void . insert $ StudyTermCandidate incidence4 221 "Biologie" void . insert $ StudyTermNameCandidate incidence4 221 "Biologie"
void . insert $ StudyTermCandidate incidence4 61 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence4 61 "Bioinformatik"
void . insert $ StudyTermCandidate incidence4 61 "Chemie" void . insert $ StudyTermNameCandidate incidence4 61 "Chemie"
void . insert $ StudyTermCandidate incidence4 61 "Biologie" void . insert $ StudyTermNameCandidate incidence4 61 "Biologie"
void . insert $ StudyTermCandidate incidence4 61 "Chemie" void . insert $ StudyTermNameCandidate incidence4 61 "Chemie"
void . insert $ StudyTermCandidate incidence4 26 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence4 26 "Bioinformatik"
void . insert $ StudyTermCandidate incidence4 26 "Chemie" void . insert $ StudyTermNameCandidate incidence4 26 "Chemie"
void . insert $ StudyTermCandidate incidence4 26 "Biologie" void . insert $ StudyTermNameCandidate incidence4 26 "Biologie"
incidence5 <- liftIO getRandom incidence5 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence5 228 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence5 228 "Bioinformatik"
void . insert $ StudyTermCandidate incidence5 228 "Physik" void . insert $ StudyTermNameCandidate incidence5 228 "Physik"
void . insert $ StudyTermCandidate incidence5 128 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence5 128 "Bioinformatik"
void . insert $ StudyTermCandidate incidence5 128 "Physik" void . insert $ StudyTermNameCandidate incidence5 128 "Physik"
incidence6 <- liftIO getRandom incidence6 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence6 228 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence6 228 "Bioinformatik"
void . insert $ StudyTermCandidate incidence6 228 "Physik" void . insert $ StudyTermNameCandidate incidence6 228 "Physik"
void . insert $ StudyTermCandidate incidence6 128 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence6 128 "Bioinformatik"
void . insert $ StudyTermCandidate incidence6 128 "Physik" void . insert $ StudyTermNameCandidate incidence6 128 "Physik"
incidence7 <- liftIO getRandom incidence7 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence7 228 "Physik" void . insert $ StudyTermNameCandidate incidence7 228 "Physik"
void . insert $ StudyTermCandidate incidence7 228 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence7 228 "Bioinformatik"
void . insert $ StudyTermCandidate incidence7 128 "Physik" void . insert $ StudyTermNameCandidate incidence7 128 "Physik"
void . insert $ StudyTermCandidate incidence7 128 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence7 128 "Bioinformatik"
incidence8 <- liftIO getRandom incidence8 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence8 128 "Physik" void . insert $ StudyTermNameCandidate incidence8 128 "Physik"
void . insert $ StudyTermCandidate incidence8 128 "Medieninformatik" void . insert $ StudyTermNameCandidate incidence8 128 "Medieninformatik"
void . insert $ StudyTermCandidate incidence8 121 "Physik" void . insert $ StudyTermNameCandidate incidence8 121 "Physik"
void . insert $ StudyTermCandidate incidence8 121 "Medieninformatik" void . insert $ StudyTermNameCandidate incidence8 121 "Medieninformatik"
incidence9 <- liftIO getRandom incidence9 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence9 79 "Informatik" void . insert $ StudyTermNameCandidate incidence9 79 "Informatik"
incidence10 <- liftIO getRandom incidence10 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence10 103 "Deutsch" void . insert $ StudyTermNameCandidate incidence10 103 "Deutsch"
void . insert $ StudyTermCandidate incidence10 103 "Betriebswirtschaftslehre" void . insert $ StudyTermNameCandidate incidence10 103 "Betriebswirtschaftslehre"
void . insert $ StudyTermCandidate incidence10 21 "Deutsch" void . insert $ StudyTermNameCandidate incidence10 21 "Deutsch"
void . insert $ StudyTermCandidate incidence10 21 "Betriebswirtschaftslehre" void . insert $ StudyTermNameCandidate incidence10 21 "Betriebswirtschaftslehre"
incidence11 <- liftIO getRandom incidence11 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence11 221 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence11 221 "Bioinformatik"
void . insert $ StudyTermCandidate incidence11 221 "Chemie" void . insert $ StudyTermNameCandidate incidence11 221 "Chemie"
void . insert $ StudyTermCandidate incidence11 221 "Biologie" void . insert $ StudyTermNameCandidate incidence11 221 "Biologie"
void . insert $ StudyTermCandidate incidence11 61 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence11 61 "Bioinformatik"
void . insert $ StudyTermCandidate incidence11 61 "Chemie" void . insert $ StudyTermNameCandidate incidence11 61 "Chemie"
void . insert $ StudyTermCandidate incidence11 61 "Biologie" void . insert $ StudyTermNameCandidate incidence11 61 "Biologie"
void . insert $ StudyTermCandidate incidence11 26 "Bioinformatik" void . insert $ StudyTermNameCandidate incidence11 26 "Bioinformatik"
void . insert $ StudyTermCandidate incidence11 26 "Chemie" void . insert $ StudyTermNameCandidate incidence11 26 "Chemie"
void . insert $ StudyTermCandidate incidence11 26 "Biologie" void . insert $ StudyTermNameCandidate incidence11 26 "Biologie"
incidence12 <- liftIO getRandom incidence12 <- liftIO getRandom
void . insert $ StudyTermCandidate incidence12 103 "Deutsch" void . insert $ StudyTermNameCandidate incidence12 103 "Deutsch"
void . insert $ StudyTermCandidate incidence12 103 "Betriebswirtschaftslehre" void . insert $ StudyTermNameCandidate incidence12 103 "Betriebswirtschaftslehre"
void . insert $ StudyTermCandidate incidence12 21 "Deutsch" void . insert $ StudyTermNameCandidate incidence12 21 "Deutsch"
void . insert $ StudyTermCandidate incidence12 21 "Betriebswirtschaftslehre" void . insert $ StudyTermNameCandidate incidence12 21 "Betriebswirtschaftslehre"
sfMMp <- insert $ StudyFeatures -- keyword type prevents record syntax here sfMMp <- insert $ StudyFeatures -- keyword type prevents record syntax here
maxMuster maxMuster
sdBsc sdBsc
sdInf sdInf
Nothing
FieldPrimary FieldPrimary
2 2
now now
@ -406,6 +407,7 @@ fillDb = do
maxMuster maxMuster
sdBsc sdBsc
sdMath sdMath
Nothing
FieldSecondary FieldSecondary
2 2
now now
@ -414,6 +416,7 @@ fillDb = do
tinaTester tinaTester
sdBsc sdBsc
sdInf sdInf
Nothing
FieldPrimary FieldPrimary
4 4
now now
@ -422,6 +425,7 @@ fillDb = do
tinaTester tinaTester
sdLAG sdLAG
sdPhys sdPhys
Nothing
FieldPrimary FieldPrimary
1 1
now now
@ -430,6 +434,7 @@ fillDb = do
tinaTester tinaTester
sdLAR sdLAR
sdMedi sdMedi
Nothing
FieldPrimary FieldPrimary
7 7
now now
@ -438,6 +443,7 @@ fillDb = do
tinaTester tinaTester
sdMst sdMst
sdMath sdMath
Nothing
FieldPrimary FieldPrimary
3 3
now now