codeforlife-portal 6.43.0__py2.py3-none-any.whl → 6.43.2__py2.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.
Potentially problematic release.
This version of codeforlife-portal might be problematic. Click here for more details.
- cfl_common/common/email_messages.py +0 -37
- cfl_common/common/helpers/emails.py +14 -39
- cfl_common/common/mail.py +89 -44
- {codeforlife_portal-6.43.0.dist-info → codeforlife_portal-6.43.2.dist-info}/METADATA +2 -2
- {codeforlife_portal-6.43.0.dist-info → codeforlife_portal-6.43.2.dist-info}/RECORD +15 -15
- portal/__init__.py +1 -1
- portal/tests/test_independent_student.py +3 -2
- portal/tests/test_organisation.py +5 -3
- portal/tests/test_views.py +11 -3
- portal/views/registration.py +11 -20
- portal/views/student/edit_account_details.py +8 -6
- portal/views/teacher/dashboard.py +56 -32
- {codeforlife_portal-6.43.0.dist-info → codeforlife_portal-6.43.2.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-6.43.0.dist-info → codeforlife_portal-6.43.2.dist-info}/WHEEL +0 -0
- {codeforlife_portal-6.43.0.dist-info → codeforlife_portal-6.43.2.dist-info}/top_level.txt +0 -0
|
@@ -29,30 +29,6 @@ def kickedEmail(request, schoolName):
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def adminGivenEmail(request, schoolName):
|
|
33
|
-
|
|
34
|
-
url = request.build_absolute_uri(reverse("dashboard"))
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
"subject": f"You have been made a school or club administrator",
|
|
38
|
-
"message": (
|
|
39
|
-
f"Administrator control of the school or club '{schoolName}' has been "
|
|
40
|
-
f"given to you. Go to {url} to start managing your school or club."
|
|
41
|
-
),
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def adminRevokedEmail(request, schoolName):
|
|
46
|
-
return {
|
|
47
|
-
"subject": f"You are no longer a school or club administrator",
|
|
48
|
-
"message": (
|
|
49
|
-
f"Your administrator control of the school or club '{schoolName}' has been "
|
|
50
|
-
f"revoked. If you think this is an error, please contact one of the other "
|
|
51
|
-
f"administrators in your school or club."
|
|
52
|
-
),
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
32
|
def studentJoinRequestSentEmail(request, schoolName, accessCode):
|
|
57
33
|
return {
|
|
58
34
|
"subject": f"School or club join request sent",
|
|
@@ -105,16 +81,3 @@ def inviteTeacherEmail(request, schoolName, token, account_exists):
|
|
|
105
81
|
)
|
|
106
82
|
|
|
107
83
|
return {"subject": f"You've been invited to join Code for Life", "message": message}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def accountDeletionEmail(request):
|
|
111
|
-
return {
|
|
112
|
-
"subject": f"We are sorry to see you go",
|
|
113
|
-
"title": "Your account was successfully deleted",
|
|
114
|
-
"message": (
|
|
115
|
-
f"If you have a moment before you leave us completely, please "
|
|
116
|
-
f"let us know the reason through our super short survey below."
|
|
117
|
-
f"\n\nGive feedback: https://usabi.li/do/d8e0313a31d7/5bef"
|
|
118
|
-
f"\n\nThank you for being part of the Code for Life community!"
|
|
119
|
-
),
|
|
120
|
-
}
|
|
@@ -6,14 +6,11 @@ from uuid import uuid4
|
|
|
6
6
|
|
|
7
7
|
import jwt
|
|
8
8
|
from common import app_settings
|
|
9
|
-
from common.
|
|
10
|
-
from common.mail import campaign_ids, send_dotdigital_email
|
|
9
|
+
from common.mail import campaign_ids, django_send_email, send_dotdigital_email
|
|
11
10
|
from common.models import Student, Teacher
|
|
12
11
|
from django.conf import settings
|
|
13
12
|
from django.contrib.auth.models import User
|
|
14
|
-
from django.core.mail import EmailMultiAlternatives
|
|
15
13
|
from django.http import HttpResponse
|
|
16
|
-
from django.template import loader
|
|
17
14
|
from django.urls import reverse
|
|
18
15
|
from django.utils import timezone
|
|
19
16
|
from requests import delete, get, post, put
|
|
@@ -31,41 +28,6 @@ class DotmailerUserType(Enum):
|
|
|
31
28
|
NO_ACCOUNT = auto()
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
def send_email(
|
|
35
|
-
sender,
|
|
36
|
-
recipients,
|
|
37
|
-
subject,
|
|
38
|
-
text_content,
|
|
39
|
-
title,
|
|
40
|
-
replace_url=None,
|
|
41
|
-
plaintext_template="email.txt",
|
|
42
|
-
html_template="email.html",
|
|
43
|
-
):
|
|
44
|
-
# add in template for templates to message
|
|
45
|
-
|
|
46
|
-
# setup templates
|
|
47
|
-
plaintext = loader.get_template(plaintext_template)
|
|
48
|
-
html = loader.get_template(html_template)
|
|
49
|
-
plaintext_email_context = {"content": text_content}
|
|
50
|
-
html_email_context = {"content": text_content, "title": title, "url_prefix": domain()}
|
|
51
|
-
|
|
52
|
-
# render templates
|
|
53
|
-
plaintext_body = plaintext.render(plaintext_email_context)
|
|
54
|
-
original_html_body = html.render(html_email_context)
|
|
55
|
-
html_body = original_html_body
|
|
56
|
-
|
|
57
|
-
if replace_url:
|
|
58
|
-
verify_url = replace_url["verify_url"]
|
|
59
|
-
verify_replace_url = re.sub(f"(.*/verify_email/)(.*)", f"\\1", verify_url)
|
|
60
|
-
html_body = re.sub(f"({verify_url})(.*){verify_url}", f"\\1\\2{verify_replace_url}", original_html_body)
|
|
61
|
-
|
|
62
|
-
# make message using templates
|
|
63
|
-
message = EmailMultiAlternatives(subject, plaintext_body, sender, recipients)
|
|
64
|
-
message.attach_alternative(html_body, "text/html")
|
|
65
|
-
|
|
66
|
-
message.send()
|
|
67
|
-
|
|
68
|
-
|
|
69
31
|
def generate_token(user, new_email="", preverified=False):
|
|
70
32
|
if preverified:
|
|
71
33
|
user.userprofile.is_verified = preverified
|
|
@@ -91,6 +53,19 @@ def _newsletter_ticked(data):
|
|
|
91
53
|
return "newsletter_ticked" in data and data["newsletter_ticked"]
|
|
92
54
|
|
|
93
55
|
|
|
56
|
+
def send_email(
|
|
57
|
+
sender,
|
|
58
|
+
recipients,
|
|
59
|
+
subject,
|
|
60
|
+
text_content,
|
|
61
|
+
title,
|
|
62
|
+
replace_url=None,
|
|
63
|
+
plaintext_template="email.txt",
|
|
64
|
+
html_template="email.html",
|
|
65
|
+
):
|
|
66
|
+
django_send_email(sender, recipients, subject, text_content, title, replace_url, plaintext_template, html_template)
|
|
67
|
+
|
|
68
|
+
|
|
94
69
|
def send_verification_email(request, user, data, new_email=None, age=None):
|
|
95
70
|
"""
|
|
96
71
|
Sends emails relating to email address verification.
|
cfl_common/common/mail.py
CHANGED
|
@@ -3,8 +3,14 @@ from dataclasses import dataclass
|
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
5
|
from common import app_settings
|
|
6
|
+
from common.app_settings import MODULE_NAME, domain
|
|
7
|
+
from django.core.mail import EmailMultiAlternatives
|
|
8
|
+
from django.template import loader
|
|
6
9
|
|
|
7
10
|
campaign_ids = {
|
|
11
|
+
"admin_given": 1569057,
|
|
12
|
+
"admin_revoked": 1569071,
|
|
13
|
+
"delete_account": 1567477,
|
|
8
14
|
"email_change_notification": 1551600,
|
|
9
15
|
"email_change_verification": 1551594,
|
|
10
16
|
"reset_password": 1557153,
|
|
@@ -34,6 +40,41 @@ class EmailAttachment:
|
|
|
34
40
|
content: str
|
|
35
41
|
|
|
36
42
|
|
|
43
|
+
def django_send_email(
|
|
44
|
+
sender,
|
|
45
|
+
recipients,
|
|
46
|
+
subject,
|
|
47
|
+
text_content,
|
|
48
|
+
title,
|
|
49
|
+
replace_url=None,
|
|
50
|
+
plaintext_template="email.txt",
|
|
51
|
+
html_template="email.html",
|
|
52
|
+
):
|
|
53
|
+
# add in template for templates to message
|
|
54
|
+
|
|
55
|
+
# setup templates
|
|
56
|
+
plaintext = loader.get_template(plaintext_template)
|
|
57
|
+
html = loader.get_template(html_template)
|
|
58
|
+
plaintext_email_context = {"content": text_content}
|
|
59
|
+
html_email_context = {"content": text_content, "title": title, "url_prefix": domain()}
|
|
60
|
+
|
|
61
|
+
# render templates
|
|
62
|
+
plaintext_body = plaintext.render(plaintext_email_context)
|
|
63
|
+
original_html_body = html.render(html_email_context)
|
|
64
|
+
html_body = original_html_body
|
|
65
|
+
|
|
66
|
+
if replace_url:
|
|
67
|
+
verify_url = replace_url["verify_url"]
|
|
68
|
+
verify_replace_url = re.sub(f"(.*/verify_email/)(.*)", f"\\1", verify_url)
|
|
69
|
+
html_body = re.sub(f"({verify_url})(.*){verify_url}", f"\\1\\2{verify_replace_url}", original_html_body)
|
|
70
|
+
|
|
71
|
+
# make message using templates
|
|
72
|
+
message = EmailMultiAlternatives(subject, plaintext_body, sender, recipients)
|
|
73
|
+
message.attach_alternative(html_body, "text/html")
|
|
74
|
+
|
|
75
|
+
message.send()
|
|
76
|
+
|
|
77
|
+
|
|
37
78
|
# pylint: disable-next=too-many-arguments
|
|
38
79
|
def send_dotdigital_email(
|
|
39
80
|
campaign_id: int,
|
|
@@ -71,47 +112,51 @@ def send_dotdigital_email(
|
|
|
71
112
|
"""
|
|
72
113
|
# pylint: enable=line-too-long
|
|
73
114
|
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
# Dotdigital emails don't work locally, so if testing emails locally use Django to send a dummy email instead
|
|
116
|
+
if MODULE_NAME == "local":
|
|
117
|
+
django_send_email(from_address, to_addresses, "dummy_subject", "dummy_text_content", "dummy_title")
|
|
118
|
+
else:
|
|
119
|
+
if auth is None:
|
|
120
|
+
auth = app_settings.DOTDIGITAL_AUTH
|
|
121
|
+
|
|
122
|
+
body = {
|
|
123
|
+
"campaignId": campaign_id,
|
|
124
|
+
"toAddresses": to_addresses,
|
|
125
|
+
}
|
|
126
|
+
if cc_addresses is not None:
|
|
127
|
+
body["ccAddresses"] = cc_addresses
|
|
128
|
+
if bcc_addresses is not None:
|
|
129
|
+
body["bccAddresses"] = bcc_addresses
|
|
130
|
+
if from_address is not None:
|
|
131
|
+
body["fromAddress"] = from_address
|
|
132
|
+
if personalization_values is not None:
|
|
133
|
+
body["personalizationValues"] = [
|
|
134
|
+
{
|
|
135
|
+
"name": key,
|
|
136
|
+
"value": value,
|
|
137
|
+
}
|
|
138
|
+
for key, value in personalization_values.items()
|
|
139
|
+
]
|
|
140
|
+
if metadata is not None:
|
|
141
|
+
body["metadata"] = metadata
|
|
142
|
+
if attachments is not None:
|
|
143
|
+
body["attachments"] = [
|
|
144
|
+
{
|
|
145
|
+
"fileName": attachment.file_name,
|
|
146
|
+
"mimeType": attachment.mime_type,
|
|
147
|
+
"content": attachment.content,
|
|
148
|
+
}
|
|
149
|
+
for attachment in attachments
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
response = requests.post(
|
|
153
|
+
url=f"https://{region}-api.dotdigital.com/v2/email/triggered-campaign",
|
|
154
|
+
json=body,
|
|
155
|
+
headers={
|
|
156
|
+
"accept": "text/plain",
|
|
157
|
+
"authorization": auth,
|
|
158
|
+
},
|
|
159
|
+
timeout=timeout,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
assert response.ok, "Failed to send email." f" Reason: {response.reason}." f" Text: {response.text}."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codeforlife-portal
|
|
3
|
-
Version: 6.43.
|
|
3
|
+
Version: 6.43.2
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.8
|
|
6
6
|
Classifier: Framework :: Django
|
|
@@ -24,7 +24,7 @@ Requires-Dist: django-classy-tags ==2.0.0
|
|
|
24
24
|
Requires-Dist: libsass ==0.23.0
|
|
25
25
|
Requires-Dist: phonenumbers ==8.12.12
|
|
26
26
|
Requires-Dist: more-itertools ==8.7.0
|
|
27
|
-
Requires-Dist: cfl-common ==6.43.
|
|
27
|
+
Requires-Dist: cfl-common ==6.43.2
|
|
28
28
|
Requires-Dist: django-ratelimit ==3.0.1
|
|
29
29
|
Requires-Dist: django-preventconcurrentlogins ==0.8.2
|
|
30
30
|
Requires-Dist: django-csp ==3.7
|
|
@@ -5,14 +5,14 @@ cfl_common/common/app_settings.py,sha256=x2ROLY5Xl5LgqjxyTiChZvQorZYUXpFzEkaLsjh
|
|
|
5
5
|
cfl_common/common/apps.py,sha256=49UXZ3bSkFKvIEOL4zM7y1sAhccQJyRtsoOg5XVd_8Y,129
|
|
6
6
|
cfl_common/common/context_processors.py,sha256=X0iuX5qu9kMWa7q8osE9CJ2LgM7pPOYQFGdjm8X3rk0,236
|
|
7
7
|
cfl_common/common/csp_config.py,sha256=sZT6s9zMT5FFIqNODsURT0ifxbDgXpDlki8UxaBq2iE,2940
|
|
8
|
-
cfl_common/common/email_messages.py,sha256=
|
|
9
|
-
cfl_common/common/mail.py,sha256=
|
|
8
|
+
cfl_common/common/email_messages.py,sha256=J57n4krBAnwqacRjIzkO-qhbdlqNWwyJiRd8ozitm-M,3148
|
|
9
|
+
cfl_common/common/mail.py,sha256=11WqmB770MmGJcATa4dOeyGHSYLceV8zZDPx_4kK-zI,6021
|
|
10
10
|
cfl_common/common/models.py,sha256=EunFsc7sOWfWiFf4IQwuy56gu8pu3YpPoOgVtsMhbRM,14958
|
|
11
11
|
cfl_common/common/permissions.py,sha256=gC6RQGZI2QDBbglx-xr_V4Hl2C2nf1V2_uPmEuoEcJo,2416
|
|
12
12
|
cfl_common/common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
|
|
13
13
|
cfl_common/common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
cfl_common/common/helpers/data_migration_loader.py,sha256=_BhS5lPmhcuVUbryBmJytlWdHyT02KYyxPkHar32mOE,1748
|
|
15
|
-
cfl_common/common/helpers/emails.py,sha256=
|
|
15
|
+
cfl_common/common/helpers/emails.py,sha256=6xY8YxYY3ywcFWguys2G_36HTaBBah4ma1p20s4mpKU,10099
|
|
16
16
|
cfl_common/common/helpers/generators.py,sha256=kTL5e91I8wgmjJ-mu4jr9vIacjccUZ5pZSAz5cUNhdM,1505
|
|
17
17
|
cfl_common/common/helpers/organisation.py,sha256=e-JKumKoXrkMTzZPv0H4ViWL8vtCt7oXJjn_zZ1ec00,427
|
|
18
18
|
cfl_common/common/migrations/0001_initial.py,sha256=Y2kt2xmdCbrmDXCgqmhXeacicNg26Zj7L7SANSsgAAI,9664
|
|
@@ -106,7 +106,7 @@ example_project/portal_test_settings.py,sha256=frp_XMpd-z1g3VFCRxB2w7AaFW2ivRVKn
|
|
|
106
106
|
example_project/settings.py,sha256=XRZZvASoIl5a9xe3masTq_CUBleuJq9ByHx8f_e2UFc,5613
|
|
107
107
|
example_project/urls.py,sha256=OVeRQ-TCpzHISBRuzqD0yd3ewF7H5U3c-f2p2alfUD0,430
|
|
108
108
|
example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
|
|
109
|
-
portal/__init__.py,sha256=
|
|
109
|
+
portal/__init__.py,sha256=hjhQopF2UeY6Ek3KDmWtiEswta3fxltdCEalD4TaXFA,23
|
|
110
110
|
portal/admin.py,sha256=k5Hsiln43DlVPoufnrx5AXWu_RijX8xi_n7wwBuuCJo,5132
|
|
111
111
|
portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
|
|
112
112
|
portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
|
|
@@ -544,18 +544,18 @@ portal/tests/test_class.py,sha256=V6Fkc6PqdisefKD3xs9PbfE2pKp-9e0gwQVkPUiu6bk,14
|
|
|
544
544
|
portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
|
|
545
545
|
portal/tests/test_emails.py,sha256=Y26VjhPOzc2aptHBSmOW9R-do3k1QaRqUc5MiwgsAQk,9156
|
|
546
546
|
portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
|
|
547
|
-
portal/tests/test_independent_student.py,sha256=
|
|
547
|
+
portal/tests/test_independent_student.py,sha256=rW3OTFWxeEHm5tSumr1L4MRkUsEgESfhapU6M3bfNhc,26437
|
|
548
548
|
portal/tests/test_invite_teacher.py,sha256=oeOaoJV1IqJSYPlaPFjnhVXdB2mq8otCTLp_lfjuCfk,12224
|
|
549
549
|
portal/tests/test_middleware.py,sha256=b6jfNmiRZ2snqLKsyJUG-RivoX5fmrqLlQkG9MeVnqM,8034
|
|
550
550
|
portal/tests/test_newsletter_footer.py,sha256=MdVUX53mEoDTa4Krq-jg9LFNo-QyghqvTvhHeNXBGnE,838
|
|
551
|
-
portal/tests/test_organisation.py,sha256=
|
|
551
|
+
portal/tests/test_organisation.py,sha256=kCMUNzLN6EzaMUBcFkqXwnqLGgOuQxQWIHHt63nhqBs,7574
|
|
552
552
|
portal/tests/test_partials.py,sha256=ydh1nef6BqvMfah2BSBS9QDiKY0xopY74k_W1YVobAE,3687
|
|
553
553
|
portal/tests/test_ratelimit.py,sha256=XWq1A9XgRrlcMHibGoJ0kc4gLc5U_u5UhKHjthxCfYA,19376
|
|
554
554
|
portal/tests/test_school_student.py,sha256=bFZwY4twaFHQLp0cltMq8cLNDZGgCHTZBCZHK0JcV8s,8604
|
|
555
555
|
portal/tests/test_security.py,sha256=FGrlRfnzi-Xx2_bn4fTZlYORKm7w_GhGkD3havvplwc,3239
|
|
556
556
|
portal/tests/test_teacher.py,sha256=_VmQCWq07uCFbvq6Vd7GN00mE7vY7WNMeQTk6bHxFPI,36898
|
|
557
557
|
portal/tests/test_teacher_student.py,sha256=NWITbUw1kijqu3c8eRHLHJKaYQMOsOMvl7PAVx5QghI,21567
|
|
558
|
-
portal/tests/test_views.py,sha256=
|
|
558
|
+
portal/tests/test_views.py,sha256=IElQJnp9fhlmqR-54nBycul2uUIjsG9dTRnYy5SxXJ4,39278
|
|
559
559
|
portal/tests/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
560
560
|
portal/tests/migrations/test_migration_make_portaladmin_teacher.py,sha256=ekMRb6cU97oT0k9gCKW7IUB7oPuGmv4uWJCqInQN7x8,2589
|
|
561
561
|
portal/tests/migrations/test_migration_preview_user_remove.py,sha256=K6D-FZT9YFEA8oMxHz9VTglVV6MZOTRYVlvwWwXc2vU,555
|
|
@@ -622,7 +622,7 @@ portal/views/home.py,sha256=J_kb0Kv5Ubk9yQ4r0IAvhPV5o_iezcaqA4Z5bd0-Idc,9869
|
|
|
622
622
|
portal/views/legal.py,sha256=nUunsTHnhMcXBcDlg1GmUal86k9Vhinne4A2FWfq78M,342
|
|
623
623
|
portal/views/organisation.py,sha256=sPDbiM7hdtpF8GKyh_4n4VPl2a-WnAgnF4q9aSvQCVI,3341
|
|
624
624
|
portal/views/play_landing_page.py,sha256=FFmjUFub3ZdlbMqkB8yX3jAImCzqrUqgb8AZcpKywZ4,308
|
|
625
|
-
portal/views/registration.py,sha256=
|
|
625
|
+
portal/views/registration.py,sha256=L9AzIG2nOU946cSOXmUMQRtDo3uxApHX-0ceXopbOCw,10888
|
|
626
626
|
portal/views/teach.py,sha256=nzlyTcgq9ImAjnqrF3esqi212qBLH5Ww1LKE2gSjoRY,210
|
|
627
627
|
portal/views/aimmo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
628
628
|
portal/views/aimmo/dashboard.py,sha256=YMOzonNE87OEP5lThY4BrF0rNyvIYpDInh_sM-n38AM,4046
|
|
@@ -633,17 +633,17 @@ portal/views/login/independent_student.py,sha256=3dFULhwMAlX4VDrJl-Znril6a9M5xKB
|
|
|
633
633
|
portal/views/login/student.py,sha256=dt6cMfWepBJsVCRcADltfYSHVpyeP1WGLKSogMJ22E0,5539
|
|
634
634
|
portal/views/login/teacher.py,sha256=kRugP7TPbZIb_BmYMYxFeugxZy8UbCry_q0_jJDJ_Mw,1975
|
|
635
635
|
portal/views/student/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
636
|
-
portal/views/student/edit_account_details.py,sha256=
|
|
636
|
+
portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B8D5SN8hOpLiGa9U,8468
|
|
637
637
|
portal/views/student/play.py,sha256=-v9lBjHF3_PAKRgWcCGGt_bEOpIkmJDJnzgR5JvqrMo,8908
|
|
638
638
|
portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
639
|
-
portal/views/teacher/dashboard.py,sha256=
|
|
639
|
+
portal/views/teacher/dashboard.py,sha256=nNA7XxylunLjyCpKmq7h_CYITi7wM3YnmjqzUL0A-bI,25236
|
|
640
640
|
portal/views/teacher/teach.py,sha256=B71jReMJ4BYFmo7NtJVK3-4DeXEwxfu_WA3Ij1RYzdI,34725
|
|
641
641
|
portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
642
642
|
portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
|
|
643
643
|
portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
|
|
644
644
|
portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
|
|
645
|
-
codeforlife_portal-6.43.
|
|
646
|
-
codeforlife_portal-6.43.
|
|
647
|
-
codeforlife_portal-6.43.
|
|
648
|
-
codeforlife_portal-6.43.
|
|
649
|
-
codeforlife_portal-6.43.
|
|
645
|
+
codeforlife_portal-6.43.2.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
|
|
646
|
+
codeforlife_portal-6.43.2.dist-info/METADATA,sha256=kUAWYPqcsp2m4LS4Kh6PwIr_K7GbBPgZA6qvvbO3XQE,1137
|
|
647
|
+
codeforlife_portal-6.43.2.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
648
|
+
codeforlife_portal-6.43.2.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
|
|
649
|
+
codeforlife_portal-6.43.2.dist-info/RECORD,,
|
portal/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "6.43.
|
|
1
|
+
__version__ = "6.43.2"
|
|
@@ -169,7 +169,8 @@ class TestIndependentStudent(TestCase):
|
|
|
169
169
|
|
|
170
170
|
# Class for Selenium tests. We plan to replace these and turn them into Cypress tests
|
|
171
171
|
class TestIndependentStudentFrontend(BaseTest):
|
|
172
|
-
|
|
172
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
173
|
+
def test_delete_indy_account(self, mock_send_dotdigital_email: Mock):
|
|
173
174
|
page = self.go_to_homepage()
|
|
174
175
|
page, _, _, email, password = create_independent_student(page)
|
|
175
176
|
page = page.independent_student_login(email, password)
|
|
@@ -214,7 +215,7 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
214
215
|
assert not User.objects.get(id=user_id).is_active
|
|
215
216
|
|
|
216
217
|
# check if email has been sent
|
|
217
|
-
|
|
218
|
+
mock_send_dotdigital_email.assert_called_once_with(campaign_ids["delete_account"], ANY)
|
|
218
219
|
|
|
219
220
|
def test_signup_without_newsletter(self):
|
|
220
221
|
page = self.go_to_homepage()
|
|
@@ -4,9 +4,11 @@ import time
|
|
|
4
4
|
|
|
5
5
|
from common.models import Teacher
|
|
6
6
|
from common.tests.utils.classes import create_class_directly
|
|
7
|
-
from common.tests.utils.organisation import (
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
from common.tests.utils.organisation import (
|
|
8
|
+
create_organisation,
|
|
9
|
+
create_organisation_directly,
|
|
10
|
+
join_teacher_to_organisation,
|
|
11
|
+
)
|
|
10
12
|
from common.tests.utils.student import create_school_student_directly
|
|
11
13
|
from common.tests.utils.teacher import signup_teacher_directly
|
|
12
14
|
from selenium.webdriver.common.by import By
|
portal/tests/test_views.py
CHANGED
|
@@ -572,7 +572,8 @@ class TestViews(TestCase):
|
|
|
572
572
|
assert response.status_code == 200
|
|
573
573
|
assert response.context_data == EXPECTED_DATA_WITH_KURONO_GAME
|
|
574
574
|
|
|
575
|
-
|
|
575
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
576
|
+
def test_delete_account(self, mock_send_dotdigital_email: Mock):
|
|
576
577
|
email, password = signup_teacher_directly()
|
|
577
578
|
u = User.objects.get(email=email)
|
|
578
579
|
usrid = u.id
|
|
@@ -593,7 +594,7 @@ class TestViews(TestCase):
|
|
|
593
594
|
response = c.post(url, {"password": "wrongPassword"})
|
|
594
595
|
|
|
595
596
|
assert response.status_code == 302
|
|
596
|
-
|
|
597
|
+
mock_send_dotdigital_email.assert_not_called()
|
|
597
598
|
|
|
598
599
|
# user has not been anonymised
|
|
599
600
|
u = User.objects.get(email=email)
|
|
@@ -604,6 +605,7 @@ class TestViews(TestCase):
|
|
|
604
605
|
response = c.post(url, {"password": password, "unsubscribe_newsletter": "on"})
|
|
605
606
|
|
|
606
607
|
assert response.status_code == 302
|
|
608
|
+
mock_send_dotdigital_email.assert_called_once()
|
|
607
609
|
assert response.url == reverse("home")
|
|
608
610
|
|
|
609
611
|
# user has been anonymised
|
|
@@ -611,7 +613,10 @@ class TestViews(TestCase):
|
|
|
611
613
|
assert u.first_name == "Deleted"
|
|
612
614
|
assert not u.is_active
|
|
613
615
|
|
|
614
|
-
|
|
616
|
+
assert c.login(username=email, password=password) == False
|
|
617
|
+
|
|
618
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
619
|
+
def test_delete_account_admin(self, mock_send_dotdigital_email: Mock):
|
|
615
620
|
"""test the passing of admin role after deletion of an admin account"""
|
|
616
621
|
|
|
617
622
|
email1, password1 = signup_teacher_directly()
|
|
@@ -666,6 +671,7 @@ class TestViews(TestCase):
|
|
|
666
671
|
# delete teacher1 account
|
|
667
672
|
url = reverse("delete_account")
|
|
668
673
|
c.post(url, {"password": password1})
|
|
674
|
+
mock_send_dotdigital_email.assert_called_once()
|
|
669
675
|
|
|
670
676
|
# user has been anonymised
|
|
671
677
|
u = User.objects.get(id=usrid1)
|
|
@@ -706,6 +712,7 @@ class TestViews(TestCase):
|
|
|
706
712
|
# now delete teacher3 account
|
|
707
713
|
url = reverse("delete_account")
|
|
708
714
|
c.post(url, {"password": password3})
|
|
715
|
+
self.assertEqual(mock_send_dotdigital_email.call_count, 2)
|
|
709
716
|
|
|
710
717
|
# 2 teachers left
|
|
711
718
|
teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
|
|
@@ -738,6 +745,7 @@ class TestViews(TestCase):
|
|
|
738
745
|
|
|
739
746
|
url = reverse("delete_account")
|
|
740
747
|
c.post(url, {"password": password2})
|
|
748
|
+
self.assertEqual(mock_send_dotdigital_email.call_count, 3)
|
|
741
749
|
|
|
742
750
|
# school should be anonymised
|
|
743
751
|
school = School._base_manager.get(id=school_id)
|
portal/views/registration.py
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import re
|
|
3
|
-
|
|
4
|
-
from common.email_messages import accountDeletionEmail
|
|
5
|
-
from portal.views.login import has_user_lockout_expired
|
|
6
|
-
|
|
7
|
-
from django.contrib.auth.models import User
|
|
8
3
|
from datetime import datetime
|
|
4
|
+
|
|
9
5
|
from common.helpers.emails import (
|
|
10
|
-
delete_contact,
|
|
11
6
|
NOTIFICATION_EMAIL,
|
|
12
7
|
PASSWORD_RESET_EMAIL,
|
|
8
|
+
delete_contact,
|
|
13
9
|
send_email,
|
|
14
10
|
)
|
|
15
|
-
from common.
|
|
16
|
-
from common.
|
|
17
|
-
|
|
11
|
+
from common.mail import campaign_ids, send_dotdigital_email
|
|
12
|
+
from common.models import DailyActivity, Student, Teacher
|
|
13
|
+
from common.permissions import not_fully_logged_in, not_logged_in
|
|
18
14
|
from django.contrib import messages as messages
|
|
19
15
|
from django.contrib.auth import get_user_model
|
|
20
|
-
from django.contrib.auth.decorators import
|
|
16
|
+
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
21
17
|
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
|
|
18
|
+
from django.contrib.auth.models import User
|
|
22
19
|
from django.contrib.auth.tokens import default_token_generator
|
|
23
20
|
from django.http import HttpResponseRedirect
|
|
24
21
|
from django.shortcuts import render
|
|
@@ -35,14 +32,15 @@ from django.views.decorators.http import require_POST
|
|
|
35
32
|
from deploy import captcha
|
|
36
33
|
from portal import app_settings
|
|
37
34
|
from portal.forms.registration import (
|
|
38
|
-
TeacherPasswordResetForm,
|
|
39
|
-
TeacherPasswordResetSetPasswordForm,
|
|
40
35
|
StudentPasswordResetForm,
|
|
41
36
|
StudentPasswordResetSetPasswordForm,
|
|
37
|
+
TeacherPasswordResetForm,
|
|
38
|
+
TeacherPasswordResetSetPasswordForm,
|
|
42
39
|
)
|
|
43
40
|
from portal.helpers.captcha import remove_captcha_from_form
|
|
44
41
|
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
45
42
|
from portal.views.api import anonymise
|
|
43
|
+
from portal.views.login import has_user_lockout_expired
|
|
46
44
|
|
|
47
45
|
|
|
48
46
|
@user_passes_test(not_logged_in, login_url=reverse_lazy("home"))
|
|
@@ -309,13 +307,6 @@ def delete_account(request):
|
|
|
309
307
|
delete_contact(email)
|
|
310
308
|
|
|
311
309
|
# send confirmation email
|
|
312
|
-
|
|
313
|
-
send_email(
|
|
314
|
-
NOTIFICATION_EMAIL,
|
|
315
|
-
[email],
|
|
316
|
-
message["subject"],
|
|
317
|
-
message["message"],
|
|
318
|
-
message["title"],
|
|
319
|
-
)
|
|
310
|
+
send_dotdigital_email(campaign_ids["delete_account"], [email])
|
|
320
311
|
|
|
321
312
|
return HttpResponseRedirect(reverse_lazy("home"))
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
from common.
|
|
2
|
-
|
|
1
|
+
from common.helpers.emails import (
|
|
2
|
+
NOTIFICATION_EMAIL,
|
|
3
|
+
delete_contact,
|
|
4
|
+
send_email,
|
|
5
|
+
update_indy_email,
|
|
6
|
+
)
|
|
3
7
|
from common.models import Student
|
|
4
8
|
from common.permissions import logged_in_as_student
|
|
5
9
|
from django.contrib import messages as messages
|
|
@@ -7,17 +11,15 @@ from django.contrib.auth import logout
|
|
|
7
11
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
8
12
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
9
13
|
from django.http import HttpResponseRedirect
|
|
14
|
+
from django.shortcuts import render
|
|
10
15
|
from django.urls import reverse_lazy
|
|
11
16
|
from django.views.generic.edit import FormView
|
|
12
|
-
from django.shortcuts import render
|
|
13
17
|
|
|
14
|
-
from portal.forms.play import
|
|
18
|
+
from portal.forms.play import IndependentStudentEditAccountForm, StudentEditAccountForm
|
|
15
19
|
from portal.forms.registration import DeleteAccountForm
|
|
16
|
-
|
|
17
20
|
from portal.helpers.password import check_update_password
|
|
18
21
|
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
19
22
|
from portal.views.api import anonymise
|
|
20
|
-
from django.contrib import messages as messages
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def _get_form(self, form_class):
|
|
@@ -2,12 +2,24 @@ from datetime import timedelta
|
|
|
2
2
|
from uuid import uuid4
|
|
3
3
|
|
|
4
4
|
from common import email_messages
|
|
5
|
-
from common.helpers.emails import (
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
from common.helpers.emails import (
|
|
6
|
+
INVITE_FROM,
|
|
7
|
+
NOTIFICATION_EMAIL,
|
|
8
|
+
DotmailerUserType,
|
|
9
|
+
add_to_dotmailer,
|
|
10
|
+
generate_token,
|
|
11
|
+
send_email,
|
|
12
|
+
update_email,
|
|
13
|
+
)
|
|
8
14
|
from common.helpers.generators import get_random_username
|
|
9
|
-
from common.
|
|
10
|
-
|
|
15
|
+
from common.mail import campaign_ids, send_dotdigital_email
|
|
16
|
+
from common.models import (
|
|
17
|
+
Class,
|
|
18
|
+
JoinReleaseStudent,
|
|
19
|
+
SchoolTeacherInvitation,
|
|
20
|
+
Student,
|
|
21
|
+
Teacher,
|
|
22
|
+
)
|
|
11
23
|
from common.permissions import check_teacher_authorised, logged_in_as_teacher
|
|
12
24
|
from common.utils import using_two_factor
|
|
13
25
|
from django.contrib import messages as messages
|
|
@@ -16,7 +28,7 @@ from django.contrib.auth.decorators import login_required, user_passes_test
|
|
|
16
28
|
from django.contrib.auth.models import User
|
|
17
29
|
from django.http import Http404, HttpResponseRedirect
|
|
18
30
|
from django.shortcuts import get_object_or_404, render
|
|
19
|
-
from django.urls import reverse_lazy
|
|
31
|
+
from django.urls import reverse, reverse_lazy
|
|
20
32
|
from django.utils import timezone
|
|
21
33
|
from django.views.decorators.http import require_POST
|
|
22
34
|
from game.level_management import levels_shared_with, unshare_level
|
|
@@ -25,14 +37,20 @@ from two_factor.utils import devices_for_user
|
|
|
25
37
|
from portal.forms.invite_teacher import InviteTeacherForm
|
|
26
38
|
from portal.forms.organisation import OrganisationForm
|
|
27
39
|
from portal.forms.registration import DeleteAccountForm
|
|
28
|
-
from portal.forms.teach import (
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
from portal.forms.teach import (
|
|
41
|
+
ClassCreationForm,
|
|
42
|
+
InvitedTeacherForm,
|
|
43
|
+
TeacherAddExternalStudentForm,
|
|
44
|
+
TeacherEditAccountForm,
|
|
45
|
+
)
|
|
31
46
|
from portal.helpers.decorators import ratelimit
|
|
32
47
|
from portal.helpers.password import check_update_password
|
|
33
|
-
from portal.helpers.ratelimit import (
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
from portal.helpers.ratelimit import (
|
|
49
|
+
RATELIMIT_LOGIN_GROUP,
|
|
50
|
+
RATELIMIT_LOGIN_RATE,
|
|
51
|
+
RATELIMIT_METHOD,
|
|
52
|
+
clear_ratelimit_cache_for_user,
|
|
53
|
+
)
|
|
36
54
|
|
|
37
55
|
from .teach import create_class
|
|
38
56
|
|
|
@@ -380,19 +398,22 @@ def invite_toggle_admin(request, invite_id):
|
|
|
380
398
|
|
|
381
399
|
if invite.invited_teacher_is_admin:
|
|
382
400
|
messages.success(request, "Administrator invite status has been given successfully")
|
|
383
|
-
|
|
401
|
+
send_dotdigital_email(
|
|
402
|
+
campaign_ids["admin_given"],
|
|
403
|
+
[invite.invited_teacher_email],
|
|
404
|
+
personalization_values={
|
|
405
|
+
"SCHOOL_CLUB_NAME": invite.school,
|
|
406
|
+
"MANAGEMENT_LINK": request.build_absolute_uri(reverse("dashboard")),
|
|
407
|
+
},
|
|
408
|
+
)
|
|
384
409
|
|
|
385
410
|
else:
|
|
386
411
|
messages.success(request, "Administrator invite status has been revoked successfully")
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
emailMessage["subject"],
|
|
393
|
-
emailMessage["message"],
|
|
394
|
-
emailMessage["subject"],
|
|
395
|
-
)
|
|
412
|
+
send_dotdigital_email(
|
|
413
|
+
campaign_ids["admin_revoked"],
|
|
414
|
+
[invite.invited_teacher_email],
|
|
415
|
+
personalization_values={"SCHOOL_CLUB_NAME": invite.school},
|
|
416
|
+
)
|
|
396
417
|
|
|
397
418
|
return HttpResponseRedirect(reverse_lazy("dashboard"))
|
|
398
419
|
|
|
@@ -411,7 +432,14 @@ def organisation_toggle_admin(request, pk):
|
|
|
411
432
|
|
|
412
433
|
if teacher.is_admin:
|
|
413
434
|
messages.success(request, "Administrator status has been given successfully.")
|
|
414
|
-
|
|
435
|
+
send_dotdigital_email(
|
|
436
|
+
campaign_ids["admin_given"],
|
|
437
|
+
[teacher.new_user.email],
|
|
438
|
+
personalization_values={
|
|
439
|
+
"SCHOOL_CLUB_NAME": teacher.school.name,
|
|
440
|
+
"MANAGEMENT_LINK": request.build_absolute_uri(reverse("dashboard")),
|
|
441
|
+
},
|
|
442
|
+
)
|
|
415
443
|
else:
|
|
416
444
|
# Remove access to all levels that are from other teachers' students
|
|
417
445
|
[
|
|
@@ -420,15 +448,11 @@ def organisation_toggle_admin(request, pk):
|
|
|
420
448
|
if hasattr(level.owner, "student") and not teacher.teaches(level.owner)
|
|
421
449
|
]
|
|
422
450
|
messages.success(request, "Administrator status has been revoked successfully.")
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
email_message["subject"],
|
|
429
|
-
email_message["message"],
|
|
430
|
-
email_message["subject"],
|
|
431
|
-
)
|
|
451
|
+
send_dotdigital_email(
|
|
452
|
+
campaign_ids["admin_revoked"],
|
|
453
|
+
[teacher.new_user.email],
|
|
454
|
+
personalization_values={"SCHOOL_CLUB_NAME": teacher.school.name},
|
|
455
|
+
)
|
|
432
456
|
|
|
433
457
|
return HttpResponseRedirect(reverse_lazy("dashboard"))
|
|
434
458
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|