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.
- cfl_common-8.9.15.dist-info/METADATA +47 -0
- cfl_common-8.9.15.dist-info/RECORD +99 -0
- {cfl_common-5.3.0.dist-info → cfl_common-8.9.15.dist-info}/WHEEL +1 -1
- common/app_settings.py +35 -5
- common/csp_config.py +85 -0
- common/fixtures/aimmo_characters.json +30 -30
- common/fixtures/aimmo_characters2.json +1 -1
- common/fixtures/aimmo_characters3.json +35 -0
- common/helpers/data_migration_loader.py +3 -4
- common/helpers/emails.py +228 -108
- common/helpers/generators.py +1 -1
- common/helpers/organisation.py +10 -0
- common/mail.py +201 -0
- common/migrations/0002_emailverification.py +1 -3
- common/migrations/0005_add_worksheets.py +2 -13
- common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +2 -14
- common/migrations/0008_unlock_worksheet_3.py +1 -6
- common/migrations/0011_student_login_id.py +3 -3
- common/migrations/0012_usersession.py +39 -0
- common/migrations/0013_class_school.py +42 -0
- common/migrations/0014_login_type.py +29 -0
- common/migrations/0015_dailyactivity.py +31 -0
- common/migrations/0016_joinreleasestudent.py +42 -0
- common/migrations/0017_copy_email_to_username.py +18 -0
- common/migrations/0018_update_aimmo_character_image_path.py +15 -0
- common/migrations/0019_aimmocharacter_alt.py +16 -0
- common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
- common/migrations/0021_school_is_active.py +28 -0
- common/migrations/0022_school_cleanup.py +29 -0
- common/migrations/0023_userprofile_aimmo_badges.py +22 -0
- common/migrations/0024_teacher_invited_by.py +25 -0
- common/migrations/0025_schoolteacherinvitation.py +47 -0
- common/migrations/0026_teacher_remove_join_request.py +22 -0
- common/migrations/0027_class_created_by.py +25 -0
- common/migrations/0028_coding_club_downloads.py +23 -0
- common/migrations/0029_dynamicelement.py +22 -0
- common/migrations/0030_add_maintenance_banner.py +25 -0
- common/migrations/0031_improve_admin_panel.py +56 -0
- common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
- common/migrations/0033_password_reset_tracking_fields.py +23 -0
- common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
- common/migrations/0035_rename_lockout_fields.py +27 -0
- common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
- common/migrations/0037_migrate_email_verification.py +21 -0
- common/migrations/0038_delete_emailverification.py +16 -0
- common/migrations/0039_copy_email_to_username.py +18 -0
- common/migrations/0040_school_county.py +18 -0
- common/migrations/0041_populate_gb_counties.py +27 -0
- common/migrations/0042_totalactivity.py +25 -0
- common/migrations/0043_add_total_activity.py +30 -0
- common/migrations/0044_update_activity_models.py +33 -0
- common/migrations/0045_otp.py +23 -0
- common/migrations/0046_alter_school_country.py +19 -0
- common/migrations/0047_delete_school_postcode.py +16 -0
- common/migrations/0048_unique_school_names.py +42 -0
- common/migrations/0049_anonymise_orphan_users.py +29 -0
- common/migrations/0050_anonymise_orphan_schools.py +30 -0
- common/migrations/0051_verify_returning_users.py +26 -0
- common/migrations/0052_add_cse_fields.py +68 -0
- common/migrations/0053_clean_class_data.py +24 -0
- common/migrations/0054_delete_aimmo_models.py +20 -0
- common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
- common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
- common/migrations/0057_teacher_teacher__is_admin.py +19 -0
- common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- common/models.py +347 -63
- common/permissions.py +20 -8
- common/static/common/img/RR_logo.svg +336 -0
- common/static/common/img/brain.svg +1 -0
- common/templates/common/onetrust_cookies_consent_notice.html +6 -6
- common/tests/test_migration_anonymise_orphan_schools.py +30 -0
- common/tests/test_migration_anonymise_orphan_users.py +30 -0
- common/tests/test_migration_blocked_time.py +3 -11
- common/tests/test_migration_remove_teacher_title.py +1 -3
- common/tests/test_migration_unique_school_names.py +33 -0
- common/tests/test_migration_verify_returning_users.py +59 -0
- common/tests/test_models.py +49 -43
- common/tests/utils/classes.py +1 -3
- common/tests/utils/email.py +11 -49
- common/tests/utils/organisation.py +10 -14
- common/tests/utils/student.py +14 -67
- common/tests/utils/teacher.py +16 -38
- common/tests/utils/user.py +1 -3
- cfl_common-5.3.0.dist-info/METADATA +0 -20
- cfl_common-5.3.0.dist-info/RECORD +0 -48
- common/email_messages.py +0 -218
- common/fixtures/unlock_worksheet3.json +0 -20
- common/fixtures/worksheets.json +0 -98
- common/fixtures/worksheets2.json +0 -110
- common/tests/test_migration_aimmo_characters.py +0 -31
- common/tests/test_migration_worksheets.py +0 -49
- {cfl_common-5.3.0.dist-info → cfl_common-8.9.15.dist-info}/top_level.txt +0 -0
common/tests/utils/student.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
from builtins import range
|
|
2
2
|
from typing import Tuple
|
|
3
|
-
from
|
|
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(
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
common/tests/utils/teacher.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
mail.outbox = []
|
|
63
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
|
|
70
64
|
|
|
71
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
)
|
common/tests/utils/user.py
CHANGED
|
@@ -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
|
-
]
|
common/fixtures/worksheets.json
DELETED
|
@@ -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
|
-
]
|