feat(users): store first names and titles

This commit is contained in:
Gregor Kleen 2019-07-26 11:36:56 +02:00
parent d0fb6ffd72
commit ceed070e35
6 changed files with 68 additions and 11 deletions

View File

@ -1239,8 +1239,9 @@ Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * p
CsvColumnsExplanationsLabel: Spalten CsvColumnsExplanationsLabel: Spalten
CsvColumnsExplanationsTip: Bedeutung der in der CSV-Datei enthaltenen Spalten CsvColumnsExplanationsTip: Bedeutung der in der CSV-Datei enthaltenen Spalten
CsvColumnExamUserSurname: Nachname des Teilnehmers CsvColumnExamUserSurname: Nachname(n) des Teilnehmers
CsvColumnExamUserName: Voller Name des Teilnehmers (inkl. Nachname) CsvColumnExamUserFirstName: Vorname(n) des Teilnehmers
CsvColumnExamUserName: Voller Name des Teilnehmers (gewöhnlicherweise inkl. Vor- und Nachname(n))
CsvColumnExamUserMatriculation: Matrikelnummer des Teilnehmers CsvColumnExamUserMatriculation: Matrikelnummer des Teilnehmers
CsvColumnExamUserField: Hauptfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat CsvColumnExamUserField: Hauptfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
CsvColumnExamUserDegree: Abschluss, den der Teilnehmer im assoziierten Hauptfach anstrebt CsvColumnExamUserDegree: Abschluss, den der Teilnehmer im assoziierten Hauptfach anstrebt
@ -1250,7 +1251,7 @@ CsvColumnExamUserExercisePoints: Anzahl von Punkten, die der Teilnehmer im Übun
CsvColumnExamUserExercisePointsMax: Maximale Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb bis zu seinem Klausurtermin erreichen hätte können CsvColumnExamUserExercisePointsMax: Maximale Anzahl von Punkten, die der Teilnehmer im Übungsbetrieb bis zu seinem Klausurtermin erreichen hätte können
CsvColumnExamUserExercisePasses: Anzahl von Übungsblättern, die der Teilnehmer bestanden hat CsvColumnExamUserExercisePasses: Anzahl von Übungsblättern, die der Teilnehmer bestanden hat
CsvColumnExamUserExercisePassesMax: Maximale Anzahl von Übungsblättern, die der Teilnehmer bis zu seinem Klausurtermin bestehen hätte können CsvColumnExamUserExercisePassesMax: Maximale Anzahl von Übungsblättern, die der Teilnehmer bis zu seinem Klausurtermin bestehen hätte können
CsvColumnExamResult: Erreichte Klausurleistung; "passed", "failed", "no-show", "voided", oder eine Note ("1.0", "1.3", "1.7", ..., "4.0", "5.0") CsvColumnExamUserResult: Erreichte Klausurleistung; "passed", "failed", "no-show", "voided", oder eine Note ("1.0", "1.3", "1.7", ..., "4.0", "5.0")
Action: Aktion Action: Aktion

View File

@ -16,6 +16,8 @@ User json -- Each Uni2work user has a corresponding row in this table; create
email (CI Text) -- Case-insensitive eMail address email (CI Text) -- Case-insensitive eMail address
displayName Text -- we only show LDAP-DisplayName, and highlight LDAP-Surname within (appended if not contained) displayName Text -- we only show LDAP-DisplayName, and highlight LDAP-Surname within (appended if not contained)
surname Text -- Display user names always through 'nameWidget displayName surname' surname Text -- Display user names always through 'nameWidget displayName surname'
firstName Text -- For export in tables, pre-split firstName from displayName
title Text Maybe -- For upcoming name customisation
maxFavourites Int default=12 -- max number of rows with this userId in table "CourseFavourite"; for convenience links; user-defined maxFavourites Int default=12 -- max number of rows with this userId in table "CourseFavourite"; for convenience links; user-defined
theme Theme default='Default' -- Color-theme of the frontend; user-defined theme Theme default='Default' -- Color-theme of the frontend; user-defined
dateTimeFormat DateTimeFormat "default='%a %d %b %Y %R'" -- preferred Date+Time display format for user; user-defined dateTimeFormat DateTimeFormat "default='%a %d %b %Y %R'" -- preferred Date+Time display format for user; user-defined

View File

@ -33,6 +33,7 @@ import qualified Data.ByteString.Base64.URL as Base64 (encode)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as Lazy.ByteString import qualified Data.ByteString.Lazy as Lazy.ByteString
import qualified Data.ByteString as ByteString
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text import qualified Data.Text.Encoding as Text
@ -2748,7 +2749,9 @@ instance YesodAuth UniWorX where
userMatrikelnummer' = lookup (Attr "LMU-Stud-Matrikelnummer") ldapData userMatrikelnummer' = lookup (Attr "LMU-Stud-Matrikelnummer") ldapData
userEmail' = lookup (Attr "mail") ldapData userEmail' = lookup (Attr "mail") ldapData
userDisplayName' = lookup (Attr "displayName") ldapData userDisplayName' = lookup (Attr "displayName") ldapData
userFirstName' = lookup (Attr "givenName") ldapData
userSurname' = lookup (Attr "sn") ldapData userSurname' = lookup (Attr "sn") ldapData
userTitle' = lookup (Attr "title") ldapData
userAuthentication userAuthentication
| isPWHash = error "PWHash should only work for users that are already known" | isPWHash = error "PWHash should only work for users that are already known"
@ -2767,12 +2770,26 @@ instance YesodAuth UniWorX where
-> return userDisplayName -> return userDisplayName
| otherwise | otherwise
-> throwError $ ServerError "Could not retrieve user name" -> throwError $ ServerError "Could not retrieve user name"
userFirstName <- if
| Just [bs] <- userFirstName'
, Right userFirstName <- Text.decodeUtf8' bs
-> return userFirstName
| otherwise
-> throwError $ ServerError "Could not retrieve user given name"
userSurname <- if userSurname <- if
| Just [bs] <- userSurname' | Just [bs] <- userSurname'
, Right userSurname <- Text.decodeUtf8' bs , Right userSurname <- Text.decodeUtf8' bs
-> return userSurname -> return userSurname
| otherwise | otherwise
-> throwError $ ServerError "Could not retrieve user surname" -> throwError $ ServerError "Could not retrieve user surname"
userTitle <- if
| maybe True (all ByteString.null) userTitle'
-> return Nothing
| Just [bs] <- userTitle'
, Right userTitle <- Text.decodeUtf8' bs
-> return $ Just userTitle
| otherwise
-> throwError $ ServerError "Could not retrieve user title"
userMatrikelnummer <- if userMatrikelnummer <- if
| Just [bs] <- userMatrikelnummer' | Just [bs] <- userMatrikelnummer'
, Right userMatrikelnummer <- Text.decodeUtf8' bs , Right userMatrikelnummer <- Text.decodeUtf8' bs

View File

@ -92,6 +92,7 @@ resultExamResult = _dbrOutput . _7 . _Just
data ExamUserTableCsv = ExamUserTableCsv data ExamUserTableCsv = ExamUserTableCsv
{ csvEUserSurname :: Maybe Text { csvEUserSurname :: Maybe Text
, csvEUserFirstName :: Maybe Text
, csvEUserName :: Maybe Text , csvEUserName :: Maybe Text
, csvEUserMatriculation :: Maybe Text , csvEUserMatriculation :: Maybe Text
, csvEUserField :: Maybe Text , csvEUserField :: Maybe Text
@ -122,6 +123,7 @@ instance DefaultOrdered ExamUserTableCsv where
instance CsvColumnsExplained ExamUserTableCsv where instance CsvColumnsExplained ExamUserTableCsv where
csvColumnsExplanations = genericCsvColumnsExplanations examUserTableCsvOptions $ Map.fromList csvColumnsExplanations = genericCsvColumnsExplanations examUserTableCsvOptions $ Map.fromList
[ ('csvEUserSurname , MsgCsvColumnExamUserSurname ) [ ('csvEUserSurname , MsgCsvColumnExamUserSurname )
, ('csvEUserFirstName , MsgCsvColumnExamUserFirstName )
, ('csvEUserName , MsgCsvColumnExamUserName ) , ('csvEUserName , MsgCsvColumnExamUserName )
, ('csvEUserMatriculation , MsgCsvColumnExamUserMatriculation ) , ('csvEUserMatriculation , MsgCsvColumnExamUserMatriculation )
, ('csvEUserField , MsgCsvColumnExamUserField ) , ('csvEUserField , MsgCsvColumnExamUserField )
@ -132,7 +134,7 @@ instance CsvColumnsExplained ExamUserTableCsv where
, ('csvEUserExerciseNumPasses , MsgCsvColumnExamUserExercisePasses ) , ('csvEUserExerciseNumPasses , MsgCsvColumnExamUserExercisePasses )
, ('csvEUserExercisePointsMax , MsgCsvColumnExamUserExercisePointsMax ) , ('csvEUserExercisePointsMax , MsgCsvColumnExamUserExercisePointsMax )
, ('csvEUserExerciseNumPassesMax, MsgCsvColumnExamUserExercisePassesMax ) , ('csvEUserExerciseNumPassesMax, MsgCsvColumnExamUserExercisePassesMax )
, ('csvEUserExamResult , MsgCsvColumnExamResult ) , ('csvEUserExamResult , MsgCsvColumnExamUserResult )
] ]
data ExamUserAction = ExamUserDeregister data ExamUserAction = ExamUserDeregister
@ -253,8 +255,6 @@ postEUsersR tid ssh csh examn = do
] ]
dbtSorting = Map.fromList dbtSorting = Map.fromList
[ sortUserNameLink queryUser [ sortUserNameLink queryUser
, sortUserSurname queryUser
, sortUserDisplayName queryUser
, sortUserMatriclenr queryUser , sortUserMatriclenr queryUser
, sortField queryStudyField , sortField queryStudyField
, sortDegreeShort queryStudyDegree , sortDegreeShort queryStudyDegree
@ -325,6 +325,7 @@ postEUsersR tid ssh csh examn = do
dbtCsvEncode :: DBTCsvEncode ExamUserTableData ExamUserTableCsv dbtCsvEncode :: DBTCsvEncode ExamUserTableData ExamUserTableCsv
dbtCsvEncode = DictJust . C.map $ ExamUserTableCsv dbtCsvEncode = DictJust . C.map $ ExamUserTableCsv
<$> view (resultUser . _entityVal . _userSurname . to Just) <$> view (resultUser . _entityVal . _userSurname . to Just)
<*> view (resultUser . _entityVal . _userFirstName . to Just)
<*> view (resultUser . _entityVal . _userDisplayName . to Just) <*> view (resultUser . _entityVal . _userDisplayName . to Just)
<*> view (resultUser . _entityVal . _userMatrikelnummer) <*> view (resultUser . _entityVal . _userMatrikelnummer)
<*> preview (resultStudyField . _entityVal . to (\StudyTerms{..} -> studyTermsName <|> studyTermsShorthand <|> Just (tshow studyTermsKey)) . _Just) <*> preview (resultStudyField . _entityVal . to (\StudyTerms{..} -> studyTermsName <|> studyTermsShorthand <|> Just (tshow studyTermsKey)) . _Just)
@ -511,6 +512,7 @@ postEUsersR tid ssh csh examn = do
[ (user E.^. UserMatrikelnummer E.==.) . E.val . Just <$> csvEUserMatriculation [ (user E.^. UserMatrikelnummer E.==.) . E.val . Just <$> csvEUserMatriculation
, (user E.^. UserDisplayName E.==.) . E.val <$> csvEUserName , (user E.^. UserDisplayName E.==.) . E.val <$> csvEUserName
, (user E.^. UserSurname E.==.) . E.val <$> csvEUserSurname , (user E.^. UserSurname E.==.) . E.val <$> csvEUserSurname
, (user E.^. UserFirstName E.==.) . E.val <$> csvEUserFirstName
] ]
let isCourseParticipant = E.exists . E.from $ \courseParticipant -> let isCourseParticipant = E.exists . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. E.val examCourse E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. E.val examCourse

View File

@ -16,6 +16,10 @@ import qualified Data.Map as Map
import Data.Set () import Data.Set ()
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Conduit.List as C
import Database.Persist.Sql import Database.Persist.Sql
import Database.Persist.Postgresql import Database.Persist.Postgresql
@ -61,7 +65,7 @@ share [mkPersist sqlSettings, mkMigrate "migrateDBVersioning"]
migrateAll :: ( MonadLogger m migrateAll :: ( MonadLogger m
, MonadBaseControl IO m , MonadBaseControl IO m
, MonadIO m , MonadResource m
) )
=> ReaderT SqlBackend m () => ReaderT SqlBackend m ()
migrateAll = do migrateAll = do
@ -86,7 +90,7 @@ migrateAll = do
requiresMigration :: forall m. requiresMigration :: forall m.
( MonadLogger m ( MonadLogger m
, MonadBaseControl IO m , MonadBaseControl IO m
, MonadIO m , MonadResource m
) )
=> ReaderT SqlBackend m Bool => ReaderT SqlBackend m Bool
requiresMigration = mapReaderT (exceptT return return) $ do requiresMigration = mapReaderT (exceptT return return) $ do
@ -117,7 +121,7 @@ getMissingMigrations :: forall m m'.
( MonadLogger m ( MonadLogger m
, MonadBaseControl IO m , MonadBaseControl IO m
, MonadIO m , MonadIO m
, MonadIO m' , MonadResource m'
) )
=> ReaderT SqlBackend m (Map (Key AppliedMigration) (ReaderT SqlBackend m' ())) => ReaderT SqlBackend m (Map (Key AppliedMigration) (ReaderT SqlBackend m' ()))
getMissingMigrations = do getMissingMigrations = do
@ -134,8 +138,9 @@ getMissingMigrations = do
-} -}
customMigrations :: ( MonadIO m customMigrations :: forall m.
) => Map (Key AppliedMigration) (ReaderT SqlBackend m ()) MonadResource m
=> Map (Key AppliedMigration) (ReaderT SqlBackend m ())
customMigrations = Map.fromListWith (>>) customMigrations = Map.fromListWith (>>)
[ ( AppliedMigrationKey [migrationVersion|initial|] [version|0.0.0|] [ ( AppliedMigrationKey [migrationVersion|initial|] [version|0.0.0|]
, whenM (columnExists "user" "theme") $ do -- New theme format , whenM (columnExists "user" "theme") $ do -- New theme format
@ -318,6 +323,24 @@ customMigrations = Map.fromListWith (>>)
ALTER TABLE "exam_occurrence" ALTER COLUMN "name" SET NOT NULL; ALTER TABLE "exam_occurrence" ALTER COLUMN "name" SET NOT NULL;
|] |]
) )
, ( AppliedMigrationKey [migrationVersion|14.0.0|] [version|15.0.0|]
, whenM (tableExists "user") $ do
[executeQQ|
ALTER TABLE "user" ADD COLUMN "first_name" text NOT NULL DEFAULT '';
ALTER TABLE "user" ADD COLUMN "title" text DEFAULT null;
|]
let getUsers = rawQuery [st|SELECT "id", "display_name", "surname" FROM "user"|] []
updateUser (uid, firstName) = [executeQQ|UPDATE "user" SET "first_name" = #{firstName} WHERE "id" = #{uid}|]
splitFirstName :: [PersistValue] -> Maybe (UserId, Text)
splitFirstName [fromPersistValue -> Right uid, fromPersistValue -> Right displayName, fromPersistValue -> Right surname] = Just . (uid, ) $ if
| Just givenName <- Text.stripSuffix surname displayName
<|> Text.stripPrefix surname displayName
-> Text.strip givenName
| otherwise
-> Text.replace surname "" displayName
splitFirstName _ = Nothing
runConduit $ getUsers .| C.mapMaybe splitFirstName .| C.mapM_ updateUser
)
] ]

View File

@ -95,6 +95,8 @@ fillDb = do
, userEmail = "G.Kleen@campus.lmu.de" , userEmail = "G.Kleen@campus.lmu.de"
, userDisplayName = "Gregor Kleen" , userDisplayName = "Gregor Kleen"
, userSurname = "Kleen" , userSurname = "Kleen"
, userFirstName = "Gregor Julius Arthur"
, userTitle = Nothing
, userMaxFavourites = 6 , userMaxFavourites = 6
, userTheme = ThemeDefault , userTheme = ThemeDefault
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat
@ -113,6 +115,8 @@ fillDb = do
, userEmail = "felix.hamann@campus.lmu.de" , userEmail = "felix.hamann@campus.lmu.de"
, userDisplayName = "Felix Hamann" , userDisplayName = "Felix Hamann"
, userSurname = "Hamann" , userSurname = "Hamann"
, userFirstName = "Felix"
, userTitle = Nothing
, userMaxFavourites = userDefaultMaxFavourites , userMaxFavourites = userDefaultMaxFavourites
, userTheme = ThemeDefault , userTheme = ThemeDefault
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat
@ -131,6 +135,8 @@ fillDb = do
, userEmail = "jost@tcs.ifi.lmu.de" , userEmail = "jost@tcs.ifi.lmu.de"
, userDisplayName = "Steffen Jost" , userDisplayName = "Steffen Jost"
, userSurname = "Jost" , userSurname = "Jost"
, userFirstName = "Steffen"
, userTitle = Just "Dr."
, userMaxFavourites = 14 , userMaxFavourites = 14
, userTheme = ThemeMossGreen , userTheme = ThemeMossGreen
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat
@ -149,6 +155,8 @@ fillDb = do
, userEmail = "max@campus.lmu.de" , userEmail = "max@campus.lmu.de"
, userDisplayName = "Max Musterstudent" , userDisplayName = "Max Musterstudent"
, userSurname = "Musterstudent" , userSurname = "Musterstudent"
, userFirstName = "Max"
, userTitle = Nothing
, userMaxFavourites = 7 , userMaxFavourites = 7
, userTheme = ThemeAberdeenReds , userTheme = ThemeAberdeenReds
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat
@ -167,6 +175,8 @@ fillDb = do
, userEmail = "tester@campus.lmu.de" , userEmail = "tester@campus.lmu.de"
, userDisplayName = "Tina Tester" , userDisplayName = "Tina Tester"
, userSurname = "von Terror" , userSurname = "von Terror"
, userFirstName = "Sabrina"
, userTitle = Just "Magister"
, userMaxFavourites = 5 , userMaxFavourites = 5
, userTheme = ThemeAberdeenReds , userTheme = ThemeAberdeenReds
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat
@ -185,6 +195,8 @@ fillDb = do
, userEmail = "vaupel.sarah@campus.lmu.de" , userEmail = "vaupel.sarah@campus.lmu.de"
, userDisplayName = "Sarah Vaupel" , userDisplayName = "Sarah Vaupel"
, userSurname = "Vaupel" , userSurname = "Vaupel"
, userFirstName = "Sarah"
, userTitle = Nothing
, userMaxFavourites = 14 , userMaxFavourites = 14
, userTheme = ThemeMossGreen , userTheme = ThemeMossGreen
, userDateTimeFormat = userDefaultDateTimeFormat , userDateTimeFormat = userDefaultDateTimeFormat