Skip to content

Commit

Permalink
Merge pull request #16593 from yafred/class-move-student
Browse files Browse the repository at this point in the history
Move a student to another class
  • Loading branch information
ornicar authored Dec 18, 2024
2 parents 5a994b1 + dda89a4 commit 2ade9c1
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 4 deletions.
18 changes: 18 additions & 0 deletions app/controllers/Clas.scala
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,24 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
else redirectTo(clas)
}

def studentMove(id: ClasId, username: UserStr) = Secure(_.Teacher) { ctx ?=> me ?=>
WithClassAndStudents(id): (clas, students) =>
WithStudent(clas, username): s =>
for
classes <- env.clas.api.clas.of(me)
others = classes.filter(_.id != clas.id)
res <- Ok.page(views.clas.student.move(clas, students, s, others))
yield res
}

def studentMovePost(id: ClasId, username: UserStr, to: ClasId) = SecureBody(_.Teacher) { ctx ?=> me ?=>
WithClassAndStudents(id): (clas, students) =>
WithStudent(clas, username): s =>
WithClass(to): toClas =>
for _ <- env.clas.api.student.move(s, toClas)
yield Redirect(routes.Clas.show(clas.id)).flashSuccess
}

def becomeTeacher = AuthBody { ctx ?=> me ?=>
couldBeTeacher.elseNotFound:
val perm = lila.core.perm.Permission.Teacher.dbKey
Expand Down
2 changes: 1 addition & 1 deletion app/views/clas.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object student:
lazy val formUi = lila.clas.ui.StudentFormUi(helpers, views.clas.ui, ui)

export ui.{ invite }
export formUi.{ newStudent as form, many as manyForm, edit, release, close }
export formUi.{ newStudent as form, many as manyForm, edit, release, close, move }

def show(
clas: Clas,
Expand Down
2 changes: 2 additions & 0 deletions conf/clas.routes
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ POST /class/$id<\w{8}>/student/:username/release controllers.clas.Clas.studentR
GET /class/$id<\w{8}>/student/:username/close controllers.clas.Clas.studentClose(id: ClasId, username: UserStr)
POST /class/$id<\w{8}>/student/:username/close controllers.clas.Clas.studentClosePost(id: ClasId, username: UserStr)
POST /class/$id<\w{8}>/invitation/revoke controllers.clas.Clas.invitationRevoke(id: ClasInviteId)
GET /class/$id<\w{8}>/student/:username/move controllers.clas.Clas.studentMove(id: ClasId, username: UserStr)
POST /class/$id<\w{8}>/student/:username/move/$to<\w{8}> controllers.clas.Clas.studentMovePost(id: ClasId, username: UserStr, to: ClasId)
11 changes: 11 additions & 0 deletions modules/clas/src/main/ClasApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@ final class ClasApi(
sendWelcomeMessage(teacher.id, user, clas)).inject(Student.WithPassword(student, password))
}

def move(s: Student.WithUser, toClas: Clas)(using teacher: Me): Fu[Option[Student]] = for
_ <- closeAccount(s)
stu = s.student.copy(id = Student.makeId(s.user.id, toClas.id), clasId = toClas.id)
moved <- colls.student.insert
.one(stu)
.inject(stu.some)
.recoverWith(lila.db.recoverDuplicateKey { _ =>
student.get(toClas, s.user.id)
})
yield moved

def manyCreate(
clas: Clas,
data: ClasForm.ManyNewStudent,
Expand Down
6 changes: 4 additions & 2 deletions modules/clas/src/main/ui/DashboardUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,13 @@ final class DashboardUi(helpers: Helpers, ui: ClasUi)(using NetDomain):
tr(
td(userIdLink(i.userId.some)),
td(i.realName),
td(if i.accepted.has(false) then "Declined" else "Pending"),
td(
if i.accepted.has(false) then trans.clas.declined.txt() else trans.clas.pending.txt()
),
td(momentFromNow(i.created.at)),
td:
postForm(action := routes.Clas.invitationRevoke(i.id)):
submitButton(cls := "button button-red button-empty")("Revoke")
submitButton(cls := "button button-red button-empty")(trans.site.delete())
)
)
val archivedBox =
Expand Down
32 changes: 31 additions & 1 deletion modules/clas/src/main/ui/StudentFormUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,11 @@ final class StudentFormUi(helpers: Helpers, clasUi: ClasUi, studentUi: StudentUi
cls := "button button-empty button-red",
title := trans.clas.closeDesc1.txt()
)(trans.clas.closeStudent())
)
),
a(
href := routes.Clas.studentMove(clas.id, s.user.username),
cls := "button button-empty"
)(trans.clas.moveToAnotherClass())
)
)
)
Expand Down Expand Up @@ -250,6 +254,32 @@ final class StudentFormUi(helpers: Helpers, clasUi: ClasUi, studentUi: StudentUi
)
)

def move(clas: Clas, students: List[Student], s: Student.WithUser, otherClasses: List[Clas])(using
Context
) =
ClasPage(s.user.username.value, Left(clas.withStudents(students)), s.student.some)(
cls := "student-show student-edit"
):

val classForms: Frag = otherClasses.map: toClass =>
postForm(action := routes.Clas.studentMovePost(clas.id, s.student.userId, toClass.id))(
form3.submit(toClass.name, icon = Icon.InternalArrow.some)(
cls := "yes-no-confirm button-blue button-empty",
title := trans.clas.moveToClass.txt(toClass.name)
)
)

frag(
studentUi.top(clas, s),
div(cls := "box__pad")(
h2(trans.clas.moveToAnotherClass()),
classForms,
form3.actions(
a(href := routes.Clas.studentShow(clas.id, s.user.username))(trans.site.cancel())
)
)
)

def close(clas: Clas, students: List[Student], s: Student.WithUser)(using Context) =
ClasPage(s.user.username.value, Left(clas.withStudents(students)), s.student.some)(
cls := "student-show student-edit"
Expand Down
4 changes: 4 additions & 0 deletions modules/coreI18n/src/main/key.scala
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ object I18nKey:
val `welcomeToClass`: I18nKey = "class:welcomeToClass"
val `invitationToClass`: I18nKey = "class:invitationToClass"
val `clickToViewInvitation`: I18nKey = "class:clickToViewInvitation"
val `pending`: I18nKey = "class:pending"
val `declined`: I18nKey = "class:declined"
val `onlyVisibleToTeachers`: I18nKey = "class:onlyVisibleToTeachers"
val `lastActiveDate`: I18nKey = "class:lastActiveDate"
val `managed`: I18nKey = "class:managed"
Expand Down Expand Up @@ -330,6 +332,8 @@ object I18nKey:
val `anInvitationHasBeenSentToX`: I18nKey = "class:anInvitationHasBeenSentToX"
val `xAlreadyHasAPendingInvitation`: I18nKey = "class:xAlreadyHasAPendingInvitation"
val `xIsAKidAccountWarning`: I18nKey = "class:xIsAKidAccountWarning"
val `moveToClass`: I18nKey = "class:moveToClass"
val `moveToAnotherClass`: I18nKey = "class:moveToAnotherClass"
val `nbPendingInvitations`: I18nKey = "class:nbPendingInvitations"
val `nbTeachers`: I18nKey = "class:nbTeachers"
val `nbStudents`: I18nKey = "class:nbStudents"
Expand Down
4 changes: 4 additions & 0 deletions translation/source/class.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Here is the link to access the class.</string>
<item quantity="one">One pending invitation</item>
<item quantity="other">%s pending invitations</item>
</plurals>
<string name="pending">Pending</string>
<string name="declined">Declined</string>
<string name="onlyVisibleToTeachers">Only visible to the class teachers</string>
<string name="lastActiveDate">Active</string>
<string name="managed">Managed</string>
Expand Down Expand Up @@ -114,4 +116,6 @@ It will display a horizontal line.</string>
<string name="anInvitationHasBeenSentToX">An invitation has been sent to %s</string>
<string name="xAlreadyHasAPendingInvitation">%s already has a pending invitation</string>
<string name="xIsAKidAccountWarning">%1$s is a kid account and can't receive your message. You must give them the invitation URL manually: %2$s</string>
<string name="moveToClass">Move to %s</string>
<string name="moveToAnotherClass">Move to another class</string>
</resources>
8 changes: 8 additions & 0 deletions ui/@types/lichess/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ interface I18n {
createMultipleAccounts: string;
/** Only create accounts for real students. Do not use this to make multiple accounts for yourself. You would get banned. */
createStudentWarning: string;
/** Declined */
declined: string;
/** Edit news */
editNews: string;
/** Features */
Expand Down Expand Up @@ -515,6 +517,10 @@ interface I18n {
maxStudentsNote: I18nFormat;
/** Message all students about new class material */
messageAllStudents: string;
/** Move to another class */
moveToAnotherClass: string;
/** Move to %s */
moveToClass: I18nFormat;
/** You can also %s to create multiple Lichess accounts from a list of student names. */
multipleAccsFormDescription: I18nFormat;
/** N/A */
Expand Down Expand Up @@ -555,6 +561,8 @@ interface I18n {
overview: string;
/** Password: %s */
passwordX: I18nFormat;
/** Pending */
pending: string;
/** Private. Will never be shown outside the class. Helps you remember who the student is. */
privateWillNeverBeShown: string;
/** Progress */
Expand Down

0 comments on commit 2ade9c1

Please sign in to comment.