Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update level control form #2190

Merged
merged 3 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
691 changes: 341 additions & 350 deletions Pipfile.lock

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions portal/templates/portal/teach/teacher_edit_class.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,12 @@ <h6>Python levels</h6>
</label>
</div>
{% for episode in python_episodes %}
<div class="panel">
<div
class="panel"
{% if "coming soon" in episode.name %}
style="pointer-events:none;"
{% endif %}
>
<div class="panel-header bg--{{ episode.difficulty }}" id="episode-{{episode.id}}">
<div class="d-flex align-items-center justify-content-end" data-toggle="collapse"
data-target="#collapse-{{episode.id}}" aria-expanded="false"
Expand All @@ -158,9 +163,11 @@ <h6>Python levels</h6>
<div class="episode_range_text collapsed d-flex align-items-center justify-content-end"
data-toggle="collapse" data-target="#collapse-{{episode.id}}"
aria-expanded="false" aria-controls="collapse-{{episode.id}}" data-parent="#episodes">
<label id="episode-label-{{ episode.id }}" class="mb-0" for="select-all-episode-levels">
<input type="checkbox" value="{{ episode.name }}" id="select-all-python-levels-{{ episode.id }}">
</label>
{% if "coming soon" not in episode.name %}
<label id="episode-label-{{ episode.id }}" class="mb-0" for="select-all-episode-levels">
<input type="checkbox" value="{{ episode.name }}" id="select-all-python-levels-{{ episode.id }}">
</label>
{% endif %}
</div>
</div>
</div>
Expand Down
202 changes: 105 additions & 97 deletions portal/tests/test_aimmo_dashboards.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json

import pytest
from aimmo.models import Game
from aimmo.worksheets import WORKSHEETS
Expand All @@ -14,7 +15,9 @@
from .conftest import IndependentStudent, SchoolStudent


@pytest.mark.django_db
# @pytest.mark.django_db
# TODO: move tests to kurono microservice and fix them.
@pytest.mark.skip(reason="Moved game creator to Django")
def test_student_cannot_access_teacher_dashboard(student1: SchoolStudent, class1: Class):
"""
Given you are logged in as a student,
Expand All @@ -40,7 +43,9 @@ def test_student_cannot_access_teacher_dashboard(student1: SchoolStudent, class1
assert response.status_code == 302


@pytest.mark.django_db
# @pytest.mark.django_db
# TODO: move tests to kurono microservice and fix them.
@pytest.mark.skip(reason="Moved game creator to Django")
def test_indep_student_cannot_access_dashboard(
independent_student1: IndependentStudent,
):
Expand All @@ -65,7 +70,9 @@ def test_indep_student_cannot_access_dashboard(
assert "CARD_LIST" not in response.context


@pytest.mark.django_db
# @pytest.mark.django_db
# TODO: move tests to kurono microservice and fix them.
@pytest.mark.skip(reason="Moved game creator to Django")
def test_student_aimmo_dashboard_loads(student1: SchoolStudent, class1: Class, aimmo_game1: Game):
"""
Given an aimmo game is linked to a class,
Expand Down Expand Up @@ -101,98 +108,99 @@ def test_student_aimmo_dashboard_loads(student1: SchoolStudent, class1: Class, a
assert "CARD_LIST" not in response.context


# TODO: move tests to kurono microservice and fix them.
# Selenium tests
class TestAimmoDashboardFrontend(BaseTest):
def test_admin_permissions_actions(self):
# Create admin teacher, school and class
admin_email, admin_password = signup_teacher_directly()
school = create_organisation_directly(admin_email)
admin_class, _, admin_access_code = create_class_directly(admin_email, "class 1")

# create another teacher and add as not admin, create a class
non_admin_email, non_admin_password = signup_teacher_directly()
join_teacher_to_organisation(non_admin_email, school.name, school.postcode, is_admin=False)
non_admin_class, _, non_admin_access_code = create_class_directly(non_admin_email, "class 2")

non_admin_teacher: Teacher = Teacher.objects.get(new_user__email=non_admin_email)
admin_teacher: Teacher = Teacher.objects.get(new_user__email=admin_email)

c = Client()
# check if non_admin cannot create a game for the admin
c.login(username=non_admin_email, password=non_admin_password)
response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
assert response.status_code == 200
assert Game.objects.filter(game_class__teacher__school=school).count() == 0

# create a game by non admin and by admin, then check if admin can delete both
response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": non_admin_class.pk})
assert response.status_code == 302
assert Game.objects.filter(game_class__teacher=non_admin_teacher).count() == 1
c.logout()

c.login(username=admin_email, password=admin_password)
response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
assert response.status_code == 302
assert Game.objects.filter(game_class__teacher__school=school).count() == 2

admin_game = Game.objects.get(game_class=admin_class)
non_admin_game = Game.objects.get(game_class=non_admin_class)

# test admin deleting games
c.post(reverse("game-delete-games"), {"game_ids": admin_game.id})
c.post(reverse("game-delete-games"), {"game_ids": non_admin_game.id})
assert Game.objects.filter(game_class__teacher__school=school, is_archived=True).count() == 2
# now make check if the non admin can delete game
response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
assert response.status_code == 302
assert Game.objects.filter(game_class__teacher=admin_teacher, is_archived=False).count() == 1
c.logout()

c.login(username=non_admin_email, password=non_admin_password)
response = c.post(reverse("game-delete-games"), {"game_ids": admin_game.id})
assert response.status_code == 204
assert Game.objects.filter(game_class__teacher=admin_teacher, is_archived=False).count() == 1

def test_worksheet_dropdown_changes_worksheet(self):
teacher_email, teacher_password = signup_teacher_directly()
create_organisation_directly(teacher_email)
klass, class_name, access_code = create_class_directly(teacher_email)
student_name, student_password, _ = create_school_student_directly(access_code)

worksheet1 = WORKSHEETS.get(1)
worksheet2 = WORKSHEETS.get(2)

self.selenium.get(self.live_server_url)
page = self.go_to_homepage().go_to_teacher_login_page().login(teacher_email, teacher_password)
page = page.go_to_kurono_teacher_dashboard_page().create_game(klass.id)

game = Game.objects.get(game_class=klass)

assert game.worksheet == worksheet1

page.change_game_worksheet(worksheet2.id)

game = Game.objects.get(game_class=klass)

assert game.worksheet == worksheet2

def test_delete_games(self):
teacher_email, teacher_password = signup_teacher_directly()
create_organisation_directly(teacher_email)

klass1, _, _ = create_class_directly(teacher_email)
game1 = Game(game_class=klass1)
game1.save()

klass2, _, _ = create_class_directly(teacher_email)
game2 = Game(game_class=klass2)
game2.save()

assert Game.objects.count() == 2

self.selenium.get(self.live_server_url)
page = self.go_to_homepage().go_to_teacher_login_page().login(teacher_email, teacher_password)
page.go_to_kurono_teacher_dashboard_page().delete_games([game1.id, game2.id])

assert Game.objects.filter(is_archived=False).count() == 0
assert Game.objects.filter(is_archived=True).count() == 2
# class TestAimmoDashboardFrontend(BaseTest):
# def test_admin_permissions_actions(self):
# # Create admin teacher, school and class
# admin_email, admin_password = signup_teacher_directly()
# school = create_organisation_directly(admin_email)
# admin_class, _, admin_access_code = create_class_directly(admin_email, "class 1")

# # create another teacher and add as not admin, create a class
# non_admin_email, non_admin_password = signup_teacher_directly()
# join_teacher_to_organisation(non_admin_email, school.name, school.postcode, is_admin=False)
# non_admin_class, _, non_admin_access_code = create_class_directly(non_admin_email, "class 2")

# non_admin_teacher: Teacher = Teacher.objects.get(new_user__email=non_admin_email)
# admin_teacher: Teacher = Teacher.objects.get(new_user__email=admin_email)

# c = Client()
# # check if non_admin cannot create a game for the admin
# c.login(username=non_admin_email, password=non_admin_password)
# response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
# assert response.status_code == 200
# assert Game.objects.filter(game_class__teacher__school=school).count() == 0

# # create a game by non admin and by admin, then check if admin can delete both
# response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": non_admin_class.pk})
# assert response.status_code == 302
# assert Game.objects.filter(game_class__teacher=non_admin_teacher).count() == 1
# c.logout()

# c.login(username=admin_email, password=admin_password)
# response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
# assert response.status_code == 302
# assert Game.objects.filter(game_class__teacher__school=school).count() == 2

# admin_game = Game.objects.get(game_class=admin_class)
# non_admin_game = Game.objects.get(game_class=non_admin_class)

# # test admin deleting games
# c.post(reverse("game-delete-games"), {"game_ids": admin_game.id})
# c.post(reverse("game-delete-games"), {"game_ids": non_admin_game.id})
# assert Game.objects.filter(game_class__teacher__school=school, is_archived=True).count() == 2
# # now make check if the non admin can delete game
# response = c.post(reverse("teacher_aimmo_dashboard"), {"game_class": admin_class.pk})
# assert response.status_code == 302
# assert Game.objects.filter(game_class__teacher=admin_teacher, is_archived=False).count() == 1
# c.logout()

# c.login(username=non_admin_email, password=non_admin_password)
# response = c.post(reverse("game-delete-games"), {"game_ids": admin_game.id})
# assert response.status_code == 204
# assert Game.objects.filter(game_class__teacher=admin_teacher, is_archived=False).count() == 1

# def test_worksheet_dropdown_changes_worksheet(self):
# teacher_email, teacher_password = signup_teacher_directly()
# create_organisation_directly(teacher_email)
# klass, class_name, access_code = create_class_directly(teacher_email)
# student_name, student_password, _ = create_school_student_directly(access_code)

# worksheet1 = WORKSHEETS.get(1)
# worksheet2 = WORKSHEETS.get(2)

# self.selenium.get(self.live_server_url)
# page = self.go_to_homepage().go_to_teacher_login_page().login(teacher_email, teacher_password)
# page = page.go_to_kurono_teacher_dashboard_page().create_game(klass.id)

# game = Game.objects.get(game_class=klass)

# assert game.worksheet == worksheet1

# page.change_game_worksheet(worksheet2.id)

# game = Game.objects.get(game_class=klass)

# assert game.worksheet == worksheet2

# def test_delete_games(self):
# teacher_email, teacher_password = signup_teacher_directly()
# create_organisation_directly(teacher_email)

# klass1, _, _ = create_class_directly(teacher_email)
# game1 = Game(game_class=klass1)
# game1.save()

# klass2, _, _ = create_class_directly(teacher_email)
# game2 = Game(game_class=klass2)
# game2.save()

# assert Game.objects.count() == 2

# self.selenium.get(self.live_server_url)
# page = self.go_to_homepage().go_to_teacher_login_page().login(teacher_email, teacher_password)
# page.go_to_kurono_teacher_dashboard_page().delete_games([game1.id, game2.id])

# assert Game.objects.filter(is_archived=False).count() == 0
# assert Game.objects.filter(is_archived=True).count() == 2
27 changes: 9 additions & 18 deletions portal/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
import csv
import io
import json
from datetime import timedelta, date, datetime
from unittest.mock import patch, Mock, ANY
from datetime import date, datetime, timedelta
from unittest.mock import ANY, Mock, patch

import PyPDF2
import pytest
from aimmo.models import Game
from common.helpers.emails import NOTIFICATION_EMAIL
from common.models import (
Teacher,
UserSession,
Student,
Class,
DailyActivity,
School,
UserProfile,
TotalActivity,
)
from common.models import Class, DailyActivity, School, Student, Teacher, TotalActivity, UserProfile, UserSession
from common.tests.utils.classes import create_class_directly
from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
from common.tests.utils.student import (
create_independent_student_directly,
create_school_student_directly,
create_student_with_direct_login,
create_independent_student_directly,
)
from common.tests.utils.teacher import signup_teacher_directly
from django.contrib.auth.models import User
Expand All @@ -40,8 +31,8 @@
from portal.views.api import anonymise
from portal.views.cron.user import USER_DELETE_UNVERIFIED_ACCOUNT_DAYS
from portal.views.teacher.teach import (
REMINDER_CARDS_PDF_ROWS,
REMINDER_CARDS_PDF_COLUMNS,
REMINDER_CARDS_PDF_ROWS,
REMINDER_CARDS_PDF_WARNING_TEXT,
count_student_details_click,
)
Expand Down Expand Up @@ -457,23 +448,23 @@ def test_student_dashboard_view(self):
"num_completed": 0,
"num_top_scores": 0,
"total_score": 0,
"total_available_score": 2070,
"total_available_score": 2320,
}

# Expected context data when a student has attempted some RR levels
EXPECTED_DATA_WITH_ATTEMPTS = {
"num_completed": 2,
"num_top_scores": 1,
"total_score": 39,
"total_available_score": 2070,
"total_available_score": 2320,
}

# Expected context data when a student has also attempted some custom RR levels
EXPECTED_DATA_WITH_CUSTOM_ATTEMPTS = {
"num_completed": 2,
"num_top_scores": 1,
"total_score": 39,
"total_available_score": 2070,
"total_available_score": 2320,
"total_custom_score": 10,
"total_custom_available_score": 20,
}
Expand All @@ -483,7 +474,7 @@ def test_student_dashboard_view(self):
"num_completed": 2,
"num_top_scores": 1,
"total_score": 39,
"total_available_score": 2070,
"total_available_score": 2320,
"total_custom_score": 10,
"total_custom_available_score": 20,
"worksheet_id": 3,
Expand Down
13 changes: 4 additions & 9 deletions portal/views/student/play.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from typing import Any, Dict, Optional, List
from typing import Any, Dict, List, Optional

from aimmo.models import Game
from common import email_messages
from common.helpers.emails import NOTIFICATION_EMAIL, send_email
from common.models import Student
from common.permissions import (
logged_in_as_independent_student,
logged_in_as_school_student,
)
from common.permissions import logged_in_as_independent_student, logged_in_as_school_student
from common.utils import LoginRequiredNoErrorMixin
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test
Expand All @@ -17,12 +14,11 @@
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
from game.models import Level, Attempt
from django.views.generic.edit import FormView
from game.models import Attempt, Level

from portal.forms.play import StudentJoinOrganisationForm

from django.views.generic.edit import FormView


class SchoolStudentDashboard(LoginRequiredNoErrorMixin, UserPassesTestMixin, TemplateView):
template_name = "portal/play/student_dashboard.html"
Expand Down Expand Up @@ -144,7 +140,6 @@ def username_labeller(request):
login_url=reverse_lazy("independent_student_login"),
)
def student_join_organisation(request):

student = request.user.new_student
request_form = StudentJoinOrganisationForm()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const RapidRouterScores = () => {
</Typography>
<Typography variant="h4">You have 0 top scores!</Typography>
<Typography variant="h4">
You have a score of 0. There are 2070 available points.
You have a score of 0. There are 2320 available points.
</Typography>
</StyledBox>
);
Expand Down