Metadata-Version: 2.4
Name: codeforlife-portal
Version: 8.10.0
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.12
Classifier: Framework :: Django
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: absl-py==2.4.0; python_version >= "3.10"
Requires-Dist: amqp==5.3.1; python_version >= "3.6"
Requires-Dist: asgiref==3.11.1; python_version >= "3.9"
Requires-Dist: bazel-runfiles==1.9.0; python_version >= "3.7"
Requires-Dist: billiard==4.2.4; python_version >= "3.7"
Requires-Dist: boto3==1.36.14; python_version >= "3.8"
Requires-Dist: botocore==1.36.26; python_version >= "3.8"
Requires-Dist: cachetools==6.2.6; python_version >= "3.9"
Requires-Dist: celery[sqs]==5.4.0; python_version >= "3.8"
Requires-Dist: certifi==2026.2.25; python_version >= "3.7"
Requires-Dist: cffi==2.0.0; python_full_version >= "3.9" and platform_python_implementation != "PyPy"
Requires-Dist: cfl-common
Requires-Dist: charset-normalizer==3.4.7; python_version >= "3.7"
Requires-Dist: click==8.3.2; python_version >= "3.10"
Requires-Dist: click-didyoumean==0.3.1; python_full_version >= "3.6.2"
Requires-Dist: click-plugins==1.1.1.2
Requires-Dist: click-repl==0.3.0; python_version >= "3.6"
Requires-Dist: codeforlife==0.32.6; python_version == "3.12"
Requires-Dist: cryptography==46.0.7; python_version >= "3.8" and python_full_version not in "3.9.0, 3.9.1"
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
Requires-Dist: django==5.2.13; python_version >= "3.10"
Requires-Dist: django-classy-tags==4.1.0; python_version >= "3.8"
Requires-Dist: django-constance==4.3.4; python_version >= "3.8"
Requires-Dist: django-cors-headers==4.7.0; python_version >= "3.9"
Requires-Dist: django-countries==7.6.1
Requires-Dist: django-csp==3.8
Requires-Dist: django-filter==25.1; python_version >= "3.9"
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
Requires-Dist: django-otp==1.7.0; python_version >= "3.8"
Requires-Dist: django-phonenumber-field==8.4.0; python_version >= "3.10"
Requires-Dist: django-pipeline==4.1.0; python_version >= "3.9"
Requires-Dist: django-preventconcurrentlogins==0.8.2
Requires-Dist: django-ratelimit==3.0.1; python_version >= "3.4"
Requires-Dist: django-recaptcha==4.1.0
Requires-Dist: django-sekizai==4.1.0; python_version >= "3.8"
Requires-Dist: django-storages[s3]==1.14.6; python_version >= "3.7"
Requires-Dist: django-treebeard==4.8.0; python_version >= "3.10"
Requires-Dist: django-two-factor-auth==1.18.1; python_version >= "3.9"
Requires-Dist: djangorestframework==3.16.1; python_version >= "3.9"
Requires-Dist: google-api-core[grpc]==2.30.3; python_version >= "3.9"
Requires-Dist: google-auth==2.48.0; python_version >= "3.8"
Requires-Dist: google-cloud-bigquery==3.38.0; python_version >= "3.9"
Requires-Dist: google-cloud-core==2.5.1; python_version >= "3.9"
Requires-Dist: google-cloud-kms==3.12.0; python_version >= "3.7"
Requires-Dist: google-crc32c==1.8.0; python_version >= "3.9"
Requires-Dist: google-resumable-media==2.8.2; python_version >= "3.9"
Requires-Dist: googleapis-common-protos[grpc]==1.74.0; python_version >= "3.9"
Requires-Dist: grpc-google-iam-v1==0.14.4; python_version >= "3.9"
Requires-Dist: grpcio==1.80.0; python_version >= "3.9"
Requires-Dist: grpcio-status==1.80.0; python_version >= "3.9"
Requires-Dist: gunicorn==23.0.0; python_version >= "3.7"
Requires-Dist: h11==0.16.0; python_version >= "3.8"
Requires-Dist: hiredis==3.3.1; python_version >= "3.8"
Requires-Dist: idna==3.11; python_version >= "3.8"
Requires-Dist: importlib-metadata==4.13.0; python_version >= "3.7"
Requires-Dist: jmespath==1.1.0; python_version >= "3.9"
Requires-Dist: kombu[sqs]==5.6.2; python_version >= "3.9"
Requires-Dist: libsass==0.23.0; python_version >= "3.8"
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
Requires-Dist: numpy==2.4.4; python_version >= "3.11"
Requires-Dist: packaging==26.0; python_version >= "3.8"
Requires-Dist: pandas==3.0.2; python_version >= "3.11"
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
Requires-Dist: phonenumbers==8.12.12
Requires-Dist: pillow==12.2.0; python_version >= "3.10"
Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8"
Requires-Dist: proto-plus==1.27.2; python_version >= "3.9"
Requires-Dist: protobuf==6.33.6; python_version >= "3.9"
Requires-Dist: psutil==7.0.0; python_version >= "3.6"
Requires-Dist: psycopg2-binary==2.9.9; python_version >= "3.7"
Requires-Dist: pyasn1==0.6.3; python_version >= "3.8"
Requires-Dist: pyasn1-modules==0.4.2; python_version >= "3.8"
Requires-Dist: pycparser==3.0; implementation_name != "PyPy"
Requires-Dist: pycurl==7.45.7
Requires-Dist: pyjwt==2.12.1; python_version >= "3.9"
Requires-Dist: pyotp==2.9.0; python_version >= "3.7"
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"
Requires-Dist: python-dotenv==1.0.1; python_version >= "3.8"
Requires-Dist: pyyaml==6.0.2; python_version >= "3.8"
Requires-Dist: qrcode==8.2; python_version >= "3.9" and python_version < "4.0"
Requires-Dist: redis[hiredis]==5.2.1; python_version >= "3.8"
Requires-Dist: regex==2024.11.6; python_version >= "3.8"
Requires-Dist: reportlab==4.4.2; python_version >= "3.7" and python_version < "4"
Requires-Dist: requests==2.33.1; python_version >= "3.10"
Requires-Dist: requests-toolbelt==1.0.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
Requires-Dist: rsa==4.9.1; python_version >= "3.6" and python_version < "4"
Requires-Dist: s3transfer==0.11.3; python_version >= "3.8"
Requires-Dist: setuptools==82.0.1; python_version >= "3.9"
Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
Requires-Dist: sqlparse==0.5.5; python_version >= "3.8"
Requires-Dist: tablib==3.7.0; python_version >= "3.9"
Requires-Dist: tink[gcpkms]==1.13.0; python_version >= "3.9"
Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9"
Requires-Dist: tzdata==2026.1; python_version >= "2"
Requires-Dist: urllib3==2.6.3
Requires-Dist: uvicorn==0.44.0; python_version >= "3.10"
Requires-Dist: uvicorn-worker==0.2.0; python_version >= "3.8"
Requires-Dist: vine==5.1.0; python_version >= "3.6"
Requires-Dist: wcwidth==0.6.0; python_version >= "3.8"
Requires-Dist: wheel==0.46.3; python_version >= "3.9"
Requires-Dist: whitenoise==6.9.0; python_version >= "3.9"
Requires-Dist: zipp==3.23.0; python_version >= "3.9"
Provides-Extra: dev
Requires-Dist: absl-py==2.4.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: amqp==5.3.1; python_version >= "3.6" and extra == "dev"
Requires-Dist: asgiref==3.11.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: asttokens==3.0.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: attrs==26.1.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: bazel-runfiles==1.9.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: billiard==4.2.4; python_version >= "3.7" and extra == "dev"
Requires-Dist: black==26.3.1; python_version >= "3.10" and extra == "dev"
Requires-Dist: boto3==1.36.14; python_version >= "3.8" and extra == "dev"
Requires-Dist: botocore==1.36.26; python_version >= "3.8" and extra == "dev"
Requires-Dist: cachetools==6.2.6; python_version >= "3.9" and extra == "dev"
Requires-Dist: celery[sqs]==5.4.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: certifi==2026.2.25; python_version >= "3.7" and extra == "dev"
Requires-Dist: cffi==2.0.0; (python_full_version >= "3.9" and platform_python_implementation != "PyPy") and extra == "dev"
Requires-Dist: cfl-common; extra == "dev"
Requires-Dist: charset-normalizer==3.4.7; python_version >= "3.7" and extra == "dev"
Requires-Dist: click==8.3.2; python_version >= "3.10" and extra == "dev"
Requires-Dist: click-didyoumean==0.3.1; python_full_version >= "3.6.2" and extra == "dev"
Requires-Dist: click-plugins==1.1.1.2; extra == "dev"
Requires-Dist: click-repl==0.3.0; python_version >= "3.6" and extra == "dev"
Requires-Dist: codeforlife==0.32.6; python_version == "3.12" and extra == "dev"
Requires-Dist: coverage[toml]==7.13.5; python_version >= "3.10" and extra == "dev"
Requires-Dist: cryptography==46.0.7; (python_version >= "3.8" and python_full_version not in "3.9.0, 3.9.1") and extra == "dev"
Requires-Dist: decorator==5.2.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7" and extra == "dev"
Requires-Dist: django==5.2.13; python_version >= "3.10" and extra == "dev"
Requires-Dist: django-cors-headers==4.7.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: django-countries==7.6.1; extra == "dev"
Requires-Dist: django-csp==3.8; extra == "dev"
Requires-Dist: django-filter==25.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: django-otp==1.7.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: django-phonenumber-field==8.4.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: django-pipeline==4.1.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: django-reverse-js==0.1.8; python_version >= "3.10" and extra == "dev"
Requires-Dist: django-selenium-clean==1.0.1; extra == "dev"
Requires-Dist: django-storages[s3]==1.14.6; python_version >= "3.7" and extra == "dev"
Requires-Dist: django-test-migrations==1.4.0; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
Requires-Dist: django-two-factor-auth==1.18.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: djangorestframework==3.16.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: execnet==2.1.2; python_version >= "3.8" and extra == "dev"
Requires-Dist: executing==2.2.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: fastdiff==0.3.0; extra == "dev"
Requires-Dist: google-api-core[grpc]==2.30.3; python_version >= "3.9" and extra == "dev"
Requires-Dist: google-auth==2.48.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: google-cloud-bigquery==3.38.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: google-cloud-core==2.5.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: google-cloud-kms==3.12.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: google-crc32c==1.8.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: google-resumable-media==2.8.2; python_version >= "3.9" and extra == "dev"
Requires-Dist: googleapis-common-protos[grpc]==1.74.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: grpc-google-iam-v1==0.14.4; python_version >= "3.9" and extra == "dev"
Requires-Dist: grpcio==1.80.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: grpcio-status==1.80.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: gunicorn==23.0.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: h11==0.16.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: hiredis==3.3.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: idna==3.11; python_version >= "3.8" and extra == "dev"
Requires-Dist: iniconfig==2.3.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: ipython==9.11.0; python_version >= "3.12" and extra == "dev"
Requires-Dist: ipython-pygments-lexers==1.1.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: isort==8.0.1; python_full_version >= "3.10.0" and extra == "dev"
Requires-Dist: jedi==0.19.2; python_version >= "3.6" and extra == "dev"
Requires-Dist: jmespath==1.1.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: kombu[sqs]==5.6.2; python_version >= "3.9" and extra == "dev"
Requires-Dist: libsass==0.23.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: matplotlib-inline==0.2.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5" and extra == "dev"
Requires-Dist: mypy-extensions==1.1.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: numpy==2.4.4; python_version >= "3.11" and extra == "dev"
Requires-Dist: outcome==1.3.0.post0; python_version >= "3.7" and extra == "dev"
Requires-Dist: packaging==26.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: pandas==3.0.2; python_version >= "3.11" and extra == "dev"
Requires-Dist: parso==0.8.6; python_version >= "3.6" and extra == "dev"
Requires-Dist: pathspec==1.0.4; python_version >= "3.9" and extra == "dev"
Requires-Dist: pexpect==4.9.0; (sys_platform != "win32" and sys_platform != "emscripten") and extra == "dev"
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: platformdirs==4.9.6; python_version >= "3.10" and extra == "dev"
Requires-Dist: pluggy==1.6.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8" and extra == "dev"
Requires-Dist: proto-plus==1.27.2; python_version >= "3.9" and extra == "dev"
Requires-Dist: protobuf==6.33.6; python_version >= "3.9" and extra == "dev"
Requires-Dist: psutil==7.0.0; python_version >= "3.6" and extra == "dev"
Requires-Dist: psycopg2-binary==2.9.9; python_version >= "3.7" and extra == "dev"
Requires-Dist: ptyprocess==0.7.0; extra == "dev"
Requires-Dist: pure-eval==0.2.3; extra == "dev"
Requires-Dist: pyasn1==0.6.3; python_version >= "3.8" and extra == "dev"
Requires-Dist: pyasn1-modules==0.4.2; python_version >= "3.8" and extra == "dev"
Requires-Dist: pycparser==3.0; implementation_name != "PyPy" and extra == "dev"
Requires-Dist: pycurl==7.45.7; extra == "dev"
Requires-Dist: pygments==2.19.2; python_version >= "3.8" and extra == "dev"
Requires-Dist: pyhamcrest==2.0.2; python_version >= "3.5" and extra == "dev"
Requires-Dist: pyjwt==2.12.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: pyotp==2.9.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: pypdf==5.1.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: pysocks==1.7.1; extra == "dev"
Requires-Dist: pytest==8.4.2; python_version >= "3.9" and extra == "dev"
Requires-Dist: pytest-cov==7.1.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: pytest-django==4.11.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: pytest-mock==3.15.1; python_version >= "3.9" and extra == "dev"
Requires-Dist: pytest-order==1.3.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: pytest-xdist==3.8.0; python_version >= "3.9" and extra == "dev"
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"
Requires-Dist: python-dotenv==1.0.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: pytokens==0.4.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: pyvirtualdisplay==3.0; extra == "dev"
Requires-Dist: qrcode==8.2; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
Requires-Dist: rapid-router==7.7.2; extra == "dev"
Requires-Dist: redis[hiredis]==5.2.1; python_version >= "3.8" and extra == "dev"
Requires-Dist: regex==2024.11.6; python_version >= "3.8" and extra == "dev"
Requires-Dist: requests==2.33.1; python_version >= "3.10" and extra == "dev"
Requires-Dist: responses==0.18.0; python_version >= "3.7" and extra == "dev"
Requires-Dist: rsa==4.9.1; (python_version >= "3.6" and python_version < "4") and extra == "dev"
Requires-Dist: s3transfer==0.11.3; python_version >= "3.8" and extra == "dev"
Requires-Dist: selenium==4.29.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: setuptools==82.0.1; python_version >= "3.9" and extra == "dev"
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"
Requires-Dist: snapshottest==1.0.0a1; extra == "dev"
Requires-Dist: sniffio==1.3.1; python_version >= "3.7" and extra == "dev"
Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
Requires-Dist: sqlparse==0.5.5; python_version >= "3.8" and extra == "dev"
Requires-Dist: stack-data==0.6.3; extra == "dev"
Requires-Dist: tablib==3.7.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: termcolor==3.3.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: tink[gcpkms]==1.13.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: traitlets==5.14.3; python_version >= "3.8" and extra == "dev"
Requires-Dist: trio==0.33.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: trio-websocket==0.12.2; python_version >= "3.8" and extra == "dev"
Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: tzdata==2026.1; python_version >= "2" and extra == "dev"
Requires-Dist: urllib3==2.6.3; extra == "dev"
Requires-Dist: uvicorn==0.44.0; python_version >= "3.10" and extra == "dev"
Requires-Dist: uvicorn-worker==0.2.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: vine==5.1.0; python_version >= "3.6" and extra == "dev"
Requires-Dist: wasmer==1.1.0; extra == "dev"
Requires-Dist: wasmer-compiler-cranelift==1.1.0; extra == "dev"
Requires-Dist: wcwidth==0.6.0; python_version >= "3.8" and extra == "dev"
Requires-Dist: websocket-client==1.9.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: wheel==0.46.3; python_version >= "3.9" and extra == "dev"
Requires-Dist: wsproto==1.3.2; python_version >= "3.10" and extra == "dev"
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist

# Code for Life Portal

[![Workflow Status](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml/badge.svg)](https://github.com/ocadotechnology/codeforlife-portal/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/ocadotechnology/codeforlife-portal/branch/master/graph/badge.svg)](https://codecov.io/gh/ocadotechnology/codeforlife-portal)

## LICENCE
In accordance with the [Terms of Use](https://www.codeforlife.education/terms#terms)
of the Code for Life website, all copyright, trademarks, and other
intellectual property rights in and relating to Code for Life (including all
content of the Code for Life website, the Rapid Router application, the
Kurono application, related software (including any drawn and/or animated
avatars, whether or not such avatars have any modifications) and any other
games, applications or any other content that we make available from time to
time) are owned by Ocado Innovation Limited.

The source code of the Code for Life portal, the Rapid Router application
and the Kurono/aimmo application are [licensed under the GNU Affero General
Public License](https://github.com/ocadotechnology/codeforlife-workspace/blob/main/LICENSE.md).
All other assets including images, logos, sounds etc., are not covered by
this licence and no-one may copy, modify, distribute, show in public or
create any derivative work from these assets.

## Code for Life

[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.

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.

We are open to contributors from anywhere around the world. Please read ahead if you'd like to get involved.

## To get started

- [Developer Guide](https://docs.codeforlife.education/developer-guide)

- [Good First Issues](https://github.com/ocadotechnology/codeforlife-portal/contribute)

- [How to set up your work environment](https://docs.codeforlife.education/git/common-setup)

- [Testing](https://docs.codeforlife.education/git/testing)
