codeforlife-portal 7.4.0__py2.py3-none-any.whl → 7.4.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codeforlife-portal
3
- Version: 7.4.0
3
+ Version: 7.4.2
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Framework :: Django
@@ -21,7 +21,7 @@ Requires-Dist: django-classy-tags==2.0.0
21
21
  Requires-Dist: libsass==0.23.0
22
22
  Requires-Dist: phonenumbers==8.12.12
23
23
  Requires-Dist: more-itertools==8.7.0
24
- Requires-Dist: cfl-common==7.4.0
24
+ Requires-Dist: cfl-common==7.4.2
25
25
  Requires-Dist: django-ratelimit==3.0.1
26
26
  Requires-Dist: django-preventconcurrentlogins==0.8.2
27
27
  Requires-Dist: django-csp==3.7
@@ -108,7 +108,7 @@ example_project/portal_test_settings.py,sha256=_gI7-HMoPJ-cDGO6n5UlEIHKHuTR5SC_X
108
108
  example_project/settings.py,sha256=HgGSG0n6Xggd0NieFVoJgn8vKGqyRbCddoB3CRuoT-Y,5640
109
109
  example_project/urls.py,sha256=3SsP5jvPAXV5xmduka4zbSZB5PbXggjsalu1ojY5KIo,434
110
110
  example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
111
- portal/__init__.py,sha256=Dbq9JbNzDz-GmfE0eozgGmdee4W8D3uVIIQv1bzHZPk,22
111
+ portal/__init__.py,sha256=sM2nJxMmGij5RSBxjI6nK_EBgrH8US-_qbfODFsue-A,22
112
112
  portal/admin.py,sha256=on1-zNRnZvf2cwBN6GVRVYRhkaksrCgfzX8XPWtkvz8,6062
113
113
  portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
114
114
  portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
@@ -259,7 +259,6 @@ portal/static/portal/img/thumbnail_python_den.png,sha256=0if8hLRK9tR-oII6IEuRRq2
259
259
  portal/static/portal/img/twitter.png,sha256=QD2ckrIWgV6WpuV0R7p717_T9a-DibzIxXsqHbvDR48,15969
260
260
  portal/static/portal/img/universities.png,sha256=y015DeiJzJyIAEC-qIGY5HgsXJDr_DhUXOx8_2xbxf0,145961
261
261
  portal/static/portal/img/wes.png,sha256=z2Z-vqqzIPkHY0PN7pgb7Im8CU0w9p2bc9VNRXGxtd0,54619
262
- portal/static/portal/img/x_close_video.png,sha256=R6YR7j0axvkIdfB2u17hvbgTWA1sCk_0GREBRfFu3aY,1051
263
262
  portal/static/portal/img/colorboxImages/border.png,sha256=Eb2D9kRqG0Gw2I3bLicfzJkSshDXf0DjTl4x4amvF0o,112
264
263
  portal/static/portal/img/colorboxImages/controls.png,sha256=zQowXWoW0otiA3-wj5sGLdGgpqO5cNj5Xs69VvdAZ-k,2893
265
264
  portal/static/portal/img/colorboxImages/loading.gif,sha256=NO9VJC_CTJTweQkCwJYB0ijpB0v3ofiMTeajm0DOOPo,9427
@@ -293,7 +292,7 @@ portal/static/portal/js/lib/jquery.min.js,sha256=2Pmvv0kuTBOenSvLm6bvfBSSHrUJ-3A
293
292
  portal/static/portal/js/lib/modernizr-build.js,sha256=pGqgo-dxjy43hd1yL75ivaH9xWF9qfVKLlD4EYlTzJ4,36698
294
293
  portal/static/portal/js/lib/papaparse.min.js,sha256=tDf9H6Pr0oXtuyG0hGdMQ5OU5EuM5IK8WMK2iTbdvd0,18829
295
294
  portal/static/portal/sass/bootstrap.scss,sha256=q_gMkiqFVJBrMKNr0kXaP7y4caychGNGkyEyEUUFN3k,2144
296
- portal/static/portal/sass/colorbox.scss,sha256=ZSilgJLSC_ZIMp2Wb5zJvwR-cyRbmcDPy93Kt78VbIM,3790
295
+ portal/static/portal/sass/colorbox.scss,sha256=DqEMSAPBfBwNGY1acoh9fCW9PPY-SchjKW7sNMxd9UM,3637
297
296
  portal/static/portal/sass/styles.scss,sha256=gTv8yjfzrN1acywPBkS9b49648LUGN4xvkwqAK-gAvc,780
298
297
  portal/static/portal/sass/bootstrap/_alerts.scss,sha256=C5kv4Wt7RFE3ESibknOeFzbE79it0qfwV59y_yaeImE,1546
299
298
  portal/static/portal/sass/bootstrap/_badges.scss,sha256=lHaAb3wWe4sh5kwNTsni1f0PCkNXBTfcVbakJTzoAYY,1228
@@ -436,7 +435,7 @@ portal/templates/captcha/includes/js_v2_invisible.html,sha256=FUyDf1RDyS7whzW2B_
436
435
  portal/templates/portal/about.html,sha256=_iD0GCP6q3-XuZ2LC-9O0KYY-mKL6c9qk3O-NRRqsRM,10321
437
436
  portal/templates/portal/base.html,sha256=4xLACNgKmRQTdEsdNNGYhLzMozzYzWIIzk31HrLGBf0,12109
438
437
  portal/templates/portal/base_no_userprofile.html,sha256=PlRufyYmUUGWBZ6CvbYhJWOMTqKqdcee4xnO5--AogA,447
439
- portal/templates/portal/coding_club.html,sha256=aaLx9UQ8p_7Vvy3Vlh1rOufeNN0KhJ6fj4oqI6QqJcM,4518
438
+ portal/templates/portal/coding_club.html,sha256=A9_hiD-ChBRnictFvAaQiSgSANfrnQ_zFY6hqBtK2rg,5767
440
439
  portal/templates/portal/contribute.html,sha256=UIC_N3Lun9wB_6jEra0wvlT9fDiiIeMby7_onXYo6k0,4176
441
440
  portal/templates/portal/dotmailer_consent_form.html,sha256=UDdizPoKYZGybr6z9nzDV4WPhJPa-S3bIKywvVgFrxg,918
442
441
  portal/templates/portal/email_invitation_sent.html,sha256=hAMzQXE3NFGnOsQlCGuo3Aps-vazSJb5BhAN7bWT48M,641
@@ -457,9 +456,9 @@ portal/templates/portal/reset_password.html,sha256=YzsREz5D2OwhicMLahVOVDXiNDxoH
457
456
  portal/templates/portal/reset_password_confirm.html,sha256=jPHSDatezRXzCG4zH_5BQPWAxLblidqro0hzvsH54ho,3499
458
457
  portal/templates/portal/reset_password_done.html,sha256=rpzN3svZne5H2FS3TJaGnHypRj2KX-SRS6DbFQkgLf0,667
459
458
  portal/templates/portal/reset_password_email_sent.html,sha256=kwNvoH_M-Qd5s-g0HiULwfQt_SD12WEMgPRSnLmldTQ,914
460
- portal/templates/portal/teach.html,sha256=bRzwfmrZr53G5zr_QF7l5peT9r5e9z7dhgIFL3POzrQ,9006
459
+ portal/templates/portal/teach.html,sha256=2qOWzA00evHYlwSvXDOz8VQo1yXB-95Db7KgqpOItm0,9006
461
460
  portal/templates/portal/ten_year_map.html,sha256=_NxEXDJMGdtguuZ3vYl00yBgYo12wv1w8QJ-LMhw8fg,8390
462
- portal/templates/portal/terms.html,sha256=SO8ruLF2kdYpOinxlVx1CHfPNgphemX4wvWJ9-W6Nj4,29155
461
+ portal/templates/portal/terms.html,sha256=ekhBnhJVHcr5BE5W4PZ-YNzGsmhvRTrj1gdpdNdSYRk,29314
463
462
  portal/templates/portal/login/independent_student.html,sha256=G4u82m0lko4yQvqRDMger5p63A6KtFI_bsEOsQWlyJw,1777
464
463
  portal/templates/portal/login/student.html,sha256=s5rUyT-u7gei2xWQ-Ba35ePC5WbtwReEzUZAKwUoZVY,1261
465
464
  portal/templates/portal/login/student_class_code.html,sha256=rJr6NB4Oz7XjZXDOUHpwzT6r6yxh7nZ5joca-z2bK-s,966
@@ -536,7 +535,6 @@ portal/tests/test_admin.py,sha256=AM2dgv8j9m4L-SDO-sMA9tQvQH9GwRBrlwRG9OgqtfI,14
536
535
  portal/tests/test_api.py,sha256=Yo5s_nEGOoG35jA39yZ6nuDOUZvuCZ8o8o8XhZos61w,13819
537
536
  portal/tests/test_captcha_forms.py,sha256=lirhIli-sHovun8VdrF0he7KRFTAd8DMCpkJ8cQNotg,1015
538
537
  portal/tests/test_class.py,sha256=43g2HslNosbSuSnnMBzM6VWk4LaV0HXkWS3f8VKVRmw,15930
539
- portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
540
538
  portal/tests/test_emails.py,sha256=pLr06j3uMBxP1raoZQWzUTBVFvsEDFtUh85J8OnqCwE,9238
541
539
  portal/tests/test_global_forms.py,sha256=A5JpAe4AYK-wpu0o1qU4THmeNv_wr7lhzaMbjz5czpY,1543
542
540
  portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
@@ -611,7 +609,7 @@ portal/views/admin.py,sha256=4Xt3zEyQH7sUwQSrwuRtoCodWidjOzd7gJUwWU96pXY,957
611
609
  portal/views/api.py,sha256=lCwiclR98G-yTgK55u8IjkueIH8iremeiZSa3jAvO-M,6990
612
610
  portal/views/dotmailer.py,sha256=x49p89TtwA1MLysRLtq5yRRzVtIpzGoU__Xb5hPuHak,3208
613
611
  portal/views/email.py,sha256=V3wXRxIjeZ4OJBVqGCQrPn-GQWKZK1PCXbR1f2Zpa_4,2174
614
- portal/views/home.py,sha256=GlNTJqSST_jQi0REuaV2aI2zram-KAhedmATdC6yU58,10247
612
+ portal/views/home.py,sha256=qt5kW7TAaRFXz7BA2BrnHe5oYc69jjoLksEbfr0Aa3c,9715
615
613
  portal/views/legal.py,sha256=nUunsTHnhMcXBcDlg1GmUal86k9Vhinne4A2FWfq78M,342
616
614
  portal/views/organisation.py,sha256=sPDbiM7hdtpF8GKyh_4n4VPl2a-WnAgnF4q9aSvQCVI,3341
617
615
  portal/views/play_landing_page.py,sha256=FFmjUFub3ZdlbMqkB8yX3jAImCzqrUqgb8AZcpKywZ4,308
@@ -628,13 +626,13 @@ portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B
628
626
  portal/views/student/play.py,sha256=GMxk65bxWOe1Ds2kb6rvuOd1GoAtt5v_9AihLNXoUL0,8768
629
627
  portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
630
628
  portal/views/teacher/dashboard.py,sha256=pjzOOCz29d4VHukaCI5QhkDsu-RPvy7SXC9dDcPo50k,27422
631
- portal/views/teacher/teach.py,sha256=MxheM_kxt_K1J7jEWlim8yZonnp63iYUTw5dCaLscLI,37056
629
+ portal/views/teacher/teach.py,sha256=yWe2K0FHEQB_K3vzPJO8MR0gU2ZaDrXGw8gXMJBH0T0,35765
632
630
  portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
633
631
  portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
634
632
  portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
635
633
  portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
636
- codeforlife_portal-7.4.0.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
637
- codeforlife_portal-7.4.0.dist-info/METADATA,sha256=Nnx8oLJTfx1yuUsKVh7pTcZQDhXxkLSvr3_ehnB9X04,3317
638
- codeforlife_portal-7.4.0.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
639
- codeforlife_portal-7.4.0.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
640
- codeforlife_portal-7.4.0.dist-info/RECORD,,
634
+ codeforlife_portal-7.4.2.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
635
+ codeforlife_portal-7.4.2.dist-info/METADATA,sha256=MNGjSumPZ-NuAy21Z5JrTcZBPa-W_2kkQCNlD-cuOFE,3317
636
+ codeforlife_portal-7.4.2.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
637
+ codeforlife_portal-7.4.2.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
638
+ codeforlife_portal-7.4.2.dist-info/RECORD,,
portal/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "7.4.0"
1
+ __version__ = "7.4.2"
@@ -51,7 +51,6 @@
51
51
  #cboxPrevious:hover{background-position:-75px -25px;}
52
52
  #cboxNext{position:absolute; bottom:0; left:27px; background:url(../img/colorboxImages/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;}
53
53
  #cboxNext:hover{background-position:-50px -25px;}
54
- #cboxClose{position:absolute; top:0; right:0; background:url(../img/x_close_video.png) no-repeat; width:52px; height:52px; text-indent:-9999px;}
55
54
 
56
55
  /*
57
56
  The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill
@@ -86,12 +86,22 @@
86
86
  </p>
87
87
  </div>
88
88
  <div>
89
- <form method="post", action="{% url 'download_student_pack' 4 %}" class="non-styled--form">
90
- {% csrf_token %}
91
- <button id="python_pack" download type="submit" class="button button--primary button--icon">
92
- Download the Python coding club pack<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
93
- </button>
94
- </form>
89
+ <!-- TODO: improve responsiveness -->
90
+ <div style="margin-bottom: 10px;">
91
+ <a id="grass_snakes_pack" download class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FKsC7tdyT6htj5JX1ig28%2FGrass%20Snakes%20Club.zip?alt=media&token=1f6e6f0e-1a10-4bef-a8f8-a20b0a59e4c0">
92
+ Lvl 1: Grass Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
93
+ </a>
94
+ </div>
95
+ <div style="margin-bottom: 10px;">
96
+ <a id="tiger_snakes_pack" download type="submit" class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FSIeSsvz348nXjNMcVPxx%2FTiger%20Snakes%20Club.zip?alt=media&token=872b383f-a209-4864-a0e0-26d7ba3a8d74">
97
+ Lvl 2: Tiger Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
98
+ </a>
99
+ </div>
100
+ <div>
101
+ <a id="cobra_snakes_pack" download type="submit" class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FITTbTC8DVwr8LdCkN82d%2FCobra%20Club.zip?alt=media&token=bfd94842-cba9-45c7-892b-331bc82c33e9">
102
+ Lvl 3: Cobra Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
103
+ </a>
104
+ </div>
95
105
  </div>
96
106
  </div>
97
107
  </div>
@@ -175,7 +175,7 @@
175
175
  </div>
176
176
  </div>
177
177
  <div class="col-sm-6">
178
- <a href="https://www.youtube-nocookie.com/embed/ko4nrr4kDzA?rel=0&amp;autoplay=1" class="youtube">
178
+ <a href="https://www.youtube-nocookie.com/embed/xMmcg-LOc3I?rel=0&amp;autoplay=1" class="youtube">
179
179
  <img src="{% static 'portal/img/thumbnail_python_den.png' %}"
180
180
  alt="Python Den logo">
181
181
  </a>
@@ -67,10 +67,11 @@
67
67
  consent to register for a Code for Life account, and to use their email address. If you are using
68
68
  this at school, you must first have your teacher&rsquo;s permission to use this site. These Terms of Use
69
69
  were most recently updated on {{ last_updated }}.</strong></p>
70
- <p>Code for Life includes all versions of &ldquo;Rapid Router&rdquo;, &ldquo;Kurono&rdquo; and any other games, platforms and
71
- other products or services released by us (whether online or otherwise), from time to time,
72
- including all Code for Life websites used to play the games, platforms and any other products or
73
- services that we make available. &ldquo;Code for Life&rdquo; and &ldquo;Rapid Router&rdquo; are registered UK trade marks of
70
+ <p>Code for Life includes all versions of &ldquo;Rapid Router&rdquo;, &ldquo;Python Den&rdquo;,
71
+ &ldquo;Kurono&rdquo; and any other games, platforms and other products or services released by us
72
+ (whether online or otherwise), from time to time, including all Code for Life websites used to play
73
+ the games, platforms and any other products or services that we make available.
74
+ &ldquo;Code for Life&rdquo;, &ldquo;Rapid Router&rdquo; and "&ldquo;Kurono&rdquo;" are registered UK trademarks of
74
75
  Ocado Innovation Limited.</p>
75
76
  <p>You must take the time to read and understand the Terms of Use and the Privacy Notice before
76
77
  registering for Code for Life.</p>
@@ -98,7 +99,7 @@
98
99
  <ul>
99
100
  <li><p>Administrators: person or persons assigned responsibility for the management and security of
100
101
  membership by other Users;</p></li>
101
- <li><p>Users: any person using the Code for Life website, Rapid Router, Kurono or any other game,
102
+ <li><p>Users: any person using the Code for Life website, Rapid Router, Python Den, Kurono or any other game,
102
103
  application or platform of ours regardless of membership;</p></li>
103
104
  <li><p>Students: Users who are provided with login information for Code for Life by a teacher, parent,
104
105
  guardian or carer;</p></li>
@@ -213,8 +214,8 @@
213
214
 
214
215
  <h5 id="intellectual-property">6. Intellectual Property</h5>
215
216
  <p>You acknowledge that all copyright, trademarks, and other intellectual property rights in and relating to
216
- Code for Life (including all content of the Code for Life website, the Rapid Router application, the Kurono
217
- application, related software (including any drawn and/or animated avatars, whether or not such avatars have
217
+ Code for Life (including all content of the Code for Life website, the Rapid Router application, the Python Den
218
+ application, the Kurono application, related software (including any drawn and/or animated avatars, whether or not such avatars have
218
219
  any modifications) and any other games, applications or any other content that we make available from time
219
220
  to time) are owned by Ocado Innovation Limited. These rights protect all of the applications, games,
220
221
  products and services you see on the Code for Life website from time to time, including the graphics of
@@ -277,7 +278,8 @@
277
278
  <ul>
278
279
  <li><p>all Code for Life websites used to play the games, platforms and any other products or
279
280
  services that we make available to you</p></li>
280
- <li><p>all versions of “Rapid Router”, “Kurono” and any other games, platforms and other products or
281
+ <li><p>all versions of “Rapid Router”, “Python Den”, “Kurono” and any
282
+ other games, platforms and other products or
281
283
  services we released (whether online or otherwise)</p></li>
282
284
  </ul>
283
285
  <p><strong>When you visit the Code for Life site or register with us you agree to follow these rules.</strong></p>
@@ -373,7 +375,7 @@
373
375
  reuse them without our permission. Just because it is easy to copy some of the content on the website, this
374
376
  does not mean it is allowed. The content covered by Intellectual Property includes:</p>
375
377
  <ol type="a">
376
- <li><p>Rapid Router and the Kurono applications</p></li>
378
+ <li><p>Rapid Router, Python Den and Kurono applications</p></li>
377
379
  <li><p>Games – this will include the design of the games (for example their graphics, style and gameplay)</p></li>
378
380
  <li><p>Avatars, whether they are drawn or animated</p></li>
379
381
  <li><p>Any other software or services you see on our website</p></li>
portal/views/home.py CHANGED
@@ -33,15 +33,9 @@ from portal.helpers.ratelimit import (
33
33
  )
34
34
  from portal.strings.coding_club import CODING_CLUB_BANNER
35
35
  from portal.strings.home_learning import HOME_LEARNING_BANNER
36
- from portal.strings.ten_year_map import (
37
- TEN_YEAR_MAP_BANNER,
38
- TEN_YEAR_MAP_HEADLINE,
39
- )
36
+ from portal.strings.ten_year_map import TEN_YEAR_MAP_BANNER, TEN_YEAR_MAP_HEADLINE
40
37
  from portal.templatetags.app_tags import cloud_storage
41
- from portal.views.teacher.teach import (
42
- DownloadType,
43
- count_student_pack_downloads_click,
44
- )
38
+ from portal.views.teacher.teach import DownloadType, count_student_pack_downloads_click
45
39
 
46
40
  LOGGER = logging.getLogger(__name__)
47
41
 
@@ -71,15 +65,11 @@ def render_signup_form(request):
71
65
  invalid_form = False
72
66
 
73
67
  teacher_signup_form = TeacherSignupForm(prefix="teacher_signup")
74
- independent_student_signup_form = IndependentStudentSignupForm(
75
- prefix="independent_student_signup"
76
- )
68
+ independent_student_signup_form = IndependentStudentSignupForm(prefix="independent_student_signup")
77
69
 
78
70
  if request.method == "POST":
79
71
  if "teacher_signup-teacher_email" in request.POST:
80
- teacher_signup_form = TeacherSignupForm(
81
- request.POST, prefix="teacher_signup"
82
- )
72
+ teacher_signup_form = TeacherSignupForm(request.POST, prefix="teacher_signup")
83
73
 
84
74
  if not captcha.CAPTCHA_ENABLED:
85
75
  remove_captcha_from_forms(teacher_signup_form)
@@ -133,15 +123,11 @@ def process_signup_form(request, data):
133
123
  [email],
134
124
  personalization_values={
135
125
  "EMAIL": email,
136
- "LOGIN_URL": request.build_absolute_uri(
137
- reverse("teacher_login")
138
- ),
126
+ "LOGIN_URL": request.build_absolute_uri(reverse("teacher_login")),
139
127
  },
140
128
  )
141
129
  else:
142
- LOGGER.warn(
143
- f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
144
- )
130
+ LOGGER.warn(f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
145
131
  else:
146
132
  teacher = Teacher.objects.factory(
147
133
  first_name=data["teacher_first_name"],
@@ -152,9 +138,7 @@ def process_signup_form(request, data):
152
138
 
153
139
  send_verification_email(request, teacher.user.user, data)
154
140
 
155
- TotalActivity.objects.update(
156
- teacher_registrations=F("teacher_registrations") + 1
157
- )
141
+ TotalActivity.objects.update(teacher_registrations=F("teacher_registrations") + 1)
158
142
 
159
143
  return render(
160
144
  request,
@@ -182,15 +166,11 @@ def process_independent_student_signup_form(request, data):
182
166
  [email],
183
167
  personalization_values={
184
168
  "EMAIL": email,
185
- "LOGIN_URL": request.build_absolute_uri(
186
- reverse("independent_student_login")
187
- ),
169
+ "LOGIN_URL": request.build_absolute_uri(reverse("independent_student_login")),
188
170
  },
189
171
  )
190
172
  else:
191
- LOGGER.warning(
192
- f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
193
- )
173
+ LOGGER.warning(f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
194
174
  return render(
195
175
  request,
196
176
  "portal/email_verification_needed.html",
@@ -208,9 +188,7 @@ def process_independent_student_signup_form(request, data):
208
188
 
209
189
  send_verification_email(request, student.new_user, data, age=age)
210
190
 
211
- TotalActivity.objects.update(
212
- independent_registrations=F("independent_registrations") + 1
213
- )
191
+ TotalActivity.objects.update(independent_registrations=F("independent_registrations") + 1)
214
192
 
215
193
  return render(
216
194
  request,
@@ -221,10 +199,7 @@ def process_independent_student_signup_form(request, data):
221
199
 
222
200
 
223
201
  def is_developer(request):
224
- return (
225
- hasattr(request.user, "userprofile")
226
- and request.user.userprofile.developer
227
- )
202
+ return hasattr(request.user, "userprofile") and request.user.userprofile.developer
228
203
 
229
204
 
230
205
  def redirect_teacher_to_correct_page(request, teacher):
@@ -254,14 +229,10 @@ def home(request):
254
229
  # tests where the first Selenium test passes, but any following test
255
230
  # fails because it cannot find the Maintenance banner instance.
256
231
  try:
257
- maintenance_banner = DynamicElement.objects.get(
258
- name="Maintenance banner"
259
- )
232
+ maintenance_banner = DynamicElement.objects.get(name="Maintenance banner")
260
233
 
261
234
  if maintenance_banner.active:
262
- messages.info(
263
- request, format_html(maintenance_banner.text), extra_tags="safe"
264
- )
235
+ messages.info(request, format_html(maintenance_banner.text), extra_tags="safe")
265
236
  except ObjectDoesNotExist:
266
237
  pass
267
238
 
@@ -279,19 +250,13 @@ def home(request):
279
250
 
280
251
 
281
252
  def coding_club(request):
282
- return render(
283
- request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}
284
- )
253
+ return render(request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER})
285
254
 
286
255
 
287
256
  def download_student_pack(request, student_pack_type):
288
257
  if request.method == "POST":
289
258
  count_student_pack_downloads_click(int(student_pack_type))
290
- link = (
291
- cloud_storage("club_packs/PythonCodingClub.zip")
292
- if DownloadType(int(student_pack_type)) == DownloadType.PYTHON_PACK
293
- else cloud_storage("club_packs/PrimaryCodingClub.zip")
294
- )
259
+ link = cloud_storage("club_packs/PrimaryCodingClub.zip")
295
260
  return redirect(link)
296
261
 
297
262
 
@@ -304,9 +269,7 @@ def home_learning(request):
304
269
 
305
270
 
306
271
  def ten_year_map_page(request):
307
- messages.info(
308
- request, "This page is currently under construction.", extra_tags="safe"
309
- )
272
+ messages.info(request, "This page is currently under construction.", extra_tags="safe")
310
273
  return render(
311
274
  request,
312
275
  "portal/ten_year_map.html",
@@ -1,11 +1,11 @@
1
1
  import csv
2
2
  import json
3
- import pytz
4
3
  from datetime import datetime, timedelta
5
4
  from enum import Enum
6
5
  from functools import partial, wraps
7
6
  from uuid import uuid4
8
7
 
8
+ import pytz
9
9
  from common.helpers.emails import send_verification_email
10
10
  from common.helpers.generators import (
11
11
  generate_access_code,
@@ -34,12 +34,10 @@ from django.urls import reverse, reverse_lazy
34
34
  from django.utils import timezone
35
35
  from django.views.decorators.http import require_POST
36
36
  from game.views.level_selection import get_blockly_episodes, get_python_episodes
37
- from portal.views.registration import handle_reset_password_tracking
38
37
  from reportlab.lib.colors import black, red
39
38
  from reportlab.lib.pagesizes import A4
40
39
  from reportlab.lib.utils import ImageReader
41
40
  from reportlab.pdfgen import canvas
42
- from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
43
41
 
44
42
  from portal.forms.teach import (
45
43
  BaseTeacherDismissStudentsFormSet,
@@ -55,13 +53,13 @@ from portal.forms.teach import (
55
53
  TeacherMoveStudentsDestinationForm,
56
54
  TeacherSetStudentPass,
57
55
  )
56
+ from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
57
+ from portal.views.registration import handle_reset_password_tracking
58
58
 
59
59
  STUDENT_PASSWORD_LENGTH = 6
60
60
  REMINDER_CARDS_PDF_ROWS = 8
61
61
  REMINDER_CARDS_PDF_COLUMNS = 1
62
- REMINDER_CARDS_PDF_WARNING_TEXT = (
63
- "Please ensure students keep login details in a secure place"
64
- )
62
+ REMINDER_CARDS_PDF_WARNING_TEXT = "Please ensure students keep login details in a secure place"
65
63
 
66
64
 
67
65
  @login_required(login_url=reverse_lazy("teacher_login"))
@@ -71,9 +69,7 @@ def teacher_onboarding_create_class(request):
71
69
  Onboarding view for creating a class (and organisation if there isn't one, yet)
72
70
  """
73
71
  teacher = request.user.new_teacher
74
- requests = Student.objects.filter(
75
- pending_class_request__teacher=teacher, new_user__is_active=True
76
- )
72
+ requests = Student.objects.filter(pending_class_request__teacher=teacher, new_user__is_active=True)
77
73
 
78
74
  if not teacher.school:
79
75
  return HttpResponseRedirect(reverse_lazy("onboarding-organisation"))
@@ -84,9 +80,7 @@ def teacher_onboarding_create_class(request):
84
80
  created_class = create_class(form, teacher)
85
81
  messages.success(
86
82
  request,
87
- "The class '{className}' has been created successfully.".format(
88
- className=created_class.name
89
- ),
83
+ "The class '{className}' has been created successfully.".format(className=created_class.name),
90
84
  )
91
85
  return HttpResponseRedirect(
92
86
  reverse_lazy(
@@ -133,9 +127,7 @@ def process_edit_class(request, access_code, onboarding_done, next_url):
133
127
  """
134
128
  klass = get_object_or_404(Class, access_code=access_code)
135
129
  teacher = request.user.new_teacher
136
- students = Student.objects.filter(
137
- class_field=klass, new_user__is_active=True
138
- ).order_by("new_user__first_name")
130
+ students = Student.objects.filter(class_field=klass, new_user__is_active=True).order_by("new_user__first_name")
139
131
 
140
132
  check_teacher_authorised(request, klass.teacher)
141
133
 
@@ -156,9 +148,7 @@ def process_edit_class(request, access_code, onboarding_done, next_url):
156
148
  login_id=hashed_login_id,
157
149
  )
158
150
 
159
- TotalActivity.objects.update(
160
- student_registrations=F("student_registrations") + 1
161
- )
151
+ TotalActivity.objects.update(student_registrations=F("student_registrations") + 1)
162
152
 
163
153
  login_url = generate_student_url(request, new_student, login_id)
164
154
  students_info.append(
@@ -241,16 +231,12 @@ def teacher_delete_class(request, access_code):
241
231
  # check user authorised to see class
242
232
  check_teacher_authorised(request, klass.teacher)
243
233
 
244
- if Student.objects.filter(
245
- class_field=klass, new_user__is_active=True
246
- ).exists():
234
+ if Student.objects.filter(class_field=klass, new_user__is_active=True).exists():
247
235
  messages.info(
248
236
  request,
249
237
  "This class still has students, please remove or delete them all before deleting the class.",
250
238
  )
251
- return HttpResponseRedirect(
252
- reverse_lazy("view_class", kwargs={"access_code": access_code})
253
- )
239
+ return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
254
240
 
255
241
  klass.anonymise()
256
242
 
@@ -267,9 +253,7 @@ def teacher_delete_students(request, access_code):
267
253
 
268
254
  # get student objects for students to be deleted, confirming they are in the class
269
255
  student_ids = json.loads(request.POST.get("transfer_students", "[]"))
270
- students = [
271
- get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
272
- ]
256
+ students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
273
257
 
274
258
  def __anonymise(user):
275
259
  # Delete all personal data from inactive user and mark as inactive.
@@ -291,9 +275,7 @@ def teacher_delete_students(request, access_code):
291
275
  else: # otherwise, just delete
292
276
  student.new_user.delete()
293
277
 
294
- return HttpResponseRedirect(
295
- reverse_lazy("view_class", kwargs={"access_code": access_code})
296
- )
278
+ return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
297
279
 
298
280
 
299
281
  @login_required(login_url=reverse_lazy("teacher_login"))
@@ -307,9 +289,7 @@ def teacher_edit_class(request, access_code):
307
289
  """
308
290
  klass = get_object_or_404(Class, access_code=access_code)
309
291
  old_teacher = klass.teacher
310
- other_teachers = Teacher.objects.filter(school=old_teacher.school).exclude(
311
- user=old_teacher.user
312
- )
292
+ other_teachers = Teacher.objects.filter(school=old_teacher.school).exclude(user=old_teacher.user)
313
293
 
314
294
  # check user authorised to see class
315
295
  check_teacher_authorised(request, klass.teacher)
@@ -339,9 +319,7 @@ def teacher_edit_class(request, access_code):
339
319
  elif "level_control_submit" in request.POST:
340
320
  level_control_form = ClassLevelControlForm(request.POST)
341
321
  if level_control_form.is_valid():
342
- return process_level_control_form(
343
- request, klass, blockly_episodes, python_episodes
344
- )
322
+ return process_level_control_form(request, klass, blockly_episodes, python_episodes)
345
323
  elif "class_move_submit" in request.POST:
346
324
  class_move_form = ClassMoveForm(other_teachers, request.POST)
347
325
  if class_move_form.is_valid():
@@ -383,9 +361,7 @@ def process_edit_class_form(request, klass, form):
383
361
  elif hours < 1000:
384
362
  # Setting to number of hours
385
363
  klass.always_accept_requests = False
386
- klass.accept_requests_until = timezone.now() + timedelta(
387
- hours=hours
388
- )
364
+ klass.accept_requests_until = timezone.now() + timedelta(hours=hours)
389
365
  messages.info(
390
366
  request,
391
367
  "Class set successfully to receive requests from external students until "
@@ -407,18 +383,12 @@ def process_edit_class_form(request, klass, form):
407
383
  klass.classmates_data_viewable = classmate_progress
408
384
  klass.save()
409
385
 
410
- messages.success(
411
- request, "The class's settings have been changed successfully."
412
- )
386
+ messages.success(request, "The class's settings have been changed successfully.")
413
387
 
414
- return HttpResponseRedirect(
415
- reverse_lazy("view_class", kwargs={"access_code": klass.access_code})
416
- )
388
+ return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": klass.access_code}))
417
389
 
418
390
 
419
- def process_level_control_form(
420
- request, klass, blockly_episodes, python_episodes
421
- ):
391
+ def process_level_control_form(request, klass, blockly_episodes, python_episodes):
422
392
  """
423
393
  Find the levels that the user wants to lock and lock them for the specific class.
424
394
  :param request: The request sent by the user submitting the form.
@@ -429,23 +399,14 @@ def process_level_control_form(
429
399
  """
430
400
  levels_to_lock_ids = []
431
401
 
432
- mark_levels_to_lock_in_episodes(
433
- request, blockly_episodes, levels_to_lock_ids
434
- )
435
- mark_levels_to_lock_in_episodes(
436
- request, python_episodes, levels_to_lock_ids
437
- )
402
+ mark_levels_to_lock_in_episodes(request, blockly_episodes, levels_to_lock_ids)
403
+ mark_levels_to_lock_in_episodes(request, python_episodes, levels_to_lock_ids)
438
404
 
439
405
  klass.locked_levels.clear()
440
- [
441
- klass.locked_levels.add(levels_to_lock_id)
442
- for levels_to_lock_id in levels_to_lock_ids
443
- ]
406
+ [klass.locked_levels.add(levels_to_lock_id) for levels_to_lock_id in levels_to_lock_ids]
444
407
 
445
408
  messages.success(request, "Your level preferences have been saved.")
446
- activity_today = DailyActivity.objects.get_or_create(
447
- date=datetime.now().date()
448
- )[0]
409
+ activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
449
410
  activity_today.level_control_submits += 1
450
411
  activity_today.save()
451
412
 
@@ -468,14 +429,10 @@ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
468
429
  [
469
430
  levels_to_lock_ids.append(episode_level["id"])
470
431
  for episode_level in episode_levels
471
- if str(episode_level["id"])
472
- not in request.POST.getlist(episode_name)
432
+ if str(episode_level["id"]) not in request.POST.getlist(episode_name)
473
433
  ]
474
434
  else:
475
- [
476
- levels_to_lock_ids.append(episode_level["id"])
477
- for episode_level in episode_levels
478
- ]
435
+ [levels_to_lock_ids.append(episode_level["id"]) for episode_level in episode_levels]
479
436
 
480
437
 
481
438
  def process_move_class_form(request, klass, form):
@@ -501,9 +458,7 @@ def teacher_edit_student(request, pk):
501
458
  student = get_object_or_404(Student, id=pk)
502
459
  check_teacher_authorised(request, student.class_field.teacher)
503
460
 
504
- name_form = TeacherEditStudentForm(
505
- student, initial={"name": student.new_user.first_name}
506
- )
461
+ name_form = TeacherEditStudentForm(student, initial={"name": student.new_user.first_name})
507
462
 
508
463
  password_form = TeacherSetStudentPass()
509
464
  set_password_mode = False
@@ -532,9 +487,7 @@ def teacher_edit_student(request, pk):
532
487
  else:
533
488
  password_form = TeacherSetStudentPass(request.POST)
534
489
  if password_form.is_valid():
535
- return process_reset_password_form(
536
- request, student, password_form
537
- )
490
+ return process_reset_password_form(request, student, password_form)
538
491
  set_password_mode = True
539
492
 
540
493
  return render(
@@ -577,9 +530,7 @@ def process_reset_password_form(request, student, password_form):
577
530
  student.new_user.set_password(new_password)
578
531
  student.new_user.save()
579
532
  student.login_id = login_id
580
- clear_ratelimit_cache_for_user(
581
- f"{student.new_user.first_name},{student.class_field.access_code}"
582
- )
533
+ clear_ratelimit_cache_for_user(f"{student.new_user.first_name},{student.class_field.access_code}")
583
534
  student.blocked_time = datetime.now(tz=pytz.utc) - timedelta(days=1)
584
535
  student.save()
585
536
 
@@ -613,9 +564,7 @@ def teacher_dismiss_students(request, access_code):
613
564
 
614
565
  # get student objects for students to be dismissed, confirming they are in the class
615
566
  student_ids = json.loads(request.POST.get("transfer_students", "[]"))
616
- students = [
617
- get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
618
- ]
567
+ students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
619
568
 
620
569
  TeacherDismissStudentsFormSet = formset_factory(
621
570
  wraps(TeacherDismissStudentsForm)(partial(TeacherDismissStudentsForm)),
@@ -626,9 +575,7 @@ def teacher_dismiss_students(request, access_code):
626
575
  if is_right_dismiss_form(request):
627
576
  formset = TeacherDismissStudentsFormSet(request.POST)
628
577
  if formset.is_valid():
629
- return process_dismiss_student_form(
630
- request, formset, klass, access_code
631
- )
578
+ return process_dismiss_student_form(request, formset, klass, access_code)
632
579
 
633
580
  else:
634
581
  initial_data = [
@@ -679,14 +626,10 @@ def process_dismiss_student_form(request, formset, klass, access_code):
679
626
  student.user.save()
680
627
 
681
628
  # log the data
682
- joinrelease = JoinReleaseStudent.objects.create(
683
- student=student, action_type=JoinReleaseStudent.RELEASE
684
- )
629
+ joinrelease = JoinReleaseStudent.objects.create(student=student, action_type=JoinReleaseStudent.RELEASE)
685
630
  joinrelease.save()
686
631
 
687
- send_verification_email(
688
- request, student.new_user, data, school=klass.teacher.school
689
- )
632
+ send_verification_email(request, student.new_user, data, school=klass.teacher.school)
690
633
 
691
634
  if not failed_users:
692
635
  messages.success(
@@ -700,9 +643,7 @@ def process_dismiss_student_form(request, formset, klass, access_code):
700
643
  "Please make sure the email has not been registered to another account.",
701
644
  )
702
645
 
703
- return HttpResponseRedirect(
704
- reverse_lazy("view_class", kwargs={"access_code": access_code})
705
- )
646
+ return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
706
647
 
707
648
 
708
649
  @login_required(login_url=reverse_lazy("teacher_login"))
@@ -717,9 +658,7 @@ def teacher_class_password_reset(request, access_code):
717
658
  check_teacher_authorised(request, klass.teacher)
718
659
 
719
660
  student_ids = json.loads(request.POST.get("transfer_students", "[]"))
720
- students = [
721
- get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
722
- ]
661
+ students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
723
662
 
724
663
  students_info = []
725
664
  handle_reset_password_tracking(request, "SCHOOL_STUDENT", access_code)
@@ -741,9 +680,7 @@ def teacher_class_password_reset(request, access_code):
741
680
  student.new_user.set_password(password)
742
681
  student.new_user.save()
743
682
  student.login_id = hashed_login_id
744
- clear_ratelimit_cache_for_user(
745
- f"{student.new_user.first_name},{access_code}"
746
- )
683
+ clear_ratelimit_cache_for_user(f"{student.new_user.first_name},{access_code}")
747
684
  student.blocked_time = datetime.now(tz=pytz.utc) - timedelta(days=1)
748
685
  student.save()
749
686
 
@@ -757,9 +694,7 @@ def teacher_class_password_reset(request, access_code):
757
694
  "students_info": students_info,
758
695
  "query_data": json.dumps(students_info),
759
696
  "class_url": request.build_absolute_uri(
760
- reverse(
761
- "student_login", kwargs={"access_code": klass.access_code}
762
- )
697
+ reverse("student_login", kwargs={"access_code": klass.access_code})
763
698
  ),
764
699
  },
765
700
  )
@@ -809,37 +744,26 @@ def teacher_move_students_to_class(request, access_code):
809
744
 
810
745
  check_if_move_authorised(request, old_class, new_class)
811
746
 
812
- transfer_students_ids = json.loads(
813
- request.POST.get("transfer_students", "[]")
814
- )
747
+ transfer_students_ids = json.loads(request.POST.get("transfer_students", "[]"))
815
748
 
816
749
  # get student objects for students to be transferred, confirming they are in the old class still
817
- transfer_students = [
818
- get_object_or_404(Student, id=i, class_field=old_class)
819
- for i in transfer_students_ids
820
- ]
750
+ transfer_students = [get_object_or_404(Student, id=i, class_field=old_class) for i in transfer_students_ids]
821
751
 
822
752
  # get new class' students
823
- new_class_students = Student.objects.filter(
824
- class_field=new_class, new_user__is_active=True
825
- ).order_by("new_user__first_name")
753
+ new_class_students = Student.objects.filter(class_field=new_class, new_user__is_active=True).order_by(
754
+ "new_user__first_name"
755
+ )
826
756
 
827
757
  TeacherMoveStudentDisambiguationFormSet = formset_factory(
828
- wraps(TeacherMoveStudentDisambiguationForm)(
829
- partial(TeacherMoveStudentDisambiguationForm)
830
- ),
758
+ wraps(TeacherMoveStudentDisambiguationForm)(partial(TeacherMoveStudentDisambiguationForm)),
831
759
  extra=0,
832
760
  formset=BaseTeacherMoveStudentsDisambiguationFormSet,
833
761
  )
834
762
 
835
763
  if is_right_move_form(request):
836
- formset = TeacherMoveStudentDisambiguationFormSet(
837
- new_class, request.POST
838
- )
764
+ formset = TeacherMoveStudentDisambiguationFormSet(new_class, request.POST)
839
765
  if formset.is_valid():
840
- return process_move_students_form(
841
- request, formset, old_class, new_class
842
- )
766
+ return process_move_students_form(request, formset, old_class, new_class)
843
767
  else:
844
768
  # format the students for the form
845
769
  initial_data = [
@@ -850,9 +774,7 @@ def teacher_move_students_to_class(request, access_code):
850
774
  for student in transfer_students
851
775
  ]
852
776
 
853
- formset = TeacherMoveStudentDisambiguationFormSet(
854
- new_class, initial=initial_data
855
- )
777
+ formset = TeacherMoveStudentDisambiguationFormSet(new_class, initial=initial_data)
856
778
 
857
779
  return render(
858
780
  request,
@@ -872,9 +794,7 @@ def check_if_move_authorised(request, old_class, new_class):
872
794
 
873
795
  # check teacher has permission to edit old_class and that both classes
874
796
  # are in the same school
875
- if (
876
- not teacher.is_admin and teacher != old_class.teacher
877
- ) or teacher.school != new_class.teacher.school:
797
+ if (not teacher.is_admin and teacher != old_class.teacher) or teacher.school != new_class.teacher.school:
878
798
  raise Http404
879
799
 
880
800
 
@@ -898,14 +818,8 @@ def process_move_students_form(request, formset, old_class, new_class):
898
818
  student.save()
899
819
  student.new_user.save()
900
820
 
901
- messages.success(
902
- request, "The students have been transferred successfully."
903
- )
904
- return HttpResponseRedirect(
905
- reverse_lazy(
906
- "view_class", kwargs={"access_code": old_class.access_code}
907
- )
908
- )
821
+ messages.success(request, "The students have been transferred successfully.")
822
+ return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": old_class.access_code}))
909
823
 
910
824
 
911
825
  class DownloadType(Enum):
@@ -938,9 +852,7 @@ def teacher_print_reminder_cards(request, access_code):
938
852
 
939
853
  CARD_INNER_HEIGHT = CARD_HEIGHT - CARD_PADDING * 2
940
854
 
941
- logo_image = ImageReader(
942
- staticfiles_storage.path("portal/img/logo_cfl_reminder_cards.jpg")
943
- )
855
+ logo_image = ImageReader(staticfiles_storage.path("portal/img/logo_cfl_reminder_cards.jpg"))
944
856
 
945
857
  klass = get_object_or_404(Class, access_code=access_code)
946
858
  # Check auth
@@ -948,12 +860,8 @@ def teacher_print_reminder_cards(request, access_code):
948
860
 
949
861
  # Use data from the query string if given
950
862
  student_data = get_student_data(request)
951
- student_login_link = request.build_absolute_uri(
952
- reverse("student_login_access_code")
953
- )
954
- class_login_link = request.build_absolute_uri(
955
- reverse("student_login", kwargs={"access_code": access_code})
956
- )
863
+ student_login_link = request.build_absolute_uri(reverse("student_login_access_code"))
864
+ class_login_link = request.build_absolute_uri(reverse("student_login", kwargs={"access_code": access_code}))
957
865
 
958
866
  # Now draw everything
959
867
  x = 0
@@ -965,17 +873,10 @@ def teacher_print_reminder_cards(request, access_code):
965
873
  if current_student_count % (NUM_X * NUM_Y) == 0:
966
874
  p.setFillColor(red)
967
875
  p.setFont("Helvetica-Bold", 10)
968
- p.drawString(
969
- PAGE_MARGIN, PAGE_MARGIN / 2, REMINDER_CARDS_PDF_WARNING_TEXT
970
- )
876
+ p.drawString(PAGE_MARGIN, PAGE_MARGIN / 2, REMINDER_CARDS_PDF_WARNING_TEXT)
971
877
 
972
878
  left = PAGE_MARGIN + x * CARD_WIDTH + x * INTER_CARD_MARGIN * 2
973
- bottom = (
974
- PAGE_HEIGHT
975
- - PAGE_MARGIN
976
- - (y + 1) * CARD_HEIGHT
977
- - y * INTER_CARD_MARGIN
978
- )
879
+ bottom = PAGE_HEIGHT - PAGE_MARGIN - (y + 1) * CARD_HEIGHT - y * INTER_CARD_MARGIN
979
880
 
980
881
  inner_bottom = bottom + CARD_PADDING
981
882
 
@@ -995,12 +896,7 @@ def teacher_print_reminder_cards(request, access_code):
995
896
  anchor="w",
996
897
  )
997
898
 
998
- text_left = (
999
- left
1000
- + INTER_CARD_MARGIN
1001
- + (logo_image.getSize()[0] / logo_image.getSize()[1])
1002
- * card_logo_height
1003
- )
899
+ text_left = left + INTER_CARD_MARGIN + (logo_image.getSize()[0] / logo_image.getSize()[1]) * card_logo_height
1004
900
 
1005
901
  # student details
1006
902
  p.setFillColor(black)
@@ -1023,9 +919,7 @@ def teacher_print_reminder_cards(request, access_code):
1023
919
  inner_bottom + CARD_INNER_HEIGHT * 0.3,
1024
920
  f"Name: {student['name']}",
1025
921
  )
1026
- p.drawString(
1027
- text_left, inner_bottom, f"Password: {student['password']}"
1028
- )
922
+ p.drawString(text_left, inner_bottom, f"Password: {student['password']}")
1029
923
 
1030
924
  x = (x + 1) % NUM_X
1031
925
  y = compute_show_page_character(p, x, y, NUM_Y)
@@ -1044,17 +938,13 @@ def teacher_print_reminder_cards(request, access_code):
1044
938
  @user_passes_test(logged_in_as_teacher, login_url=reverse_lazy("teacher_login"))
1045
939
  def teacher_download_csv(request, access_code):
1046
940
  response = HttpResponse(content_type="text/csv")
1047
- response[
1048
- "Content-Disposition"
1049
- ] = 'attachment; filename="student_login_urls.csv"'
941
+ response["Content-Disposition"] = 'attachment; filename="student_login_urls.csv"'
1050
942
 
1051
943
  klass = get_object_or_404(Class, access_code=access_code)
1052
944
  # Check auth
1053
945
  check_teacher_authorised(request, klass.teacher)
1054
946
 
1055
- class_url = request.build_absolute_uri(
1056
- reverse("student_login", kwargs={"access_code": access_code})
1057
- )
947
+ class_url = request.build_absolute_uri(reverse("student_login", kwargs={"access_code": access_code}))
1058
948
 
1059
949
  # Use data from the query string if given
1060
950
  student_data = get_student_data(request)
@@ -1062,9 +952,7 @@ def teacher_download_csv(request, access_code):
1062
952
  writer = csv.writer(response)
1063
953
  writer.writerow([access_code, class_url])
1064
954
  for student in student_data:
1065
- writer.writerow(
1066
- [student["name"], student["password"], student["login_url"]]
1067
- )
955
+ writer.writerow([student["name"], student["password"], student["login_url"]])
1068
956
 
1069
957
  count_student_details_click(DownloadType.CSV)
1070
958
 
@@ -1092,22 +980,16 @@ def compute_show_page_end(p, x, y):
1092
980
 
1093
981
 
1094
982
  def count_student_pack_downloads_click(student_pack_type):
1095
- activity_today = DailyActivity.objects.get_or_create(
1096
- date=datetime.now().date()
1097
- )[0]
983
+ activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
1098
984
  if DownloadType(student_pack_type) == DownloadType.PRIMARY_PACK:
1099
985
  activity_today.primary_coding_club_downloads += 1
1100
- elif DownloadType(student_pack_type) == DownloadType.PYTHON_PACK:
1101
- activity_today.python_coding_club_downloads += 1
1102
986
  else:
1103
987
  raise Exception("Unknown download type")
1104
988
  activity_today.save()
1105
989
 
1106
990
 
1107
991
  def count_student_details_click(download_type):
1108
- activity_today = DailyActivity.objects.get_or_create(
1109
- date=datetime.now().date()
1110
- )[0]
992
+ activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
1111
993
 
1112
994
  if download_type == DownloadType.CSV:
1113
995
  activity_today.csv_click_count += 1
Binary file
@@ -1,41 +0,0 @@
1
- from datetime import timedelta, datetime
2
-
3
- from common.models import DailyActivity
4
- from selenium.webdriver.common.by import By
5
- from selenium.webdriver.support import expected_conditions as EC
6
- from selenium.webdriver.support.ui import WebDriverWait
7
-
8
- from portal.tests.base_test import BaseTest
9
-
10
-
11
- class TestDailyActivities(BaseTest):
12
- def test_coding_club_increment(self):
13
-
14
- # first create dailyActivity one day before datetime.now()
15
- # to check if it can handle incrementing on different days
16
- # then check if increments are done on the same day
17
- old_date = datetime.now() - timedelta(days=1)
18
- old_daily_activity = DailyActivity(date=old_date)
19
- old_daily_activity.save()
20
-
21
- for i in range(4):
22
- # check both buttons
23
- self.go_to_homepage()
24
- button_id = "primary_pack" if i < 2 else "python_pack"
25
- find_out_more_button = WebDriverWait(self.selenium, 10).until(
26
- EC.element_to_be_clickable((By.ID, "find_out_more"))
27
- )
28
- find_out_more_button.click()
29
-
30
- daily_count_button = WebDriverWait(self.selenium, 10).until(
31
- EC.visibility_of_element_located((By.ID, button_id))
32
- )
33
- daily_count_button.click()
34
- # check the old_date is still the same
35
- old_daily_activity = DailyActivity.objects.get(date=old_date)
36
- assert old_daily_activity.primary_coding_club_downloads == 0
37
- assert old_daily_activity.python_coding_club_downloads == 0
38
- # check the current_date is incremented to 2
39
- current_daily_activity = DailyActivity.objects.get(date=datetime.now())
40
- assert current_daily_activity.primary_coding_club_downloads == 2
41
- assert current_daily_activity.python_coding_club_downloads == 2