diff --git a/portal/static/portal/img/howe_dell_1.png b/portal/static/portal/img/howe_dell_1.png new file mode 100644 index 000000000..120672c22 Binary files /dev/null and b/portal/static/portal/img/howe_dell_1.png differ diff --git a/portal/static/portal/img/howe_dell_2.png b/portal/static/portal/img/howe_dell_2.png new file mode 100644 index 000000000..64a4e1329 Binary files /dev/null and b/portal/static/portal/img/howe_dell_2.png differ diff --git a/portal/static/portal/img/howe_dell_3.png b/portal/static/portal/img/howe_dell_3.png new file mode 100644 index 000000000..b24c29097 Binary files /dev/null and b/portal/static/portal/img/howe_dell_3.png differ diff --git a/portal/static/portal/img/long_europe_map.png b/portal/static/portal/img/long_europe_map.png new file mode 100644 index 000000000..491fb61b5 Binary files /dev/null and b/portal/static/portal/img/long_europe_map.png differ diff --git a/portal/static/portal/img/ten_year_map_pin.svg b/portal/static/portal/img/ten_year_map_pin.svg new file mode 100644 index 000000000..232e86eb8 --- /dev/null +++ b/portal/static/portal/img/ten_year_map_pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/portal/static/portal/js/tenYearMap.js b/portal/static/portal/js/tenYearMap.js new file mode 100644 index 000000000..83ab43101 --- /dev/null +++ b/portal/static/portal/js/tenYearMap.js @@ -0,0 +1,14 @@ +let currentActivePin; + +function setActivePin(city) { + if (currentActivePin) { + currentActivePin.setAttribute("fill", "#FFC709"); + } + + currentActivePin = document.getElementById(city + "-pin").getElementsByTagName("svg")[0] + currentActivePin.setAttribute("fill", "#EE0857"); +} + +$(document).ready(function () { + setActivePin("hatfield"); +}); diff --git a/portal/static/portal/sass/partials/_carousel.scss b/portal/static/portal/sass/partials/_carousel.scss index 2b39d0bd3..5542fef03 100644 --- a/portal/static/portal/sass/partials/_carousel.scss +++ b/portal/static/portal/sass/partials/_carousel.scss @@ -13,6 +13,7 @@ &.next, &.prev { display: flex; + flex-wrap: wrap; } > .carousel-image-wrapper { @@ -155,3 +156,23 @@ /* shrink padding because cards have their own */ @include _padding(0px, 2px, 0px, 2px); } + +.carousel-button { + margin-top: 2rem; +} + +.carousel-image { + margin-top: 15px; +} + +.carousel-image--column { + padding-right: 20px; +} + +.carousel-column--images { + margin-top: -15px; +} + +.carousel-header { + margin-top: 0rem !important; +} diff --git a/portal/static/portal/sass/partials/_images.scss b/portal/static/portal/sass/partials/_images.scss index de16702ae..155729d0c 100644 --- a/portal/static/portal/sass/partials/_images.scss +++ b/portal/static/portal/sass/partials/_images.scss @@ -73,6 +73,287 @@ img { content: url("../img/logo_cfl.png"); } +.map-pin { + position: absolute; + background: 0 0; + min-width: 0px !important; +} + +#map-container { + position: relative; +} + +#hatfield { + left: 34.5%; + top: 26.7%; +} + +#barcelona { + left: 38%; + top: 58.8%; +} + +#krakow { + left: 61.7%; + top: 30.3%; +} + +#sofia { + left: 68.5%; + top: 55.5%; +} + +@media only screen and (max-width: 1400px) { + #sofia { + left: 68.8%; + top: 55.8%; + } +} + +@media only screen and (max-width: 1200px) { + #hatfield { + left: 33.5%; + top: 25.1%; + } + + #barcelona { + left: 36.7%; + top: 56.4%; + } + + #krakow { + left: 60.7%; + top: 28.7%; + } + + #sofia { + left: 68%; + top: 53.5%; + } +} + +@media only screen and (max-width: 992px) { + #hatfield { + left: 32.7%; + top: 22.8%; + } + + #barcelona { + left: 35.7%; + top: 54.6%; + } + + #krakow { + left: 59.7%; + top: 26.3%; + } + + #sofia { + left: 67%; + top: 50.2%; + } +} + +@media only screen and (max-width: 767px) { + #barcelona { + left: 36.7%; + top: 54%; + } + + #sofia { + left: 67.5%; + top: 51.2%; + } +} + +@media only screen and (max-width: 700px) { + #hatfield { + left: 32.5%; + top: 20.8%; + } + + #barcelona { + left: 35.4%; + top: 53.5%; + } + + #krakow { + left: 59.7%; + top: 25.3%; + } + + #sofia { + left: 67%; + top: 50.2%; + } +} + +@media only screen and (max-width: 650px) { + #barcelona { + left: 35.4%; + top: 52.5%; + } + + #sofia { + left: 66%; + top:48.2%; + } +} + +@media only screen and (max-width: 600px) { + #hatfield { + left: 31.5%; + top: 18.8%; + } + + #barcelona { + left: 34.4%; + top: 50.5%; + } + + #krakow { + left: 58.7%; + top: 23.3%; + } + + #sofia { + left: 66%; + top: 48.2%; + } +} + +@media only screen and (max-width: 550px) { + #hatfield { + left: 30.5%; + top: 17.3%; + } + + #barcelona { + left: 33.4%; + top: 49%; + } + + #krakow { + left: 57.7%; + top: 21.6%; + } + + #sofia { + left: 65%; + top: 46%; + } +} + +@media only screen and (max-width: 500px) { + #hatfield { + left: 29.8%; + top: 16.3%; + } + + #barcelona { + left: 33%; + top: 47%; + } + + #krakow { + left: 57%; + top: 20%; + } + + #sofia { + left: 64%; + top: 44%; + } +} + +@media only screen and (max-width: 450px) { + #hatfield { + left: 28.7%; + top: 17%; + } + + #barcelona { + left: 32%; + top: 44.5%; + } + + #krakow { + left: 56%; + top: 21.5%; + } + + #sofia { + left: 63%; + top: 42%; + } +} + +@media only screen and (max-width: 430px) { + #hatfield { + left: 28.5%; + top: 17%; + } + + #barcelona { + left: 31.5%; + top: 42.5%; + } + + #krakow { + left: 55%; + top: 19.5%; + } + + #sofia { + left: 62%; + top: 40.5%; + } +} + +@media only screen and (max-width: 390px) { + #hatfield { + left: 27.8%; + top: 15.5%; + } + + #barcelona { + left: 31%; + top: 40.5%; + } + + #krakow { + left: 52.5%; + top: 19%; + } + + #sofia { + left: 61%; + top: 39%; + } +} + +@media only screen and (max-width: 360px) { + #hatfield { + left: 27.5%; + top: 14.5%; + } + + #barcelona { + left: 30.5%; + top: 40%; + } + + #krakow { + left: 53%; + top: 18%; + } + + #sofia { + left: 60%; + top: 35.5%; + } +} + .glyphicon { @include _font-size(30px); font-weight: 500; diff --git a/portal/strings/ten_year_map.py b/portal/strings/ten_year_map.py new file mode 100644 index 000000000..0100d7145 --- /dev/null +++ b/portal/strings/ten_year_map.py @@ -0,0 +1,13 @@ +TEN_YEAR_MAP_BANNER = { + "title": "Celebrate", + "subtitle": "Join us in celebrating our 10 year anniversary", + "text": "", + "image_class": "banner--picture--educate", + "alt": "Female teacher helping young female student on a laptop", +} + +TEN_YEAR_MAP_HEADLINE = { + "title": "Code for Life is turning 10!", + "description": "For our 10th anniversary, we're going global! Click on the pins on the map " + "to check out what events are being run by our amazing volunteers from around the world.", +} diff --git a/portal/templates/portal/ten_year_map.html b/portal/templates/portal/ten_year_map.html new file mode 100644 index 000000000..9eec66bca --- /dev/null +++ b/portal/templates/portal/ten_year_map.html @@ -0,0 +1,147 @@ +{% extends 'portal/base.html' %} +{% load static %} +{% load app_tags banner_tags headline_tags character_list_tags %} + +{% block subNav %} +{% banner banner_name="BANNER" %} +{% endblock subNav %} + +{% block scripts %} +{{ block.super }} + +{% endblock scripts %} + +{% block content %} + +
+ {% headline headline_name="HEADLINE" %} +
+ +
+
+ World map + + + + +
+
+ +
+
+
+
+ +
+
+
+
+ + + +{% endblock content %} diff --git a/portal/tests/test_views.py b/portal/tests/test_views.py index a512449f5..e87fcbb34 100644 --- a/portal/tests/test_views.py +++ b/portal/tests/test_views.py @@ -55,9 +55,7 @@ def setUpTestData(cls): cls.email, cls.password = signup_teacher_directly() cls.school = create_organisation_directly(cls.email) _, _, cls.class_access_code = create_class_directly(cls.email) - _, cls.password_student, cls.student = create_school_student_directly( - cls.class_access_code - ) + _, cls.password_student, cls.student = create_school_student_directly(cls.class_access_code) def login(self): c = Client() @@ -66,9 +64,7 @@ def login(self): def test_reminder_cards(self): c = self.login() - url = reverse( - "teacher_print_reminder_cards", args=[self.class_access_code] - ) + url = reverse("teacher_print_reminder_cards", args=[self.class_access_code]) # First test with 2 dummy students NAME1 = "Test name" @@ -102,9 +98,7 @@ def test_reminder_cards(self): # page number students_per_page = REMINDER_CARDS_PDF_ROWS * REMINDER_CARDS_PDF_COLUMNS for _ in range(len(studentlist), students_per_page + 1): - studentlist.append( - {"name": NAME1, "password": PASSWORD1, "login_url": URL} - ) + studentlist.append({"name": NAME1, "password": PASSWORD1, "login_url": URL}) assert len(studentlist) == students_per_page + 1 @@ -143,9 +137,7 @@ def test_csv(self): reader = csv.reader(io.StringIO(content)) access_code = self.class_access_code - class_url = reverse( - "student_login", kwargs={"access_code": access_code} - ) + class_url = reverse("student_login", kwargs={"access_code": access_code}) row0 = next(reader) assert row0[0].strip() == access_code assert class_url in row0[1].strip() @@ -184,9 +176,7 @@ def test_organisation_kick_has_correct_permissions(self): def test_daily_activity_student_details(self): c = self.login() - url = reverse( - "teacher_print_reminder_cards", args=[self.class_access_code] - ) + url = reverse("teacher_print_reminder_cards", args=[self.class_access_code]) data = { "data": json.dumps( @@ -234,9 +224,7 @@ def test_daily_activity_student_details(self): def test_release_verified_student(self): c = Client() - student_login_url = reverse( - "student_login", args=[self.class_access_code] - ) + student_login_url = reverse("student_login", args=[self.class_access_code]) response = c.post( student_login_url, { @@ -252,9 +240,7 @@ def test_release_verified_student(self): c.logout() c.login(username=self.email, password=self.password) - release_url = reverse( - "teacher_dismiss_students", args=[self.class_access_code] - ) + release_url = reverse("teacher_dismiss_students", args=[self.class_access_code]) response = c.post( release_url, { @@ -291,9 +277,7 @@ def _set_up_test_data(self): teacher_email, teacher_password = signup_teacher_directly() create_organisation_directly(teacher_email) _, _, class_access_code = create_class_directly(teacher_email) - student_name, student_password, _ = create_school_student_directly( - class_access_code - ) + student_name, student_password, _ = create_school_student_directly(class_access_code) return ( teacher_email, @@ -326,16 +310,9 @@ def _create_and_login_school_student(self, next_url=False): _, _, name, password, class_access_code = self._set_up_test_data() if next_url: - url = ( - reverse( - "student_login", kwargs={"access_code": class_access_code} - ) - + "?next=/" - ) + url = reverse("student_login", kwargs={"access_code": class_access_code}) + "?next=/" else: - url = reverse( - "student_login", kwargs={"access_code": class_access_code} - ) + url = reverse("student_login", kwargs={"access_code": class_access_code}) c = Client() response = c.post(url, {"username": name, "password": password}) @@ -374,9 +351,7 @@ def test_teacher_session(self): def _get_user_class(self, name, class_access_code): klass = Class.objects.get(access_code=class_access_code) - students = Student.objects.filter( - new_user__first_name__iexact=name, class_field=klass - ) + students = Student.objects.filter(new_user__first_name__iexact=name, class_field=klass) assert len(students) == 1 user = students[0].new_user return user, klass @@ -418,9 +393,7 @@ def test_student_session_class_link(self): _, _, name, password, class_access_code = self._set_up_test_data() c = Client() - url = reverse( - "student_login", kwargs={"access_code": class_access_code} - ) + url = reverse("student_login", kwargs={"access_code": class_access_code}) c.post(url, {"username": name, "password": password}) # check if there's a UserSession data within the last 10 secs @@ -441,9 +414,7 @@ def test_student_login_failed(self): randomname = "randomname" c = Client() - url = reverse( - "student_login", kwargs={"access_code": class_access_code} - ) + url = reverse("student_login", kwargs={"access_code": class_access_code}) c.post(url, {"username": randomname, "password": "xx"}) # check if there's a UserSession data within the last 10 secs @@ -469,9 +440,7 @@ def test_indep_student_session(self): def test_student_direct_login(self): _, _, _, _, class_access_code = self._set_up_test_data() - student, login_id, _, _ = create_student_with_direct_login( - class_access_code - ) + student, login_id, _, _ = create_student_with_direct_login(class_access_code) c = Client() assert c.login(user_id=student.new_user.id, login_id=login_id) == True @@ -542,6 +511,12 @@ def test_contributor(self): response = c.get(page_url) assert response.status_code == 200 + def test_ten_year_map(self): + c = Client() + page_url = reverse("celebrate") + response = c.get(page_url) + assert response.status_code == 200 + def test_student_dashboard_view(self): teacher_email, teacher_password = signup_teacher_directly() create_organisation_directly(teacher_email) @@ -593,9 +568,7 @@ def test_student_dashboard_view(self): c = Client() # Login and check initial data - url = reverse( - "student_login", kwargs={"access_code": class_access_code} - ) + url = reverse("student_login", kwargs={"access_code": class_access_code}) c.post(url, {"username": student_name, "password": student_password}) student_dashboard_url = reverse("student_details") @@ -674,9 +647,7 @@ def test_delete_account(self, mock_send_dotdigital_email: Mock): # try again with the correct password url = reverse("delete_account") - response = c.post( - url, {"password": password, "unsubscribe_newsletter": "on"} - ) + response = c.post(url, {"password": password, "unsubscribe_newsletter": "on"}) assert response.status_code == 302 mock_send_dotdigital_email.assert_called_once() @@ -758,9 +729,7 @@ def test_delete_account_admin(self, mock_send_dotdigital_email: Mock): school_id = school.id school_name = school.name - teachers = Teacher.objects.filter(school=school).order_by( - "new_user__last_name", "new_user__first_name" - ) + teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name") assert len(teachers) == 3 # one of the remaining teachers should be admin (the second in our case, as it's alphabetical) @@ -791,9 +760,7 @@ def test_delete_account_admin(self, mock_send_dotdigital_email: Mock): self.assertEqual(mock_send_dotdigital_email.call_count, 2) # 2 teachers left - teachers = Teacher.objects.filter(school=school).order_by( - "new_user__last_name", "new_user__first_name" - ) + teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name") assert len(teachers) == 2 # teacher2 should still be admin, teacher4 is not passed admin role because there is teacher2 @@ -805,9 +772,7 @@ def test_delete_account_admin(self, mock_send_dotdigital_email: Mock): # delete teacher4 anonymise(user4) - teachers = Teacher.objects.filter(school=school).order_by( - "new_user__last_name", "new_user__first_name" - ) + teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name") assert len(teachers) == 1 u = User.objects.get(id=usrid2) assert u.new_teacher.is_admin @@ -865,17 +830,13 @@ def test_logged_in_as_admin_check(self): c.logout() @patch("common.helpers.emails.send_dotdigital_email") - def test_registrations_increment_data( - self, mock_send_dotdigital_email: Mock - ): + def test_registrations_increment_data(self, mock_send_dotdigital_email: Mock): c = Client() total_activity = TotalActivity.objects.get(id=1) teacher_registration_count = total_activity.teacher_registrations student_registration_count = total_activity.student_registrations - independent_registration_count = ( - total_activity.independent_registrations - ) + independent_registration_count = total_activity.independent_registrations response = c.post( reverse("register"), @@ -895,10 +856,7 @@ def test_registrations_increment_data( total_activity = TotalActivity.objects.get(id=1) - assert ( - total_activity.teacher_registrations - == teacher_registration_count + 1 - ) + assert total_activity.teacher_registrations == teacher_registration_count + 1 response = c.post( reverse("register"), @@ -920,10 +878,7 @@ def test_registrations_increment_data( total_activity = TotalActivity.objects.get(id=1) - assert ( - total_activity.independent_registrations - == independent_registration_count + 1 - ) + assert total_activity.independent_registrations == independent_registration_count + 1 teacher_email, teacher_password = signup_teacher_directly() create_organisation_directly(teacher_email) @@ -939,10 +894,7 @@ def test_registrations_increment_data( total_activity = TotalActivity.objects.get(id=1) - assert ( - total_activity.student_registrations - == student_registration_count + 3 - ) + assert total_activity.student_registrations == student_registration_count + 3 # CRON view tests @@ -961,12 +913,8 @@ def generic( secure=False, **extra, ): - wsgi_response = super().generic( - method, path, data, content_type, secure, **extra - ) - assert ( - 200 <= wsgi_response.status_code < 300 - ), f"Response has error status code: {wsgi_response.status_code}" + wsgi_response = super().generic(method, path, data, content_type, secure, **extra) + assert 200 <= wsgi_response.status_code < 300, f"Response has error status code: {wsgi_response.status_code}" return wsgi_response @@ -985,9 +933,7 @@ def setUp(self): indy_email, _, _ = create_independent_student_directly() self.teacher_user = User.objects.get(email=teacher_email) - self.teacher_user_profile = UserProfile.objects.get( - user=self.teacher_user - ) + self.teacher_user_profile = UserProfile.objects.get(user=self.teacher_user) self.indy_user = User.objects.get(email=indy_email) self.indy_user_profile = UserProfile.objects.get(user=self.indy_user) @@ -1003,17 +949,11 @@ def send_verify_email_reminder( assert_called: bool, mock_send_dotdigital_email: Mock, ): - self.teacher_user.date_joined = timezone.now() - timedelta( - days=days, hours=12 - ) + self.teacher_user.date_joined = timezone.now() - timedelta(days=days, hours=12) self.teacher_user.save() - self.student_user.date_joined = timezone.now() - timedelta( - days=days, hours=12 - ) + self.student_user.date_joined = timezone.now() - timedelta(days=days, hours=12) self.student_user.save() - self.indy_user.date_joined = timezone.now() - timedelta( - days=days, hours=12 - ) + self.indy_user.date_joined = timezone.now() - timedelta(days=days, hours=12) self.indy_user.save() self.teacher_user_profile.is_verified = is_verified @@ -1024,13 +964,9 @@ def send_verify_email_reminder( self.client.get(reverse(view_name)) if assert_called: - mock_send_dotdigital_email.assert_any_call( - ANY, [self.teacher_user.email], personalization_values=ANY - ) + mock_send_dotdigital_email.assert_any_call(ANY, [self.teacher_user.email], personalization_values=ANY) - mock_send_dotdigital_email.assert_any_call( - ANY, [self.indy_user.email], personalization_values=ANY - ) + mock_send_dotdigital_email.assert_any_call(ANY, [self.indy_user.email], personalization_values=ANY) # Check only two emails are sent - the student should never be included. assert mock_send_dotdigital_email.call_count == 2 @@ -1040,40 +976,22 @@ def send_verify_email_reminder( mock_send_dotdigital_email.reset_mock() def test_first_verify_email_reminder_view(self): - self.send_verify_email_reminder( - 6, False, "first-verify-email-reminder", False - ) - self.send_verify_email_reminder( - 7, False, "first-verify-email-reminder", True - ) - self.send_verify_email_reminder( - 7, True, "first-verify-email-reminder", False - ) - self.send_verify_email_reminder( - 8, False, "first-verify-email-reminder", False - ) + self.send_verify_email_reminder(6, False, "first-verify-email-reminder", False) + self.send_verify_email_reminder(7, False, "first-verify-email-reminder", True) + self.send_verify_email_reminder(7, True, "first-verify-email-reminder", False) + self.send_verify_email_reminder(8, False, "first-verify-email-reminder", False) def test_second_verify_email_reminder_view(self): - self.send_verify_email_reminder( - 13, False, "second-verify-email-reminder", False - ) - self.send_verify_email_reminder( - 14, False, "second-verify-email-reminder", True - ) - self.send_verify_email_reminder( - 14, True, "second-verify-email-reminder", False - ) - self.send_verify_email_reminder( - 15, False, "second-verify-email-reminder", False - ) + self.send_verify_email_reminder(13, False, "second-verify-email-reminder", False) + self.send_verify_email_reminder(14, False, "second-verify-email-reminder", True) + self.send_verify_email_reminder(14, True, "second-verify-email-reminder", False) + self.send_verify_email_reminder(15, False, "second-verify-email-reminder", False) def test_anonymise_unverified_accounts_view(self): now = timezone.now() for user in [self.teacher_user, self.indy_user, self.student_user]: - user.date_joined = now - timedelta( - days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1 - ) + user.date_joined = now - timedelta(days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1) user.save() for user_profile in [self.teacher_user_profile, self.indy_user_profile]: @@ -1138,9 +1056,7 @@ def anonymise_unverified_users( new_user=indy_user, ) - activity_today = DailyActivity.objects.get_or_create( - date=datetime.now().date() - )[0] + activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0] daily_teacher_count = activity_today.anonymised_unverified_teachers daily_indy_count = activity_today.anonymised_unverified_independents @@ -1163,30 +1079,16 @@ def anonymise_unverified_users( assert indy_user_active == assert_active assert student_user_active - activity_today = DailyActivity.objects.get_or_create( - date=datetime.now().date() - )[0] + activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0] total_activity = TotalActivity.objects.get(id=1) if not teacher_user_active: - assert ( - activity_today.anonymised_unverified_teachers - == daily_teacher_count + 1 - ) - assert ( - total_activity.anonymised_unverified_teachers - == total_teacher_count + 1 - ) + assert activity_today.anonymised_unverified_teachers == daily_teacher_count + 1 + assert total_activity.anonymised_unverified_teachers == total_teacher_count + 1 if not indy_user_active: - assert ( - activity_today.anonymised_unverified_independents - == daily_indy_count + 1 - ) - assert ( - total_activity.anonymised_unverified_independents - == total_indy_count + 1 - ) + assert activity_today.anonymised_unverified_independents == daily_indy_count + 1 + assert total_activity.anonymised_unverified_independents == total_indy_count + 1 teacher_user.delete() indy_user.delete() diff --git a/portal/urls.py b/portal/urls.py index 2511810f9..970c19048 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -17,22 +17,16 @@ from portal.helpers.decorators import ratelimit from portal.helpers.ratelimit import ( RATELIMIT_LOGIN_GROUP, - RATELIMIT_METHOD, RATELIMIT_LOGIN_RATE, RATELIMIT_LOGIN_RATE_SCHOOL_STUDENT, + RATELIMIT_METHOD, + school_student_key, ) -from portal.helpers.ratelimit import school_student_key from portal.helpers.regexes import ACCESS_CODE_REGEX, JWT_REGEX from portal.views import cron -from portal.views.about import about, getinvolved, contribute -from portal.views.admin import ( - AdminChangePasswordDoneView, - AdminChangePasswordView, -) -from portal.views.aimmo.dashboard import ( - StudentAimmoDashboard, - TeacherAimmoDashboard, -) +from portal.views.about import about, contribute, getinvolved +from portal.views.admin import AdminChangePasswordDoneView, AdminChangePasswordView +from portal.views.aimmo.dashboard import StudentAimmoDashboard, TeacherAimmoDashboard from portal.views.api import ( AnonymiseOrphanSchoolsView, InactiveUsersView, @@ -41,10 +35,7 @@ number_users_per_country, registered_users, ) -from portal.views.dotmailer import ( - dotmailer_consent_form, - process_newsletter_form, -) +from portal.views.dotmailer import dotmailer_consent_form, process_newsletter_form from portal.views.email import verify_email from portal.views.home import ( coding_club, @@ -54,53 +45,55 @@ logout_view, register_view, reset_screentime_warning, + ten_year_map_page, ) from portal.views.legal import privacy_notice, terms from portal.views.login import old_login_form_redirect from portal.views.login.independent_student import IndependentStudentLoginView from portal.views.login.student import ( - StudentLoginView, StudentClassCodeView, + StudentLoginView, student_direct_login, ) from portal.views.login.teacher import TeacherLoginView from portal.views.organisation import organisation_leave, organisation_manage from portal.views.play_landing_page import play_landing_page from portal.views.registration import ( + delete_account, password_reset_check_and_confirm, password_reset_done, student_password_reset, teacher_password_reset, - delete_account, ) from portal.views.student.edit_account_details import ( - independentStudentEditAccountView, SchoolStudentEditAccountView, + independentStudentEditAccountView, student_edit_account, ) from portal.views.student.play import ( - SchoolStudentDashboard, IndependentStudentDashboard, + SchoolStudentDashboard, student_join_organisation, ) from portal.views.teach import teach from portal.views.teacher.dashboard import ( dashboard_manage, - organisation_kick, + delete_teacher_invite, invite_toggle_admin, + invited_teacher, + organisation_kick, organisation_toggle_admin, + resend_invite_teacher, teacher_accept_student_request, teacher_disable_2FA, teacher_reject_student_request, - delete_teacher_invite, - invited_teacher, - resend_invite_teacher, ) from portal.views.teacher.teach import ( teacher_class_password_reset, teacher_delete_class, teacher_delete_students, teacher_dismiss_students, + teacher_download_csv, teacher_edit_class, teacher_edit_student, teacher_move_students, @@ -108,7 +101,6 @@ teacher_onboarding_create_class, teacher_onboarding_edit_class, teacher_print_reminder_cards, - teacher_download_csv, teacher_view_class, ) from portal.views.two_factor.core import CustomSetupView @@ -117,9 +109,7 @@ js_info_dict = {"packages": ("conf.locale",)} two_factor_patterns = [ - url( - r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup" - ), + url(r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup"), url(r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr"), url( r"^account/two_factor/setup/complete/$", @@ -187,9 +177,7 @@ ), url( r"^favicon\.ico$", - RedirectView.as_view( - url="/static/portal/img/favicon.ico", permanent=True - ), + RedirectView.as_view(url="/static/portal/img/favicon.ico", permanent=True), ), url( r"^administration/password_change/$", @@ -201,9 +189,7 @@ AdminChangePasswordDoneView.as_view(), name="administration_password_change_done", ), - url( - r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users" - ), + url(r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users"), url( r"^locked_out/$", TemplateView.as_view(template_name="portal/locked_out.html"), @@ -281,9 +267,7 @@ url(r"^consent_form/$", dotmailer_consent_form, name="consent_form"), url( r"^verify_email/$", - TemplateView.as_view( - template_name="portal/email_verification_needed.html" - ), + TemplateView.as_view(template_name="portal/email_verification_needed.html"), name="email_verification", ), url( @@ -396,9 +380,7 @@ url(r"^contribute", contribute, name="contribute"), url(r"^terms", terms, name="terms"), url(r"^privacy-notice/$", privacy_notice, name="privacy_notice"), - url( - r"^privacy-policy/$", privacy_notice, name="privacy_policy" - ), # Keeping this to route from old URL + url(r"^privacy-policy/$", privacy_notice, name="privacy_policy"), # Keeping this to route from old URL url(r"^teach/dashboard/$", dashboard_manage, name="dashboard"), url( r"^teach/dashboard/kick/(?P[0-9]+)/$", @@ -529,4 +511,5 @@ RemoveFakeAccounts.as_view(), name="remove_fake_accounts", ), + url(r"^celebrate/", ten_year_map_page, name="celebrate"), ] diff --git a/portal/views/home.py b/portal/views/home.py index 00f4e0a06..0dbf2bb2a 100644 --- a/portal/views/home.py +++ b/portal/views/home.py @@ -33,30 +33,19 @@ ) from portal.strings.coding_club import CODING_CLUB_BANNER from portal.strings.home_learning import HOME_LEARNING_BANNER +from portal.strings.ten_year_map import ( + TEN_YEAR_MAP_BANNER, + TEN_YEAR_MAP_HEADLINE, +) from portal.templatetags.app_tags import cloud_storage -from portal.views.teacher.teach import DownloadType, count_student_pack_downloads_click +from portal.views.teacher.teach import ( + DownloadType, + count_student_pack_downloads_click, +) LOGGER = logging.getLogger(__name__) -def teach_email_labeller(request): - if request.method == "POST" and "teacher_login" in request.POST: - return request.POST["login-teacher_email"] - - return "" - - -def play_name_labeller(request): - if request.method == "POST": - if "school_login" in request.POST: - return request.POST["login-name"] + ":" + request.POST["login-access_code"] - - if "independent_student_login" in request.POST: - return request.POST["independent_student-username"] - - return "" - - def register_view(request): if request.user.is_authenticated: return redirect_user_to_dashboard(request) @@ -82,11 +71,15 @@ def render_signup_form(request): invalid_form = False teacher_signup_form = TeacherSignupForm(prefix="teacher_signup") - independent_student_signup_form = IndependentStudentSignupForm(prefix="independent_student_signup") + independent_student_signup_form = IndependentStudentSignupForm( + prefix="independent_student_signup" + ) if request.method == "POST": if "teacher_signup-teacher_email" in request.POST: - teacher_signup_form = TeacherSignupForm(request.POST, prefix="teacher_signup") + teacher_signup_form = TeacherSignupForm( + request.POST, prefix="teacher_signup" + ) if not captcha.CAPTCHA_ENABLED: remove_captcha_from_forms(teacher_signup_form) @@ -140,11 +133,15 @@ def process_signup_form(request, data): [email], personalization_values={ "EMAIL": email, - "LOGIN_URL": request.build_absolute_uri(reverse("teacher_login")), + "LOGIN_URL": request.build_absolute_uri( + reverse("teacher_login") + ), }, ) else: - LOGGER.warn(f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}") + LOGGER.warn( + f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}" + ) else: teacher = Teacher.objects.factory( first_name=data["teacher_first_name"], @@ -155,9 +152,16 @@ def process_signup_form(request, data): send_verification_email(request, teacher.user.user, data) - TotalActivity.objects.update(teacher_registrations=F("teacher_registrations") + 1) + TotalActivity.objects.update( + teacher_registrations=F("teacher_registrations") + 1 + ) - return render(request, "portal/email_verification_needed.html", {"usertype": "TEACHER"}, status=302) + return render( + request, + "portal/email_verification_needed.html", + {"usertype": "TEACHER"}, + status=302, + ) def process_independent_student_signup_form(request, data): @@ -178,12 +182,21 @@ def process_independent_student_signup_form(request, data): [email], personalization_values={ "EMAIL": email, - "LOGIN_URL": request.build_absolute_uri(reverse("independent_student_login")), + "LOGIN_URL": request.build_absolute_uri( + reverse("independent_student_login") + ), }, ) else: - LOGGER.warning(f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}") - return render(request, "portal/email_verification_needed.html", {"usertype": "INDEP_STUDENT"}, status=302) + LOGGER.warning( + f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}" + ) + return render( + request, + "portal/email_verification_needed.html", + {"usertype": "INDEP_STUDENT"}, + status=302, + ) student = Student.objects.independentStudentFactory( name=data["name"], email=data["email"], password=data["password"] @@ -195,13 +208,23 @@ def process_independent_student_signup_form(request, data): send_verification_email(request, student.new_user, data, age=age) - TotalActivity.objects.update(independent_registrations=F("independent_registrations") + 1) + TotalActivity.objects.update( + independent_registrations=F("independent_registrations") + 1 + ) - return render(request, "portal/email_verification_needed.html", {"usertype": "INDEP_STUDENT"}, status=302) + return render( + request, + "portal/email_verification_needed.html", + {"usertype": "INDEP_STUDENT"}, + status=302, + ) def is_developer(request): - return hasattr(request.user, "userprofile") and request.user.userprofile.developer + return ( + hasattr(request.user, "userprofile") + and request.user.userprofile.developer + ) def redirect_teacher_to_correct_page(request, teacher): @@ -227,13 +250,18 @@ def redirect_teacher_to_correct_page(request, teacher): @cache_control(private=True) def home(request): - # Putting this in a try catch because it causes some weird issue in the tests where the first Selenium test passes, - # but any following test fails because it cannot find the Maintenance banner instance. + # Putting this in a try catch because it causes some weird issue in the + # tests where the first Selenium test passes, but any following test + # fails because it cannot find the Maintenance banner instance. try: - maintenance_banner = DynamicElement.objects.get(name="Maintenance banner") + maintenance_banner = DynamicElement.objects.get( + name="Maintenance banner" + ) if maintenance_banner.active: - messages.info(request, format_html(maintenance_banner.text), extra_tags="safe") + messages.info( + request, format_html(maintenance_banner.text), extra_tags="safe" + ) except ObjectDoesNotExist: pass @@ -251,7 +279,9 @@ def home(request): def coding_club(request): - return render(request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}) + return render( + request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER} + ) def download_student_pack(request, student_pack_type): @@ -266,7 +296,22 @@ def download_student_pack(request, student_pack_type): def home_learning(request): - return render(request, "portal/home_learning.html", {"HOME_LEARNING_BANNER": HOME_LEARNING_BANNER}) + return render( + request, + "portal/home_learning.html", + {"HOME_LEARNING_BANNER": HOME_LEARNING_BANNER}, + ) + + +def ten_year_map_page(request): + messages.info( + request, "This page is currently under construction.", extra_tags="safe" + ) + return render( + request, + "portal/ten_year_map.html", + {"BANNER": TEN_YEAR_MAP_BANNER, "HEADLINE": TEN_YEAR_MAP_HEADLINE}, + ) def reset_screentime_warning(request):