diff --git a/PageActionPrime.txt b/PageActionPrime.txt index 98c8477e3..39562d190 100644 --- a/PageActionPrime.txt +++ b/PageActionPrime.txt @@ -1,4 +1,5 @@ -Es s +Übersicht über PageActions und Workflow im Vergleich zum alten UniWorX: +----- Course Actions im alten UniWorX: - Studenten @@ -8,6 +9,8 @@ Course Actions im alten UniWorX: - E-Mails - Online-Evaluation +----- + CourseActions in Uni2Work: -1 Übungsblätter (öfter) [ Auch aus SheetList übernommen un hier ebenfalls angzeigt: diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index e39fd7231..2f13a93f9 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -6,6 +6,7 @@ BtnDeregister: Abmelden BtnHijack: Sitzung übernehmen Aborted: Abgebrochen +Remarks: Hinweise Registered: Angemeldet RegisteredHeader: Anmeldung RegisteredSince date@Text: Angemeldet seit #{date} diff --git a/routes b/routes index 9b15ab3b9..381a9486a 100644 --- a/routes +++ b/routes @@ -37,6 +37,7 @@ / HomeR GET !free /users UsersR GET -- no tags, i.e. admins only /users/#CryptoUUIDUser AdminUserR GET POST !development +/users/#CryptoUUIDUser/delete AdminUserDeleteR POST !development /users/#CryptoUUIDUser/hijack AdminHijackUserR POST !adminANDno-escalation /admin AdminR GET /admin/features AdminFeaturesR GET POST @@ -52,7 +53,7 @@ /help HelpR GET POST !free /user ProfileR GET POST !free -/user/profile ProfileDataR GET POST !free +/user/profile ProfileDataR GET !free /user/authpreds AuthPredsR GET POST !free /term TermShowR GET !free diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs index 623e6dc18..ed3b194ac 100644 --- a/src/Handler/Course.hs +++ b/src/Handler/Course.hs @@ -833,6 +833,7 @@ getCUserR _tid _ssh _csh uCId = do -- - User is a lecturer for course uid <- decrypt uCId User{..} <- runDB $ get404 uid + -- USE src/utils/Form.formResult defaultLayout -- TODO [whamlet| @@ -844,5 +845,6 @@ getCHiWisR = error "CHiWisR: Not implemented" getCNotesR, postCNotesR :: TermId -> SchoolId -> CourseShorthand -> Handler Html -- NOTE: The route getNotesR is abused for correctorORlecturer access rights! -getCNotesR = error "CNotesR: Not implemented" +-- PROBLEM: Correctors usually don't know Participants by name (anonymous), maybe notes are not shared? +getCNotesR = error "CNotesR: Not implemented" postCNotesR = error "CNotesR: Not implemented" diff --git a/src/Handler/Profile.hs b/src/Handler/Profile.hs index c67b11abf..60a06c165 100644 --- a/src/Handler/Profile.hs +++ b/src/Handler/Profile.hs @@ -121,134 +121,47 @@ postProfileR = do setTitle . toHtml $ "Profil " <> userIdent $(widgetFile "formPageI18n") -postProfileDataR :: Handler Html -postProfileDataR = do - ((btnResult,_), _) <- runFormPost buttonForm - case btnResult of - (FormSuccess BtnDelete) -> do - (uid, User{..}) <- requireAuthPair - clearCreds False -- Logout-User - ((deletedSubmissions,groupSubmissions),deletedSubmissionGroups) <- runDB $ deleteUser uid - -- addMessageIHamlet - $(addMessageFile Success "templates/deletedUser.hamlet") -- USE THIS ONE - -- addMessageI Success $ MsgDeleteUser deletedSubmissions - -- when (groupSubmissions > 0) $ addMessageI Info $ MsgDeleteUserGroupSubmissions groupSubmissions - defaultLayout - $(widgetFile "deletedUser") - - -- (FormSuccess BtnAbort ) -> do - -- addMessageI Info MsgAborted - -- redirect ProfileDataR - _other -> getProfileDataR - - - -deleteUser :: UserId -> DB ((Int,Int),Int64) -- TODO: Restrict deletions for lecturers, tutors and students in course that won't allow deregistration -deleteUser duid = do - -- E.deleteCount for submissions is not cascading, hence we first select and then delete manually - -- We delete all files tied to submissions where the user is the lone submissionUser - - -- Do not deleteCascade submissions where duid is the corrector: - updateWhere [SubmissionRatingBy ==. Just duid] [SubmissionRatingBy =. Nothing] - - groupSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.>. E.val (0::Int64)) - singleSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.==. E.val (0::Int64)) - deleteCascade duid - forM_ singleSubmissions $ \(E.Value submissionId) -> do - deleteFileIds <- map E.unValue <$> getSubmissionFiles submissionId - deleteCascade submissionId - deleteCascadeWhere [FileId <-. deleteFileIds] -- TODO: throws exception for de-duplicated files - - deletedSubmissionGroups <- deleteSingleSubmissionGroups - return ((length singleSubmissions, length groupSubmissions),deletedSubmissionGroups) - where - 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 - E.on $ submission E.^. SubmissionId E.==. suser E.^. SubmissionUserSubmission - let numBuddies = E.sub_select $ E.from $ \subUsers -> do - E.where_ $ subUsers E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId - E.&&. subUsers E.^. SubmissionUserUser E.!=. E.val duid - return E.countRows - E.where_ $ suser E.^. SubmissionUserUser E.==. E.val duid - E.&&. whereBuddies numBuddies - return $ submission E.^. SubmissionId - - getSubmissionFiles :: SubmissionId -> DB [E.Value (Key File)] - getSubmissionFiles subId = E.select $ E.from $ \file -> do - E.where_ $ E.exists $ E.from $ \submissionFile -> - E.where_ $ submissionFile E.^. SubmissionFileSubmission E.==. E.val subId - E.&&. submissionFile E.^. SubmissionFileFile E.==. file E.^. FileId - return $ file E.^. FileId - - deleteSingleSubmissionGroups = E.deleteCount $ E.from $ \submissionGroup -> do - E.where_ $ E.exists $ E.from $ \subGroupUser -> - E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId - E.&&. subGroupUser E.^. SubmissionGroupUserUser E.==. E.val duid - E.where_ $ E.notExists $ E.from $ \subGroupUser -> - E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId - E.&&. subGroupUser E.^. SubmissionGroupUserUser E.!=. E.val duid - - getProfileDataR :: Handler Html getProfileDataR = do - (uid, User{..}) <- requireAuthPair + userEnt <- requireAuth + dataWidget <- runDB $ makeProfileData userEnt + defaultLayout $ do + dataWidget + $(widgetFile "dsgvDisclaimer") + +makeProfileData :: (Entity User) -> DB Widget +makeProfileData (Entity uid User{..}) = do -- MsgRenderer mr <- getMsgRenderer - (admin_rights,lecturer_rights,lecture_corrector,studies) <- runDB $ (,,,) <$> - E.select - ( E.from $ \(adright `E.InnerJoin` school) -> do - E.where_ $ adright E.^. UserAdminUser E.==. E.val uid - E.on $ adright E.^. UserAdminSchool E.==. school E.^. SchoolId - return (school E.^. SchoolShorthand) - ) - <*> - E.select - ( E.from $ \(lecright `E.InnerJoin` school) -> do - E.where_ $ lecright E.^. UserLecturerUser E.==. E.val uid - E.on $ lecright E.^. UserLecturerSchool E.==. school E.^. SchoolId - return (school E.^. SchoolShorthand) - ) - <*> - E.select - ( E.distinct $ E.from $ \(sheet `E.InnerJoin` corrector `E.InnerJoin` course) -> do - E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId - E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet - E.where_ $ corrector E.^. SheetCorrectorUser E.==. E.val uid - return (course E.^. CourseTerm, course E.^. CourseSchool, course E.^. CourseShorthand) - ) - <*> - E.select - ( E.from $ \(studydegree `E.InnerJoin` studyfeat `E.InnerJoin` studyterms) -> do - E.where_ $ studyfeat E.^. StudyFeaturesUser E.==. E.val uid - E.on $ studyfeat E.^. StudyFeaturesField E.==. studyterms E.^. StudyTermsId - E.on $ studyfeat E.^. StudyFeaturesDegree E.==. studydegree E.^. StudyDegreeId - return (studyfeat, studydegree, studyterms) - ) - ( (hasRows, ownedCoursesTable) - , enrolledCoursesTable - , submissionTable - , submissionGroupTable - , correctionsTable - ) <- runDB $ (,,,,) - <$> mkOwnedCoursesTable uid -- Tabelle mit eigenen Kursen - <*> mkEnrolledCoursesTable uid -- Tabelle mit allen Teilnehmer: Kurs (link), Datum - <*> mkSubmissionTable uid -- Tabelle mit allen Abgaben und Abgabe-Gruppen - <*> mkSubmissionGroupTable uid -- Tabelle mit allen Abgabegruppen - <*> mkCorrectionsTable uid -- Tabelle mit allen Korrektor-Aufgaben - - + admin_rights <- E.select $ E.from $ \(adright `E.InnerJoin` school) -> do + E.where_ $ adright E.^. UserAdminUser E.==. E.val uid + E.on $ adright E.^. UserAdminSchool E.==. school E.^. SchoolId + return (school E.^. SchoolShorthand) + lecturer_rights <- E.select $ E.from $ \(lecright `E.InnerJoin` school) -> do + E.where_ $ lecright E.^. UserLecturerUser E.==. E.val uid + E.on $ lecright E.^. UserLecturerSchool E.==. school E.^. SchoolId + return (school E.^. SchoolShorthand) + lecture_corrector <- E.select $ E.distinct $ E.from $ \(sheet `E.InnerJoin` corrector `E.InnerJoin` course) -> do + E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId + E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet + E.where_ $ corrector E.^. SheetCorrectorUser E.==. E.val uid + return (course E.^. CourseTerm, course E.^. CourseSchool, course E.^. CourseShorthand) + studies <- E.select $ E.from $ \(studydegree `E.InnerJoin` studyfeat `E.InnerJoin` studyterms) -> do + E.where_ $ studyfeat E.^. StudyFeaturesUser E.==. E.val uid + E.on $ studyfeat E.^. StudyFeaturesField E.==. studyterms E.^. StudyTermsId + E.on $ studyfeat E.^. StudyFeaturesDegree E.==. studydegree E.^. StudyDegreeId + return (studyfeat, studydegree, studyterms) + --Tables + (hasRows, ownedCoursesTable) <- mkOwnedCoursesTable uid -- Tabelle mit eigenen Kursen + enrolledCoursesTable <- mkEnrolledCoursesTable uid -- Tabelle mit allen Teilnehmer: Kurs (link), Datum + submissionTable <- mkSubmissionTable uid -- Tabelle mit allen Abgaben und Abgabe-Gruppen + submissionGroupTable <- mkSubmissionGroupTable uid -- Tabelle mit allen Abgabegruppen + correctionsTable <- mkCorrectionsTable uid -- Tabelle mit allen Korrektor-Aufgaben let examTable = [whamlet|Klausuren werden momentan leider noch nicht unterstützt.|] let ownTutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|] let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|] - lastLogin <- traverse (formatTime SelFormatDateTime) userLastAuthentication - - -- Delete Button - (btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form ButtonDelete) - defaultLayout $ do - let delWdgt = $(widgetFile "widgets/data-delete/data-delete") - $(widgetFile "profileData") - $(widgetFile "dsgvDisclaimer") + return $(widgetFile "profileData") diff --git a/src/Handler/Users.hs b/src/Handler/Users.hs index 3fa72341f..a0c3993d9 100644 --- a/src/Handler/Users.hs +++ b/src/Handler/Users.hs @@ -16,6 +16,8 @@ import qualified Data.Map as Map import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Utils as E +import Handler.Profile (makeProfileData) + hijackUserForm :: CryptoUUIDUser -> Form () hijackUserForm cID csrf = do @@ -157,7 +159,7 @@ postAdminUserR uuid = do uid <- decrypt uuid let fromSchoolList = Set.fromList . map (userAdminSchool . entityVal) let unValueRights (school, E.Value isAdmin, E.Value isLecturer) = (school,isAdmin,isLecturer) - (User{..}, fromSchoolList -> adminSchools, fmap unValueRights -> userRights) <- runDB $ (,,) + (user@User{..}, fromSchoolList -> adminSchools, fmap unValueRights -> userRights) <- runDB $ (,,) <$> get404 uid <*> selectList [UserAdminUser ==. adminId] [] <*> E.select ( E.from $ \school -> do @@ -172,7 +174,7 @@ postAdminUserR uuid = do ) -- above data is needed for both form generation and result evaluation let userRightsForm :: Form [(SchoolId, Bool, Bool)] - userRightsForm csrf = do + userRightsForm = identifyForm FIDuserRights $ \csrf -> do boxRights <- forM userRights $ \(school@(Entity sid _), isAdmin, isLecturer) -> if Set.member sid adminSchools then do @@ -205,5 +207,79 @@ postAdminUserR uuid = do formResult result userRightsAction let heading = [whamlet|_{MsgAccessRightsFor} ^{nameWidget userDisplayName userSurname}|] - siteLayout heading + -- Delete Button needed in data-delete + (btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form ButtonDelete) + userDataWidget <- runDB $ makeProfileData $ Entity uid user + siteLayout heading $ do + let deleteWidget = $(widgetFile "widgets/data-delete/data-delete") $(widgetFile "adminUser") + + +postAdminUserDeleteR :: CryptoUUIDUser -> Handler Html +postAdminUserDeleteR uuid = do + uid <- decrypt uuid + ((btnResult,_), _) <- runFormPost (buttonForm :: Form ButtonDelete) + case btnResult of + (FormSuccess BtnDelete) -> do + User{..} <- runDB $ get404 uid + -- clearCreds False -- Logout-User + ((deletedSubmissions,groupSubmissions),deletedSubmissionGroups) <- runDB $ deleteUser uid + -- addMessageIHamlet + $(addMessageFile Success "templates/deletedUser.hamlet") -- USE THIS ONE + -- addMessageI Success $ MsgDeleteUser deletedSubmissions + -- when (groupSubmissions > 0) $ addMessageI Info $ MsgDeleteUserGroupSubmissions groupSubmissions + defaultLayout + $(widgetFile "deletedUser") + + -- (FormSuccess BtnAbort ) -> do + -- addMessageI Info MsgAborted + -- redirect ProfileDataR + _other -> getAdminUserR uuid + + + +deleteUser :: UserId -> DB ((Int,Int),Int64) -- TODO: Restrict deletions for lecturers, tutors and students in course that won't allow deregistration +deleteUser duid = do + -- E.deleteCount for submissions is not cascading, hence we first select and then delete manually + -- We delete all files tied to submissions where the user is the lone submissionUser + + -- Do not deleteCascade submissions where duid is the corrector: + updateWhere [SubmissionRatingBy ==. Just duid] [SubmissionRatingBy =. Nothing] + + groupSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.>. E.val (0::Int64)) + singleSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.==. E.val (0::Int64)) + deleteCascade duid + forM_ singleSubmissions $ \(E.Value submissionId) -> do + deleteFileIds <- map E.unValue <$> getSubmissionFiles submissionId + deleteCascade submissionId + deleteCascadeWhere [FileId <-. deleteFileIds] -- TODO: throws exception for de-duplicated files + + deletedSubmissionGroups <- deleteSingleSubmissionGroups + return ((length singleSubmissions, length groupSubmissions),deletedSubmissionGroups) + where + 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 + E.on $ submission E.^. SubmissionId E.==. suser E.^. SubmissionUserSubmission + let numBuddies = E.sub_select $ E.from $ \subUsers -> do + E.where_ $ subUsers E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId + E.&&. subUsers E.^. SubmissionUserUser E.!=. E.val duid + return E.countRows + E.where_ $ suser E.^. SubmissionUserUser E.==. E.val duid + E.&&. whereBuddies numBuddies + return $ submission E.^. SubmissionId + + getSubmissionFiles :: SubmissionId -> DB [E.Value (Key File)] + getSubmissionFiles subId = E.select $ E.from $ \file -> do + E.where_ $ E.exists $ E.from $ \submissionFile -> + E.where_ $ submissionFile E.^. SubmissionFileSubmission E.==. E.val subId + E.&&. submissionFile E.^. SubmissionFileFile E.==. file E.^. FileId + return $ file E.^. FileId + + deleteSingleSubmissionGroups = E.deleteCount $ E.from $ \submissionGroup -> do + E.where_ $ E.exists $ E.from $ \subGroupUser -> + E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId + E.&&. subGroupUser E.^. SubmissionGroupUserUser E.==. E.val duid + E.where_ $ E.notExists $ E.from $ \subGroupUser -> + E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId + E.&&. subGroupUser E.^. SubmissionGroupUserUser E.!=. E.val duid + diff --git a/src/Handler/Utils.hs b/src/Handler/Utils.hs index 35faf6676..42ed1b985 100644 --- a/src/Handler/Utils.hs +++ b/src/Handler/Utils.hs @@ -44,11 +44,16 @@ simpleLink :: Widget -> Route UniWorX -> Widget simpleLink lbl url = [whamlet|^{lbl}|] -- | toWidget-Version of @nameHtml@, for convenience -nameWidget :: Text -> Text -> Widget +nameWidget :: Text -- ^ userDisplayName + -> Text -- ^ userSurname + -> Widget nameWidget displayName surname = toWidget $ nameHtml displayName surname -- | toWidget-Version of @nameEmailHtml@, for convenience -nameEmailWidget :: CI Text -> Text -> Text -> Widget +nameEmailWidget :: CI Text -- ^ userEmail + -> Text -- ^ userDisplayName + -> Text -- ^ userSurname + -> Widget nameEmailWidget email displayName surname = toWidget $ nameEmailHtml email displayName surname -- | Show user's displayName, highlighting the surname if possible. diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index b82d788fa..48935471c 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -125,14 +125,16 @@ linkButton lbl cls url = do -- buttonForm :: (Button UniWorX a, Finite a) => Markup -> MForm (HandlerT UniWorX IO) (FormResult a, Widget) buttonForm :: (Button UniWorX a, Finite a) => Form a -buttonForm csrf = do - (res, ($ []) -> fViews) <- aFormToForm . disambiguateButtons $ combinedButtonFieldF "" - return (res, [whamlet| - $newline never - #{csrf} - $forall bView <- fViews - ^{fvInput bView} - |]) +buttonForm = identifyForm FIDbuttonForm buttonFormAux -- TODO: distinguish diffent buttons despite @disambiguateButtons@ + where + buttonFormAux csrf = do + (res, ($ []) -> fViews) <- aFormToForm . disambiguateButtons $ combinedButtonFieldF "" + return (res, [whamlet| + $newline never + #{csrf} + $forall bView <- fViews + ^{fvInput bView} + |]) ------------ diff --git a/src/Handler/Utils/Templates.hs b/src/Handler/Utils/Templates.hs index f29d79fba..44be14620 100644 --- a/src/Handler/Utils/Templates.hs +++ b/src/Handler/Utils/Templates.hs @@ -4,7 +4,11 @@ import Data.Either (isLeft) import Import.NoFoundation -modal :: WidgetT site IO () -> Either (SomeRoute site) (WidgetT site IO ()) -> WidgetT site IO () + +-- | Create a link to a modal +modal :: WidgetT site IO () -- ^ Widget that represents the link + -> Either (SomeRoute site) (WidgetT site IO ()) -- ^ Modal contant: either dynamic link or static widget + -> WidgetT site IO () -- ^ result widget modal modalTrigger modalContent = do let modalDynamic = isLeft modalContent modalId <- newIdent diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs index c82f02226..9413e5e36 100644 --- a/src/Utils/Form.hs +++ b/src/Utils/Form.hs @@ -217,6 +217,8 @@ data FormIdentifier | FIDDBTable | FIDDelete | FIDCourseRegister + | FIDuserRights + | FIDbuttonForm deriving (Eq, Ord, Read, Show) instance PathPiece FormIdentifier where @@ -238,7 +240,7 @@ identifyForm' resLens identVal form fragment = do |] -- Check if we got its value back. - hasIdent <- (== Just identVal) <$> lookupGlobalPostParamForm PostFormIdentifier + hasIdent <- (== Just identVal) <$> lookupGlobalPostParamForm PostFormIdentifier -- Run the form proper (with our hidden ). If the -- data is missing, then do not provide any params to the @@ -250,7 +252,7 @@ identifyForm' resLens identVal form fragment = do identifyForm :: (Monad m, PathPiece ident, Eq ident) => ident -> (Html -> MForm m (FormResult a, widget)) -> (Html -> MForm m (FormResult a, widget)) identifyForm = identifyForm' id - + {- Hinweise zur Erinnerung: - identForm primär, wenn es mehr als ein Formular pro Handler gibt diff --git a/templates/adminUser.hamlet b/templates/adminUser.hamlet index 60d0d6b47..8f5e9c0b3 100644 --- a/templates/adminUser.hamlet +++ b/templates/adminUser.hamlet @@ -1,6 +1,14 @@ -

- $# Does not use link-email.hamlet, but should - ^{mailtoHtml userEmail} -

- ^{formWidget} - ^{submitButtonView} +
+ $# Does not use link-email.hamlet, but should + ^{mailtoHtml userEmail} + + ^{formWidget} + ^{submitButtonView} +
+ ^{userDataWidget} +

+ ^{modal "Benutzer löschen" (Right deleteWidget)} + Achtung, dieser Link löscht momentan noch den kompletten Benutzer + unwiderruflich aus der Live-Datenbank mit + DELETE CASCADE uid + \ Klausurdaten müssen jedoch langfristig gespeichert werden! \ No newline at end of file diff --git a/templates/deletedUser.hamlet b/templates/deletedUser.hamlet index 69b723987..373057fb9 100644 --- a/templates/deletedUser.hamlet +++ b/templates/deletedUser.hamlet @@ -1,6 +1,10 @@

_{MsgUserAccountDeleted userDisplayName} +
+ #{nameEmailHtml userEmail userDisplayName userSurname} +
+ #{mailtoHtml userEmail}
#{display deletedSubmissions} Abgaben wurden unwiederruflich gelöscht. $if groupSubmissions > 0 @@ -12,5 +16,3 @@ $if deletedSubmissionGroups > 0
#{display deletedSubmissionGroups} benannte Abgabengruppen wurden gelöscht, da diese dadurch leer wurden. -
- Good Bye! diff --git a/templates/profileData.hamlet b/templates/profileData.hamlet index 341ff3662..08c5ae02f 100644 --- a/templates/profileData.hamlet +++ b/templates/profileData.hamlet @@ -115,20 +115,28 @@ Auflistung aller tatsächlich zugewiesenen Korrekturen . -

- ^{modal "Alle Benutzerbezogenen Daten löschen" (Right delWdgt)} -

-

Hinweise: + +
+

_{MsgRemarks}