cfl-common 5.3.0__py3-none-any.whl → 8.9.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. cfl_common-8.9.15.dist-info/METADATA +47 -0
  2. cfl_common-8.9.15.dist-info/RECORD +99 -0
  3. {cfl_common-5.3.0.dist-info → cfl_common-8.9.15.dist-info}/WHEEL +1 -1
  4. common/app_settings.py +35 -5
  5. common/csp_config.py +85 -0
  6. common/fixtures/aimmo_characters.json +30 -30
  7. common/fixtures/aimmo_characters2.json +1 -1
  8. common/fixtures/aimmo_characters3.json +35 -0
  9. common/helpers/data_migration_loader.py +3 -4
  10. common/helpers/emails.py +228 -108
  11. common/helpers/generators.py +1 -1
  12. common/helpers/organisation.py +10 -0
  13. common/mail.py +201 -0
  14. common/migrations/0002_emailverification.py +1 -3
  15. common/migrations/0005_add_worksheets.py +2 -13
  16. common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +2 -14
  17. common/migrations/0008_unlock_worksheet_3.py +1 -6
  18. common/migrations/0011_student_login_id.py +3 -3
  19. common/migrations/0012_usersession.py +39 -0
  20. common/migrations/0013_class_school.py +42 -0
  21. common/migrations/0014_login_type.py +29 -0
  22. common/migrations/0015_dailyactivity.py +31 -0
  23. common/migrations/0016_joinreleasestudent.py +42 -0
  24. common/migrations/0017_copy_email_to_username.py +18 -0
  25. common/migrations/0018_update_aimmo_character_image_path.py +15 -0
  26. common/migrations/0019_aimmocharacter_alt.py +16 -0
  27. common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
  28. common/migrations/0021_school_is_active.py +28 -0
  29. common/migrations/0022_school_cleanup.py +29 -0
  30. common/migrations/0023_userprofile_aimmo_badges.py +22 -0
  31. common/migrations/0024_teacher_invited_by.py +25 -0
  32. common/migrations/0025_schoolteacherinvitation.py +47 -0
  33. common/migrations/0026_teacher_remove_join_request.py +22 -0
  34. common/migrations/0027_class_created_by.py +25 -0
  35. common/migrations/0028_coding_club_downloads.py +23 -0
  36. common/migrations/0029_dynamicelement.py +22 -0
  37. common/migrations/0030_add_maintenance_banner.py +25 -0
  38. common/migrations/0031_improve_admin_panel.py +56 -0
  39. common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
  40. common/migrations/0033_password_reset_tracking_fields.py +23 -0
  41. common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
  42. common/migrations/0035_rename_lockout_fields.py +27 -0
  43. common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
  44. common/migrations/0037_migrate_email_verification.py +21 -0
  45. common/migrations/0038_delete_emailverification.py +16 -0
  46. common/migrations/0039_copy_email_to_username.py +18 -0
  47. common/migrations/0040_school_county.py +18 -0
  48. common/migrations/0041_populate_gb_counties.py +27 -0
  49. common/migrations/0042_totalactivity.py +25 -0
  50. common/migrations/0043_add_total_activity.py +30 -0
  51. common/migrations/0044_update_activity_models.py +33 -0
  52. common/migrations/0045_otp.py +23 -0
  53. common/migrations/0046_alter_school_country.py +19 -0
  54. common/migrations/0047_delete_school_postcode.py +16 -0
  55. common/migrations/0048_unique_school_names.py +42 -0
  56. common/migrations/0049_anonymise_orphan_users.py +29 -0
  57. common/migrations/0050_anonymise_orphan_schools.py +30 -0
  58. common/migrations/0051_verify_returning_users.py +26 -0
  59. common/migrations/0052_add_cse_fields.py +68 -0
  60. common/migrations/0053_clean_class_data.py +24 -0
  61. common/migrations/0054_delete_aimmo_models.py +20 -0
  62. common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
  63. common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
  64. common/migrations/0057_teacher_teacher__is_admin.py +19 -0
  65. common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
  66. common/models.py +347 -63
  67. common/permissions.py +20 -8
  68. common/static/common/img/RR_logo.svg +336 -0
  69. common/static/common/img/brain.svg +1 -0
  70. common/templates/common/onetrust_cookies_consent_notice.html +6 -6
  71. common/tests/test_migration_anonymise_orphan_schools.py +30 -0
  72. common/tests/test_migration_anonymise_orphan_users.py +30 -0
  73. common/tests/test_migration_blocked_time.py +3 -11
  74. common/tests/test_migration_remove_teacher_title.py +1 -3
  75. common/tests/test_migration_unique_school_names.py +33 -0
  76. common/tests/test_migration_verify_returning_users.py +59 -0
  77. common/tests/test_models.py +49 -43
  78. common/tests/utils/classes.py +1 -3
  79. common/tests/utils/email.py +11 -49
  80. common/tests/utils/organisation.py +10 -14
  81. common/tests/utils/student.py +14 -67
  82. common/tests/utils/teacher.py +16 -38
  83. common/tests/utils/user.py +1 -3
  84. cfl_common-5.3.0.dist-info/METADATA +0 -20
  85. cfl_common-5.3.0.dist-info/RECORD +0 -48
  86. common/email_messages.py +0 -218
  87. common/fixtures/unlock_worksheet3.json +0 -20
  88. common/fixtures/worksheets.json +0 -98
  89. common/fixtures/worksheets2.json +0 -110
  90. common/tests/test_migration_aimmo_characters.py +0 -31
  91. common/tests/test_migration_worksheets.py +0 -49
  92. {cfl_common-5.3.0.dist-info → cfl_common-8.9.15.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,10 @@
1
1
  from builtins import range
2
2
  from typing import Tuple
3
- from uuid import uuid4
4
-
5
- from django.core import mail
3
+ from unittest.mock import patch
6
4
 
7
5
  from common.helpers.emails import generate_token
8
- from common.models import Class, Student
9
-
10
6
  from common.helpers.generators import generate_login_id
7
+ from common.models import Class, Student
11
8
 
12
9
  from . import email
13
10
 
@@ -49,7 +46,7 @@ def create_student_with_direct_login(access_code) -> Tuple[Student, str]:
49
46
  login_id, hashed_login_id = generate_login_id()
50
47
  student = Student.objects.schoolFactory(klass, name, password, hashed_login_id)
51
48
 
52
- return student, login_id
49
+ return student, login_id, name, password
53
50
 
54
51
 
55
52
  def create_independent_student_directly(preverified=True):
@@ -62,7 +59,7 @@ def create_independent_student_directly(preverified=True):
62
59
  """
63
60
  name, username, email, password = generate_independent_student_details()
64
61
 
65
- student = Student.objects.independentStudentFactory(username, name, email, password)
62
+ student = Student.objects.independentStudentFactory(name, email, password)
66
63
 
67
64
  # verify student
68
65
  generate_token(student.new_user, preverified=preverified)
@@ -92,11 +89,9 @@ def create_many_school_students(page, number_of_students):
92
89
 
93
90
  def generate_independent_student_details():
94
91
  name = "Independent Student %d" % generate_independent_student_details.next_id
95
- username = "Student user %d" % generate_independent_student_details.next_id
96
- email_address = (
97
- "Student%d@codeforlife.com" % generate_independent_student_details.next_id
98
- )
99
- password = "Password2"
92
+ email_address = "student%d@codeforlife.com" % generate_independent_student_details.next_id
93
+ username = email_address
94
+ password = "$RFVBGT%^YHNmju7$RFVBGT%^YHNmju7$RFVBGT%^YHNmju7"
100
95
 
101
96
  generate_independent_student_details.next_id += 1
102
97
 
@@ -106,71 +101,23 @@ def generate_independent_student_details():
106
101
  generate_independent_student_details.next_id = 1
107
102
 
108
103
 
109
- def signup_duplicate_independent_student_fail(
110
- page, duplicate_username=None, duplicate_email=None, newsletter=False
111
- ):
104
+ @patch("common.helpers.emails.send_dotdigital_email")
105
+ def create_independent_student(page, mock_send_dotdigital_email):
112
106
  page = page.go_to_signup_page()
113
107
 
114
108
  name, username, email_address, password = generate_independent_student_details()
115
-
116
- if not duplicate_email:
117
- duplicate_email = email_address
118
-
119
- if not duplicate_username:
120
- duplicate_username = username
121
-
122
- page = page.independent_student_signup(
123
- name,
124
- duplicate_username,
125
- duplicate_email,
126
- password=password,
127
- confirm_password=password,
128
- newsletter=newsletter,
129
- )
109
+ page = page.independent_student_signup(name, email_address, password=password, confirm_password=password)
130
110
 
131
111
  page = page.return_to_home_page()
132
112
 
133
- page = email.follow_duplicate_account_link_to_login(
134
- page, mail.outbox[0], "independent"
135
- )
136
-
137
- return page, name, username, email_address, password
138
-
113
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
139
114
 
140
- def create_independent_student(page, newsletter=False):
141
- page = page.go_to_signup_page()
142
-
143
- name, username, email_address, password = generate_independent_student_details()
144
- page = page.independent_student_signup(
145
- name,
146
- username,
147
- email_address,
148
- password=password,
149
- confirm_password=password,
150
- newsletter=newsletter,
151
- )
152
-
153
- page = page.return_to_home_page()
154
-
155
- page = email.follow_verify_email_link_to_login(page, mail.outbox[0], "independent")
156
- mail.outbox = []
115
+ page = email.follow_verify_email_link_to_login(page, verification_url, "independent")
157
116
 
158
117
  return page, name, username, email_address, password
159
118
 
160
119
 
161
- def verify_email(page):
162
- assert len(mail.outbox) > 0
163
-
164
- page = email.follow_verify_email_link_to_login(page, mail.outbox[0], "independent")
165
- mail.outbox = []
120
+ def verify_email(page, verification_url):
121
+ page = email.follow_verify_email_link_to_login(page, verification_url, "independent")
166
122
 
167
123
  return page
168
-
169
-
170
- def submit_independent_student_signup_form(page, password="test"):
171
- page = page.go_to_signup_page()
172
-
173
- name, username, email_address, _ = generate_independent_student_details()
174
- return page.independent_student_signup(
175
- name, username, email_address, password, password, success=False
176
- )
@@ -1,9 +1,9 @@
1
1
  import random
2
2
  import sys
3
+ from unittest.mock import patch
3
4
 
4
5
  from common.helpers.emails import generate_token
5
6
  from common.models import Teacher
6
- from django.core import mail
7
7
 
8
8
  from . import email
9
9
 
@@ -12,11 +12,8 @@ def generate_details(**kwargs):
12
12
  random_int = random.randint(1, sys.maxsize)
13
13
  first_name = kwargs.get("first_name", "Test")
14
14
  last_name = kwargs.get("last_name", f"Teacher {random_int}")
15
- email_address = kwargs.get(
16
- "email_address",
17
- f"testteacher{random_int}@codeforlife.com",
18
- )
19
- password = kwargs.get("password", "Password2!")
15
+ email_address = kwargs.get("email_address", f"testteacher{random_int}@codeforlife.com")
16
+ password = kwargs.get("password", "$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn")
20
17
 
21
18
  return first_name, last_name, email_address, password
22
19
 
@@ -36,7 +33,8 @@ def signup_teacher_directly(preverified=True, **kwargs):
36
33
  return email_address, password
37
34
 
38
35
 
39
- def signup_duplicate_teacher_fail(page, duplicate_email):
36
+ @patch("portal.views.home.send_dotdigital_email")
37
+ def signup_duplicate_teacher_fail(page, duplicate_email, mock_send_dotdigital_email):
40
38
  page = page.go_to_signup_page()
41
39
 
42
40
  first_name, last_name, email_address, password = generate_details()
@@ -44,52 +42,32 @@ def signup_duplicate_teacher_fail(page, duplicate_email):
44
42
 
45
43
  page = page.return_to_home_page()
46
44
 
47
- page = email.follow_duplicate_account_link_to_login(page, mail.outbox[0], "teacher")
48
- mail.outbox = []
45
+ login_link = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["LOGIN_URL"]
46
+
47
+ page = email.follow_duplicate_account_link_to_login(page, login_link, "teacher")
49
48
 
50
49
  return page, email_address, password
51
50
 
52
51
 
53
- def signup_teacher(page, newsletter=False):
52
+ @patch("common.helpers.emails.send_dotdigital_email")
53
+ def signup_teacher(page, mock_send_dotdigital_email, newsletter=False):
54
54
  page = page.go_to_signup_page()
55
55
 
56
56
  first_name, last_name, email_address, password = generate_details()
57
57
  page = page.signup(
58
- first_name,
59
- last_name,
60
- email_address,
61
- password=password,
62
- confirm_password=password,
63
- newsletter=newsletter,
58
+ first_name, last_name, email_address, password=password, confirm_password=password, newsletter=newsletter
64
59
  )
65
60
 
66
61
  page = page.return_to_home_page()
67
62
 
68
- page = email.follow_verify_email_link_to_onboarding(page, mail.outbox[0])
69
- mail.outbox = []
63
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
70
64
 
71
- return page, email_address, password
65
+ page = email.follow_verify_email_link_to_onboarding(page, verification_url)
72
66
 
67
+ return page, email_address, password
73
68
 
74
- def verify_email(page):
75
- assert len(mail.outbox) > 0
76
69
 
77
- page = email.follow_verify_email_link_to_login(page, mail.outbox[0], "teacher")
78
- mail.outbox = []
70
+ def verify_email(page, verification_url):
71
+ page = email.follow_verify_email_link_to_login(page, verification_url, "teacher")
79
72
 
80
73
  return page
81
-
82
-
83
- def submit_teacher_signup_form(page, password="test"):
84
- page = page.go_to_signup_page()
85
-
86
- first_name, last_name, email_address, _ = generate_details()
87
- return page.signup(
88
- first_name,
89
- last_name,
90
- email_address,
91
- password=password,
92
- confirm_password=password,
93
- success=False,
94
- newsletter=True,
95
- )
@@ -7,9 +7,7 @@ def get_superuser():
7
7
  try:
8
8
  return User.objects.get(username="superuser")
9
9
  except User.DoesNotExist:
10
- return User.objects.create_superuser(
11
- "superuser", "superuser@codeforlife.education", "password"
12
- )
10
+ return User.objects.create_superuser("superuser", "superuser@codeforlife.education", "password")
13
11
 
14
12
 
15
13
  def create_user_directly(active=True, **kwargs):
@@ -1,20 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: cfl-common
3
- Version: 5.3.0
4
- Summary: UNKNOWN
5
- Home-page: UNKNOWN
6
- Author: UNKNOWN
7
- Author-email: UNKNOWN
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Operating System :: OS Independent
12
- Requires-Dist: django (==2.2.24)
13
- Requires-Dist: djangorestframework (==3.12.2)
14
- Requires-Dist: django-two-factor-auth (==1.13)
15
- Requires-Dist: django-countries (==6.1.3)
16
- Requires-Dist: wagtail (==2.11.*)
17
-
18
- UNKNOWN
19
-
20
-
@@ -1,48 +0,0 @@
1
- common/__init__.py,sha256=XlncBOpKp_gekbKH7Y_i6yu1qy5tJc3Y8sn8cDy-Vgk,48
2
- common/app_settings.py,sha256=bUZ_fdtlfPetbzHdwa5DCTWD62JkfyOJzmFc_wioLyE,1656
3
- common/apps.py,sha256=49UXZ3bSkFKvIEOL4zM7y1sAhccQJyRtsoOg5XVd_8Y,129
4
- common/context_processors.py,sha256=X0iuX5qu9kMWa7q8osE9CJ2LgM7pPOYQFGdjm8X3rk0,236
5
- common/email_messages.py,sha256=md3Po0s3xQ5koBZdtJiYk9lcr2dL0U57hKx-zjZLmGY,7854
6
- common/models.py,sha256=VGb1j6bz1J4hQIN29zUAPICsbmKfnKkkWlmHNH6KEbI,8641
7
- common/permissions.py,sha256=KYwnAaGPlWS9xTr6kVWEyqgipIq3zTJNyrErcCin2O8,1871
8
- common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
9
- common/fixtures/aimmo_characters.json,sha256=MHJ5SJ9_48gOzEukku0UTH8bBQR6Tap--4dfAGOsN6o,1365
10
- common/fixtures/aimmo_characters2.json,sha256=vM8tIcKHb1A7b6gyvrklZKl-hhu0yTmZA8lH8oWrcjk,1284
11
- common/fixtures/unlock_worksheet3.json,sha256=F6yth5BuhAZpexSVco5n3CZidvFZ1Tye437UHVY8k_Q,1864
12
- common/fixtures/worksheets.json,sha256=yC50h8DHDCuwgTXJcPKfFC7qjIjSOUtjtUDN1sLQnxc,7699
13
- common/fixtures/worksheets2.json,sha256=B31mKMcwz6l42MjKYjRNO8cR1ZDEv_SxZvSHxR9PmAE,8139
14
- common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- common/helpers/data_migration_loader.py,sha256=05OteSWSuCLt2BRIlRhHCNWVigYjyoiaMw323ZKFZPk,1778
16
- common/helpers/emails.py,sha256=zsZ8G3TVcjW9M9YQZhV6L4sM_nvCD5a0uOLa8zDvbrI,7483
17
- common/helpers/generators.py,sha256=wJ90XBe5zN-JXEPSpnQhCG0sdpBLEBUj1MG2K_GOWPk,1505
18
- common/migrations/0001_initial.py,sha256=Y2kt2xmdCbrmDXCgqmhXeacicNg26Zj7L7SANSsgAAI,9664
19
- common/migrations/0002_emailverification.py,sha256=_tbAHuICs2y5Rdahry98GQ5plBh46SQ-0nZGrfTQAlY,2156
20
- common/migrations/0003_aimmocharacter.py,sha256=ZS_-sJCVUnFdhDjyH75P5bryg5QKHPDfe-SXXK7k9KM,922
21
- common/migrations/0004_add_aimmocharacters.py,sha256=543WKEphsvdKo8Z7f8wPgZHGxRPWMdo288O5_7edawo,430
22
- common/migrations/0005_add_worksheets.py,sha256=dM4Z2A3xDrjADml9zPWVP1YsVNXrbG3jmZQBGvOu1RQ,480
23
- common/migrations/0006_update_aimmo_character_image_path.py,sha256=De1d02dAYE2KT8uW1qPi0NmWp45_zXVNH7kjWg3f2ko,431
24
- common/migrations/0007_add_pdf_names_to_first_two_worksheets.py,sha256=W1wmCPQd1NGjFlwxPTVJBrUqdq_kWSZMXa8og_wIDAY,513
25
- common/migrations/0008_unlock_worksheet_3.py,sha256=nBkODMtQNPnsxrY0wTE1g1icYBqyahhtKt8MPx4_Pyc,414
26
- common/migrations/0009_add_blocked_time_to_teacher_and_student.py,sha256=hdff316HkAVCB0EW61qbR2lxwkxSTsD7OBK66PLBUUQ,599
27
- common/migrations/0010_remove_teacher_title.py,sha256=dMX-wgD-nzRx2AmrvuLMtb1X7TIv1gZ9icacFgI1LNc,396
28
- common/migrations/0011_student_login_id.py,sha256=9aeEowaiCltBTp0WCJcXlk3mfAq-5kmJguT6Sjs5m04,400
29
- common/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- common/templates/common/freshdesk_widget.html,sha256=gc4BCbyh-ADCd0TkLYnT4X8FgOcJL_Bum-PSFXv3Vog,336
31
- common/templates/common/onetrust_cookies_consent_notice.html,sha256=CtCTESe0_BOxyU8Y-56qLoEeQhaG554oy03N8A6NQnI,1069
32
- common/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- common/tests/test_migration_aimmo_characters.py,sha256=Z8BkpGuAl7Kj56sXSIhgApVMbfZK4CFeYD3wm72s2kY,1093
34
- common/tests/test_migration_blocked_time.py,sha256=a5gDDyn0x7TmEieId04scIXSritei5_qN1CX4Bx5eo0,652
35
- common/tests/test_migration_remove_teacher_title.py,sha256=BTc_XQvT08Ryhry08Y4ZLiGRAFTXzcSpgv62z66zlH4,536
36
- common/tests/test_migration_worksheets.py,sha256=JqaGR2uGZ20YEPRCoM8-fLu3sTe6LDx3dCvKHYzoFtk,1775
37
- common/tests/test_models.py,sha256=ZpjLU0Zk_h8XaLuxE5y03-QLsT21n9RMbMjUVncWusU,2981
38
- common/tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- common/tests/utils/classes.py,sha256=LppAbCofWdpK_A42T4dQRKTQ6Y5BooWnd-AnGUQaz0U,1100
40
- common/tests/utils/email.py,sha256=CeuDl1w6RbA_-DnrwcTXhCL2D_JzbGB2poLA9eLoneE,2958
41
- common/tests/utils/organisation.py,sha256=OY5WY2ThnpRUU3zfADuIv6ommE2R-ZJ_OJBc-w50nmA,1173
42
- common/tests/utils/student.py,sha256=zK_b1HTXeVPanJvAu1NcMgO4h8-fZaLrIrBSl6plPYc,4962
43
- common/tests/utils/teacher.py,sha256=UfXf8uVTtgfYfr4fypfvJ1Esom0y4RR99uEhqzq-HmQ,2747
44
- common/tests/utils/user.py,sha256=Utl3gNykKRWryCoK9cJf9BTPthU7xz_azRD8cmvV38g,941
45
- cfl_common-5.3.0.dist-info/METADATA,sha256=PZ6nnvwSIRggk6jEy0Ardb8eS5pKYEEKnbl7dqanHug,471
46
- cfl_common-5.3.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
47
- cfl_common-5.3.0.dist-info/top_level.txt,sha256=LOtYx8KZTmnxM_zLK4rwrcI3PRc40Ihwp5rgaQ-ceaI,7
48
- cfl_common-5.3.0.dist-info/RECORD,,
common/email_messages.py DELETED
@@ -1,218 +0,0 @@
1
- from django.urls import reverse
2
-
3
-
4
- def emailSubjectPrefix():
5
- return "Code for Life"
6
-
7
-
8
- def emailBodySignOff(request):
9
- return (
10
- f"\n\nThanks,\n\nThe Code for Life team.\n"
11
- f"{request.build_absolute_uri(reverse('home'))}"
12
- )
13
-
14
-
15
- def emailVerificationNeededEmail(request, token):
16
- return {
17
- "subject": f"{emailSubjectPrefix()}: Email address verification needed",
18
- "message": (
19
- f"Please go to "
20
- f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': token}))} "
21
- f"to verify your email address."
22
- f"{emailBodySignOff(request)}"
23
- ),
24
- }
25
-
26
-
27
- def emailChangeVerificationEmail(request, token):
28
- return {
29
- "subject": f"{emailSubjectPrefix()}: Email address verification needed",
30
- "message": (
31
- f"You are changing your email, please go to "
32
- f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': token}))} "
33
- f"to verify your new email address. If you are not part of Code for Life "
34
- f"then please ignore this email."
35
- f"{emailBodySignOff(request)}"
36
- ),
37
- }
38
-
39
-
40
- def emailChangeNotificationEmail(request):
41
- return {
42
- "subject": f"{emailSubjectPrefix()}: Email address changed",
43
- "message": (
44
- f"Someone has tried to change the email address of your account. If this "
45
- f"was not you, please get in contact with us."
46
- f"{emailBodySignOff(request)}"
47
- ),
48
- }
49
-
50
-
51
- def emailChangeDuplicateNotificationEmail(request, email):
52
- return {
53
- "subject": f"{emailSubjectPrefix()}: Duplicate account error",
54
- "message": (
55
- f"A user is already registered with this email address: {email}.\n"
56
- f"Please change your email address to something else."
57
- f"{emailBodySignOff(request)}"
58
- ),
59
- }
60
-
61
-
62
- def userAlreadyRegisteredEmail(request, email, is_independent_student=False):
63
- if is_independent_student:
64
- login_url = reverse("independent_student_login")
65
- else:
66
- login_url = reverse("teacher_login")
67
-
68
- return {
69
- "subject": f"{emailSubjectPrefix()}: Duplicate account error",
70
- "message": (
71
- f"A user is already registered with this email address: {email}.\n"
72
- f"If you've already registered, please login: "
73
- f"{request.build_absolute_uri(login_url)}.\n"
74
- f"Otherwise please register with a different email address."
75
- f"{emailBodySignOff(request)}"
76
- ),
77
- }
78
-
79
-
80
- def indepStudentUsernameAlreadyExistsEmail(request, username):
81
- return {
82
- "subject": f"{emailSubjectPrefix()}: Username already taken",
83
- "message": f"A user is already registered with this username: {username}.\n"
84
- f"If you've already registered, please login: "
85
- f"{request.build_absolute_uri(reverse('independent_student_login'))}.\n"
86
- f"Otherwise please register with a different username."
87
- f"{emailBodySignOff(request)}",
88
- }
89
-
90
-
91
- def joinRequestPendingEmail(request, pendingAddress):
92
- return {
93
- "subject": f"{emailSubjectPrefix()}: School or club join request pending",
94
- "message": (
95
- f"Someone with the email address '{pendingAddress}' has asked to join your "
96
- f"school or club, please go to "
97
- f"{request.build_absolute_uri(reverse('dashboard'))} to view the pending "
98
- f"join request."
99
- f"{emailBodySignOff(request)}"
100
- ),
101
- }
102
-
103
-
104
- def joinRequestSentEmail(request, schoolName):
105
- return {
106
- "subject": f"{emailSubjectPrefix()}: School or club join request sent",
107
- "message": (
108
- f"Your request to join the school or club '{schoolName}' has been sent. "
109
- f"Someone will either accept or deny your request soon."
110
- f"{emailBodySignOff(request)}"
111
- ),
112
- }
113
-
114
-
115
- def joinRequestAcceptedEmail(request, schoolName):
116
- return {
117
- "subject": f"{emailSubjectPrefix()}: School or club join request accepted",
118
- "message": (
119
- f"Your request to join the school or club '{schoolName}' has been accepted."
120
- f"{emailBodySignOff(request)}"
121
- ),
122
- }
123
-
124
-
125
- def joinRequestDeniedEmail(request, schoolName):
126
- return {
127
- "subject": f"{emailSubjectPrefix()}: School or club join request denied",
128
- "message": (
129
- f"Your request to join the school or club '{schoolName}' has been denied. "
130
- f"If you think this was in error you should speak to the administrator of "
131
- f"that school or club."
132
- f"{emailBodySignOff(request)}"
133
- ),
134
- }
135
-
136
-
137
- def kickedEmail(request, schoolName):
138
- return {
139
- "subject": f"{emailSubjectPrefix()}: You were removed from your school or club",
140
- "message": (
141
- f"You have been removed from the school or club '{schoolName}'. "
142
- f"If you think this was an error, please contact the administrator of that "
143
- f"school or club."
144
- f"{emailBodySignOff(request)}"
145
- ),
146
- }
147
-
148
-
149
- def adminGivenEmail(request, schoolName):
150
- return {
151
- "subject": f"{emailSubjectPrefix()}: You have been made a school or club administrator",
152
- "message": (
153
- f"Administrator control of the school or club '{schoolName}' has been "
154
- f"given to you. Go to {request.build_absolute_uri(reverse('dashboard'))} "
155
- f"to start managing your school or club."
156
- f"{emailBodySignOff(request)}"
157
- ),
158
- }
159
-
160
-
161
- def adminRevokedEmail(request, schoolName):
162
- return {
163
- "subject": f"{emailSubjectPrefix()}: You are no longer a school or club administrator",
164
- "message": (
165
- f"Your administrator control of the school or club '{schoolName}' has been "
166
- f"revoked. If you think this is an error, please contact one of the other "
167
- f"administrators in your school or club."
168
- f"{emailBodySignOff(request)}"
169
- ),
170
- }
171
-
172
-
173
- def studentJoinRequestSentEmail(request, schoolName, accessCode):
174
- return {
175
- "subject": f"{emailSubjectPrefix()}: School or club join request sent",
176
- "message": (
177
- f"Your request to join the school or club '{schoolName}' in class "
178
- f"{accessCode} has been sent to that class's teacher, who will either "
179
- f"accept or deny your request."
180
- f"{emailBodySignOff(request)}"
181
- ),
182
- }
183
-
184
-
185
- def studentJoinRequestNotifyEmail(request, username, email, accessCode):
186
- return {
187
- "subject": f"{emailSubjectPrefix()}: School or club join request by student {username}",
188
- "message": (
189
- f"There is a request waiting from student with username '{username}' and "
190
- f"email {email} to join your class {accessCode}. Go to "
191
- f"{request.build_absolute_uri(reverse('dashboard'))} to review the request."
192
- f"{emailBodySignOff(request)}"
193
- ),
194
- }
195
-
196
-
197
- def studentJoinRequestRejectedEmail(request, schoolName, accessCode):
198
- return {
199
- "subject": f"{emailSubjectPrefix()}: School or club join request rejected",
200
- "message": (
201
- f"Your request to join the school or club '{schoolName}' in class "
202
- f"{accessCode} has been rejected. Speak to your teacher if you think this "
203
- f"is an error."
204
- f"{emailBodySignOff(request)}"
205
- ),
206
- }
207
-
208
-
209
- def inviteTeacherEmail(request):
210
- return {
211
- "subject": f"{emailSubjectPrefix()}: You've been invited to join Code for Life",
212
- "message": (
213
- f"A colleague at your school or code club has invited you to become part of "
214
- f"Code for Life.\n\nPlease register your details to get started.\n\n"
215
- f"{request.build_absolute_uri(reverse('register'))}"
216
- f"{emailBodySignOff(request)}"
217
- ),
218
- }
@@ -1,20 +0,0 @@
1
- [
2
- {
3
- "model": "aimmo.worksheet",
4
- "pk": 3,
5
- "fields": {
6
- "name": "Ancient",
7
- "description": "You are now familiar with your equipment and have managed to unlock the gateway to the first time checkpoint. You find yourself in a strange place, full of familiar structures in the form of ruins. After a few minutes, you hear steps and realise you\u2019re not alone! No time to move around aimlessly now, or you might run into someone! Use your navigating system to scan your surroundings and move only towards the artefacts you want to pick up. Be stealthy!",
8
- "short_description": "You find yourself in a strange place, full of familiar structures in the form of ruins.",
9
- "image_path": "images/worksheets/ancient.jpg",
10
- "active_image_path": "images/worksheets/ancient_active.png",
11
- "era": 2,
12
- "thumbnail_text": "",
13
- "thumbnail_image_path": "images/worksheets/lock.png",
14
- "teacher_pdf_name": "Kurono_teacher_guide_3",
15
- "student_pdf_name": "Kurono_challenge_3",
16
- "sort_order": 300,
17
- "starter_code": "#-----------------------------------------------------------------\r\n# Worksheet 3 challenges:\r\n# - scan surroundings for artefacts\r\n# - move towards the nearest artefact\r\n# - pick up the artefact\r\n#\r\n# Previous subroutines: \r\n# - MoveAction(DIRECTION)\r\n# - world_state.get_cell(LOCATION) -> Cell\r\n#\r\n# New subroutines:\r\n# - world_state.scan_nearby(LOCATION) -> List(Artefact)\r\n# - MoveTowardsAction(ARTEFACT)\r\n# - PickupAction()\r\n#-----------------------------------------------------------------\r\n\r\ndef next_turn(world_state, avatar_state):\r\n # Your code goes here\r\n\r\n # Scan your surroundings\r\n \r\n # Change this to Move action to move the avatar\r\n action = WaitAction()\r\n return action"
18
- }
19
- }
20
- ]
@@ -1,98 +0,0 @@
1
- [
2
- {
3
- "model": "aimmo.worksheet",
4
- "pk": 1,
5
- "fields": {
6
- "name": "Present Day I: The Museum",
7
- "description": "While working at the Scriptsonian museum for class credit, you have accidentally activated the mysterious Kurono time machine! The museum\u2019s valuable artefacts are scattered across the shelves, the floor, the whole spacetime continuum. It\u2019s going to take more than just a backpack to collect them all. You need to use the Kurono time machine to get them back \u2013 but before jumping in you need to learn how to use its navigation system. This challenge shows you how to move around the map so you don\u2019t run into problems in the future\u2026or past.",
8
- "short_description": "While working at the Scriptsonian museum for class credit, you have accidentally activated the mysterious Kurono time machine!",
9
- "image_path": "images/worksheets/future.jpg",
10
- "active_image_path": "images/worksheets/future_active.png",
11
- "era": 1,
12
- "thumbnail_text": "",
13
- "thumbnail_image_path": "images/worksheets/lock.png",
14
- "sort_order": 100,
15
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
16
- }
17
- },
18
- {
19
- "model": "aimmo.worksheet",
20
- "pk": 2,
21
- "fields": {
22
- "name": "Present Day II",
23
- "description": "Now that you know how to navigate, it\u2019s time to clean up the evidence before leaving this timeline! Some of the artefacts escaped the vortex and ended up all over the museum - if each of you picks ten of them up, you should be done in time to embark in your journey. But beware! The blue artefacts will need to be separated from the red ones, as they have opposing magnetic fields. Can you tell which one is what type?",
24
- "short_description": "Now that you know how to navigate, it\u2019s time to clean up the evidence before leaving this timeline!",
25
- "image_path": "images/worksheets/future.jpg",
26
- "active_image_path": "images/worksheets/future2_active.png",
27
- "era": 1,
28
- "thumbnail_text": "",
29
- "thumbnail_image_path": "images/worksheets/lock.png",
30
- "sort_order": 200,
31
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
32
- }
33
- },
34
- {
35
- "model": "aimmo.worksheet",
36
- "pk": 3,
37
- "fields": {
38
- "name": "Ancient",
39
- "description": "You are now familiar with your equipment and have managed to unlock the gateway to the first time checkpoint. You find yourself in a strange place, full of familiar structures in the form of ruins. After a few minutes, you hear steps and realise you\u2019re not alone! No time to move around aimlessly now, or you might run into someone! Use your navigating system to scan your surroundings and move only towards the artefacts you want to pick up. Be stealthy!",
40
- "short_description": "You find yourself in a strange place, full of familiar structures in the form of ruins.",
41
- "image_path": "images/worksheets/ancient.jpg",
42
- "active_image_path": "images/worksheets/ancient_active.png",
43
- "era": 2,
44
- "thumbnail_text": "Coming Soon",
45
- "thumbnail_image_path": "",
46
- "sort_order": 300,
47
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
48
- }
49
- },
50
- {
51
- "model": "aimmo.worksheet",
52
- "pk": 4,
53
- "fields": {
54
- "name": "21st Century",
55
- "description": "After successfully collecting all the missing artefacts from the first time checkpoint, you arrive at what looks like the 21st century. You recognise some cars parked here and there, old-fashioned roads and houses like the ones your history teacher told you about. On the bright side, you seem to be alone and safe to walk around... for now. A more recent timeline doesn\u2019t make artefacts easier to find, though. Or at least not the right ones. In this timeline there seems to be an amount of falsified objects that resemble the ones you\u2019re looking for, but aren\u2019t quite genuine. These will have no value in the museum. Your navigation system will be able to tell you whether an object is genuine or not, but it\u2019s up to you to decide which ones to bring back!",
56
- "short_description": "After successfully collecting all the missing artefacts from the first time checkpoint, you arrive at what looks like the 21st century.",
57
- "image_path": "images/worksheets/modern.jpg",
58
- "active_image_path": "images/worksheets/modern_active.png",
59
- "era": 3,
60
- "thumbnail_text": "Coming Soon",
61
- "thumbnail_image_path": "",
62
- "sort_order": 400,
63
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
64
- }
65
- },
66
- {
67
- "model": "aimmo.worksheet",
68
- "pk": 5,
69
- "fields": {
70
- "name": "Prehistoric",
71
- "description": "Oh no! Looks like the time machine has taken you even further back in time. You find yourself in a place that looks very\u2026 wild. You can hear the occasional distant roaring, added to the buzzing of what sound like gigantic insects. Vegetation is so abundant around you that you can barely move. It might be a good idea to use your navigation system to scan all around you before even trying! And don\u2019t forget to keep track of the portal that\u2019ll send you back to your original timeline - just in case something with sharp teeth starts running towards you.",
72
- "short_description": "Oh no! Looks like the time machine has taken you even further back in time. You find yourself in a place that looks very\u2026 wild.",
73
- "image_path": "images/worksheets/prehistory.jpg",
74
- "active_image_path": "images/worksheets/prehistory_active.png",
75
- "era": 4,
76
- "thumbnail_text": "Coming Soon",
77
- "thumbnail_image_path": "",
78
- "sort_order": 500,
79
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
80
- }
81
- },
82
- {
83
- "model": "aimmo.worksheet",
84
- "pk": 6,
85
- "fields": {
86
- "name": "Back to the Future",
87
- "description": "You\u2019ve made it! Well, almost. You\u2019re back to your original time, but something seems to be off. The museum is still there, with all the chaos you still need to tidy up, but the environment isn\u2019t quite right. The electronic containers and shelves seem slightly different, and when the AI interlocutor speaks, you don\u2019t recognise the language. It looks like you\u2019ve changed something in the past that affected events in your present! If you manage to extract the metadata from the recovered artefacts, you should be able to find out which timelines they were found in. You can input this text into the Kurono machine to get things back to normal.",
88
- "short_description": "You\u2019ve made it! Well, almost. You\u2019re back to your original time, but something seems to be off.",
89
- "image_path": "images/worksheets/broken_future.jpg",
90
- "active_image_path": "images/worksheets/broken_future_active.png",
91
- "era": 5,
92
- "thumbnail_text": "Coming Soon",
93
- "thumbnail_image_path": "",
94
- "sort_order": 600,
95
- "starter_code": "def next_turn(world_state, avatar_state):\r\n new_dir = direction.NORTH\r\n # Your code goes here\r\n action = MoveAction(new_dir)\r\n return action"
96
- }
97
- }
98
- ]