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.

@@ -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.app_settings import domain
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 auth is None:
75
- auth = app_settings.DOTDIGITAL_AUTH
76
-
77
- body = {
78
- "campaignId": campaign_id,
79
- "toAddresses": to_addresses,
80
- }
81
- if cc_addresses is not None:
82
- body["ccAddresses"] = cc_addresses
83
- if bcc_addresses is not None:
84
- body["bccAddresses"] = bcc_addresses
85
- if from_address is not None:
86
- body["fromAddress"] = from_address
87
- if personalization_values is not None:
88
- body["personalizationValues"] = [
89
- {
90
- "name": key,
91
- "value": value,
92
- }
93
- for key, value in personalization_values.items()
94
- ]
95
- if metadata is not None:
96
- body["metadata"] = metadata
97
- if attachments is not None:
98
- body["attachments"] = [
99
- {
100
- "fileName": attachment.file_name,
101
- "mimeType": attachment.mime_type,
102
- "content": attachment.content,
103
- }
104
- for attachment in attachments
105
- ]
106
-
107
- response = requests.post(
108
- url=f"https://{region}-api.dotdigital.com/v2/email/triggered-campaign",
109
- json=body,
110
- headers={
111
- "accept": "text/plain",
112
- "authorization": auth,
113
- },
114
- timeout=timeout,
115
- )
116
-
117
- assert response.ok, "Failed to send email." f" Reason: {response.reason}." f" Text: {response.text}."
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.0
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.0
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=DRiz6MCKUGdFsC-pN9EwFqzPhpzMWXaT9HPcji1BkvE,4437
9
- cfl_common/common/mail.py,sha256=5iwvedYfaJUv7v8vVpV1kyBtnw04EJhHPy3FRGI9WHM,4223
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=u2X2brjHIlUDNIgQ6-Ld23Y4zyouJGTFH-G7HNQDBYs,11041
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=QcuQSlJPY-R1FVgen2wz3xT3Zw5oZBeORJW5UieLYIU,23
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=ysWpkYiwjPdB7gO3ow-5JuxqLi0IywRSxG1s3rKzTK0,26282
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=fOtck-0MkPM2F0V4RFH-QUeWEk6yUIXDv_GI5cl8sdg,7649
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=6y4ICpo5KPTWQed3J1Hg74ZGBKI2y3-HNHAowOsge80,38769
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=oGHec_NvBTRd--4pAXtCevteAj6ivF8PKwbwul46Mmk,11011
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=keMakqgqy5xB76QbpwsnkadxbMg_dGsAxLuP2CoWbvc,8551
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=_zKAEIaMwCI5LdF5QN2kLy59UTkNhNnx5mYhe-mfdVA,24991
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.0.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
646
- codeforlife_portal-6.43.0.dist-info/METADATA,sha256=YnRGpJH354nDAr6qgrjVk7parxVg530N5gvYp6EtDNk,1137
647
- codeforlife_portal-6.43.0.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
648
- codeforlife_portal-6.43.0.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
649
- codeforlife_portal-6.43.0.dist-info/RECORD,,
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.0"
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
- def test_delete_indy_account(self):
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
- assert len(mail.outbox) == 1
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 (create_organisation,
8
- create_organisation_directly,
9
- join_teacher_to_organisation)
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
@@ -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
- def test_delete_account(self):
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
- assert response.url == reverse("dashboard")
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
- def test_delete_account_admin(self):
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)
@@ -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.models import Teacher, Student, DailyActivity
16
- from common.permissions import not_logged_in, not_fully_logged_in
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 user_passes_test, login_required
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
- message = accountDeletionEmail(request)
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.email_messages import accountDeletionEmail
2
- from common.helpers.emails import NOTIFICATION_EMAIL, delete_contact, send_email, update_indy_email
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 StudentEditAccountForm, IndependentStudentEditAccountForm
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 (INVITE_FROM, NOTIFICATION_EMAIL,
6
- DotmailerUserType, add_to_dotmailer,
7
- generate_token, send_email, update_email)
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.models import (Class, JoinReleaseStudent, SchoolTeacherInvitation,
10
- Student, Teacher)
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 (ClassCreationForm, InvitedTeacherForm,
29
- TeacherAddExternalStudentForm,
30
- TeacherEditAccountForm)
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 (RATELIMIT_LOGIN_GROUP,
34
- RATELIMIT_LOGIN_RATE, RATELIMIT_METHOD,
35
- clear_ratelimit_cache_for_user)
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
- emailMessage = email_messages.adminGivenEmail(request, invite.school)
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
- emailMessage = email_messages.adminRevokedEmail(request, invite.school)
388
-
389
- send_email(
390
- NOTIFICATION_EMAIL,
391
- [invite.invited_teacher_email],
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
- email_message = email_messages.adminGivenEmail(request, teacher.school.name)
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
- email_message = email_messages.adminRevokedEmail(request, teacher.school.name)
424
-
425
- send_email(
426
- NOTIFICATION_EMAIL,
427
- [teacher.new_user.email],
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