codeforlife-portal 8.4.4__py2.py3-none-any.whl → 8.4.6__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/setup.py CHANGED
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import re
3
+ import json
4
+ import typing as t
3
5
 
4
6
  from setuptools import find_packages, setup
5
7
 
@@ -10,25 +12,46 @@ with open("../portal/__init__.py", "r") as fd:
10
12
  re.MULTILINE,
11
13
  ).group(1)
12
14
 
15
+
16
+ def parse_requirements(packages: t.Dict[str, t.Dict[str, t.Any]]):
17
+ """Parse a group of requirements from `Pipfile.lock`.
18
+
19
+ https://setuptools.pypa.io/en/latest/userguide/dependency_management.html
20
+
21
+ Args:
22
+ packages: The group name of the requirements.
23
+
24
+ Returns:
25
+ The requirements as a list of strings, required by `setuptools.setup`.
26
+ """
27
+
28
+ requirements: t.List[str] = []
29
+ for name, package in packages.items():
30
+ requirement = name
31
+ if "extras" in package:
32
+ requirement += f"[{','.join(package['extras'])}]"
33
+ if "version" in package:
34
+ requirement += package["version"]
35
+ if "markers" in package:
36
+ requirement += f"; {package['markers']}"
37
+ requirements.append(requirement)
38
+
39
+ return requirements
40
+
41
+
42
+ # Parse Pipfile.lock into strings.
43
+ with open("Pipfile.lock", "r", encoding="utf-8") as pipfile_lock:
44
+ lock = json.load(pipfile_lock)
45
+ install_requires = parse_requirements(lock["default"])
46
+ dev_requires = parse_requirements(lock["develop"])
47
+
13
48
  setup(
14
49
  name="cfl-common",
15
50
  long_description="Common package for Code for Life",
16
51
  packages=find_packages(),
17
52
  version=version,
18
53
  include_package_data=True,
19
- install_requires=[
20
- "django==4.2.20",
21
- "django-countries==7.6.1",
22
- "django-csp==3.8",
23
- "django-import-export==4.2.0",
24
- "django-pipeline==3.1.0",
25
- "django-two-factor-auth==1.17.0",
26
- "djangorestframework==3.15.2",
27
- "libsass==0.23.0",
28
- "more-itertools==8.7.0",
29
- "pgeocode==0.4.0",
30
- "pyjwt==2.6.0",
31
- ],
54
+ install_requires=install_requires,
32
55
  tests_require=[],
33
56
  test_suite="tests",
34
57
  classifiers=[
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.2
2
+ Name: codeforlife-portal
3
+ Version: 8.4.6
4
+ Classifier: Programming Language :: Python
5
+ Classifier: Programming Language :: Python :: 3.12
6
+ Classifier: Framework :: Django
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE.md
9
+ Requires-Dist: asgiref==3.8.1; python_version >= "3.8"
10
+ Requires-Dist: certifi==2025.1.31; python_version >= "3.6"
11
+ Requires-Dist: cfl-common==8.4.5
12
+ Requires-Dist: chardet==5.2.0; python_version >= "3.7"
13
+ Requires-Dist: charset-normalizer==3.4.1; python_version >= "3.7"
14
+ Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
15
+ Requires-Dist: django==4.2.20; python_version >= "3.8"
16
+ Requires-Dist: django-classy-tags==4.1.0; python_version >= "3.8"
17
+ Requires-Dist: django-countries==7.6.1
18
+ Requires-Dist: django-csp==3.8
19
+ Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
20
+ Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
21
+ Requires-Dist: django-otp==1.5.4; python_version >= "3.7"
22
+ Requires-Dist: django-phonenumber-field==8.0.0; python_version >= "3.8"
23
+ Requires-Dist: django-pipeline==3.1.0
24
+ Requires-Dist: django-preventconcurrentlogins==0.8.2
25
+ Requires-Dist: django-ratelimit==3.0.1; python_version >= "3.4"
26
+ Requires-Dist: django-recaptcha==4.0.0
27
+ Requires-Dist: django-sekizai==4.1.0; python_version >= "3.8"
28
+ Requires-Dist: django-treebeard==4.7.1; python_version >= "3.8"
29
+ Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8"
30
+ Requires-Dist: djangorestframework==3.15.2; python_version >= "3.8"
31
+ Requires-Dist: idna==3.10; python_version >= "3.6"
32
+ Requires-Dist: importlib-metadata==4.13.0; python_version >= "3.7"
33
+ Requires-Dist: libsass==0.23.0; python_version >= "3.8"
34
+ Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
35
+ Requires-Dist: numpy==2.2.3; python_version >= "3.10"
36
+ Requires-Dist: pandas==2.2.3; python_version >= "3.9"
37
+ Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
38
+ Requires-Dist: phonenumbers==8.12.12
39
+ Requires-Dist: pillow==11.1.0; python_version >= "3.9"
40
+ Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
41
+ Requires-Dist: pypng==0.20220715.0
42
+ Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
43
+ Requires-Dist: pytz==2025.1
44
+ Requires-Dist: pyyaml==6.0.2; python_version >= "3.8"
45
+ Requires-Dist: qrcode==7.4.2; python_version >= "3.7"
46
+ Requires-Dist: reportlab==4.2.5; python_version >= "3.7" and python_version < "4"
47
+ Requires-Dist: requests==2.32.3; python_version >= "3.8"
48
+ Requires-Dist: setuptools==74.0.0; python_version >= "3.8"
49
+ Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
50
+ Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
51
+ Requires-Dist: tablib==3.7.0; python_version >= "3.9"
52
+ Requires-Dist: typing-extensions==4.12.2; python_version >= "3.8"
53
+ Requires-Dist: tzdata==2025.1; python_version >= "2"
54
+ Requires-Dist: urllib3==2.3.0; python_version >= "3.9"
55
+ Requires-Dist: zipp==3.21.0; python_version >= "3.9"
56
+ Provides-Extra: dev
57
+ Requires-Dist: asgiref==3.8.1; python_version >= "3.8" and extra == "dev"
58
+ Requires-Dist: asttokens==3.0.0; python_version >= "3.8" and extra == "dev"
59
+ Requires-Dist: attrs==25.3.0; python_version >= "3.8" and extra == "dev"
60
+ Requires-Dist: black==25.1.0; python_version >= "3.9" and extra == "dev"
61
+ Requires-Dist: certifi==2025.1.31; python_version >= "3.6" and extra == "dev"
62
+ Requires-Dist: cfl-common==8.4.5; extra == "dev"
63
+ Requires-Dist: charset-normalizer==3.4.1; python_version >= "3.7" and extra == "dev"
64
+ Requires-Dist: click==8.1.8; python_version >= "3.7" and extra == "dev"
65
+ Requires-Dist: coverage[toml]==7.7.0; python_version >= "3.9" and extra == "dev"
66
+ Requires-Dist: decorator==5.2.1; python_version >= "3.8" and extra == "dev"
67
+ Requires-Dist: diff-match-patch==20241021; python_version >= "3.7" and extra == "dev"
68
+ Requires-Dist: django==4.2.20; python_version >= "3.8" and extra == "dev"
69
+ Requires-Dist: django-countries==7.6.1; extra == "dev"
70
+ Requires-Dist: django-csp==3.8; extra == "dev"
71
+ Requires-Dist: django-formtools==2.5.1; python_version >= "3.8" and extra == "dev"
72
+ Requires-Dist: django-import-export==4.2.0; python_version >= "3.9" and extra == "dev"
73
+ Requires-Dist: django-otp==1.5.4; python_version >= "3.7" and extra == "dev"
74
+ Requires-Dist: django-phonenumber-field==8.0.0; python_version >= "3.8" and extra == "dev"
75
+ Requires-Dist: django-pipeline==3.1.0; extra == "dev"
76
+ Requires-Dist: django-reverse-js==0.1.7; python_version >= "3.10" and extra == "dev"
77
+ Requires-Dist: django-selenium-clean==1.0.1; extra == "dev"
78
+ Requires-Dist: django-test-migrations==1.4.0; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
79
+ Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8" and extra == "dev"
80
+ Requires-Dist: djangorestframework==3.15.2; python_version >= "3.8" and extra == "dev"
81
+ Requires-Dist: execnet==2.1.1; python_version >= "3.8" and extra == "dev"
82
+ Requires-Dist: executing==2.2.0; python_version >= "3.8" and extra == "dev"
83
+ Requires-Dist: fastdiff==0.3.0; extra == "dev"
84
+ Requires-Dist: h11==0.14.0; python_version >= "3.7" and extra == "dev"
85
+ Requires-Dist: idna==3.10; python_version >= "3.6" and extra == "dev"
86
+ Requires-Dist: iniconfig==2.0.0; python_version >= "3.7" and extra == "dev"
87
+ Requires-Dist: ipython==9.0.2; python_version >= "3.11" and extra == "dev"
88
+ Requires-Dist: ipython-pygments-lexers==1.1.1; python_version >= "3.8" and extra == "dev"
89
+ Requires-Dist: isort==6.0.1; python_full_version >= "3.9.0" and extra == "dev"
90
+ Requires-Dist: jedi==0.19.2; python_version >= "3.6" and extra == "dev"
91
+ Requires-Dist: libsass==0.23.0; python_version >= "3.8" and extra == "dev"
92
+ Requires-Dist: matplotlib-inline==0.1.7; python_version >= "3.8" and extra == "dev"
93
+ Requires-Dist: more-itertools==8.7.0; python_version >= "3.5" and extra == "dev"
94
+ Requires-Dist: mypy-extensions==1.0.0; python_version >= "3.5" and extra == "dev"
95
+ Requires-Dist: numpy==2.2.3; python_version >= "3.10" and extra == "dev"
96
+ Requires-Dist: outcome==1.3.0.post0; python_version >= "3.7" and extra == "dev"
97
+ Requires-Dist: packaging==24.2; python_version >= "3.8" and extra == "dev"
98
+ Requires-Dist: pandas==2.2.3; python_version >= "3.9" and extra == "dev"
99
+ Requires-Dist: parso==0.8.4; python_version >= "3.6" and extra == "dev"
100
+ Requires-Dist: pathspec==0.12.1; python_version >= "3.8" and extra == "dev"
101
+ Requires-Dist: pexpect==4.9.0; (sys_platform != "win32" and sys_platform != "emscripten") and extra == "dev"
102
+ Requires-Dist: pgeocode==0.4.0; python_version >= "3.8" and extra == "dev"
103
+ Requires-Dist: platformdirs==4.3.6; python_version >= "3.8" and extra == "dev"
104
+ Requires-Dist: pluggy==1.5.0; python_version >= "3.8" and extra == "dev"
105
+ Requires-Dist: prompt-toolkit==3.0.50; python_full_version >= "3.8.0" and extra == "dev"
106
+ Requires-Dist: ptyprocess==0.7.0; extra == "dev"
107
+ Requires-Dist: pure-eval==0.2.3; extra == "dev"
108
+ Requires-Dist: pygments==2.19.1; python_version >= "3.8" and extra == "dev"
109
+ Requires-Dist: pyhamcrest==2.0.2; python_version >= "3.5" and extra == "dev"
110
+ Requires-Dist: pyjwt==2.6.0; python_version >= "3.7" and extra == "dev"
111
+ Requires-Dist: pypdf==5.1.0; python_version >= "3.8" and extra == "dev"
112
+ Requires-Dist: pypng==0.20220715.0; extra == "dev"
113
+ Requires-Dist: pysocks==1.7.1; extra == "dev"
114
+ Requires-Dist: pytest==8.3.5; python_version >= "3.8" and extra == "dev"
115
+ Requires-Dist: pytest-cov==6.0.0; python_version >= "3.9" and extra == "dev"
116
+ Requires-Dist: pytest-django==4.8.0; python_version >= "3.8" and extra == "dev"
117
+ Requires-Dist: pytest-mock==3.14.0; python_version >= "3.8" and extra == "dev"
118
+ Requires-Dist: pytest-order==1.3.0; python_version >= "3.7" and extra == "dev"
119
+ Requires-Dist: pytest-xdist==3.6.1; python_version >= "3.8" and extra == "dev"
120
+ Requires-Dist: python-dateutil==2.9.0.post0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
121
+ Requires-Dist: pytz==2025.1; extra == "dev"
122
+ Requires-Dist: pyvirtualdisplay==3.0; extra == "dev"
123
+ Requires-Dist: qrcode==7.4.2; python_version >= "3.7" and extra == "dev"
124
+ Requires-Dist: rapid-router==7.4.1; extra == "dev"
125
+ Requires-Dist: requests==2.32.3; python_version >= "3.8" and extra == "dev"
126
+ Requires-Dist: responses==0.18.0; python_version >= "3.7" and extra == "dev"
127
+ Requires-Dist: selenium==4.29.0; python_version >= "3.9" and extra == "dev"
128
+ Requires-Dist: six==1.17.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
129
+ Requires-Dist: snapshottest==1.0.0a1; extra == "dev"
130
+ Requires-Dist: sniffio==1.3.1; python_version >= "3.7" and extra == "dev"
131
+ Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
132
+ Requires-Dist: sqlparse==0.5.3; python_version >= "3.8" and extra == "dev"
133
+ Requires-Dist: stack-data==0.6.3; extra == "dev"
134
+ Requires-Dist: tablib==3.7.0; python_version >= "3.9" and extra == "dev"
135
+ Requires-Dist: termcolor==2.5.0; python_version >= "3.9" and extra == "dev"
136
+ Requires-Dist: traitlets==5.14.3; python_version >= "3.8" and extra == "dev"
137
+ Requires-Dist: trio==0.29.0; python_version >= "3.9" and extra == "dev"
138
+ Requires-Dist: trio-websocket==0.12.2; python_version >= "3.8" and extra == "dev"
139
+ Requires-Dist: typing-extensions==4.12.2; python_version >= "3.8" and extra == "dev"
140
+ Requires-Dist: tzdata==2025.1; python_version >= "2" and extra == "dev"
141
+ Requires-Dist: urllib3==2.3.0; python_version >= "3.9" and extra == "dev"
142
+ Requires-Dist: wasmer==1.1.0; extra == "dev"
143
+ Requires-Dist: wasmer-compiler-cranelift==1.1.0; extra == "dev"
144
+ Requires-Dist: wcwidth==0.2.13; extra == "dev"
145
+ Requires-Dist: websocket-client==1.8.0; python_version >= "3.8" and extra == "dev"
146
+ Requires-Dist: wsproto==1.2.0; python_full_version >= "3.7.0" and extra == "dev"
147
+ Dynamic: classifier
148
+ Dynamic: description
149
+ Dynamic: description-content-type
150
+ Dynamic: provides-extra
151
+ Dynamic: requires-dist
152
+
153
+ # Code for Life Portal
154
+
155
+ [![Workflow Status](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml/badge.svg)](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml)
156
+ [![codecov](https://codecov.io/gh/ocadotechnology/codeforlife-portal/branch/master/graph/badge.svg)](https://codecov.io/gh/ocadotechnology/codeforlife-portal)
157
+
158
+ ## LICENCE
159
+ In accordance with the [Terms of Use](https://www.codeforlife.education/terms#terms)
160
+ of the Code for Life website, all copyright, trademarks, and other
161
+ intellectual property rights in and relating to Code for Life (including all
162
+ content of the Code for Life website, the Rapid Router application, the
163
+ Kurono application, related software (including any drawn and/or animated
164
+ avatars, whether or not such avatars have any modifications) and any other
165
+ games, applications or any other content that we make available from time to
166
+ time) are owned by Ocado Innovation Limited.
167
+
168
+ The source code of the Code for Life portal, the Rapid Router application
169
+ and the Kurono/aimmo application are [licensed under the GNU Affero General
170
+ Public License](https://github.com/ocadotechnology/codeforlife-workspace/blob/main/LICENSE.md).
171
+ All other assets including images, logos, sounds etc., are not covered by
172
+ this licence and no-one may copy, modify, distribute, show in public or
173
+ create any derivative work from these assets.
174
+
175
+ ## Code for Life
176
+
177
+ [Code for Life](https://www.codeforlife.education/) has been developed by Ocado Technology as a **free, open-source** project to inspire the next generation of computer scientists and to help teachers deliver the computing curriculum.
178
+
179
+ This repository hosts the source code of the [main website](https://www.codeforlife.education/), which includes the registration, log in, teacher and student dashboards, the teaching materials, etc.
180
+
181
+ We are open to contributors from anywhere around the world. Please read ahead if you'd like to get involved.
182
+
183
+ ## To get started
184
+
185
+ - [Developer Guide](https://docs.codeforlife.education/developer-guide)
186
+
187
+ - [Good First Issues](https://github.com/ocadotechnology/codeforlife-portal/contribute)
188
+
189
+ - [How to set up your work environment](https://docs.codeforlife.education/git/common-setup)
190
+
191
+ - [Testing](https://docs.codeforlife.education/git/testing)
@@ -1,5 +1,5 @@
1
1
  cfl_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- cfl_common/setup.py,sha256=_jt0eA-njT6AMjkEcpNxTXOlM8vdSjurM29Q0bOWXQI,978
2
+ cfl_common/setup.py,sha256=zjoRk8QkIeb4ZfRW6BeozmtX_6Fme913s9ntoUJrpBM,1720
3
3
  cfl_common/common/__init__.py,sha256=XlncBOpKp_gekbKH7Y_i6yu1qy5tJc3Y8sn8cDy-Vgk,48
4
4
  cfl_common/common/app_settings.py,sha256=Bw1DXkZpNIdwUJ-cIOjZnngH5_NbMXC0koW7NgQ0pKY,2495
5
5
  cfl_common/common/apps.py,sha256=49UXZ3bSkFKvIEOL4zM7y1sAhccQJyRtsoOg5XVd_8Y,129
@@ -106,18 +106,17 @@ deploy/static/apple-touch-icon.png,sha256=QjNQ8jDKI4BpfMMsrs2t3H0fXHa5KHV45fK_VS
106
106
  deploy/static/robots.txt,sha256=5cS4RITuQhbpNzvpk4AyDCXdlIBfmfCoBYRvCHY2VT8,24
107
107
  deploy/templates/deploy/csrf_failure.html,sha256=-pBRPn4Y7nUdYHGpTHCokT9Boi-isuwuivF8V2K1SgM,412
108
108
  example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
- example_project/manage.py,sha256=EUgybZlZ7xk2Zf2KCwBbK_z7gf7Ifqs0_bl4Kijhdgo,242
110
- example_project/portal_test_settings.py,sha256=mTyObCGkZEI0IQSZkqCjb1WZ7ebjQLwuiT_SykRnP6g,7315
111
- example_project/settings.py,sha256=pHQDI_A_yXV3u1efj689tc-vPI6nBFma4gMMPBzj0aI,5652
109
+ example_project/portal_test_settings.py,sha256=fSjkcEWgW1bQW7wTPpiWzytPfKnlzDnI9ET6InTXI98,7342
110
+ example_project/settings.py,sha256=b680hIHT1mtrvBjOQSli1AkDo0k3anZYHfsKhFheKlM,5684
112
111
  example_project/urls.py,sha256=FUTzHPlUS1O5kqMHjL5V4L552N2ln7uTDXcw9wjKUto,422
113
112
  example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
114
- portal/__init__.py,sha256=MrwcgcjFD253hhCUb3rvQPGreC9sFiXhfjgsNwRScbM,22
113
+ portal/__init__.py,sha256=Xix7wwydRWY_pO1keDB1yg_hS-RtTMRi-x5wQ2GzTl8,22
115
114
  portal/admin.py,sha256=RKJizTF6dPJKmGPZw7nZUM0X8jkiTjgyKhLQxtvHJ0I,6148
116
115
  portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
117
116
  portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
118
117
  portal/beta.py,sha256=0TCC-9_KZoM1nuzJ9FiuKR5n9JITdMYenHGQtRvn9UU,255
119
118
  portal/context_processors.py,sha256=1TrUZqnMqGa5f7ERph9EpBqojSMJvOrcpnJzTdeCLDI,133
120
- portal/handlers.py,sha256=gF99OfQrGcIGDnUyONhvylZNU8sl6XHYEurwu0fuiss,422
119
+ portal/handlers.py,sha256=TSaTIsEi-q8cwoYezw4NgVKyFy1UZkqREMipeSKA_KQ,421
121
120
  portal/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
121
  portal/urls.py,sha256=BWhRI-9rGEydbasI0UCtYYASqHbyUXokfepNWty7cQg,18218
123
122
  portal/wsgi.py,sha256=3yRcNxBQG30NhzrVi93bX-DrbXtsIQBc70HiW5wbOyE,401
@@ -529,27 +528,27 @@ portal/templatetags/headline_tags.py,sha256=1KHOjcVZZTLfWnxpFAUoHhIndd3RbuD1hby0
529
528
  portal/templatetags/hero_card_tags.py,sha256=nwTyPvjuaoaeG14ck8q1nbb-wSsTd6Grz3DOUCPEOMc,939
530
529
  portal/templatetags/table_tags.py,sha256=eU8kkBky_Ouq_61-6-YVu5_1zH941KRJD_Wvs_TMXqE,448
531
530
  portal/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
532
- portal/tests/base_test.py,sha256=fepj6X-y_a1jdXi80QA6KIDV703NCJYDAVOwL6Th2fA,2459
531
+ portal/tests/base_test.py,sha256=SxoX3UMZ8rTDHqKJFUDaUpTvPS2w7qUdOXviv6wtS1o,1649
533
532
  portal/tests/conftest.py,sha256=Rk4SOaFS22bFwNPRDOQHeNsm0We5BdEI52NTcfSlv1g,1297
534
533
  portal/tests/selenium_test_case.py,sha256=eWUF_5SqkI178bkay5SUDa06r0QTIKUUT8jTAhrdbmk,469
535
534
  portal/tests/test_2FA.py,sha256=0N4C9Ab3TvO9W__oQLCo-fLDH1Ho3CiGGsSg-2TiZUE,3597
536
535
  portal/tests/test_admin.py,sha256=AM2dgv8j9m4L-SDO-sMA9tQvQH9GwRBrlwRG9OgqtfI,1451
537
536
  portal/tests/test_api.py,sha256=Yo5s_nEGOoG35jA39yZ6nuDOUZvuCZ8o8o8XhZos61w,13819
538
537
  portal/tests/test_captcha_forms.py,sha256=Yn_VYO_6jbq6AeKeLcv-YFL1YwXZpU0C3y7SK8fRUm4,1033
539
- portal/tests/test_class.py,sha256=noXBXFNlV1Z5lryVH4IxMVDfh8Kb3g_GwXlu3GKPwCI,18089
538
+ portal/tests/test_class.py,sha256=KBDHL4JdjiifY1l3-TLxOVtaCHtZrpWXU5y7xGtrq7s,18124
540
539
  portal/tests/test_emails.py,sha256=pLr06j3uMBxP1raoZQWzUTBVFvsEDFtUh85J8OnqCwE,9238
541
540
  portal/tests/test_global_forms.py,sha256=GIm_oSN4VsfaO--E2SMRu8CwVraan0UBj-_LE_tu8w0,833
542
541
  portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
543
542
  portal/tests/test_independent_student.py,sha256=NrRjTEr6V4WXpCE74N8LYNVocvLSvddkjuo3dYpfAZc,27245
544
- portal/tests/test_invite_teacher.py,sha256=gUe1spFp60v3i6kMqGoNgJd0OlBEcwplPPNYLomTJS4,12269
543
+ portal/tests/test_invite_teacher.py,sha256=QKHWVzyd5OLpTOQ0j8p9ZF6DSCWPHcWHZwCpNTFG_6o,12320
545
544
  portal/tests/test_middleware.py,sha256=HV7AdgWyvgrtPyDMw3np5YDbUstFpKif7Qqw-e8gs5s,8258
546
545
  portal/tests/test_organisation.py,sha256=kCMUNzLN6EzaMUBcFkqXwnqLGgOuQxQWIHHt63nhqBs,7574
547
546
  portal/tests/test_partials.py,sha256=cSLNLjdsriROjPxkZlM3HKD3CSKDuKgpaDIdL3fPyBs,1794
548
547
  portal/tests/test_ratelimit.py,sha256=XWq1A9XgRrlcMHibGoJ0kc4gLc5U_u5UhKHjthxCfYA,19376
549
548
  portal/tests/test_school_student.py,sha256=bFZwY4twaFHQLp0cltMq8cLNDZGgCHTZBCZHK0JcV8s,8604
550
549
  portal/tests/test_security.py,sha256=FGrlRfnzi-Xx2_bn4fTZlYORKm7w_GhGkD3havvplwc,3239
551
- portal/tests/test_teacher.py,sha256=frtRdsjiwerj7KZ7jbLbSPlBgCspQ9FGFXBXy-m2gas,29351
552
- portal/tests/test_teacher_student.py,sha256=NWITbUw1kijqu3c8eRHLHJKaYQMOsOMvl7PAVx5QghI,21567
550
+ portal/tests/test_teacher.py,sha256=y4h5Bt7Xlglh9pylSl1jOOl5tl6rqHTewddBjB3RzRA,29171
551
+ portal/tests/test_teacher_student.py,sha256=4Di29Lsymbmh6WxZ0FYpqZ4fMN8NqNMTXXOsii1dfW4,21624
553
552
  portal/tests/test_views.py,sha256=Hv0rVOLmYivMqzJK1AQeRJ85YHTCRrlr12hcf1B83Bk,47566
554
553
  portal/tests/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
555
554
  portal/tests/migrations/test_migration_make_portaladmin_teacher.py,sha256=ekMRb6cU97oT0k9gCKW7IUB7oPuGmv4uWJCqInQN7x8,2589
@@ -604,7 +603,7 @@ portal/tests/snapshots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
604
603
  portal/tests/snapshots/snap_test_partials.py,sha256=ErYrQezzALwvP-vrOwOVyITG67WEUpcroSdYs414fek,2481
605
604
  portal/tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
606
605
  portal/tests/utils/classes.py,sha256=MEwGTKjPRYdWiVL1V6l28NpdvRUa06yRa6PPcHT6Trc,223
607
- portal/tests/utils/messages.py,sha256=LeYqCEU-ubEO1lrPSAaXF6WsM8M7C-ZIfVwwlbAFT28,1769
606
+ portal/tests/utils/messages.py,sha256=tUGzJzhZwvzmO9H3CPxpWUfi2aXTpAWfkDQ2u2T3LbA,1770
608
607
  portal/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
609
608
  portal/views/about.py,sha256=-muXy17UhxCSKkjnMAkSLXiCvT_pBPlf2ykTYr794dI,443
610
609
  portal/views/admin.py,sha256=4Xt3zEyQH7sUwQSrwuRtoCodWidjOzd7gJUwWU96pXY,957
@@ -634,8 +633,8 @@ portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
634
633
  portal/views/two_factor/core.py,sha256=Lk32z2SN2Pg0rRkK-N-LXMvXC1kKKsH3l692kiSDQ4E,964
635
634
  portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
636
635
  portal/views/two_factor/profile.py,sha256=SHSg_xHccE5PtD-OfuOkYhREYz_er4bj5ro1RjJ88Yw,393
637
- codeforlife_portal-8.4.4.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
638
- codeforlife_portal-8.4.4.dist-info/METADATA,sha256=Bcx9awmkGczRxQeEbFFXPhuMYgpgFhM2K8f2p43N96E,3077
639
- codeforlife_portal-8.4.4.dist-info/WHEEL,sha256=WDDPHYzpiOIm6GP1C2_8y8W6q16ICddAgOHlhTje9Qc,109
640
- codeforlife_portal-8.4.4.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
641
- codeforlife_portal-8.4.4.dist-info/RECORD,,
636
+ codeforlife_portal-8.4.6.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
637
+ codeforlife_portal-8.4.6.dist-info/METADATA,sha256=bTwPM2n2vOxrV1stY4xhlXCZjODVeWEYZq1A_pg-ONQ,12065
638
+ codeforlife_portal-8.4.6.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
639
+ codeforlife_portal-8.4.6.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
640
+ codeforlife_portal-8.4.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -6,19 +6,19 @@ from selenium import webdriver
6
6
 
7
7
  DEBUG = True
8
8
 
9
- headless_chrome_options = webdriver.ChromeOptions()
10
- headless_chrome_options.add_argument("--headless")
11
- headless_chrome_options.add_argument("--window-size=1920,1080")
12
- headless_chrome_options.add_argument("--start-maximized")
13
- headless_chrome_options.add_argument("--disable-gpu")
14
- headless_chrome_options.add_argument("--no-sandbox")
15
- headless_chrome_options.add_argument("--disable-extensions")
16
- headless_chrome_options.add_argument("--disable-dev-shm-usage")
9
+ headless_firefox_options = webdriver.FirefoxOptions()
10
+ headless_firefox_options.add_argument("--headless")
11
+ headless_firefox_options.add_argument("--window-size=1920,1080")
12
+ headless_firefox_options.add_argument("--start-maximized")
13
+ headless_firefox_options.add_argument("--disable-gpu")
14
+ headless_firefox_options.add_argument("--no-sandbox")
15
+ headless_firefox_options.add_argument("--disable-extensions")
16
+ headless_firefox_options.add_argument("--disable-dev-shm-usage")
17
17
 
18
18
  SELENIUM_WEBDRIVERS = {
19
- "default": {"callable": webdriver.Chrome, "args": (), "kwargs": {}},
20
- "firefox": {"callable": webdriver.Firefox, "args": (), "kwargs": {}},
21
- "chrome-headless": {"callable": webdriver.Chrome, "args": (), "kwargs": {"options": headless_chrome_options}},
19
+ "default": {"callable": webdriver.Firefox, "args": (), "kwargs": {}},
20
+ "chrome": {"callable": webdriver.Chrome, "args": (), "kwargs": {}},
21
+ "firefox-headless": {"callable": webdriver.Firefox, "args": (), "kwargs": {"options": headless_firefox_options}},
22
22
  }
23
23
 
24
24
  SELENIUM_WIDTHS = [1624]
@@ -74,7 +74,7 @@ STATIC_URL = "/static/"
74
74
  STATICFILES_DIRS = [os.path.join(BASE_DIR, "portal/static")]
75
75
  MEDIA_ROOT = os.path.join(STATIC_ROOT, "email_media/")
76
76
 
77
- WSGI_APPLICATION = "wsgi.application"
77
+ WSGI_APPLICATION = "example_project.wsgi.application"
78
78
 
79
79
  LOGIN_REDIRECT_URL = "/teach/dashboard/"
80
80
 
@@ -26,11 +26,11 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "portal/static")]
26
26
  MEDIA_ROOT = os.path.join(STATIC_ROOT, "email_media/")
27
27
  SECRET_KEY = "not-a-secret"
28
28
 
29
- ROOT_URLCONF = "urls"
29
+ ROOT_URLCONF = "example_project.urls"
30
30
 
31
31
  ALLOWED_HOSTS = ["*"]
32
32
 
33
- WSGI_APPLICATION = "wsgi.application"
33
+ WSGI_APPLICATION = "example_project.wsgi.application"
34
34
 
35
35
  LOGIN_REDIRECT_URL = "/teach/dashboard/"
36
36
 
portal/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "8.4.4"
1
+ __version__ = "8.4.6"
portal/handlers.py CHANGED
@@ -1,10 +1,9 @@
1
+ from common.utils import two_factor_cache_key
1
2
  from django.core.cache import cache
2
3
  from django.db.models.signals import post_save, pre_delete
3
4
  from django.dispatch import receiver
4
5
  from django_otp.models import Device
5
6
 
6
- from common.utils import two_factor_cache_key
7
-
8
7
 
9
8
  @receiver([post_save, pre_delete])
10
9
  def clear_two_factor_cache(sender, **kwargs):
portal/tests/base_test.py CHANGED
@@ -5,30 +5,9 @@ from django.contrib.sites.models import Site
5
5
  from django.urls import reverse
6
6
 
7
7
  from deploy import captcha
8
-
9
- # Uncomment to use FireFox
10
- # master_browser = webdriver.Firefox()
11
8
  from portal.tests.pageObjects.portal.home_page import HomePage
12
9
  from .selenium_test_case import SeleniumTestCase
13
10
 
14
- from selenium.webdriver.common.action_chains import ActionChains
15
- from selenium.webdriver.common.by import By
16
- from selenium.webdriver.support.ui import WebDriverWait
17
- from selenium.webdriver.support import expected_conditions as EC
18
-
19
-
20
- def button_click_handler(page, self, button_element):
21
- current_button = WebDriverWait(self.selenium, 20).until(EC.presence_of_element_located((By.ID, button_element)))
22
- ActionChains(self.selenium).move_to_element(current_button).click(current_button).perform()
23
-
24
-
25
- def click_buttons_by_id(page, self, button_ids):
26
- if isinstance(button_ids, str):
27
- button_click_handler(page, self, button_ids)
28
- else:
29
- for button_id in button_ids:
30
- button_click_handler(page, self, button_id)
31
-
32
11
 
33
12
  class BaseTest(SeleniumTestCase):
34
13
  @classmethod
@@ -1,5 +1,6 @@
1
1
  from __future__ import absolute_import
2
2
 
3
+ import time
3
4
  from datetime import datetime, timedelta
4
5
 
5
6
  from common.models import Class, DailyActivity, Teacher
@@ -550,6 +551,8 @@ class TestClassFrontend(BaseTest):
550
551
 
551
552
  page = page.create_class(class_name, False)
552
553
 
554
+ time.sleep(1)
555
+
553
556
  assert page.was_form_invalid(
554
557
  "form-create-class", "Class name may only contain letters, numbers, dashes, underscores, and spaces."
555
558
  )
@@ -16,7 +16,7 @@ from selenium.webdriver.common.by import By
16
16
  from selenium.webdriver.support import expected_conditions as EC
17
17
  from selenium.webdriver.support.wait import WebDriverWait
18
18
 
19
- from portal.tests.base_test import BaseTest, click_buttons_by_id
19
+ from portal.tests.base_test import BaseTest
20
20
 
21
21
  FADE_TIME = 0.9
22
22
  WAIT_TIME = 15
@@ -185,7 +185,7 @@ class TestTeacherInviteActions(BaseTest):
185
185
  field.send_keys(invite_data[key])
186
186
 
187
187
  # check if invite text for a user has been generated
188
- click_buttons_by_id(page, self, "invite_teacher_button")
188
+ page.browser.find_element(By.ID, "invite_teacher_button").click()
189
189
  banner = page.browser.find_element(By.ID, "messages")
190
190
  assert (
191
191
  f"You have invited {invite_data['teacher_first_name']} {invite_data['teacher_last_name']} to your school."
@@ -194,14 +194,17 @@ class TestTeacherInviteActions(BaseTest):
194
194
 
195
195
  # check if popup message appears and if the invite is changed to admin
196
196
  sleep(1) # this HAS to be there because of the animation :/
197
- click_buttons_by_id(page, self, ["make_admin_button_invite", "add_admin_button"])
197
+ page.browser.find_element(By.ID, "make_admin_button_invite").click()
198
+ sleep(1)
199
+ page.browser.find_element(By.ID, "add_admin_button").click()
200
+
198
201
  invite = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")[0]
199
202
  assert invite.invited_teacher_is_admin
200
203
  banner = page.browser.find_element(By.ID, "messages")
201
204
  assert "Administrator invite status has been given successfully" in banner.text
202
205
 
203
206
  # revoke admin
204
- click_buttons_by_id(page, self, "make_non_admin_button_invite")
207
+ page.browser.find_element(By.ID, "make_non_admin_button_invite").click()
205
208
 
206
209
  banner = page.browser.find_element(By.ID, "messages")
207
210
  assert "Administrator invite status has been revoked successfully" in banner.text
@@ -220,8 +223,7 @@ class TestTeacherInviteActions(BaseTest):
220
223
  for key in invite_data.keys():
221
224
  field = page.browser.find_element(By.NAME, key)
222
225
  field.send_keys(invite_data[key])
223
- invite_button = page.browser.find_element(By.NAME, "invite_teacher_button")
224
- invite_button.click()
226
+ page.browser.find_element(By.NAME, "invite_teacher_button").click()
225
227
 
226
228
  # check object was created
227
229
  invite_queryset = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")
@@ -251,7 +253,7 @@ class TestTeacherInviteActions(BaseTest):
251
253
  field = page.browser.find_element(By.NAME, key)
252
254
  field.send_keys(invite_data[key])
253
255
 
254
- click_buttons_by_id(page, self, "invite_teacher_button")
256
+ page.browser.find_element(By.ID, "invite_teacher_button").click()
255
257
 
256
258
  banner = page.browser.find_element(By.XPATH, '//*[@id="messages"]/div/div/div/div/div/p')
257
259
  assert (
@@ -260,7 +262,7 @@ class TestTeacherInviteActions(BaseTest):
260
262
  )
261
263
 
262
264
  # resend an invite
263
- click_buttons_by_id(page, self, "resend-invite")
265
+ page.browser.find_element(By.ID, "resend-invite").click()
264
266
 
265
267
  # check if invite was updated by 30 days (used 29 for rounding errors)
266
268
  new_invite_expiry = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")[0].expiry
@@ -33,9 +33,7 @@ from selenium.webdriver.support import expected_conditions as EC
33
33
  from selenium.webdriver.support.wait import WebDriverWait
34
34
 
35
35
  from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
36
- from portal.tests.base_test import click_buttons_by_id
37
36
  from portal.tests.test_invite_teacher import WAIT_TIME
38
-
39
37
  from .base_test import BaseTest
40
38
  from .pageObjects.portal.home_page import HomePage
41
39
  from .utils.messages import (
@@ -644,25 +642,13 @@ class TestTeacherFrontend(BaseTest):
644
642
  field = page.browser.find_element(By.NAME, key)
645
643
  field.send_keys(invite_data[key])
646
644
 
647
- invite_button = page.browser.find_element(
648
- By.NAME, "invite_teacher_button"
649
- )
650
- invite_button.click()
651
-
645
+ page.browser.find_element(By.NAME, "invite_teacher_button").click()
652
646
  # Once invite sent test the make admin button
653
- """
654
- make_admin_button = WebDriverWait(self.selenium, WAIT_TIME).until(
655
- EC.element_to_be_clickable((By.ID, "make_admin_button_invite"))
656
- )
657
- make_admin_button.click()
658
- """
659
-
660
- button_ids = [
661
- "make_admin_button_invite",
662
- "cancel_admin_popup_button",
663
- "delete-invite",
664
- ]
665
- click_buttons_by_id(page, self, button_ids)
647
+ page.browser.find_element(By.ID, "make_admin_button_invite").click()
648
+ time.sleep(1)
649
+ page.browser.find_element(By.ID, "cancel_admin_popup_button").click()
650
+ time.sleep(1)
651
+ page.browser.find_element(By.ID, "delete-invite").click()
666
652
 
667
653
  # Delete the invite and check if the form invite with
668
654
  # admin checked also makes a popup
@@ -673,17 +659,16 @@ class TestTeacherFrontend(BaseTest):
673
659
  checkbox = page.browser.find_element(By.NAME, "make_admin_ticked")
674
660
  checkbox.click()
675
661
 
676
- button_ids = ["invite_teacher_button", "cancel_admin_popup_button"]
677
-
678
- click_buttons_by_id(page, self, button_ids)
662
+ page.browser.find_element(By.ID, "invite_teacher_button").click()
663
+ time.sleep(1)
664
+ page.browser.find_element(By.ID, "cancel_admin_popup_button").click()
679
665
 
680
666
  # Non admin teacher joined - make admin should also make a popup
681
-
682
667
  join_teacher_to_organisation(joining_email, school.name)
683
668
 
684
669
  # refresh the page and scroll to the buttons
685
- page.browser.execute_script("location.reload()")
686
- click_buttons_by_id(page, self, "make_admin_button")
670
+ page.browser.find_element(By.CSS_SELECTOR, ".logo").click()
671
+ page.browser.find_element(By.ID, "make_admin_button").click()
687
672
 
688
673
  assert page.element_exists((By.CLASS_NAME, "popup-box__msg"))
689
674
 
@@ -795,6 +780,8 @@ class TestTeacherFrontend(BaseTest):
795
780
  .complete_setup()
796
781
  )
797
782
 
783
+ time.sleep(1)
784
+
798
785
  assert page.has_onboarding_complete_popup()
799
786
 
800
787
  def get_to_forgotten_password_page(self):
@@ -1,6 +1,7 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  import json
4
+ import time
4
5
  from unittest.mock import Mock, patch
5
6
 
6
7
  import pytest
@@ -24,7 +25,6 @@ from selenium.webdriver.common.alert import Alert
24
25
  from selenium.webdriver.common.by import By
25
26
 
26
27
  from portal.tests.pageObjects.portal.home_page import HomePage
27
-
28
28
  from .base_test import BaseTest
29
29
 
30
30
 
@@ -146,6 +146,8 @@ class TestTeacherStudentFrontend(BaseTest):
146
146
  .import_students_from_csv("test_students_names_no_name.csv")
147
147
  )
148
148
 
149
+ time.sleep(1)
150
+
149
151
  alert = Alert(page.browser)
150
152
  assert alert.text == "'Name' column not found in CSV file."
151
153
  alert.dismiss()
@@ -184,6 +186,8 @@ class TestTeacherStudentFrontend(BaseTest):
184
186
  .import_students_from_csv("test_students_names_no_name.csv")
185
187
  )
186
188
 
189
+ time.sleep(1)
190
+
187
191
  alert = Alert(page.browser)
188
192
  assert alert.text == "'Name' column not found in CSV file."
189
193
  alert.dismiss()
@@ -1,5 +1,6 @@
1
1
  from selenium.webdriver.common.by import By
2
2
 
3
+
3
4
  def is_message_showing(browser, message):
4
5
  return message in browser.find_element(By.ID, "messages").text
5
6
 
@@ -1,62 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: codeforlife-portal
3
- Version: 8.4.4
4
- Classifier: Programming Language :: Python
5
- Classifier: Programming Language :: Python :: 3.12
6
- Classifier: Framework :: Django
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE.md
9
- Requires-Dist: django-recaptcha==4.0.0
10
- Requires-Dist: pyyaml==6.0.2
11
- Requires-Dist: importlib-metadata==4.13.0
12
- Requires-Dist: reportlab==4.2.5
13
- Requires-Dist: django-formtools==2.5.1
14
- Requires-Dist: django-otp==1.5.4
15
- Requires-Dist: requests==2.32.2
16
- Requires-Dist: django-treebeard==4.7.1
17
- Requires-Dist: django-sekizai==4.1.0
18
- Requires-Dist: django-classy-tags==4.1.0
19
- Requires-Dist: phonenumbers==8.12.12
20
- Requires-Dist: django-ratelimit==3.0.1
21
- Requires-Dist: django-preventconcurrentlogins==0.8.2
22
- Requires-Dist: setuptools==74.0.0
23
-
24
- # Code for Life Portal
25
-
26
- [![Workflow Status](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml/badge.svg)](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml)
27
- [![codecov](https://codecov.io/gh/ocadotechnology/codeforlife-portal/branch/master/graph/badge.svg)](https://codecov.io/gh/ocadotechnology/codeforlife-portal)
28
-
29
- ## LICENCE
30
- In accordance with the [Terms of Use](https://www.codeforlife.education/terms#terms)
31
- of the Code for Life website, all copyright, trademarks, and other
32
- intellectual property rights in and relating to Code for Life (including all
33
- content of the Code for Life website, the Rapid Router application, the
34
- Kurono application, related software (including any drawn and/or animated
35
- avatars, whether or not such avatars have any modifications) and any other
36
- games, applications or any other content that we make available from time to
37
- time) are owned by Ocado Innovation Limited.
38
-
39
- The source code of the Code for Life portal, the Rapid Router application
40
- and the Kurono/aimmo application are [licensed under the GNU Affero General
41
- Public License](https://github.com/ocadotechnology/codeforlife-workspace/blob/main/LICENSE.md).
42
- All other assets including images, logos, sounds etc., are not covered by
43
- this licence and no-one may copy, modify, distribute, show in public or
44
- create any derivative work from these assets.
45
-
46
- ## Code for Life
47
-
48
- [Code for Life](https://www.codeforlife.education/) has been developed by Ocado Technology as a **free, open-source** project to inspire the next generation of computer scientists and to help teachers deliver the computing curriculum.
49
-
50
- This repository hosts the source code of the [main website](https://www.codeforlife.education/), which includes the registration, log in, teacher and student dashboards, the teaching materials, etc.
51
-
52
- We are open to contributors from anywhere around the world. Please read ahead if you'd like to get involved.
53
-
54
- ## To get started
55
-
56
- - [Developer Guide](https://docs.codeforlife.education/developer-guide)
57
-
58
- - [Good First Issues](https://github.com/ocadotechnology/codeforlife-portal/contribute)
59
-
60
- - [How to set up your work environment](https://docs.codeforlife.education/git/common-setup)
61
-
62
- - [Testing](https://docs.codeforlife.education/git/testing)
example_project/manage.py DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env python
2
- import os
3
- import sys
4
-
5
- if __name__ == "__main__":
6
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
7
-
8
- from django.core.management import execute_from_command_line
9
-
10
- execute_from_command_line(sys.argv)