Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX

This commit is contained in:
Steffen Jost 2019-08-22 16:41:37 +02:00
commit b7eab7f103
91 changed files with 960 additions and 217 deletions

View File

@ -227,6 +227,8 @@ CourseAllocationCapacityMayNotBeChanged: Kapazität eines Kurses, der an einer Z
CourseLecturerRightsIdentical: Alle Sorten von Kursverwalter haben identische Rechte.
School: Institut
NoSuchTerm tid@TermId: Semester #{tid} gibt es nicht.
NoSuchSchool ssh@SchoolId: Institut #{ssh} gibt es nicht.
NoSuchCourseShorthand csh@CourseShorthand: Kein Kurs mit Kürzel #{csh} bekannt.
@ -963,10 +965,12 @@ MenuHelp: Hilfe
MenuProfile: Anpassen
MenuLogin: Login
MenuLogout: Logout
MenuAllocationList: Zentralanmeldungen
MenuCourseList: Kurse
MenuCourseMembers: Kursteilnehmer
MenuCourseAddMembers: Kursteilnehmer hinzufügen
MenuCourseCommunication: Kursmitteilung
MenuCourseApplications: Bewerbungen
MenuTermShow: Semester
MenuSubmissionDelete: Abgabe löschen
MenuUsers: Benutzer
@ -1469,6 +1473,7 @@ MailSchoolLecturerInviteHeading school@SchoolName: Einladung zum Dozent für „
SchoolLecturerInviteExplanation: Sie wurden eingeladen, Dozent für ein Institut zu sein. Sie können, nachdem Sie die Einladung annehmen, eigenständig neue Kurse anlegen.
SchoolLecturerInvitationAccepted school@SchoolName: Einladung zum Dozent für „#{school}“ angenommen
AllocationName: Name
AllocationTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'}: #{allocation}
AllocationShortTitle termText@Text ssh'@SchoolShorthand ash@AllocationShorthand: #{termText} - #{ssh'} - #{ash}
AllocationDescription: Beschreibung
@ -1513,4 +1518,14 @@ ApplicationRatingCommentInvisibleTip: Dient zunächst nur als Notiz für Kursver
AllocationSchoolShort: Institut
Allocation: Zentralanmeldung
AllocationRegisterTo: Anmeldungen bis
AllocationRegisterTo: Anmeldungen bis
AllocationListTitle: Zentralanmeldungen
CourseApplicationsListTitle: Bewerbungen
CourseApplicationId: Bewerbungsnummer
CourseApplicationRatingPoints: Bewertung
CourseApplicationVeto: Veto
UserDisplayName: Voller Name
UserMatriculation: Matrikelnummer

View File

@ -12,10 +12,10 @@ User json -- Each Uni2work user has a corresponding row in this table; create
authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash)
lastAuthentication UTCTime Maybe -- last login date
tokensIssuedAfter UTCTime Maybe -- do not accept bearer tokens issued before this time (accept all tokens if null)
matrikelnummer Text Maybe -- optional immatriculation-string; usually a number, but not always (e.g. lecturers, pupils, guests,...)
matrikelnummer UserMatriculation Maybe -- optional immatriculation-string; usually a number, but not always (e.g. lecturers, pupils, guests,...)
email (CI Text) -- Case-insensitive eMail address
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'
displayName UserDisplayName -- we only show LDAP-DisplayName, and highlight LDAP-Surname within (appended if not contained)
surname UserSurname -- 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

1
routes
View File

@ -81,6 +81,7 @@
/school SchoolListR GET !development
/school/#SchoolId SchoolShowR GET !development
/allocation/ AllocationListR GET !free
/allocation/#TermId/#SchoolId/#AllocationShorthand AllocationR:
/ AShowR GET !free
/register ARegisterR POST !time

View File

@ -64,10 +64,6 @@ import qualified Yesod.Core.Types as Yesod (Logger(..))
import qualified Data.HashMap.Strict as HashMap
import Utils.Lens
import Data.Proxy
import qualified Data.Aeson as Aeson
import System.Exit

View File

@ -10,7 +10,6 @@ module Auth.LDAP
) where
import Import.NoFoundation hiding (userEmail, userDisplayName)
import Control.Lens
import Network.Connection
import Data.CaseInsensitive (CI)

View File

@ -0,0 +1,19 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Colonnade.Instances
(
) where
import ClassyPrelude
import Control.Lens.Indexed (FunctorWithIndex(imap))
import Colonnade.Encode (Colonnade(..), OneColonnade(..))
instance Functor h => FunctorWithIndex (Maybe a) (Colonnade h a) where
imap f (Colonnade ones) = Colonnade $ dimapColonnade' <$> ones
where
dimapColonnade' OneColonnade{..} = OneColonnade
{ oneColonnadeEncode = \x -> f (Just x) $ oneColonnadeEncode x
, oneColonnadeHead = f Nothing <$> oneColonnadeHead
}

View File

@ -18,6 +18,7 @@ module Database.Esqueleto.Utils
, SqlHashable
, sha256
, maybe
, SqlProject(..)
) where
@ -232,3 +233,16 @@ maybe onNothing onJust val = E.case_
(onJust $ E.veryUnsafeCoerceSqlExprValue val)
]
(E.else_ onNothing)
class (PersistEntity entity, PersistField value) => SqlProject entity value entity' value' | entity value entity' -> value', entity value value' -> entity' where
sqlProject :: E.SqlExpr entity' -> EntityField entity value -> E.SqlExpr (E.Value value')
unSqlProject :: forall p1 p2. p1 entity -> p2 entity' -> value -> value'
instance (PersistEntity val, PersistField typ) => SqlProject val typ (E.Entity val) typ where
sqlProject = (E.^.)
unSqlProject _ _ = id
instance (PersistEntity val, PersistField typ) => SqlProject val typ (Maybe (E.Entity val)) (Maybe typ) where
sqlProject = (E.?.)
unSqlProject _ _ = Just

View File

@ -65,7 +65,6 @@ import Control.Monad.Memo.Class (MonadMemo(..), for4)
import qualified Control.Monad.Catch as C
import Handler.Utils.StudyFeatures
import Utils.Lens
import Utils.Form
import Utils.Sheet
import Utils.SystemMessage
@ -1047,6 +1046,7 @@ tagAccessPredicate AuthAllocationRegistered = APDB $ \mAuthId route _ -> case ro
r -> $unsupportedAuthPredicate AuthAllocationRegistered r
tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
CourseR tid ssh csh (CUserR cID) -> exceptT return return $ do
cTime <- liftIO getCurrentTime
let authorizedIfExists f = do
[E.Value ok] <- lift . E.select . return . E.exists $ E.from f
whenExceptT ok Authorized
@ -1107,12 +1107,16 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is applicant for this course
$cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` courseApplication) -> do
$cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \((course `E.InnerJoin` courseApplication) `E.LeftOuterJoin` allocation) -> do
E.on $ allocation E.?. AllocationId E.==. courseApplication E.^. CourseApplicationAllocation
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.where_ $ E.maybe E.true (E.maybe E.false $ \f -> f E.<=. E.val cTime) (allocation E.?. AllocationStaffAllocationFrom)
E.&&. E.maybe E.true (E.maybe E.true $ \t -> t E.>=. E.val cTime) (allocation E.?. AllocationStaffAllocationTo)
unauthorizedI MsgUnauthorizedParticipant
r -> $unsupportedAuthPredicate AuthParticipant r
tagAccessPredicate AuthCapacity = APDB $ \_ route _ -> case route of
@ -1748,10 +1752,11 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb (TermSchoolCourseListR tid ssh) = return (original $ unSchoolKey ssh, Just $ TermCourseListR tid)
breadcrumb AllocationListR = return ("Zentralanmeldungen", Just HomeR)
breadcrumb (AllocationR tid ssh ash AShowR) = do
mr <- getMessageRender
Entity _ Allocation{allocationName} <- runDB . getBy404 $ TermSchoolAllocationShort tid ssh ash
return ([st|#{allocationName} (#{mr (ShortTermIdentifier (unTermKey tid))}, #{original (unSchoolKey ssh)})|], Just $ HomeR)
return ([st|#{allocationName} (#{mr (ShortTermIdentifier (unTermKey tid))}, #{original (unSchoolKey ssh)})|], Just $ AllocationListR)
breadcrumb (AllocationR tid ssh ash (AApplicationR _)) = return ("Bewerbung", Just $ AllocationR tid ssh ash AShowR)
breadcrumb CourseListR = return ("Kurse" , Nothing)
@ -1776,6 +1781,8 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb (CourseR tid ssh csh CExamListR) = return ("Prüfungen", Just $ CourseR tid ssh csh CShowR)
breadcrumb (CourseR tid ssh csh CExamNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CExamListR)
breadcrumb (CourseR tid ssh csh CApplicationsR) = return ("Bewerbungen", Just $ CourseR tid ssh csh CShowR)
breadcrumb (CExamR tid ssh csh examn EShowR) = return (original examn, Just $ CourseR tid ssh csh CExamListR)
breadcrumb (CExamR tid ssh csh examn EEditR) = return ("Bearbeiten", Just $ CExamR tid ssh csh examn EShowR)
breadcrumb (CExamR tid ssh csh examn EUsersR) = return ("Teilnehmer", Just $ CExamR tid ssh csh examn EShowR)
@ -1970,35 +1977,11 @@ pageActions (HomeR) =
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgAdminHeading
, menuItemIcon = Just "screwdriver"
, menuItemRoute = SomeRoute AdminR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgAdminFeaturesHeading
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute AdminFeaturesR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuMessageList
, menuItemLabel = MsgMenuAllocationList
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute MessageListR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuAdminErrMsg
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute AdminErrMsgR
, menuItemRoute = SomeRoute AllocationListR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
@ -2022,20 +2005,12 @@ pageActions (AdminR) =
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgErrMsgHeading
, menuItemLabel = MsgMenuAdminErrMsg
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute AdminErrMsgR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuUsers
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute UsersR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuAdminTest
@ -2201,6 +2176,14 @@ pageActions (CourseListR) =
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuAllocationList
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute AllocationListR
, menuItemModal = False
, menuItemAccessCallback' = return True
}
]
pageActions (CourseNewR) = [
MenuItem
@ -2279,6 +2262,28 @@ pageActions (CourseR tid ssh csh CShowR) =
anyM examNames $ examAccess . E.unValue
in runDB $ lecturerAccess `or2M` existsVisible
}
, MenuItem
{ menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuCourseApplications
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ CourseR tid ssh csh CApplicationsR
, menuItemModal = False
, menuItemAccessCallback' =
let courseWhere course = course <$ do
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
existsApplications = E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
void $ courseWhere course
courseApplications = fmap (any E.unValue) . E.select . E.from $ \course -> do
void $ courseWhere course
return $ course E.^. CourseApplicationsRequired
courseAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse) -> do
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
void $ courseWhere course
in runDB $ courseAllocation `or2M` courseApplications `or2M` existsApplications
}
, MenuItem
{ menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuCourseMembers

View File

@ -8,8 +8,6 @@ import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder)
import Control.Monad.Trans.Except
import Control.Monad.Trans.Writer (mapWriterT)
import Utils.Lens
-- import Data.Time
import Data.Char (isDigit)
import qualified Data.Text as Text

View File

@ -6,3 +6,4 @@ import Handler.Allocation.Info as Handler.Allocation
import Handler.Allocation.Show as Handler.Allocation
import Handler.Allocation.Application as Handler.Allocation
import Handler.Allocation.Register as Handler.Allocation
import Handler.Allocation.List as Handler.Allocation

View File

@ -12,8 +12,6 @@ module Handler.Allocation.Application
import Import hiding (hash)
import Handler.Utils
import Utils.Lens
import qualified Data.Text as Text
import qualified Data.Set as Set

View File

@ -0,0 +1,86 @@
module Handler.Allocation.List
( getAllocationListR
) where
import Import
import qualified Database.Esqueleto as E
import Handler.Utils.Table.Columns
import Handler.Utils.Table.Pagination
type AllocationTableExpr = E.SqlExpr (Entity Allocation)
type AllocationTableData = DBRow (Entity Allocation)
allocationListIdent :: Text
allocationListIdent = "allocations"
queryAllocation :: Getter AllocationTableExpr (E.SqlExpr (Entity Allocation))
queryAllocation = id
resultAllocation :: Lens' AllocationTableData (Entity Allocation)
resultAllocation = _dbrOutput
allocationTermLink :: TermId -> SomeRoute UniWorX
allocationTermLink tid = SomeRoute (AllocationListR, [(dbFilterKey allocationListIdent "term", toPathPiece tid)])
allocationSchoolLink :: SchoolId -> SomeRoute UniWorX
allocationSchoolLink ssh = SomeRoute (AllocationListR, [(dbFilterKey allocationListIdent "school", toPathPiece ssh)])
allocationLink :: Allocation -> SomeRoute UniWorX
allocationLink Allocation{..} = SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR
getAllocationListR :: Handler Html
getAllocationListR = do
let
dbtSQLQuery :: AllocationTableExpr -> E.SqlQuery _
dbtSQLQuery = return
dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) AllocationTableData
dbtProj = return
dbtRowKey = view $ queryAllocation . to (E.^. AllocationId)
dbtColonnade :: Colonnade Sortable _ _
dbtColonnade = mconcat
[ anchorColonnade (views (resultAllocation . _entityVal . _allocationTerm) allocationTermLink) $ colTermShort (resultAllocation . _entityVal . _allocationTerm)
, anchorColonnade (views (resultAllocation . _entityVal . _allocationSchool) allocationSchoolLink) $ colSchoolShort (resultAllocation . _entityVal . _allocationSchool)
, anchorColonnade (views (resultAllocation . _entityVal) allocationLink) $ colAllocationName (resultAllocation . _entityVal . _allocationName)
]
dbtSorting = mconcat
[ sortTerm $ queryAllocation . to (E.^. AllocationTerm)
, sortSchool $ queryAllocation . to (E.^. AllocationSchool)
, sortAllocationName $ queryAllocation . to (E.^. AllocationName)
]
dbtFilter = mconcat
[ fltrTerm $ queryAllocation . to (E.^. AllocationTerm)
, fltrSchool $ queryAllocation . to (E.^. AllocationSchool)
, fltrAllocation queryAllocation
]
dbtFilterUI = mconcat
[ fltrTermUI
, fltrSchoolUI
, fltrAllocationUI
]
dbtStyle = def
{ dbsFilterLayout = defaultDBSFilterLayout
}
dbtParams = def
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
dbtIdent = allocationListIdent
psValidator :: PSValidator _ _
psValidator = def
& defaultSorting [SortAscBy "term", SortAscBy "school", SortAscBy "allocation"]
table <- runDB $ dbTableWidget' psValidator DBTable{..}
siteLayoutMsg MsgAllocationListTitle $ do
setTitleI MsgAllocationListTitle
table

View File

@ -8,8 +8,6 @@ module Handler.Allocation.Register
import Import
import Utils.Lens
import Handler.Utils.Form
{-# ANN module ("HLint: ignore Use newtype instead of data"::String) #-}

View File

@ -4,8 +4,7 @@ module Handler.Allocation.Show
import Import
import Handler.Utils
import Utils.Lens
import Handler.Allocation.Register
import Handler.Allocation.Application

View File

@ -12,8 +12,6 @@ import Handler.Utils.SheetType
import Handler.Utils.Delete
-- import Handler.Utils.Zip
import Utils.Lens
import Data.List as List (nub, foldl, foldr)
import Data.Set (Set)
import qualified Data.Set as Set

View File

@ -6,8 +6,10 @@ module Handler.Course.Application
import Import
import Handler.Utils
import Handler.Utils.Table.Columns
import qualified Database.Esqueleto as E
import Database.Esqueleto.Utils.TH
import System.FilePath (addExtension)
@ -37,6 +39,192 @@ getCAFilesR tid ssh csh cID = do
serveSomeFiles archiveName $ fsSource .| C.map entityVal
type CourseApplicationsTableExpr = ( E.SqlExpr (Entity CourseApplication)
`E.InnerJoin` E.SqlExpr (Entity User)
)
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity Allocation))
`E.LeftOuterJoin` ( E.SqlExpr (Maybe (Entity StudyFeatures))
`E.InnerJoin` E.SqlExpr (Maybe (Entity StudyTerms))
`E.InnerJoin` E.SqlExpr (Maybe (Entity StudyDegree))
)
type CourseApplicationsTableData = DBRow ( Entity CourseApplication
, Entity User
, E.Value Bool -- hasFiles
, Maybe (Entity Allocation)
, Maybe (Entity StudyFeatures)
, Maybe (Entity StudyTerms)
, Maybe (Entity StudyDegree)
)
courseApplicationsIdent :: Text
courseApplicationsIdent = "applications"
queryCourseApplication :: Getter CourseApplicationsTableExpr (E.SqlExpr (Entity CourseApplication))
queryCourseApplication = to $ $(sqlIJproj 2 1) . $(sqlLOJproj 3 1)
queryUser :: Getter CourseApplicationsTableExpr (E.SqlExpr (Entity User))
queryUser = to $ $(sqlIJproj 2 2) . $(sqlLOJproj 3 1)
queryHasFiles :: Getter CourseApplicationsTableExpr (E.SqlExpr (E.Value Bool))
queryHasFiles = to $ hasFiles . $(sqlIJproj 2 1) . $(sqlLOJproj 3 1)
where
hasFiles appl = E.exists . E.from $ \courseApplicationFile ->
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. appl E.^. CourseApplicationId
queryAllocation :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity Allocation)))
queryAllocation = to $(sqlLOJproj 3 2)
queryStudyFeatures :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity StudyFeatures)))
queryStudyFeatures = to $ $(sqlIJproj 3 1) . $(sqlLOJproj 3 3)
queryStudyTerms :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity StudyTerms)))
queryStudyTerms = to $ $(sqlIJproj 3 2) . $(sqlLOJproj 3 3)
queryStudyDegree :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity StudyDegree)))
queryStudyDegree = to $ $(sqlIJproj 3 3) . $(sqlLOJproj 3 3)
resultCourseApplication :: Lens' CourseApplicationsTableData (Entity CourseApplication)
resultCourseApplication = _dbrOutput . _1
resultUser :: Lens' CourseApplicationsTableData (Entity User)
resultUser = _dbrOutput . _2
resultHasFiles :: Lens' CourseApplicationsTableData Bool
resultHasFiles = _dbrOutput . _3 . _Value
resultAllocation :: Traversal' CourseApplicationsTableData (Entity Allocation)
resultAllocation = _dbrOutput . _4 . _Just
resultStudyFeatures :: Traversal' CourseApplicationsTableData (Entity StudyFeatures)
resultStudyFeatures = _dbrOutput . _5 . _Just
resultStudyTerms :: Traversal' CourseApplicationsTableData (Entity StudyTerms)
resultStudyTerms = _dbrOutput . _6 . _Just
resultStudyDegree :: Traversal' CourseApplicationsTableData (Entity StudyDegree)
resultStudyDegree = _dbrOutput . _7 . _Just
getCApplicationsR, postCApplicationsR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCApplicationsR = postCApplicationsR
postCApplicationsR = fail "not implemented" -- dbtable of _all_ course applications
postCApplicationsR tid ssh csh = do
table <- runDB $ do
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
let
allocationLink :: Allocation -> SomeRoute UniWorX
allocationLink Allocation{..} = SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR
participantLink :: MonadCrypto m => UserId -> m (SomeRoute UniWorX)
participantLink uid = do
cID <- encrypt uid
return . SomeRoute . CourseR tid ssh csh $ CUserR cID
dbtSQLQuery :: CourseApplicationsTableExpr -> E.SqlQuery _
dbtSQLQuery = runReaderT $ do
courseApplication <- view queryCourseApplication
hasFiles <- view queryHasFiles
user <- view queryUser
allocation <- view queryAllocation
studyFeatures <- view queryStudyFeatures
studyTerms <- view queryStudyTerms
studyDegree <- view queryStudyDegree
lift $ do
E.on $ studyDegree E.?. StudyDegreeId E.==. studyFeatures E.?. StudyFeaturesDegree
E.on $ studyTerms E.?. StudyTermsId E.==. studyFeatures E.?. StudyFeaturesField
E.on $ studyFeatures E.?. StudyFeaturesId E.==. courseApplication E.^. CourseApplicationField
E.on $ courseApplication E.^. CourseApplicationAllocation E.==. allocation E.?. AllocationId
E.on $ user E.^. UserId E.==. courseApplication E.^. CourseApplicationUser
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid
return (courseApplication, user, hasFiles, allocation, studyFeatures, studyTerms, studyDegree)
dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) CourseApplicationsTableData
dbtProj = runReaderT $ do
appId <- view $ resultCourseApplication . _entityKey
cID <- encrypt appId
guardM . hasReadAccessTo $ CApplicationR tid ssh csh cID CAFilesR -- TODO: replace with CAShowR
view id
dbtRowKey = view $ queryCourseApplication . to (E.^. CourseApplicationId)
dbtColonnade :: Colonnade Sortable _ _
dbtColonnade = mconcat
[ emptyOpticColonnade (resultAllocation . _entityVal) $ \l -> anchorColonnade (views l allocationLink) $ colAllocationShorthand (l . _allocationShorthand)
, colApplicationId (resultCourseApplication . _entityKey)
, anchorColonnadeM (views (resultUser . _entityKey) participantLink) $ colUserDisplayName (resultUser . _entityVal . $(multifocusL 2) _userDisplayName _userSurname)
, colUserMatriculation (resultUser . _entityVal . _userMatrikelnummer)
, emptyOpticColonnade (resultStudyTerms . _entityVal) colStudyTerms
, emptyOpticColonnade (resultStudyDegree . _entityVal) colStudyDegree
, emptyOpticColonnade (resultStudyFeatures . _entityVal . _studyFeaturesSemester) colStudyFeaturesSemester
, colApplicationText (resultCourseApplication . _entityVal . _courseApplicationText)
, lmap ((tid, ssh, csh), ) $ colApplicationFiles ($(multifocusL 5) (_1 . _1) (_1 . _2) (_1 . _3) (_2 . resultCourseApplication . _entityKey) (_2 . resultHasFiles))
, colApplicationVeto (resultCourseApplication . _entityVal . _courseApplicationRatingVeto)
, colApplicationRatingPoints (resultCourseApplication . _entityVal . _courseApplicationRatingPoints)
, colApplicationRatingComment (resultCourseApplication . _entityVal . _courseApplicationRatingComment)
]
dbtSorting = mconcat
[ sortAllocationShorthand $ queryAllocation . to (E.?. AllocationShorthand)
, sortUserName' $ $(multifocusG 2) (queryUser . to (E.^. UserDisplayName)) (queryUser . to (E.^. UserSurname))
, sortUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
, sortStudyTerms queryStudyTerms
, sortStudyDegree queryStudyDegree
, sortStudyFeaturesSemester $ queryStudyFeatures . to (E.?. StudyFeaturesSemester)
, sortApplicationText $ queryCourseApplication . to (E.^. CourseApplicationText)
, sortApplicationFiles queryHasFiles
, sortApplicationVeto $ queryCourseApplication . to (E.^. CourseApplicationRatingVeto)
, sortApplicationRatingPoints $ queryCourseApplication . to (E.^. CourseApplicationRatingPoints)
, sortApplicationRatingComment $ queryCourseApplication . to (E.^. CourseApplicationRatingComment)
]
dbtFilter = mconcat
[ fltrAllocation queryAllocation
, fltrUserName' $ queryUser . to (E.^. UserDisplayName)
, fltrUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
, fltrStudyTerms queryStudyTerms
, fltrStudyDegree queryStudyDegree
, fltrStudyFeaturesSemester $ queryStudyFeatures . to (E.?. StudyFeaturesSemester)
, fltrApplicationText $ queryCourseApplication . to (E.^. CourseApplicationText)
, fltrApplicationFiles queryHasFiles
, fltrApplicationVeto $ queryCourseApplication . to (E.^. CourseApplicationRatingVeto)
, fltrApplicationRatingPoints $ queryCourseApplication . to (E.^. CourseApplicationRatingPoints)
, fltrApplicationRatingComment $ queryCourseApplication . to (E.^. CourseApplicationRatingComment)
]
dbtFilterUI = mconcat
[ fltrAllocationUI
, fltrUserNameUI'
, fltrUserMatriculationUI
, fltrStudyTermsUI
, fltrStudyDegreeUI
, fltrStudyFeaturesSemesterUI
, fltrApplicationTextUI
, fltrApplicationFilesUI
, fltrApplicationVetoUI
, fltrApplicationRatingPointsUI
, fltrApplicationRatingCommentUI
]
dbtStyle = def
{ dbsFilterLayout = defaultDBSFilterLayout
}
dbtParams = def
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
dbtIdent = courseApplicationsIdent
psValidator :: PSValidator _ _
psValidator = def
dbTableWidget' psValidator DBTable{..}
let title = prependCourseTitle tid ssh csh MsgCourseApplicationsListTitle
siteLayoutMsg title $ do
setTitleI title
table

View File

@ -5,7 +5,6 @@ module Handler.Course.Edit
import Import
import Utils.Lens
import Utils.Form
import Handler.Utils
import Handler.Utils.Invitations

View File

@ -8,7 +8,6 @@ module Handler.Course.LecturerInvite
import Import
import Utils.Lens
import Utils.Form
import Handler.Utils.Invitations

View File

@ -10,7 +10,6 @@ import Import
import Data.Maybe (fromJust)
import Utils.Lens
import Utils.Form
-- import Utils.DB
import Handler.Utils

View File

@ -8,7 +8,6 @@ module Handler.Course.ParticipantInvite
import Import
import Utils.Lens
import Utils.Form
import Handler.Utils
import Handler.Utils.Invitations

View File

@ -7,7 +7,6 @@ module Handler.Course.Register
import Import
import Utils.Lens
import Handler.Utils
import Data.Function ((&))

View File

@ -12,7 +12,6 @@ import qualified Database.Esqueleto.Utils as E
import Database.Esqueleto.Utils.TH
import qualified Data.CaseInsensitive as CI
import Utils.Lens
import qualified Data.Map as Map

View File

@ -4,7 +4,6 @@ module Handler.Course.User
import Import
import Utils.Lens
import Utils.Form
import Handler.Utils
import Database.Esqueleto.Utils.TH

View File

@ -9,7 +9,6 @@ module Handler.Course.Users
import Import
import Utils.Lens
import Utils.Form
import Handler.Utils
import Handler.Utils.Database

View File

@ -5,8 +5,6 @@ module Handler.CryptoIDDispatch
import Import
import Data.Proxy
import qualified Data.Text as Text
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))

View File

@ -8,8 +8,6 @@ import Handler.Exam.RegistrationInvite
import Handler.Utils
import Handler.Utils.Exam
import Handler.Utils.Invitations
import Utils.Lens
import qualified Data.Set as Set

View File

@ -12,8 +12,6 @@ import Import
import Handler.Utils.Invitations
import Handler.Utils.Exam
import Utils.Lens
import Text.Hamlet (ihamlet)
import Data.Aeson hiding (Result(..))

View File

@ -6,8 +6,6 @@ import Import
import Handler.Exam.Form
import Handler.Exam.CorrectorInvite
import Utils.Lens
import qualified Data.Set as Set
import Handler.Utils

View File

@ -8,8 +8,6 @@ module Handler.Exam.Form
) where
import Import
import Utils.Lens hiding (parts)
import Handler.Exam.CorrectorInvite
import Handler.Utils
@ -230,12 +228,12 @@ examPartsForm prev = wFormToAForm $ do
examFormTemplate :: Entity Exam -> DB ExamForm
examFormTemplate (Entity eId Exam{..}) = do
parts <- selectList [ ExamPartExam ==. eId ] []
examParts <- selectList [ ExamPartExam ==. eId ] []
occurrences <- selectList [ ExamOccurrenceExam ==. eId ] []
correctors <- selectList [ ExamCorrectorExam ==. eId ] []
invitations <- map (\(email, InvDBDataExamCorrector) -> email) <$> sourceInvitationsList eId
parts' <- forM parts $ \(Entity pid part) -> (,) <$> encrypt pid <*> pure part
examParts' <- forM examParts $ \(Entity pid part) -> (,) <$> encrypt pid <*> pure part
occurrences' <- forM occurrences $ \(Entity oid occ) -> (,) <$> encrypt oid <*> pure occ
return ExamForm
@ -267,7 +265,7 @@ examFormTemplate (Entity eId Exam{..}) = do
, eofDescription = examOccurrenceDescription
}
, efExamParts = Set.fromList $ do
(Just -> epfId, ExamPart{..}) <- parts'
(Just -> epfId, ExamPart{..}) <- examParts'
return ExamPartForm
{ epfId
, epfName = examPartName

View File

@ -16,8 +16,6 @@ import Handler.Utils.Invitations
import qualified Data.Set as Set
import Text.Hamlet (ihamlet)
import Utils.Lens
import Data.Aeson hiding (Result(..))

View File

@ -5,8 +5,6 @@ module Handler.Exam.Show
import Import
import Handler.Exam.Register
import Utils.Lens hiding (parts)
import Data.Map ((!?))
import qualified Data.Map as Map
@ -24,7 +22,7 @@ getEShowR tid ssh csh examn = do
cTime <- liftIO getCurrentTime
mUid <- maybeAuthId
(Entity _ Exam{..}, parts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, occurrences, (registered, mayRegister), occurrenceNamesShown) <- runDB $ do
(Entity _ Exam{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, occurrences, (registered, mayRegister), occurrenceNamesShown) <- runDB $ do
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
let examVisible = NTop (Just cTime) >= NTop examVisibleFrom
@ -35,12 +33,12 @@ getEShowR tid ssh csh examn = do
let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments
occurrenceAssignmentsShown <- or2M (return occurrenceAssignmentsVisible) . hasReadAccessTo $ CExamR tid ssh csh examn EEditR
parts <- selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ]
examParts <- selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ]
resultsRaw <- for mUid $ \uid ->
E.select . E.from $ \examPartResult -> do
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map entityKey parts)
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map entityKey examParts)
return examPartResult
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
@ -66,7 +64,7 @@ getEShowR tid ssh csh examn = do
occurrenceNamesShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR
return (exam, parts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, occurrences, (registered, mayRegister), occurrenceNamesShown)
return (exam, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, occurrences, (registered, mayRegister), occurrenceNamesShown)
let examTimes = all (\(Entity _ ExamOccurrence{..}, _) -> Just examOccurrenceStart == examStart && examOccurrenceEnd == examEnd) occurrences
registerWidget

View File

@ -6,7 +6,6 @@ module Handler.Exam.Users
import Import
import Utils.Lens
import Handler.Utils
import Handler.Utils.Exam
import Handler.Utils.Table.Columns

View File

@ -5,8 +5,6 @@ import Import
import qualified Data.Aeson.Encode.Pretty as Aeson
import qualified Data.Text.Lazy.Builder as Builder
import Utils.Lens
import qualified Data.UUID as UUID
import Data.Semigroup (Min(..), Max(..))

View File

@ -2,7 +2,6 @@ module Handler.Home where
import Import
import Utils.Lens
import Handler.Utils
import Handler.Utils.Table.Cells

View File

@ -14,7 +14,6 @@ import qualified Data.Conduit.List as C
import qualified Database.Esqueleto as E
import Database.Esqueleto.Utils.TH
import Utils.Lens
import Utils.Form
import Handler.Utils
import Handler.Utils.Delete

View File

@ -5,7 +5,6 @@ import Import
import Handler.Utils
import Handler.Utils.Table.Cells
import Utils.Lens
-- import Colonnade hiding (fromMaybe, singleton)
-- import Yesod.Colonnade
import Data.Monoid (Any(..))

View File

@ -49,11 +49,6 @@ import Data.Map (Map, (!))
import Data.Monoid (Any(..))
-- import Control.Lens
import Utils.Lens
--import qualified Data.Aeson as Aeson
import Control.Monad.Random.Class (MonadRandom(..))
import Utils.Sql

View File

@ -6,8 +6,6 @@ import Import
import Jobs
import Utils.Lens
-- import Yesod.Form.Bootstrap3
import Handler.Utils

View File

@ -11,8 +11,6 @@ import qualified Data.List.NonEmpty as NonEmpty
import Handler.Utils
import Handler.Utils.Table.Cells
import Utils.Lens
import qualified Database.Esqueleto as E
-- htmlField' moved to Handler.Utils.Form/Fields

View File

@ -5,8 +5,6 @@ import Handler.Utils
import Handler.Utils.Table.Cells
import qualified Data.Map as Map
import Utils.Lens
import qualified Database.Esqueleto as E
import qualified Data.Set as Set

View File

@ -25,8 +25,6 @@ import qualified Data.CaseInsensitive as CI
import qualified Data.Text as Text
import Utils.Lens
import Data.Aeson hiding (Result(..))
import Text.Hamlet (ihamlet)

View File

@ -4,7 +4,6 @@ module Handler.Tutorial.Users
import Import
import Utils.Lens
import Utils.Form
-- import Utils.DB
import Handler.Utils

View File

@ -13,8 +13,6 @@ import Handler.Utils.Invitations
import qualified Auth.LDAP as Auth
import Utils.Lens
import qualified Data.CaseInsensitive as CI
import qualified Data.Set as Set

View File

@ -4,8 +4,6 @@ module Handler.Utils
import Import
import Utils.Lens
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Map ((!))

View File

@ -9,7 +9,6 @@ module Handler.Utils.Communication
import Import
import Handler.Utils
import Utils.Lens
import Jobs.Queue
import Control.Monad.Trans.Reader (mapReaderT)

View File

@ -5,8 +5,6 @@ module Handler.Utils.ContentDisposition
import Import
import Utils.Lens
-- | Check whether the user's preference for files is inline-viewing or downloading
downloadFiles :: (MonadHandler m, HandlerSite m ~ UniWorX) => m Bool
downloadFiles = do

View File

@ -16,8 +16,6 @@ module Handler.Utils.DateTime
import Import
import Utils.Lens
import Data.Time.Zones
import qualified Data.Time.Zones as TZ

View File

@ -17,8 +17,6 @@ module Handler.Utils.Delete
import Import
import Handler.Utils.Form
import Utils.Lens
import qualified Data.Text as Text
import qualified Data.Set as Set

View File

@ -12,8 +12,6 @@ import qualified Database.Esqueleto.Utils as E
import qualified Database.Esqueleto.Internal.Sql as E
import Database.Esqueleto.Utils.TH
import Utils.Lens
import qualified Data.Conduit.List as C
import qualified Data.Map as Map

View File

@ -40,13 +40,9 @@ import Control.Monad.Error.Class (MonadError(..))
import Data.Either (partitionEithers)
import Utils.Lens
import Data.Aeson (eitherDecodeStrict')
import Data.Aeson.Text (encodeToLazyText)
import Data.Proxy
import qualified Text.Email.Validate as Email
import Yesod.Core.Types (FileInfo(..))

View File

@ -17,7 +17,6 @@ module Handler.Utils.Form.MassInput
import Import
import Utils.Form
import Utils.Lens
import Handler.Utils.Form.MassInput.Liveliness
import Handler.Utils.Form.MassInput.TH

View File

@ -10,8 +10,6 @@ import qualified Data.Set as Set
import Data.Map ((!))
import qualified Data.Map as Map
import Utils.Lens
data OccurrenceScheduleKind = ScheduleKindWeekly
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)

View File

@ -16,7 +16,6 @@ module Handler.Utils.Invitations
) where
import Import
import Utils.Lens
import Utils.Form
import Jobs.Queue

View File

@ -7,8 +7,6 @@ module Handler.Utils.Mail
import Import
import Utils.Lens
import qualified Data.CaseInsensitive as CI
import qualified Data.ByteString.Lazy as LBS

View File

@ -39,8 +39,6 @@ import qualified Database.Esqueleto as E
import qualified Data.Conduit.List as Conduit
import Utils.Lens
instance HasResolution prec => Pretty (Fixed prec) where
pretty = pretty . show

View File

@ -5,7 +5,6 @@ module Handler.Utils.SheetType
import Import
import Data.Monoid (Sum(..))
import Utils.Lens
addBonusToPoints :: SheetTypeSummary -> SheetTypeSummary
addBonusToPoints sts =

View File

@ -15,8 +15,6 @@ import Import hiding (joinPath)
import Jobs.Queue
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))
import Utils.Lens
import Control.Monad.State as State (StateT)
import Control.Monad.State.Class as State
import Control.Monad.Writer (MonadWriter(..), execWriterT, execWriter)

View File

@ -2,7 +2,6 @@ module Handler.Utils.Table where
-- General Utilities for Tables
import Import
import Data.Profunctor
import Control.Monad.Except
@ -51,10 +50,12 @@ headedRowSelector toExternal fromExternal attrs colonnade tdata = do
externalIds <- mapM (lift . toExternal) tdata
let
checkbox extId = Field parse view UrlEncoded
checkbox extId = Field{..}
where
parse [] _ = return $ Right Nothing
parse optlist _ = runExceptT $ do
fieldEnctype = UrlEncoded
fieldParse [] _ = return $ Right Nothing
fieldParse optlist _ = runExceptT $ do
extIds <- maybe (throwError "Error parsing values") return $ mapM fromPathPiece optlist
case () of
_ | extId `elem` extIds
@ -62,11 +63,11 @@ headedRowSelector toExternal fromExternal attrs colonnade tdata = do
| otherwise
-> return Nothing
view _ name attributes val _ =
fieldView theId name attributes val _ =
-- TODO: move this to a *.hamlet file
[whamlet|
<label style="display: block">
<input type=checkbox name=#{name} value=#{toPathPiece extId} *{attributes} :isRight val:checked>
<input ##{theId} type=checkbox name=#{name} value=#{toPathPiece extId} *{attributes} :isRight val:checked>
|]
selectionIdent <- newFormIdent

View File

@ -13,7 +13,6 @@ import Control.Monad.Trans.Writer (WriterT)
import Text.Blaze (ToMarkup(..))
import Utils.Lens
import Handler.Utils
import Utils.Occurrences
@ -101,6 +100,9 @@ msgCell = textCell . toMessage
---------------------
-- Icon cells
iconCell :: IsDBTable m a => Icon -> DBCell m a
iconCell = cell . toWidget . icon
addIconFixedWidth :: (IsDBTable m a) => DBCell m a -> DBCell m a
addIconFixedWidth = over cellAttrs $ insertClass "icon-fixed-width"

View File

@ -12,14 +12,19 @@ import Import
-- import Text.Blaze (ToMarkup(..))
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
import Database.Esqueleto.Utils (mkExactFilter, mkExactFilterWith, mkContainsFilter, mkContainsFilterWith, anyFilter)
import Utils.Lens
import Handler.Utils
import Handler.Utils.Table.Cells
import qualified Data.CaseInsensitive as CI
import qualified Colonnade
import Colonnade.Encode (Colonnade(..), OneColonnade(..))
import Text.Blaze (toMarkup)
--------------------------------
-- Generic Columns
@ -35,6 +40,36 @@ import qualified Data.CaseInsensitive as CI
-- * fltrXYZ : filter definitions for these columns
-- * additional helper, such as default sorting
type OpticColonnade focus
= forall m x r' h.
( IsDBTable m x
, FromSortable h
)
=> (forall focus'. Getting focus' r' focus)
-> Colonnade h r' (DBCell m x)
type OpticSortColumn' focus
= forall t sortingMap.
( IsMap sortingMap
, ContainerKey sortingMap ~ SortingKey
, MapValue sortingMap ~ SortColumn t
)
=> (forall focus'. Getting focus' t focus)
-> sortingMap
type OpticSortColumn val = OpticSortColumn' (E.SqlExpr (E.Value val))
type OpticFilterColumn' t inp focus
= forall filterMap.
( IsMap filterMap
, ContainerKey filterMap ~ FilterKey
, MapValue filterMap ~ FilterColumn t
, IsFilterColumn t (t -> inp -> E.SqlExpr (E.Value Bool))
)
=> (forall focus'. Getting focus' t focus)
-> filterMap
type OpticFilterColumn t focus = OpticFilterColumn' t (Set focus) (E.SqlExpr (E.Value focus))
-----------------------
-- Numbers and Indices
@ -44,6 +79,174 @@ import qualified Data.CaseInsensitive as CI
dbRowIndicator :: IsDBTable m Any => Colonnade Sortable (DBRow r) (DBCell m Any)
dbRowIndicator = sortable Nothing (i18nCell MsgNrColumn) $ \DBRow{ dbrIndex } -> tellCell (Any True) $ textCell $ tshow dbrIndex
-----------
-- Terms --
-----------
colTermShort :: OpticColonnade TermId
colTermShort resultTid = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "term") (i18nCell MsgTerm)
body = i18nCell . ShortTermIdentifier . unTermKey . view resultTid
sortTerm :: OpticSortColumn TermId
sortTerm queryTid = singletonMap "term" . SortColumn $ view queryTid
fltrTerm :: OpticFilterColumn t TermId
fltrTerm queryTid = singletonMap "term" . FilterColumn $ mkExactFilter (view queryTid)
fltrTermUI :: DBFilterUI
fltrTermUI mPrev = prismAForm (singletonFilter "term" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift termField) (fslI MsgTerm)
-------------
-- Schools --
-------------
colSchoolShort :: OpticColonnade SchoolId
colSchoolShort resultSsh = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "school") (i18nCell MsgSchool)
body = i18nCell . unSchoolKey . view resultSsh
sortSchool :: OpticSortColumn SchoolId
sortSchool querySsh = singletonMap "school" . SortColumn $ view querySsh
fltrSchool :: OpticFilterColumn t SchoolId
fltrSchool querySsh = singletonMap "school" . FilterColumn $ mkExactFilter (view querySsh)
fltrSchoolUI :: DBFilterUI
fltrSchoolUI mPrev = prismAForm (singletonFilter "school" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift schoolField) (fslI MsgSchool)
-----------------
-- Allocations --
-----------------
colAllocationName :: OpticColonnade AllocationName
colAllocationName resultName = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "allocation") (i18nCell MsgAllocationName)
body = i18nCell . view resultName
sortAllocationName :: OpticSortColumn AllocationName
sortAllocationName queryName = singletonMap "allocation" . SortColumn $ view queryName
fltrAllocation :: forall allocation t shorthand name.
( E.SqlProject Allocation AllocationShorthand allocation shorthand
, E.SqlProject Allocation AllocationName allocation name
, E.SqlString name, E.SqlString shorthand
)
=> OpticFilterColumn' t (Set Text) (E.SqlExpr allocation)
fltrAllocation query = singletonMap "allocation" . FilterColumn $ anyFilter
[ mkContainsFilterWith (unSqlProject' . CI.mk) $ views query (`E.sqlProject` AllocationShorthand)
, mkContainsFilterWith (unSqlProject' . CI.mk) $ views query (`E.sqlProject` AllocationName)
]
where
unSqlProject' = E.unSqlProject (Proxy @Allocation) (Proxy @allocation)
fltrAllocationUI :: DBFilterUI
fltrAllocationUI mPrev = prismAForm (singletonFilter "allocation" . maybePrism _PathPiece) mPrev $ aopt (ciField :: Field _ AllocationName) (fslI MsgAllocation)
colAllocationShorthand :: OpticColonnade AllocationShorthand
colAllocationShorthand resultShort = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "allocation-short") (i18nCell MsgAllocation)
body = i18nCell . view resultShort
sortAllocationShorthand :: forall shorthand. PersistField shorthand => OpticSortColumn shorthand
sortAllocationShorthand queryShorthand = singletonMap "allocation-short" . SortColumn $ view queryShorthand
-------------------------
-- Course Applications --
-------------------------
colApplicationId :: OpticColonnade CourseApplicationId
colApplicationId resultId = Colonnade.singleton (fromSortable header) body
where
header = Sortable Nothing (i18nCell MsgCourseApplicationId)
body = views resultId $ cell . (toWidget . toMarkup =<<) . (encrypt :: CourseApplicationId -> WidgetT UniWorX IO CryptoFileNameCourseApplication)
colApplicationRatingPoints :: OpticColonnade (Maybe ExamGrade)
colApplicationRatingPoints resultPoints = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "points") (i18nCell MsgCourseApplicationRatingPoints)
body = views resultPoints $ maybe mempty i18nCell
sortApplicationRatingPoints :: OpticSortColumn (Maybe ExamGrade)
sortApplicationRatingPoints queryPoints = singletonMap "points" . SortColumn $ view queryPoints
fltrApplicationRatingPoints :: OpticFilterColumn t (Maybe ExamGrade)
fltrApplicationRatingPoints queryPoints = singletonMap "points" . FilterColumn . mkExactFilter $ view queryPoints
fltrApplicationRatingPointsUI :: DBFilterUI
fltrApplicationRatingPointsUI mPrev = prismAForm (singletonFilter "points" . maybePrism _PathPiece) mPrev $ aopt examGradeField (fslI MsgCourseApplicationRatingPoints)
colApplicationVeto :: OpticColonnade Bool
colApplicationVeto resultVeto = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "veto") (i18nCell MsgCourseApplicationVeto)
body = views resultVeto $ bool mempty (iconCell IconApplicationVeto)
sortApplicationVeto :: OpticSortColumn Bool
sortApplicationVeto queryVeto = singletonMap "veto" . SortColumn $ view queryVeto
fltrApplicationVeto :: OpticFilterColumn t Bool
fltrApplicationVeto queryVeto = singletonMap "veto" . FilterColumn . mkExactFilter $ view queryVeto
fltrApplicationVetoUI :: DBFilterUI
fltrApplicationVetoUI mPrev = prismAForm (singletonFilter "veto" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgCourseApplicationVeto)
colApplicationRatingComment :: OpticColonnade (Maybe Text)
colApplicationRatingComment resultComment = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "comment") (i18nCell MsgApplicationRatingComment)
body = views resultComment . maybe mempty $ cell . modal (toWidget $ hasComment True) . Right . toWidget
sortApplicationRatingComment :: OpticSortColumn (Maybe Text)
sortApplicationRatingComment queryComment = singletonMap "comment" . SortColumn $ view queryComment
fltrApplicationRatingComment :: OpticFilterColumn' t (Set Text) (E.SqlExpr (E.Value (Maybe Text)))
fltrApplicationRatingComment queryComment = singletonMap "comment" . FilterColumn . mkContainsFilterWith Just $ view queryComment
fltrApplicationRatingCommentUI :: DBFilterUI
fltrApplicationRatingCommentUI mPrev = prismAForm (singletonFilter "comment") mPrev $ aopt textField (fslI MsgApplicationRatingComment)
colApplicationText :: OpticColonnade (Maybe Text)
colApplicationText resultText = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "text") (i18nCell MsgCourseApplicationText)
body = views resultText . maybe mempty $ cell . modal (toWidget $ hasComment True) . Right . toWidget
sortApplicationText :: OpticSortColumn (Maybe Text)
sortApplicationText queryText = singletonMap "text" . SortColumn $ view queryText
fltrApplicationText :: OpticFilterColumn' t (Set Text) (E.SqlExpr (E.Value (Maybe Text)))
fltrApplicationText queryText = singletonMap "text" . FilterColumn . mkContainsFilterWith Just $ view queryText
fltrApplicationTextUI :: DBFilterUI
fltrApplicationTextUI mPrev = prismAForm (singletonFilter "text") mPrev $ aopt textField (fslI MsgCourseApplicationText)
colApplicationFiles :: OpticColonnade (TermId, SchoolId, CourseShorthand, CourseApplicationId, Bool) -- ^ `Bool` controls whether link is shown, use result of determination whether files exist
colApplicationFiles resultInfo = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "has-files") (i18nCell MsgCourseApplicationFiles)
body = views resultInfo $ \(tid, ssh, csh, appId, showLink) -> if
| showLink
-> flip anchorCellM (asWidgetT $ toWidget iconApplicationFiles) $ do
cID <- encrypt appId
return $ CApplicationR tid ssh csh cID CAFilesR
| otherwise
-> mempty
sortApplicationFiles :: OpticSortColumn Bool
sortApplicationFiles queryFiles = singletonMap "has-files" . SortColumn $ view queryFiles
fltrApplicationFiles :: OpticFilterColumn t Bool
fltrApplicationFiles queryFiles = singletonMap "has-files" . FilterColumn . mkExactFilter $ view queryFiles
fltrApplicationFilesUI :: DBFilterUI
fltrApplicationFilesUI mPrev = prismAForm (singletonFilter "has-files" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgCourseApplicationFiles)
---------------
-- Files
@ -92,9 +295,24 @@ defaultSortingByFileModification = defaultSorting [SortAscBy "time"]
---------------
-- User names
-- | Generic sort key from msg does not work, since we have no show Instance for RenderMesage UniWorX msg. Dangerous anyway!
colUserName' :: (IsDBTable m c, HasUser a, RenderMessage UniWorX msg, Show msg) => msg -> Colonnade Sortable a (DBCell m c)
colUserName' msg = sortable (Just $ fromString $ show msg) (i18nCell msg) cellHasUser
colUserDisplayName :: OpticColonnade (UserDisplayName, UserSurname)
colUserDisplayName resultDisplayName = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "user-name") (i18nCell MsgUserDisplayName)
body = views resultDisplayName $ cell . uncurry nameWidget
sortUserName' :: OpticSortColumn' (E.SqlExpr (E.Value UserDisplayName), E.SqlExpr (E.Value UserSurname))
sortUserName' queryDisplayName = singletonMap "user-name" . SortColumns $ \(view queryDisplayName -> (dn, sn))
-> [ SomeExprValue sn
, SomeExprValue dn
]
fltrUserName' :: OpticFilterColumn t UserDisplayName
fltrUserName' queryDisplayName = singletonMap "user-name" . FilterColumn . mkContainsFilter $ view queryDisplayName
fltrUserNameUI' :: DBFilterUI
fltrUserNameUI' mPrev = prismAForm (singletonFilter "user-name") mPrev $ aopt textField (fslI MsgUserDisplayName)
colUserName :: (IsDBTable m c, HasUser a) => Colonnade Sortable a (DBCell m c)
colUserName = sortable (Just "user-name") (i18nCell MsgCourseMembers) cellHasUser
@ -103,11 +321,12 @@ colUserNameLink :: (IsDBTable m c, HasEntity a User) => (CryptoUUIDUser -> Route
colUserNameLink userLink = sortable (Just "user-name") (i18nCell MsgCourseMembers) (cellHasUserLink userLink)
-- | Intended to work with @nameWidget@, showing highlighter Surname within Displayname
-- TOOD: We want to sort first by UserSurname and then by UserDisplayName, not supportet by dbTable
-- see also @defaultSortingName@
sortUserName :: IsString a => (t -> E.SqlExpr (Entity User)) -> (a, SortColumn t)
sortUserName queryUser = ("user-name", SortColumn $ toSortKey . queryUser)
where toSortKey user = (user E.^. UserSurname) E.++. (user E.^. UserDisplayName)
sortUserName queryUser = ("user-name", SortColumns $ queryUser >>> \user ->
[ SomeExprValue $ user E.^. UserSurname
, SomeExprValue $ user E.^. UserDisplayName
]
)
-- | Alias for sortUserName for consistency, since column comes in two variants
sortUserNameLink :: IsString a => (t -> E.SqlExpr (Entity User)) -> (a, SortColumn t)
@ -175,7 +394,25 @@ fltrUserNameEmailUI mPrev =
prismAForm (singletonFilter "user-name-email") mPrev $ aopt textField (fslI MsgCourseMembers)
-------------------
-- Matriclenumber
-- Matriculation --
-------------------
colUserMatriculation :: OpticColonnade (Maybe UserMatriculation)
colUserMatriculation resultMatriculation = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "user-matriculation") (i18nCell MsgUserMatriculation)
body = views resultMatriculation . maybe mempty $ cell . toWidget
sortUserMatriculation :: OpticSortColumn (Maybe UserMatriculation)
sortUserMatriculation queryMatriculation = singletonMap "user-matriculation" . SortColumn $ view queryMatriculation
fltrUserMatriculation :: OpticFilterColumn' t (Set Text) (E.SqlExpr (E.Value (Maybe UserMatriculation)))
fltrUserMatriculation queryMatriculation = singletonMap "user-matriculation" . FilterColumn . mkContainsFilterWith Just $ view queryMatriculation
fltrUserMatriculationUI :: DBFilterUI
fltrUserMatriculationUI mPrev = prismAForm (singletonFilter "user-matriculation") mPrev $ aopt textField (fslI MsgUserMatriculation)
colUserMatriclenr :: (IsDBTable m c, HasUser a) => Colonnade Sortable a (DBCell m c)
colUserMatriclenr = sortable (Just "user-matriclenumber") (i18nCell MsgMatrikelNr) cellHasMatrikelnummer
@ -218,6 +455,109 @@ fltrUserEmailUI mPrev =
-- Study features --
--------------------
colStudyDegree :: OpticColonnade StudyDegree
colStudyDegree resultDegree = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "features-degree") (i18nCell MsgStudyFeatureDegree)
body = views resultDegree $ \StudyDegree{..}
-> cell . maybe (toWidget $ toMarkup studyDegreeKey) toWidget $ studyDegreeShorthand <|> studyDegreeName
sortStudyDegree :: forall studyDegree name shorthand key.
( E.SqlProject StudyDegree (Maybe StudyDegreeName) studyDegree name
, E.SqlProject StudyDegree (Maybe StudyDegreeShorthand) studyDegree shorthand
, E.SqlProject StudyDegree StudyDegreeKey studyDegree key
, PersistField key, PersistField name, PersistField shorthand
)
=> OpticSortColumn' (E.SqlExpr studyDegree)
sortStudyDegree queryDegree = singletonMap "features-degree" . SortColumns $ \(view queryDegree -> degree)
-> [ SomeExprValue $ degree `E.sqlProject` StudyDegreeName
, SomeExprValue $ degree `E.sqlProject` StudyDegreeShorthand
, SomeExprValue $ degree `E.sqlProject` StudyDegreeKey
]
fltrStudyDegree :: forall studyDegree t name shorthand key.
( E.SqlProject StudyDegree (Maybe StudyDegreeName) studyDegree name
, E.SqlProject StudyDegree (Maybe StudyDegreeShorthand) studyDegree shorthand
, E.SqlProject StudyDegree StudyDegreeKey studyDegree key
, E.SqlString name, E.SqlString shorthand, PersistField key
)
=> OpticFilterColumn' t (Set Text) (E.SqlExpr studyDegree)
fltrStudyDegree queryDegree = singletonMap "features-degree" . FilterColumn $ anyFilter
[ mkContainsFilterWith (unSqlProject' . Just) $ view queryDegree >>> (`E.sqlProject` StudyDegreeName)
, mkContainsFilterWith (unSqlProject' . Just) $ view queryDegree >>> (`E.sqlProject` StudyDegreeShorthand)
, mkExactFilterWith (fmap unSqlProject' . (readMay :: Text -> Maybe StudyDegreeKey)) $ view queryDegree >>> (`E.sqlProject` StudyDegreeKey) >>> E.just
]
where
unSqlProject' :: E.SqlProject StudyDegree value studyDegree value' => value -> value'
unSqlProject' = E.unSqlProject (Proxy @StudyDegree) (Proxy @studyDegree)
fltrStudyDegreeUI :: DBFilterUI
fltrStudyDegreeUI mPrev = prismAForm (singletonFilter "features-degree") mPrev $ aopt textField (fslI MsgStudyFeatureDegree)
colStudyTerms :: OpticColonnade StudyTerms
colStudyTerms resultTerms = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "features-terms") (i18nCell MsgStudyTerm)
body = views resultTerms $ \StudyTerms{..}
-> cell . maybe (toWidget $ toMarkup studyTermsKey) toWidget $ studyTermsShorthand <|> studyTermsName
sortStudyTerms :: forall studyTerms name shorthand key.
( E.SqlProject StudyTerms (Maybe StudyTermsName) studyTerms name
, E.SqlProject StudyTerms (Maybe StudyTermsShorthand) studyTerms shorthand
, E.SqlProject StudyTerms StudyTermsKey studyTerms key
, PersistField key, PersistField name, PersistField shorthand
)
=> OpticSortColumn' (E.SqlExpr studyTerms)
sortStudyTerms queryTerms = singletonMap "features-terms" . SortColumns $ \(view queryTerms -> terms)
-> [ SomeExprValue $ terms `E.sqlProject` StudyTermsName
, SomeExprValue $ terms `E.sqlProject` StudyTermsShorthand
, SomeExprValue $ terms `E.sqlProject` StudyTermsKey
]
fltrStudyTerms :: forall studyTerms t name shorthand key.
( E.SqlProject StudyTerms (Maybe StudyTermsName) studyTerms name
, E.SqlProject StudyTerms (Maybe StudyTermsShorthand) studyTerms shorthand
, E.SqlProject StudyTerms StudyTermsKey studyTerms key
, E.SqlString name, E.SqlString shorthand, PersistField key
)
=> OpticFilterColumn' t (Set Text) (E.SqlExpr studyTerms)
fltrStudyTerms queryTerms = singletonMap "features-terms" . FilterColumn $ anyFilter
[ mkContainsFilterWith (unSqlProject' . Just) $ view queryTerms >>> (`E.sqlProject` StudyTermsName)
, mkContainsFilterWith (unSqlProject' . Just) $ view queryTerms >>> (`E.sqlProject` StudyTermsShorthand)
, mkExactFilterWith (fmap unSqlProject' . (readMay :: Text -> Maybe StudyTermsKey)) $ view queryTerms >>> (`E.sqlProject` StudyTermsKey) >>> E.just
]
where
unSqlProject' :: E.SqlProject StudyTerms value studyTerms value' => value -> value'
unSqlProject' = E.unSqlProject (Proxy @StudyTerms) (Proxy @studyTerms)
fltrStudyTermsUI :: DBFilterUI
fltrStudyTermsUI mPrev = prismAForm (singletonFilter "features-terms") mPrev $ aopt textField (fslI MsgStudyTerm)
colStudyFeaturesSemester :: OpticColonnade Int
colStudyFeaturesSemester resultSemester = Colonnade.singleton (fromSortable header) body
where
header = Sortable (Just "features-semester") (i18nCell MsgStudyFeatureAge)
body = views resultSemester $ cell . toWidget . toMarkup
sortStudyFeaturesSemester :: forall semester. PersistField semester => OpticSortColumn semester
sortStudyFeaturesSemester querySemester = singletonMap "features-semester" . SortColumn $ view querySemester
fltrStudyFeaturesSemester :: forall studyFeatures t semester.
( E.SqlProject StudyFeatures Int studyFeatures semester
, PersistField semester
)
=> OpticFilterColumn' t (Set Int) (E.SqlExpr (E.Value semester))
fltrStudyFeaturesSemester querySemester = singletonMap "features-semester" . FilterColumn . mkExactFilterWith unSqlProject' $ view querySemester
where
unSqlProject' :: Int -> semester
unSqlProject' = E.unSqlProject (Proxy @StudyFeatures) (Proxy @studyFeatures)
fltrStudyFeaturesSemesterUI :: DBFilterUI
fltrStudyFeaturesSemesterUI mPrev = prismAForm (singletonFilter "features-semester" . maybePrism _PathPiece) mPrev $ aopt (intField :: Field _ Int) (fslI MsgStudyFeatureAge)
colFeaturesSemester :: (IsDBTable m c, HasStudyFeatures x) => Getting (Leftmost x) a x -> Colonnade Sortable a (DBCell m c)
colFeaturesSemester feature = sortable (Just "features-semester") (i18nCell MsgStudyFeatureAge) $ maybe mempty cellHasSemester . firstOf feature
@ -282,3 +622,64 @@ fltrDegree queryFeatures = ( "degree"
fltrDegreeUI :: Maybe (Map FilterKey [Text]) -> AForm (YesodDB UniWorX) (Map FilterKey [Text])
fltrDegreeUI mPrev =
prismAForm (singletonFilter "degree") mPrev $ aopt textField (fslI MsgDegreeName)
----------------------------
-- Colonnade manipulation --
----------------------------
imapColonnade :: (a -> c -> c)
-> Colonnade h a c
-> Colonnade h a c
-- ^ Not quite `imap`
imapColonnade f (Colonnade ones) = Colonnade $ dimapColonnade' <$> ones
where
dimapColonnade' OneColonnade{..} = OneColonnade
{ oneColonnadeEncode = \x -> f x $ oneColonnadeEncode x
, oneColonnadeHead
}
anchorColonnade :: forall h r' m a url.
( HasRoute UniWorX url
, IsDBTable m a
, HandlerSite m ~ UniWorX
)
=> (r' -> url)
-> Colonnade h r' (DBCell m a)
-> Colonnade h r' (DBCell m a)
anchorColonnade = anchorColonnadeM . (return .)
anchorColonnadeM :: forall h r' m a url.
( HasRoute UniWorX url
, IsDBTable m a
, HandlerSite m ~ UniWorX
)
=> (r' -> WidgetT UniWorX IO url)
-> Colonnade h r' (DBCell m a)
-> Colonnade h r' (DBCell m a)
anchorColonnadeM mkUrl = imapColonnade anchorColonnade'
where
anchorColonnade' :: r' -> DBCell m a -> DBCell m a
anchorColonnade' inp (view dbCell -> (attrs, act)) = review dbCell . (attrs,) $
view (dbCell . _2) . anchorCellM (mkUrl inp) =<< act
emptyOpticColonnade :: forall h r' focus c.
( Monoid c
)
=> Fold r' focus -- ^ View on @focus@ within @r'@ that may produce any number of results
-> ((forall focus'. Getting focus' r' focus) -> Colonnade h r' c) -- ^ `OpticColonnade focus`
-> Colonnade h r' c
-- ^ Generalize an `OpticColonnade` from `Getter` to `Fold` by defaulting results of zero or more than one values to `mempty`
emptyOpticColonnade l c = Colonnade $ oldColonnade <&> \column -> column { oneColonnadeEncode = \s -> defaultColumn s $ oneColonnadeEncode column }
where
Colonnade oldColonnade = c $ singular l
-- This is safe (as long as we don't evaluate the `oneColonnadeEncode`s)
-- because `Getter s a` is of kind @k -> *@ and can thus only be inspected
-- by @c@ through application which is precluded by the type of `Getter s a`
-- and the definition of `OneColonnade`
defaultColumn :: r' -> (r' -> c) -> c
defaultColumn x f = case x ^.. l of
[_] -> f x
_ -> mempty

View File

@ -1,5 +1,7 @@
module Handler.Utils.Table.Pagination
( module Handler.Utils.Table.Pagination.Types
, dbFilterKey
, SomeExprValue(..)
, SortColumn(..), SortDirection(..)
, SortingSetting(..)
, pattern SortAscBy, pattern SortDescBy
@ -10,7 +12,7 @@ module Handler.Utils.Table.Pagination
, DBCsvActionMode(..)
, DBCsvDiff(..), _DBCsvDiffNew, _DBCsvDiffExisting, _DBCsvDiffMissing, _dbCsvOldKey, _dbCsvOld, _dbCsvNewKey, _dbCsvNew
, DBTCsvEncode, DBTCsvDecode(..)
, DBTable(..), noCsvEncode, IsDBTable(..), DBCell(..)
, DBTable(..), DBFilterUI, noCsvEncode, IsDBTable(..), DBCell(..)
, singletonFilter
, DBParams(..)
, cellAttrs, cellContents
@ -81,8 +83,6 @@ import Text.Hamlet (hamletFile)
import Data.Ratio ((%))
import Control.Lens.Extras (is)
import Data.List (elemIndex)
import Data.Aeson (Options(..), SumEncoding(..), defaultOptions)
@ -116,7 +116,26 @@ type Monoid' x = (Sem.Semigroup x, Monoid x)
#endif
data WithIdent x = forall ident. PathPiece ident => WithIdent { _ident :: ident, _withoutIdent :: x }
instance PathPiece x => PathPiece (WithIdent x) where
toPathPiece (WithIdent ident x)
| not . null $ toPathPiece ident = toPathPiece ident <> "-" <> toPathPiece x
| otherwise = toPathPiece x
fromPathPiece txt = do
let sep = "-"
(ident, (Text.stripSuffix sep -> Just rest)) <- return $ Text.breakOn sep txt
WithIdent <$> pure ident <*> fromPathPiece rest
dbFilterKey :: PathPiece dbtIdent => dbtIdent -> FilterKey -> Text
dbFilterKey ident = toPathPiece . WithIdent ident
data SomeExprValue = forall a. PersistField a => SomeExprValue { getSomeExprValue :: E.SqlExpr (E.Value a) }
data SortColumn t = forall a. PersistField a => SortColumn { getSortColumn :: t -> E.SqlExpr (E.Value a) }
| SortColumns { getSortColumns :: t -> [SomeExprValue] }
data SortDirection = SortAsc | SortDesc
deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic)
@ -133,9 +152,11 @@ deriveJSON defaultOptions
{ constructorTagModifier = camelToPathPiece' 1
} ''SortDirection
sqlSortDirection :: t -> (SortColumn t, SortDirection) -> E.SqlExpr E.OrderBy
sqlSortDirection t (SortColumn e, SortAsc ) = E.asc $ e t
sqlSortDirection t (SortColumn e, SortDesc) = E.desc $ e t
sqlSortDirection :: t -> (SortColumn t, SortDirection) -> [E.SqlExpr E.OrderBy]
sqlSortDirection t (SortColumn e , SortAsc ) = pure . E.asc $ e t
sqlSortDirection t (SortColumn e , SortDesc) = pure . E.desc $ e t
sqlSortDirection t (SortColumns es, SortAsc ) = es t <&> \(SomeExprValue v) -> E.asc v
sqlSortDirection t (SortColumns es, SortDesc) = es t <&> \(SomeExprValue v) -> E.desc v
data SortingSetting = SortingSetting
@ -486,17 +507,6 @@ singletonFilter key = prism' fromInner (fmap Just . fromOuter)
fromOuter = Map.lookup key >=> listToMaybe
data WithIdent x = forall ident. PathPiece ident => WithIdent { _ident :: ident, _withoutIdent :: x }
instance PathPiece x => PathPiece (WithIdent x) where
toPathPiece (WithIdent ident x)
| not . null $ toPathPiece ident = toPathPiece ident <> "-" <> toPathPiece x
| otherwise = toPathPiece x
fromPathPiece txt = do
let sep = "-"
(ident, (Text.stripSuffix sep -> Just rest)) <- return $ Text.breakOn sep txt
WithIdent <$> pure ident <*> fromPathPiece rest
type DBTCsvEncode r' csv = DictMaybe (ToNamedRecord csv, DefaultOrdered csv, CsvColumnsExplained csv) (Conduit r' (YesodDB UniWorX) csv)
data DBTCsvDecode r' k' csv = forall route csvAction csvActionClass csvException.
( FromNamedRecord csv, ToNamedRecord csv, DefaultOrdered csv
@ -529,7 +539,7 @@ data DBTable m x = forall a r r' h i t k k' csv.
, dbtColonnade :: Colonnade h r' (DBCell m x)
, dbtSorting :: Map SortingKey (SortColumn t)
, dbtFilter :: Map FilterKey (FilterColumn t)
, dbtFilterUI :: Maybe (Map FilterKey [Text]) -> AForm (YesodDB UniWorX) (Map FilterKey [Text])
, dbtFilterUI :: DBFilterUI
, dbtStyle :: DBStyle r'
, dbtParams :: DBParams m x
, dbtCsvEncode :: DBTCsvEncode r' csv
@ -537,6 +547,8 @@ data DBTable m x = forall a r r' h i t k k' csv.
, dbtIdent :: i
}
type DBFilterUI = Maybe (Map FilterKey [Text]) -> AForm (YesodDB UniWorX) (Map FilterKey [Text])
noCsvEncode :: DictMaybe (ToNamedRecord Void, DefaultOrdered Void, CsvColumnsExplained Void) (Conduit r' (YesodDB UniWorX) Void)
noCsvEncode = Nothing
@ -771,7 +783,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
piInput <- lift . runInputGetResult $ PaginationInput
<$> iopt (multiSelectField $ return sortingOptions) (wIdent "sorting")
<*> (assertM' (not . Map.null) . Map.mapMaybe (assertM $ not . null) <$> Map.traverseWithKey (\k _ -> iopt multiTextField . wIdent $ toPathPiece k) dbtFilter)
<*> (assertM' (not . Map.null) . Map.mapMaybe (assertM $ not . null) <$> Map.traverseWithKey (\k _ -> iopt multiTextField $ dbFilterKey dbtIdent' k) dbtFilter)
<*> iopt pathPieceField (wIdent "pagesize")
<*> iopt intField (wIdent "page")
@ -818,7 +830,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
tblLink f = SomeRoute . (currentRoute, ) . over (mapped . _2) (fromMaybe Text.empty) $ (f . substPi . setParam "_hasdata" Nothing) getParams
substPi = foldr (.) id
[ setParams (wIdent "sorting") . map toPathPiece $ fromMaybe [] piSorting
, foldr (.) id . map (\k -> setParams (wIdent $ toPathPiece k) . fromMaybe [] . join $ traverse (Map.lookup k) piFilter) $ Map.keys dbtFilter
, foldr (.) id . map (\k -> setParams (dbFilterKey dbtIdent' k) . fromMaybe [] . join $ traverse (Map.lookup k) piFilter) $ Map.keys dbtFilter
, setParam (wIdent "pagesize") $ fmap toPathPiece piLimit
, setParam (wIdent "page") $ fmap toPathPiece piPage
, setParam (wIdent "pagination") Nothing
@ -863,7 +875,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
rows' <- E.select . E.from $ \t -> do
res <- dbtSQLQuery t
E.orderBy (map (sqlSortDirection t) psSorting')
E.orderBy $ concatMap (sqlSortDirection t) psSorting'
case csvMode of
FormSuccess DBCsvExport -> return ()
FormSuccess DBCsvImport{} -> return ()

View File

@ -4,7 +4,7 @@ module Handler.Utils.Table.Pagination.Types
( FilterKey, SortingKey
, Sortable(..)
, sortable
, ToSortable(..)
, ToSortable(..), FromSortable(..)
, SortableP(..)
, DBTableInvalid(..)
) where
@ -58,6 +58,19 @@ instance ToSortable Headless where
pSortable = Nothing
class FromSortable s where
fromSortable :: forall a. Sortable a -> s a
instance FromSortable Sortable where
fromSortable = id
instance FromSortable Headed where
fromSortable Sortable{..} = Headed sortableContent
instance FromSortable Headless where
fromSortable _ = Headless
data DBTableInvalid = DBTIRowsMissing Int
deriving (Eq, Ord, Read, Show, Generic, Typeable)

View File

@ -5,8 +5,6 @@ module Handler.Utils.Tokens
import Import
import Utils.Lens
import Control.Monad.Trans.Maybe (runMaybeT)

View File

@ -10,8 +10,6 @@ import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Internal.Sql as E
import Database.Esqueleto.Utils.TH
import Utils.Lens
fetchTutorialAux :: ( SqlBackendCanRead backend
, E.SqlSelect b a

View File

@ -10,6 +10,7 @@ import Model.Submission as Import
import Model.Tokens as Import
import Utils.Tokens as Import
import Utils.Frontend.Modal as Import
import Utils.Lens as Import
import Settings as Import
import Settings.StaticFiles as Import

View File

@ -3,7 +3,18 @@ module Import.NoModel
, MForm
) where
import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, getMessages, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField, identifyForm, cons, HasHttpManager(..))
import ClassyPrelude.Yesod as Import
hiding ( formatTime
, derivePersistFieldJSON
, getMessages, addMessage, addMessageI
, (.=)
, MForm
, Proxy
, foldlM
, static
, boolField, identifyForm
, HasHttpManager(..)
)
import Model.Types.TH.JSON as Import
import Model.Types.TH.Wordlist as Import
@ -80,6 +91,8 @@ import Data.Void as Import (Void)
import Algebra.Lattice as Import hiding (meet, join)
import Data.Proxy as Import (Proxy(..))
import Language.Haskell.TH.Instances as Import ()
import Data.List.NonEmpty.Instances as Import ()
import Data.NonNull.Instances as Import ()
@ -114,6 +127,16 @@ import System.FilePath.Instances as Import ()
import Net.IP.Instances as Import ()
import Data.Void.Instances as Import ()
import Crypto.Hash.Instances as Import ()
import Colonnade.Instances as Import ()
import Control.Lens as Import
hiding ( (<.>)
, universe
, cons, uncons, snoc, unsnoc, (<|)
, Index, index, (<.)
)
import Control.Lens.Extras as Import (is)
import Data.Set.Lens as Import
import Control.Monad.Trans.RWS (RWST)

View File

@ -6,8 +6,6 @@ module Jobs
) where
import Import
import Utils.Lens
import Jobs.Types as Types hiding (JobCtl(JobCtlQueue))
import Jobs.Types (JobCtl(JobCtlQueue))
import Jobs.Queue

View File

@ -4,8 +4,6 @@ module Jobs.Crontab
import Import
import Utils.Lens
import qualified Data.HashMap.Strict as HashMap
import Jobs.Types

View File

@ -9,8 +9,6 @@ import qualified Data.CaseInsensitive as CI
import Handler.Utils
import Utils.Lens
import Data.Bitraversable

View File

@ -3,7 +3,6 @@ module Jobs.Handler.Invitation
) where
import Import
import Utils.Lens
import Handler.Utils.Mail
import qualified Data.CaseInsensitive as CI

View File

@ -4,7 +4,6 @@ module Jobs.Handler.SendCourseCommunication
import Import
import Utils.Lens
import Handler.Utils
import qualified Data.Set as Set

View File

@ -6,7 +6,6 @@ module Jobs.Handler.SendNotification.SubmissionRated
import Import
import Utils.Lens
import Handler.Utils
import Jobs.Handler.SendNotification.Utils

View File

@ -5,7 +5,6 @@ module Jobs.Handler.SendNotification.UserAuthModeUpdate
) where
import Import
import Utils.Lens
import Handler.Utils.Mail
import Jobs.Handler.SendNotification.Utils

View File

@ -4,7 +4,6 @@ module Jobs.Handler.SendPasswordReset
import Import
import Utils.Lens
import Handler.Utils
import Handler.Utils.Users

View File

@ -8,8 +8,6 @@ import Handler.Utils.DateTime
import Text.Shakespeare.Text
import Utils.Lens
dispatchJobSendTestEmail :: Email -> MailContext -> Handler ()
dispatchJobSendTestEmail jEmail jMailContext = mailT jMailContext $ do
_mailTo .= [Address Nothing jEmail]

View File

@ -4,7 +4,6 @@ module Jobs.Handler.TransactionLog
) where
import Import hiding (currentYear)
import Utils.Lens hiding ((<.))
import Handler.Utils.DateTime
import Database.Persist.Sql (updateWhereCount, deleteWhereCount)

View File

@ -14,8 +14,6 @@ import Data.Proxy (Proxy(..))
import qualified Data.ByteArray as ByteArray
import Utils.Lens
import Network.HTTP.Simple (httpJSON, httpLBS)
import qualified Network.HTTP.Simple as HTTP

View File

@ -11,7 +11,6 @@ module Jobs.Queue
import Import hiding ((<>))
import Utils.Sql
import Utils.Lens
import Jobs.Types
import Control.Monad.Trans.Writer (WriterT, runWriterT)

View File

@ -18,6 +18,17 @@ type Points = Centi
type Email = Text
type UserDisplayName = Text
type UserSurname = Text
type UserMatriculation = Text
type StudyDegreeName = Text
type StudyDegreeShorthand = Text
type StudyDegreeKey = Int
type StudyTermsName = Text
type StudyTermsShorthand = Text
type StudyTermsKey = Int
type SchoolName = CI Text
type SchoolShorthand = CI Text
type CourseName = CI Text

View File

@ -9,7 +9,6 @@ module Model.Types.DateTime
) where
import Import.NoModel
import Control.Lens
import qualified Data.Text as Text
import qualified Data.CaseInsensitive as CI

View File

@ -14,7 +14,6 @@ import Model.Types.Common
import qualified Data.Text as Text
import Control.Lens hiding (universe)
import Utils.Lens.TH
import qualified Data.Csv as Csv

View File

@ -8,7 +8,6 @@ module Model.Types.Misc
) where
import Import.NoModel
import Control.Lens
import Data.Maybe (fromJust)

View File

@ -11,8 +11,6 @@ import Import.NoModel
import Model.Types.Common
import Utils.Lens.TH
import Control.Lens
import Control.Lens.Extras (is)
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
import Data.Set (Set)

View File

@ -20,8 +20,6 @@ import Data.Word.Word24
import qualified Data.CaseInsensitive as CI
import Control.Lens
import qualified Data.Text as Text
import qualified Data.Set as Set

View File

@ -44,8 +44,6 @@ import qualified Data.Text.Encoding as Text
import qualified Ldap.Client as Ldap
import Control.Lens
import qualified Network.HaskellNet.Auth as HaskellNet (UserName, Password, AuthType(..))
import qualified Network.Socket as HaskellNet (PortNumber(..), HostName)
import qualified Network

View File

@ -59,6 +59,8 @@ data Icon
| IconApplyTrue
| IconApplyFalse
| IconNoCorrectors
| IconApplicationVeto
| IconApplicationFiles
deriving (Eq, Ord, Enum, Bounded, Show, Read)
iconText :: Icon -> Text
@ -92,6 +94,8 @@ iconText = \case
IconApplyTrue -> "file-alt"
IconApplyFalse -> "trash"
IconNoCorrectors -> "user-slash"
IconApplicationVeto -> "times"
IconApplicationFiles -> "file-alt"
instance Universe Icon
instance Finite Icon

View File

@ -3,13 +3,18 @@
module Utils.Lens ( module Utils.Lens ) where
import ClassyPrelude.Yesod hiding (HasHttpManager(..))
import qualified ClassyPrelude.Yesod as Yesod (HasHttpManager(..))
import Import.NoModel
import Model
import qualified ClassyPrelude.Yesod as Yesod (HasHttpManager(..))
import Control.Lens as Utils.Lens hiding ((<.>), universe, snoc)
import Control.Lens as Utils.Lens
hiding ( (<.>)
, universe
, cons, uncons, snoc, unsnoc, (<|)
, Index, index, (<.)
)
import Control.Lens.Extras as Utils.Lens (is)
import Utils.Lens.TH as Utils.Lens (makeLenses_, makeClassyFor_)
import Utils.Lens.TH as Utils.Lens
import Data.Set.Lens as Utils.Lens
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))
@ -42,6 +47,8 @@ _SchoolId = iso unSchoolKey SchoolKey
-----------------------------------
-- Lens Definitions for our Types
makeClassyFor_ ''Term
-- makeLenses_ ''Course
makeClassyFor_ ''Course

View File

@ -1,10 +1,17 @@
module Utils.Lens.TH where
module Utils.Lens.TH
( makeLenses_, makeClassyFor_
, multifocusG, multifocusL
) where
import ClassyPrelude (Maybe(..), (<>))
import ClassyPrelude
import Control.Lens
import Control.Lens.Internal.FieldTH
import Language.Haskell.TH
import Numeric.Natural
import Data.Foldable (Foldable(foldl))
-- import Control.Lens.Misc
{-
NOTE: The code for lensRules_ and makeLenses_ was stolen from package lens-misc-0.0.2.0,
@ -65,3 +72,47 @@ makeClassyFor_ recName = makeFieldOptics (classyRulesFor_ clNamer) recName
clNamer :: ClassyNamer
-- clNamer _ = Just (clsName, funName) -- for newer versions >= 4.17
clNamer _ = Just (mkName clsName, mkName funName)
multifocusG :: Natural -> ExpQ
multifocusG = multifocusOptic
[e|to . view|]
(\s a -> [t|Getting $(a) $(s) $(a)|])
(\s a -> [t|Getter $(s) $(a)|])
(\doGet _doSet -> [e|to $(doGet)|])
multifocusL :: Natural -> ExpQ
multifocusL = multifocusOptic
[e|cloneLens|]
(\s a -> [t|ALens' $(s) $(a)|])
(\s a -> [t|Lens' $(s) $(a)|])
(\doGet doSet -> [e|lens $(doGet) $(doSet)|])
multifocusOptic :: _ -> _ -> _ -> _ -> Natural -> ExpQ
multifocusOptic _ _ _ _ 0 = [e|united|]
multifocusOptic doClone _ _ _ 1 = doClone
multifocusOptic doClone alensT lensT lens' (fromIntegral -> n) = do
ll <- newName "l"
ls <- replicateM n $ newName "l"
s <- newName "s"
xs <- replicateM n $ newName "x"
tS <- newName "s"
tXs <- replicateM n $ newName "x" :: Q [Name]
let tup = foldl (\t x -> [t|$(t) $(varT x)|]) (tupleT (length tXs)) tXs
mkL x = alensT (varT tS) (varT x)
letE
[ sigD ll $ foldr (\x t -> [t|$(mkL x) -> $(t)|]) (lensT (varT tS) tup) tXs
, funD ll
[ clause
(map (viewP doClone . varP) ls)
(normalB $ lens'
(lamE [varP s] . tupE . flip map ls $ \l -> [e| $(varE s) ^. $(varE l) |])
(lamE [varP s, tupP $ map varP xs] . foldr (\(x, l) x' -> [e|$(x') & $(varE l) .~ $(varE x)|]) (varE s) $ zip xs ls)
)
[]
]
]
(varE ll)

View File

@ -23,7 +23,7 @@ setSerializable act = setSerializable' (0 :: Integer)
let
delay :: NominalDiffTime
delay = 1e-3 * 2 ^ logBackoff
$logWarnS "Sql" $ tshow (delay, e)
$logDebugS "Sql" $ tshow (delay, e)
transactionUndo
threadDelay . round $ delay * 1e6
setSerializable' (succ logBackoff)

View File

@ -126,7 +126,7 @@ $if not (null occurrences)
$maybe desc <- examOccurrenceDescription
#{desc}
$if gradingShown && not (null parts)
$if gradingShown && not (null examParts)
<section>
<h2>
_{MsgExamParts}
@ -139,7 +139,7 @@ $if gradingShown && not (null parts)
<th .table__th>_{MsgExamPartMaxPoints}
<th .table__th>_{MsgExamPartResultPoints}
<tbody>
$forall Entity partId ExamPart{examPartName, examPartWeight, examPartMaxPoints} <- parts
$forall Entity partId ExamPart{examPartName, examPartWeight, examPartMaxPoints} <- examParts
<tr .table__row>
<td .table__td>#{examPartName}
<td .table__td>

View File

@ -3,14 +3,16 @@ $if null rows && (dbsEmptyStyle == DBESNoHeading)
_{dbsEmptyMessage}
$else
<div .table-header>
<div .table__row-count>
_{MsgRowCount rowCount}
$if rowCount > 5
<div .table__row-count>
_{MsgRowCount rowCount}
^{table}
<div .table-footer>
<div .table__row-count>
_{MsgRowCount rowCount}
$if rowCount > 5
<div .table__row-count>
_{MsgRowCount rowCount}
$# Since the current pagesize is always a member of pagesizeOptions we don't need to check `pageCount > 1`
$if toEnum (fromIntegral rowCount) > minimum (pagesizeOptions referencePagesize)
^{pagesizeWdgt'}